Skip to content

Instantly share code, notes, and snippets.

@baetheus
Last active March 28, 2020 17:57
Show Gist options
  • Select an option

  • Save baetheus/c7d4525b0775fa7a752cc7afe31d052a to your computer and use it in GitHub Desktop.

Select an option

Save baetheus/c7d4525b0775fa7a752cc7afe31d052a to your computer and use it in GitHub Desktop.
Playing around with nom and parsing
use nom::{
branch::alt,
bytes::complete::{tag, take_until, take_while},
character::{complete::line_ending, is_alphabetic},
combinator::{map, opt, rest},
sequence::tuple,
IResult,
};
use std::str::from_utf8;
const FEAT_COMMIT: &str = r#"feat(my-feature): a good new feature
This is some body text
"#;
const BREAKING_COMMIT: &str = r#"fix(my-feature)!: changed the api to work better
This is some body text
"#;
#[derive(Debug, PartialEq, Eq)]
enum CommitType {
Feature,
Fix,
Other(String),
}
#[derive(Debug, PartialEq, Eq)]
struct Commit {
commit_type: CommitType,
breaking_change: bool,
scope: Option<String>,
message: String,
body: Option<String>,
}
// Hiding errors is bad practice
fn from_utf8_string(input: &[u8]) -> Option<String> {
match from_utf8(input).map(|s| s.to_string()) {
Ok(s) => Some(s),
_ => None,
}
}
fn to_commit_type(input: &[u8]) -> String {
from_utf8_string(input).unwrap_or("unknown".to_string())
}
fn alpha(input: &[u8]) -> IResult<&[u8], &[u8]> {
take_while(is_alphabetic)(input)
}
fn commit_type(input: &[u8]) -> IResult<&[u8], CommitType> {
alt((
map(tag("feat"), |_| CommitType::Feature),
map(tag("fix"), |_| CommitType::Fix),
// While this is alphabetic, it's not necessarily utf8
// Consider shunting to error here or dropping Other's internal value.
map(alpha, |r| CommitType::Other(to_commit_type(r))),
))(input)
}
fn commit_split(input: &[u8]) -> IResult<&[u8], &[u8]> {
tag(": ")(input)
}
fn scope(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8], &[u8])> {
tuple((tag("("), take_until(")"), tag(")")))(input)
}
fn opt_scope(input: &[u8]) -> IResult<&[u8], Option<&[u8]>> {
map(opt(scope), |o| o.map(|(_, m, _)| m))(input)
}
fn breaking_change(input: &[u8]) -> IResult<&[u8], bool> {
map(opt(tag("!")), |o| o.is_some())(input)
}
fn rest_of_line(input: &[u8]) -> IResult<&[u8], &[u8]> {
map(tuple((take_until("\n"), line_ending)), |(o, _)| o)(input)
}
fn body(input: &[u8]) -> IResult<&[u8], Option<&[u8]>> {
opt(rest)(input)
}
// At some point we should deal with encoding. Somewhere around here is probably best
fn commit(input: &[u8]) -> IResult<&[u8], Commit> {
map(
tuple((
commit_type,
opt_scope,
breaking_change,
commit_split,
rest_of_line,
body,
)),
|(commit_type, scope, breaking_change, _, message, body)| Commit {
commit_type,
// Here is where chain/bind would be great
scope: scope.map(from_utf8_string).flatten(),
breaking_change,
message: from_utf8_string(message).unwrap_or("No message".to_string()),
body: body.map(from_utf8_string).flatten(),
},
)(input)
}
fn main() {
if let Ok(feat_result) = commit(FEAT_COMMIT.as_bytes()) {
dbg!(feat_result);
};
if let Ok(fix_result) = commit(BREAKING_COMMIT.as_bytes()) {
dbg!(fix_result);
}
}
[src/main.rs:111] feat_result = (
[],
Commit {
commit_type: Feature,
breaking_change: false,
scope: Some(
"my-feature",
),
message: "a good new feature",
body: Some(
"\nThis is some body text\n",
),
},
)
[src/main.rs:114] fix_result = (
[],
Commit {
commit_type: Fix,
breaking_change: true,
scope: Some(
"my-feature",
),
message: "changed the api to work better",
body: Some(
"\nThis is some body text\n",
),
},
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment