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",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "node ./test/spec.mjs './test/examples/*.mjs'"
|
||||
"test": "./scripts/run-tests"
|
||||
},
|
||||
"author": "",
|
||||
"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;
|
35
src/Lexer.mjs
Normal file → Executable file
35
src/Lexer.mjs
Normal file → Executable file
|
@ -1,15 +1,14 @@
|
|||
import chevrotain from "chevrotain";
|
||||
const { createToken, Lexer } = chevrotain;
|
||||
|
||||
function getValueParser(...stoppers) {
|
||||
return function exec(text, startOffset) {
|
||||
function valueParser(text, startOffset) {
|
||||
for (let i = startOffset; i < text.length; i += 1) {
|
||||
if (text[i] === '\\') {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stoppers.includes(text[i])) {
|
||||
if (['\n', '#'].includes(text[i])) {
|
||||
if (i !== startOffset) {
|
||||
return [text.substring(startOffset, i)];
|
||||
} else {
|
||||
|
@ -20,7 +19,6 @@ function getValueParser(...stoppers) {
|
|||
|
||||
return [text.substr(startOffset)];
|
||||
}
|
||||
}
|
||||
|
||||
export const SectionHeading = createToken({
|
||||
name: "SectionHeading",
|
||||
|
@ -33,53 +31,44 @@ export const Property = createToken({
|
|||
});
|
||||
export const Value = createToken({
|
||||
name: "Value",
|
||||
pattern: { exec: getValueParser('\n', '#') },
|
||||
pattern: { exec: valueParser },
|
||||
line_breaks: 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({
|
||||
name: "Comment",
|
||||
pattern: { exec: getValueParser('\n') },
|
||||
line_breaks: true,
|
||||
pop_mode: true,
|
||||
pattern: /#.*/,
|
||||
line_breaks: false,
|
||||
});
|
||||
export const WhiteSpace = createToken({
|
||||
name: "WhiteSpace",
|
||||
pattern: /\s+/,
|
||||
group: Lexer.SKIPPED,
|
||||
});
|
||||
export const Newline = createToken({
|
||||
name: "Newline",
|
||||
pattern: /\n+/,
|
||||
});
|
||||
|
||||
export const tokens = [
|
||||
CommentStart,
|
||||
CommentStartNewline,
|
||||
Value,
|
||||
Comment,
|
||||
SectionHeading,
|
||||
Property,
|
||||
WhiteSpace,
|
||||
Newline,
|
||||
];
|
||||
|
||||
export default new Lexer({
|
||||
defaultMode: "line_mode",
|
||||
modes: {
|
||||
line_mode: [
|
||||
CommentStartNewline,
|
||||
CommentStart,
|
||||
Comment,
|
||||
SectionHeading,
|
||||
Property,
|
||||
Newline,
|
||||
WhiteSpace,
|
||||
],
|
||||
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 {
|
||||
tokens,
|
||||
Comment,
|
||||
CommentStart,
|
||||
CommentStartNewline,
|
||||
Property,
|
||||
SectionHeading,
|
||||
Newline,
|
||||
Value,
|
||||
} from "./Lexer.mjs";
|
||||
|
||||
|
@ -15,25 +14,22 @@ export default class UnitFileParser extends CstParser {
|
|||
super(tokens);
|
||||
const $ = this;
|
||||
|
||||
$.RULE("commentLine", () => {
|
||||
$.CONSUME(CommentStartNewline);
|
||||
$.CONSUME(Comment);
|
||||
$.RULE("value", () => {
|
||||
$.CONSUME(Value);
|
||||
});
|
||||
|
||||
$.RULE("comment", () => {
|
||||
$.CONSUME(CommentStart);
|
||||
$.CONSUME(Comment);
|
||||
});
|
||||
|
||||
$.RULE("value", () => {
|
||||
$.CONSUME(Value);
|
||||
});
|
||||
|
||||
$.RULE("sectionHeadingStatement", () => {
|
||||
$.CONSUME(SectionHeading);
|
||||
$.OPTION(() => {
|
||||
$.SUBRULE($.comment);
|
||||
});
|
||||
$.OPTION1(() => {
|
||||
$.CONSUME(Newline);
|
||||
});
|
||||
});
|
||||
|
||||
$.RULE("propertyStatement", () => {
|
||||
|
@ -47,7 +43,7 @@ export default class UnitFileParser extends CstParser {
|
|||
$.RULE("sectionStatement", () => {
|
||||
$.OR([
|
||||
{ ALT: () => $.SUBRULE($.propertyStatement) },
|
||||
{ ALT: () => $.SUBRULE($.commentLine) },
|
||||
{ ALT: () => $.SUBRULE($.comment) },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -55,6 +51,9 @@ export default class UnitFileParser extends CstParser {
|
|||
$.SUBRULE($.sectionHeadingStatement);
|
||||
$.MANY(() => {
|
||||
$.SUBRULE($.sectionStatement);
|
||||
$.OPTION(() => {
|
||||
$.CONSUME(Newline);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -63,6 +62,7 @@ export default class UnitFileParser extends CstParser {
|
|||
$.OR([
|
||||
{ ALT: () => $.SUBRULE($.section) },
|
||||
{ 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 Parser } from "./Parser.mjs";
|
||||
export { default as visitorCreator } from "./visitor.mjs";
|
||||
export { default as visitorCreator } from "./visitor-creator.mjs";
|
||||
|
||||
import Parser from "./Parser.mjs";
|
||||
import Lexer from "./Lexer.mjs";
|
||||
import visitorCreator from "./visitor.mjs";
|
||||
|
||||
export default (input) => {
|
||||
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);
|
||||
return ast;
|
||||
}
|
||||
export { default as stringToAst } from "./string-to-ast.mjs";
|
||||
export { default as stringToData } from "./string-to-data.mjs";
|
||||
export { default as astToData } from "./ast-to-data.mjs";
|
||||
export { default as astToString } from "./ast-to-string.mjs";
|
||||
export { default as astToSTring } from "./ast-to-string.mjs";
|
||||
export { default as dataToAst } from "./data-to-ast.mjs";
|
||||
export { default as dataToString } from "./data-to-string.mjs";
|
||||
|
|
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]
|
||||
Description=Idle manager for Wayland`;
|
||||
|
||||
export const result = {
|
||||
export const input = {
|
||||
type: 'unitFile',
|
||||
comments: [],
|
||||
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
|
||||
Description=Idle manager for Wayland # End of value comment
|
||||
# Inline comment
|
||||
|
@ -10,9 +11,12 @@ Alias=asdf#Comment Without spaces
|
|||
# Comment only in this body
|
||||
`;
|
||||
|
||||
export const result = {
|
||||
export const ast = {
|
||||
type: 'unitFile',
|
||||
comments: [ { type: 'comment', value: 'Outside comment' } ],
|
||||
comments: [
|
||||
{ type: 'comment', value: 'Outside comment' },
|
||||
{ type: 'comment', value: 'Outside comment part 2' },
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
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]
|
||||
ExecStart=/usr/bin/swayidle -w \\
|
||||
timeout 600 'swaylock-bg' \\
|
||||
|
@ -11,7 +11,7 @@ ExecPause=/usr/bin/swayidle -w \\
|
|||
after-resume 'swaylock-bg'#No space comment
|
||||
`;
|
||||
|
||||
export const result = {
|
||||
export const ast = {
|
||||
type: 'unitFile',
|
||||
comments: [],
|
||||
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-else.service
|
||||
After=something-different.service`;
|
||||
|
||||
export const result = {
|
||||
export const ast = {
|
||||
type: 'unitFile',
|
||||
comments: [],
|
||||
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`;
|
||||
|
||||
export const result = {
|
||||
export const ast = {
|
||||
type: 'unitFile',
|
||||
comments: [],
|
||||
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();
|
||||
|
||||
return class UnitFileVisitor extends BaseSQLVisitorWithDefaults {
|
||||
|
@ -10,7 +10,7 @@ export default (parser) => {
|
|||
_comment(ctx) {
|
||||
return {
|
||||
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);
|
||||
}
|
||||
|
||||
commentLine(ctx) {
|
||||
return this._comment(ctx);
|
||||
}
|
||||
|
||||
propertyStatement(ctx) {
|
||||
const name = ctx.Property[0].image;
|
||||
return {
|
||||
|
@ -33,7 +29,7 @@ export default (parser) => {
|
|||
}
|
||||
|
||||
sectionStatement(ctx) {
|
||||
const content = ctx.propertyStatement?.[0] || ctx.commentLine?.[0];
|
||||
const content = ctx.propertyStatement?.[0] || ctx.comment?.[0];
|
||||
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