mirror of
https://github.com/zizmorcore/zizmor.git
synced 2025-12-23 08:47:33 +00:00
Extract yamlpatch into a support crate (#1001)
Co-authored-by: William Woodruff <william@yossarian.net>
This commit is contained in:
parent
32558743e2
commit
8f7e3eeb8d
16 changed files with 3429 additions and 3210 deletions
1
.github/workflows/release-support-crate.yml
vendored
1
.github/workflows/release-support-crate.yml
vendored
|
|
@ -7,6 +7,7 @@ on:
|
|||
- "github-actions-expressions/v*"
|
||||
- "github-actions-models/v*"
|
||||
- "yamlpath/v*"
|
||||
- "yamlpatch/v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package-name:
|
||||
|
|
|
|||
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -3663,6 +3663,21 @@ version = "0.8.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
|
||||
|
||||
[[package]]
|
||||
name = "yamlpatch"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"insta",
|
||||
"line-index",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror 2.0.12",
|
||||
"yamlpath",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yamlpath"
|
||||
version = "0.23.1"
|
||||
|
|
@ -3843,5 +3858,6 @@ dependencies = [
|
|||
"tree-sitter",
|
||||
"tree-sitter-bash",
|
||||
"tree-sitter-powershell",
|
||||
"yamlpatch",
|
||||
"yamlpath",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ resolver = "2"
|
|||
members = [
|
||||
"crates/github-actions-expressions",
|
||||
"crates/github-actions-models",
|
||||
"crates/yamlpatch",
|
||||
"crates/yamlpath",
|
||||
"crates/zizmor",
|
||||
]
|
||||
|
|
@ -64,6 +65,7 @@ tree-sitter = "0.25.6"
|
|||
tree-sitter-bash = "0.23.3"
|
||||
tree-sitter-powershell = "0.25.6"
|
||||
yamlpath = { path = "crates/yamlpath", version = "0.23.1" }
|
||||
yamlpatch = { path = "crates/yamlpatch", version = "0.1.0" }
|
||||
tree-sitter-yaml = "0.7.1"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
|
|
|
|||
30
crates/yamlpatch/Cargo.toml
Normal file
30
crates/yamlpatch/Cargo.toml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "yamlpatch"
|
||||
version = "0.1.0"
|
||||
description = "Comment and format-preserving YAML patch operations"
|
||||
repository = "https://github.com/zizmorcore/zizmor/tree/main/crates/yamlpatch"
|
||||
keywords = ["yaml", "patch"]
|
||||
|
||||
authors = [
|
||||
"Mostafa Moradian <mstfmoradian@gmail.com>",
|
||||
"William Woodruff <william@yossarian.net>"
|
||||
]
|
||||
homepage.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
indexmap.workspace = true
|
||||
line-index.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
thiserror.workspace = true
|
||||
yamlpath.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
insta.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
21
crates/yamlpatch/LICENSE
Normal file
21
crates/yamlpatch/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025-present, Mostafa Moradian <mstfmoradian @ gmail.com>, William Woodruff <william @ yossarian.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
56
crates/yamlpatch/README.md
Normal file
56
crates/yamlpatch/README.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# yamlpatch
|
||||
|
||||
[](https://github.com/zizmorcore/zizmor/actions/workflows/ci.yml)
|
||||
[](https://crates.io/crates/yamlpatch)
|
||||
[](https://docs.rs/yamlpatch)
|
||||
[](https://github.com/sponsors/woodruffw)
|
||||
[](https://discord.com/invite/PGU3zGZuGG)
|
||||
|
||||
Comment and format-preserving YAML patch operations.
|
||||
|
||||
`yamlpatch` builds on [`yamlpath`] to provide surgical modification capabilities
|
||||
while preserving comments, formatting, and structure.
|
||||
|
||||
[`yamlpath`]: https://github.com/zizmorcore/zizmor/tree/main/crates/yamlpath
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> This is not a substitute for comprehensive YAML processing libraries.
|
||||
> It's designed for targeted modifications that preserve the original
|
||||
> document's formatting and comments.
|
||||
|
||||
## Why?
|
||||
|
||||
When working with YAML configuration files, it's often necessary to make
|
||||
programmatic changes while preserving the human-readable aspects of the
|
||||
file: comments, formatting, indentation, and style choices.
|
||||
|
||||
Traditional YAML processing involves parsing to a document model, making
|
||||
changes, and re-serializing. This approach *destroys* the original formatting
|
||||
and comments, making the result less suitable for version control and
|
||||
human review.
|
||||
|
||||
`yamlpatch` solves this by providing targeted patch operations that:
|
||||
|
||||
- Preserve comments and their positioning
|
||||
- Maintain original indentation and formatting
|
||||
- Respect different YAML styles (block vs. flow, single vs. multi-line)
|
||||
- Support precise fragment rewriting within string values
|
||||
- Handle complex nested structures gracefully
|
||||
|
||||
## Operations
|
||||
|
||||
`yamlpatch` supports several types of patch operations:
|
||||
|
||||
- **Replace**: Replace a value at a specific path
|
||||
- **Add**: Add new key-value pairs to mappings
|
||||
- **Remove**: Remove keys or elements
|
||||
- **MergeInto**: Merge values into existing mappings
|
||||
- **RewriteFragment**: Rewrite portions of string values (useful for templating)
|
||||
|
||||
Each operation is designed to work with the existing document structure
|
||||
and formatting, making minimal changes while achieving the desired result.
|
||||
|
||||
## License
|
||||
|
||||
MIT License.
|
||||
1041
crates/yamlpatch/src/lib.rs
Normal file
1041
crates/yamlpatch/src/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
2237
crates/yamlpatch/tests/unit_tests.rs
Normal file
2237
crates/yamlpatch/tests/unit_tests.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -70,6 +70,7 @@ tree-sitter.workspace = true
|
|||
tree-sitter-bash.workspace = true
|
||||
tree-sitter-powershell.workspace = true
|
||||
yamlpath.workspace = true
|
||||
yamlpatch.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
csv.workspace = true
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use crate::{
|
|||
models::{StepBodyCommon, StepCommon, uses::RepositoryUsesExt as _},
|
||||
state::AuditState,
|
||||
utils::split_patterns,
|
||||
yaml_patch::{Op, Patch},
|
||||
};
|
||||
use yamlpatch::{Op, Patch};
|
||||
|
||||
pub(crate) struct Artipacked;
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ impl Artipacked {
|
|||
key: step.location().key,
|
||||
disposition: Default::default(),
|
||||
patches: vec![Patch {
|
||||
route: step.route(),
|
||||
route: step.route().into(),
|
||||
operation: Op::MergeInto {
|
||||
key: "with".to_string(),
|
||||
updates: indexmap::IndexMap::from_iter([(
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ use crate::{
|
|||
},
|
||||
models::workflow::{JobExt, Workflow},
|
||||
utils::ExtractedExpr,
|
||||
yaml_patch::{Op, Patch},
|
||||
};
|
||||
use yamlpatch::{Op, Patch};
|
||||
|
||||
pub(crate) struct BotConditions;
|
||||
|
||||
|
|
@ -385,7 +385,7 @@ impl BotConditions {
|
|||
key: &workflow.key,
|
||||
disposition: FixDisposition::Safe,
|
||||
patches: vec![Patch {
|
||||
route: if_route,
|
||||
route: if_route.into(),
|
||||
operation: Op::RewriteFragment {
|
||||
from: spoofable_context_raw.into(),
|
||||
to: safe_context.into(),
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ use crate::{
|
|||
},
|
||||
state::AuditState,
|
||||
utils::{DEFAULT_ENVIRONMENT_VARIABLES, ExtractedExpr, extract_expressions},
|
||||
yaml_patch::{Op, Patch},
|
||||
};
|
||||
use yamlpatch::{Op, Patch};
|
||||
|
||||
pub(crate) struct TemplateInjection;
|
||||
|
||||
|
|
@ -284,7 +284,7 @@ impl TemplateInjection {
|
|||
|
||||
let mut patches = vec![];
|
||||
patches.push(Patch {
|
||||
route: step.route().with_keys(&["run".into()]),
|
||||
route: step.route().with_keys(&["run".into()]).into(),
|
||||
operation: Op::RewriteFragment {
|
||||
from: raw.as_raw().to_string().into(),
|
||||
to: format!("${{{env_var}}}").into(),
|
||||
|
|
@ -300,7 +300,7 @@ impl TemplateInjection {
|
|||
.contains(&env_var.as_str())
|
||||
{
|
||||
patches.push(Patch {
|
||||
route: step.route(),
|
||||
route: step.route().into(),
|
||||
operation: Op::MergeInto {
|
||||
key: "env".to_string(),
|
||||
updates: indexmap::IndexMap::from_iter([(
|
||||
|
|
|
|||
|
|
@ -5,11 +5,8 @@ use clap::ValueEnum;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use self::location::{Location, SymbolicLocation};
|
||||
use crate::{
|
||||
InputKey,
|
||||
models::AsDocument,
|
||||
yaml_patch::{self, Patch},
|
||||
};
|
||||
use crate::{InputKey, models::AsDocument};
|
||||
use yamlpatch::{self, Patch};
|
||||
|
||||
pub(crate) mod location;
|
||||
|
||||
|
|
@ -137,7 +134,7 @@ impl Fix<'_> {
|
|||
&self,
|
||||
document: &yamlpath::Document,
|
||||
) -> anyhow::Result<yamlpath::Document> {
|
||||
match yaml_patch::apply_yaml_patches(document, &self.patches) {
|
||||
match yamlpatch::apply_yaml_patches(document, &self.patches) {
|
||||
Ok(new_document) => Ok(new_document),
|
||||
Err(e) => Err(anyhow!("fix failed: {e}")),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,20 @@ impl<'doc> From<Vec<RouteComponent<'doc>>> for Route<'doc> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'doc> From<Route<'doc>> for yamlpatch::Route<'doc> {
|
||||
fn from(route: Route<'doc>) -> Self {
|
||||
let yamlpatch_components: Vec<yamlpatch::RouteComponent<'doc>> = route
|
||||
.components
|
||||
.iter()
|
||||
.map(|comp| match comp {
|
||||
RouteComponent::Key(key) => yamlpatch::RouteComponent::Key(key),
|
||||
RouteComponent::Index(idx) => yamlpatch::RouteComponent::Index(*idx),
|
||||
})
|
||||
.collect();
|
||||
yamlpatch::Route::from(yamlpatch_components)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! route {
|
||||
($($key:expr),* $(,)?) => {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ mod output;
|
|||
mod registry;
|
||||
mod state;
|
||||
mod utils;
|
||||
mod yaml_patch;
|
||||
|
||||
// TODO: Dedupe this with the top-level `sponsors.json` used by the
|
||||
// README + docs site.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue