aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/json
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-05 04:45:11 +0900
committernsfisis <nsfisis@gmail.com>2026-06-05 04:45:11 +0900
commitd0edb5b7ac3456f99c2f2576e8992cc12d60574a (patch)
treeb69546366676c06e1c8fabddb592c202436a92b2 /crates/shirabe/src/json
parent53800ab77565de1c16fb8a95e047eba1cb3a160c (diff)
downloadphp-shirabe-d0edb5b7ac3456f99c2f2576e8992cc12d60574a.tar.gz
php-shirabe-d0edb5b7ac3456f99c2f2576e8992cc12d60574a.tar.zst
php-shirabe-d0edb5b7ac3456f99c2f2576e8992cc12d60574a.zip
refactor(json): model JsonFile encode/write options as a typed struct
Replace the i64 bitmask + encode_with_indent split with a JsonEncodeOptions struct (Default = JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE, default indent). encode/write each get a default form plus an explicit encode_with_options/write_with_options variant, mirroring PHP's optional $options argument. write_with_options always encodes with self.indent, matching PHP write(). Also reconcile call sites with the PHP sources: most ported sites passed 0 where Composer omits the argument (= default flags), so JsonManipulator/ShowCommand and JsonConfigSource now use the default options; only ComposerRepository and Locker genuinely pass 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/json')
-rw-r--r--crates/shirabe/src/json/json_file.rs88
-rw-r--r--crates/shirabe/src/json/json_manipulator.rs68
2 files changed, 88 insertions, 68 deletions
diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs
index 0b1b010..06f3946 100644
--- a/crates/shirabe/src/json/json_file.rs
+++ b/crates/shirabe/src/json/json_file.rs
@@ -24,6 +24,50 @@ use crate::json::JsonValidationException;
use crate::util::Filesystem;
use crate::util::HttpDownloader;
+#[derive(Debug, Clone)]
+pub struct JsonEncodeOptions {
+ pub unescaped_slashes: bool,
+ pub pretty_print: bool,
+ pub unescaped_unicode: bool,
+ pub indent: String,
+}
+
+impl Default for JsonEncodeOptions {
+ fn default() -> Self {
+ Self {
+ unescaped_slashes: true,
+ pretty_print: true,
+ unescaped_unicode: true,
+ indent: JsonFile::INDENT_DEFAULT.to_string(),
+ }
+ }
+}
+
+impl JsonEncodeOptions {
+ pub fn none() -> Self {
+ Self {
+ unescaped_slashes: false,
+ pretty_print: false,
+ unescaped_unicode: false,
+ indent: JsonFile::INDENT_DEFAULT.to_string(),
+ }
+ }
+
+ fn to_flags(&self) -> i64 {
+ let mut flags = 0;
+ if self.unescaped_slashes {
+ flags |= JSON_UNESCAPED_SLASHES;
+ }
+ if self.pretty_print {
+ flags |= JSON_PRETTY_PRINT;
+ }
+ if self.unescaped_unicode {
+ flags |= JSON_UNESCAPED_UNICODE;
+ }
+ flags
+ }
+}
+
/// Reads/writes json files.
#[derive(Debug)]
pub struct JsonFile {
@@ -171,21 +215,15 @@ impl JsonFile {
}
pub fn write(&self, hash: PhpMixed) -> Result<()> {
- self.write2(
- hash,
- JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
- )
+ self.write_with_options(hash, JsonEncodeOptions::default())
}
- /// Writes json file.
- ///
- /// @param mixed[] $hash writes hash into json file
- /// @param int $options json_encode options
- /// @throws \UnexpectedValueException|\Exception
- /// @return void
- pub fn write2(&self, hash: PhpMixed, options: i64) -> Result<()> {
+ pub fn write_with_options(&self, hash: PhpMixed, options: JsonEncodeOptions) -> Result<()> {
if self.path == "php://memory" {
- file_put_contents(&self.path, Self::encode(&hash, options).as_bytes());
+ file_put_contents(
+ &self.path,
+ Self::encode_with_options(&hash, options.clone()).as_bytes(),
+ );
return Ok(());
}
@@ -220,12 +258,8 @@ impl JsonFile {
&self.path,
&format!(
"{}{}",
- Self::encode(&hash, options),
- if options & JSON_PRETTY_PRINT != 0 {
- "\n"
- } else {
- ""
- },
+ Self::encode_with_options(&hash, options.clone()),
+ if options.pretty_print { "\n" } else { "" },
),
)?;
Ok(())
@@ -392,18 +426,12 @@ impl JsonFile {
Ok(true)
}
- /// Encodes an array into (optionally pretty-printed) JSON
- ///
- /// @param mixed $data Data to encode into a formatted JSON string
- /// @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
- /// @param string $indent Indentation string
- /// @return string Encoded json
- pub fn encode(data: &PhpMixed, options: i64) -> String {
- Self::encode_with_indent(data, options, Self::INDENT_DEFAULT)
+ pub fn encode(data: &PhpMixed) -> String {
+ Self::encode_with_options(data, JsonEncodeOptions::default())
}
- pub fn encode_with_indent(data: &PhpMixed, options: i64, indent: &str) -> String {
- let json = json_encode_ex(data, options);
+ pub fn encode_with_options(data: &PhpMixed, options: JsonEncodeOptions) -> String {
+ let json = json_encode_ex(data, options.to_flags());
let json = match json {
Some(j) => j,
@@ -415,9 +443,9 @@ impl JsonFile {
}
};
- if (options & JSON_PRETTY_PRINT) > 0 && indent != Self::INDENT_DEFAULT {
+ if options.pretty_print && options.indent != Self::INDENT_DEFAULT {
// Pretty printing and not using default indentation
- let indent_owned = indent.to_string();
+ let indent_owned = options.indent.clone();
return Preg::replace_callback(
r"#^ {4,}#m",
move |m: &indexmap::IndexMap<
diff --git a/crates/shirabe/src/json/json_manipulator.rs b/crates/shirabe/src/json/json_manipulator.rs
index e5510d3..d907e91 100644
--- a/crates/shirabe/src/json/json_manipulator.rs
+++ b/crates/shirabe/src/json/json_manipulator.rs
@@ -89,7 +89,7 @@ impl JsonManipulator {
"{{{}^(?P<start>\\s*\\{{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?)(?P<property>{}\\s*:\\s*)(?P<value>(?&json))(?P<end>.*)}}sx",
Self::DEFINES,
preg_quote(
- &JsonFile::encode(&PhpMixed::String(r#type.to_string()), 0),
+ &JsonFile::encode(&PhpMixed::String(r#type.to_string())),
None
),
);
@@ -126,10 +126,11 @@ impl JsonManipulator {
move |m: &IndexMap<CaptureKey, String>| -> String {
format!(
"{}{}\"{}\"",
- JsonFile::encode(
- &PhpMixed::String(str_replace("\\/", "/", &existing_owned)),
- 0
- ),
+ JsonFile::encode(&PhpMixed::String(str_replace(
+ "\\/",
+ "/",
+ &existing_owned
+ ))),
m.get(&CaptureKey::ByName("separator".to_string()))
.cloned()
.unwrap_or_default(),
@@ -161,8 +162,8 @@ impl JsonManipulator {
self.newline,
self.indent,
self.indent,
- JsonFile::encode(&PhpMixed::String(package.to_string()), 0),
- JsonFile::encode(&PhpMixed::String(constraint.to_string()), 0),
+ JsonFile::encode(&PhpMixed::String(package.to_string())),
+ JsonFile::encode(&PhpMixed::String(constraint.to_string())),
groups_1
),
"\\$",
@@ -176,8 +177,8 @@ impl JsonManipulator {
self.newline,
self.indent,
self.indent,
- JsonFile::encode(&PhpMixed::String(package.to_string()), 0),
- JsonFile::encode(&PhpMixed::String(constraint.to_string()), 0),
+ JsonFile::encode(&PhpMixed::String(package.to_string())),
+ JsonFile::encode(&PhpMixed::String(constraint.to_string())),
self.newline,
self.indent
);
@@ -370,7 +371,7 @@ impl JsonManipulator {
let object_regex = format!(
"{{{}^(?P<start>\\s*\\{{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?\"repositories\"\\s*:\\s*\\{{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?{}\\s*:\\s*)(?P<repository>(?&object))(?P<end>.*)}}sx",
Self::DEFINES,
- preg_quote(&JsonFile::encode(&repository_index, 0), None)
+ preg_quote(&JsonFile::encode(&repository_index), None)
);
let mut matches: IndexMap<String, String> = IndexMap::new();
@@ -404,7 +405,7 @@ impl JsonManipulator {
.get(&CaptureKey::ByName("start".to_string()))
.cloned()
.unwrap_or_default(),
- JsonFile::encode(&PhpMixed::String(url_owned.clone()), 0),
+ JsonFile::encode(&PhpMixed::String(url_owned.clone())),
repository_matches
.get(&CaptureKey::ByName("end".to_string()))
.cloned()
@@ -681,7 +682,7 @@ impl JsonManipulator {
"{{{}^(?P<start> \\s* \\{{ \\s* (?: (?&string) \\s* : (?&json) \\s* , \\s* )*?{}\\s*:\\s*)(?P<content>(?&object))(?P<end>.*)}}sx",
Self::DEFINES,
preg_quote(
- &JsonFile::encode(&PhpMixed::String(main_node.to_string()), 0),
+ &JsonFile::encode(&PhpMixed::String(main_node.to_string())),
None
)
);
@@ -791,7 +792,7 @@ impl JsonManipulator {
self.newline,
self.indent,
self.indent,
- JsonFile::encode(&PhpMixed::String(name_owned.clone()), 0),
+ JsonFile::encode(&PhpMixed::String(name_owned.clone())),
self.format(&value_local, 1, false)?,
whitespace
),
@@ -807,7 +808,7 @@ impl JsonManipulator {
&format!(
"{{{}{}: {},{}{}{}",
whitespace,
- JsonFile::encode(&PhpMixed::String(name_owned.clone()), 0),
+ JsonFile::encode(&PhpMixed::String(name_owned.clone())),
self.format(&value_local, 1, false)?,
self.newline,
self.indent,
@@ -832,7 +833,7 @@ impl JsonManipulator {
self.newline,
self.indent,
self.indent,
- JsonFile::encode(&PhpMixed::String(name_owned.clone()), 0),
+ JsonFile::encode(&PhpMixed::String(name_owned.clone())),
self.format(&value_local, 1, false)?,
whitespace
);
@@ -881,7 +882,7 @@ impl JsonManipulator {
"{{{}^(?P<start> \\s* \\{{ \\s* (?: (?&string) \\s* : (?&json) \\s* , \\s* )*?{}\\s*:\\s*)(?P<content>(?&object))(?P<end>.*)}}sx",
Self::DEFINES,
preg_quote(
- &JsonFile::encode(&PhpMixed::String(main_node.to_string()), 0),
+ &JsonFile::encode(&PhpMixed::String(main_node.to_string())),
None
)
);
@@ -1146,7 +1147,7 @@ impl JsonManipulator {
"{{{}^(?P<start> \\s* \\{{ \\s* (?: (?&string) \\s* : (?&json) \\s* , \\s* )*?{}\\s*:\\s*)(?P<content>(?&array))(?P<end>.*)}}sx",
Self::DEFINES,
preg_quote(
- &JsonFile::encode(&PhpMixed::String(main_node.to_string()), 0),
+ &JsonFile::encode(&PhpMixed::String(main_node.to_string())),
None
)
);
@@ -1313,7 +1314,7 @@ impl JsonManipulator {
"{{{}^(?P<start> \\s* \\{{ \\s* (?: (?&string) \\s* : (?&json) \\s* , \\s* )*?{}\\s*:\\s*)(?P<content>(?&array))(?P<end>.*)}}sx",
Self::DEFINES,
preg_quote(
- &JsonFile::encode(&PhpMixed::String(main_node.to_string()), 0),
+ &JsonFile::encode(&PhpMixed::String(main_node.to_string())),
None
)
);
@@ -1416,7 +1417,7 @@ impl JsonManipulator {
"{{{}^(?P<start> \\s* \\{{ \\s* (?: (?&string) \\s* : (?&json) \\s* , \\s* )*?{}\\s*:\\s*)(?P<content>(?&array))(?P<end>.*)}}sx",
Self::DEFINES,
preg_quote(
- &JsonFile::encode(&PhpMixed::String(main_node.to_string()), 0),
+ &JsonFile::encode(&PhpMixed::String(main_node.to_string())),
None
)
);
@@ -1509,10 +1510,7 @@ impl JsonManipulator {
let regex = format!(
"{{{}^(?P<start>\\s*\\{{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?)(?P<key>{}\\s*:\\s*(?&json))(?P<end>.*)}}sx",
Self::DEFINES,
- preg_quote(
- &JsonFile::encode(&PhpMixed::String(key.to_string()), 0),
- None
- )
+ preg_quote(&JsonFile::encode(&PhpMixed::String(key.to_string())), None)
);
let mut matches: IndexMap<String, String> = IndexMap::new();
if decoded.as_array().and_then(|a| a.get(key)).is_some()
@@ -1527,7 +1525,7 @@ impl JsonManipulator {
self.contents = format!(
"{}{}: {}{}",
matches.get("start").cloned().unwrap_or_default(),
- JsonFile::encode(&PhpMixed::String(key.to_string()), 0),
+ JsonFile::encode(&PhpMixed::String(key.to_string())),
content,
matches.get("end").cloned().unwrap_or_default()
);
@@ -1551,7 +1549,7 @@ impl JsonManipulator {
",{}{}{}: {}{}}}",
self.newline,
self.indent,
- JsonFile::encode(&PhpMixed::String(key.to_string()), 0),
+ JsonFile::encode(&PhpMixed::String(key.to_string())),
content,
self.newline
),
@@ -1570,7 +1568,7 @@ impl JsonManipulator {
&format!(
"{}{}: {}{}}}",
self.indent,
- JsonFile::encode(&PhpMixed::String(key.to_string()), 0),
+ JsonFile::encode(&PhpMixed::String(key.to_string())),
content,
self.newline
),
@@ -1594,10 +1592,7 @@ impl JsonManipulator {
let regex = format!(
"{{{}^(?P<start>\\s*\\{{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?)(?P<removal>{}\\s*:\\s*(?&json))\\s*,?\\s*(?P<end>.*)}}sx",
Self::DEFINES,
- preg_quote(
- &JsonFile::encode(&PhpMixed::String(key.to_string()), 0),
- None
- )
+ preg_quote(&JsonFile::encode(&PhpMixed::String(key.to_string())), None)
);
let mut matches: IndexMap<String, String> = IndexMap::new();
if Preg::is_match_named(&regex, &self.contents, &mut matches).unwrap_or(false) {
@@ -1641,10 +1636,7 @@ impl JsonManipulator {
let regex = format!(
"{{{}^(?P<start>\\s*\\{{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?{}\\s*:\\s*)(?P<removal>\\{{(?P<removal_space>\\s*+)\\}})(?P<end>\\s*,?\\s*.*)}}sx",
Self::DEFINES,
- preg_quote(
- &JsonFile::encode(&PhpMixed::String(key.to_string()), 0),
- None
- )
+ preg_quote(&JsonFile::encode(&PhpMixed::String(key.to_string())), None)
);
let mut matches: IndexMap<String, String> = IndexMap::new();
if Preg::is_match_named(&regex, &self.contents, &mut matches).unwrap_or(false) {
@@ -1730,7 +1722,7 @@ impl JsonManipulator {
elems.push(format!(
"{}{}: {}",
str_repeat(&self.indent, (depth + 2) as usize),
- JsonFile::encode(&PhpMixed::String(key.clone()), 0),
+ JsonFile::encode(&PhpMixed::String(key.clone())),
self.format(val, depth + 1, false)?
));
}
@@ -1745,7 +1737,7 @@ impl JsonManipulator {
));
}
- Ok(JsonFile::encode(&data, 0))
+ Ok(JsonFile::encode(&data))
}
pub(crate) fn detect_indenting(&mut self) {
@@ -1803,7 +1795,7 @@ impl ManipulatorFormatter {
elems.push(format!(
"{}{}: {}",
str_repeat(&self.indent, (depth + 2) as usize),
- JsonFile::encode(&PhpMixed::String(key.clone()), 0),
+ JsonFile::encode(&PhpMixed::String(key.clone())),
self.format(val, depth + 1, false)?
));
}
@@ -1818,6 +1810,6 @@ impl ManipulatorFormatter {
));
}
- Ok(JsonFile::encode(&data, 0))
+ Ok(JsonFile::encode(&data))
}
}