diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fb2b2f1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI +on: + - push + - pull_request +jobs: + build: + name: Rust clippy and unit tests + runs-on: ubuntu-latest + strategy: + matrix: + rustup_channel: + - stable + - beta + - nightly + dev_flag: + - "" + - "--release" + steps: + - uses: actions/checkout@v2 + - name: rustup install + run: | + rustup toolchain update --no-self-update ${{ matrix.rustup_channel }} + rustup default ${{ matrix.rustup_channel }} + - name: install just + run: cargo install just + - run: just build + - name: unit tests + run: just test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/README.md b/README.md index 500f3c2..060d57c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ +// # RIIR why not Rewrite It In Rust (**RIIR**) Are you an author or contributor to a software project? -Have you ever been asked to rewrite, or consider rewriting that project in [Rust](https://www.rust-lang.org/)? +Have you ever been asked to rewrite, or consider rewriting that project in [Rust][rust-lang]? If so, you may have been a victim of the RIIR agenda that is sweeping the web. -If this has happened to you, please [report it](https://github.com/ansuz/RIIR/issues/) so that something can be done. +If this has happened to you, please [report it][issues] so that something can be done. ## FAQ @@ -18,7 +21,23 @@ No. This is a joke. ### Y U HATE RUST SO MUCH? -I don't, actually. I believe that those who spend their time asking people to rewrite their projects are probably not themselves active Rust developers, as those active devs are probably busy [writing memory-safe code](https://trac.torproject.org/projects/tor/ticket/11331). +I don't, actually. I believe that those who spend their time asking people to rewrite their projects are probably not themselves active Rust developers, as those active devs are probably busy [writing memory-safe code][memory-safe]. ### R U OFFENDING ME? ![](rust.png) + +## Building +This readme is valid Rust language. +It can be built into an executable by running the [just][just] command: + + just build + +Then run "just test", and this readme will be regenerated in Rust code! + +[rust-lang]: https://www.rust-lang.org/ +[issues]: https://github.com/ansuz/RIIR/issues/ +[memory-safe]: https://trac.torproject.org/projects/tor/ticket/11331 +[just]: https://github.com/casey/just + +// diff --git a/justfile b/justfile new file mode 100644 index 0000000..0448e9d --- /dev/null +++ b/justfile @@ -0,0 +1,19 @@ +build: clean build-macro + #!/usr/bin/env bash + LIB_PATH=$(echo -n target/debug/deps/libriir_macro-*.so) + rustc \ + --crate-name riir \ + --edition=2018 \ + README.md \ + --crate-type bin \ + --out-dir target/debug \ + -L dependency=target/debug/deps \ + --extern riir_macro=$LIB_PATH +clean: + #!/usr/bin/env bash + rm target/debug/deps/libriir_macro-*.so || true +build-macro: + cd riir-macro && cargo build --target-dir=../target $( (rustc --version | grep nightly >/dev/null) && echo --features span) + +test: + cd riir-macro && cargo test --target-dir=../target $( (rustc --version | grep nightly >/dev/null) && echo --features span) diff --git a/riir-macro/Cargo.lock b/riir-macro/Cargo.lock new file mode 100644 index 0000000..c7f96f9 --- /dev/null +++ b/riir-macro/Cargo.lock @@ -0,0 +1,33 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "riir-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/riir-macro/Cargo.toml b/riir-macro/Cargo.toml new file mode 100644 index 0000000..25ce472 --- /dev/null +++ b/riir-macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "riir-macro" +version = "0.1.0" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.18" +quote = "1.0.6" + +[features] +span = ["proc-macro2/span-locations"] diff --git a/riir-macro/src/lib.rs b/riir-macro/src/lib.rs new file mode 100644 index 0000000..d34d077 --- /dev/null +++ b/riir-macro/src/lib.rs @@ -0,0 +1,142 @@ +#![cfg_attr(feature = "span", feature(proc_macro_span))] + +use proc_macro2::{Delimiter, Span, TokenStream, TokenTree}; +use quote::quote; + +#[proc_macro] +pub fn print_literally(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let string = print_ts(ts.into()); + let ts = quote! { + fn main() { + print!(#string); + } + }; + ts.into() +} + +fn print_ts(ts: TokenStream) -> String { + let mut output = String::new(); + + #[cfg_attr(feature = "span", allow(unused_mut, unused_variables))] + let mut was_hash = false; + #[cfg_attr(not(feature = "span"), allow(unused_mut, unused_variables))] + let mut last_span: Option = None; + + for tt in ts { + #[cfg(feature = "span")] + { + if let Some(last_span) = last_span { + let end = last_span.end(); + let start = tt.span().start(); + let end_col = if start.line > end.line { + let lines = start.line - end.line; + output.extend((0..lines).map(|_| '\n')); + 0 + } else { + end.column + }; + let space_size = start.column as isize - end_col as isize; + output.extend((0..space_size).map(|_| ' ')); + } + } + #[cfg_attr(not(feature = "span"), allow(unused_assignments))] + { + last_span = Some(tt.span()); + } + match tt { + TokenTree::Ident(ident) => { + #[cfg_attr(not(feature = "span"), allow(unused_mut))] + let mut string = ident.to_string(); + #[cfg(feature = "span")] + { + fill_space(ident.span(), &mut string); + } + #[cfg(not(feature = "span"))] + { + string.push(' '); + } + output.push_str(&string); + } + TokenTree::Punct(punct) => { + let mut string = punct.as_char().to_string(); + #[cfg(feature = "span")] + { + fill_space(punct.span(), &mut string); + } + #[cfg(not(feature = "span"))] + { + use proc_macro2::Spacing; + + if punct.as_char() == '#' { + if !was_hash { + string = format!("\n{}", string); + } + was_hash = true; + } + if punct.spacing() == Spacing::Joint { + string.push(' '); + } + } + output.push_str(&string); + } + TokenTree::Literal(lit) => { + #[cfg_attr(not(feature = "span"), allow(unused_mut))] + let mut string = lit.to_string(); + #[cfg(feature = "span")] + { + fill_space(lit.span(), &mut string); + } + output.push_str(&string); + } + TokenTree::Group(group) => { + let mut string = String::new(); + let (l, r) = match group.delimiter() { + Delimiter::Parenthesis => ("(", ")"), + Delimiter::Bracket => ("[", "]"), + Delimiter::Brace => ("{", "}"), + Delimiter::None => ("", ""), + }; + string.push_str(l); + string.push_str(&print_ts(group.stream())); + string.push_str(r); + #[cfg(feature = "span")] + { + fill_space(group.span(), &mut string); + } + output.push_str(&string); + } + } + } + output +} + +#[cfg(feature = "span")] +fn fill_space(span: Span, string: &mut String) { + let start_col = if span.end().line > span.start().line { + let lines = span.end().line - span.start().line; + string.extend((0..lines).map(|_| '\n')); + -1 + } else { + (span.start().column + string.len()) as isize + }; + let space_size = span.end().column as isize - start_col; + string.extend((0..space_size).map(|_| ' ')); +} + +#[cfg(test)] +#[test] +fn test() { + use std::{fs, path::PathBuf}; + + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test.txt"); + let content = fs::read_to_string(path).unwrap(); + let actual = print_ts(content.parse().unwrap()); + #[cfg(feature = "span")] + { + assert_eq!(actual, content.trim()); + } + #[cfg(not(feature = "span"))] + { + // TODO write unit tests for non-span-sensitive code + } +} diff --git a/riir-macro/test.txt b/riir-macro/test.txt new file mode 100644 index 0000000..40edba1 --- /dev/null +++ b/riir-macro/test.txt @@ -0,0 +1,2 @@ +a bc + ## de f diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly