Skip to content

Config updates #44

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

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions git-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ doctest = false

[dependencies]
bstr = { version = "0.2.13", default-features = false, features = ["std"] }
dangerous = { version = "0.9.0", default-features = false, features = ["full-backtrace"] }
quick-error = "2.0.0"
3 changes: 2 additions & 1 deletion git-config/src/file/edit.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{borrowed, file::File, owned, Span};
use crate::{borrowed, file::File, owned};
use dangerous::Span;
use std::io;

/// Represents a possible edit to the git configuration file
Expand Down
16 changes: 6 additions & 10 deletions git-config/src/file/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{borrowed, spanned, Span};
use crate::{borrowed, spanned};
use bstr::{BStr, ByteSlice};
use dangerous::Span;

#[derive(Clone, PartialOrd, PartialEq, Ord, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub(crate) enum Token {
Section(spanned::Section),
Entry(spanned::Entry),
Expand All @@ -28,21 +29,16 @@ impl Token {
/// After reading a configuration file its contents is stored verbatim and indexed to allow retrieval
/// of sections and entry values on demand. These are returned as [`borrowed`] items, which are read-only but
/// can be transformed into editable items.
#[derive(Clone, PartialOrd, PartialEq, Ord, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub struct File {
buf: Vec<u8>,
/// A config file as parsed into tokens, where each [`Token`] is one of the three relevant items in git config files.
tokens: Vec<Token>, // but how do we get fast lookups and proper value lookup based on decoded values?
// On the fly is easier, otherwise we have to deal with a lookup cache of sorts and
// many more allocations up front (which might be worth it only once we have measurements).
// Cow<'a, _> would bind to our buffer so the cache can't be in this type.
// Probably it could be the 'Config' type which handles multiple files and treats them as one,
// and only if there is any need.
tokens: Vec<Token>,
}

impl File {
pub(crate) fn bytes_at(&self, span: Span) -> &BStr {
&self.buf[span.to_range()].as_bstr()
span.of(self.buf.as_slice()).unwrap().as_bstr()
}

pub(crate) fn token(&self, index: usize) -> &Token {
Expand Down
30 changes: 2 additions & 28 deletions git-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,12 @@
//! Additionally it's a stated goal as well to apply such restrictions only when values are read and optionally allow
//! a less limited character set. This opens up the git configuration format to other languages than English.

use std::ops::Range;

/// A span is a range into a set of bytes - see it as a selection into a Git config file.
///
/// Similar to [`std::ops::RangeInclusive`], but tailor made to work for us.
/// There are various issues with std ranges, which we don't have to opt into for the simple Range-like item we need.
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
struct Span {
pub start: usize,
pub end_inclusive: usize,
}

impl From<Span> for Range<usize> {
fn from(Span { start, end_inclusive }: Span) -> Self {
Range {
start,
end: end_inclusive + 1,
}
}
}

impl Span {
/// Convert a span into the standard library range type.
fn to_range(&self) -> Range<usize> {
self.clone().into()
}
}

///
pub mod file;
pub use file::File;

pub(crate) mod parse;

/// A module with specialized value types as they exist within git config files.
pub mod value;

Expand Down
2 changes: 1 addition & 1 deletion git-config/src/owned.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::Span;
use bstr::BString;
use dangerous::Span;

/// A key-value entry of a git-config file, like `name = value`
pub struct Entry {
Expand Down
135 changes: 135 additions & 0 deletions git-config/src/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use crate::{file, spanned};
use dangerous::{BytesReader, Error, Expected, Input, Span};

fn config(bytes: &[u8]) -> Result<Vec<file::Token>, Expected<'_>> {
fn config<'i, E>(r: &mut BytesReader<'i, E>) -> Result<Vec<file::Token>, E>
where
E: Error<'i>,
{
let mut tokens = Vec::new();
if let Some(section) = skip_whitespace_or_comment(r, ConsumeTo::NextToken) {
tokens.push(spanned::Comment(Span::from(section)))
};
unimplemented!("sections and values");
}
dangerous::input(bytes).read_all(|r| config(r))
}

enum ConsumeTo {
NextToken,
EndOfLine,
}

#[must_use]
fn skip_whitespace_or_comment<'a, E>(r: &mut BytesReader<'a, E>, to_where: ConsumeTo) -> Option<&'a [u8]> {
fn skip_whitespace_or_comment<E>(r: &mut BytesReader<'_, E>, to_where: ConsumeTo) {
fn skip_comment<E>(r: &mut BytesReader<'_, E>) -> usize {
if r.peek_eq(b'#') {
r.take_until_opt(b'\n').len()
} else {
0
}
}

let (mut last, mut current) = (0, 0);
loop {
current += skip_comment(r);
current += r
.take_while(|c: u8| {
let iwb = c.is_ascii_whitespace();
iwb && match to_where {
ConsumeTo::NextToken => true,
ConsumeTo::EndOfLine => c != b'\n',
}
})
.len();
if last == current {
if let ConsumeTo::EndOfLine = to_where {
r.consume_opt(b'\n');
}
break;
}
last = current;
}
}
let parsed = r
.take_consumed(|r| skip_whitespace_or_comment(r, to_where))
.as_dangerous();
if parsed.is_empty() {
None
} else {
Some(parsed)
}
}

#[cfg(test)]
mod tests {
mod comments {
use crate::parse::{skip_whitespace_or_comment, ConsumeTo};
use dangerous::{Input, Span};

macro_rules! decode_span {
($name:ident, $input:literal, $option:path, $range:expr, $explain:literal) => {
#[test]
fn $name() {
let bytes = $input;
let (res, _remaining) =
dangerous::input(bytes).read_infallible(|r| skip_whitespace_or_comment(r, $option));
assert_eq!(
res.map(Span::from),
Some(Span::from(&$input[$range])),
$explain
);
}
};
}

decode_span!(
no_comment_till_next_token,
b" \n \t\n",
ConsumeTo::NextToken,
0..13,
"it consumes newlines as well, taking everything"
);

decode_span!(
no_comment_to_end_of_line,
b" \n \t ",
ConsumeTo::EndOfLine,
0..6,
"it consumes only a single line, including the EOF marker"
);

decode_span!(
comment_to_next_token,
b" #ho \n \t ",
ConsumeTo::NextToken,
0..13,
"comments are the same as whitespace"
);

decode_span!(
comment_to_end_of_line,
b"# hi \n \t ",
ConsumeTo::EndOfLine,
0..6,
"comments are the same as whitespace"
);

decode_span!(
whitespace_to_token,
b" a=2 \n \t ",
ConsumeTo::NextToken,
0..3,
"it does not consume tokens"
);

decode_span!(
whitespace_to_token_on_next_line,
b" \n b=2\t ",
ConsumeTo::NextToken,
0..7,
"it does not consume tokens while skipping lines"
);
}
}
10 changes: 5 additions & 5 deletions git-config/src/spanned.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::Span;
use dangerous::Span;

// we parse leading and trailing whitespace into comments, avoiding the notion of whitespace.
// This means we auto-trim whitespace otherwise, which we a feature.
// All whitespace is automatically an empty comment.
#[derive(Clone, PartialOrd, PartialEq, Ord, Eq)]
pub(crate) struct Comment(Span);
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct Comment(pub(crate) Span);

/// A section or sub-section (in case `sub_name` is `Some()`), i.e.
///
Expand All @@ -15,14 +15,14 @@ pub(crate) struct Comment(Span);
///
/// [section "Sub-Section"]
/// ```
#[derive(Clone, PartialOrd, PartialEq, Ord, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct Section {
pub(crate) name: Span,
pub(crate) sub_name: Option<Span>,
}

/// A key-value entry of a git-config file, like `name = value`.
#[derive(Clone, PartialOrd, PartialEq, Ord, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct Entry {
pub(crate) name: Span,
pub(crate) value: Option<Span>,
Expand Down