Add doc comments to recipes (#101)
If a `#...` comment appears on the line immediately before a recipe, it is considered to be a doc comment for that recipe. Doc comments will be printed when recipes are `--list`ed or `--dump`ed. Also adds some color to the `--list`ing. Fixes #84
This commit is contained in:
parent
112462ec62
commit
26bfef4a2f
11
GRAMMAR.md
11
GRAMMAR.md
@ -13,7 +13,7 @@ tokens
|
|||||||
BACKTICK = `[^`\n\r]*`
|
BACKTICK = `[^`\n\r]*`
|
||||||
COLON = :
|
COLON = :
|
||||||
COMMENT = #([^!].*)?$
|
COMMENT = #([^!].*)?$
|
||||||
EOL = \n|\r\n
|
NEWLINE = \n|\r\n
|
||||||
EQUALS = =
|
EQUALS = =
|
||||||
INTERPOLATION_START = {{
|
INTERPOLATION_START = {{
|
||||||
INTERPOLATION_END = }}
|
INTERPOLATION_END = }}
|
||||||
@ -36,9 +36,12 @@ justfile : item* EOF
|
|||||||
item : recipe
|
item : recipe
|
||||||
| assignment
|
| assignment
|
||||||
| export
|
| export
|
||||||
| EOL
|
| eol
|
||||||
|
|
||||||
assignment : NAME '=' expression EOL
|
eol : NEWLINE
|
||||||
|
| COMMENT NEWLINE
|
||||||
|
|
||||||
|
assignment : NAME '=' expression eol
|
||||||
|
|
||||||
export : 'export' assignment
|
export : 'export' assignment
|
||||||
|
|
||||||
@ -58,7 +61,7 @@ dependencies : NAME+
|
|||||||
|
|
||||||
body : INDENT line+ DEDENT
|
body : INDENT line+ DEDENT
|
||||||
|
|
||||||
line : LINE (TEXT | interpolation)+ EOL
|
line : LINE (TEXT | interpolation)+ eol
|
||||||
|
|
||||||
interpolation : '{{' expression '}}'
|
interpolation : '{{' expression '}}'
|
||||||
```
|
```
|
||||||
|
17
justfile
17
justfile
@ -5,9 +5,7 @@ test: build
|
|||||||
filter PATTERN: build
|
filter PATTERN: build
|
||||||
cargo test --lib {{PATTERN}}
|
cargo test --lib {{PATTERN}}
|
||||||
|
|
||||||
test-quine:
|
# test with backtrace
|
||||||
cargo run -- quine clean
|
|
||||||
|
|
||||||
backtrace:
|
backtrace:
|
||||||
RUST_BACKTRACE=1 cargo test --lib
|
RUST_BACKTRACE=1 cargo test --lib
|
||||||
|
|
||||||
@ -22,6 +20,7 @@ watch COMMAND='test':
|
|||||||
|
|
||||||
version = `sed -En 's/version[[:space:]]*=[[:space:]]*"([^"]+)"/v\1/p' Cargo.toml`
|
version = `sed -En 's/version[[:space:]]*=[[:space:]]*"([^"]+)"/v\1/p' Cargo.toml`
|
||||||
|
|
||||||
|
# publish to crates.io
|
||||||
publish: lint clippy test
|
publish: lint clippy test
|
||||||
git branch | grep '* master'
|
git branch | grep '* master'
|
||||||
git diff --no-ext-diff --quiet --exit-code
|
git diff --no-ext-diff --quiet --exit-code
|
||||||
@ -33,6 +32,7 @@ publish: lint clippy test
|
|||||||
git push origin --tags
|
git push origin --tags
|
||||||
@echo 'Remember to merge the {{version}} branch on GitHub!'
|
@echo 'Remember to merge the {{version}} branch on GitHub!'
|
||||||
|
|
||||||
|
# clean up feature branch BRANCH
|
||||||
done BRANCH:
|
done BRANCH:
|
||||||
git checkout {{BRANCH}}
|
git checkout {{BRANCH}}
|
||||||
git pull --rebase github master
|
git pull --rebase github master
|
||||||
@ -40,9 +40,11 @@ done BRANCH:
|
|||||||
git pull --rebase github master
|
git pull --rebase github master
|
||||||
git branch -d {{BRANCH}}
|
git branch -d {{BRANCH}}
|
||||||
|
|
||||||
|
# install just from crates.io
|
||||||
install:
|
install:
|
||||||
cargo install -f just
|
cargo install -f just
|
||||||
|
|
||||||
|
# install development dependencies
|
||||||
install-dev-deps:
|
install-dev-deps:
|
||||||
rustup install nightly
|
rustup install nightly
|
||||||
rustup update nightly
|
rustup update nightly
|
||||||
@ -50,14 +52,18 @@ install-dev-deps:
|
|||||||
cargo install -f cargo-watch
|
cargo install -f cargo-watch
|
||||||
cargo install -f cargo-check
|
cargo install -f cargo-check
|
||||||
|
|
||||||
|
# everyone's favorite animate paper clip
|
||||||
clippy:
|
clippy:
|
||||||
rustup run nightly cargo clippy
|
rustup run nightly cargo clippy
|
||||||
|
|
||||||
|
# count non-empty lines of code
|
||||||
sloc:
|
sloc:
|
||||||
@cat src/*.rs | wc -l
|
@cat src/*.rs | sed '/^\s*$/d' | wc -l
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
|
echo Checking for FIXME/TODO...
|
||||||
! grep --color -En 'FIXME|TODO' src/*.rs
|
! grep --color -En 'FIXME|TODO' src/*.rs
|
||||||
|
echo Checking for long lines...
|
||||||
! grep --color -En '.{100}' src/*.rs
|
! grep --color -En '.{100}' src/*.rs
|
||||||
|
|
||||||
nop:
|
nop:
|
||||||
@ -68,6 +74,9 @@ fail:
|
|||||||
backtick-fail:
|
backtick-fail:
|
||||||
echo {{`exit 1`}}
|
echo {{`exit 1`}}
|
||||||
|
|
||||||
|
test-quine:
|
||||||
|
cargo run -- quine clean
|
||||||
|
|
||||||
# make a quine, compile it, and verify it
|
# make a quine, compile it, and verify it
|
||||||
quine: create
|
quine: create
|
||||||
cc tmp/gen0.c -o tmp/gen0
|
cc tmp/gen0.c -o tmp/gen0
|
||||||
|
19
src/app.rs
19
src/app.rs
@ -1,6 +1,7 @@
|
|||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
extern crate atty;
|
extern crate atty;
|
||||||
|
extern crate ansi_term;
|
||||||
|
|
||||||
use std::{io, fs, env, process, convert, ffi};
|
use std::{io, fs, env, process, convert, ffi};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
@ -46,6 +47,14 @@ impl UseColor {
|
|||||||
UseColor::Never => false,
|
UseColor::Never => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn blue(self, stream: atty::Stream) -> ansi_term::Style {
|
||||||
|
if self.should_color_stream(stream) {
|
||||||
|
ansi_term::Style::new().fg(ansi_term::Color::Blue)
|
||||||
|
} else {
|
||||||
|
ansi_term::Style::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit<P: convert::AsRef<ffi::OsStr>>(path: P) -> ! {
|
fn edit<P: convert::AsRef<ffi::OsStr>>(path: P) -> ! {
|
||||||
@ -210,11 +219,19 @@ pub fn app() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present("list") {
|
if matches.is_present("list") {
|
||||||
|
let blue = use_color.blue(atty::Stream::Stdout);
|
||||||
println!("Available recipes:");
|
println!("Available recipes:");
|
||||||
for (name, recipe) in &justfile.recipes {
|
for (name, recipe) in &justfile.recipes {
|
||||||
print!(" {}", name);
|
print!(" {}", name);
|
||||||
for parameter in &recipe.parameters {
|
for parameter in &recipe.parameters {
|
||||||
print!(" {}", parameter);
|
if use_color.should_color_stream(atty::Stream::Stdout) {
|
||||||
|
print!(" {:#}", parameter);
|
||||||
|
} else {
|
||||||
|
print!(" {}", parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(doc) = recipe.doc {
|
||||||
|
print!(" {} {}", blue.paint("#"), blue.paint(doc));
|
||||||
}
|
}
|
||||||
println!("");
|
println!("");
|
||||||
}
|
}
|
||||||
|
@ -1004,13 +1004,15 @@ recipe:
|
|||||||
#[test]
|
#[test]
|
||||||
fn dump() {
|
fn dump() {
|
||||||
let text ="
|
let text ="
|
||||||
|
# this recipe does something
|
||||||
recipe:
|
recipe:
|
||||||
@exit 100";
|
@exit 100";
|
||||||
integration_test(
|
integration_test(
|
||||||
&["--dump"],
|
&["--dump"],
|
||||||
text,
|
text,
|
||||||
0,
|
0,
|
||||||
"recipe:
|
"# this recipe does something
|
||||||
|
recipe:
|
||||||
@exit 100
|
@exit 100
|
||||||
",
|
",
|
||||||
"",
|
"",
|
||||||
@ -1096,15 +1098,19 @@ fn list() {
|
|||||||
integration_test(
|
integration_test(
|
||||||
&["--list"],
|
&["--list"],
|
||||||
r#"
|
r#"
|
||||||
|
|
||||||
|
# this does a thing
|
||||||
hello a b='B ' c='C':
|
hello a b='B ' c='C':
|
||||||
echo {{a}} {{b}} {{c}}
|
echo {{a}} {{b}} {{c}}
|
||||||
|
|
||||||
|
# this comment will be ignored
|
||||||
|
|
||||||
a Z="\t z":
|
a Z="\t z":
|
||||||
"#,
|
"#,
|
||||||
0,
|
0,
|
||||||
r"Available recipes:
|
r"Available recipes:
|
||||||
a Z='\t z'
|
a Z='\t z'
|
||||||
hello a b='B\t' c='C'
|
hello a b='B\t' c='C' # this does a thing
|
||||||
",
|
",
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
125
src/lib.rs
125
src/lib.rs
@ -19,7 +19,7 @@ extern crate edit_distance;
|
|||||||
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
use std::{fs, fmt, process, io, cmp};
|
use std::{fs, fmt, process, io, iter, cmp};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@ -59,6 +59,10 @@ fn re(pattern: &str) -> Regex {
|
|||||||
Regex::new(pattern).unwrap()
|
Regex::new(pattern).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn empty<T, C: iter::FromIterator<T>>() -> C {
|
||||||
|
iter::empty().collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn contains<T: PartialOrd>(range: &Range<T>, i: T) -> bool {
|
fn contains<T: PartialOrd>(range: &Range<T>, i: T) -> bool {
|
||||||
i >= range.start && i < range.end
|
i >= range.start && i < range.end
|
||||||
}
|
}
|
||||||
@ -67,6 +71,7 @@ fn contains<T: PartialOrd>(range: &Range<T>, i: T) -> bool {
|
|||||||
struct Recipe<'a> {
|
struct Recipe<'a> {
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
|
doc: Option<&'a str>,
|
||||||
lines: Vec<Vec<Fragment<'a>>>,
|
lines: Vec<Vec<Fragment<'a>>>,
|
||||||
dependencies: Vec<&'a str>,
|
dependencies: Vec<&'a str>,
|
||||||
dependency_tokens: Vec<Token<'a>>,
|
dependency_tokens: Vec<Token<'a>>,
|
||||||
@ -84,10 +89,12 @@ struct Parameter<'a> {
|
|||||||
|
|
||||||
impl<'a> Display for Parameter<'a> {
|
impl<'a> Display for Parameter<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
write!(f, "{}", self.name)?;
|
let green = maybe_green(f.alternate());
|
||||||
|
let cyan = maybe_cyan(f.alternate());
|
||||||
|
write!(f, "{}", cyan.paint(self.name))?;
|
||||||
if let Some(ref default) = self.default {
|
if let Some(ref default) = self.default {
|
||||||
let escaped = default.chars().flat_map(char::escape_default).collect::<String>();;
|
let escaped = default.chars().flat_map(char::escape_default).collect::<String>();;
|
||||||
write!(f, r#"='{}'"#, escaped)?;
|
write!(f, r#"='{}'"#, green.paint(escaped))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -281,11 +288,11 @@ impl<'a> Recipe<'a> {
|
|||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
let mut evaluator = Evaluator {
|
let mut evaluator = Evaluator {
|
||||||
evaluated: Map::new(),
|
evaluated: empty(),
|
||||||
scope: scope,
|
scope: scope,
|
||||||
exports: exports,
|
exports: exports,
|
||||||
assignments: &Map::new(),
|
assignments: &empty(),
|
||||||
overrides: &Map::new(),
|
overrides: &empty(),
|
||||||
quiet: options.quiet,
|
quiet: options.quiet,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -418,6 +425,9 @@ impl<'a> Recipe<'a> {
|
|||||||
|
|
||||||
impl<'a> Display for Recipe<'a> {
|
impl<'a> Display for Recipe<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
if let Some(doc) = self.doc {
|
||||||
|
writeln!(f, "# {}", doc)?;
|
||||||
|
}
|
||||||
write!(f, "{}", self.name)?;
|
write!(f, "{}", self.name)?;
|
||||||
for parameter in &self.parameters {
|
for parameter in &self.parameters {
|
||||||
write!(f, " {}", parameter)?;
|
write!(f, " {}", parameter)?;
|
||||||
@ -455,9 +465,9 @@ fn resolve_recipes<'a>(
|
|||||||
text: &'a str,
|
text: &'a str,
|
||||||
) -> Result<(), CompileError<'a>> {
|
) -> Result<(), CompileError<'a>> {
|
||||||
let mut resolver = Resolver {
|
let mut resolver = Resolver {
|
||||||
seen: Set::new(),
|
seen: empty(),
|
||||||
stack: vec![],
|
stack: empty(),
|
||||||
resolved: Set::new(),
|
resolved: empty(),
|
||||||
recipes: recipes,
|
recipes: recipes,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -553,9 +563,9 @@ fn resolve_assignments<'a>(
|
|||||||
let mut resolver = AssignmentResolver {
|
let mut resolver = AssignmentResolver {
|
||||||
assignments: assignments,
|
assignments: assignments,
|
||||||
assignment_tokens: assignment_tokens,
|
assignment_tokens: assignment_tokens,
|
||||||
stack: vec![],
|
stack: empty(),
|
||||||
seen: Set::new(),
|
seen: empty(),
|
||||||
evaluated: Set::new(),
|
evaluated: empty(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for name in assignments.keys() {
|
for name in assignments.keys() {
|
||||||
@ -626,11 +636,11 @@ fn evaluate_assignments<'a>(
|
|||||||
) -> Result<Map<&'a str, String>, RunError<'a>> {
|
) -> Result<Map<&'a str, String>, RunError<'a>> {
|
||||||
let mut evaluator = Evaluator {
|
let mut evaluator = Evaluator {
|
||||||
assignments: assignments,
|
assignments: assignments,
|
||||||
evaluated: Map::new(),
|
evaluated: empty(),
|
||||||
exports: &Set::new(),
|
exports: &empty(),
|
||||||
overrides: overrides,
|
overrides: overrides,
|
||||||
quiet: quiet,
|
quiet: quiet,
|
||||||
scope: &Map::new(),
|
scope: &empty(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for name in assignments.keys() {
|
for name in assignments.keys() {
|
||||||
@ -676,7 +686,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
if let Some(value) = self.overrides.get(name) {
|
if let Some(value) = self.overrides.get(name) {
|
||||||
self.evaluated.insert(name, value.to_string());
|
self.evaluated.insert(name, value.to_string());
|
||||||
} else {
|
} else {
|
||||||
let value = self.evaluate_expression(expression, &Map::new())?;
|
let value = self.evaluate_expression(expression, &empty())?;
|
||||||
self.evaluated.insert(name, value);
|
self.evaluated.insert(name, value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -950,6 +960,22 @@ fn maybe_red(colors: bool) -> ansi_term::Style {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn maybe_green(colors: bool) -> ansi_term::Style {
|
||||||
|
if colors {
|
||||||
|
ansi_term::Style::new().fg(ansi_term::Color::Green)
|
||||||
|
} else {
|
||||||
|
ansi_term::Style::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_cyan(colors: bool) -> ansi_term::Style {
|
||||||
|
if colors {
|
||||||
|
ansi_term::Style::new().fg(ansi_term::Color::Cyan)
|
||||||
|
} else {
|
||||||
|
ansi_term::Style::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn maybe_bold(colors: bool) -> ansi_term::Style {
|
fn maybe_bold(colors: bool) -> ansi_term::Style {
|
||||||
if colors {
|
if colors {
|
||||||
ansi_term::Style::new().bold()
|
ansi_term::Style::new().bold()
|
||||||
@ -1130,7 +1156,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ran = Set::new();
|
let mut ran = empty();
|
||||||
|
|
||||||
for (i, argument) in arguments.iter().enumerate() {
|
for (i, argument) in arguments.iter().enumerate() {
|
||||||
if let Some(recipe) = self.recipes.get(argument) {
|
if let Some(recipe) = self.recipes.get(argument) {
|
||||||
@ -1678,14 +1704,13 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
|
|
||||||
fn parse(text: &str) -> Result<Justfile, CompileError> {
|
fn parse(text: &str) -> Result<Justfile, CompileError> {
|
||||||
let tokens = tokenize(text)?;
|
let tokens = tokenize(text)?;
|
||||||
let filtered: Vec<_> = tokens.into_iter().filter(|token| token.kind != Comment).collect();
|
|
||||||
let parser = Parser {
|
let parser = Parser {
|
||||||
text: text,
|
text: text,
|
||||||
tokens: itertools::put_back(filtered),
|
tokens: itertools::put_back(tokens),
|
||||||
recipes: Map::<&str, Recipe>::new(),
|
recipes: empty(),
|
||||||
assignments: Map::<&str, Expression>::new(),
|
assignments: empty(),
|
||||||
assignment_tokens: Map::<&str, Token>::new(),
|
assignment_tokens: empty(),
|
||||||
exports: Set::<&str>::new(),
|
exports: empty(),
|
||||||
};
|
};
|
||||||
parser.file()
|
parser.file()
|
||||||
}
|
}
|
||||||
@ -1738,6 +1763,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn expect_eol(&mut self) -> Option<Token<'a>> {
|
fn expect_eol(&mut self) -> Option<Token<'a>> {
|
||||||
|
self.accepted(Comment);
|
||||||
if self.peek(Eol) {
|
if self.peek(Eol) {
|
||||||
self.accept(Eol);
|
self.accept(Eol);
|
||||||
None
|
None
|
||||||
@ -1755,7 +1781,12 @@ impl<'a> Parser<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recipe(&mut self, name: Token<'a>, quiet: bool) -> Result<(), CompileError<'a>> {
|
fn recipe(
|
||||||
|
&mut self,
|
||||||
|
name: Token<'a>,
|
||||||
|
doc: Option<Token<'a>>,
|
||||||
|
quiet: bool,
|
||||||
|
) -> Result<(), CompileError<'a>> {
|
||||||
if let Some(recipe) = self.recipes.get(name.lexeme) {
|
if let Some(recipe) = self.recipes.get(name.lexeme) {
|
||||||
return Err(name.error(ErrorKind::DuplicateRecipe {
|
return Err(name.error(ErrorKind::DuplicateRecipe {
|
||||||
recipe: recipe.name,
|
recipe: recipe.name,
|
||||||
@ -1875,6 +1906,7 @@ impl<'a> Parser<'a> {
|
|||||||
self.recipes.insert(name.lexeme, Recipe {
|
self.recipes.insert(name.lexeme, Recipe {
|
||||||
line_number: name.line,
|
line_number: name.line,
|
||||||
name: name.lexeme,
|
name: name.lexeme,
|
||||||
|
doc: doc.map(|t| t.lexeme[1..].trim()),
|
||||||
dependencies: dependencies,
|
dependencies: dependencies,
|
||||||
dependency_tokens: dependency_tokens,
|
dependency_tokens: dependency_tokens,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
@ -1930,13 +1962,43 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn file(mut self) -> Result<Justfile<'a>, CompileError<'a>> {
|
fn file(mut self) -> Result<Justfile<'a>, CompileError<'a>> {
|
||||||
|
// how do i associate comments with recipes?
|
||||||
|
// save the last doc
|
||||||
|
// clear it after every item
|
||||||
|
|
||||||
|
let mut doc = None;
|
||||||
|
|
||||||
|
/*
|
||||||
|
trait Swap<T> {
|
||||||
|
fn swap(&mut self, T) -> T
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Swap<Option<I>> for Option<I> {
|
||||||
|
fn swap(&mut self, replacement: Option<I>) -> Option<I> {
|
||||||
|
std::mem::replace(self, replacement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match self.tokens.next() {
|
match self.tokens.next() {
|
||||||
Some(token) => match token.kind {
|
Some(token) => match token.kind {
|
||||||
Eof => break,
|
Eof => break,
|
||||||
Eol => continue,
|
Eol => {
|
||||||
|
doc = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Comment => {
|
||||||
|
if let Some(token) = self.expect_eol() {
|
||||||
|
return Err(token.error(ErrorKind::InternalError {
|
||||||
|
message: format!("found comment followed by {}", token.kind),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
doc = Some(token);
|
||||||
|
}
|
||||||
At => if let Some(name) = self.accept(Name) {
|
At => if let Some(name) = self.accept(Name) {
|
||||||
self.recipe(name, true)?;
|
self.recipe(name, doc, true)?;
|
||||||
|
doc = None;
|
||||||
} else {
|
} else {
|
||||||
let unexpected = &self.tokens.next().unwrap();
|
let unexpected = &self.tokens.next().unwrap();
|
||||||
return Err(self.unexpected_token(unexpected, &[Name]));
|
return Err(self.unexpected_token(unexpected, &[Name]));
|
||||||
@ -1945,18 +2007,19 @@ impl<'a> Parser<'a> {
|
|||||||
let next = self.tokens.next().unwrap();
|
let next = self.tokens.next().unwrap();
|
||||||
if next.kind == Name && self.accepted(Equals) {
|
if next.kind == Name && self.accepted(Equals) {
|
||||||
self.assignment(next, true)?;
|
self.assignment(next, true)?;
|
||||||
|
doc = None;
|
||||||
} else {
|
} else {
|
||||||
self.tokens.put_back(next);
|
self.tokens.put_back(next);
|
||||||
self.recipe(token, false)?;
|
self.recipe(token, doc, false)?;
|
||||||
|
doc = None;
|
||||||
}
|
}
|
||||||
} else if self.accepted(Equals) {
|
} else if self.accepted(Equals) {
|
||||||
self.assignment(token, false)?;
|
self.assignment(token, false)?;
|
||||||
|
doc = None;
|
||||||
} else {
|
} else {
|
||||||
self.recipe(token, false)?;
|
self.recipe(token, doc, false)?;
|
||||||
|
doc = None;
|
||||||
},
|
},
|
||||||
Comment => return Err(token.error(ErrorKind::InternalError {
|
|
||||||
message: "found comment in token stream".to_string()
|
|
||||||
})),
|
|
||||||
_ => return return Err(self.unexpected_token(&token, &[Name, At])),
|
_ => return return Err(self.unexpected_token(&token, &[Name, At])),
|
||||||
},
|
},
|
||||||
None => return Err(CompileError {
|
None => return Err(CompileError {
|
||||||
|
34
src/unit.rs
34
src/unit.rs
@ -90,36 +90,38 @@ fn parse_error(text: &str, expected: CompileError) {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tokanize_strings() {
|
fn tokanize_strings() {
|
||||||
tokenize_success(
|
tokenize_success(
|
||||||
r#"a = "'a'" + '"b"' + "'c'" + '"d"'"#,
|
r#"a = "'a'" + '"b"' + "'c'" + '"d"'#echo hello"#,
|
||||||
r#"N="+'+"+'."#
|
r#"N="+'+"+'#."#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_recipe_interpolation_eol() {
|
fn tokenize_recipe_interpolation_eol() {
|
||||||
let text = "foo:
|
let text = "foo: # some comment
|
||||||
{{hello}}
|
{{hello}}
|
||||||
";
|
";
|
||||||
tokenize_success(text, "N:$>^{N}$<.");
|
tokenize_success(text, "N:#$>^{N}$<.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_recipe_interpolation_eof() {
|
fn tokenize_recipe_interpolation_eof() {
|
||||||
let text = "foo:
|
let text = "foo: # more comments
|
||||||
{{hello}}";
|
{{hello}}
|
||||||
tokenize_success(text, "N:$>^{N}<.");
|
# another comment
|
||||||
|
";
|
||||||
|
tokenize_success(text, "N:#$>^{N}$<#$.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_recipe_complex_interpolation_expression() {
|
fn tokenize_recipe_complex_interpolation_expression() {
|
||||||
let text = "foo:\n {{a + b + \"z\" + blarg}}";
|
let text = "foo: #lol\n {{a + b + \"z\" + blarg}}";
|
||||||
tokenize_success(text, "N:$>^{N+N+\"+N}<.");
|
tokenize_success(text, "N:#$>^{N+N+\"+N}<.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_recipe_multiple_interpolations() {
|
fn tokenize_recipe_multiple_interpolations() {
|
||||||
let text = "foo:\n {{a}}0{{b}}1{{c}}";
|
let text = "foo:#ok\n {{a}}0{{b}}1{{c}}";
|
||||||
tokenize_success(text, "N:$>^{N}_{N}_{N}<.");
|
tokenize_success(text, "N:#$>^{N}_{N}_{N}<.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -134,16 +136,19 @@ hello blah blah blah : a b c #whatever
|
|||||||
#[test]
|
#[test]
|
||||||
fn tokenize_empty_lines() {
|
fn tokenize_empty_lines() {
|
||||||
let text = "
|
let text = "
|
||||||
|
# this does something
|
||||||
hello:
|
hello:
|
||||||
asdf
|
asdf
|
||||||
bsdf
|
bsdf
|
||||||
|
|
||||||
csdf
|
csdf
|
||||||
|
|
||||||
dsdf
|
dsdf # whatever
|
||||||
|
|
||||||
|
# yolo
|
||||||
";
|
";
|
||||||
|
|
||||||
tokenize_success(text, "$N:$>^_$^_$$^_$$^_$<.");
|
tokenize_success(text, "$#$N:$>^_$^_$$^_$$^_$$<#$.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -173,11 +178,12 @@ hello:
|
|||||||
|
|
||||||
d
|
d
|
||||||
|
|
||||||
|
# hello
|
||||||
bob:
|
bob:
|
||||||
frank
|
frank
|
||||||
";
|
";
|
||||||
|
|
||||||
tokenize_success(text, "$N:$>^_$^_$$^_$$^_$$<N:$>^_$<.");
|
tokenize_success(text, "$N:$>^_$^_$$^_$$^_$$<#$N:$>^_$<.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user