diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f8cc40..2b684141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Updated the activity bar icon for better visibility +### ⚡️ Optimizations + +- [#243](https://github.com/estruyf/vscode-front-matter/issues/243): Refactoring front matter parsing + ### 🐞 Fixes diff --git a/package.json b/package.json index aee0f9bd..f247d802 100644 --- a/package.json +++ b/package.json @@ -1468,4 +1468,4 @@ "dependencies": { "node-fetch": "^2.6.7" } -} \ No newline at end of file +} diff --git a/src/commands/Article.ts b/src/commands/Article.ts index bbac62a3..3fe20014 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -4,7 +4,6 @@ import * as vscode from 'vscode'; import { Field, TaxonomyType } from "../models"; import { format } from "date-fns"; import { ArticleHelper, Settings, SlugHelper } from '../helpers'; -import matter = require('gray-matter'); import { Notifications } from '../helpers/Notifications'; import { extname, basename, parse, dirname } from 'path'; import { COMMAND_NAME, DefaultFields } from '../constants'; @@ -13,6 +12,7 @@ import { ExplorerView } from '../explorerView/ExplorerView'; import { DateHelper } from '../helpers/DateHelper'; import { parseWinPath } from '../helpers/parseWinPath'; import { Telemetry } from '../helpers/Telemetry'; +import { ParsedFrontMatter } from '../parsers'; export class Article { @@ -103,7 +103,7 @@ export class Article { * Update the date in the front matter * @param article */ - public static updateDate(article: matter.GrayMatterFile, forceCreate: boolean = false) { + public static updateDate(article: ParsedFrontMatter, forceCreate: boolean = false) { article.data = ArticleHelper.updateDates(article.data); return article; } @@ -125,7 +125,7 @@ export class Article { ArticleHelper.update( editor, - updatedArticle as matter.GrayMatterFile + updatedArticle as ParsedFrontMatter ); } @@ -145,7 +145,7 @@ export class Article { private static setLastModifiedDateInner( document: vscode.TextDocument - ): matter.GrayMatterFile | undefined { + ): ParsedFrontMatter | undefined { const article = ArticleHelper.getFrontMatterFromDocument(document); if (!article) { @@ -343,7 +343,7 @@ export class Article { /** * Get the current article */ - private static getCurrent(): matter.GrayMatterFile | undefined { + private static getCurrent(): ParsedFrontMatter | undefined { const editor = vscode.window.activeTextEditor; if (!editor) { return; @@ -364,7 +364,7 @@ export class Article { * @param field * @param forceCreate */ - private static articleDate(article: matter.GrayMatterFile, field: string, forceCreate: boolean) { + private static articleDate(article: ParsedFrontMatter, field: string, forceCreate: boolean) { if (typeof article.data[field] !== "undefined" || forceCreate) { article.data[field] = Article.formatDate(new Date()); } diff --git a/src/commands/Settings.ts b/src/commands/Settings.ts index 4fbd5043..5f7b9538 100644 --- a/src/commands/Settings.ts +++ b/src/commands/Settings.ts @@ -1,10 +1,9 @@ import * as vscode from 'vscode'; -import * as matter from 'gray-matter'; import * as fs from 'fs'; import { TaxonomyType } from "../models"; import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, EXTENSION_NAME } from '../constants'; import { ArticleHelper, Settings as SettingsHelper, FilesHelper } from '../helpers'; -import { TomlEngine, getFmLanguage, getFormatOpts } from '../helpers/TomlEngine'; +import { FrontMatterParser } from '../parsers'; import { DumpOptions } from 'js-yaml'; import { Notifications } from '../helpers/Notifications'; @@ -90,10 +89,6 @@ export class Settings { const progressNr = allMdFiles.length/100; progress.report({ increment: 0}); - // Get language options - const language = getFmLanguage(); - const langOpts = getFormatOpts(language); - let i = 0; for (const file of allMdFiles) { progress.report({ increment: (++i/progressNr) }); @@ -102,10 +97,7 @@ export class Settings { const txtData = mdFile.getText(); if (txtData) { try { - const article = matter(txtData, { - ...TomlEngine, - ...langOpts - }); + const article = FrontMatterParser.fromFile(txtData); if (article && article.data) { const { data } = article; const mdTags = data["tags"]; @@ -218,13 +210,8 @@ export class Settings { progress.report({ increment: (++i/progressNr) }); const mdFile = fs.readFileSync(file.path, { encoding: "utf8" }); if (mdFile) { - const language = getFmLanguage(); - const langOpts = getFormatOpts(language); try { - const article = matter(mdFile, { - ...TomlEngine, - ...langOpts - }); + const article = FrontMatterParser.fromFile(mdFile); if (article && article.data) { const { data } = article; let taxonomies: string[] = data[matterProp]; @@ -239,9 +226,7 @@ export class Settings { data[matterProp] = [...new Set(taxonomies)].sort(); const spaces = vscode.window.activeTextEditor?.options?.tabSize; // Update the file - fs.writeFileSync(file.path, matter.stringify(article.content, article.data, { - ...TomlEngine, - ...langOpts, + fs.writeFileSync(file.path, FrontMatterParser.toFile(article.content, article.data, { indent: spaces || 2 } as DumpOptions as any), { encoding: "utf8" }); } diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index d70293ba..bb84db7b 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -1,11 +1,10 @@ import { MarkdownFoldingProvider } from './../providers/MarkdownFoldingProvider'; import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ContentType'; import * as vscode from 'vscode'; -import * as matter from "gray-matter"; import * as fs from "fs"; import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; import { DumpOptions } from 'js-yaml'; -import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine'; +import { FrontMatterParser, ParsedFrontMatter } from '../parsers'; import { Extension, Logger, Settings, SlugHelper } from '.'; import { format, parse } from 'date-fns'; import { Notifications } from './Notifications'; @@ -56,7 +55,7 @@ export class ArticleHelper { * @param editor * @param article */ - public static async update(editor: vscode.TextEditor, article: matter.GrayMatterFile) { + public static async update(editor: vscode.TextEditor, article: ParsedFrontMatter) { const update = this.generateUpdate(editor.document, article); await editor.edit(builder => builder.replace(update.range, update.newText)); @@ -66,7 +65,7 @@ export class ArticleHelper { * Generate the update to be applied to the article. * @param article */ - public static generateUpdate(document: vscode.TextDocument, article: matter.GrayMatterFile): vscode.TextEdit { + public static generateUpdate(document: vscode.TextDocument, article: ParsedFrontMatter): vscode.TextEdit { const nrOfLines = document.lineCount as number; const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(nrOfLines, 0)); const removeQuotes = Settings.get(SETTING_REMOVE_QUOTES) as string[]; @@ -117,9 +116,6 @@ export class ArticleHelper { const indentArray = Settings.get(SETTING_INDENT_ARRAY) as boolean; const commaSeparated = Settings.get(SETTING_COMMA_SEPARATED_FIELDS); - const language = getFmLanguage(); - const langOpts = getFormatOpts(language); - const spaces = vscode.window.activeTextEditor?.options?.tabSize; if (commaSeparated) { @@ -130,9 +126,7 @@ export class ArticleHelper { } } - return matter.stringify(content, data, ({ - ...TomlEngine, - ...langOpts, + return FrontMatterParser.toFile(content, data, ({ noArrayIndent: !indentArray, skipInvalid: true, noCompatMode: true, @@ -169,7 +163,7 @@ export class ArticleHelper { /** * Get date from front matter */ - public static getDate(article: matter.GrayMatterFile | null) { + public static getDate(article: ParsedFrontMatter | null) { if (!article) { return; } @@ -358,17 +352,12 @@ export class ArticleHelper { * @param fileContents * @returns */ - private static parseFile(fileContents: string, fileName: string): matter.GrayMatterFile | null { + private static parseFile(fileContents: string, fileName: string): ParsedFrontMatter | null { try { const commaSeparated = Settings.get(SETTING_COMMA_SEPARATED_FIELDS); if (fileContents) { - const language: string = getFmLanguage(); - const langOpts = getFormatOpts(language); - let article: matter.GrayMatterFile | null = matter(fileContents, { - ...TomlEngine, - ...langOpts - }); + let article = FrontMatterParser.fromFile(fileContents); if (article?.data) { if (commaSeparated) { diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index ad0501f0..5ac4b9ea 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -3,13 +3,13 @@ import { window, env as vscodeEnv, ProgressLocation } from 'vscode'; import { ArticleHelper } from '.'; import { Folders } from '../commands/Folders'; import { exec } from 'child_process'; -import matter = require('gray-matter'); import * as os from 'os'; import { join } from 'path'; import { Notifications } from './Notifications'; import ContentProvider from '../providers/ContentProvider'; import { Dashboard } from '../commands/Dashboard'; import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; +import { ParsedFrontMatter } from '../parsers'; export class CustomScript { @@ -117,7 +117,7 @@ export class CustomScript { }); } - private static async runScript(wsPath: string, article: matter.GrayMatterFile | null, contentPath: string, script: ICustomScript): Promise { + private static async runScript(wsPath: string, article: ParsedFrontMatter | null, contentPath: string, script: ICustomScript): Promise { return new Promise((resolve, reject) => { let articleData = ""; if (os.type() === "Windows_NT") { diff --git a/src/helpers/SeoHelper.ts b/src/helpers/SeoHelper.ts index 7e1b3487..2703cc4f 100644 --- a/src/helpers/SeoHelper.ts +++ b/src/helpers/SeoHelper.ts @@ -1,10 +1,10 @@ import * as vscode from 'vscode'; import { ArticleHelper } from '.'; -import matter = require('gray-matter'); +import { ParsedFrontMatter } from '../parsers'; export class SeoHelper { - public static checkLength(editor: vscode.TextEditor, collection: vscode.DiagnosticCollection, article: matter.GrayMatterFile, fieldName: string, length: number) { + public static checkLength(editor: vscode.TextEditor, collection: vscode.DiagnosticCollection, article: ParsedFrontMatter, fieldName: string, length: number) { const value = article.data[fieldName]; if (value.length > length) { const text = editor.document.getText(); diff --git a/src/helpers/TomlEngine.ts b/src/helpers/TomlEngine.ts deleted file mode 100644 index be0f4d97..00000000 --- a/src/helpers/TomlEngine.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as toml from '@iarna/toml'; -import { SETTING_FRONTMATTER_TYPE } from '../constants'; -import { Settings } from './SettingsHelper'; - -export const getFmLanguage = (): string => { - const language = Settings.get(SETTING_FRONTMATTER_TYPE) as string || "YAML"; - return language.toLowerCase(); -}; - -export const getFormatOpts = (format: string): { language: string, delimiters: string | [string, string] | undefined } => { - const formats: { [prop: string]: { language: string, delimiters: string | [string, string] | undefined }} = { - yaml: { language: 'yaml', delimiters: '---' }, - toml: { language: 'toml', delimiters: '+++' }, - json: { language: 'json', delimiters: '---' }, - }; - - return formats[format]; -}; - -export const TomlEngine = { - engines: { - toml: { - parse: (value: string) => { - return toml.parse(value); - }, - stringify: (value: any) => { - return toml.stringify(value); - } - } - } -}; \ No newline at end of file diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 0942eda9..efc40674 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -9,6 +9,7 @@ export * from './FrameworkDetector'; export * from './GroupBy'; export * from './ImageHelper'; export * from './Logger'; +export * from './MediaHelpers'; export * from './MediaLibrary'; export * from './MessageHelper'; export * from './Notifications'; @@ -19,7 +20,7 @@ export * from './SettingsHelper'; export * from './SlugHelper'; export * from './Sorting'; export * from './StringHelpers'; -export * from './TomlEngine'; +export * from './Telemetry'; export * from './decodeBase64Image'; export * from './getNonce'; export * from './isValidFile'; diff --git a/src/parsers/FrontMatterParser.ts b/src/parsers/FrontMatterParser.ts new file mode 100644 index 00000000..155678d4 --- /dev/null +++ b/src/parsers/FrontMatterParser.ts @@ -0,0 +1,51 @@ +import { Engines, getFormatOpts } from "."; +import * as matter from "gray-matter"; +import { Settings } from "../helpers"; +import { SETTING_FRONTMATTER_TYPE } from "../constants"; + +export interface Format { + language: string; + delimiters: string | [string, string] | undefined; +} + +export interface ParsedFrontMatter { + data: { [key: string]: any }; + content: string +} + +export class FrontMatterParser { + + public static fromFile(content: string): ParsedFrontMatter { + const format = getFormatOpts(this.getLanguage()); + const result = matter(content, { ...Engines, ...format }); + // in the absent of a body when serializing an entry we use an empty one + // when calling `toFile`, so we don't want to add it when parsing. + + return { + data: { ...result.data }, + content: (result.content.trim() && result.content) + }; + } + + public static toFile( + content: string, + metadata: Object, + options?: any + ) { + // Stringify to YAML if the format was not set + const format = getFormatOpts(this.getLanguage()); + + const trimLastLineBreak = content.slice(-1) !== '\n'; + const file = matter.stringify(content, metadata, { + ...Engines, + ...options, + ...format + }); + return trimLastLineBreak && file.slice(-1) === '\n' ? file.substring(0, file.length - 1) : file; + } + + private static getLanguage() { + const language = Settings.get(SETTING_FRONTMATTER_TYPE) as string || "YAML"; + return language.toLowerCase(); + } +} \ No newline at end of file diff --git a/src/parsers/ParserEngines.ts b/src/parsers/ParserEngines.ts new file mode 100644 index 00000000..ab57f80c --- /dev/null +++ b/src/parsers/ParserEngines.ts @@ -0,0 +1,25 @@ +import * as toml from '@iarna/toml'; +import { Format } from '.'; + +export const getFormatOpts = (format: string): Format => { + const formats: { [prop: string]: Format} = { + yaml: { language: 'yaml', delimiters: '---' }, + toml: { language: 'toml', delimiters: '+++' }, + json: { language: 'json', delimiters: '---' }, + }; + + return formats[format]; +}; + +export const Engines = { + engines: { + toml: { + parse: (value: string) => { + return toml.parse(value); + }, + stringify: (value: any) => { + return toml.stringify(value); + } + } + } +} \ No newline at end of file diff --git a/src/parsers/index.ts b/src/parsers/index.ts new file mode 100644 index 00000000..07a2f694 --- /dev/null +++ b/src/parsers/index.ts @@ -0,0 +1,2 @@ +export * from './FrontMatterParser'; +export * from './ParserEngines';