use debian_control::relations::VersionConstraint;
use debversion::Version;
use std::path::{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,
}
}
}
pub(crate) fn action_file(action: &Action) -> &Path {
match action {
Action::Deb822(a) => match a {
Deb822Action::SetField { file, .. }
| Deb822Action::SetFieldWithIndent { file, .. }
| Deb822Action::RemoveField { file, .. }
| Deb822Action::RenameField { file, .. }
| Deb822Action::RemoveParagraph { file, .. }
| Deb822Action::AppendParagraph { file, .. }
| Deb822Action::NormalizeFieldSpacing { file, .. }
| Deb822Action::DropRelation { file, .. }
| Deb822Action::ReplaceRelation { file, .. }
| Deb822Action::SetRelationVersionConstraint { file, .. }
| Deb822Action::EnsureSubstvar { file, .. }
| Deb822Action::DropSubstvar { file, .. }
| Deb822Action::EnsureRelation { file, .. }
| Deb822Action::MoveRelation { file, .. }
| Deb822Action::MakeAlternativePrimary { file, .. }
| Deb822Action::ReorderParagraphs { file, .. }
| Deb822Action::DropFieldComments { file, .. } => file,
},
Action::Systemd(a) => match a {
SystemdAction::SetField { file, .. }
| SystemdAction::RemoveField { file, .. }
| SystemdAction::RenameField { file, .. }
| SystemdAction::Add { file, .. }
| SystemdAction::RemoveValue { file, .. } => file,
},
Action::DesktopIni(a) => match a {
DesktopIniAction::SetField { file, .. }
| DesktopIniAction::RemoveField { file, .. }
| DesktopIniAction::RemoveAll { file, .. }
| DesktopIniAction::RenameField { file, .. } => file,
},
Action::Yaml(a) => match a {
YamlAction::SetField { file, .. }
| YamlAction::SetFieldOrdered { file, .. }
| YamlAction::RemoveField { file, .. }
| YamlAction::RenameField { file, .. } => file,
},
Action::Changelog(a) => match a {
ChangelogAction::ReplaceEntryChanges { file, .. }
| ChangelogAction::SetEntryDate { file, .. }
| ChangelogAction::RemoveBullet { file, .. }
| ChangelogAction::ReplaceBullet { file, .. }
| ChangelogAction::SetEntryVersion { file, .. } => file,
},
Action::Watch(a) => match a {
WatchAction::SetEntryMatchingPattern { file, .. }
| WatchAction::RemoveEntryOption { file, .. }
| WatchAction::SetEntryOption { file, .. }
| WatchAction::SetEntryUrl { file, .. }
| WatchAction::ConvertEntryToTemplate { file, .. } => file,
},
Action::Makefile(a) => match a {
MakefileAction::ReplaceRecipe { file, .. }
| MakefileAction::RemoveRecipe { file, .. }
| MakefileAction::SetVariable { file, .. }
| MakefileAction::SetVariableOperator { file, .. }
| MakefileAction::RemoveVariable { file, .. }
| MakefileAction::RemoveRule { file, .. }
| MakefileAction::RemovePhonyTarget { file, .. }
| MakefileAction::RenameRuleTarget { file, .. }
| MakefileAction::AddRule { file, .. }
| MakefileAction::AddPhonyTarget { file, .. }
| MakefileAction::AddInclude { file, .. }
| MakefileAction::ReplaceVariableWithInclude { file, .. }
| MakefileAction::InsertIncludeBeforeVariable { file, .. } => file,
},
Action::Dep3(a) => match a {
Dep3Action::SetField { file, .. }
| Dep3Action::RemoveField { file, .. }
| Dep3Action::RenameField { file, .. } => file,
},
Action::LintianOverrides(a) => match a {
LintianOverridesAction::AddLine { file, .. }
| LintianOverridesAction::DropLine { file, .. }
| LintianOverridesAction::RenameTag { file, .. }
| LintianOverridesAction::SetLineInfo { file, .. } => file,
},
Action::Maintscript(a) => match a {
MaintscriptAction::DropEntry { file, .. } => file,
},
Action::Debcargo(a) => match a {
DebcargoAction::SetSourceField { file, .. }
| DebcargoAction::SetTopLevelBool { file, .. } => file,
},
Action::RunCommand(a) => match a {
RunCommandAction::Run { scope, .. } => scope,
},
Action::Filesystem(a) => match a {
FilesystemAction::SetMode { file, .. }
| FilesystemAction::Delete { file }
| FilesystemAction::Rename { file, .. }
| FilesystemAction::RemoveDirIfEmpty { file }
| FilesystemAction::Write { file, .. }
| FilesystemAction::ReplaceText { file, .. }
| FilesystemAction::Substitute { file, .. }
| FilesystemAction::NormalizeLineEndings { file } => file,
},
}
}
#[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,
},
MakeAlternativePrimary {
file: PathBuf,
paragraph: ParagraphSelector,
field: String,
package: String,
},
ReorderParagraphs {
file: PathBuf,
key_field: String,
order: Vec<String>,
},
DropFieldComments {
file: PathBuf,
paragraph: ParagraphSelector,
field: 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);
}
}