@bscotch/gcdata
Advanced tools
Comparing version 0.20.1 to 0.21.0
@@ -1,4 +0,4 @@ | ||
export { parseStringifiedStoryline, updateChangesFromParsedStoryline, } from './cl2.comfort.parse.js'; | ||
export { stringifyStoryline } from './cl2.comfort.stringify.js'; | ||
export { isComfortMote as isStorylineMote, listComforts as listStorylines, comfortSchemaId as storylineSchemaId, type ComfortData as StorylineData, type ComfortMote as StorylineMote, type StorylineUpdateResult, } from './cl2.comfort.types.js'; | ||
export { parseStringifiedComfort, updateChangesFromParsedComfort, } from './cl2.comfort.parse.js'; | ||
export { stringifyComfort } from './cl2.comfort.stringify.js'; | ||
export { isComfortMote, listComforts, type ComfortUpdateResult, } from './cl2.comfort.types.js'; | ||
//# sourceMappingURL=cl2.comfort.d.ts.map |
@@ -1,4 +0,4 @@ | ||
export { parseStringifiedStoryline, updateChangesFromParsedStoryline, } from './cl2.comfort.parse.js'; | ||
export { stringifyStoryline } from './cl2.comfort.stringify.js'; | ||
export { isComfortMote as isStorylineMote, listComforts as listStorylines, comfortSchemaId as storylineSchemaId, } from './cl2.comfort.types.js'; | ||
export { parseStringifiedComfort, updateChangesFromParsedComfort, } from './cl2.comfort.parse.js'; | ||
export { stringifyComfort } from './cl2.comfort.stringify.js'; | ||
export { isComfortMote, listComforts, } from './cl2.comfort.types.js'; | ||
//# sourceMappingURL=cl2.comfort.js.map |
import type { GameChanger } from './GameChanger.js'; | ||
import { type StorylineUpdateResult } from './cl2.storyline.types.js'; | ||
export declare function parseStringifiedStoryline(text: string, packed: GameChanger, options?: { | ||
import { type ComfortUpdateResult } from './cl2.comfort.types.js'; | ||
export declare function parseStringifiedComfort(text: string, packed: GameChanger, options?: { | ||
checkSpelling?: boolean; | ||
}): StorylineUpdateResult; | ||
export declare function updateChangesFromParsedStoryline(parsed: StorylineUpdateResult['parsed'], moteId: string, packed: GameChanger): Promise<void>; | ||
}): ComfortUpdateResult; | ||
export declare function updateChangesFromParsedComfort(parsed: ComfortUpdateResult['parsed'], moteId: string, packed: GameChanger): Promise<void>; | ||
//# sourceMappingURL=cl2.comfort.parse.d.ts.map |
import { assert } from './assert.js'; | ||
import { getStagingOptions } from './cl2.quest.utils.js'; | ||
import { arrayTagPattern, getStorylineMote, getStorylineSchema, lineIsArrayItem, linePatterns, parseIfMatch, storylineSchemaId, } from './cl2.storyline.types.js'; | ||
import { bsArrayToArray, createBsArrayKey, updateBsArrayOrder, } from './helpers.js'; | ||
import { checkWords, includes } from './util.js'; | ||
export function parseStringifiedStoryline(text, packed, options = {}) { | ||
import { getComfortSchema, linePatterns, } from './cl2.comfort.types.js'; | ||
import { isCommentLine, isStageLine, prepareParserHelpers, updateWipChangesFromParsed, } from './cl2.shared.parse.js'; | ||
import { comfortSchemaId } from './cl2.shared.types.js'; | ||
export function parseStringifiedComfort(text, packed, options = {}) { | ||
const result = { | ||
@@ -17,120 +16,26 @@ diagnostics: [], | ||
}; | ||
const availableGlobalLabels = new Set([ | ||
'Name', | ||
'Description', | ||
'Stage', | ||
]); | ||
const stagingOptions = getStagingOptions(packed.working); | ||
/** Terms from the glossary for use in autocompletes */ | ||
const glossaryTerms = (packed.glossary?.relevantTerms() || []).map((t) => t.text); | ||
const checkSpelling = (item) => { | ||
if (!item || !options.checkSpelling) | ||
return; | ||
result.words.push(...checkWords(item, packed.glossary)); | ||
}; | ||
const lines = text.split(/(\r?\n)/g); | ||
let index = 0; | ||
let lineNumber = 0; | ||
for (const line of lines) { | ||
const helpers = prepareParserHelpers(text, packed, { | ||
...options, | ||
globalLabels: new Set([ | ||
'Name', | ||
'Description', | ||
'Unlocked Description', | ||
'Stage', | ||
]), | ||
}, result); | ||
for (const line of helpers.lines) { | ||
const trace = []; | ||
try { | ||
// Is this just a newline? | ||
if (line.match(/\r?\n/)) { | ||
// Then we just need to increment the index | ||
index += line.length; | ||
lineNumber++; | ||
continue; | ||
} | ||
const lineRange = { | ||
start: { | ||
index, | ||
line: lineNumber, | ||
character: 0, | ||
}, | ||
end: { | ||
index: index + line.length, | ||
line: lineNumber, | ||
character: line.length, | ||
}, | ||
}; | ||
// Is this just a blank line? | ||
if (!line) { | ||
// Add global autocompletes | ||
result.completions.push({ | ||
type: 'labels', | ||
start: lineRange.start, | ||
end: lineRange.end, | ||
options: availableGlobalLabels, | ||
}); | ||
continue; | ||
} | ||
const lineRange = helpers.currentLineRange; | ||
// Find the first matching pattern and pull the values from it. | ||
let parsedLine = null; | ||
for (const pattern of linePatterns) { | ||
parsedLine = parseIfMatch(pattern, line, lineRange.start); | ||
if (parsedLine) | ||
break; | ||
} | ||
if (!parsedLine) { | ||
// Then this is likely the result of uncommenting something | ||
// that was commented out, resulting in a line that starts with | ||
// the comment's array tag. Provide a deletion edit! | ||
parsedLine = parseIfMatch(`^${arrayTagPattern} +(?<text>.*)$`, line, lineRange.start); | ||
if (parsedLine) { | ||
result.edits.push({ | ||
start: lineRange.start, | ||
end: lineRange.end, | ||
newText: parsedLine.text.value, | ||
}); | ||
} | ||
else { | ||
result.diagnostics.push({ | ||
message: `Unfamiliar syntax: ${line}`, | ||
...lineRange, | ||
}); | ||
} | ||
index += line.length; | ||
const parsedLine = helpers.parseCurrentLine(linePatterns); | ||
if (!parsedLine) | ||
continue; | ||
} | ||
// Ensure the array tag. It goes right after the label or indicator. | ||
if (!parsedLine.arrayTag?.value && lineIsArrayItem(line)) { | ||
const arrayTag = createBsArrayKey(); | ||
const start = parsedLine.indicator?.end || parsedLine.label?.end; | ||
result.edits.push({ | ||
start, | ||
end: start, | ||
newText: `#${arrayTag}`, | ||
}); | ||
parsedLine.arrayTag = { | ||
start, | ||
end: start, | ||
value: arrayTag, | ||
}; | ||
} | ||
// If this has a label, remove it from the list of available labels | ||
if (parsedLine.label?.value && | ||
availableGlobalLabels.has(parsedLine.label.value)) { | ||
availableGlobalLabels.delete(parsedLine.label.value); | ||
} | ||
// If this has a text section, provide glossary autocompletes | ||
if ('text' in parsedLine) { | ||
const start = parsedLine.text.start; | ||
const end = parsedLine.text.end; | ||
result.completions.push({ | ||
type: 'glossary', | ||
start, | ||
end, | ||
options: glossaryTerms, | ||
}); | ||
} | ||
// Work through each line type to add diagnostics and completions | ||
const labelLower = parsedLine.label?.value?.toLowerCase(); | ||
const indicator = parsedLine.indicator?.value; | ||
if (indicator === '//') { | ||
// Then this is a comment/note | ||
result.parsed.comments.push({ | ||
id: parsedLine.arrayTag?.value?.trim(), | ||
text: parsedLine.text?.value?.trim(), | ||
}); | ||
checkSpelling(parsedLine.text); | ||
if (isCommentLine(parsedLine)) { | ||
helpers.addComment(parsedLine); | ||
} | ||
@@ -148,23 +53,11 @@ else if (labelLower === 'name') { | ||
result.parsed.description = parsedLine.text?.value?.trim(); | ||
checkSpelling(parsedLine.text); | ||
helpers.checkSpelling(parsedLine.text); | ||
} | ||
else if (labelLower === 'stage') { | ||
const stage = parsedLine.text?.value?.trim(); | ||
if (includes(stagingOptions, stage)) { | ||
result.parsed.stage = stage; | ||
} | ||
else { | ||
result.diagnostics.push({ | ||
message: `Stage must be one of: ${stagingOptions.join(', ')}`, | ||
...lineRange, | ||
}); | ||
// Provide autocomplete options | ||
result.completions.push({ | ||
type: 'stages', | ||
options: stagingOptions, | ||
start: parsedLine.labelGroup.end, | ||
end: lineRange.end, | ||
}); | ||
} | ||
else if (labelLower === 'unlocked description') { | ||
result.parsed.unlockedDescription = parsedLine.text?.value?.trim(); | ||
helpers.checkSpelling(parsedLine.text); | ||
} | ||
else if (isStageLine(parsedLine)) { | ||
helpers.addStage(parsedLine); | ||
} | ||
else { | ||
@@ -184,7 +77,7 @@ // Then we're in an error state on this line! | ||
} | ||
index += line.length; | ||
helpers.index += line.length; | ||
} | ||
return result; | ||
} | ||
export async function updateChangesFromParsedStoryline(parsed, moteId, packed) { | ||
export async function updateChangesFromParsedComfort(parsed, moteId, packed) { | ||
const _traceLogs = []; | ||
@@ -197,6 +90,4 @@ const trace = (log) => _traceLogs.push(log); | ||
packed.clearMoteChanges(moteId); | ||
const storylineMoteBase = getStorylineMote(packed.base, moteId); | ||
const storylineMoteWorking = getStorylineMote(packed.working, moteId); | ||
const schema = getStorylineSchema(packed.working); | ||
assert(schema, `${storylineSchemaId} schema not found in working copy`); | ||
const schema = getComfortSchema(packed.working); | ||
assert(schema, `${comfortSchemaId} schema not found in working copy`); | ||
assert(schema.name, 'Quest mote must have a name pointer'); | ||
@@ -208,43 +99,4 @@ const updateMote = (path, value) => { | ||
updateMote('data/description/text', parsed.description); | ||
if (parsed.stage) { | ||
updateMote('data/wip/staging', parsed.stage); | ||
} | ||
else if (storylineMoteWorking?.data.wip) { | ||
updateMote('data/wip/staging', null); | ||
} | ||
const parsedComments = parsed.comments.filter((c) => !!c.text); | ||
//#region COMMENTS | ||
// Add/Update COMMENTS | ||
trace(`Updating comments`); | ||
for (const comment of parsedComments) { | ||
trace(`Updating comment ${comment.id} with text "${comment.text}"`); | ||
updateMote(`data/wip/notes/${comment.id}/element/text`, comment.text); | ||
} | ||
// Remove deleted comments | ||
for (const existingComment of bsArrayToArray(storylineMoteBase?.data.wip?.notes || {})) { | ||
if (!parsedComments.find((c) => c.id === existingComment.id)) { | ||
trace(`Deleting comment ${existingComment.id}`); | ||
updateMote(`data/wip/notes/${existingComment.id}`, null); | ||
} | ||
} | ||
// Get the BASE order of the comments (if any) and use those | ||
// as the starting point for an up to date order. | ||
const comments = parsedComments.map((c) => { | ||
// Look up the base comment | ||
let comment = storylineMoteBase?.data.wip?.notes?.[c.id]; | ||
if (!comment) { | ||
comment = storylineMoteWorking?.data.wip?.notes?.[c.id]; | ||
// @ts-expect-error - order is a required field, but it'll be re-added | ||
delete comment?.order; | ||
} | ||
assert(comment, `Comment ${c.id} not found in base or working mote`); | ||
return { ...comment, id: c.id }; | ||
}); | ||
trace('Updating comment order'); | ||
updateBsArrayOrder(comments); | ||
comments.forEach((comment) => { | ||
trace(`Updating comment ${comment.id} order to ${comment.order}`); | ||
updateMote(`data/wip/notes/${comment.id}/order`, comment.order); | ||
}); | ||
//#endregion | ||
updateMote('data/unlocked_description/text', parsed.unlockedDescription); | ||
updateWipChangesFromParsed(parsed, moteId, packed, trace); | ||
trace(`Writing changes`); | ||
@@ -251,0 +103,0 @@ await packed.writeChanges(); |
@@ -1,3 +0,3 @@ | ||
export type StorylineMoteDataPointer = `data/${StorylineMotePointer}`; | ||
export type StorylineMotePointer = `` | `description/description` | `description/skip` | `description/text` | `description` | `icon` | `name/description` | `name/skip` | `name/text` | `name` | `order` | `wip/notes/${string}/element/author` | `wip/notes/${string}/element/text` | `wip/notes/${string}/element/timestamp` | `wip/notes/${string}/element` | `wip/notes/${string}/order` | `wip/notes/${string}` | `wip/notes` | `wip/staging` | `wip`; | ||
export type ComfortMoteDataPointer = `data/${ComfortMotePointer}`; | ||
export type ComfortMotePointer = `` | `artisan_id` | `description/description` | `description/skip` | `description/text` | `description` | `ignore_in_progression_map` | `name/description` | `name/skip` | `name/text` | `name` | `order` | `priors/${string}/element` | `priors/${string}/order` | `priors/${string}` | `priors` | `uicon` | `unlocked_by` | `unlocked_description/description` | `unlocked_description/skip` | `unlocked_description/text` | `unlocked_description` | `wip/notes/${string}/element/author` | `wip/notes/${string}/element/text` | `wip/notes/${string}/element/timestamp` | `wip/notes/${string}/element` | `wip/notes/${string}/order` | `wip/notes/${string}` | `wip/notes` | `wip/staging` | `wip`; | ||
//# sourceMappingURL=cl2.comfort.pointers.d.ts.map |
@@ -0,4 +1,4 @@ | ||
import { ComfortMote } from './cl2.shared.types.js'; | ||
import type { GameChanger } from './GameChanger.js'; | ||
import { type StorylineMote } from './cl2.storyline.types.js'; | ||
export declare function stringifyStoryline(mote: StorylineMote, packed: GameChanger): string; | ||
export declare function stringifyComfort(mote: ComfortMote, packed: GameChanger): string; | ||
//# sourceMappingURL=cl2.comfort.stringify.d.ts.map |
import { bsArrayToArray, toArrayTag } from './helpers.js'; | ||
export function stringifyStoryline(mote, packed) { | ||
import { cleanGameChangerString } from './util.js'; | ||
export function stringifyComfort(mote, packed) { | ||
// METADATA | ||
const blocks = [ | ||
`Name: ${packed.working.getMoteName(mote)}`, | ||
`Description: ${mote.data.description?.text || ''}\n`, | ||
`Name: ${cleanGameChangerString(packed.working.getMoteName(mote))}`, | ||
`Description: ${cleanGameChangerString(mote.data.description?.text)}\n`, | ||
`Unlocked Description: ${cleanGameChangerString(mote.data.unlocked_description?.text)}\n`, | ||
]; | ||
@@ -8,0 +10,0 @@ if (mote.data.wip?.staging) { |
import type { Gcdata } from './GameChanger.js'; | ||
import type { ParsedLine } from './cl2.shared.types.js'; | ||
import type { Crashlands2 } from './cl2.types.auto.js'; | ||
import type { ParsedBase, ParserResult } from './cl2.types.editor.js'; | ||
import type { Position, Range } from './types.editor.js'; | ||
import type { BschemaRoot, Mote } from './types.js'; | ||
export declare const comfortSchemaId = "cl2_artisan_glads"; | ||
export type ComfortData = Crashlands2.Schemas['cl2_artisan_glads']; | ||
export type ComfortMote = Mote<ComfortData>; | ||
type CompletionsData = { | ||
type: 'glossary'; | ||
options: string[]; | ||
} | { | ||
type: 'stages'; | ||
options: string[]; | ||
} | { | ||
type: 'labels'; | ||
options: Set<string>; | ||
}; | ||
export interface StorylineUpdateResult extends ParserResult { | ||
parsed: ParsedBase & { | ||
description?: string; | ||
}; | ||
completions: (Range & CompletionsData)[]; | ||
import { ComfortMote, ParserResult } from './cl2.shared.types.js'; | ||
import type { BschemaRoot } from './types.js'; | ||
export interface ComfortUpdateResult extends ParserResult<{ | ||
description?: string; | ||
unlockedDescription?: string; | ||
}> { | ||
} | ||
@@ -29,9 +12,4 @@ export declare function listComforts(gcData: Gcdata): ComfortMote[]; | ||
export declare function getComfortMote(gcData: Gcdata, moteId: string): ComfortMote | undefined; | ||
export declare function getComfortMotes(gcData: Gcdata): ComfortMote[]; | ||
export declare function getComfortSchema(gcData: Gcdata): BschemaRoot | undefined; | ||
export declare const arrayTagPattern = "(?:#(?<arrayTag>[a-z0-9]+))"; | ||
export declare const linePatterns: string[]; | ||
export declare function parseIfMatch(pattern: string, line: string, startPosition: Position): ParsedLine | null; | ||
export declare function lineIsArrayItem(line: string): boolean; | ||
export {}; | ||
//# sourceMappingURL=cl2.comfort.types.d.ts.map |
@@ -1,4 +0,3 @@ | ||
import { z } from 'zod'; | ||
import { assert } from './assert.js'; | ||
export const comfortSchemaId = 'cl2_artisan_glads'; | ||
import { arrayTagPattern, comfortSchemaId, } from './cl2.shared.types.js'; | ||
export function listComforts(gcData) { | ||
@@ -12,77 +11,14 @@ return gcData.listMotesBySchema(comfortSchemaId); | ||
const mote = gcData.getMote(moteId); | ||
assert(!mote || isComfortMote(mote), `Mote ${moteId} is not a comfort`); | ||
assert(!mote || isComfortMote(mote), `Mote ${moteId} is not a storyline`); | ||
return mote; | ||
} | ||
export function getComfortMotes(gcData) { | ||
const motes = gcData.listMotesBySchema(comfortSchemaId); | ||
return motes; | ||
} | ||
export function getComfortSchema(gcData) { | ||
return gcData.getSchema(comfortSchemaId); | ||
} | ||
// PATTERNS | ||
// Note: These patterns are defined so that they'll work on partial lines | ||
// as much as possible, so their group names should always be checked for existence. | ||
const linePartsSchema = z.object({ | ||
indicator: z | ||
.string() | ||
.optional() | ||
.describe('The symbol prefixing the line to indicate what the line type is'), | ||
arrayTag: z | ||
.string() | ||
.regex(/^[a-z0-9]+$/) | ||
.optional() | ||
.describe("BsArrayElement identifier (without the '#' prefix)"), | ||
labelGroup: z.string().optional().describe('The label, including the `:`'), | ||
label: z.string().optional().describe('For `Label:Value` elements'), | ||
text: z | ||
.string() | ||
.optional() | ||
.describe('For dialog and similar, the text content'), | ||
}); | ||
export const arrayTagPattern = '(?:#(?<arrayTag>[a-z0-9]+))'; | ||
export const linePatterns = [ | ||
/** Label:Text */ | ||
`^(?<labelGroup>(?<label>Name|Description|Stage)\\s*:)\\s*(?<text>.*?)\\s*$`, | ||
`^(?<labelGroup>(?<label>Name|Description|Unlocked Description|Stage)\\s*:)\\s*(?<text>.*?)\\s*$`, | ||
/** Comment Line */ | ||
`^(?<indicator>//)\\s*?${arrayTagPattern}?\\s*(?<text>.*?)\\s*$`, | ||
]; | ||
export function parseIfMatch(pattern, line, startPosition) { | ||
const rawMatch = line.match(new RegExp(pattern)); | ||
if (!rawMatch) | ||
return null; | ||
const parsedLine = linePartsSchema.parse(rawMatch.groups); | ||
const result = { | ||
_: { | ||
start: startPosition, | ||
end: { ...startPosition }, | ||
value: line, | ||
}, | ||
}; | ||
result._.end.character += line.length; | ||
result._.end.index += line.length; | ||
for (const [key, value] of Object.entries(parsedLine)) { | ||
if (typeof value !== 'undefined') { | ||
// Figure out where this is in the matches so we | ||
// can get the start and end positions. | ||
const startChar = line.indexOf(`${value}`); | ||
const endChar = startChar + `${value}`.length; | ||
const start = { ...startPosition }; | ||
start.character += startChar; | ||
start.index += startChar; | ||
const end = { ...startPosition }; | ||
end.character += endChar; | ||
end.index += endChar; | ||
result[key] = { | ||
start, | ||
end, | ||
value: value, | ||
}; | ||
} | ||
} | ||
return result; | ||
} | ||
export function lineIsArrayItem(line) { | ||
return /^\/\//.test(line); | ||
} | ||
//# sourceMappingURL=cl2.comfort.types.js.map |
export { parseStringifiedQuest, updateChangesFromParsedQuest, } from './cl2.quest.parse.js'; | ||
export { stringifyQuest } from './cl2.quest.stringify.js'; | ||
export { isQuestMote, listQuests, questSchemaId, type QuestData, type QuestMote, type QuestUpdateResult, } from './cl2.quest.types.js'; | ||
export { isQuestMote, listQuests, type QuestUpdateResult, } from './cl2.quest.types.js'; | ||
//# sourceMappingURL=cl2.quest.d.ts.map |
export { parseStringifiedQuest, updateChangesFromParsedQuest, } from './cl2.quest.parse.js'; | ||
export { stringifyQuest } from './cl2.quest.stringify.js'; | ||
export { isQuestMote, listQuests, questSchemaId, } from './cl2.quest.types.js'; | ||
export { isQuestMote, listQuests, } from './cl2.quest.types.js'; | ||
//# sourceMappingURL=cl2.quest.js.map |
@@ -1,2 +0,2 @@ | ||
import { GameChanger } from './GameChanger.js'; | ||
import type { GameChanger } from './GameChanger.js'; | ||
import { QuestUpdateResult } from './cl2.quest.types.js'; | ||
@@ -3,0 +3,0 @@ export declare function parseStringifiedQuest(text: string, packed: GameChanger, options?: { |
import { assert } from './assert.js'; | ||
import { linePatterns, } from './cl2.quest.types.js'; | ||
import { getMomentStyleNames, getMoteLists, getRequirementQuestStatuses, getRequirementStyleNames, getStagingOptions, isEmoteMoment, } from './cl2.quest.utils.js'; | ||
import { arrayTagPattern, lineIsArrayItem, parseIfMatch, } from './cl2.shared.types.js'; | ||
import { bsArrayToArray, changedPosition, createBsArrayKey, updateBsArrayOrder, } from './helpers.js'; | ||
import { checkWords, includes } from './util.js'; | ||
import { getMomentStyleNames, getMoteLists, getRequirementQuestStatuses, getRequirementStyleNames, isEmoteMoment, } from './cl2.quest.utils.js'; | ||
import { isCommentLine, isStageLine, prepareParserHelpers, updateWipChangesFromParsed, } from './cl2.shared.parse.js'; | ||
import { bsArrayToArray, changedPosition, updateBsArrayOrder, } from './helpers.js'; | ||
export function parseStringifiedQuest(text, packed, options = {}) { | ||
@@ -15,3 +14,2 @@ const motes = getMoteLists(packed.working); | ||
const requirementStyles = getRequirementStyleNames(packed.working); | ||
const stagingOptions = getStagingOptions(packed.working); | ||
const requirementQuestStatuses = getRequirementQuestStatuses(packed.working); | ||
@@ -21,22 +19,2 @@ const requirementCompletions = [...requirementStyles]; | ||
requirementCompletions.push(...requirementQuestStatuses.map((s) => `Quest ${s}`)); | ||
/** | ||
* Shared list of keywords that can be used at the start of any line, | ||
* with required-unique entries removed when found. | ||
*/ | ||
const nonUniqueGlobalLabels = new Set(['Clue']); | ||
const availableGlobalLabels = new Set([ | ||
'Stage', | ||
'Name', | ||
'Storyline', | ||
'Giver', | ||
'Receiver', | ||
'Clue', | ||
'Start Requirements', | ||
'Start Moments', | ||
'End Requirements', | ||
'End Moments', | ||
'Log', | ||
]); | ||
/** Terms from the glossary for use in autocompletes */ | ||
const glossaryTerms = (packed.glossary?.relevantTerms() || []).map((t) => t.text); | ||
const result = { | ||
@@ -57,18 +35,19 @@ diagnostics: [], | ||
}; | ||
const lines = text.split(/(\r?\n)/g); | ||
let index = 0; | ||
let lineNumber = 0; | ||
const emojiIdFromName = (name) => { | ||
if (!name) { | ||
return undefined; | ||
} | ||
const emoji = motes.emojis.find((e) => packed.working.getMoteName(e)?.toLowerCase() === | ||
name?.trim().toLowerCase() || e.id === name?.trim()); | ||
return emoji?.id; | ||
}; | ||
const checkSpelling = (item) => { | ||
if (!item || !options.checkSpelling || !packed.glossary) | ||
return; | ||
result.words.push(...checkWords(item, packed.glossary)); | ||
}; | ||
const helpers = prepareParserHelpers(text, packed, { | ||
...options, | ||
globalNonUniqueLabels: new Set(['Clue']), | ||
globalLabels: new Set([ | ||
'Stage', | ||
'Name', | ||
'Storyline', | ||
'Giver', | ||
'Receiver', | ||
'Clue', | ||
'Start Requirements', | ||
'Start Moments', | ||
'End Requirements', | ||
'End Moments', | ||
'Log', | ||
]), | ||
}, result); | ||
/** The MoteId for the last speaker we saw. Used to figure out who to assign stuff to */ | ||
@@ -79,33 +58,13 @@ let lastSpeaker; | ||
let lastEmojiGroup; | ||
for (const line of lines) { | ||
for (const line of helpers.lines) { | ||
const trace = []; | ||
try { | ||
// Is this just a newline? | ||
if (line.match(/\r?\n/)) { | ||
// Then we just need to increment the index | ||
index += line.length; | ||
lineNumber++; | ||
continue; | ||
} | ||
const lineRange = { | ||
start: { | ||
index, | ||
line: lineNumber, | ||
character: 0, | ||
}, | ||
end: { | ||
index: index + line.length, | ||
line: lineNumber, | ||
character: line.length, | ||
}, | ||
}; | ||
const lineRange = helpers.currentLineRange; | ||
// Is this just a blank line? | ||
if (!line) { | ||
// Add global autocompletes | ||
result.completions.push({ | ||
type: 'labels', | ||
const pos = { | ||
start: lineRange.start, | ||
end: lineRange.end, | ||
options: availableGlobalLabels, | ||
}); | ||
}; | ||
if (isQuestMomentLabel(lastSectionGroup)) { | ||
@@ -115,4 +74,3 @@ result.completions.push({ | ||
options: momentStyles, | ||
start: lineRange.start, | ||
end: lineRange.end, | ||
...pos, | ||
}); | ||
@@ -124,4 +82,3 @@ } | ||
options: requirementCompletions, | ||
start: lineRange.start, | ||
end: lineRange.end, | ||
...pos, | ||
}); | ||
@@ -132,61 +89,5 @@ } | ||
// Find the first matching pattern and pull the values from it. | ||
let parsedLine = null; | ||
for (const pattern of linePatterns) { | ||
parsedLine = parseIfMatch(pattern, line, lineRange.start); | ||
if (parsedLine) | ||
break; | ||
} | ||
if (!parsedLine) { | ||
// Then this is likely the result of uncommenting something | ||
// that was commented out, resulting in a line that starts with | ||
// the comment's array tag. Provide a deletion edit! | ||
parsedLine = parseIfMatch(`^${arrayTagPattern} +(?<text>.*)$`, line, lineRange.start); | ||
if (parsedLine) { | ||
result.edits.push({ | ||
start: lineRange.start, | ||
end: lineRange.end, | ||
newText: parsedLine.text.value, | ||
}); | ||
} | ||
else { | ||
result.diagnostics.push({ | ||
message: `Unfamiliar syntax: ${line}`, | ||
...lineRange, | ||
}); | ||
} | ||
index += line.length; | ||
const parsedLine = helpers.parseCurrentLine(linePatterns); | ||
if (!parsedLine) | ||
continue; | ||
} | ||
// Ensure the array tag. It goes right after the label or indicator. | ||
if (!parsedLine.arrayTag?.value && lineIsArrayItem(line)) { | ||
const arrayTag = createBsArrayKey(); | ||
const start = parsedLine.indicator?.end || parsedLine.label?.end; | ||
result.edits.push({ | ||
start, | ||
end: start, | ||
newText: `#${arrayTag}`, | ||
}); | ||
parsedLine.arrayTag = { | ||
start, | ||
end: start, | ||
value: arrayTag, | ||
}; | ||
} | ||
// If this has a label, remove it from the list of available labels | ||
if (parsedLine.label?.value && | ||
availableGlobalLabels.has(parsedLine.label.value) && | ||
!nonUniqueGlobalLabels.has(parsedLine.label.value)) { | ||
availableGlobalLabels.delete(parsedLine.label.value); | ||
} | ||
// If this has a text section, provide glossary autocompletes | ||
if ('text' in parsedLine) { | ||
const start = parsedLine.text.start; | ||
const end = parsedLine.text.end; | ||
result.completions.push({ | ||
type: 'glossary', | ||
start, | ||
end, | ||
options: glossaryTerms, | ||
}); | ||
} | ||
// Track common problems so that we don't need to repeat logic | ||
@@ -245,3 +146,3 @@ /** The character where a mote should exist. */ | ||
// Then this is a dialog line, either within a Clue or a Dialog Moment | ||
const emoji = emojiIdFromName(parsedLine.emojiName?.value); | ||
const emoji = helpers.emojiIdFromName(parsedLine.emojiName?.value); | ||
if (parsedLine.emojiGroup) { | ||
@@ -252,6 +153,6 @@ // Emojis are optional. If we see a "group" (parentheses) then | ||
at: changedPosition(parsedLine.emojiGroup.start, { characters: 1 }), | ||
options: motes.emojis, | ||
options: helpers.emojis, | ||
}; | ||
} | ||
checkSpelling(parsedLine.text); | ||
helpers.checkSpelling(parsedLine.text); | ||
const moment = { | ||
@@ -287,24 +188,8 @@ kind: 'dialogue', | ||
} | ||
else if (labelLower === 'stage') { | ||
const stage = parsedLine.text?.value?.trim(); | ||
if (includes(stagingOptions, stage)) { | ||
result.parsed.stage = stage; | ||
} | ||
else { | ||
result.diagnostics.push({ | ||
message: `Stage must be one of: ${stagingOptions.join(', ')}`, | ||
...lineRange, | ||
}); | ||
// Provide autocomplete options | ||
result.completions.push({ | ||
type: 'stages', | ||
options: stagingOptions, | ||
start: parsedLine.labelGroup.end, | ||
end: lineRange.end, | ||
}); | ||
} | ||
else if (isStageLine(parsedLine)) { | ||
helpers.addStage(parsedLine); | ||
} | ||
else if (labelLower === 'log') { | ||
result.parsed.quest_start_log = parsedLine.text?.value?.trim(); | ||
checkSpelling(parsedLine.text); | ||
helpers.checkSpelling(parsedLine.text); | ||
} | ||
@@ -372,3 +257,3 @@ else if (labelLower === 'storyline') { | ||
}), | ||
options: motes.emojis, | ||
options: helpers.emojis, | ||
}; | ||
@@ -385,3 +270,3 @@ } | ||
speaker: parsedLine.moteTag?.value?.trim(), | ||
emoji: emojiIdFromName(parsedLine.emojiName?.value), | ||
emoji: helpers.emojiIdFromName(parsedLine.emojiName?.value), | ||
}); | ||
@@ -504,9 +389,4 @@ } | ||
} | ||
else if (indicator === '//') { | ||
// Then this is a comment/note | ||
result.parsed.comments.push({ | ||
id: parsedLine.arrayTag?.value?.trim(), | ||
text: parsedLine.text?.value?.trim(), | ||
}); | ||
checkSpelling(parsedLine.text); | ||
else if (isCommentLine(parsedLine)) { | ||
helpers.addComment(parsedLine); | ||
} | ||
@@ -525,3 +405,3 @@ if (requiresEmoji) { | ||
} | ||
else if (!emojiIdFromName(parsedLine.emojiName?.value)) { | ||
else if (!helpers.emojiIdFromName(parsedLine.emojiName?.value)) { | ||
result.diagnostics.push({ | ||
@@ -565,3 +445,3 @@ message: `Emoji "${parsedLine.emojiName?.value}" not found!`, | ||
} | ||
index += line.length; | ||
helpers.index += line.length; | ||
} | ||
@@ -603,44 +483,4 @@ return result; | ||
updateMote('data/storyline', parsed.storyline); | ||
if (parsed.stage) { | ||
updateMote('data/wip/staging', parsed.stage); | ||
} | ||
else if (questMoteWorking?.data.wip) { | ||
updateMote('data/wip/staging', null); | ||
} | ||
const parsedComments = parsed.comments.filter((c) => !!c.text); | ||
updateWipChangesFromParsed(parsed, moteId, packed, trace); | ||
const parsedClues = parsed.clues.filter((c) => !!c.id && !!c.speaker); | ||
//#region COMMENTS | ||
// Add/Update COMMENTS | ||
trace(`Updating comments`); | ||
for (const comment of parsedComments) { | ||
trace(`Updating comment ${comment.id} with text "${comment.text}"`); | ||
updateMote(`data/wip/notes/${comment.id}/element/text`, comment.text); | ||
} | ||
// Remove deleted comments | ||
for (const existingComment of bsArrayToArray(questMoteBase?.data.wip?.notes || {})) { | ||
if (!parsedComments.find((c) => c.id === existingComment.id)) { | ||
trace(`Deleting comment ${existingComment.id}`); | ||
updateMote(`data/wip/notes/${existingComment.id}`, null); | ||
} | ||
} | ||
// Get the BASE order of the comments (if any) and use those | ||
// as the starting point for an up to date order. | ||
const comments = parsedComments.map((c) => { | ||
// Look up the base comment | ||
let comment = questMoteBase?.data.wip?.notes?.[c.id]; | ||
if (!comment) { | ||
comment = questMoteWorking?.data.wip?.notes?.[c.id]; | ||
// @ts-expect-error - order is a required field, but it'll be re-added | ||
delete comment?.order; | ||
} | ||
assert(comment, `Comment ${c.id} not found in base or working mote`); | ||
return { ...comment, id: c.id }; | ||
}); | ||
trace('Updating comment order'); | ||
updateBsArrayOrder(comments); | ||
comments.forEach((comment) => { | ||
trace(`Updating comment ${comment.id} order to ${comment.order}`); | ||
updateMote(`data/wip/notes/${comment.id}/order`, comment.order); | ||
}); | ||
//#endregion | ||
//#region CLUES | ||
@@ -647,0 +487,0 @@ // Add/update clues |
import type { Gcdata } from './GameChanger.js'; | ||
import { CompletionsData } from './cl2.shared.types.js'; | ||
import { ParserResult, QuestMote } from './cl2.shared.types.js'; | ||
import type { Crashlands2 } from './cl2.types.auto.js'; | ||
import { ParsedComment, ParserResult } from './cl2.types.editor.js'; | ||
import type { Range } from './types.editor.js'; | ||
import type { Mote } from './types.js'; | ||
export declare const questSchemaId = "cl2_quest"; | ||
export type QuestData = Crashlands2.Schemas['cl2_quest']; | ||
export type QuestMote = Mote<QuestData>; | ||
export declare function listQuests(gcData: Gcdata): QuestMote[]; | ||
@@ -70,21 +65,16 @@ export declare function isQuestMote(mote: any): mote is Mote<Crashlands2.Quest>; | ||
export type QuestRequirementsLabel = `quest_${'start' | 'end'}_requirements`; | ||
export interface QuestUpdateResult extends ParserResult { | ||
completions: (Range & CompletionsData)[]; | ||
parsed: { | ||
name?: string; | ||
/** The moteId for the storyline */ | ||
storyline?: string; | ||
/** The moteId for the quest giver */ | ||
quest_giver?: string; | ||
/** The moteId for the quest receiver */ | ||
quest_receiver?: string; | ||
clues: ParsedClue[]; | ||
quest_start_log?: string; | ||
stage?: Crashlands2.Staging; | ||
comments: ParsedComment[]; | ||
} & { | ||
[K in QuestMomentsLabel]: ParsedMoment[]; | ||
} & { | ||
[K in QuestRequirementsLabel]: ParsedRequirement[]; | ||
}; | ||
export interface QuestUpdateResult extends ParserResult<{ | ||
/** The moteId for the storyline */ | ||
storyline?: string; | ||
/** The moteId for the quest giver */ | ||
quest_giver?: string; | ||
/** The moteId for the quest receiver */ | ||
quest_receiver?: string; | ||
clues: ParsedClue[]; | ||
quest_start_log?: string; | ||
} & { | ||
[K in QuestMomentsLabel]: ParsedMoment[]; | ||
} & { | ||
[K in QuestRequirementsLabel]: ParsedRequirement[]; | ||
}> { | ||
} | ||
@@ -91,0 +81,0 @@ export type Section = (typeof sections)[number]; |
@@ -1,3 +0,2 @@ | ||
import { arrayTagPattern } from './cl2.shared.types.js'; | ||
export const questSchemaId = 'cl2_quest'; | ||
import { arrayTagPattern, questSchemaId, } from './cl2.shared.types.js'; | ||
export function listQuests(gcData) { | ||
@@ -4,0 +3,0 @@ return gcData.listMotesBySchema(questSchemaId); |
@@ -8,5 +8,3 @@ import type { Gcdata } from './GameChanger.js'; | ||
quests: import("./types.js").Mote<Crashlands2.Quest, string>[]; | ||
emojis: import("./types.js").Mote<Crashlands2.Emoji11, string>[]; | ||
}; | ||
export declare function getStagingOptions(packed: Gcdata): Crashlands2.Staging[]; | ||
export declare function getRequirementQuestStatuses(packed: Gcdata): string[]; | ||
@@ -13,0 +11,0 @@ export declare function getRequirementStyleNames(packed: Gcdata): string[]; |
@@ -12,4 +12,2 @@ import { assert } from './assert.js'; | ||
assert(quests.length > 0, 'Should have at least one quest mote'); | ||
const emojis = packed.listMotesBySchema('cl2_emoji'); | ||
assert(emojis.length > 0, 'Should have at least one emoji mote'); | ||
return { | ||
@@ -20,16 +18,4 @@ allowedSpeakers, | ||
quests, | ||
emojis, | ||
}; | ||
} | ||
export function getStagingOptions(packed) { | ||
const stagingSubchema = resolvePointerInSchema(['wip', 'staging'], { | ||
schema_id: 'cl2_quest', | ||
data: { | ||
wip: { | ||
staging: 'any', | ||
}, | ||
}, | ||
}, packed); | ||
return stagingSubchema.enum; | ||
} | ||
function getAllowedSpeakers(packed) { | ||
@@ -36,0 +22,0 @@ const speakerSubchema = resolvePointerInSchema(['quest_start_moments', 'anykey', 'element', 'speech', 'speaker'], { |
import { z } from 'zod'; | ||
import type { Crashlands2 } from './cl2.types.auto.js'; | ||
import type { Position } from './types.editor.js'; | ||
import type { Gcdata } from './GameChanger.js'; | ||
import type { ParsedLineItem, ParsedWord, Position, Range } from './types.editor.js'; | ||
import type { Mote } from './types.js'; | ||
export declare const questSchemaId = "cl2_quest"; | ||
export type QuestData = Crashlands2.Schemas['cl2_quest']; | ||
export type QuestMote = Mote<QuestData>; | ||
export declare const chatSchemaId = "cl2_chat"; | ||
export type ChatData = Crashlands2.Schemas['cl2_chat']; | ||
export type ChatMote = Mote<ChatData>; | ||
export declare const buddySchemaId = "artisan"; | ||
export type BuddyData = Crashlands2.Schemas['artisan']; | ||
export type BuddyMote = Mote<BuddyData>; | ||
export declare const npcSchemaId = "cl2_npc"; | ||
export type NpcData = Crashlands2.Schemas['cl2_npc']; | ||
export type NpcMote = Mote<NpcData>; | ||
export declare const comfortSchemaId = "cl2_artisan_glads"; | ||
export type ComfortData = Crashlands2.Schemas['cl2_artisan_glads']; | ||
export type ComfortMote = Mote<ComfortData>; | ||
export declare const storylineSchemaId = "cl2_storyline"; | ||
export type StorylineData = Crashlands2.Schemas['cl2_storyline']; | ||
export type StorylineMote = Mote<StorylineData>; | ||
export interface ParsedComment { | ||
/** arrayId */ | ||
id: string | undefined; | ||
text: string | undefined; | ||
} | ||
export interface ParsedBase { | ||
name?: string; | ||
stage?: Crashlands2.Staging; | ||
comments: ParsedComment[]; | ||
} | ||
export interface ParserResult<P extends Record<string, any>> { | ||
diagnostics: (Range & { | ||
message: string; | ||
})[]; | ||
hovers: (Range & { | ||
title?: string; | ||
description?: string; | ||
})[]; | ||
edits: (Range & { | ||
newText: string; | ||
})[]; | ||
completions: (Range & CompletionsData)[]; | ||
words: ParsedWord[]; | ||
parsed: ParsedBase & P; | ||
} | ||
export type CompletionsData = { | ||
@@ -27,7 +71,2 @@ type: 'motes'; | ||
}; | ||
export interface ParsedLineItem<V = string> { | ||
start: Position; | ||
end: Position; | ||
value: V; | ||
} | ||
export type ParsedLine = { | ||
@@ -58,5 +97,2 @@ [K in keyof LineParts]?: ParsedLineItem<LineParts[K]>; | ||
}, "strip", z.ZodTypeAny, { | ||
status?: string | undefined; | ||
text?: string | undefined; | ||
style?: string | undefined; | ||
indicator?: string | undefined; | ||
@@ -71,6 +107,6 @@ arrayTag?: string | undefined; | ||
sep?: string | undefined; | ||
}, { | ||
status?: string | undefined; | ||
text?: string | undefined; | ||
style?: string | undefined; | ||
status?: string | undefined; | ||
}, { | ||
indicator?: string | undefined; | ||
@@ -85,5 +121,10 @@ arrayTag?: string | undefined; | ||
sep?: string | undefined; | ||
text?: string | undefined; | ||
style?: string | undefined; | ||
status?: string | undefined; | ||
}>; | ||
export declare function parseIfMatch(pattern: string, line: string, startPosition: Position): ParsedLine | null; | ||
export declare function lineIsArrayItem(line: string): boolean; | ||
export declare function getStagingOptions(packed: Gcdata): Crashlands2.Staging[]; | ||
export declare function getEmojis(packed: Gcdata): Mote<Crashlands2.Schemas['cl2_emoji']>[]; | ||
//# sourceMappingURL=cl2.shared.types.d.ts.map |
import { z } from 'zod'; | ||
import { assert } from './assert.js'; | ||
import { resolvePointerInSchema } from './util.js'; | ||
export const questSchemaId = 'cl2_quest'; | ||
export const chatSchemaId = 'cl2_chat'; | ||
export const buddySchemaId = 'artisan'; | ||
export const npcSchemaId = 'cl2_npc'; | ||
export const comfortSchemaId = 'cl2_artisan_glads'; | ||
export const storylineSchemaId = 'cl2_storyline'; | ||
export const arrayTagPattern = '(?:#(?<arrayTag>[a-z0-9]+))'; | ||
@@ -79,3 +87,3 @@ export const linePartsSchema = z.object({ | ||
export function lineIsArrayItem(line) { | ||
if (line.match(/^(\t|name|stage|storyline|(start|end) (moments|requirements)|log|giver|receiver|description)/i)) { | ||
if (line.match(/^(\t|name|stage|storyline|(start|end) (moments|requirements)|log|giver|receiver|description|unlocked description)/i)) { | ||
return false; | ||
@@ -85,2 +93,18 @@ } | ||
} | ||
export function getStagingOptions(packed) { | ||
const stagingSubchema = resolvePointerInSchema(['wip', 'staging'], { | ||
schema_id: 'cl2_quest', | ||
data: { | ||
wip: { | ||
staging: 'any', | ||
}, | ||
}, | ||
}, packed); | ||
return stagingSubchema.enum; | ||
} | ||
export function getEmojis(packed) { | ||
const emojis = packed.listMotesBySchema('cl2_emoji'); | ||
assert(emojis.length > 0, 'Should have at least one emoji mote'); | ||
return emojis; | ||
} | ||
//# sourceMappingURL=cl2.shared.types.js.map |
export { parseStringifiedStoryline, updateChangesFromParsedStoryline, } from './cl2.storyline.parse.js'; | ||
export { stringifyStoryline } from './cl2.storyline.stringify.js'; | ||
export { isStorylineMote, listStorylines, storylineSchemaId, type StorylineData, type StorylineMote, type StorylineUpdateResult, } from './cl2.storyline.types.js'; | ||
export { isStorylineMote, listStorylines, type StorylineUpdateResult, } from './cl2.storyline.types.js'; | ||
//# sourceMappingURL=cl2.storyline.d.ts.map |
export { parseStringifiedStoryline, updateChangesFromParsedStoryline, } from './cl2.storyline.parse.js'; | ||
export { stringifyStoryline } from './cl2.storyline.stringify.js'; | ||
export { isStorylineMote, listStorylines, storylineSchemaId, } from './cl2.storyline.types.js'; | ||
export { isStorylineMote, listStorylines, } from './cl2.storyline.types.js'; | ||
//# sourceMappingURL=cl2.storyline.js.map |
import { assert } from './assert.js'; | ||
import { getStagingOptions } from './cl2.quest.utils.js'; | ||
import { arrayTagPattern, lineIsArrayItem, parseIfMatch, } from './cl2.shared.types.js'; | ||
import { getStorylineMote, getStorylineSchema, linePatterns, storylineSchemaId, } from './cl2.storyline.types.js'; | ||
import { bsArrayToArray, createBsArrayKey, updateBsArrayOrder, } from './helpers.js'; | ||
import { checkWords, includes } from './util.js'; | ||
import { isCommentLine, isStageLine, prepareParserHelpers, updateWipChangesFromParsed, } from './cl2.shared.parse.js'; | ||
import { storylineSchemaId } from './cl2.shared.types.js'; | ||
import { getStorylineSchema, linePatterns, } from './cl2.storyline.types.js'; | ||
export function parseStringifiedStoryline(text, packed, options = {}) { | ||
@@ -18,120 +16,21 @@ const result = { | ||
}; | ||
const availableGlobalLabels = new Set([ | ||
'Name', | ||
'Description', | ||
'Stage', | ||
]); | ||
const stagingOptions = getStagingOptions(packed.working); | ||
/** Terms from the glossary for use in autocompletes */ | ||
const glossaryTerms = (packed.glossary?.relevantTerms() || []).map((t) => t.text); | ||
const checkSpelling = (item) => { | ||
if (!item || !options.checkSpelling) | ||
return; | ||
result.words.push(...checkWords(item, packed.glossary)); | ||
}; | ||
const lines = text.split(/(\r?\n)/g); | ||
let index = 0; | ||
let lineNumber = 0; | ||
for (const line of lines) { | ||
const helpers = prepareParserHelpers(text, packed, { | ||
...options, | ||
globalLabels: new Set(['Name', 'Description', 'Stage']), | ||
}, result); | ||
for (const line of helpers.lines) { | ||
const trace = []; | ||
try { | ||
// Is this just a newline? | ||
if (line.match(/\r?\n/)) { | ||
// Then we just need to increment the index | ||
index += line.length; | ||
lineNumber++; | ||
continue; | ||
} | ||
const lineRange = { | ||
start: { | ||
index, | ||
line: lineNumber, | ||
character: 0, | ||
}, | ||
end: { | ||
index: index + line.length, | ||
line: lineNumber, | ||
character: line.length, | ||
}, | ||
}; | ||
// Is this just a blank line? | ||
if (!line) { | ||
// Add global autocompletes | ||
result.completions.push({ | ||
type: 'labels', | ||
start: lineRange.start, | ||
end: lineRange.end, | ||
options: availableGlobalLabels, | ||
}); | ||
continue; | ||
} | ||
const lineRange = helpers.currentLineRange; | ||
// Find the first matching pattern and pull the values from it. | ||
let parsedLine = null; | ||
for (const pattern of linePatterns) { | ||
parsedLine = parseIfMatch(pattern, line, lineRange.start); | ||
if (parsedLine) | ||
break; | ||
} | ||
if (!parsedLine) { | ||
// Then this is likely the result of uncommenting something | ||
// that was commented out, resulting in a line that starts with | ||
// the comment's array tag. Provide a deletion edit! | ||
parsedLine = parseIfMatch(`^${arrayTagPattern} +(?<text>.*)$`, line, lineRange.start); | ||
if (parsedLine) { | ||
result.edits.push({ | ||
start: lineRange.start, | ||
end: lineRange.end, | ||
newText: parsedLine.text.value, | ||
}); | ||
} | ||
else { | ||
result.diagnostics.push({ | ||
message: `Unfamiliar syntax: ${line}`, | ||
...lineRange, | ||
}); | ||
} | ||
index += line.length; | ||
const parsedLine = helpers.parseCurrentLine(linePatterns); | ||
if (!parsedLine) | ||
continue; | ||
} | ||
// Ensure the array tag. It goes right after the label or indicator. | ||
if (!parsedLine.arrayTag?.value && lineIsArrayItem(line)) { | ||
const arrayTag = createBsArrayKey(); | ||
const start = parsedLine.indicator?.end || parsedLine.label?.end; | ||
result.edits.push({ | ||
start, | ||
end: start, | ||
newText: `#${arrayTag}`, | ||
}); | ||
parsedLine.arrayTag = { | ||
start, | ||
end: start, | ||
value: arrayTag, | ||
}; | ||
} | ||
// If this has a label, remove it from the list of available labels | ||
if (parsedLine.label?.value && | ||
availableGlobalLabels.has(parsedLine.label.value)) { | ||
availableGlobalLabels.delete(parsedLine.label.value); | ||
} | ||
// If this has a text section, provide glossary autocompletes | ||
if ('text' in parsedLine) { | ||
const start = parsedLine.text.start; | ||
const end = parsedLine.text.end; | ||
result.completions.push({ | ||
type: 'glossary', | ||
start, | ||
end, | ||
options: glossaryTerms, | ||
}); | ||
} | ||
// Work through each line type to add diagnostics and completions | ||
const labelLower = parsedLine.label?.value?.toLowerCase(); | ||
const indicator = parsedLine.indicator?.value; | ||
if (indicator === '//') { | ||
// Then this is a comment/note | ||
result.parsed.comments.push({ | ||
id: parsedLine.arrayTag?.value?.trim(), | ||
text: parsedLine.text?.value?.trim(), | ||
}); | ||
checkSpelling(parsedLine.text); | ||
if (isCommentLine(parsedLine)) { | ||
helpers.addComment(parsedLine); | ||
} | ||
@@ -149,22 +48,6 @@ else if (labelLower === 'name') { | ||
result.parsed.description = parsedLine.text?.value?.trim(); | ||
checkSpelling(parsedLine.text); | ||
helpers.checkSpelling(parsedLine.text); | ||
} | ||
else if (labelLower === 'stage') { | ||
const stage = parsedLine.text?.value?.trim(); | ||
if (includes(stagingOptions, stage)) { | ||
result.parsed.stage = stage; | ||
} | ||
else { | ||
result.diagnostics.push({ | ||
message: `Stage must be one of: ${stagingOptions.join(', ')}`, | ||
...lineRange, | ||
}); | ||
// Provide autocomplete options | ||
result.completions.push({ | ||
type: 'stages', | ||
options: stagingOptions, | ||
start: parsedLine.labelGroup.end, | ||
end: lineRange.end, | ||
}); | ||
} | ||
else if (isStageLine(parsedLine)) { | ||
helpers.addStage(parsedLine); | ||
} | ||
@@ -185,3 +68,3 @@ else { | ||
} | ||
index += line.length; | ||
helpers.index += line.length; | ||
} | ||
@@ -198,4 +81,2 @@ return result; | ||
packed.clearMoteChanges(moteId); | ||
const storylineMoteBase = getStorylineMote(packed.base, moteId); | ||
const storylineMoteWorking = getStorylineMote(packed.working, moteId); | ||
const schema = getStorylineSchema(packed.working); | ||
@@ -209,43 +90,3 @@ assert(schema, `${storylineSchemaId} schema not found in working copy`); | ||
updateMote('data/description/text', parsed.description); | ||
if (parsed.stage) { | ||
updateMote('data/wip/staging', parsed.stage); | ||
} | ||
else if (storylineMoteWorking?.data.wip) { | ||
updateMote('data/wip/staging', null); | ||
} | ||
const parsedComments = parsed.comments.filter((c) => !!c.text); | ||
//#region COMMENTS | ||
// Add/Update COMMENTS | ||
trace(`Updating comments`); | ||
for (const comment of parsedComments) { | ||
trace(`Updating comment ${comment.id} with text "${comment.text}"`); | ||
updateMote(`data/wip/notes/${comment.id}/element/text`, comment.text); | ||
} | ||
// Remove deleted comments | ||
for (const existingComment of bsArrayToArray(storylineMoteBase?.data.wip?.notes || {})) { | ||
if (!parsedComments.find((c) => c.id === existingComment.id)) { | ||
trace(`Deleting comment ${existingComment.id}`); | ||
updateMote(`data/wip/notes/${existingComment.id}`, null); | ||
} | ||
} | ||
// Get the BASE order of the comments (if any) and use those | ||
// as the starting point for an up to date order. | ||
const comments = parsedComments.map((c) => { | ||
// Look up the base comment | ||
let comment = storylineMoteBase?.data.wip?.notes?.[c.id]; | ||
if (!comment) { | ||
comment = storylineMoteWorking?.data.wip?.notes?.[c.id]; | ||
// @ts-expect-error - order is a required field, but it'll be re-added | ||
delete comment?.order; | ||
} | ||
assert(comment, `Comment ${c.id} not found in base or working mote`); | ||
return { ...comment, id: c.id }; | ||
}); | ||
trace('Updating comment order'); | ||
updateBsArrayOrder(comments); | ||
comments.forEach((comment) => { | ||
trace(`Updating comment ${comment.id} order to ${comment.order}`); | ||
updateMote(`data/wip/notes/${comment.id}/order`, comment.order); | ||
}); | ||
//#endregion | ||
updateWipChangesFromParsed(parsed, moteId, packed, trace); | ||
trace(`Writing changes`); | ||
@@ -252,0 +93,0 @@ await packed.writeChanges(); |
@@ -0,4 +1,4 @@ | ||
import { StorylineMote } from './cl2.shared.types.js'; | ||
import type { GameChanger } from './GameChanger.js'; | ||
import { type StorylineMote } from './cl2.storyline.types.js'; | ||
export declare function stringifyStoryline(mote: StorylineMote, packed: GameChanger): string; | ||
//# sourceMappingURL=cl2.storyline.stringify.d.ts.map |
import type { Gcdata } from './GameChanger.js'; | ||
import type { Crashlands2 } from './cl2.types.auto.js'; | ||
import type { ParsedBase, ParserResult } from './cl2.types.editor.js'; | ||
import type { Range } from './types.editor.js'; | ||
import type { BschemaRoot, Mote } from './types.js'; | ||
export declare const storylineSchemaId = "cl2_storyline"; | ||
export type StorylineData = Crashlands2.Schemas['cl2_storyline']; | ||
export type StorylineMote = Mote<StorylineData>; | ||
type CompletionsData = { | ||
type: 'glossary'; | ||
options: string[]; | ||
} | { | ||
type: 'stages'; | ||
options: string[]; | ||
} | { | ||
type: 'labels'; | ||
options: Set<string>; | ||
}; | ||
export interface StorylineUpdateResult extends ParserResult { | ||
parsed: ParsedBase & { | ||
description?: string; | ||
}; | ||
completions: (Range & CompletionsData)[]; | ||
import { ParserResult, StorylineMote } from './cl2.shared.types.js'; | ||
import type { BschemaRoot } from './types.js'; | ||
export interface StorylineUpdateResult extends ParserResult<{ | ||
description?: string; | ||
}> { | ||
} | ||
@@ -28,6 +11,4 @@ export declare function listStorylines(gcData: Gcdata): StorylineMote[]; | ||
export declare function getStorylineMote(gcData: Gcdata, moteId: string): StorylineMote | undefined; | ||
export declare function getStorylineMotes(gcData: Gcdata): StorylineMote[]; | ||
export declare function getStorylineSchema(gcData: Gcdata): BschemaRoot | undefined; | ||
export declare const linePatterns: string[]; | ||
export {}; | ||
//# sourceMappingURL=cl2.storyline.types.d.ts.map |
import { assert } from './assert.js'; | ||
import { arrayTagPattern } from './cl2.shared.types.js'; | ||
export const storylineSchemaId = 'cl2_storyline'; | ||
import { arrayTagPattern, storylineSchemaId, } from './cl2.shared.types.js'; | ||
export function listStorylines(gcData) { | ||
@@ -15,6 +14,2 @@ return gcData.listMotesBySchema(storylineSchemaId); | ||
} | ||
export function getStorylineMotes(gcData) { | ||
const motes = gcData.listMotesBySchema(storylineSchemaId); | ||
return motes; | ||
} | ||
export function getStorylineSchema(gcData) { | ||
@@ -21,0 +16,0 @@ return gcData.getSchema(storylineSchemaId); |
@@ -1,5 +0,7 @@ | ||
export * from './GameChanger.js'; | ||
export * from './cl2.comfort.js'; | ||
export * from './cl2.quest.js'; | ||
export * from './cl2.shared.types.js'; | ||
export * from './cl2.storyline.js'; | ||
export * from './cl2.types.auto.js'; | ||
export * from './GameChanger.js'; | ||
export * from './helpers.js'; | ||
@@ -6,0 +8,0 @@ export * from './types.cl2.rumpus.js'; |
@@ -1,5 +0,7 @@ | ||
export * from './GameChanger.js'; | ||
export * from './cl2.comfort.js'; | ||
export * from './cl2.quest.js'; | ||
export * from './cl2.shared.types.js'; | ||
export * from './cl2.storyline.js'; | ||
export * from './cl2.types.auto.js'; | ||
export * from './GameChanger.js'; | ||
export * from './helpers.js'; | ||
@@ -6,0 +8,0 @@ export * from './types.cl2.rumpus.js'; |
@@ -25,2 +25,12 @@ export interface Range { | ||
} | ||
export type ParsedWord = Range & { | ||
value: string; | ||
suggestions?: string[]; | ||
valid: boolean; | ||
}; | ||
export interface ParsedLineItem<V = string> { | ||
start: Position; | ||
end: Position; | ||
value: V; | ||
} | ||
//# sourceMappingURL=types.editor.d.ts.map |
import type { Glossary } from '@bscotch/cl2-string-server-shared'; | ||
import type { Gcdata } from './GameChanger.js'; | ||
import { ParsedLineItem } from './cl2.shared.types.js'; | ||
import { ParsedWord } from './cl2.types.editor.js'; | ||
import type { ParsedLineItem, ParsedWord } from './types.editor.js'; | ||
import { type Bschema, type Mote } from './types.js'; | ||
@@ -47,2 +46,3 @@ export declare function sizeOf(thing: any): number; | ||
export declare function includes<T>(arr: T[], value: any): value is T; | ||
export declare function cleanGameChangerString(str: string | undefined): string; | ||
//# sourceMappingURL=util.d.ts.map |
@@ -312,2 +312,5 @@ import { assert } from './assert.js'; | ||
} | ||
export function cleanGameChangerString(str) { | ||
return (str || '').replace(/\r?\n/g, ' / ').trim(); | ||
} | ||
//# sourceMappingURL=util.js.map |
{ | ||
"name": "@bscotch/gcdata", | ||
"version": "0.20.1", | ||
"version": "0.21.0", | ||
"license": "MIT", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1645116
127
30282