Initial commit
This commit is contained in:
commit
7c3259bc7f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
yarn-error.log
|
109
package-lock.json
generated
Normal file
109
package-lock.json
generated
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"name": "unitfile-parser",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chevrotain": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-G634q7M5EiqNNv+0MKcQES2jmqabbi4PvUDpzjG2t+i1XQFaMCz0o8BZ8lbQbZex4RqkzJ3pOy+UwNLFlQm4eg==",
|
||||||
|
"requires": {
|
||||||
|
"regexp-to-ast": "0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"concat-map": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"glob": {
|
||||||
|
"version": "7.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||||
|
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"regexp-to-ast": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw=="
|
||||||
|
},
|
||||||
|
"wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
package.json
Normal file
18
package.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "unitfile-parser",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Parse systemd unit files",
|
||||||
|
"main": "src/mod.mjs",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "node ./test/spec.mjs './test/examples/*.mjs'"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"chevrotain": "^7.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"glob": "^7.1.6"
|
||||||
|
}
|
||||||
|
}
|
78
src/Lexer.mjs
Normal file
78
src/Lexer.mjs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import chevrotain from "chevrotain";
|
||||||
|
const { createToken, Lexer } = chevrotain;
|
||||||
|
|
||||||
|
function getValueParser(...stoppers) {
|
||||||
|
return function exec(text, startOffset) {
|
||||||
|
for (let i = startOffset; i < text.length; i += 1) {
|
||||||
|
if (text[i] === '\\') {
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stoppers.includes(text[i])) {
|
||||||
|
if (i !== startOffset) {
|
||||||
|
return [text.substring(startOffset, i)];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [text.substr(startOffset)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SectionHeading = createToken({
|
||||||
|
name: "SectionHeading",
|
||||||
|
pattern: /\[[A-z]+\]/,
|
||||||
|
});
|
||||||
|
export const Property = createToken({
|
||||||
|
name: "Property",
|
||||||
|
pattern: /[A-z][A-z]+=/,
|
||||||
|
push_mode: "value_mode",
|
||||||
|
});
|
||||||
|
export const Value = createToken({
|
||||||
|
name: "Value",
|
||||||
|
pattern: { exec: getValueParser('\n', '#') },
|
||||||
|
line_breaks: true,
|
||||||
|
pop_mode: true,
|
||||||
|
});
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
export const WhiteSpace = createToken({
|
||||||
|
name: "WhiteSpace",
|
||||||
|
pattern: /\s+/,
|
||||||
|
group: Lexer.SKIPPED,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tokens = [
|
||||||
|
CommentStart,
|
||||||
|
Value,
|
||||||
|
Comment,
|
||||||
|
SectionHeading,
|
||||||
|
Property,
|
||||||
|
WhiteSpace,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default new Lexer({
|
||||||
|
defaultMode: "line_mode",
|
||||||
|
modes: {
|
||||||
|
line_mode: [
|
||||||
|
CommentStart,
|
||||||
|
SectionHeading,
|
||||||
|
Property,
|
||||||
|
WhiteSpace,
|
||||||
|
],
|
||||||
|
value_mode: [ Value ],
|
||||||
|
comment_mode: [ Comment ],
|
||||||
|
},
|
||||||
|
});
|
66
src/Parser.mjs
Normal file
66
src/Parser.mjs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import chevrotain from "chevrotain";
|
||||||
|
const { CstParser } = chevrotain;
|
||||||
|
import {
|
||||||
|
tokens,
|
||||||
|
Comment,
|
||||||
|
CommentStart,
|
||||||
|
Property,
|
||||||
|
SectionHeading,
|
||||||
|
Value,
|
||||||
|
} from "./Lexer.mjs";
|
||||||
|
|
||||||
|
export default class UnitFileParser extends CstParser {
|
||||||
|
constructor() {
|
||||||
|
super(tokens);
|
||||||
|
const $ = this;
|
||||||
|
|
||||||
|
$.RULE("comment", () => {
|
||||||
|
$.CONSUME(CommentStart);
|
||||||
|
$.CONSUME(Comment);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.RULE("value", () => {
|
||||||
|
$.CONSUME(Value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.RULE("sectionHeadingStatement", () => {
|
||||||
|
$.CONSUME(SectionHeading);
|
||||||
|
$.OPTION(() => {
|
||||||
|
$.SUBRULE($.comment);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$.RULE("propertyStatement", () => {
|
||||||
|
$.CONSUME(Property);
|
||||||
|
$.SUBRULE($.value);
|
||||||
|
$.OPTION(() => {
|
||||||
|
$.SUBRULE($.comment);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$.RULE("sectionStatement", () => {
|
||||||
|
$.OR([
|
||||||
|
{ ALT: () => $.SUBRULE($.propertyStatement) },
|
||||||
|
{ ALT: () => $.SUBRULE($.comment) },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.RULE("section", () => {
|
||||||
|
$.SUBRULE($.sectionHeadingStatement);
|
||||||
|
$.MANY(() => {
|
||||||
|
$.SUBRULE($.sectionStatement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$.RULE("unitFile", () => {
|
||||||
|
$.MANY(() => {
|
||||||
|
$.OR([
|
||||||
|
{ ALT: () => $.SUBRULE($.section) },
|
||||||
|
{ ALT: () => $.SUBRULE($.comment) },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.performSelfAnalysis();
|
||||||
|
}
|
||||||
|
}
|
3
src/mod.mjs
Normal file
3
src/mod.mjs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as Lexer } from "./Lexer.mjs";
|
||||||
|
export { default as Parser } from "./Parser.mjs";
|
||||||
|
export { default as visitorCreator } from "./visitor.mjs";
|
53
src/visitor.mjs
Normal file
53
src/visitor.mjs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
export default (parser) => {
|
||||||
|
const BaseSQLVisitorWithDefaults = parser.getBaseCstVisitorConstructorWithDefaults();
|
||||||
|
|
||||||
|
return class UnitFileVisitor extends BaseSQLVisitorWithDefaults {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.validateVisitor();
|
||||||
|
};
|
||||||
|
|
||||||
|
comment(ctx) {
|
||||||
|
return {
|
||||||
|
type: "comment",
|
||||||
|
value: ctx.Comment.map(({ image }) => image.replace(/^\s/, '').replace(/\s$/, '')).join(''),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyStatement(ctx) {
|
||||||
|
const name = ctx.Property[0].image;
|
||||||
|
return {
|
||||||
|
type: "setting",
|
||||||
|
name: name.substring(0, name.length - 1),
|
||||||
|
value: ctx.value.map(v => v.children.Value.map(({ image }) => image.replace(/\s$/, '')).join('')).join(''),
|
||||||
|
comment: this.visit(ctx.comment?.[0]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionStatement(ctx) {
|
||||||
|
const content = ctx.propertyStatement?.[0] || ctx.comment?.[0];
|
||||||
|
return this.visit(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
section(ctx) {
|
||||||
|
const titleString = ctx.sectionHeadingStatement[0].children.SectionHeading[0].image;
|
||||||
|
const titleComment = this.visit(ctx.sectionHeadingStatement[0].children.comment?.[0])
|
||||||
|
return {
|
||||||
|
type: "section",
|
||||||
|
title: titleString.substring(1, titleString.length - 1),
|
||||||
|
titleComment,
|
||||||
|
body: ctx.sectionStatement?.map(this.visit.bind(this)) || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unitFile(ctx) {
|
||||||
|
const comments = ctx.comment?.map(comment => this.visit(comment)) || [];
|
||||||
|
const sections = ctx.section?.map(section => this.visit(section)) || [];
|
||||||
|
return {
|
||||||
|
type: "unitFile",
|
||||||
|
comments,
|
||||||
|
sections,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
21
test/examples/basic.mjs
Normal file
21
test/examples/basic.mjs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
export const content = `[Unit]
|
||||||
|
Description=Idle manager for Wayland`;
|
||||||
|
|
||||||
|
export const result = {
|
||||||
|
type: 'unitFile',
|
||||||
|
comments: [],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
title: 'Unit',
|
||||||
|
titleComment: undefined,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'Description',
|
||||||
|
value: 'Idle manager for Wayland'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
13
test/examples/blueman-tray.service
Normal file
13
test/examples/blueman-tray.service
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=a status icon application for blueman
|
||||||
|
Documentation=man:blueman-tray(1)
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/blueman-tray
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
7
test/examples/comment-only.mjs
Normal file
7
test/examples/comment-only.mjs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const content = `# Start of file comment`;
|
||||||
|
|
||||||
|
export const result = {
|
||||||
|
type: 'unitFile',
|
||||||
|
comments: [ { type: 'comment', value: 'Start of file comment' } ],
|
||||||
|
sections: []
|
||||||
|
};
|
50
test/examples/comments.mjs
Normal file
50
test/examples/comments.mjs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
export const content = `# Outside comment
|
||||||
|
[Unit] # Heading comment
|
||||||
|
# Inline comment
|
||||||
|
Description=Idle manager for Wayland # End of value comment
|
||||||
|
ExecStart=echo\
|
||||||
|
"some string" # End of value comment
|
||||||
|
Alias=asdf#Comment Without spaces
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
# Comment only in this body
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const result = {
|
||||||
|
type: 'unitFile',
|
||||||
|
comments: [ { type: 'comment', value: 'Outside comment' } ],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
title: 'Unit',
|
||||||
|
titleComment: { type: 'comment', value: 'Heading comment' },
|
||||||
|
body: [
|
||||||
|
{ type: 'comment', value: 'Inline comment' },
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'Description',
|
||||||
|
value: 'Idle manager for Wayland',
|
||||||
|
comment: { type: 'comment', value: 'End of value comment' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'ExecStart',
|
||||||
|
value: 'echo "some string"',
|
||||||
|
comment: { type: 'comment', value: 'End of value comment' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'Alias',
|
||||||
|
value: 'asdf',
|
||||||
|
comment: { type: 'comment', value: 'Comment Without spaces' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
title: 'Install',
|
||||||
|
titleComment: { type: 'comment', value: 'Comment only in this body' },
|
||||||
|
body: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
13
test/examples/gammastep.service
Normal file
13
test/examples/gammastep.service
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=set color temperature of display according to time of day
|
||||||
|
Documentation=man:gammastep(1)
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/sbin/gammastep -l geoclue2 -m wayland -v
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
12
test/examples/keepassxc.service
Normal file
12
test/examples/keepassxc.service
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Keepassxc
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=keepassxc
|
||||||
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
KillMode=process
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
13
test/examples/libinput-gestures.service
Normal file
13
test/examples/libinput-gestures.service
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Actions gestures on your touchpad using libinput
|
||||||
|
Documentation=https://github.com/bulletmark/libinput-gestures
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=python3 /usr/bin/libinput-gestures
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
13
test/examples/mako.service
Normal file
13
test/examples/mako.service
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=A lightweight Wayland notification daemon
|
||||||
|
Documentation=man:mako(1)
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/mako
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
48
test/examples/multiline-value.mjs
Normal file
48
test/examples/multiline-value.mjs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
export const content = `
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/swayidle -w \\
|
||||||
|
timeout 600 'swaylock-bg' \\
|
||||||
|
timeout 900 'swaymsg "output * dpms off"' \\
|
||||||
|
resume 'swaymsg "output * dpms on"' \\
|
||||||
|
after-resume 'swaylock-bg'
|
||||||
|
ExecStop=/usr/bin/swayidle -w \\
|
||||||
|
after-resume 'swaylock-bg' # Comment at the end
|
||||||
|
ExecPause=/usr/bin/swayidle -w \\
|
||||||
|
after-resume 'swaylock-bg'#No space comment
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const result = {
|
||||||
|
type: 'unitFile',
|
||||||
|
comments: [],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
title: 'Service',
|
||||||
|
titleComment: undefined,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'ExecStart',
|
||||||
|
value: '/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'",
|
||||||
|
comment: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'ExecStop',
|
||||||
|
value: "/usr/bin/swayidle -w \\\n after-resume 'swaylock-bg'",
|
||||||
|
comment: { type: 'comment', value: 'Comment at the end' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'ExecPause',
|
||||||
|
value: "/usr/bin/swayidle -w \\\n after-resume 'swaylock-bg'",
|
||||||
|
comment: { type: 'comment', value: 'No space comment' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
12
test/examples/nextcloud.service
Normal file
12
test/examples/nextcloud.service
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Nextcloud Client
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/nextcloud --background
|
||||||
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
KillMode=process
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
13
test/examples/nm-applet.service
Normal file
13
test/examples/nm-applet.service
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=network monitor and control GUI applet
|
||||||
|
Documentation=man:nm-applet(1)
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/nm-applet --indicator
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
13
test/examples/polkit-gnome-authentication-agent.service
Normal file
13
test/examples/polkit-gnome-authentication-agent.service
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Legacy polkit authentication agent for GNOME
|
||||||
|
Documentation=https://gitlab.freedesktop.org/polkit/polkit/
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
36
test/examples/repeated-settings.mjs
Normal file
36
test/examples/repeated-settings.mjs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
export const content = `[Install]
|
||||||
|
After=something.service
|
||||||
|
After=something-else.service
|
||||||
|
After=something-different.service`;
|
||||||
|
|
||||||
|
export const result = {
|
||||||
|
type: 'unitFile',
|
||||||
|
comments: [],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
title: 'Install',
|
||||||
|
titleComment: undefined,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'After',
|
||||||
|
value: 'something.service',
|
||||||
|
comment: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'After',
|
||||||
|
value: 'something-else.service',
|
||||||
|
comment: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'After',
|
||||||
|
value: 'something-different.service',
|
||||||
|
comment: undefined
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
13
test/examples/scream-ivshmem-pulse.service
Normal file
13
test/examples/scream-ivshmem-pulse.service
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Scream IVSHMEM pulse reciever
|
||||||
|
After=pulseaudio.service
|
||||||
|
Wants=pulseaudio.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStartPre=/usr/bin/truncate -s 0 /dev/shm/scream-ivshmem
|
||||||
|
ExecStartPre=/usr/bin/dd if=/dev/zero of=/dev/shm/scream-ivshmem bs=1M count=2
|
||||||
|
ExecStart=/usr/bin/scream -m /dev/shm/scream-ivshmem
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
14
test/examples/sway.service
Normal file
14
test/examples/sway.service
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=sway - SirCmpwn's Wayland window manager
|
||||||
|
Documentation=man:sway(5)
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
EnvironmentFile=-%h/.config/sway/env
|
||||||
|
ExecStart=/usr/bin/sway -d
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=1
|
||||||
|
TimeoutStopSec=10
|
18
test/examples/swayidle.service
Normal file
18
test/examples/swayidle.service
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Start of file comment
|
||||||
|
[Unit] # Title comment
|
||||||
|
Description=Idle manager for Wayland
|
||||||
|
Documentation=man:swayidle(1)
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/swayidle -w \
|
||||||
|
timeout 600 'swaylock-bg' \
|
||||||
|
timeout 900 'swaymsg "output * dpms off"' \
|
||||||
|
resume 'swaymsg "output * dpms on"' \
|
||||||
|
after-resume 'swaylock-bg'
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
13
test/examples/waybar.service
Normal file
13
test/examples/waybar.service
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors.
|
||||||
|
Documentation=https://github.com/Alexays/Waybar/wiki/
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/waybar
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
22
test/examples/weird-characters.mjs
Normal file
22
test/examples/weird-characters.mjs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
export const content = `[Install]
|
||||||
|
Description=Test description(1) https://something/? \\# # test`;
|
||||||
|
|
||||||
|
export const result = {
|
||||||
|
type: 'unitFile',
|
||||||
|
comments: [],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
title: 'Install',
|
||||||
|
titleComment: undefined,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'setting',
|
||||||
|
name: 'Description',
|
||||||
|
value: 'Test description(1) https://something/? \\#',
|
||||||
|
comment: { type: 'comment', value: 'test' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
10
test/examples/xdg-desktop-portal-wlr.service
Normal file
10
test/examples/xdg-desktop-portal-wlr.service
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Portal service (wlroots implementation)
|
||||||
|
PartOf=graphical-session.target
|
||||||
|
After=graphical-session.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.freedesktop.impl.portal.desktop.wlr
|
||||||
|
ExecStart=/usr/lib/xdg-desktop-portal-wlr
|
||||||
|
Restart=on-failure
|
7
test/examples/xdg-desktop-portal.service
Normal file
7
test/examples/xdg-desktop-portal.service
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Portal service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.freedesktop.portal.Desktop
|
||||||
|
ExecStart=/usr/lib/xdg-desktop-portal
|
14
test/examples/xsettingsd.service
Normal file
14
test/examples/xsettingsd.service
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=X Settings Daemon
|
||||||
|
Documentation=https://github.com/derat/xsettingsd/wiki/Installation
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/xsettingsd
|
||||||
|
ExecStop=/usr/bin/env pkill xsettingsd
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
16
test/examples/ydotool.service
Normal file
16
test/examples/ydotool.service
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[Unit]
|
||||||
|
Description=ydotool - Generic command-line automation tool (no X!)
|
||||||
|
Documentation=https://github.com/ReimuNotMoe/ydotool
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/ydotoold
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=1
|
||||||
|
TimeoutStopSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sway-session.target
|
79
test/spec.mjs
Normal file
79
test/spec.mjs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import glob from "glob";
|
||||||
|
import { join } from "path";
|
||||||
|
import {
|
||||||
|
Lexer,
|
||||||
|
Parser,
|
||||||
|
visitorCreator,
|
||||||
|
} from "../src/mod.mjs";
|
||||||
|
|
||||||
|
const runTest = (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;
|
||||||
|
};
|
||||||
|
|
||||||
|
(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.map(
|
||||||
|
({ content, result }) => {
|
||||||
|
const ast = runTest(content);
|
||||||
|
const resultString = JSON.stringify(ast);
|
||||||
|
const expectedString = JSON.stringify(result);
|
||||||
|
if (resultString !== expectedString) {
|
||||||
|
console.dir(ast, { depth: Infinity });
|
||||||
|
throw new Error(`Mismatching result on testcase:
|
||||||
|
${content}
|
||||||
|
|
||||||
|
=======Result======
|
||||||
|
|
||||||
|
${resultString}
|
||||||
|
|
||||||
|
=======Expected======
|
||||||
|
|
||||||
|
${expectedString}
|
||||||
|
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.filter(r => r !== null);
|
||||||
|
|
||||||
|
if (results.length) {
|
||||||
|
results.forEach(err => console.error(err));
|
||||||
|
throw new Error(`${results.length} failed tests`);
|
||||||
|
}
|
||||||
|
})();
|
Loading…
Reference in a new issue