diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d95a371 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ion-c"] + path = ion-c + url = https://github.com/amzn/ion-c.git diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e7821e6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,126 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" + +[[package]] +name = "cc" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" + +[[package]] +name = "clap" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cmake" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb" +dependencies = [ + "cc", +] + +[[package]] +name = "hermit-abi" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" +dependencies = [ + "libc", +] + +[[package]] +name = "ion-cli" +version = "0.1.0" +dependencies = [ + "clap", + "cmake", +] + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "strsim" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" + +[[package]] +name = "textwrap" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fc72ade --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ion-cli" +version = "0.1.0" +authors = ["The Ion Team "] +edition = "2018" +description = "Command line tool for working with the Ion data format." +repository = "https://github.com/amzn/ion-cli" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "~2.27.0" +libc = "0.2" + +[build-dependencies] +cmake = "0.1.44" + +[[bin]] +name = "ion" +test = false +bench = false + diff --git a/README.md b/README.md index 847260c..7824c7a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ -## My Project +## `ion-cli` -TODO: Fill this README out! +_This package is considered experimental. It is under active/early development, +and the API is subject to change._ -Be sure to: +## Developer notes -* Change the title in this README -* Edit your repository description on GitHub +Run the following command to initialize all of the necessary git submodules. +``` +git submodule update --init --recursive +``` ## Security @@ -13,5 +16,4 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more inform ## License -This project is licensed under the Apache-2.0 License. - +This project is licensed under the Apache-2.0 License. \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..3279e0f --- /dev/null +++ b/build.rs @@ -0,0 +1,54 @@ +use cmake::Config; +use std::fs::create_dir_all; +use std::path::Path; +use std::process; + +fn main() { + let build_dir = Path::new("./ion-c/build/release"); + + // Create the ion-c build directory if necessary + if !build_dir.is_dir() { + println!("Creating build directory {}", build_dir.display()); + if let Err(error) = create_dir_all(build_dir) { + eprintln!("Could not create build directory: {:?}", error); + process::exit(1); + } + } + + // Configure and run CMake + Config::new("ion-c") + .define("CMAKE_BUILD_TYPE", "Release") + .out_dir("./ion-c/build/release") + .build(); + + // Output lines that start with "cargo:" are interpreted by Cargo. See the docs for details: + // https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script + + // The `ion` executable statically links to the `ion-c` CLI. The following output tells Cargo + // which libraries to link against and in which directories they can be found. + + // ion_events library + println!("cargo:rustc-link-search=native=./ion-c/build/release/build/tools/events"); + println!("cargo:rustc-link-lib=static=ion_events_static"); + + // ion_c library + println!("cargo:rustc-link-search=native=./ion-c/build/release/build/ionc"); + println!("cargo:rustc-link-lib=static=ionc_static"); + + // decNumber library + println!("cargo:rustc-link-search=native=./ion-c/build/release/build/decNumber"); + println!("cargo:rustc-link-lib=static=decNumberStatic"); + + // C++ library + println!("cargo:rustc-link-search=native=/usr/lib"); + println!("cargo:rustc-link-lib=c++"); + + // ion-c CLI library + println!("cargo:rustc-link-search=native=./ion-c/build/release/build/tools/cli/"); + println!("cargo:rustc-link-lib=static=ion_cli_main"); + + // Only rebuild ion-c if that submodule directory is updated + println!("cargo:rereun-if-changed={}", build_dir.display()); + // ...or if this build script is changed. + println!("cargo:rereun-if-changed=build.rs"); +} diff --git a/ion-c b/ion-c new file mode 160000 index 0000000..65561ef --- /dev/null +++ b/ion-c @@ -0,0 +1 @@ +Subproject commit 65561ef3441947b8a07b3700cc972f344162cdad diff --git a/src/bin/ion/commands/dump.rs b/src/bin/ion/commands/dump.rs new file mode 100644 index 0000000..873c6de --- /dev/null +++ b/src/bin/ion/commands/dump.rs @@ -0,0 +1,96 @@ +use clap::{App, Arg, ArgMatches}; + +use libc::c_char; +use libc::c_int; +use std::ffi::CString; +use std::ptr; + +// ion_c_cli_main is a C function that lives in the ion-c CLI, to which ion-cli is +// statically linked. +extern "C" { + fn ion_c_cli_main(argc: c_int, argv: *const *const c_char); +} + +fn run_ion_c_cli(args: &[&str]) { + // Convert the length-prefixed Rust str arguments to null-terminated C strings + let argv_as_c_str = args + .iter() + .map(|arg| CString::new(*arg).unwrap()) + .collect::>(); + + // Convert the C strings to char * pointers. Note: it's important that we collect() + // the values below into a separate vector from the values above; it guarantees that + // the memory being pointed to will still be valid by the time the ion_c_cli accesses it. + let mut argv_as_char_star = argv_as_c_str + .iter() + .map(|arg| arg.as_ptr()) + .collect::>(); + + // The number of arguments as a C int + let argc = argv_as_char_star.len() as c_int; + + // Programs sometimes rely on argv being null-terminated, so we'll push a null onto the array. + argv_as_char_star.push(ptr::null()); + + let argv = argv_as_char_star.as_ptr(); + + unsafe { + ion_c_cli_main(argc, argv); + } +} + +pub fn app() -> App<'static, 'static> { + App::new("dump") + .about("Prints Ion in the requested format") + .arg( + Arg::with_name("format") + .long("format") + .short("f") + .takes_value(true) + .default_value("pretty") + .possible_values(&["binary", "text", "pretty"]) + .help("Output format"), + ) + .arg( + Arg::with_name("output") + .long("output") + .short("o") + .takes_value(true) + .help("Output file [default: STDOUT]"), + ) + .arg( + // All argv entries after the program name (argv[0]) + // and any `clap`-managed options are considered input files. + Arg::with_name("input") + .index(1) + .multiple(true) + .help("Input file [default: STDIN]"), + ) +} + +pub fn run(command_name: &str, matches: &ArgMatches<'static>) { + let mut args: Vec<&str> = vec![command_name, "process"]; + + // -f pretty|text|binary + if let Some(format) = matches.value_of("format") { + args.push("-f"); + args.push(format); + } + + // -o filename + if let Some(output_file) = matches.value_of("output") { + args.push("-o"); + args.push(output_file); + } + + // ...files + if let Some(input_file_iter) = matches.values_of("input") { + for input_file in input_file_iter { + args.push(input_file); + } + } else { + args.push("-"); // Signifies STDIN + } + + run_ion_c_cli(&args); +} diff --git a/src/bin/ion/commands/mod.rs b/src/bin/ion/commands/mod.rs new file mode 100644 index 0000000..8482b20 --- /dev/null +++ b/src/bin/ion/commands/mod.rs @@ -0,0 +1,17 @@ +use clap::{App, ArgMatches}; + +pub mod dump; + +// Creates a Vec of CLI configurations for all of the available built-in commands +pub fn built_in_commands() -> Vec> { + vec![dump::app()] +} + +// Maps the given command name to the entry point for that command if it exists +pub fn runner_for_built_in_command(command_name: &str) -> Option)> { + let runner = match command_name { + "dump" => dump::run, + _ => return None, + }; + Some(runner) +} diff --git a/src/bin/ion/main.rs b/src/bin/ion/main.rs new file mode 100644 index 0000000..da3f168 --- /dev/null +++ b/src/bin/ion/main.rs @@ -0,0 +1,33 @@ +mod commands; + +use crate::commands::{built_in_commands, runner_for_built_in_command}; +use clap::{crate_authors, crate_version, App, AppSettings}; + +const PROGRAM_NAME: &str = "ion"; + +fn main() { + let mut app = App::new(PROGRAM_NAME) + .version(crate_version!()) + .author(crate_authors!()) + .setting(AppSettings::SubcommandRequiredElseHelp) + .setting(AppSettings::TrailingVarArg); + + for command in built_in_commands() { + app = app.subcommand(command); + } + + let args = app.get_matches(); + let (command_name, command_args) = args.subcommand(); + + if let Some(runner) = runner_for_built_in_command(command_name) { + // If a runner is registered for the given command name, command_args is guaranteed to + // be defined. + runner(command_name, command_args.unwrap()); + } else { + let message = format!( + "The requested command ('{}') is not supported and clap did not generate an error message.", + command_name + ); + unreachable!(message); + } +}