Skip to content

Oops I wrote it again #7

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
14 changes: 3 additions & 11 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
# Compiled files
*.o
*.so
*.rlib
*.dll

# Executables
*.exe

# Generated by Cargo
/target/
**/*.rs.bk
target/
Cargo.lock
15 changes: 14 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
[package]
name = "pipeline"
version = "0.5.0"
version = "0.6.0"
authors = ["Johann Hofmann <[email protected]>"]
description = "A macro collection to pipe |> your functions calls, like in F# or Elixir."
repository = "https://github.com/johannhof/pipeline.rs"
readme = "README.md"
keywords = ["pipe", "function", "elixir", "macro", "composition"]
license = "MIT OR Apache-2.0"
edition = "2018"

[dependencies]
proc-macro-hack = { version = "0.5.15", optional = true }
pipeline-macro = { path = "macro", version = "0.6.0" }

[dev-dependencies]
anyhow = "1.0.28"

[features]
default = ["proc-macro-hack"]
nightly = ["pipeline-macro/nightly", "unstable"]
unstable = []
67 changes: 9 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Put this in your Cargo.toml
```toml
[dependencies]

pipeline = "0.5.0"
pipeline = "0.6.0"
```

Then you can import the macros with extern crate and macro_use
Expand All @@ -20,8 +20,7 @@ extern crate pipeline;
## Examples

```rust
// pipe_res exits the pipeline early if a function returns an Err()
let result = pipe_res!("http://rust-lang.org" => download => parse => get_links)
let result = pipe!("http://rust-lang.org" => download => parse => get_links)
```

```rust
Expand All @@ -31,69 +30,21 @@ fn times(a: u32, b: u32) -> u32{

let num = pipe!(
4
=> (times(10))
=> {|i: u32| i * 2}
=> (times(4))
=> _.times(_, 10)
=> |i: u32| i * 2
=> _ * 4
);

// takes a string length, doubles it and converts it back into a string
let length = pipe!(
"abcd"
=> [len]
=> (as u32)
=> times(2)
=> [to_string]
=> _.len
=> _ as u32
=> times(_, 2)
=> ToString::to_string
);
```

## Macros

- `pipe!` is the "standard" pipe macro
- `pipe_res!` works like `pipe!` but takes only functions that return a `Result` (of the
same type) and returns early if that result is an Err. Useful for combining multiple IO
transformations like opening a file, reading the contents and making an HTTP request.
- `pipe_opt!` works like `pipe!` but takes only functions that return an `Option` (of the same type).
The pipeline will continue to operate on the initial value as long as `None` is returned from all functions.
If a function in the pipeline returns `Some`, the macro will exit early and return that value.
This can be useful if you want to try out several functions to see which can make use of that value in a specified order.

## Syntax Features

Any `pipe` starts with an expression as initial value and requires you
to specify a function to transform that initial value.
```rust
let result = pipe!(2 => times2);
```

You can get more fancy with functions, too, if you add parentheses like
in a normal function call, the passed parameters will be applied to that
function after the transformed value.

> You have to put it in parentheses
because the Rust macro system can be very restrictive.
If you figure out a way to do it without please make a PR.

```rust
let result = pipe!(2 => (times(2)));
```

You can pass closures \o/! A closure must be wrapped in curly brackets (`{}`)
```rust
let result = pipe!(
2
=> (times(2))
=> {|i: u32| i * 2}
);
```

If you want a function to be called as a method on the transform value,
put it in square brackets (`[]`).
```rust
let result = pipe!(
"abcd"
=> [len]
);
```

## License

Expand Down
21 changes: 21 additions & 0 deletions macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "pipeline-macro"
version = "0.6.0"
authors = ["Johann Hofmann <[email protected]>", "piegames <[email protected]>"]
license = "MIT OR Apache-2.0"
description = "Procedural macros for the pipeline crate"
edition = "2018"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0.9"
quote = "1.0.3"
syn = { version = "1.0.17", features = ["extra-traits", "full"] }
proc-macro-hack = { version = "0.5.15", optional = true }

[features]
default = ["proc-macro-hack"]
nightly = ["unstable"]
unstable = []
15 changes: 15 additions & 0 deletions macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use self::pipeline::Pipeline;
use proc_macro::TokenStream;
#[cfg(not(feature = "nightly"))]
use proc_macro_hack::proc_macro_hack;
use quote::ToTokens;
use syn::parse_macro_input;

#[cfg_attr(feature = "nightly", proc_macro)]
#[cfg_attr(not(feature = "nightly"), proc_macro_hack)]
pub fn pipe(input: TokenStream) -> TokenStream {
let pipe = parse_macro_input!(input as Pipeline);
pipe.into_token_stream().into()
}

mod pipeline;
81 changes: 81 additions & 0 deletions macro/src/pipeline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::{quote, ToTokens};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
parse_quote,
token, Expr, Ident, Result, Token, Stmt,
};

#[derive(Debug)]
pub(super) struct Pipeline {
first: Expr,
statements: Vec<Stmt>,
}

impl Parse for Pipeline {
fn parse(input: ParseStream) -> Result<Self> {
let first: Expr = input.parse()?;

let mut statements: Vec<Stmt> = vec![];
while !input.is_empty() {
input.parse::<Token![=>]>()?;

let (expr, has_argument) = replace(input, false)?;
let expr: Expr = parse_quote!(#expr);
statements.push(if has_argument {
parse_quote!{
let ret = #expr;
}
} else {
parse_quote!{
let ret = #expr(ret);
}
})
}
Ok(Self {
first,
statements
})
}
}

fn replace(input: ParseStream, recursed: bool) -> Result<(TokenStream, bool)> {
let mut tokens = TokenStream::new();
let mut has_argument = false;
while !input.is_empty() {
if input.peek(Token![=>]) && !recursed{
break;
} else if input.peek(token::Paren) {
let content;
let paren_token = parenthesized!(content in input);
let (token_tree, has_argument_inner) = replace(&content, true)?;
has_argument |= has_argument_inner;
paren_token.surround(&mut tokens, |tokens| {
token_tree.to_tokens(tokens);
});
} else if input.peek(Token![_]) {
let _token = input.parse::<Token![_]>()?;
let ident = Ident::new("ret", Span::call_site());
tokens.extend(quote!(#ident));
has_argument = true;
} else {
let token_tree = input.parse::<TokenTree>()?;
tokens.extend(Some(token_tree));
}
}
Ok((tokens, has_argument))
}

impl ToTokens for Pipeline {
fn to_tokens(&self, tokens: &mut TokenStream) {
let first = &self.first;
let statements = &self.statements;
let expanded = quote!{{
let ret = #first;
#( #statements )*
ret
}};
tokens.extend(expanded);
}
}
Loading