Commit e842e313 authored by derek-knox's avatar derek-knox

Use gray-matter vs. prev approach at MR noise tradeoff

We will iterate on reducing this noise. The eof
noise has been solved for.
parent 8f2f7442
......@@ -68,7 +68,7 @@ export default {
return templatedContent;
},
onInputChange(newVal) {
this.parsedSource.sync(newVal, this.isWysiwygMode);
this.parsedSource.syncContent(newVal, this.isWysiwygMode);
this.isModified = this.parsedSource.isModified();
},
onModeChange(mode) {
......
import getFrontMatterLanguageDefinition from './parse_source_file_language_support';
import grayMatter from 'gray-matter';
const parseSourceFile = (raw, options = { frontMatterLanguage: 'yaml' }) => {
const { open, close } = getFrontMatterLanguageDefinition(options.frontMatterLanguage);
const anyChar = '[\\s\\S]';
const frontMatterBlock = `^${open}$${anyChar}*?^${close}$`;
const frontMatterRegex = new RegExp(`${frontMatterBlock}`, 'm');
const preGroupedRegex = new RegExp(`(${anyChar}*?)(${frontMatterBlock})(\\s*)(${anyChar}*)`, 'm'); // preFrontMatter, frontMatter, spacing, and content
let initial;
let editable;
const parseSourceFile = raw => {
const remake = source => grayMatter(source, {});
const hasFrontMatter = source => frontMatterRegex.test(source);
let editable = remake(raw);
const buildPayload = (source, header, spacing, body) => {
return { raw: source, header, spacing, body };
};
const parse = source => {
if (hasFrontMatter(source)) {
const match = source.match(preGroupedRegex);
const [, preFrontMatter, frontMatter, spacing, content] = match;
const header = preFrontMatter + frontMatter;
return buildPayload(source, header, spacing, content);
const syncContent = (newVal, isBody) => {
if (isBody) {
editable.content = newVal;
} else {
editable = remake(newVal);
}
return buildPayload(source, '', '', source);
};
const syncEditable = () => {
/*
We re-parse as markdown editing could have added non-body changes (preFrontMatter, frontMatter, or spacing).
Re-parsing additionally gets us the desired body that was extracted from the potentially mutated editable.raw
*/
editable = parse(editable.raw);
};
const trimmedEditable = () => grayMatter.stringify(editable).trim();
const refreshEditableRaw = () => {
editable.raw = `${editable.header}${editable.spacing}${editable.body}`;
};
const content = (isBody = false) => (isBody ? editable.content.trim() : trimmedEditable()); // gray-matter internally adds an eof newline so we trim to bypass, open issue: https://github.com/jonschlinkert/gray-matter/issues/96
const sync = (newVal, isBodyToRaw) => {
const editableKey = isBodyToRaw ? 'body' : 'raw';
editable[editableKey] = newVal;
const matter = () => editable.matter;
if (isBodyToRaw) {
refreshEditableRaw();
}
syncEditable();
const syncMatter = newMatter => {
const targetMatter = newMatter.replace(/---/gm, ''); // TODO dynamic delimiter removal vs. hard code
const currentMatter = matter();
const currentContent = content();
const newSource = currentContent.replace(currentMatter, targetMatter);
syncContent(newSource);
editable.matter = newMatter;
};
const frontMatter = () => editable.header;
const matterObject = () => editable.data;
const setFrontMatter = val => {
editable.header = val;
refreshEditableRaw();
const syncMatterObject = obj => {
editable.data = obj;
};
const content = (isBody = false) => {
const editableKey = isBody ? 'body' : 'raw';
return editable[editableKey];
};
const isModified = () => initial.raw !== editable.raw;
initial = parse(raw);
editable = parse(raw);
const isModified = () => trimmedEditable() !== raw;
return {
frontMatter,
setFrontMatter,
matter,
syncMatter,
matterObject,
syncMatterObject,
content,
syncContent,
isModified,
sync,
};
};
......
const frontMatterLanguageDefinitions = [
{ name: 'yaml', open: '---', close: '---' },
{ name: 'toml', open: '\\+\\+\\+', close: '\\+\\+\\+' },
{ name: 'json', open: '{', close: '}' },
];
const getFrontMatterLanguageDefinition = name => {
const languageDefinition = frontMatterLanguageDefinitions.find(def => def.name === name);
if (!languageDefinition) {
throw new Error(`Unsupported front matter language: ${name}`);
}
return languageDefinition;
};
export default getFrontMatterLanguageDefinition;
......@@ -81,7 +81,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
it('updates parsedSource with new content', () => {
const newContent = 'New content';
const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'sync');
const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'syncContent');
findRichContentEditor().vm.$emit('input', newContent);
......
export const sourceContentHeaderYAML = `---
layout: handbook-page-toc
title: Handbook
twitter_image: '/images/tweets/handbook-gitlab.png'
twitter_image: /images/tweets/handbook-gitlab.png
---`;
export const sourceContentHeaderTOML = `+++
layout: "handbook-page-toc"
title: "Handbook"
twitter_image: "/images/tweets/handbook-gitlab.png"
+++`;
export const sourceContentHeaderJSON = `{
"layout": "handbook-page-toc",
"title": "Handbook",
"twitter_image": "/images/tweets/handbook-gitlab.png",
}`;
export const sourceContentSpacing = `
`;
export const sourceContentHeaderObjYAML = {
layout: 'handbook-page-toc',
title: 'Handbook',
twitter_image: '/images/tweets/handbook-gitlab.png',
};
export const sourceContentSpacing = `\n`;
export const sourceContentBody = `## On this page
{:.no_toc .hidden-md .hidden-lg}
- TOC
{:toc .hidden-md .hidden-lg}
![image](path/to/image1.png)
`;
![image](path/to/image1.png)`;
export const sourceContentYAML = `${sourceContentHeaderYAML}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentTOML = `${sourceContentHeaderTOML}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentJSON = `${sourceContentHeaderJSON}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentTitle = 'Handbook';
export const username = 'gitlabuser';
......
import getFrontMatterLanguageDefinition from '~/static_site_editor/services/parse_source_file_language_support';
describe('static_site_editor/services/parse_source_file_language_support', () => {
describe('getFrontMatterLanguageDefinition', () => {
it.each`
languageName
${'yaml'}
${'toml'}
${'json'}
${'abcd'}
`('returns $hasMatch when provided $languageName', ({ languageName }) => {
try {
const definition = getFrontMatterLanguageDefinition(languageName);
expect(definition.name).toBe(languageName);
} catch (error) {
expect(error.message).toBe(`Unsupported front matter language: ${languageName}`);
}
});
});
});
import {
sourceContentYAML as content,
sourceContentTOML as tomlContent,
sourceContentJSON as jsonContent,
sourceContentHeaderYAML as yamlFrontMatter,
sourceContentHeaderTOML as tomlFrontMatter,
sourceContentHeaderJSON as jsonFrontMatter,
sourceContentHeaderObjYAML as yamlFrontMatterObj,
sourceContentBody as body,
} from '../mock_data';
......@@ -18,20 +15,15 @@ describe('static_site_editor/services/parse_source_file', () => {
const newContentComplex = `${contentComplex} ${edit}`;
describe('unmodified front matter', () => {
const yamlOptions = { frontMatterLanguage: 'yaml' };
it.each`
parsedSource | targetFrontMatter
${parseSourceFile(content)} | ${yamlFrontMatter}
${parseSourceFile(contentComplex)} | ${yamlFrontMatter}
${parseSourceFile(content, yamlOptions)} | ${yamlFrontMatter}
${parseSourceFile(contentComplex, yamlOptions)} | ${yamlFrontMatter}
${parseSourceFile(tomlContent, { frontMatterLanguage: 'toml' })} | ${tomlFrontMatter}
${parseSourceFile(jsonContent, { frontMatterLanguage: 'json' })} | ${jsonFrontMatter}
parsedSource | targetFrontMatter
${parseSourceFile(content)} | ${yamlFrontMatter}
${parseSourceFile(contentComplex)} | ${yamlFrontMatter}
`(
'returns $targetFrontMatter when frontMatter queried',
({ parsedSource, targetFrontMatter }) => {
expect(parsedSource.frontMatter()).toBe(targetFrontMatter);
expect(targetFrontMatter).toContain(parsedSource.matter());
expect(parsedSource.matterObject()).toEqual(yamlFrontMatterObj);
},
);
});
......@@ -63,6 +55,7 @@ describe('static_site_editor/services/parse_source_file', () => {
describe('modified front matter', () => {
const newYamlFrontMatter = '---\nnewKey: newVal\n---';
const newYamlFrontMatterObj = { newKey: 'newVal' };
const contentWithNewFrontMatter = content.replace(yamlFrontMatter, newYamlFrontMatter);
const contentComplexWithNewFrontMatter = contentComplex.replace(
yamlFrontMatter,
......@@ -76,11 +69,12 @@ describe('static_site_editor/services/parse_source_file', () => {
`(
'returns the correct front matter and modified content',
({ parsedSource, targetContent }) => {
expect(parsedSource.frontMatter()).toBe(yamlFrontMatter);
expect(yamlFrontMatter).toContain(parsedSource.matter());
parsedSource.setFrontMatter(newYamlFrontMatter);
parsedSource.syncMatter(newYamlFrontMatter);
expect(parsedSource.frontMatter()).toBe(newYamlFrontMatter);
expect(parsedSource.matter()).toBe(newYamlFrontMatter);
expect(parsedSource.matterObject()).toEqual(newYamlFrontMatterObj);
expect(parsedSource.content()).toBe(targetContent);
},
);
......@@ -99,7 +93,7 @@ describe('static_site_editor/services/parse_source_file', () => {
`(
'returns $isModified after a $targetRaw sync',
({ parsedSource, isModified, targetRaw, targetBody }) => {
parsedSource.sync(targetRaw);
parsedSource.syncContent(targetRaw);
expect(parsedSource.isModified()).toBe(isModified);
expect(parsedSource.content()).toBe(targetRaw);
......
......@@ -3813,13 +3813,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@=3.1.0, debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
......@@ -3834,6 +3827,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "^2.1.1"
debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
decamelize-keys@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
......@@ -5138,14 +5138,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@^1.0.0:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"
follow-redirects@^1.10.0:
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
......@@ -5588,6 +5581,16 @@ graphql@^14.7.0:
dependencies:
iterall "^1.2.2"
gray-matter@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.2.tgz#9aa379e3acaf421193fce7d2a28cebd4518ac454"
integrity sha512-7hB/+LxrOjq/dd8APlK0r24uL/67w7SkYnfwhNFwg/VDIGWGmduTDYf3WNstLW2fbbmRwrDGCVSJ2isuf2+4Hw==
dependencies:
js-yaml "^3.11.0"
kind-of "^6.0.2"
section-matter "^1.0.0"
strip-bom-string "^1.0.0"
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
......@@ -7113,7 +7116,7 @@ js-cookie@^2.2.1:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@~3.13.1:
js-yaml@^3.11.0, js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@~3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
......@@ -10499,6 +10502,14 @@ scss-tokenizer@^0.2.3:
js-base64 "^2.1.8"
source-map "^0.4.2"
section-matter@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"
integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==
dependencies:
extend-shallow "^2.0.1"
kind-of "^6.0.0"
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
......@@ -11161,6 +11172,11 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
strip-bom-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment