Added lots of testcases
This commit is contained in:
parent
b78122ac70
commit
1f207fd7c8
0
package-lock.json
generated
Normal file → Executable file
0
package-lock.json
generated
Normal file → Executable file
2
package.json
Normal file → Executable file
2
package.json
Normal file → Executable file
|
@ -5,7 +5,7 @@
|
||||||
"main": "src/mod.mjs",
|
"main": "src/mod.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node ./test/spec.mjs './test/examples/*.mjs'"
|
"test": "./scripts/run-tests"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|
8
scripts/run-tests
Executable file
8
scripts/run-tests
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
node ./src/spec/string-to-ast.mjs;
|
||||||
|
node ./src/spec/ast-to-data.mjs;
|
||||||
|
node ./src/spec/data-to-ast.mjs;
|
||||||
|
node ./src/spec/ast-to-string.mjs;
|
59
src/Lexer.mjs
Normal file → Executable file
59
src/Lexer.mjs
Normal file → Executable file
|
@ -1,25 +1,23 @@
|
||||||
import chevrotain from "chevrotain";
|
import chevrotain from "chevrotain";
|
||||||
const { createToken, Lexer } = chevrotain;
|
const { createToken, Lexer } = chevrotain;
|
||||||
|
|
||||||
function getValueParser(...stoppers) {
|
function valueParser(text, startOffset) {
|
||||||
return function exec(text, startOffset) {
|
for (let i = startOffset; i < text.length; i += 1) {
|
||||||
for (let i = startOffset; i < text.length; i += 1) {
|
if (text[i] === '\\') {
|
||||||
if (text[i] === '\\') {
|
i += 1;
|
||||||
i += 1;
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stoppers.includes(text[i])) {
|
|
||||||
if (i !== startOffset) {
|
|
||||||
return [text.substring(startOffset, i)];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [text.substr(startOffset)];
|
if (['\n', '#'].includes(text[i])) {
|
||||||
|
if (i !== startOffset) {
|
||||||
|
return [text.substring(startOffset, i)];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [text.substr(startOffset)];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SectionHeading = createToken({
|
export const SectionHeading = createToken({
|
||||||
|
@ -33,53 +31,44 @@ export const Property = createToken({
|
||||||
});
|
});
|
||||||
export const Value = createToken({
|
export const Value = createToken({
|
||||||
name: "Value",
|
name: "Value",
|
||||||
pattern: { exec: getValueParser('\n', '#') },
|
pattern: { exec: valueParser },
|
||||||
line_breaks: true,
|
line_breaks: true,
|
||||||
pop_mode: true,
|
pop_mode: true,
|
||||||
});
|
});
|
||||||
export const CommentStartNewline = createToken({
|
|
||||||
name: "CommentStartNewline",
|
|
||||||
pattern: '\n#',
|
|
||||||
push_mode: "comment_mode",
|
|
||||||
});
|
|
||||||
export const CommentStart = createToken({
|
|
||||||
name: "CommentStart",
|
|
||||||
pattern: '#',
|
|
||||||
push_mode: "comment_mode",
|
|
||||||
});
|
|
||||||
export const Comment = createToken({
|
export const Comment = createToken({
|
||||||
name: "Comment",
|
name: "Comment",
|
||||||
pattern: { exec: getValueParser('\n') },
|
pattern: /#.*/,
|
||||||
line_breaks: true,
|
line_breaks: false,
|
||||||
pop_mode: true,
|
|
||||||
});
|
});
|
||||||
export const WhiteSpace = createToken({
|
export const WhiteSpace = createToken({
|
||||||
name: "WhiteSpace",
|
name: "WhiteSpace",
|
||||||
pattern: /\s+/,
|
pattern: /\s+/,
|
||||||
group: Lexer.SKIPPED,
|
group: Lexer.SKIPPED,
|
||||||
});
|
});
|
||||||
|
export const Newline = createToken({
|
||||||
|
name: "Newline",
|
||||||
|
pattern: /\n+/,
|
||||||
|
});
|
||||||
|
|
||||||
export const tokens = [
|
export const tokens = [
|
||||||
CommentStart,
|
|
||||||
CommentStartNewline,
|
|
||||||
Value,
|
Value,
|
||||||
Comment,
|
Comment,
|
||||||
SectionHeading,
|
SectionHeading,
|
||||||
Property,
|
Property,
|
||||||
WhiteSpace,
|
WhiteSpace,
|
||||||
|
Newline,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default new Lexer({
|
export default new Lexer({
|
||||||
defaultMode: "line_mode",
|
defaultMode: "line_mode",
|
||||||
modes: {
|
modes: {
|
||||||
line_mode: [
|
line_mode: [
|
||||||
CommentStartNewline,
|
Comment,
|
||||||
CommentStart,
|
|
||||||
SectionHeading,
|
SectionHeading,
|
||||||
Property,
|
Property,
|
||||||
|
Newline,
|
||||||
WhiteSpace,
|
WhiteSpace,
|
||||||
],
|
],
|
||||||
value_mode: [ Value ],
|
value_mode: [ Value ],
|
||||||
comment_mode: [ Comment ],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
22
src/Parser.mjs
Normal file → Executable file
22
src/Parser.mjs
Normal file → Executable file
|
@ -3,10 +3,9 @@ const { CstParser } = chevrotain;
|
||||||
import {
|
import {
|
||||||
tokens,
|
tokens,
|
||||||
Comment,
|
Comment,
|
||||||
CommentStart,
|
|
||||||
CommentStartNewline,
|
|
||||||
Property,
|
Property,
|
||||||
SectionHeading,
|
SectionHeading,
|
||||||
|
Newline,
|
||||||
Value,
|
Value,
|
||||||
} from "./Lexer.mjs";
|
} from "./Lexer.mjs";
|
||||||
|
|
||||||
|
@ -15,25 +14,22 @@ export default class UnitFileParser extends CstParser {
|
||||||
super(tokens);
|
super(tokens);
|
||||||
const $ = this;
|
const $ = this;
|
||||||
|
|
||||||
$.RULE("commentLine", () => {
|
$.RULE("value", () => {
|
||||||
$.CONSUME(CommentStartNewline);
|
$.CONSUME(Value);
|
||||||
$.CONSUME(Comment);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$.RULE("comment", () => {
|
$.RULE("comment", () => {
|
||||||
$.CONSUME(CommentStart);
|
|
||||||
$.CONSUME(Comment);
|
$.CONSUME(Comment);
|
||||||
});
|
});
|
||||||
|
|
||||||
$.RULE("value", () => {
|
|
||||||
$.CONSUME(Value);
|
|
||||||
});
|
|
||||||
|
|
||||||
$.RULE("sectionHeadingStatement", () => {
|
$.RULE("sectionHeadingStatement", () => {
|
||||||
$.CONSUME(SectionHeading);
|
$.CONSUME(SectionHeading);
|
||||||
$.OPTION(() => {
|
$.OPTION(() => {
|
||||||
$.SUBRULE($.comment);
|
$.SUBRULE($.comment);
|
||||||
});
|
});
|
||||||
|
$.OPTION1(() => {
|
||||||
|
$.CONSUME(Newline);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$.RULE("propertyStatement", () => {
|
$.RULE("propertyStatement", () => {
|
||||||
|
@ -47,7 +43,7 @@ export default class UnitFileParser extends CstParser {
|
||||||
$.RULE("sectionStatement", () => {
|
$.RULE("sectionStatement", () => {
|
||||||
$.OR([
|
$.OR([
|
||||||
{ ALT: () => $.SUBRULE($.propertyStatement) },
|
{ ALT: () => $.SUBRULE($.propertyStatement) },
|
||||||
{ ALT: () => $.SUBRULE($.commentLine) },
|
{ ALT: () => $.SUBRULE($.comment) },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,6 +51,9 @@ export default class UnitFileParser extends CstParser {
|
||||||
$.SUBRULE($.sectionHeadingStatement);
|
$.SUBRULE($.sectionHeadingStatement);
|
||||||
$.MANY(() => {
|
$.MANY(() => {
|
||||||
$.SUBRULE($.sectionStatement);
|
$.SUBRULE($.sectionStatement);
|
||||||
|
$.OPTION(() => {
|
||||||
|
$.CONSUME(Newline);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -63,6 +62,7 @@ export default class UnitFileParser extends CstParser {
|
||||||
$.OR([
|
$.OR([
|
||||||
{ ALT: () => $.SUBRULE($.section) },
|
{ ALT: () => $.SUBRULE($.section) },
|
||||||
{ ALT: () => $.SUBRULE($.comment) },
|
{ ALT: () => $.SUBRULE($.comment) },
|
||||||
|
{ ALT: () => $.CONSUME(Newline) },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
37
src/ast-to-data.mjs
Executable file
37
src/ast-to-data.mjs
Executable file
|
@ -0,0 +1,37 @@
|
||||||
|
function nodeToData(node) {
|
||||||
|
switch (node.type) {
|
||||||
|
case "unitFile":
|
||||||
|
return node.sections.map(nodeToData);
|
||||||
|
case "section":
|
||||||
|
return {
|
||||||
|
title: node.title,
|
||||||
|
settings: node.body
|
||||||
|
.filter(n => n.type === "setting")
|
||||||
|
.reduce(
|
||||||
|
(acc, setting) => {
|
||||||
|
if (acc[setting.name]) {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[setting.name]: [acc[setting.name], setting.value].flat(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[setting.name]: nodeToData(setting),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
case "setting":
|
||||||
|
return node.value;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unrecognized node type: ${node.type}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function astToData(file) {
|
||||||
|
return nodeToData(file);
|
||||||
|
}
|
23
src/ast-to-string.mjs
Executable file
23
src/ast-to-string.mjs
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
function nodeToString(node) {
|
||||||
|
switch (node.type) {
|
||||||
|
case "unitFile":
|
||||||
|
return `${(node.comments || []).map(nodeToString).join('\n')}
|
||||||
|
${(node.sections || []).map(nodeToString).join('\n')}`;
|
||||||
|
case "comment":
|
||||||
|
return `# ${comment.value}`;
|
||||||
|
case "section":
|
||||||
|
const titleComment = ` ${node.titleComment ? nodeToString(node.titleComment) : ''}`;
|
||||||
|
return `[${node.title}]${titleComment}
|
||||||
|
${(node.body || []).map(nodeToString).join('\n')}`;
|
||||||
|
case "setting":
|
||||||
|
const comment = ` ${node.comment ? nodeToString(node.comment) : ''}`;
|
||||||
|
return `${node.name}=${node.value}${comment}`;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unrecognized node type: ${node.type}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function astToString(file) {
|
||||||
|
return nodeToString(file);
|
||||||
|
}
|
40
src/data-to-ast.mjs
Executable file
40
src/data-to-ast.mjs
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
function sectionToAst(section) {
|
||||||
|
return {
|
||||||
|
type: "section",
|
||||||
|
title: section.title,
|
||||||
|
body: Object.keys(section.settings)
|
||||||
|
.reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
const val = section.settings[key];
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
return [
|
||||||
|
...acc,
|
||||||
|
...val.map(value => ({
|
||||||
|
type: "setting",
|
||||||
|
name: key,
|
||||||
|
value,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...acc,
|
||||||
|
{
|
||||||
|
type: "setting",
|
||||||
|
name: key,
|
||||||
|
value: val,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function dataToAst(file) {
|
||||||
|
return {
|
||||||
|
type: "unitFile",
|
||||||
|
comments: [],
|
||||||
|
sections: file.map(sectionToAst),
|
||||||
|
};
|
||||||
|
}
|
6
src/data-to-string.mjs
Executable file
6
src/data-to-string.mjs
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
import astToString from "./ast-to-string.mjs";
|
||||||
|
import dataToAst from "./data-to-ast.mjs";
|
||||||
|
|
||||||
|
export default function dataToString(data) {
|
||||||
|
return astToString(dataToAst(data));
|
||||||
|
}
|
35
src/mod.mjs
Normal file → Executable file
35
src/mod.mjs
Normal file → Executable file
|
@ -1,30 +1,11 @@
|
||||||
export { default as Lexer } from "./Lexer.mjs";
|
export { default as Lexer } from "./Lexer.mjs";
|
||||||
export { default as Parser } from "./Parser.mjs";
|
export { default as Parser } from "./Parser.mjs";
|
||||||
export { default as visitorCreator } from "./visitor.mjs";
|
export { default as visitorCreator } from "./visitor-creator.mjs";
|
||||||
|
|
||||||
import Parser from "./Parser.mjs";
|
export { default as stringToAst } from "./string-to-ast.mjs";
|
||||||
import Lexer from "./Lexer.mjs";
|
export { default as stringToData } from "./string-to-data.mjs";
|
||||||
import visitorCreator from "./visitor.mjs";
|
export { default as astToData } from "./ast-to-data.mjs";
|
||||||
|
export { default as astToString } from "./ast-to-string.mjs";
|
||||||
export default (input) => {
|
export { default as astToSTring } from "./ast-to-string.mjs";
|
||||||
const parser = new Parser([], { outputCst: true });
|
export { default as dataToAst } from "./data-to-ast.mjs";
|
||||||
const lexingresult = Lexer.tokenize(input);
|
export { default as dataToString } from "./data-to-string.mjs";
|
||||||
|
|
||||||
if (lexingresult.errors.length > 0) {
|
|
||||||
console.dir(lexingresult, { depth: Infinity });
|
|
||||||
throw new Error("Lexing errors detected")
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.input = lexingresult.tokens;
|
|
||||||
const cst = parser.unitFile();
|
|
||||||
|
|
||||||
if (parser.errors.length > 0) {
|
|
||||||
console.dir(parser.errors, { depth: Infinity });
|
|
||||||
throw new Error("Parsing errors detected")
|
|
||||||
}
|
|
||||||
|
|
||||||
const Visitor = visitorCreator(parser);
|
|
||||||
const visitor = new Visitor();
|
|
||||||
const ast = visitor.visit(cst);
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
|
|
44
src/spec/ast-to-data.mjs
Executable file
44
src/spec/ast-to-data.mjs
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
import astToData from "../ast-to-data.mjs";
|
||||||
|
|
||||||
|
function runTest(input, expectedOutput) {
|
||||||
|
const result = astToData(input);
|
||||||
|
|
||||||
|
const expectedOutputString = JSON.stringify(expectedOutput);
|
||||||
|
const resultString = JSON.stringify(result);
|
||||||
|
if (expectedOutputString !== resultString) {
|
||||||
|
console.dir(input, { depth: Infinity });
|
||||||
|
throw new Error(`Mismatching result on testcase:
|
||||||
|
=======Result======
|
||||||
|
|
||||||
|
${resultString}
|
||||||
|
|
||||||
|
=======Expected======
|
||||||
|
|
||||||
|
${expectedOutputString}\n\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const cases = await Promise.all([
|
||||||
|
"./parser/basic.mjs",
|
||||||
|
"./parser/comments.mjs",
|
||||||
|
"./parser/comment-only.mjs",
|
||||||
|
"./parser/multiline-value.mjs",
|
||||||
|
"./parser/weird-characters.mjs",
|
||||||
|
"./parser/repeated-settings.mjs",
|
||||||
|
"./parser/real.nix-daemon.mjs",
|
||||||
|
"./parser/real.maia-console@.mjs",
|
||||||
|
/*
|
||||||
|
"./parser/real.dbus-org.bluez.mjs",
|
||||||
|
"./parser/real.display-manager.mjs",
|
||||||
|
"./parser/real.systemd-fsck-silent@.mjs",
|
||||||
|
"./parser/real.systemd-fsck-silent-root.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.Avahi.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.ModemManager1.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.nm-dispatcher.mjs",
|
||||||
|
*/
|
||||||
|
].map(file => import(file)));
|
||||||
|
const results = cases.forEach(({ ast, data }) => runTest(ast, data));
|
||||||
|
})();
|
||||||
|
|
14
test/examples/basic.mjs → src/spec/ast-to-data/basic.mjs
Normal file → Executable file
14
test/examples/basic.mjs → src/spec/ast-to-data/basic.mjs
Normal file → Executable file
|
@ -1,7 +1,4 @@
|
||||||
export const content = `[Unit]
|
export const input = {
|
||||||
Description=Idle manager for Wayland`;
|
|
||||||
|
|
||||||
export const result = {
|
|
||||||
type: 'unitFile',
|
type: 'unitFile',
|
||||||
comments: [],
|
comments: [],
|
||||||
sections: [
|
sections: [
|
||||||
|
@ -19,3 +16,12 @@ export const result = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const output = [
|
||||||
|
{
|
||||||
|
title: 'Unit',
|
||||||
|
settings: {
|
||||||
|
Description: 'Idle manager for Wayland',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
2
src/spec/ast-to-data/full-tree.mjs
Executable file
2
src/spec/ast-to-data/full-tree.mjs
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
export const input = ;
|
||||||
|
export const result = ;
|
0
src/spec/ast-to-string.mjs
Executable file
0
src/spec/ast-to-string.mjs
Executable file
44
src/spec/data-to-ast.mjs
Executable file
44
src/spec/data-to-ast.mjs
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
import astToData from "../data-to-ast.mjs";
|
||||||
|
|
||||||
|
function runTest(input, expectedOutput) {
|
||||||
|
const result = astToData(input);
|
||||||
|
|
||||||
|
const expectedOutputString = JSON.stringify(expectedOutput);
|
||||||
|
const resultString = JSON.stringify(result);
|
||||||
|
if (expectedOutputString !== resultString) {
|
||||||
|
console.dir(input, { depth: Infinity });
|
||||||
|
throw new Error(`Mismatching result on testcase:
|
||||||
|
=======Result======
|
||||||
|
|
||||||
|
${resultString}
|
||||||
|
|
||||||
|
=======Expected======
|
||||||
|
|
||||||
|
${expectedOutputString}\n\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const cases = await Promise.all([
|
||||||
|
"./parser/basic.mjs",
|
||||||
|
"./parser/comments.mjs",
|
||||||
|
"./parser/comment-only.mjs",
|
||||||
|
"./parser/multiline-value.mjs",
|
||||||
|
"./parser/weird-characters.mjs",
|
||||||
|
"./parser/repeated-settings.mjs",
|
||||||
|
"./parser/real.nix-daemon.mjs",
|
||||||
|
"./parser/real.maia-console@.mjs",
|
||||||
|
/*
|
||||||
|
"./parser/real.dbus-org.bluez.mjs",
|
||||||
|
"./parser/real.display-manager.mjs",
|
||||||
|
"./parser/real.systemd-fsck-silent@.mjs",
|
||||||
|
"./parser/real.systemd-fsck-silent-root.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.Avahi.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.ModemManager1.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.nm-dispatcher.mjs",
|
||||||
|
*/
|
||||||
|
].map(file => import(file)));
|
||||||
|
const results = cases.forEach(({ ast, data }) => runTest(ast, data));
|
||||||
|
})();
|
||||||
|
|
0
src/spec/data-to-string.mjs
Executable file
0
src/spec/data-to-string.mjs
Executable file
30
src/spec/parser/basic.mjs
Executable file
30
src/spec/parser/basic.mjs
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
export const string = `[Unit]
|
||||||
|
Description=Idle manager for Wayland`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
type: 'unitFile',
|
||||||
|
comments: [],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
title: 'Unit',
|
||||||
|
titleComment: undefined,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'Description',
|
||||||
|
value: 'Idle manager for Wayland'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const data = [
|
||||||
|
{
|
||||||
|
title: 'Unit',
|
||||||
|
settings: {
|
||||||
|
Description: 'Idle manager for Wayland',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
9
src/spec/parser/comment-only.mjs
Executable file
9
src/spec/parser/comment-only.mjs
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
export const string = `# Start of file comment`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
type: 'unitFile',
|
||||||
|
comments: [ { type: 'comment', value: 'Start of file comment' } ],
|
||||||
|
sections: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const data = [];
|
25
test/examples/comments.mjs → src/spec/parser/comments.mjs
Normal file → Executable file
25
test/examples/comments.mjs → src/spec/parser/comments.mjs
Normal file → Executable file
|
@ -1,4 +1,5 @@
|
||||||
export const content = `# Outside comment
|
export const string = `# Outside comment
|
||||||
|
# Outside comment part 2
|
||||||
[Unit] # Heading comment
|
[Unit] # Heading comment
|
||||||
Description=Idle manager for Wayland # End of value comment
|
Description=Idle manager for Wayland # End of value comment
|
||||||
# Inline comment
|
# Inline comment
|
||||||
|
@ -10,9 +11,12 @@ Alias=asdf#Comment Without spaces
|
||||||
# Comment only in this body
|
# Comment only in this body
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const result = {
|
export const ast = {
|
||||||
type: 'unitFile',
|
type: 'unitFile',
|
||||||
comments: [ { type: 'comment', value: 'Outside comment' } ],
|
comments: [
|
||||||
|
{ type: 'comment', value: 'Outside comment' },
|
||||||
|
{ type: 'comment', value: 'Outside comment part 2' },
|
||||||
|
],
|
||||||
sections: [
|
sections: [
|
||||||
{
|
{
|
||||||
type: 'section',
|
type: 'section',
|
||||||
|
@ -50,3 +54,18 @@ export const result = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const data = [
|
||||||
|
{
|
||||||
|
title: 'Unit',
|
||||||
|
settings: {
|
||||||
|
Description: 'Idle manager for Wayland',
|
||||||
|
ExecStart: 'echo "some string"',
|
||||||
|
Alias: 'asdf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Install',
|
||||||
|
settings: {},
|
||||||
|
}
|
||||||
|
];
|
19
test/examples/multiline-value.mjs → src/spec/parser/multiline-value.mjs
Normal file → Executable file
19
test/examples/multiline-value.mjs → src/spec/parser/multiline-value.mjs
Normal file → Executable file
|
@ -1,4 +1,4 @@
|
||||||
export const content = `
|
export const string = `
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/bin/swayidle -w \\
|
ExecStart=/usr/bin/swayidle -w \\
|
||||||
timeout 600 'swaylock-bg' \\
|
timeout 600 'swaylock-bg' \\
|
||||||
|
@ -11,7 +11,7 @@ ExecPause=/usr/bin/swayidle -w \\
|
||||||
after-resume 'swaylock-bg'#No space comment
|
after-resume 'swaylock-bg'#No space comment
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const result = {
|
export const ast = {
|
||||||
type: 'unitFile',
|
type: 'unitFile',
|
||||||
comments: [],
|
comments: [],
|
||||||
sections: [
|
sections: [
|
||||||
|
@ -46,3 +46,18 @@ export const result = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const data = [
|
||||||
|
{
|
||||||
|
title: 'Service',
|
||||||
|
settings: {
|
||||||
|
ExecStart: '/usr/bin/swayidle -w \\\n' +
|
||||||
|
" timeout 600 'swaylock-bg' \\\n" +
|
||||||
|
` timeout 900 'swaymsg "output * dpms off"' \\\n` +
|
||||||
|
` resume 'swaymsg "output * dpms on"' \\\n` +
|
||||||
|
" after-resume 'swaylock-bg'",
|
||||||
|
ExecStop: "/usr/bin/swayidle -w \\\n after-resume 'swaylock-bg'",
|
||||||
|
ExecPause: "/usr/bin/swayidle -w \\\n after-resume 'swaylock-bg'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
118
src/spec/parser/real.dbus-org.bluez.mjs
Executable file
118
src/spec/parser/real.dbus-org.bluez.mjs
Executable file
|
@ -0,0 +1,118 @@
|
||||||
|
export const string = `[Unit]
|
||||||
|
Description=Bluetooth service
|
||||||
|
Documentation=man:bluetoothd(8)
|
||||||
|
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.bluez
|
||||||
|
ExecStart=/usr/lib/bluetooth/bluetoothd
|
||||||
|
NotifyAccess=main
|
||||||
|
#WatchdogSec=10
|
||||||
|
#Restart=on-failure
|
||||||
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||||
|
LimitNPROC=1
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectSystem=full
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=bluetooth.target
|
||||||
|
Alias=dbus-org.bluez.service`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "Bluetooth service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Documentation",
|
||||||
|
"value": "man:bluetoothd(8)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ConditionPathIsDirectory",
|
||||||
|
"value": "/sys/class/bluetooth"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Type",
|
||||||
|
"value": "dbus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "BusName",
|
||||||
|
"value": "org.bluez"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "/usr/lib/bluetooth/bluetoothd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "NotifyAccess",
|
||||||
|
"value": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "WatchdogSec=10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "Restart=on-failure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "CapabilityBoundingSet",
|
||||||
|
"value": "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "LimitNPROC",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ProtectHome",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ProtectSystem",
|
||||||
|
"value": "full"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Install",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "WantedBy",
|
||||||
|
"value": "bluetooth.target"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Alias",
|
||||||
|
"value": "dbus-org.bluez.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
172
src/spec/parser/real.dbus-org.freedesktop.Avahi.mjs
Executable file
172
src/spec/parser/real.dbus-org.freedesktop.Avahi.mjs
Executable file
|
@ -0,0 +1,172 @@
|
||||||
|
export const string = `# This file is part of avahi.
|
||||||
|
#
|
||||||
|
# avahi is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as
|
||||||
|
# published by the Free Software Foundation; either version 2 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# avahi is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with avahi; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||||
|
# USA.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Avahi mDNS/DNS-SD Stack
|
||||||
|
Requires=avahi-daemon.socket
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.freedesktop.Avahi
|
||||||
|
ExecStart=/usr/bin/avahi-daemon -s
|
||||||
|
ExecReload=/usr/bin/avahi-daemon -r
|
||||||
|
NotifyAccess=main
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
Also=avahi-daemon.socket
|
||||||
|
Alias=dbus-org.freedesktop.Avahi.service`
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "This file is part of avahi."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "avahi is free software; you can redistribute it and/or modify it"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "under the terms of the GNU Lesser General Public License as"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "published by the Free Software Foundation; either version 2 of the"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "License, or (at your option) any later version."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "avahi is distributed in the hope that it will be useful, but WITHOUT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "ANY WARRANTY; without even the implied warranty of MERCHANTABILITY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "License for more details."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "You should have received a copy of the GNU Lesser General Public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "License along with avahi; if not, write to the Free Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "USA."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "Avahi mDNS/DNS-SD Stack"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Requires",
|
||||||
|
"value": "avahi-daemon.socket"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Type",
|
||||||
|
"value": "dbus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "BusName",
|
||||||
|
"value": "org.freedesktop.Avahi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "/usr/bin/avahi-daemon -s"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecReload",
|
||||||
|
"value": "/usr/bin/avahi-daemon -r"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "NotifyAccess",
|
||||||
|
"value": "main"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Install",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "WantedBy",
|
||||||
|
"value": "multi-user.target"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Also",
|
||||||
|
"value": "avahi-daemon.socket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Alias",
|
||||||
|
"value": "dbus-org.freedesktop.Avahi.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
132
src/spec/parser/real.dbus-org.freedesktop.ModemManager1.mjs
Executable file
132
src/spec/parser/real.dbus-org.freedesktop.ModemManager1.mjs
Executable file
|
@ -0,0 +1,132 @@
|
||||||
|
export const string = `[Unit]
|
||||||
|
Description=Modem Manager
|
||||||
|
After=polkit.service
|
||||||
|
Requires=polkit.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.freedesktop.ModemManager1
|
||||||
|
ExecStart=/usr/bin/ModemManager
|
||||||
|
StandardError=null
|
||||||
|
Restart=on-abort
|
||||||
|
CapabilityBoundingSet=CAP_SYS_ADMIN
|
||||||
|
ProtectSystem=true
|
||||||
|
ProtectHome=true
|
||||||
|
PrivateTmp=true
|
||||||
|
RestrictAddressFamilies=AF_NETLINK AF_UNIX
|
||||||
|
NoNewPrivileges=true
|
||||||
|
User=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
Alias=dbus-org.freedesktop.ModemManager1.service`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "Modem Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "After",
|
||||||
|
"value": "polkit.service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Requires",
|
||||||
|
"value": "polkit.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Type",
|
||||||
|
"value": "dbus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "BusName",
|
||||||
|
"value": "org.freedesktop.ModemManager1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "/usr/bin/ModemManager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "StandardError",
|
||||||
|
"value": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Restart",
|
||||||
|
"value": "on-abort"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "CapabilityBoundingSet",
|
||||||
|
"value": "CAP_SYS_ADMIN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ProtectSystem",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ProtectHome",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "PrivateTmp",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "RestrictAddressFamilies",
|
||||||
|
"value": "AF_NETLINK AF_UNIX"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "NoNewPrivileges",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "User",
|
||||||
|
"value": "root"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Install",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "WantedBy",
|
||||||
|
"value": "multi-user.target"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Alias",
|
||||||
|
"value": "dbus-org.freedesktop.ModemManager1.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
78
src/spec/parser/real.dbus-org.freedesktop.nm-dispatcher.mjs
Executable file
78
src/spec/parser/real.dbus-org.freedesktop.nm-dispatcher.mjs
Executable file
|
@ -0,0 +1,78 @@
|
||||||
|
export const string = `[Unit]
|
||||||
|
Description=Network Manager Script Dispatcher Service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.freedesktop.nm_dispatcher
|
||||||
|
ExecStart=/usr/lib/nm-dispatcher
|
||||||
|
|
||||||
|
# We want to allow scripts to spawn long-running daemons, so tell
|
||||||
|
# systemd to not clean up when nm-dispatcher exits
|
||||||
|
KillMode=process
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
Alias=dbus-org.freedesktop.nm-dispatcher.service
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "Network Manager Script Dispatcher Service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Type",
|
||||||
|
"value": "dbus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "BusName",
|
||||||
|
"value": "org.freedesktop.nm_dispatcher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "/usr/lib/nm-dispatcher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "We want to allow scripts to spawn long-running daemons, so tell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": "systemd to not clean up when nm-dispatcher exits"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "KillMode",
|
||||||
|
"value": "process"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Install",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Alias",
|
||||||
|
"value": "dbus-org.freedesktop.nm-dispatcher.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
90
src/spec/parser/real.display-manager.mjs
Executable file
90
src/spec/parser/real.display-manager.mjs
Executable file
|
@ -0,0 +1,90 @@
|
||||||
|
export const string = `[Unit]
|
||||||
|
Description=TUI display manager
|
||||||
|
After=systemd-user-sessions.service plymouth-quit-wait.service
|
||||||
|
After=getty@tty2.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=idle
|
||||||
|
ExecStart=/usr/bin/ly
|
||||||
|
StandardInput=tty
|
||||||
|
TTYPath=/dev/tty2
|
||||||
|
TTYReset=yes
|
||||||
|
TTYVHangup=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
Alias=display-manager.service`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "TUI display manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "After",
|
||||||
|
"value": "systemd-user-sessions.service plymouth-quit-wait.service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "After",
|
||||||
|
"value": "getty@tty2.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Type",
|
||||||
|
"value": "idle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "/usr/bin/ly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "StandardInput",
|
||||||
|
"value": "tty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "TTYPath",
|
||||||
|
"value": "/dev/tty2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "TTYReset",
|
||||||
|
"value": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "TTYVHangup",
|
||||||
|
"value": "yes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Install",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Alias",
|
||||||
|
"value": "display-manager.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
104
src/spec/parser/real.maia-console@.mjs
Executable file
104
src/spec/parser/real.maia-console@.mjs
Executable file
|
@ -0,0 +1,104 @@
|
||||||
|
export const string = `[Unit]
|
||||||
|
Description=maia color scheme for the console
|
||||||
|
After=getty@%i.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/bin/maia-console
|
||||||
|
StandardOutput=tty
|
||||||
|
TTYPath=/dev/%i
|
||||||
|
TTYVTDisallocate=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=getty@%i.service`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "maia color scheme for the console"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "After",
|
||||||
|
"value": "getty@%i.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Type",
|
||||||
|
"value": "oneshot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "/usr/bin/maia-console"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "StandardOutput",
|
||||||
|
"value": "tty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "TTYPath",
|
||||||
|
"value": "/dev/%i"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "TTYVTDisallocate",
|
||||||
|
"value": "yes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Install",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "WantedBy",
|
||||||
|
"value": "getty@%i.service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const data = [
|
||||||
|
{
|
||||||
|
title: 'Unit',
|
||||||
|
settings: {
|
||||||
|
Description: 'maia color scheme for the console',
|
||||||
|
After: 'getty@%i.service',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Service',
|
||||||
|
settings: {
|
||||||
|
Type: 'oneshot',
|
||||||
|
ExecStart: '/usr/bin/maia-console',
|
||||||
|
StandardOutput: 'tty',
|
||||||
|
TTYPath: '/dev/%i',
|
||||||
|
TTYVTDisallocate: 'yes',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Install',
|
||||||
|
settings: {
|
||||||
|
WantedBy: 'getty@%i.service',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
99
src/spec/parser/real.nix-daemon.mjs
Executable file
99
src/spec/parser/real.nix-daemon.mjs
Executable file
|
@ -0,0 +1,99 @@
|
||||||
|
export const string = `[Unit]
|
||||||
|
Description=Nix Daemon
|
||||||
|
RequiresMountsFor=/nix/store
|
||||||
|
RequiresMountsFor=/nix/var
|
||||||
|
ConditionPathIsReadWrite=/nix/var/nix/daemon-socket
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=@/nix/store/fwak7l5jjl0py4wldsqjbv7p7rdzql0b-nix-2.3.9/bin/nix-daemon nix-daemon --daemon
|
||||||
|
KillMode=process
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "Nix Daemon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "RequiresMountsFor",
|
||||||
|
"value": "/nix/store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "RequiresMountsFor",
|
||||||
|
"value": "/nix/var"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ConditionPathIsReadWrite",
|
||||||
|
"value": "/nix/var/nix/daemon-socket"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "@/nix/store/fwak7l5jjl0py4wldsqjbv7p7rdzql0b-nix-2.3.9/bin/nix-daemon nix-daemon --daemon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "KillMode",
|
||||||
|
"value": "process"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Install",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "WantedBy",
|
||||||
|
"value": "multi-user.target"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const data = [
|
||||||
|
{
|
||||||
|
title: 'Unit',
|
||||||
|
settings: {
|
||||||
|
Description: 'Nix Daemon',
|
||||||
|
RequiresMountsFor: [
|
||||||
|
'/nix/store',
|
||||||
|
'/nix/var',
|
||||||
|
],
|
||||||
|
ConditionPathIsReadWrite: '/nix/var/nix/daemon-socket',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Service',
|
||||||
|
settings: {
|
||||||
|
ExecStart: '@/nix/store/fwak7l5jjl0py4wldsqjbv7p7rdzql0b-nix-2.3.9/bin/nix-daemon nix-daemon --daemon',
|
||||||
|
KillMode: 'process',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Install',
|
||||||
|
settings: {
|
||||||
|
WantedBy: 'multi-user.target',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
130
src/spec/parser/real.systemd-fsck-silent-root.mjs
Executable file
130
src/spec/parser/real.systemd-fsck-silent-root.mjs
Executable file
|
@ -0,0 +1,130 @@
|
||||||
|
export const string = `# SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
#
|
||||||
|
# This file is part of systemd.
|
||||||
|
#
|
||||||
|
# systemd is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=File System Check on Root Device
|
||||||
|
Documentation=man:systemd-fsck-root.service(8)
|
||||||
|
DefaultDependencies=no
|
||||||
|
Before=local-fs.target shutdown.target
|
||||||
|
ConditionPathIsReadWrite=!/
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/usr/lib/systemd/systemd-fsck
|
||||||
|
StandardOutput=null
|
||||||
|
StandardError=journal+console
|
||||||
|
TimeoutSec=0`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " SPDX-License-Identifier: LGPL-2.1+"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " This file is part of systemd."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " systemd is free software; you can redistribute it and/or modify it"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " under the terms of the GNU Lesser General Public License as published by"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " the Free Software Foundation; either version 2.1 of the License, or"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " (at your option) any later version."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "File System Check on Root Device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Documentation",
|
||||||
|
"value": "man:systemd-fsck-root.service(8)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "DefaultDependencies",
|
||||||
|
"value": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Before",
|
||||||
|
"value": "local-fs.target shutdown.target"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ConditionPathIsReadWrite",
|
||||||
|
"value": "!/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Type",
|
||||||
|
"value": "oneshot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "RemainAfterExit",
|
||||||
|
"value": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "/usr/lib/systemd/systemd-fsck"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "StandardOutput",
|
||||||
|
"value": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "StandardError",
|
||||||
|
"value": "journal+console"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "TimeoutSec",
|
||||||
|
"value": "0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
136
src/spec/parser/real.systemd-fsck-silent@.mjs
Executable file
136
src/spec/parser/real.systemd-fsck-silent@.mjs
Executable file
|
@ -0,0 +1,136 @@
|
||||||
|
export const string = `# SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
#
|
||||||
|
# This file is part of systemd.
|
||||||
|
#
|
||||||
|
# systemd is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=File System Check on %f
|
||||||
|
Documentation=man:systemd-fsck@.service(8)
|
||||||
|
DefaultDependencies=no
|
||||||
|
BindsTo=%i.device
|
||||||
|
After=%i.device systemd-fsck-silent-root.service local-fs-pre.target
|
||||||
|
Before=systemd-quotacheck.service shutdown.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/usr/lib/systemd/systemd-fsck
|
||||||
|
StandardOutput=null
|
||||||
|
StandardError=journal+console
|
||||||
|
TimeoutSec=0`;
|
||||||
|
|
||||||
|
export const ast = {
|
||||||
|
"type": "unitFile",
|
||||||
|
"comments": [
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " SPDX-License-Identifier: LGPL-2.1+"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " This file is part of systemd."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " systemd is free software; you can redistribute it and/or modify it"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " under the terms of the GNU Lesser General Public License as published by"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " the Free Software Foundation; either version 2.1 of the License, or"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"value": " (at your option) any later version."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Unit",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Description",
|
||||||
|
"value": "File System Check on %f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Documentation",
|
||||||
|
"value": "man:systemd-fsck@.service(8)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "DefaultDependencies",
|
||||||
|
"value": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "BindsTo",
|
||||||
|
"value": "%i.device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "After",
|
||||||
|
"value": "%i.device systemd-fsck-silent-root.service local-fs-pre.target"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Before",
|
||||||
|
"value": "systemd-quotacheck.service shutdown.target"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"title": "Service",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "Type",
|
||||||
|
"value": "oneshot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "RemainAfterExit",
|
||||||
|
"value": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "ExecStart",
|
||||||
|
"value": "/usr/lib/systemd/systemd-fsck"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "StandardOutput",
|
||||||
|
"value": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "StandardError",
|
||||||
|
"value": "journal+console"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setting",
|
||||||
|
"name": "TimeoutSec",
|
||||||
|
"value": "0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
17
test/examples/repeated-settings.mjs → src/spec/parser/repeated-settings.mjs
Normal file → Executable file
17
test/examples/repeated-settings.mjs → src/spec/parser/repeated-settings.mjs
Normal file → Executable file
|
@ -1,9 +1,9 @@
|
||||||
export const content = `[Install]
|
export const string = `[Install]
|
||||||
After=something.service
|
After=something.service
|
||||||
After=something-else.service
|
After=something-else.service
|
||||||
After=something-different.service`;
|
After=something-different.service`;
|
||||||
|
|
||||||
export const result = {
|
export const ast = {
|
||||||
type: 'unitFile',
|
type: 'unitFile',
|
||||||
comments: [],
|
comments: [],
|
||||||
sections: [
|
sections: [
|
||||||
|
@ -34,3 +34,16 @@ export const result = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const data = [
|
||||||
|
{
|
||||||
|
title: 'Install',
|
||||||
|
settings: {
|
||||||
|
After: [
|
||||||
|
'something.service',
|
||||||
|
'something-else.service',
|
||||||
|
'something-different.service',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
13
test/examples/weird-characters.mjs → src/spec/parser/weird-characters.mjs
Normal file → Executable file
13
test/examples/weird-characters.mjs → src/spec/parser/weird-characters.mjs
Normal file → Executable file
|
@ -1,7 +1,7 @@
|
||||||
export const content = `[Install]
|
export const string = `[Install]
|
||||||
Description=Test description(1) https://something/? \\# # test`;
|
Description=Test description(1) https://something/? \\# # test`;
|
||||||
|
|
||||||
export const result = {
|
export const ast = {
|
||||||
type: 'unitFile',
|
type: 'unitFile',
|
||||||
comments: [],
|
comments: [],
|
||||||
sections: [
|
sections: [
|
||||||
|
@ -20,3 +20,12 @@ export const result = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const data = [
|
||||||
|
{
|
||||||
|
title: 'Install',
|
||||||
|
settings: {
|
||||||
|
Description: 'Test description(1) https://something/? \\#',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
43
src/spec/string-to-ast.mjs
Executable file
43
src/spec/string-to-ast.mjs
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
import stringToAst from "../string-to-ast.mjs";
|
||||||
|
|
||||||
|
function runTest(input, result) {
|
||||||
|
const ast = stringToAst(input);
|
||||||
|
|
||||||
|
const resultString = JSON.stringify(ast, null, 2);
|
||||||
|
const expectedString = JSON.stringify(result, null, 2);
|
||||||
|
if (resultString !== expectedString) {
|
||||||
|
throw new Error(`mismatching result on testcase:
|
||||||
|
${input}
|
||||||
|
|
||||||
|
=======result======
|
||||||
|
|
||||||
|
${resultString}
|
||||||
|
|
||||||
|
=======expected======
|
||||||
|
|
||||||
|
${expectedString}
|
||||||
|
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const cases = await Promise.all([
|
||||||
|
"./parser/basic.mjs",
|
||||||
|
"./parser/comments.mjs",
|
||||||
|
"./parser/comment-only.mjs",
|
||||||
|
"./parser/multiline-value.mjs",
|
||||||
|
"./parser/weird-characters.mjs",
|
||||||
|
"./parser/repeated-settings.mjs",
|
||||||
|
"./parser/real.nix-daemon.mjs",
|
||||||
|
"./parser/real.maia-console@.mjs",
|
||||||
|
"./parser/real.dbus-org.bluez.mjs",
|
||||||
|
"./parser/real.display-manager.mjs",
|
||||||
|
"./parser/real.systemd-fsck-silent@.mjs",
|
||||||
|
"./parser/real.systemd-fsck-silent-root.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.Avahi.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.ModemManager1.mjs",
|
||||||
|
"./parser/real.dbus-org.freedesktop.nm-dispatcher.mjs",
|
||||||
|
].map(file => import(file)));
|
||||||
|
const results = cases.forEach(({ string, ast }) => runTest(string, ast));
|
||||||
|
})();
|
30
src/string-to-ast.mjs
Executable file
30
src/string-to-ast.mjs
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
import Parser from "./Parser.mjs";
|
||||||
|
import Lexer from "./Lexer.mjs";
|
||||||
|
import visitorCreator from "./visitor-creator.mjs";
|
||||||
|
|
||||||
|
export default function stringToAst(input, c) {
|
||||||
|
const config = c || { strict: true };
|
||||||
|
const parser = new Parser([], { outputCst: true });
|
||||||
|
const lexingresult = Lexer.tokenize(input);
|
||||||
|
|
||||||
|
if (config.strict && lexingresult.errors.length > 0) {
|
||||||
|
console.log(input);
|
||||||
|
console.dir(lexingresult, { depth: Infinity });
|
||||||
|
throw new Error("Lexing errors detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.input = lexingresult.tokens;
|
||||||
|
const cst = parser.unitFile();
|
||||||
|
|
||||||
|
if (config.strict && parser.errors.length > 0) {
|
||||||
|
console.log(input);
|
||||||
|
//console.dir(lexingresult, { depth: Infinity });
|
||||||
|
console.dir(parser.errors, { depth: Infinity });
|
||||||
|
throw new Error("Parsing errors detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
const Visitor = visitorCreator(parser);
|
||||||
|
const visitor = new Visitor();
|
||||||
|
const ast = visitor.visit(cst);
|
||||||
|
return ast;
|
||||||
|
}
|
6
src/string-to-data.mjs
Executable file
6
src/string-to-data.mjs
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
import stringToAst from "./string-to-ast.mjs";
|
||||||
|
import astToData from "./astToData.mjs";
|
||||||
|
|
||||||
|
export default function stringToData(data) {
|
||||||
|
return astToData(stringToAst(data));
|
||||||
|
}
|
12
src/visitor.mjs → src/visitor-creator.mjs
Normal file → Executable file
12
src/visitor.mjs → src/visitor-creator.mjs
Normal file → Executable file
|
@ -1,4 +1,4 @@
|
||||||
export default (parser) => {
|
export default function visitorCreator(parser) {
|
||||||
const BaseSQLVisitorWithDefaults = parser.getBaseCstVisitorConstructorWithDefaults();
|
const BaseSQLVisitorWithDefaults = parser.getBaseCstVisitorConstructorWithDefaults();
|
||||||
|
|
||||||
return class UnitFileVisitor extends BaseSQLVisitorWithDefaults {
|
return class UnitFileVisitor extends BaseSQLVisitorWithDefaults {
|
||||||
|
@ -10,7 +10,7 @@ export default (parser) => {
|
||||||
_comment(ctx) {
|
_comment(ctx) {
|
||||||
return {
|
return {
|
||||||
type: "comment",
|
type: "comment",
|
||||||
value: ctx.Comment.map(({ image }) => image.replace(/^\s/, '').replace(/\s$/, '')).join(''),
|
value: ctx.Comment.map(({ image }) => image.replace(/^#\s?/, '').replace(/\s$/, '')).join(''),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,6 @@ export default (parser) => {
|
||||||
return this._comment(ctx);
|
return this._comment(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
commentLine(ctx) {
|
|
||||||
return this._comment(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
propertyStatement(ctx) {
|
propertyStatement(ctx) {
|
||||||
const name = ctx.Property[0].image;
|
const name = ctx.Property[0].image;
|
||||||
return {
|
return {
|
||||||
|
@ -33,7 +29,7 @@ export default (parser) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
sectionStatement(ctx) {
|
sectionStatement(ctx) {
|
||||||
const content = ctx.propertyStatement?.[0] || ctx.commentLine?.[0];
|
const content = ctx.propertyStatement?.[0] || ctx.comment?.[0];
|
||||||
return this.visit(content);
|
return this.visit(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,4 +54,4 @@ export default (parser) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
export const content = `# Start of file comment`;
|
|
||||||
|
|
||||||
export const result = {
|
|
||||||
type: 'unitFile',
|
|
||||||
comments: [ { type: 'comment', value: 'Start of file comment' } ],
|
|
||||||
sections: []
|
|
||||||
};
|
|
|
@ -1,67 +0,0 @@
|
||||||
import glob from "glob";
|
|
||||||
import { join } from "path";
|
|
||||||
import {
|
|
||||||
Lexer,
|
|
||||||
Parser,
|
|
||||||
visitorCreator,
|
|
||||||
} from "../src/mod.mjs";
|
|
||||||
|
|
||||||
const runTest = (input, result) => {
|
|
||||||
const parser = new Parser([], { outputCst: true });
|
|
||||||
const lexingresult = Lexer.tokenize(input);
|
|
||||||
|
|
||||||
if (lexingresult.errors.length > 0) {
|
|
||||||
console.dir(lexingresult, { depth: Infinity });
|
|
||||||
throw new Error("Lexing errors detected")
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.input = lexingresult.tokens;
|
|
||||||
const cst = parser.unitFile();
|
|
||||||
|
|
||||||
if (parser.errors.length > 0) {
|
|
||||||
console.dir(parser.errors, { depth: Infinity });
|
|
||||||
throw new Error("Parsing errors detected")
|
|
||||||
}
|
|
||||||
|
|
||||||
const Visitor = visitorCreator(parser);
|
|
||||||
const visitor = new Visitor();
|
|
||||||
const ast = visitor.visit(cst);
|
|
||||||
|
|
||||||
const resultString = JSON.stringify(ast, null, 2);
|
|
||||||
const expectedString = JSON.stringify(result, null, 2);
|
|
||||||
if (resultString !== expectedString) {
|
|
||||||
console.dir(ast, { depth: Infinity });
|
|
||||||
console.dir(lexingresult, { depth: Infinity });
|
|
||||||
throw new Error(`Mismatching result on testcase:
|
|
||||||
${input}
|
|
||||||
|
|
||||||
=======Result======
|
|
||||||
|
|
||||||
${resultString}
|
|
||||||
|
|
||||||
=======Expected======
|
|
||||||
|
|
||||||
${expectedString}
|
|
||||||
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const files = await Promise.all(
|
|
||||||
process.argv
|
|
||||||
.slice(2)
|
|
||||||
.map(arg => new Promise(
|
|
||||||
(resolve, reject) => glob(arg, {}, (err, files) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(files.map(file => join(process.cwd(), file)));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
const cases = await Promise.all(files.flat().map(file => import(file)));
|
|
||||||
const results = cases.forEach(({ content, result }) => runTest(content, result));
|
|
||||||
})();
|
|
Loading…
Reference in a new issue