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
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.sjrx.intellij.plugins.systemdunitfiles.intentions

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.util.PsiTreeUtil
import net.sjrx.intellij.plugins.systemdunitfiles.psi.impl.UnitFilePropertyImpl

class ReplaceInvalidLiteralChoiceQuickFix(val offset: Int, val invalidToken : String, val replacementToken : String) : LocalQuickFix {

override fun getName(): String {
return "Replace '${invalidToken}' with '${replacementToken}'"
}

override fun getFamilyName(): String {
return "Replace invalid value"
}

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val fullPropertyValue = descriptor.psiElement.text

val newText = fullPropertyValue.substring(0, offset) + replacementToken + fullPropertyValue.substring(offset+invalidToken.length)
val newElement = UnitElementFactory.createProperty(project, (descriptor.psiElement.parent as UnitFilePropertyImpl).key, newText)
val property = PsiTreeUtil.getParentOfType(descriptor.psiElement, UnitFilePropertyImpl::class.java)?: return

property.replace(newElement)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ class SemanticDataRepository private constructor() {
validatorMap.putAll(TtySizeOptionValue.validators)
validatorMap.putAll(ExecDirectoriesOptionValue.validators)

validatorMap.putAll(IOLimitOptionValue.validators)
validatorMap.putAll(ImagePolicyOptionValue.validators)
validatorMap.putAll(CPUWeightOptionValue.validators)
validatorMap.putAll(CPUSharesOptionValue.validators)

// Scopes are not supported since they aren't standard unit files.

sectionNameToKeyValuesFromDoc.remove(SCOPE_KEYWORD)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*


class CPUSharesOptionValue : GrammarOptionValue(validatorName, GRAMMAR) {

companion object {
val validatorName = "config_parse_cpu_shares"

val GRAMMAR =
SequenceCombinator(
IntegerTerminal(2, 262145),
EOF()
)

val validators = mapOf(
Validator(validatorName, "0") to CPUSharesOptionValue()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*


class CPUWeightOptionValue : GrammarOptionValue(validatorName, GRAMMAR) {

companion object {
val validatorName = "config_parse_cg_cpu_weight"

val GRAMMAR =
SequenceCombinator(
AlternativeCombinator(
FlexibleLiteralChoiceTerminal("idle"),
IntegerTerminal(1, 10001)
),
EOF()
)

val validators = mapOf(
Validator(validatorName, "0") to CPUWeightOptionValue()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*



class IOLimitOptionValue() : GrammarOptionValue("config_parse_io_limit", GRAMMAR) {

companion object {
val GRAMMAR = SequenceCombinator(OneOrMore(SequenceCombinator(DEVICE, BYTES)), EOF())

val validators = mapOf(
Validator("config_parse_io_limit", "0") to IOLimitOptionValue()
)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*

class ImagePolicyOptionValue : GrammarOptionValue(validatorName, IMAGE_POLICY_COMBINATOR) {

companion object {
val validatorName = "config_parse_image_policy"

// Image Polcies
// https://www.freedesktop.org/software/systemd/man/latest/systemd.image-policy.html
val IMAGE_POLICY_SEPARATOR = LiteralChoiceTerminal(":")
val PARTITION_IDENTIFIER = FlexibleLiteralChoiceTerminal( "root", "usr", "home", "srv", "esp", "xbootldr", "swap", "root-verity", "root-verity-sig", "usr-verity", "usr-verity-sig", "tmp", "var")
val PARTITION_POLICY_FLAG = FlexibleLiteralChoiceTerminal("unprotected", "verity", "signed", "encrypted", "unused", "absent", "read-only-off", "read-only-on", "growfs-off", "growfs-on")
val PARTITION_POLICY_FLAG_SEPARATOR = LiteralChoiceTerminal("+")
val PARTITION_IDENTIFIER_FLAG_SEPARATOR = LiteralChoiceTerminal("=")


val PARTITION_POLICY_FLAGS = SequenceCombinator(PARTITION_POLICY_FLAG, ZeroOrMore(SequenceCombinator(PARTITION_POLICY_FLAG_SEPARATOR, PARTITION_POLICY_FLAG)))


val SINGLE_EXPLICIT_IMAGE_POLICY = SequenceCombinator(PARTITION_IDENTIFIER, PARTITION_IDENTIFIER_FLAG_SEPARATOR, PARTITION_POLICY_FLAGS)
val DEFAULT_IMAGE_POLICY = SequenceCombinator(PARTITION_POLICY_FLAG_SEPARATOR, PARTITION_POLICY_FLAGS)
var IMAGE_POLICY= AlternativeCombinator(SINGLE_EXPLICIT_IMAGE_POLICY, DEFAULT_IMAGE_POLICY)
val IMAGE_POLICY_COMBINATOR = SequenceCombinator(IMAGE_POLICY, ZeroOrMore(SequenceCombinator(IMAGE_POLICY_SEPARATOR, IMAGE_POLICY)), EOF())

val validators = mapOf(
Validator(validatorName, "0") to ImagePolicyOptionValue()
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar

import kotlin.math.max

/**
* This is a sequence of tokens that must match any of them.
*/
class AlternativeCombinator(vararg val tokens: Combinator) : Combinator {

fun match(value: String, offset: Int, f: (Combinator, String, Int) -> MatchResult): MatchResult {


var longestTokenMatch = emptyList<String>()
var longestTerminalMatch = emptyList<TerminalCombinator>()
var maxLength = 0

for (token in tokens) {
val match = f(token, value, offset)
if (match.matchResult != -1) {
return match
}



if (match.tokens.size > longestTerminalMatch.size) {
longestTerminalMatch = match.terminals
longestTokenMatch = match.tokens
maxLength = max(maxLength, match.longestMatch)
}
}

return MatchResult(longestTokenMatch, -1, longestTerminalMatch, maxLength)
}

override fun SyntacticMatch(value: String, offset: Int): MatchResult {
return match(value, offset, Combinator::SyntacticMatch)
}

override fun SemanticMatch(value: String, offset: Int): MatchResult {
return match(value, offset, Combinator::SemanticMatch)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar

interface Combinator {
/**
* WARNING: At the current time this combinator implementation doesn't necessarily guarantee a match.
*
* Seq(ZeroOrMore(Literal("fizz")), Literal("fizz"))
*
* If you try and match "fizz", the ZeroOrMore would greedily consume the fizz, and the second wouldn't match.
*
* I'm unclear if this will actually be a problem, and whether it's worth fixing.
*/

/**
* This checks the value string, starting at offset for a syntactic match.
*
* In a nutshell a syntactic match might accept things that we should color and try and analyze
* but might be incorrect.
*
* For example if you something accepts a positive number, a syntactic regex should match any number even negative or floats
*
* The return value is -1 for no match, or a new offset if this token matched something.
*/
fun SyntacticMatch(value : String, offset: Int): MatchResult

/**
* This checks the value string, starting at offset for a semantic match.
*
* In a nutshell a semantic match means we understood and it valid as far as the grammar is concerned.
*
* The return value is -1 for no match, or a new offset if this token matched something.
*/
fun SemanticMatch(value : String, offset: Int): MatchResult

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar

val BYTES = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*")
val DEVICE = RegexTerminal("\\S+\\s*", "/[^\\u0000. ]+\\s*")
val IOPS = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*")
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar

class EOF : Combinator {
override fun SyntacticMatch(value: String, offset: Int): MatchResult {
return if (offset == value.length) {
MatchResult(emptyList(), offset, emptyList(), value.length)
} else {
NoMatch
}
}

override fun SemanticMatch(value: String, offset: Int): MatchResult {
return if (offset == value.length) {
MatchResult(emptyList(), offset, emptyList(), value.length)
} else {
NoMatch
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar

import kotlin.math.max

class FlexibleLiteralChoiceTerminal(vararg val choices: String) : TerminalCombinator {

init {
choices.sortBy { -it.length }
}

val syntaticMatch: Regex

init {

var dash = false
var specialChars = ""
var lowerCase = false
var upperCase = false
var numbers = false
var maxLength = 0

for (choice in choices) {
maxLength = max(maxLength, choice.length)
for (char in choice) {
if (char in 'a'..'z') {
lowerCase = true
continue
}

if (char in 'A'..'Z') {
upperCase = true
continue
}

if (char in '0'..'9') {
numbers = true
continue
}

if (char == '-') {
dash = true
continue
}

specialChars += char
}
}

var regexClass = ""

if (lowerCase) {
regexClass += "a-z"
}

if (upperCase) {
regexClass += "A-Z"
}

if (numbers) {
regexClass += "0-9"
}

regexClass += specialChars

if (dash) {
regexClass += "-"
}

syntaticMatch = ("[" + regexClass + "]{1,$maxLength}").toRegex()
}


override fun SyntacticMatch(value: String, offset: Int): MatchResult {
for (choice in choices) {
if (value.substring(offset).startsWith(choice)) {
return MatchResult(listOf(choice), offset + choice.length, listOf(this), offset + choice.length)
}
}

val matchResult = syntaticMatch.matchAt(value, offset) ?: return NoMatch

return MatchResult(listOf(matchResult.value), offset + matchResult.value.length, listOf(this), offset + matchResult.value.length)
}

override fun SemanticMatch(value: String, offset: Int): MatchResult {
for (choice in choices) {
if (value.substring(offset).startsWith(choice)) {
return MatchResult(listOf(choice), offset + choice.length, listOf(this), offset + choice.length)
}
}
return NoMatch.copy(longestMatch = offset)
}


}
Loading