Skip to content

Commit

Permalink
feat(BundleText): Finish BundleText
Browse files Browse the repository at this point in the history
  • Loading branch information
realth000 committed Apr 25, 2024
1 parent fc5fa30 commit 5619a33
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 76 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "racros"
version = "0.1.0"
version = "0.2.0"
description = "Collection of rust macros"
categories = ["rust-patterns", "value-formatting"]
keywords = ["derive", "macros"]
Expand Down
66 changes: 53 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@ Implement [`TryFrom`] `String` and [`ToString`] for enums with following feature
* Specify what `String` value can convert from/to.
* Allow convert from multiple `String` values.
* Set default convert style:
* `lowercase`
* `UPPERCASE`
* `camelCase`
* `PascalCase`
* `snake_case`
* `SCREAMING_CASE`
* `lowercase`
* `UPPERCASE`
* `camelCase`
* `PascalCase`
* `snake_case`
* `SCREAMING_CASE`

For the following code:

Expand Down Expand Up @@ -165,13 +165,14 @@ impl ToString for MyEnum {
```

The string format can be set to
* `lowercase`
* `UPPERCASE`
* `camelCase`
* `PascalCase`
* `snake_case`
* `SCREAMING_CASE`
by adding a `#[autorule = "xxxx"]` attribute to the enum:

* `lowercase`
* `UPPERCASE`
* `camelCase`
* `PascalCase`
* `snake_case`
* `SCREAMING_CASE`
by adding a `#[autorule = "xxxx"]` attribute to the enum:

``` rust
#[derive(AutoStr)]
Expand Down Expand Up @@ -418,3 +419,42 @@ assert_eq!(s31.bar1.foo2, s2.foo2);
assert_eq!(s31.bar1.foo3, s2.foo3);

```

### BundleText

Bundle text content or command output into static str at compile time and use in runtime.

#### Usage

* Bundle file: #[bundle(name = "get_file", file = "file/path")]
* Bundle command: #[bundle(name = "get_rustc_version", command = "rustc --version")]

#### Example

```rust
use std::io::Read;
use std::process::Stdio;
use racros::BundleText;

#[derive(BundleText)]
#[bundle(name = "some_file", file = "data/text")]
#[bundle(name = "my_rustc_version", command = "rustc --version")]
enum Bundler{}

assert_eq!(
Bundler::some_file(),
r#"Some Text
To Read"#
);
let mut stdout = String::new();
std::process::Command::new("rustc")
.arg("--version")
.stdout(Stdio::piped())
.spawn()
.unwrap()
.stdout
.unwrap()
.read_to_string(&mut stdout)
.expect("failed to run rustc");
assert_eq!(Bundler::my_rustc_version(), stdout);
```
2 changes: 2 additions & 0 deletions data/text
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Some Text
To Read
22 changes: 21 additions & 1 deletion examples/bundle_text.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
use std::io::Read;
use std::process::Stdio;

use racros::BundleText;

#[derive(BundleText)]
#[bundle(name = "some_file", file = "data/text")]
#[bundle(name = "my_rustc_version", command = "rustc --version")]
enum Bundler {}

fn main() {}
fn main() {
assert_eq!(
Bundler::some_file(),
r#"Some Text
To Read"#
);
let mut stdout = String::new();
std::process::Command::new("rustc")
.arg("--version")
.stdout(Stdio::piped())
.spawn()
.unwrap()
.stdout
.unwrap()
.read_to_string(&mut stdout)
.expect("failed to run rustc");
assert_eq!(Bundler::my_rustc_version(), stdout);
}
4 changes: 3 additions & 1 deletion src/auto_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use syn::{
Meta, MetaList, MetaNameValue,
};

use crate::util::{compiling_error, to_camel_case, to_pascal_case, to_snake_case, to_screaming_case};
use crate::util::{
compiling_error, to_camel_case, to_pascal_case, to_screaming_case, to_snake_case,
};

#[derive(Debug)]
enum Rules {
Expand Down
166 changes: 106 additions & 60 deletions src/bundle_text.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use crate::util::compiling_error;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Literal, Punct, TokenTree};
use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use std::fs::OpenOptions;
use std::io::Read;
use std::process::Stdio;

use proc_macro2::{Ident, TokenTree};
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Attribute, Data, DeriveInput, Meta, MetaList, Path};

static KEY_NAME: &str = "name";
static KEY_FILE: &str = "file";
static KEY_COMMAND: &str = "command";
use crate::util::compiling_error;

static ERROR_INVALID_USAGE: &str = r#"invalid usage of #[derive(BundleText)].
const KEY_NAME: &str = "name";
const KEY_FILE: &str = "file";
const KEY_COMMAND: &str = "command";

const ERROR_INVALID_USAGE: &str = r#"invalid usage of #[derive(BundleText)].
Only support bundling file content or command output.
Example:
1. Bundle file: #[bundle(name = "get_file", file = "file/path")]
Expand All @@ -27,45 +31,9 @@ pub fn bundle_text_internal(input: TokenStream) -> TokenStream {
}
}

/// Describe and parse the bundlers in attributes.
///
/// # Usage
///
/// ## Bundle file content
///
/// `#[bundle(name = "get_file", file = "file/path")]`
///
/// ## Bundle command output
///
/// `#[bundle(name = "get_rustc_version", command = "rustc --version")]`
#[derive(Debug)]
struct SingleBundler {
name: Ident,
punct: Punct,
name_value: Literal,
punct2: Punct,
source: Ident,
punct3: Punct,
source_value: Literal,
}

impl Parse for SingleBundler {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
name: input.parse()?,
punct: input.parse()?,
name_value: input.parse()?,
punct2: input.parse()?,
source: input.parse()?,
punct3: input.parse()?,
source_value: input.parse()?,
})
}
}

fn bundle_text_enum(ast: &DeriveInput) -> TokenStream {
let mut keys = vec![];
let mut values = vec![];
let target_ident = &ast.ident;
let mut ret = vec![];
for attr in &ast.attrs {
if let Attribute {
meta:
Expand All @@ -80,6 +48,8 @@ fn bundle_text_enum(ast: &DeriveInput) -> TokenStream {
if segments.is_empty() || segments.last().unwrap().ident != "bundle" {
continue;
}
let mut keys = vec![];
let mut values = vec![];
for tt in ts.into_token_stream() {
match tt {
TokenTree::Ident(ident) => keys.push(ident),
Expand All @@ -88,20 +58,96 @@ fn bundle_text_enum(ast: &DeriveInput) -> TokenStream {
}
}

break;
// Catch and check these legal values:
//
// 1. `#[bundle(name = "get_file", file = "file/path")]`
// 2. `#[bundle(name = "get_rustc_version", command = "rustc --version")]`
if keys.len() != 2
|| values.len() != 2
|| keys[0] != KEY_NAME
|| (keys[1] != KEY_FILE && keys[1] != KEY_COMMAND)
{
return compiling_error!(proc_macro2::Span::call_site(), "{ERROR_INVALID_USAGE}");
}
let v1s = values[0].to_string();
let name = Ident::new(v1s.get(1..v1s.len() - 1).unwrap(), values[0].span());
let content = match keys[1].to_string().as_str() {
KEY_FILE => {
// Surrounded by double quote.
let raw_file_name = values[1].to_string();
let file_name = raw_file_name.get(1..raw_file_name.len() - 1).unwrap();
let mut file_path = std::env::current_dir().unwrap();
file_path.push(file_name);
let file = OpenOptions::new().read(true).open(&file_path);
if file.is_err() {
return compiling_error!(
proc_macro2::Span::call_site(),
"failed to open file {file_path:#?}: {}",
file.err().unwrap()
);
}
let mut file_content = String::new();
if let Err(e) = file.unwrap().read_to_string(&mut file_content) {
return compiling_error!(
proc_macro2::Span::call_site(),
"failed to read file {file_path:#?}: {}",
e,
);
}
file_content
}
KEY_COMMAND => {
let v1s = values[1].to_string();

let mut command_and_args = v1s
.get(1..v1s.len() - 1)
.unwrap()
.split(" ")
.map(|x| x.to_string())
.collect::<Vec<_>>();
let command = command_and_args.remove(0);
let child = std::process::Command::new(command)
.args(command_and_args)
.stdout(Stdio::piped())
.spawn();
if let Err(e) = child {
return compiling_error!(
proc_macro2::Span::call_site(),
"failed to run command {}: {}",
values[1],
e,
);
}
let mut command_output = String::new();
let mut x = child.unwrap().stdout.unwrap();
let run_result = x.read_to_string(&mut command_output);
if run_result.is_err() {
return compiling_error!(
proc_macro2::Span::call_site(),
"failed to run command {}: {}",
values[1],
run_result.err().unwrap()
);
}

command_output
}
_ => panic!("impossible"),
};

let expand = quote! {
fn #name() -> &'static str {
#content
}
};

ret.push(expand);
}
}
// Catch and check these legal values:
//
// 1. `#[bundle(name = "get_file", file = "file/path")]`
// 2. `#[bundle(name = "get_rustc_version", command = "rustc --version")]`
if keys.len() != 2
|| values.len() != 2
|| keys[0] != KEY_NAME
|| (keys[1] != KEY_FILE && keys[1] != KEY_COMMAND)
{
return compiling_error!(proc_macro2::Span::call_site(), "{ERROR_INVALID_USAGE}");
}
panic!("keys={keys:#?}, values={values:#?}, {:#?}", keys.len());
TokenStream::new()
let expand = quote! {
impl #target_ident {
#(#ret)*
}
};
expand.into()
}
41 changes: 41 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
//!
//! Add a `copy_with` function for decorated type, copy value from another `Self` if that value is
//! not `default` value.
//!
//! ## [`BundleText`]
//!
//! Bundle text content or command output into static str at compile time and use in runtime.
////////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -385,6 +389,43 @@ pub fn auto_debug(input: TokenStream) -> TokenStream {
auto_debug::auto_debug_internal(input)
}

/// Bundle text contents and command outputs into static str and use in runtime.
///
/// # Usage
///
/// * Bundle file: #[bundle(name = "get_file", file = "file/path")]
/// * Bundle command: #[bundle(name = "get_rustc_version", command = "rustc --version")]
///
/// # Example
///
/// ```
/// use std::io::Read;
/// use std::process::Stdio;
/// use racros::BundleText;
///
/// #[derive(BundleText)]
/// #[bundle(name = "some_file", file = "data/text")]
/// #[bundle(name = "my_rustc_version", command = "rustc --version")]
/// enum Bundler{}
///
/// assert_eq!(
/// Bundler::some_file(),
/// r#"Some Text
/// To Read"#
/// );
/// let mut stdout = String::new();
/// std::process::Command::new("rustc")
/// .arg("--version")
/// .stdout(Stdio::piped())
/// .spawn()
/// .unwrap()
/// .stdout
/// .unwrap()
/// .read_to_string(&mut stdout)
/// .expect("failed to run rustc");
/// assert_eq!(Bundler::my_rustc_version(), stdout);
/// ```
///
#[proc_macro_derive(BundleText, attributes(bundle))]
pub fn bundle_text(input: TokenStream) -> TokenStream {
bundle_text::bundle_text_internal(input)
Expand Down

0 comments on commit 5619a33

Please sign in to comment.