Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Unit File Support for systemd
-----------------------------
systemd & Unit File Support
---------------------------

## Introduction

This plugin adds support for [systemd unit files](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#) to IntelliJ.
This plugin adds support for [systemd & unit files](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#) to IntelliJ.

## Features
* Syntax highlighting for unit files.
Expand All @@ -28,24 +28,27 @@ This plugin adds support for [systemd unit files](https://www.freedesktop.org/so
## Usage
To create a file simply right-click on a folder and <kbd>New</kbd> > <kbd>File</kbd>, and enter a file name ending any of:
* `.automount`
* `.device`
* `.mount`
* `.path`
* `.service`
* `.slice`
* `.socket`
* `.swap`
* `.target`
* `.timer`
* [Unit Files](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html)
* `.automount`
* `.device`
* `.mount`
* `.path`
* `.service`
* `.slice`
* `.socket`
* `.swap`
* `.target`
* `.timer`
* [Nspawn Container Settings](https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html#)
* `.nspawn`

The file should then be associated with this plugin and the above features should work.

__NOTE__: `.scope` units are not configured via unit configuration files and so we don't support them.

## Installation

This plugin is avaliable to install at the [JetBrains Plugin Repository](https://plugins.jetbrains.com/plugin/11070-unit-file-support-systemd-).
This plugin is available to install at the [JetBrains Plugin Repository](https://plugins.jetbrains.com/plugin/11070-unit-file-support-systemd-).

Contributors
-------------
Expand Down
16 changes: 15 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,21 @@ tasks.register<GenerateParserTask>("generateParserTask") {


tasks.register<Copy>("generateOptionValidator") {
from("./systemd-build/build/load-fragment-gperf.gperf")
listOf(
"journald-gperf.gperf",
"link-config-gperf.gperf",
"load-fragment-gperf.gperf",
"logind-gperf.gperf",
"netdev-gperf.gperf",
"networkd-gperf.gperf",
"networkd-network-gperf.gperf",
"nspawn-gperf.gperf",
"resolved-dnssd-gperf.gperf",
"resolved-gperf.gperf",
"timesyncd-gperf.gperf").forEach {
fileName -> from("./systemd-build/build/${fileName}")
}

into("${sourceSets["main"].output.resourcesDir?.getAbsolutePath()}/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/")
}

Expand Down
193 changes: 111 additions & 82 deletions buildSrc/src/main/groovy/GenerateDataFromManPages.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -55,75 +55,86 @@ class GenerateDataFromManPages extends DefaultTask {
* Map that stores for each file name, the name of an option attribute
*/
@Internal
def fileAndSectionTitleToSectionName = [
'systemd.unit.xml' :
['sections':
['[Unit] Section Options' : ['Unit'],
'[Install] Section Options': ['Install'],
'Conditions and Asserts' : ['Unit']
]
],
'systemd.service.xml' :
['sections':
['Options': ['Service']]
],
'systemd.timer.xml' :
['sections':
['Options': ['Timer']]
],
'systemd.automount.xml' :
['sections':
['Options': ['Automount']]
],
'systemd.mount.xml' :
['sections':
['Options': ['Mount']]
],
'systemd.path.xml' :
['sections':
['Options': ['Path']]
],
'systemd.socket.xml' :
['sections':
['Options': ['Socket']]
],
'systemd.swap.xml' :
['sections':
['Options': ['Swap']]
],
'systemd.resource-control.xml':
['sections':
[
'Options' : ['Slice', 'Service', 'Socket', 'Mount', 'Swap'],
'Deprecated Options': ['Slice', 'Service', 'Socket', 'Mount', 'Swap'],
]
],
'systemd.kill.xml' :
['sections':
['Options': ['Service', "Socket", "Mount", "Swap"]]
],
'systemd.exec.xml' :
['sections':
[
'Paths' : ['Service', 'Socket', 'Mount', 'Swap'],
'Credentials' : ['Service', 'Socket', 'Mount', 'Swap'],
'User/Group Identity' : ['Service', 'Socket', 'Mount', 'Swap'],
'Capabilities' : ['Service', 'Socket', 'Mount', 'Swap'],
'Security' : ['Service', 'Socket', 'Mount', 'Swap'],
'Mandatory Access Control' : ['Service', 'Socket', 'Mount', 'Swap'],
'Process Properties' : ['Service', 'Socket', 'Mount', 'Swap'],
'Scheduling' : ['Service', 'Socket', 'Mount', 'Swap'],
'Sandboxing' : ['Service', 'Socket', 'Mount', 'Swap'],
'System Call Filtering' : ['Service', 'Socket', 'Mount', 'Swap'],
'Environment' : ['Service', 'Socket', 'Mount', 'Swap'],
'Logging and Standard Input/Output': ['Service', 'Socket', 'Mount', 'Swap'],
'System V Compatibility' : ['Service', 'Socket', 'Mount', 'Swap'],
]
]
def fileTypeToFileAndSectionTitleToSectionName = [
'unit' : [
'systemd.unit.xml' :
['sections':
['[Unit] Section Options' : ['Unit'],
'[Install] Section Options': ['Install'],
'Conditions and Asserts' : ['Unit']
]
],
'systemd.service.xml' :
['sections':
['Options': ['Service']]
],
'systemd.timer.xml' :
['sections':
['Options': ['Timer']]
],
'systemd.automount.xml' :
['sections':
['Options': ['Automount']]
],
'systemd.mount.xml' :
['sections':
['Options': ['Mount']]
],
'systemd.path.xml' :
['sections':
['Options': ['Path']]
],
'systemd.socket.xml' :
['sections':
['Options': ['Socket']]
],
'systemd.swap.xml' :
['sections':
['Options': ['Swap']]
],
'systemd.resource-control.xml':
['sections':
[
'Options' : ['Slice', 'Service', 'Socket', 'Mount', 'Swap'],
'Deprecated Options': ['Slice', 'Service', 'Socket', 'Mount', 'Swap'],
]
],
'systemd.kill.xml' :
['sections':
['Options': ['Service', "Socket", "Mount", "Swap"]]
],
'systemd.exec.xml' :
['sections':
[
'Paths' : ['Service', 'Socket', 'Mount', 'Swap'],
'Credentials' : ['Service', 'Socket', 'Mount', 'Swap'],
'User/Group Identity' : ['Service', 'Socket', 'Mount', 'Swap'],
'Capabilities' : ['Service', 'Socket', 'Mount', 'Swap'],
'Security' : ['Service', 'Socket', 'Mount', 'Swap'],
'Mandatory Access Control' : ['Service', 'Socket', 'Mount', 'Swap'],
'Process Properties' : ['Service', 'Socket', 'Mount', 'Swap'],
'Scheduling' : ['Service', 'Socket', 'Mount', 'Swap'],
'Sandboxing' : ['Service', 'Socket', 'Mount', 'Swap'],
'System Call Filtering' : ['Service', 'Socket', 'Mount', 'Swap'],
'Environment' : ['Service', 'Socket', 'Mount', 'Swap'],
'Logging and Standard Input/Output': ['Service', 'Socket', 'Mount', 'Swap'],
'System V Compatibility' : ['Service', 'Socket', 'Mount', 'Swap'],
]
]],
'nspawn': [
'systemd.nspawn.xml':
['sections':
[
'[Exec] Section Options' : ['Exec'],
'[Files] Section Options' : ['Files'],
'[Network] Section Options': ['Network'],
]
]]

]

@Internal
Map<String /* Section */, Map<String /*Keyword*/, Map<String /*Attribute*/, String /*Value*/>>> sectionToKeyWordMapFromDoc = [:]
Map<String /* File Type */, Map<String /* Section */, Map<String /*Keyword*/, Map<String /*Attribute*/, String /*Value*/>>>> fileTypeToSectionToKeyWordMapFromDoc = [:]

@Internal
final XPath xpath
Expand All @@ -149,14 +160,22 @@ class GenerateDataFromManPages extends DefaultTask {
logger.debug("Regenerating valid keys")


fileAndSectionTitleToSectionName.keySet().each { file ->
logger.debug("Starting $file")
processFile(file)
fileTypeToFileAndSectionTitleToSectionName.entrySet().each {
fileToSectionToKeyWordMapFromDoc -> fileToSectionToKeyWordMapFromDoc.value.keySet().each {
file ->
logger.debug("Starting $file")
var fileType = fileToSectionToKeyWordMapFromDoc.key
processFile(fileType, file)
}


}



logger.debug("Complete")

def json = JsonOutput.toJson(this.sectionToKeyWordMapFromDoc)
def json = JsonOutput.toJson(this.fileTypeToSectionToKeyWordMapFromDoc)
json = JsonOutput.prettyPrint(json)

File outputData = new File(this.generatedJsonFileLocation.getAbsolutePath() + "/sectionToKeywordMapFromDoc.json")
Expand All @@ -173,20 +192,20 @@ class GenerateDataFromManPages extends DefaultTask {
*
* @param filename
*/
void processFile(String filename) {
void processFile(String fileType, String filename) {
File file = new File(this.systemdSourceCodeRoot.getAbsolutePath() + "/man/$filename")

generateKeywordAndValueJsonMapForFile(file)
generateKeywordAndValueJsonMapForFile(fileType, file)

generateDocumentationHtmlFromManPages(file)
generateDocumentationHtmlFromManPages(fileType, file)
}

/**
* Opens the file that will be scanned and extracts a list of variables from it storing it in JSON
*
* @param File file
*/
private void generateKeywordAndValueJsonMapForFile(File file) {
private void generateKeywordAndValueJsonMapForFile(String fileType, File file) {

String filename = file.getName()

Expand All @@ -206,12 +225,21 @@ class GenerateDataFromManPages extends DefaultTask {
"/refentry/refsect1/variablelist[not(contains(@class,'environment-variables'))]/varlistentry",
records, XPathConstants.NODESET);
}
else if (file.getAbsolutePath().endsWith("systemd.nspawn.xml")) {
result = (NodeList)xpath.evaluate(
"//variablelist[(contains(@class,'nspawn-directives'))]/varlistentry",
records, XPathConstants.NODESET);
}
else {
result = (NodeList)xpath.evaluate(
"//variablelist[(contains(@class,'unit-directives'))]/varlistentry",
records, XPathConstants.NODESET);
}

if (result.getLength() == 0) {
throw new IllegalStateException("Could not find variables under $filename")
}


for (int i = 0; i < result.getLength(); i++) {
Node varListEntry = result.item(i)
Expand All @@ -227,19 +255,20 @@ class GenerateDataFromManPages extends DefaultTask {
try {

String titleOfSection = xpath.evaluate("ancestor::refsect1/title[text()]", varListEntry)
List<String> sections = fileAndSectionTitleToSectionName[filename]['sections'][titleOfSection]
List<String> sections = fileTypeToFileAndSectionTitleToSectionName[fileType][filename]['sections'][titleOfSection]

String originalSection = xpath.evaluate("term/varname[text()]", varListEntry, XPathConstants.STRING)

String originalKeyName = getOptionNameAndValue(originalSection, filename)[0]

for (String section : sections) {
logger.debug("Found options $section in $option in ${file.getAbsolutePath()}")
sectionToKeyWordMapFromDoc.putIfAbsent(section, new TreeMap<>())
fileTypeToSectionToKeyWordMapFromDoc.putIfAbsent(fileType, new TreeMap<>())
fileTypeToSectionToKeyWordMapFromDoc.get(fileType).putIfAbsent(section, new TreeMap<>())
def val = ["declaredInFile": filename]
if (!keyValue.isEmpty()) val["values"] = keyValue
if (keyName != originalKeyName) val["declaredUnderKeyword"] = originalKeyName
sectionToKeyWordMapFromDoc[section][keyName] = val
fileTypeToSectionToKeyWordMapFromDoc[fileType][section][keyName] = val
}
}
catch (IllegalStateException e) {
Expand Down Expand Up @@ -272,14 +301,14 @@ class GenerateDataFromManPages extends DefaultTask {
* @param File sourceFile - the source file to extract
* @return
*/
private generateDocumentationHtmlFromManPages(File sourceFile) {
private generateDocumentationHtmlFromManPages(String fileType, File sourceFile) {
DocumentBuilder builder = dbf.newDocumentBuilder()
Document document = builder.parse(sourceFile)
Transformer transformer = getXsltTransformer()

String xsltOutput = transformDocument(document, transformer)

segmentParametersIntoFiles(sourceFile.getName(), xsltOutput)
segmentParametersIntoFiles(fileType, sourceFile.getName(), xsltOutput)
}

/**
Expand Down Expand Up @@ -339,7 +368,7 @@ class GenerateDataFromManPages extends DefaultTask {
* @param sourceFileName - the name of the source file we pulled the data from
* @param parameterInfoXMLAsString - A transformed XML document representing the documentation for systemd
*/
private void segmentParametersIntoFiles(String sourceFileName, String parameterInfoXMLAsString) {
private void segmentParametersIntoFiles(String fileType, String sourceFileName, String parameterInfoXMLAsString) {
def builder = dbf.newDocumentBuilder()

ByteArrayInputStream bis = new ByteArrayInputStream(parameterInfoXMLAsString.getBytes("UTF-8"))
Expand Down Expand Up @@ -368,11 +397,11 @@ class GenerateDataFromManPages extends DefaultTask {

String name = match.group(1)

List<String> foo = fileAndSectionTitleToSectionName[sourceFileName]['sections'][sectionTitle]
List<String> foo = fileTypeToFileAndSectionTitleToSectionName[fileType][sourceFileName]['sections'][sectionTitle]

for (String sectionName : foo) {
File outputFile = new File(
this.generatedJsonFileLocation.getAbsolutePath() + "/documents/completion/" + sectionName + "/" + name + ".html")
this.generatedJsonFileLocation.getAbsolutePath() + "/documents/completion/" + fileType + "/" + sectionName + "/" + name + ".html")
outputFile.getParentFile().mkdirs()

Writer write = new BufferedWriter(new FileWriter(outputFile))
Expand Down
4 changes: 3 additions & 1 deletion ci/release.Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,9 @@ pipeline {

sh("""
mkdir -p ./systemd-build/build
cp /opt/systemd-source/systemd/*.gperf ./systemd-build/build
cat /opt/systemd-source/systemd/last_commit_date
cat /opt/systemd-source/systemd/last_commit_hash
cp -R /mount/* ./systemd-build/build
cp /opt/systemd-source/systemd/last_commit_date /opt/systemd-source/systemd/last_commit_hash ./systemd-build/build
cp -R /opt/systemd-source/systemd/man ./systemd-build/build
""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ object UnitFileIcon {
@JvmField
val FILE = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/systemd.svg", UnitFileIcon::class.java)
val AUTOMOUNT = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/automount.svg", UnitFileIcon::class.java)
val NSPAWN = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/nspawn.svg", UnitFileIcon::class.java)
val DEVICE = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/device.svg", UnitFileIcon::class.java)
val MOUNT = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/mount.svg", UnitFileIcon::class.java)
val PATH = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/path.svg", UnitFileIcon::class.java)
Expand Down
Loading
Loading