Skip to content

git-config output to std::io::Write #456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d087f12
feat: Add `parse::(Event|section::Header|Comment)::write_to(…)`. (#331)
Byron Jul 12, 2022
b22732a
change!: remove `Boolean::to_bstring()` along with a few `From` impls…
Byron Jul 12, 2022
0e392f8
change!: remove `Integer::to_bstring()` as well as some `TryFrom` imp…
Byron Jul 12, 2022
a02d575
adapt to breaking changes in `git-config` (#331)
Byron Jul 12, 2022
4f6cd8c
change!: Add `File::write_to()` and `File::to_bstring()`; remove some…
Byron Jul 12, 2022
78bb93c
new roundtrip test on file level (#331)
Byron Jul 12, 2022
9fac8e0
refactor (#331)
Byron Jul 12, 2022
00d1a9b
basic escaping of subsection names during serialization (#331)
Byron Jul 12, 2022
00592f6
prepare for validation of `parse::section::Header` (#331)
Byron Jul 12, 2022
cfd974f
feat!: section names are now validated. (#331)
Byron Jul 12, 2022
ae3895c
feat: `parse::Header::new(…)` with sub-section name validation (#331)
Byron Jul 12, 2022
219cf7a
change!: Enforce `parse::section::Header::new()` by making its fields…
Byron Jul 12, 2022
12cf005
more header escaping tests (#331)
Byron Jul 12, 2022
9a2f597
re-add newlines after multi-line values (#331)
Byron Jul 12, 2022
895ce40
fix!: `File::rename_section()` with validation of input arguments. (#…
Byron Jul 12, 2022
1d8dee2
remove unused makefile targets and add `nextest` target
Byron Jul 12, 2022
a93a156
fix!: Simplify specifying keys when mutating config values. (#331)
Byron Jul 12, 2022
59ec7f7
validation for Keys and header names (#331)
Byron Jul 12, 2022
ee9ac95
auto-compute whitespace for sections, even though it probably needs t…
Byron Jul 13, 2022
a0d6caa
refactor (#331)
Byron Jul 13, 2022
c88eea8
refactor (#331)
Byron Jul 13, 2022
9157717
feat: whitespace in mutable sections can be finely controlled, and is…
Byron Jul 13, 2022
db1f34d
feat: `File::from_str()` implementation, to support `let config: File…
Byron Jul 13, 2022
9f59356
feat: whitespace in newly pushed keys is derived from first section v…
Byron Jul 13, 2022
83a0922
change!: rename `file::MutableSection::set_leading_space()` to `set_l…
Byron Jul 13, 2022
5e6f9d9
a few tests for `MutableValue` showing that it's too buggy right now …
Byron Jul 13, 2022
afa736a
refactor (#331)
Byron Jul 13, 2022
8118644
feat: proper escaping of value bytes to allow round-tripping after mu…
Byron Jul 13, 2022
7e03b83
default space is just a single tab, not two ones (#331)
Byron Jul 13, 2022
511985a
more empty-value tests (#331)
Byron Jul 13, 2022
badd00c
thanks clippy
Byron Jul 13, 2022
15cd1d2
refactor (#331)
Byron Jul 13, 2022
a7eff01
avoid extra copies when setting values and escaping them (#331)
Byron Jul 13, 2022
5418bc7
feat: place spaces around `key = value` pairs, or whatever is used in…
Byron Jul 13, 2022
c9a2390
thanks clippy
Byron Jul 13, 2022
048b925
fix: `file::MutableMultiValue` escapes input values and maintains key…
Byron Jul 13, 2022
0a7391a
change!: conform APIs of `file::MutableValue` and `file::MutableMulti…
Byron Jul 13, 2022
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
23 changes: 9 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,6 @@ help: ## Display this help

always:

##@ Publishing & Versioning

try-publish-all: ## Dry-run publish all crates in the currently set version if they are not published yet.
(cd cargo-smart-release && cargo build --bin cargo-smart-release) && cargo-smart-release/target/debug/cargo-smart-release smart-release gitoxide

try-bump-minor-version: ## Show how updating the minor version of PACKAGE=<name> would look like.
(cd cargo-smart-release && cargo build --bin cargo-smart-release) && cargo-smart-release/target/debug/cargo-smart-release smart-release --update-crates-index --bump minor --no-dependencies --no-publish --no-tag --no-push -v $(PACKAGE)

bump-minor-version: ## Similar to try-bump-minor-version, but actually performs the operation on PACKAGE=<name>
(cd cargo-smart-release && cargo build --bin cargo-smart-release) && cargo-smart-release/target/debug/cargo-smart-release smart-release --update-crates-index --bump minor --no-dependencies --skip-publish --skip-tag --skip-push -v $(PACKAGE) --execute

##@ Release Builds

release-all: release release-lean release-small ## all release builds
Expand Down Expand Up @@ -67,9 +56,6 @@ clippy: ## Run cargo clippy on all crates
check-msrv: ## run cargo msrv to validate the current msrv requirements, similar to what CI does
cd git-repository && cargo check --package git-repository --no-default-features --features async-network-client,unstable,local-time-support,max-performance

check-win: ## see that windows compiles, provided the x86_64-pc-windows-msvc target and cargo-xwin are present.
cargo xwin build --target x86_64-pc-windows-msvc --no-default-features --features small

check: ## Build all code in suitable configurations
cargo check --all
cargo check --no-default-features --features small
Expand Down Expand Up @@ -169,6 +155,9 @@ unit-tests: ## run all unit tests
&& cargo test
cd gitoxide-core && cargo test --lib

nextest: ## run tests with `cargo nextest` (all unit-tests, no doc-tests, faster)
cargo nextest run --all

continuous-unit-tests: ## run all unit tests whenever something changes
watchexec -w src $(MAKE) unit-tests

Expand Down Expand Up @@ -323,3 +312,9 @@ check-size: ## Run cargo-diet on all crates to see that they are still in bound
fmt: ## run nightly rustfmt for its extra features, but check that it won't upset stable rustfmt
cargo +nightly fmt --all -- --config-path rustfmt-nightly.toml
cargo +stable fmt --all -- --check

##@ Publishing & Versioning

try-publish-all: ## Dry-run publish all crates in the currently set version if they are not published yet.
(cd cargo-smart-release && cargo build --bin cargo-smart-release) && cargo-smart-release/target/debug/cargo-smart-release smart-release gitoxide

2 changes: 0 additions & 2 deletions git-config/src/file/access/low_level/mod.rs

This file was deleted.

4 changes: 3 additions & 1 deletion git-config/src/file/access/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod comfort;
mod low_level;
mod mutate;
mod raw;
mod read_only;
mod write;
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::borrow::Cow;

use bstr::BStr;

use crate::file::rename_section;
use crate::{
file::{MutableSection, SectionBody},
lookup,
Expand Down Expand Up @@ -51,21 +50,23 @@ impl<'event> File<'event> {
/// # use git_config::File;
/// # use std::convert::TryFrom;
/// # use bstr::ByteSlice;
/// # use git_config::parse::section;
/// let mut git_config = git_config::File::default();
/// let mut section = git_config.new_section("hello", Some("world".into()));
/// section.push("a".into(), b"b".as_bstr().into());
/// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n");
/// let mut section = git_config.new_section("hello", Some("world".into()))?;
/// section.push(section::Key::try_from("a")?, b"b".as_bstr().into());
/// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n");
/// let _section = git_config.new_section("core", None);
/// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n");
/// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n[core]\n");
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn new_section(
&mut self,
section_name: impl Into<Cow<'event, str>>,
subsection_name: impl Into<Option<Cow<'event, str>>>,
) -> MutableSection<'_, 'event> {
let mut section = self.push_section(section_name, subsection_name, SectionBody::default());
) -> Result<MutableSection<'_, 'event>, section::header::Error> {
let mut section = self.push_section(section_name, subsection_name, SectionBody::default())?;
section.push_newline();
section
Ok(section)
}

/// Removes the section, returning the events it had, if any. If multiple
Expand Down Expand Up @@ -131,42 +132,25 @@ impl<'event> File<'event> {
section_name: impl Into<Cow<'event, str>>,
subsection_name: impl Into<Option<Cow<'event, str>>>,
section: SectionBody<'event>,
) -> MutableSection<'_, 'event> {
let subsection_name = subsection_name.into().map(into_cow_bstr);
self.push_section_internal(
section::Header {
name: section::Name(into_cow_bstr(section_name.into())),
separator: subsection_name.is_some().then(|| Cow::Borrowed(" ".into())),
subsection_name,
},
section,
)
) -> Result<MutableSection<'_, 'event>, section::header::Error> {
Ok(self.push_section_internal(section::Header::new(section_name, subsection_name)?, section))
}

/// Renames a section, modifying the last matching section.
pub fn rename_section<'a>(
&mut self,
section_name: &str,
subsection_name: impl Into<Option<&'a str>>,
new_section_name: impl Into<section::Name<'event>>,
new_section_name: impl Into<Cow<'event, str>>,
new_subsection_name: impl Into<Option<Cow<'event, str>>>,
) -> Result<(), lookup::existing::Error> {
) -> Result<(), rename_section::Error> {
let id = self
.section_ids_by_name_and_subname(section_name, subsection_name.into())?
.rev()
.next()
.expect("list of sections were empty, which violates invariant");
let header = self.section_headers.get_mut(&id).expect("known section-id");
header.name = new_section_name.into();
header.subsection_name = new_subsection_name.into().map(into_cow_bstr);

*header = section::Header::new(new_section_name, new_subsection_name)?;
Ok(())
}
}

fn into_cow_bstr(c: Cow<'_, str>) -> Cow<'_, BStr> {
match c {
Cow::Borrowed(s) => Cow::Borrowed(s.into()),
Cow::Owned(s) => Cow::Owned(s.into()),
}
}
55 changes: 26 additions & 29 deletions git-config/src/file/access/raw.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::{borrow::Cow, collections::HashMap};

use bstr::{BStr, BString};
use bstr::BStr;

use crate::file::mutable::value::EntryData;
use crate::{
file::{value::EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size},
file::{Index, MutableMultiValue, MutableSection, MutableValue, Size},
lookup,
parse::{section, Event},
File,
Expand All @@ -24,10 +25,9 @@ impl<'event> File<'event> {
subsection_name: Option<&str>,
key: &str,
) -> Result<Cow<'_, BStr>, lookup::existing::Error> {
let key = section::Key(Cow::<BStr>::Borrowed(key.into()));
let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?;
for section_id in section_ids.rev() {
if let Some(v) = self.sections.get(&section_id).expect("known section id").value(&key) {
if let Some(v) = self.sections.get(&section_id).expect("known section id").value(key) {
return Ok(v);
}
}
Expand All @@ -52,23 +52,22 @@ impl<'event> File<'event> {
let key = section::Key(Cow::<BStr>::Borrowed(key.into()));

while let Some(section_id) = section_ids.next() {
let mut size = 0;
let mut index = 0;
let mut size = 0;
let mut found_key = false;
for (i, event) in self
.sections
.get(&section_id)
.expect("sections does not have section id from section ids")
.expect("known section id")
.as_ref()
// todo: iter backwards
.iter()
.enumerate()
{
match event {
Event::SectionKey(event_key) if *event_key == key => {
found_key = true;
size = 1;
index = i;
size = 1;
}
Event::Newline(_) | Event::Whitespace(_) | Event::ValueNotDone(_) if found_key => {
size += 1;
Expand All @@ -77,7 +76,10 @@ impl<'event> File<'event> {
found_key = false;
size += 1;
}
_ => (),
Event::KeyValueSeparator if found_key => {
size += 1;
}
_ => {}
}
}

Expand Down Expand Up @@ -141,12 +143,7 @@ impl<'event> File<'event> {
let mut values = Vec::new();
let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?;
for section_id in section_ids {
values.extend(
self.sections
.get(&section_id)
.expect("known section id")
.values(&section::Key(Cow::<BStr>::Borrowed(key.into()))),
);
values.extend(self.sections.get(&section_id).expect("known section id").values(key));
}

if values.is_empty() {
Expand Down Expand Up @@ -188,7 +185,7 @@ impl<'event> File<'event> {
/// ]
/// );
///
/// git_config.raw_values_mut("core", None, "a")?.set_str_all("g");
/// git_config.raw_values_mut("core", None, "a")?.set_all("g");
///
/// assert_eq!(
/// git_config.raw_values("core", None, "a")?,
Expand Down Expand Up @@ -306,10 +303,10 @@ impl<'event> File<'event> {
section_name: &str,
subsection_name: Option<&str>,
key: &str,
new_value: BString,
new_value: &BStr,
) -> Result<(), lookup::existing::Error> {
self.raw_value_mut(section_name, subsection_name, key)
.map(|mut entry| entry.set_bytes(new_value))
.map(|mut entry| entry.set(new_value))
}

/// Sets a multivar in a given section, optional subsection, and key value.
Expand Down Expand Up @@ -347,9 +344,9 @@ impl<'event> File<'event> {
/// # use bstr::BStr;
/// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
/// let new_values = vec![
/// Cow::<BStr>::Borrowed("x".into()),
/// Cow::<BStr>::Borrowed("y".into()),
/// Cow::<BStr>::Borrowed("z".into()),
/// "x".into(),
/// "y".into(),
/// "z".into(),
/// ];
/// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?;
/// let fetched_config = git_config.raw_values("core", None, "a")?;
Expand All @@ -368,8 +365,8 @@ impl<'event> File<'event> {
/// # use bstr::BStr;
/// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
/// let new_values = vec![
/// Cow::<BStr>::Borrowed("x".into()),
/// Cow::<BStr>::Borrowed("y".into()),
/// "x".into(),
/// "y".into(),
/// ];
/// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?;
/// let fetched_config = git_config.raw_values("core", None, "a")?;
Expand All @@ -387,21 +384,21 @@ impl<'event> File<'event> {
/// # use bstr::BStr;
/// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
/// let new_values = vec![
/// Cow::<BStr>::Borrowed("x".into()),
/// Cow::<BStr>::Borrowed("y".into()),
/// Cow::<BStr>::Borrowed("z".into()),
/// Cow::<BStr>::Borrowed("discarded".into()),
/// "x".into(),
/// "y".into(),
/// "z".into(),
/// "discarded".into(),
/// ];
/// git_config.set_raw_multi_value("core", None, "a", new_values)?;
/// assert!(!git_config.raw_values("core", None, "a")?.contains(&Cow::<BStr>::Borrowed("discarded".into())));
/// # Ok::<(), git_config::lookup::existing::Error>(())
/// ```
pub fn set_raw_multi_value(
pub fn set_raw_multi_value<'a>(
&mut self,
section_name: &str,
subsection_name: Option<&str>,
key: &str,
new_values: impl IntoIterator<Item = Cow<'event, BStr>>,
new_values: impl IntoIterator<Item = &'a BStr>,
) -> Result<(), lookup::existing::Error> {
self.raw_values_mut(section_name, subsection_name, key)
.map(|mut v| v.set_values(new_values))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,15 @@ impl<'event> File<'event> {
/// assert_eq!(url.map_or(0, |s| s.count()), 2);
///
/// for (i, (header, body)) in config.sections_by_name_with_header("url").unwrap().enumerate() {
/// let url = header.subsection_name.as_ref();
/// let instead_of = body.value(&section::Key::from("insteadOf"));
/// let url = header.subsection_name().unwrap();
/// let instead_of = body.value("insteadOf").unwrap();
///
/// if i == 0 {
/// assert_eq!(instead_of.unwrap().as_ref(), "https://github.com/");
/// assert_eq!(url.unwrap().as_ref(), "ssh://[email protected]/");
/// assert_eq!(instead_of.as_ref(), "https://github.com/");
/// assert_eq!(url, "ssh://[email protected]/");
/// } else {
/// assert_eq!(instead_of.unwrap().as_ref(), "https://bitbucket.org/");
/// assert_eq!(url.unwrap().as_ref(), "ssh://[email protected]");
/// assert_eq!(instead_of.as_ref(), "https://bitbucket.org/");
/// assert_eq!(url, "ssh://[email protected]");
/// }
/// }
/// # Ok::<(), Box<dyn std::error::Error>>(())
Expand Down
35 changes: 35 additions & 0 deletions git-config/src/file/access/write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use crate::File;
use bstr::BString;

impl File<'_> {
/// Serialize this type into a `BString` for convenience.
///
/// Note that `to_string()` can also be used, but might not be lossless.
#[must_use]
pub fn to_bstring(&self) -> BString {
let mut buf = Vec::new();
self.write_to(&mut buf).expect("io error impossible");
buf.into()
}

/// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly
/// as it was parsed.
pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
for event in self.frontmatter_events.as_ref() {
event.write_to(&mut out)?;
}

for section_id in &self.section_order {
self.section_headers
.get(section_id)
.expect("known section-id")
.write_to(&mut out)?;

for event in self.sections.get(section_id).expect("known section-id").as_ref() {
event.write_to(&mut out)?;
}
}

Ok(())
}
}
Loading