use debian_control::relations::VersionConstraint;
use debversion::Version;
use std::path::PathBuf;
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ActionPlan {
pub label: String,
#[serde(default, skip_serializing_if = "core::ops::Not::not")]
pub opinionated: bool,
pub actions: Vec<Action>,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Action {
Deb822(Deb822Action),
Systemd(SystemdAction),
DesktopIni(DesktopIniAction),
Yaml(YamlAction),
Changelog(ChangelogAction),
Watch(WatchAction),
Makefile(MakefileAction),
Dep3(Dep3Action),
LintianOverrides(LintianOverridesAction),
Maintscript(MaintscriptAction),
Debcargo(DebcargoAction),
RunCommand(RunCommandAction),
Filesystem(FilesystemAction),
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum IndentPattern {
Fixed {
spaces: usize,
},
FieldNameLength,
}
impl IndentPattern {
pub fn to_deb822(&self) -> deb822_lossless::IndentPattern {
match self {
IndentPattern::Fixed { spaces } => deb822_lossless::IndentPattern::Fixed(*spaces),
IndentPattern::FieldNameLength => deb822_lossless::IndentPattern::FieldNameLength,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum Deb822Action {
SetField {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
value: String,
},
SetFieldWithIndent {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
value: String,
indent: IndentPattern,
},
RemoveField {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
},
RenameField {
file: PathBuf,
paragraph: ParagraphSelector,
from: String,
to: String,
},
RemoveParagraph {
file: PathBuf,
paragraph: ParagraphSelector,
},
AppendParagraph {
file: PathBuf,
fields: Vec<(String, String)>,
#[serde(default, skip_serializing_if = "Option::is_none")]
indent: Option<usize>,
},
NormalizeFieldSpacing {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
},
DropRelation {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
package: String,
},
ReplaceRelation {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
from_package: String,
to_entry: String,
},
EnsureSubstvar {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
substvar: String,
},
DropSubstvar {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
substvar: String,
},
EnsureRelation {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
entry: String,
},
SetRelationVersionConstraint {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
package: String,
constraint: Option<(VersionConstraint, Version)>,
},
MoveRelation {
file: PathBuf,
paragraph: ParagraphSelector,
from_field: String,
to_field: String,
package: String,
},
ReorderParagraphs {
file: PathBuf,
key_field: String,
order: Vec<String>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum SystemdAction {
SetField {
file: PathBuf,
section: String,
field: String,
value: String,
},
RemoveField {
file: PathBuf,
section: String,
field: String,
},
RenameField {
file: PathBuf,
section: String,
from: String,
to: String,
},
Add {
file: PathBuf,
section: String,
field: String,
value: String,
},
RemoveValue {
file: PathBuf,
section: String,
field: String,
value: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum DesktopIniAction {
SetField {
file: PathBuf,
group: String,
field: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
locale: Option<String>,
value: String,
},
RemoveField {
file: PathBuf,
group: String,
field: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
locale: Option<String>,
},
RemoveAll {
file: PathBuf,
group: String,
field: String,
},
RenameField {
file: PathBuf,
group: String,
from: String,
to: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum YamlAction {
SetField {
file: PathBuf,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
parent_path: Vec<YamlPathComponent>,
key: String,
value: String,
},
SetFieldOrdered {
file: PathBuf,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
parent_path: Vec<YamlPathComponent>,
key: String,
value: String,
field_order: Vec<String>,
},
RemoveField {
file: PathBuf,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
parent_path: Vec<YamlPathComponent>,
key: String,
},
RenameField {
file: PathBuf,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
parent_path: Vec<YamlPathComponent>,
from: String,
to: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum YamlPathComponent {
Key {
key: String,
},
Index {
index: usize,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum ChangelogAction {
ReplaceEntryChanges {
file: PathBuf,
version: String,
lines: Vec<String>,
},
SetEntryDate {
file: PathBuf,
version: String,
rfc2822: String,
},
RemoveBullet {
file: PathBuf,
version: String,
author: Option<String>,
text: String,
#[serde(default)]
occurrence: usize,
},
ReplaceBullet {
file: PathBuf,
version: String,
author: Option<String>,
text: String,
#[serde(default)]
occurrence: usize,
new_lines: Vec<String>,
},
SetEntryVersion {
file: PathBuf,
version: String,
new_version: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum WatchAction {
SetEntryMatchingPattern {
file: PathBuf,
url: String,
new_pattern: String,
},
RemoveEntryOption {
file: PathBuf,
url: String,
option: String,
},
SetEntryOption {
file: PathBuf,
url: String,
option: String,
value: String,
},
SetEntryUrl {
file: PathBuf,
url: String,
new_url: String,
},
ConvertEntryToTemplate {
file: PathBuf,
url: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum MakefileAction {
ReplaceRecipe {
file: PathBuf,
target: String,
recipe: String,
new_recipe: String,
},
RemoveRecipe {
file: PathBuf,
target: String,
recipe: String,
},
SetVariable {
file: PathBuf,
name: String,
value: String,
},
SetVariableOperator {
file: PathBuf,
name: String,
operator: String,
},
RemoveVariable {
file: PathBuf,
name: String,
},
RemoveRule {
file: PathBuf,
target: String,
},
RemovePhonyTarget {
file: PathBuf,
target: String,
},
RenameRuleTarget {
file: PathBuf,
from_target: String,
to_target: String,
},
AddRule {
file: PathBuf,
target: String,
prerequisites: Vec<String>,
},
AddPhonyTarget {
file: PathBuf,
target: String,
},
AddInclude {
file: PathBuf,
path: String,
},
ReplaceVariableWithInclude {
file: PathBuf,
name: String,
path: String,
},
InsertIncludeBeforeVariable {
file: PathBuf,
path: String,
before_variable: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum Dep3Action {
SetField {
file: PathBuf,
field: String,
value: String,
},
RemoveField {
file: PathBuf,
field: String,
},
RenameField {
file: PathBuf,
from_field: String,
to_field: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct OverrideLineSelector {
pub tag: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub info: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub package: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum LintianOverridesAction {
AddLine {
file: PathBuf,
#[serde(default, skip_serializing_if = "Option::is_none")]
package: Option<String>,
tag: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
info: Option<String>,
},
DropLine {
file: PathBuf,
selector: OverrideLineSelector,
},
RenameTag {
file: PathBuf,
from_tag: String,
to_tag: String,
},
SetLineInfo {
file: PathBuf,
selector: OverrideLineSelector,
new_info: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum MaintscriptAction {
DropEntry {
file: PathBuf,
entry: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum DebcargoAction {
SetSourceField {
file: PathBuf,
field: String,
value: String,
},
SetTopLevelBool {
file: PathBuf,
field: String,
value: bool,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum RunCommandAction {
Run {
argv: Vec<String>,
scope: PathBuf,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
env: Vec<(String, String)>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum FilesystemAction {
SetMode {
file: PathBuf,
mode: u32,
},
Delete {
file: PathBuf,
},
Rename {
file: PathBuf,
to: PathBuf,
},
RemoveDirIfEmpty {
file: PathBuf,
},
Write {
file: PathBuf,
content: Vec<u8>,
},
ReplaceText {
file: PathBuf,
range: TextRange,
replacement: String,
},
Substitute {
file: PathBuf,
from: String,
to: String,
},
NormalizeLineEndings {
file: PathBuf,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ParagraphSelector {
Source,
Binary {
package: String,
},
CopyrightHeader,
CopyrightFiles {
glob: String,
},
CopyrightLicense {
name: String,
},
Index {
index: usize,
},
ByKey {
field: String,
value: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TextRange {
pub start: usize,
pub end: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn action_serializes_with_kind_tag() {
let action = Action::Deb822(Deb822Action::SetField {
file: PathBuf::from("debian/control"),
paragraph: ParagraphSelector::Binary {
package: "foo".into(),
},
field: "Priority".into(),
value: "optional".into(),
});
let json = serde_json::to_value(&action).unwrap();
assert_eq!(json["kind"], "deb822");
assert_eq!(json["op"], "set_field");
assert_eq!(json["field"], "Priority");
assert_eq!(json["value"], "optional");
assert_eq!(json["paragraph"]["kind"], "binary");
assert_eq!(json["paragraph"]["package"], "foo");
}
#[test]
fn action_roundtrips_through_json() {
let original = Action::Filesystem(FilesystemAction::SetMode {
file: PathBuf::from("debian/rules"),
mode: 0o755,
});
let json = serde_json::to_string(&original).unwrap();
let parsed: Action = serde_json::from_str(&json).unwrap();
assert_eq!(original, parsed);
}
}