| | import { |
| | Tag, |
| | isTruthy, |
| | Value, |
| | TokenizationError, |
| | type TagToken, |
| | type Context, |
| | type Emitter, |
| | type Template, |
| | type TopLevelToken, |
| | } from 'liquidjs' |
| | import versionSatisfiesRange from '@/versions/lib/version-satisfies-range' |
| | import supportedOperators, { |
| | type IfversionSupportedOperator, |
| | } from './ifversion-supported-operators' |
| |
|
| | interface Branch { |
| | cond: string |
| | templates: Template[] |
| | } |
| |
|
| | interface VersionObj { |
| | shortName: string |
| | hasNumberedReleases?: boolean |
| | currentRelease?: string |
| | internalLatestRelease?: string |
| | } |
| |
|
| | const SyntaxHelp = |
| | "Syntax Error in 'ifversion' with range - Valid syntax: ifversion [plan] [operator] [releaseNumber]" |
| |
|
| | const supportedOperatorsRegex = new RegExp(`[${supportedOperators.join('')}]`) |
| | const releaseRegex = /\d\d?\.\d\d?/ |
| | const notRegex = /(?:^|\s)not\s/ |
| |
|
| | |
| | |
| | |
| | |
| | export default class Ifversion extends Tag { |
| | tagToken: TagToken |
| | branches: Branch[] |
| | elseTemplates: Template[] |
| | currentVersionObj: VersionObj | null = null |
| |
|
| | |
| | constructor(tagToken: TagToken, remainTokens: TopLevelToken[], liquid: any) { |
| | super(tagToken, remainTokens, liquid) |
| |
|
| | this.tagToken = tagToken |
| | this.branches = [] |
| | this.elseTemplates = [] |
| |
|
| | let p: Template[] |
| | const stream = this.liquid.parser |
| | .parseStream(remainTokens) |
| | .on('start', () => |
| | this.branches.push({ |
| | cond: tagToken.args, |
| | templates: (p = []), |
| | }), |
| | ) |
| | .on('tag:elsif', (token: any) => { |
| | this.branches.push({ |
| | cond: token.args, |
| | templates: (p = []), |
| | }) |
| | }) |
| | .on('tag:else', () => (p = this.elseTemplates)) |
| | .on('tag:endif', () => stream.stop()) |
| | .on('template', (tpl: Template) => p.push(tpl)) |
| | .on('end', () => { |
| | throw new Error(`tag ${tagToken.getText()} not closed`) |
| | }) |
| |
|
| | stream.start() |
| | } |
| |
|
| | |
| | |
| | *render(ctx: Context, emitter: Emitter): Generator<any, void, unknown> { |
| | const r = this.liquid.renderer |
| |
|
| | this.currentVersionObj = (ctx.environments as any).currentVersionObj |
| |
|
| | for (const branch of this.branches) { |
| | let resolvedBranchCond = branch.cond |
| |
|
| | |
| | resolvedBranchCond = this.handleNots(resolvedBranchCond) |
| |
|
| | |
| | |
| | resolvedBranchCond = this.handleOperators(resolvedBranchCond) |
| |
|
| | |
| | |
| | |
| | if ((ctx.environments as any).markdownRequested) { |
| | resolvedBranchCond = this.handleVersionNames(resolvedBranchCond) |
| | } |
| |
|
| | |
| | const cond = yield new Value(resolvedBranchCond, this.liquid).value(ctx, ctx.opts.lenientIf) |
| |
|
| | if (isTruthy(cond, ctx)) { |
| | yield r.renderTemplates(branch.templates, ctx, emitter) |
| | return |
| | } |
| | } |
| | yield r.renderTemplates(this.elseTemplates, ctx, emitter) |
| | } |
| |
|
| | handleNots(resolvedBranchCond: string): string { |
| | if (!notRegex.test(resolvedBranchCond)) return resolvedBranchCond |
| |
|
| | const condArray = resolvedBranchCond.split(' ') |
| |
|
| | |
| | const notIndex = condArray.findIndex((el: string) => el === 'not') |
| |
|
| | |
| | const condParts = condArray.slice(notIndex, notIndex + 2) |
| |
|
| | |
| | const versionToEvaluate = condParts[1] |
| |
|
| | |
| | |
| | |
| | const resolvedBoolean = !(versionToEvaluate === this.currentVersionObj!.shortName) |
| |
|
| | |
| | resolvedBranchCond = resolvedBranchCond.replace(condParts.join(' '), String(resolvedBoolean)) |
| |
|
| | |
| | if (notRegex.test(resolvedBranchCond)) { |
| | return this.handleNots(resolvedBranchCond) |
| | } |
| |
|
| | return resolvedBranchCond |
| | } |
| |
|
| | handleOperators(resolvedBranchCond: string): string { |
| | if (!supportedOperatorsRegex.test(resolvedBranchCond)) return resolvedBranchCond |
| |
|
| | |
| | const condArray = resolvedBranchCond.split(' ') |
| |
|
| | |
| | const operatorIndex = condArray.findIndex((el: string) => |
| | supportedOperators.find((op: string) => el === op), |
| | ) |
| |
|
| | |
| | const condParts = condArray.slice(operatorIndex - 1, operatorIndex + 2) |
| |
|
| | |
| | const [versionShortName, operator, releaseToEvaluate] = condParts |
| |
|
| | |
| | const syntaxError = |
| | !supportedOperators.includes(operator as IfversionSupportedOperator) || |
| | !releaseRegex.test(releaseToEvaluate) |
| |
|
| | if (syntaxError) { |
| | throw new TokenizationError(SyntaxHelp, this.tagToken) |
| | } |
| |
|
| | if (!this.currentVersionObj) { |
| | console.warn( |
| | ` |
| | If this happens, it means the context prepared for rendering Liquid |
| | did not supply an object called 'currentVersionObj'. |
| | To fix the error, find the code that prepares the context before |
| | calling 'liquid.parseAndRender' and make sure there's an object |
| | called 'currentVersionObj' included there. |
| | ` |
| | .replace(/\n\s+/g, ' ') |
| | .trim(), |
| | ) |
| | throw new Error('currentVersionObj not found in environment context.') |
| | } |
| |
|
| | const currentRelease = this.currentVersionObj.hasNumberedReleases |
| | ? this.currentVersionObj.currentRelease |
| | : this.currentVersionObj.internalLatestRelease |
| |
|
| | let resolvedBoolean: boolean |
| | if (operator === '!=') { |
| | |
| | |
| | resolvedBoolean = |
| | versionShortName === this.currentVersionObj!.shortName |
| | ? releaseToEvaluate !== currentRelease |
| | : true |
| | } else { |
| | |
| | |
| | resolvedBoolean = |
| | versionShortName === this.currentVersionObj!.shortName |
| | ? versionSatisfiesRange(currentRelease!, `${operator}${releaseToEvaluate}`) |
| | : false |
| | } |
| |
|
| | |
| | resolvedBranchCond = resolvedBranchCond.replace(condParts.join(' '), String(resolvedBoolean)) |
| |
|
| | |
| | if (supportedOperatorsRegex.test(resolvedBranchCond)) { |
| | return this.handleOperators(resolvedBranchCond) |
| | } |
| |
|
| | return resolvedBranchCond |
| | } |
| |
|
| | handleVersionNames(resolvedBranchCond: string): string { |
| | if (!this.currentVersionObj) { |
| | console.warn('currentVersionObj not found in ifversion context.') |
| | return resolvedBranchCond |
| | } |
| |
|
| | |
| | const tokens = resolvedBranchCond.split(/\s+/) |
| | const processedTokens = tokens.map((token: string) => { |
| | |
| | const versionShortNames = ['fpt', 'ghec', 'ghes', 'ghae'] |
| | if (versionShortNames.includes(token)) { |
| | |
| | |
| | return token === this.currentVersionObj!.shortName ? 'true' : 'false' |
| | } |
| | |
| | return token |
| | }) |
| |
|
| | return processedTokens.join(' ') |
| | } |
| | } |
| |
|