Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ rust/src/ast/nodes.rs
rust/src/errors.rs
rust/src/nodes.rs
rust/src/union_types.rs
rust/src/visitor.rs
sig/serialized_ast_errors.rbs
sig/serialized_ast_nodes.rbs
src/analyze_missing_end.c
Expand Down
2 changes: 0 additions & 2 deletions rust/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
[build]
rerun-if-changed = ["../src"]
7 changes: 6 additions & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
[workspace]
members = [
"."
]

[package]
name = "herb"
version = "0.8.10"
Expand All @@ -21,7 +26,7 @@ include = [
[lib]
name = "herb"
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]
crate-type = ["rlib"]

[[bin]]
name = "herb-rust"
Expand Down
35 changes: 23 additions & 12 deletions rust/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,55 @@ BIN_NAME = herb-rust
BIN_PATH = $(BUILD_DIR)/debug/$(BIN_NAME)
RELEASE_BIN_PATH = $(BUILD_DIR)/release/$(BIN_NAME)

.PHONY: all build release clean test cli help templates format vendor
.PHONY: all build release clean test cli help templates format vendor wasm

all: templates build

templates:
cd .. && bundle exec rake templates

build: templates
cargo +stable build --verbose --bin $(BIN_NAME)
@echo "Built debug binary: $(BIN_PATH)"
cargo +stable build --verbose --workspace
@echo "Built workspace (debug)"

release: templates
cargo build --release --bin $(BIN_NAME)
@echo "Built release binary: $(RELEASE_BIN_PATH)"
cargo build --release --workspace
@echo "Built workspace (release)"

cli: build
@echo "CLI ready! Use: ./bin/$(BIN_NAME) [command] [file]"
@echo ""
@./bin/$(BIN_NAME) || true

test: build
NO_COLOR=1 cargo +stable test --verbose
NO_COLOR=1 cargo +stable test --verbose --workspace

format:
cargo +nightly fmt
cargo +nightly fmt --all
@echo "Formatted Rust code"

format-check:
cargo +nightly fmt --check
cargo +nightly fmt --all --check
@echo "Checked Rust code formatting"

lint:
cargo +stable clippy --all-targets --all-features
cargo +stable clippy --workspace --all-targets --all-features
@echo "Linted Rust code"

clean:
cargo clean
@echo "Cleaned Rust build artifacts"

wasm: templates
@echo "Building herb-linter for wasm32-unknown-emscripten..."
@# Temporarily remove cdylib from herb crate-type to avoid emscripten linker errors
sed 's/crate-type = \["cdylib", "rlib"\]/crate-type = ["rlib"]/' Cargo.toml > Cargo.toml.wasm
mv Cargo.toml Cargo.toml.bak && mv Cargo.toml.wasm Cargo.toml
cargo rustc --release --target wasm32-unknown-emscripten -p herb-linter --lib --crate-type staticlib || \
(mv Cargo.toml.bak Cargo.toml && exit 1)
mv Cargo.toml.bak Cargo.toml
@echo "Built target/wasm32-unknown-emscripten/release/libherb_linter.a"

vendor:
@echo "Vendoring C sources into vendor/libherb..."
rm -rf vendor/
Expand All @@ -62,11 +72,12 @@ help:
@echo "Targets:"
@echo " all - Build everything (templates + Rust)"
@echo " templates - Generate code from ERB templates"
@echo " build - Build debug binary"
@echo " release - Build release binary"
@echo " build - Build workspace (debug)"
@echo " release - Build workspace (release)"
@echo " cli - Show CLI usage"
@echo " test - Run Rust tests"
@echo " test - Run all workspace tests"
@echo " format - Format Rust code with rustfmt"
@echo " wasm - Build herb-linter staticlib for wasm32-unknown-emscripten"
@echo " vendor - Vendor C sources into vendor/"
@echo " clean - Remove build artifacts"
@echo " help - Show this help"
Expand Down
149 changes: 79 additions & 70 deletions rust/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
let target = env::var("TARGET").unwrap_or_default();
let is_wasm = target.contains("wasm32") || target.contains("emscripten");

let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let vendor_src_dir = manifest_dir.join("vendor/libherb/src");
let vendor_include_dir = manifest_dir.join("vendor/libherb/src/include");
Expand All @@ -11,71 +14,78 @@ fn main() {
(vendor_src_dir, vendor_include_dir, manifest_dir.clone())
} else {
let root = manifest_dir.parent().unwrap();
(
root.join("src"),
root.join("src/include"),
root.to_path_buf(),
)
(root.join("src"), root.join("src/include"), root.to_path_buf())
};

let vendor_prism_src = manifest_dir.join("vendor/prism/src");
let vendor_prism_include = manifest_dir.join("vendor/prism/include");
let prism_include = if is_wasm {
let vendor_prism_include = manifest_dir.join("vendor/prism/include");

let prism_include = if vendor_prism_src.exists() {
let mut prism_sources = Vec::new();
for path in glob::glob(vendor_prism_src.join("**/*.c").to_str().unwrap())
.unwrap()
.flatten()
{
prism_sources.push(path);
if vendor_prism_include.exists() {
vendor_prism_include
} else {
let prism_path = get_prism_path(&root_dir);
prism_path.join("include")
}
} else {
let vendor_prism_src = manifest_dir.join("vendor/prism/src");
let vendor_prism_include = manifest_dir.join("vendor/prism/include");

let prism_include = if vendor_prism_src.exists() {
let mut prism_sources = Vec::new();
for path in glob::glob(vendor_prism_src.join("**/*.c").to_str().unwrap()).unwrap().flatten() {
prism_sources.push(path);
}

let mut prism_build = cc::Build::new();
prism_build
.flag("-std=c99")
.flag("-fPIC")
.opt_level(2)
.include(&vendor_prism_include)
.files(&prism_sources)
.warnings(false);

prism_build.compile("prism");

vendor_prism_include
} else {
let prism_path = get_prism_path(&root_dir);
let prism_build = prism_path.join("build");
println!("cargo:rustc-link-search=native={}", prism_build.display());
println!("cargo:rustc-link-lib=static=prism");
prism_path.join("include")
};

let mut c_sources = Vec::new();

for path in glob::glob(src_dir.join("**/*.c").to_str().unwrap()).unwrap().flatten() {
if !path.ends_with("main.c") {
c_sources.push(path);
}
}

let mut prism_build = cc::Build::new();
prism_build
let mut build = cc::Build::new();
build
.flag("-std=c99")
.flag("-Wall")
.flag("-Wextra")
.flag("-Wno-unused-parameter")
.flag("-fPIC")
.opt_level(2)
.include(&vendor_prism_include)
.files(&prism_sources)
.warnings(false);

prism_build.compile("prism");
.include(&include_dir)
.include(&prism_include)
.files(&c_sources);

vendor_prism_include
} else {
let prism_path = get_prism_path(&root_dir);
let prism_build = prism_path.join("build");
println!("cargo:rustc-link-search=native={}", prism_build.display());
println!("cargo:rustc-link-lib=static=prism");
prism_path.join("include")
};
build.compile("herb");

let mut c_sources = Vec::new();

for path in glob::glob(src_dir.join("**/*.c").to_str().unwrap())
.unwrap()
.flatten()
{
if !path.ends_with("main.c") {
c_sources.push(path);
for source in &c_sources {
println!("cargo:rerun-if-changed={}", source.display());
}
}

let mut build = cc::Build::new();
build
.flag("-std=c99")
.flag("-Wall")
.flag("-Wextra")
.flag("-Wno-unused-parameter")
.flag("-fPIC")
.opt_level(2)
.include(&include_dir)
.include(&prism_include)
.files(&c_sources);

build.compile("herb");

let bindings = bindgen::Builder::default()
prism_include
};

let mut builder = bindgen::Builder::default()
.header(include_dir.join("analyze.h").to_str().unwrap())
.header(include_dir.join("herb.h").to_str().unwrap())
.header(include_dir.join("ast_nodes.h").to_str().unwrap())
Expand Down Expand Up @@ -116,19 +126,23 @@ fn main() {
.derive_debug(true)
.derive_default(false)
.prepend_enum_name(false)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));

let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
if is_wasm {
let host = env::var("HOST").unwrap_or_default();

if !host.is_empty() {
builder = builder.clang_arg(format!("--target={}", host));
}

for source in &c_sources {
println!("cargo:rerun-if-changed={}", source.display());
builder = builder.layout_tests(false);
}

let bindings = builder.generate().expect("Unable to generate bindings");

let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings.write_to_file(out_path.join("bindings.rs")).expect("Couldn't write bindings!");

println!("cargo:rerun-if-changed={}", include_dir.display());
println!("cargo:rerun-if-changed=build.rs");
}
Expand All @@ -140,14 +154,9 @@ fn get_prism_path(root_dir: &Path) -> PathBuf {
.output()
.expect("Failed to run `bundle show prism`");

let output_str = String::from_utf8(output.stdout).expect("Failed to parse bundle output");
let output_string = String::from_utf8(output.stdout).expect("Failed to parse bundle output");

let path_str = output_str
.lines()
.last()
.expect("No output from bundle show prism")
.trim()
.to_string();
let path_string = output_string.lines().last().expect("No output from bundle show prism").trim().to_string();

PathBuf::from(path_str)
PathBuf::from(path_string)
}
3 changes: 2 additions & 1 deletion rust/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
edition = "2021"
max_width = 100
max_width = 160
hard_tabs = false
tab_spaces = 2
newline_style = "Unix"
Expand All @@ -15,4 +15,5 @@ ignore = [
"src/errors.rs",
"src/nodes.rs",
"src/union_types.rs",
"src/visitor.rs",
]
4 changes: 1 addition & 3 deletions rust/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ pub unsafe fn token_from_c(token_ptr: *const token_T) -> Token {
CStr::from_ptr(token.value).to_string_lossy().into_owned()
};

let token_type = CStr::from_ptr(crate::ffi::token_type_to_string(token.type_))
.to_string_lossy()
.into_owned();
let token_type = CStr::from_ptr(crate::ffi::token_type_to_string(token.type_)).to_string_lossy().into_owned();

Token {
token_type,
Expand Down
5 changes: 2 additions & 3 deletions rust/src/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
pub use crate::bindings::{
ast_node_free, element_source_to_string, hb_array_get, hb_array_size, hb_buffer_init,
hb_buffer_value, hb_string_T, herb_extract, herb_extract_ruby_to_buffer_with_options,
herb_free_tokens, herb_lex, herb_parse, herb_prism_version, herb_version, token_type_to_string,
ast_node_free, element_source_to_string, hb_array_get, hb_array_size, hb_buffer_init, hb_buffer_value, hb_string_T, herb_extract,
herb_extract_ruby_to_buffer_with_options, herb_free_tokens, herb_lex, herb_parse, herb_prism_version, herb_version, token_type_to_string,
};
19 changes: 4 additions & 15 deletions rust/src/herb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ pub fn parse_with_options(source: &str, options: &ParserOptions) -> Result<Parse
return Err("Failed to parse source".to_string());
}

let document_node = crate::ast::convert_document_node(ast as *const std::ffi::c_void)
.ok_or_else(|| "Failed to convert AST".to_string())?;
let document_node = crate::ast::convert_document_node(ast as *const std::ffi::c_void).ok_or_else(|| "Failed to convert AST".to_string())?;

let result = ParseResult::new(document_node, source.to_string(), Vec::new(), options);

Expand All @@ -99,10 +98,7 @@ pub fn extract_ruby(source: &str) -> Result<String, String> {
extract_ruby_with_options(source, &ExtractRubyOptions::default())
}

pub fn extract_ruby_with_options(
source: &str,
options: &ExtractRubyOptions,
) -> Result<String, String> {
pub fn extract_ruby_with_options(source: &str, options: &ExtractRubyOptions) -> Result<String, String> {
unsafe {
let c_source = CString::new(source).map_err(|e| e.to_string())?;

Expand All @@ -119,11 +115,7 @@ pub fn extract_ruby_with_options(
preserve_positions: options.preserve_positions,
};

crate::ffi::herb_extract_ruby_to_buffer_with_options(
c_source.as_ptr(),
&mut output,
&c_options,
);
crate::ffi::herb_extract_ruby_to_buffer_with_options(c_source.as_ptr(), &mut output, &c_options);

let c_str = std::ffi::CStr::from_ptr(crate::ffi::hb_buffer_value(&output));
let rust_str = c_str.to_string_lossy().into_owned();
Expand All @@ -137,10 +129,7 @@ pub fn extract_ruby_with_options(
pub fn extract_html(source: &str) -> Result<String, String> {
unsafe {
let c_source = CString::new(source).map_err(|e| e.to_string())?;
let result = crate::ffi::herb_extract(
c_source.as_ptr(),
crate::bindings::HERB_EXTRACT_LANGUAGE_HTML,
);
let result = crate::ffi::herb_extract(c_source.as_ptr(), crate::bindings::HERB_EXTRACT_LANGUAGE_HTML);

if result.is_null() {
return Ok(String::new());
Expand Down
7 changes: 1 addition & 6 deletions rust/src/lex_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@ impl LexResult {
}

pub fn inspect(&self) -> String {
self
.tokens
.iter()
.map(|token| token.inspect())
.collect::<Vec<_>>()
.join("\n")
self.tokens.iter().map(|token| token.inspect()).collect::<Vec<_>>().join("\n")
}
}

Expand Down
Loading
Loading