diff --git a/.travis.yml b/.travis.yml index 75626cf..077b42b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,8 @@ branches: # NOTE: Need below regex to trigger deploy on push-tags - /^\d+\.\d+\.\d+.*$/ - /^feature\/.*$/ -script: ci/script.sh -before_deploy: ci/before_deploy.sh +script: ci/script.bash +before_deploy: ci/before_deploy.bash deploy: provider: releases skip_cleanup: true diff --git a/Cargo.lock b/Cargo.lock index 3c1f1fc..98295de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,14 @@ name = "adler32" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "aho-corasick" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -21,6 +29,33 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "autocfg" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "base64" version = "0.9.3" @@ -36,7 +71,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -100,6 +135,38 @@ name = "dtoa" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "env_logger" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "flate2" version = "1.0.6" @@ -171,6 +238,24 @@ name = "linked-hash-map" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "log" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "miniz-sys" version = "0.1.11" @@ -233,7 +318,7 @@ dependencies = [ "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -273,13 +358,16 @@ dependencies = [ [[package]] name = "refmt" -version = "0.1.4" +version = "0.2.0" dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -288,6 +376,18 @@ dependencies = [ "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex-syntax" version = "0.6.4" @@ -296,6 +396,11 @@ dependencies = [ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc-demangle" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ryu" version = "0.2.7" @@ -316,17 +421,17 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_derive" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -337,7 +442,7 @@ dependencies = [ "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -347,7 +452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -369,12 +474,12 @@ dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "0.15.23" +version = "0.15.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -382,6 +487,17 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "synstructure" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syntect" version = "3.0.2" @@ -396,8 +512,8 @@ dependencies = [ "onig 4.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "plist 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -413,6 +529,14 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "termcolor" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "termion" version = "1.5.1" @@ -432,12 +556,20 @@ dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "toml" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -460,11 +592,21 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "utf8-ranges" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "walkdir" version = "2.2.7" @@ -512,6 +654,15 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wincolor" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "xml-rs" version = "0.7.0" @@ -530,8 +681,12 @@ dependencies = [ [metadata] "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" +"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727" +"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" "checksum bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f2fb9e29e72fd6bc12071533d5dc7664cb01480c59406f656d7ac25c7bd8ff7" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" @@ -543,6 +698,9 @@ dependencies = [ "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum crc32fast 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e91d5240c6975ef33aeb5f148f35275c25eda8e8a5f95abe421978b05b8bf192" "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" +"checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum flate2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2291c165c8e703ee54ef3055ad6188e3d51108e2ded18e9f2476e774fc5ad3d4" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" @@ -554,6 +712,8 @@ dependencies = [ "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74" "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db4c41318937f6e76648f42826b1d9ade5c09cafb5aef7e351240a70f39206e9" "checksum miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0300eafb20369952951699b68243ab4334f4b10a88f411c221d444b36c40e649" "checksum miniz_oxide 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ad30a47319c16cde58d0314f5d98202a80c9083b5f61178457403dfb14e509c" "checksum miniz_oxide_c_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "28edaef377517fd9fe3e085c37d892ce7acd1fbeab9239c5a36eec352d8a8b7e" @@ -566,28 +726,35 @@ dependencies = [ "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" "checksum redox_syscall 0.1.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f22c50afdcf3f0a31ebb6b47697f6a7c5e5a24967e842858118bce0615f0afad" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f" "checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1" +"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" "checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" "checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" "checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267" -"checksum serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)" = "157e12af46859e968da75dea9845530e13d03bcab2009a41b9b7bb3cf4eb3ec2" -"checksum serde_derive 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)" = "9469829702497daf2daf3c190e130c3fa72f719920f73c86160d43e8f8d76951" +"checksum serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)" = "0e732ed5a5592c17d961555e3b552985baf98d50ce418b7b655f31f6ba7eb1b7" +"checksum serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d6115a3ca25c224e409185325afc16a0d5aaaabc15c42b09587d6f1ba39a5b" "checksum serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "bdf540260cfee6da923831f4776ddc495ada940c30117977c70f1313a6130545" "checksum serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0887a8e097a69559b56aa2526bf7aff7c3048cf627dff781f0b56a6001534593" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum strum 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6b3fc98c482ff9bb37a6db6a6491218c4c82bec368bd5682033e5b96b969143" "checksum strum_macros 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6969d7021d96b53b12b774d4f412026f1debe7f168a0b8c59e93b4c1e850a05f" -"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" +"checksum syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)" = "734ecc29cd36e8123850d9bf21dfd62ef8300aaa8f879aabaa899721808be37c" +"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum syntect 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e02dd9df97a68a2d005ace28ff24c610abfc3ce17afcfdb22a077645dabb599a" "checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327" +"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" @@ -595,5 +762,6 @@ dependencies = [ "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" "checksum xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2" "checksum yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "95acf0db5515d07da9965ec0e0ba6cc2d825e2caeb7303b66ca441729801254e" diff --git a/Cargo.toml b/Cargo.toml index 82c6526..3728d39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,26 @@ [package] name = "refmt" -version = "0.1.4" +version = "0.2.0" authors = ["yoshihitoh "] edition = "2018" -[lib] -name = "refmt_core" -path = "src/lib.rs" +[badges] +travis-ci = { repository = "yoshihitoh/refmt" } [[bin]] name = "refmt" -path = "src/bin/refmt.rs" +path = "src/bin/refmt/main.rs" [[bin]] name = "refmt-generate-assets" -path = "src/bin/generate_assets.rs" +path = "src/bin/generate_assets/main.rs" [dependencies] +ansi_term = "0.11" atty = "0.2" -bincode = "1.0" +env_logger = "0.6" +failure = "0.1" +log = "0.4" serde = "1.0" serde_yaml = "0.8" strum = "0.13" diff --git a/ci/before_deploy.sh b/ci/before_deploy.bash similarity index 96% rename from ci/before_deploy.sh rename to ci/before_deploy.bash index a69b1f2..ef0a0d1 100755 --- a/ci/before_deploy.sh +++ b/ci/before_deploy.bash @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -exo pipefail diff --git a/ci/script.sh b/ci/script.bash similarity index 88% rename from ci/script.sh rename to ci/script.bash index 07061ce..eac1209 100755 --- a/ci/script.sh +++ b/ci/script.bash @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -exo pipefail diff --git a/src/assets.rs b/src/assets.rs index 8d6dd90..a11abaa 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -1,75 +1,38 @@ -use std::path::Path; +use syntect::highlighting::{Theme, ThemeSet}; +use syntect::parsing::{SyntaxReference, SyntaxSet}; -use syntect::dumps::{dump_to_file, from_binary}; -use syntect::highlighting::ThemeSet; -use syntect::parsing::{SyntaxSet, SyntaxSetBuilder}; +const DEFAULT_THEME: &str = "Monokai Extended"; -struct AssetBuilder<'a> { - assets_dir: &'a Path, -} - -impl<'a> AssetBuilder<'a> { - fn build_syntaxes(&self) -> Result { - let mut syntax_set_builder = SyntaxSetBuilder::new(); - syntax_set_builder.add_plain_text_syntax(); - syntax_set_builder.add_from_folder(self.assets_dir.join("syntaxes"), true)?; - - Ok(syntax_set_builder.build()) - } - - fn build_themes(&self) -> Result { - let mut theme_set = ThemeSet::default(); - theme_set.add_from_folder(self.assets_dir.join("themes"))?; - - Ok(theme_set) - } +pub struct HighlightAssets { + pub syntax_set: SyntaxSet, + pub theme_set: ThemeSet, } -struct AssetLoader {} - -impl AssetLoader { - fn load_syntaxes(&self) -> SyntaxSet { - from_binary(include_bytes!("../assets/syntaxes.bin")) +impl HighlightAssets { + pub fn new(syntax_set: SyntaxSet, theme_set: ThemeSet) -> HighlightAssets { + HighlightAssets { + syntax_set, + theme_set, + } } - fn load_themes(&self) -> ThemeSet { - from_binary(include_bytes!("../assets/themes.bin")) + pub fn get_syntax(&self, name: &str) -> &SyntaxReference { + self.syntax_set.find_syntax_by_extension(name).unwrap() } -} -impl Default for AssetLoader { - fn default() -> Self { - AssetLoader {} + pub fn syntaxes(&self) -> &[SyntaxReference] { + self.syntax_set.syntaxes() } -} - -pub struct HighlightAssets { - pub syntax_set: SyntaxSet, - pub theme_set: ThemeSet, -} -impl HighlightAssets { - pub fn load_integrated() -> Result { - let loader = AssetLoader::default(); - Ok(HighlightAssets { - syntax_set: loader.load_syntaxes(), - theme_set: loader.load_themes(), - }) + pub fn get_default_theme(&self) -> &Theme { + self.get_theme(DEFAULT_THEME) } - pub fn build_from_files(assets_dir: &Path) -> Result { - let builder = AssetBuilder { assets_dir }; - Ok(HighlightAssets { - syntax_set: builder.build_syntaxes()?, - theme_set: builder.build_themes()?, - }) + pub fn get_theme(&self, name: &str) -> &Theme { + &self.theme_set.themes[name] } - pub fn save(&self, assets_dir: &Path) -> Result<(), bincode::Error> { - // TODO: Remove bincode from dependencies. It is only used for this error handling. - dump_to_file(&self.syntax_set, assets_dir.join("syntaxes.bin"))?; - dump_to_file(&self.theme_set, assets_dir.join("themes.bin"))?; - - Ok(()) + pub fn themes(&self) -> Vec<&Theme> { + self.theme_set.themes.iter().map(|(_, v)| v).collect() } } diff --git a/src/bin/generate_assets.rs b/src/bin/generate_assets.rs deleted file mode 100644 index cd546e6..0000000 --- a/src/bin/generate_assets.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::path::Path; - -use clap::{App, Arg}; -use refmt_core::assets::HighlightAssets; - -#[derive(Debug)] -struct ProgramOption { - assets_dir: String, -} - -#[derive(Debug)] -enum GenerateError { - SyntectError(syntect::LoadingError), - BincodeError(bincode::Error), -} - -impl From for GenerateError { - fn from(e: syntect::LoadingError) -> Self { - GenerateError::SyntectError(e) - } -} - -impl From for GenerateError { - fn from(e: bincode::Error) -> Self { - GenerateError::BincodeError(e) - } -} - -fn parse_args() -> ProgramOption { - let m = App::new("generate_assets") - .arg( - Arg::with_name("ASSETS_DIR") - .long("assets-dir") - .help("Set path to assets directory") - .takes_value(true) - .value_name("ASSETS_DIR") - .required(true), - ) - .get_matches(); - - ProgramOption { - assets_dir: m.value_of("ASSETS_DIR").unwrap().to_string(), - } -} - -fn main() -> Result<(), GenerateError> { - let options = parse_args(); - let assets = HighlightAssets::build_from_files(Path::new(&options.assets_dir))?; - assets.save(Path::new(&options.assets_dir))?; - Ok(()) -} diff --git a/src/bin/generate_assets/main.rs b/src/bin/generate_assets/main.rs new file mode 100644 index 0000000..10e4b32 --- /dev/null +++ b/src/bin/generate_assets/main.rs @@ -0,0 +1,90 @@ +use std::path::Path; + +use clap::{App, Arg}; +use failure::ResultExt; +use syntect::dumps::dump_to_file; +use syntect::highlighting::ThemeSet; +use syntect::parsing::{SyntaxSet, SyntaxSetBuilder}; + +use refmt::assets::HighlightAssets; +use refmt::errors; + +struct AssetBuilder {} + +impl AssetBuilder { + pub fn build_from_files( + syntaxes_dir: &Path, + themes_dir: &Path, + ) -> Result { + let builder = AssetBuilder::default(); + Ok(HighlightAssets { + syntax_set: builder.build_syntaxes(syntaxes_dir)?, + theme_set: builder.build_themes(themes_dir)?, + }) + } + + pub fn save(assets: HighlightAssets, assets_dir: &Path) -> Result<(), errors::Error> { + dump_to_file(&assets.syntax_set, assets_dir.join("syntaxes.bin")) + .context(errors::ErrorKind::CreatingAssets)?; + dump_to_file(&assets.theme_set, assets_dir.join("themes.bin")) + .context(errors::ErrorKind::CreatingAssets)?; + + Ok(()) + } + + fn build_syntaxes(&self, syntaxes_dir: &Path) -> Result { + let mut syntax_set_builder = SyntaxSetBuilder::new(); + syntax_set_builder.add_plain_text_syntax(); + syntax_set_builder + .add_from_folder(syntaxes_dir, true) + .context(errors::ErrorKind::CreatingAssets)?; + + Ok(syntax_set_builder.build()) + } + + fn build_themes(&self, themes_dir: &Path) -> Result { + let mut theme_set = ThemeSet::default(); + theme_set + .add_from_folder(themes_dir) + .context(errors::ErrorKind::CreatingAssets)?; + + Ok(theme_set) + } +} + +impl Default for AssetBuilder { + fn default() -> Self { + AssetBuilder {} + } +} + +#[derive(Debug)] +struct ProgramOption { + assets_dir: String, +} + +fn parse_args() -> ProgramOption { + let m = App::new("generate_assets") + .arg( + Arg::with_name("ASSETS_DIR") + .long("assets-dir") + .help("Set path to assets directory") + .takes_value(true) + .value_name("ASSETS_DIR") + .required(true), + ) + .get_matches(); + + ProgramOption { + assets_dir: m.value_of("ASSETS_DIR").unwrap().to_string(), + } +} + +fn main() -> Result<(), errors::Error> { + let options = parse_args(); + let assets_dir = Path::new(&options.assets_dir); + let assets = + AssetBuilder::build_from_files(&assets_dir.join("syntaxes"), &assets_dir.join("themes"))?; + AssetBuilder::save(assets, Path::new(&options.assets_dir))?; + Ok(()) +} diff --git a/src/bin/refmt.rs b/src/bin/refmt.rs deleted file mode 100644 index 6ca0e9e..0000000 --- a/src/bin/refmt.rs +++ /dev/null @@ -1,224 +0,0 @@ -use std::fs::File; -use std::io::{self, stdin, stdout, BufReader, BufWriter, Read, Stdin, Stdout, Write}; -use std::path::Path; -use std::str::FromStr; - -use clap::{crate_authors, crate_name, crate_version, App, AppSettings, Arg}; -use syntect::easy::HighlightLines; -use syntect::util::as_24_bit_terminal_escaped; - -use refmt_core::assets::HighlightAssets; -use refmt_core::translator::json::JsonTranslator; -use refmt_core::translator::toml::TomlTranslator; -use refmt_core::translator::translator::{Format, TranslateError, Translator}; -use refmt_core::translator::yaml::YamlTranslator; - -#[derive(Debug)] -enum ConvertError { - IoError(io::Error), - TranslateError(TranslateError), - ArgumentError(String), - HighlightLoadingError(syntect::LoadingError), - BincodeError(bincode::Error), -} - -impl From for ConvertError { - fn from(e: io::Error) -> Self { - ConvertError::IoError(e) - } -} - -impl From for ConvertError { - fn from(e: TranslateError) -> Self { - ConvertError::TranslateError(e) - } -} - -impl From for ConvertError { - fn from(e: syntect::LoadingError) -> Self { - ConvertError::HighlightLoadingError(e) - } -} - -impl From for ConvertError { - fn from(e: bincode::Error) -> Self { - ConvertError::BincodeError(e) - } -} - -type ConvertResult = Result; - -static JSON_TRANSLATOR: JsonTranslator = JsonTranslator {}; -static TOML_TRANSLATOR: TomlTranslator = TomlTranslator {}; -static YAML_TRANSLATOR: YamlTranslator = YamlTranslator {}; - -fn translator_for(format: Format) -> &'static Translator { - match format { - Format::Json => &JSON_TRANSLATOR, - Format::Toml => &TOML_TRANSLATOR, - Format::Yaml => &YAML_TRANSLATOR, - } -} - -#[derive(Debug)] -struct ProgramOptions { - input: Option, - input_format: Format, - output: Option, - output_format: Format, - is_tty: bool, -} - -fn infer_format(file: Option<&str>, format_name: Option<&str>) -> ConvertResult { - if file.is_none() && format_name.is_none() { - return Err(ConvertError::ArgumentError( - "cannot determine file format, need to specify either FILE or FORMAT".to_string(), - )); - } - - let format_name = format_name - .or(file.and_then(|f| Path::new(f).extension().map(|ext| ext.to_str().unwrap()))); - Ok(Format::from_str(format_name.unwrap_or(""))?) -} - -fn parse_args() -> ConvertResult { - let is_tty = atty::is(atty::Stream::Stdout); - let color_setting = if is_tty { - AppSettings::ColoredHelp - } else { - AppSettings::ColorNever - }; - - let mut app = App::new(crate_name!()) - .about("Translate data format into another one.") - .author(crate_authors!()) - .version(crate_version!()) - .global_setting(color_setting) - .arg( - Arg::with_name("INPUT") - .help("set the input file to use") - .short("i") - .long("input") - .takes_value(true) - .value_name("FILE"), - ) - .arg( - Arg::with_name("INPUT_FORMAT") - .help("set the name of input format") - .long("input-format") - .takes_value(true) - .value_name("FORMAT_NAME") - .case_insensitive(true) - .possible_values(&Format::names()), - ) - .arg( - Arg::with_name("OUTPUT") - .help("set the output file to use") - .short("o") - .long("output") - .takes_value(true) - .value_name("FILE"), - ) - .arg( - Arg::with_name("OUTPUT_FORMAT") - .help("set the name of output format") - .long("output-format") - .takes_value(true) - .value_name("FORMAT_NAME") - .case_insensitive(true) - .possible_values(&Format::names()), - ); - let m = app.clone().get_matches(); - - let input_format = - infer_format(m.value_of("INPUT"), m.value_of("INPUT_FORMAT")).map_err(|e| { - app.print_long_help().unwrap_or(()); - - ConvertError::ArgumentError(format!( - "cannot determine format of the input file: {:?}", - e - )) - })?; - let output_format = infer_format(m.value_of("OUTPUT"), m.value_of("OUTPUT_FORMAT")); - let output_format = if let Err(ConvertError::ArgumentError(_)) = output_format { - input_format - } else { - output_format? - }; - - let option = ProgramOptions { - input: m.value_of("INPUT").map(|s| s.to_string()), - input_format, - output: m.value_of("OUTPUT").map(|s| s.to_string()), - output_format, - is_tty, - }; - - Ok(option) -} - -fn main() -> ConvertResult<()> { - let option = parse_args()?; - run(option) -} - -fn reader_for(file: Option<&str>, sin: Stdin) -> ConvertResult> { - let r = match file { - Some(f) => Box::new(File::open(f)?) as Box, - None => Box::new(sin) as Box, - }; - Ok(r) -} - -fn writer_for(file: Option<&str>, sout: Stdout) -> ConvertResult> { - let w = match file { - Some(f) => Box::new(File::create(f)?) as Box, - None => Box::new(sout) as Box, - }; - Ok(w) -} - -fn read_all_text(r: Box) -> ConvertResult { - let mut reader = BufReader::new(r); - let mut s = String::new(); - reader.read_to_string(&mut s)?; - Ok(s) -} - -fn write_all_text( - w: Box, - s: &str, - fmt: Format, - assets: &HighlightAssets, - is_tty: bool, -) -> ConvertResult<()> { - let mut writer = BufWriter::new(w); - - let ps = &assets.syntax_set; - let syntax = ps.find_syntax_by_extension(fmt.preferred_extension()); - - if is_tty && syntax.is_some() { - let syntax = syntax.unwrap(); - let mut h = HighlightLines::new(syntax, &assets.theme_set.themes["Monokai Extended"]); - - let ranges = h.highlight(s, &ps); - let escaped = as_24_bit_terminal_escaped(&ranges, true); - writeln!(writer, "{}", escaped)?; - } else { - writeln!(writer, "{}", s)?; - } - - Ok(()) -} - -fn run(option: ProgramOptions) -> ConvertResult<()> { - let assets = HighlightAssets::load_integrated()?; - - let r = reader_for(option.input.as_ref().map(|s| s.as_str()), stdin())?; - let w = writer_for(option.output.as_ref().map(|s| s.as_str()), stdout())?; - - let from_text = read_all_text(r)?; - let translator = translator_for(option.output_format); - let to_text = translator.translate(&from_text, option.input_format)?; - write_all_text(w, &to_text, option.output_format, &assets, option.is_tty) -} diff --git a/src/bin/refmt/app.rs b/src/bin/refmt/app.rs new file mode 100644 index 0000000..08a7818 --- /dev/null +++ b/src/bin/refmt/app.rs @@ -0,0 +1,212 @@ +use std::fs::File; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; +use std::path::Path; +use std::str::FromStr; + +use clap::{crate_authors, crate_name, crate_version, App as ClapApp, AppSettings, Arg}; +use failure::ResultExt; +use log::debug; +use syntect::dumps::from_binary; + +use refmt::assets::HighlightAssets; +use refmt::errors; +use refmt::format::{Format, FormattedText}; + +use crate::printer::{HighlightTextPrinter, PlainTextPrinter, Printer}; + +#[derive(Debug)] +struct Config { + input_file: Option, + input_format: Format, + output_file: Option, + output_format: Format, + color_enabled: bool, + theme_name: String, +} + +fn infer_format(file: Option<&str>, format_name: Option<&str>) -> Result { + let format_name = format_name + .or_else(|| { + file.and_then(|f| Path::new(f).extension()) + .and_then(|s| s.to_str()) + }) + .ok_or(errors::Error::from(errors::ErrorKind::InferFormat))?; + + Format::from_str(format_name) +} + +impl Config { + fn new(app: ClapApp, color_enabled: bool) -> Result { + let matches = app.get_matches(); + let input_file = matches.value_of("INPUT_FILE"); + debug!("input_file: {:?}", input_file); + + let input_format = matches.value_of("INPUT_FORMAT"); + debug!("input_format: {:?}", input_format); + let input_format = infer_format(input_file, input_format)?; + + let output_file = matches.value_of("OUTPUT_FILE"); + debug!("output_file: {:?}", output_file); + + let output_format = matches.value_of("OUTPUT_FORMAT"); + debug!("output_format: {:?}", output_format); + let output_format = + infer_format(output_file, output_format).or_else(|e| match e.kind() { + errors::ErrorKind::InferFormat => Ok(input_format), + _ => Err(e), + })?; + + let theme_name = "Monokai Extended"; + + Ok(Config { + input_file: input_file.map(|s| s.to_string()), + input_format, + output_file: output_file.map(|s| s.to_string()), + output_format, + color_enabled: color_enabled, + theme_name: theme_name.to_string(), + }) + } +} + +fn build_clap_app(color_enabled: bool) -> clap::App<'static, 'static> { + let color_setting = if color_enabled { + AppSettings::ColoredHelp + } else { + AppSettings::ColorNever + }; + + ClapApp::new(crate_name!()) + .about("reformat between JSON, YAML and TOML.") + .author(crate_authors!()) + .version(crate_version!()) + .global_setting(color_setting) + .arg( + Arg::with_name("INPUT_FILE") + .help("set the input file to use. Assume STDIN if omitted") + .short("i") + .long("input") + .takes_value(true) + .value_name("FILE"), + ) + .arg( + Arg::with_name("INPUT_FORMAT") + .help("set the name of input format. Assume format by file extension if omitted.") + .long("input-format") + .takes_value(true) + .value_name("FORMAT_NAME") + .case_insensitive(true) + .possible_values(&Format::names()), + ) + .arg( + Arg::with_name("OUTPUT_FILE") + .help("set the output file to use. Assume STDOUT if omitted") + .short("o") + .long("output") + .takes_value(true) + .value_name("FILE"), + ) + .arg( + Arg::with_name("OUTPUT_FORMAT") + .help("set the name of output format. Assume format by file extension if omitted") + .long("output-format") + .takes_value(true) + .value_name("FORMAT_NAME") + .case_insensitive(true) + .possible_values(&Format::names()), + ) +} + +pub struct App { + config: Config, + assets: HighlightAssets, +} + +impl App { + pub fn new() -> Result { + let color_enabled = atty::is(atty::Stream::Stdout); + let config = Config::new(build_clap_app(color_enabled), color_enabled)?; + let assets = App::load_integrated_assets(); + + debug!("config: {:?}", config); + debug!( + "syntaxes: {:?}", + assets + .syntaxes() + .iter() + .map(|s| s.name.as_str()) + .collect::>() + ); + debug!( + "themes: {:?}", + assets + .themes() + .iter() + .map(|&t| t + .name + .as_ref() + .map(|s| s.as_str()) + .unwrap_or("** unnamed theme **")) + .collect::>() + ); + Ok(App { config, assets }) + } + + pub fn run(&self) -> Result<(), errors::Error> { + let input_text = self.read_from_input()?; + let output_text = input_text.convert_to(self.config.output_format)?; + + self.write_to_output(&output_text) + } + + fn load_integrated_assets() -> HighlightAssets { + HighlightAssets::new( + from_binary(include_bytes!("../../../assets/syntaxes.bin")), + from_binary(include_bytes!("../../../assets/themes.bin")), + ) + } + + fn read_from_input(&self) -> Result { + // open reader + let stdin = stdin(); + let lock = stdin.lock(); + let mut reader = if let Some(f) = self.config.input_file.as_ref() { + Box::new(BufReader::new( + File::open(f).context(errors::ErrorKind::Io)?, + )) as Box + } else { + Box::new(lock) as Box + }; + + // read + let mut text = String::new(); + reader + .read_to_string(&mut text) + .context(errors::ErrorKind::Io)?; + + Ok(FormattedText::new(self.config.input_format, text)) + } + + fn write_to_output(&self, text: &FormattedText) -> Result<(), errors::Error> { + // open writer + let stdout = stdout(); + let lock = stdout.lock(); + let mut w = if let Some(f) = self.config.output_file.as_ref() { + Box::new(BufWriter::new( + File::create(f).context(errors::ErrorKind::Io)?, + )) as Box + } else { + Box::new(lock) as Box + }; + + // select printer + let printer = if self.config.output_file.is_none() && self.config.color_enabled { + Box::new(HighlightTextPrinter::new(&self.assets)) as Box + } else { + Box::new(PlainTextPrinter::default()) as Box + }; + + // print + printer.print(&mut w, text) + } +} diff --git a/src/bin/refmt/main.rs b/src/bin/refmt/main.rs new file mode 100644 index 0000000..a4bdeeb --- /dev/null +++ b/src/bin/refmt/main.rs @@ -0,0 +1,45 @@ +use std::process; + +use failure::Fail; + +use refmt::errors; + +mod app; +mod printer; + +fn handle_error(error: &errors::Error) { + use ansi_term::Color::Red; + let label = Red.paint("[refmt error]"); + if let Some(cause) = error.cause() { + eprintln!("{}: {}. cause: {}", label, error, cause); + } else { + eprintln!("{}: {}.", label, error); + } +} + +fn initialize() { + env_logger::init(); +} + +fn terminate(code: i32) { + process::exit(code) +} + +fn run() -> Result<(), errors::Error> { + let app = app::App::new()?; + app.run() +} + +fn main() { + initialize(); + + let code = match run() { + Ok(()) => 0, + Err(e) => { + handle_error(&e); + 1 + } + }; + + terminate(code); +} diff --git a/src/bin/refmt/printer.rs b/src/bin/refmt/printer.rs new file mode 100644 index 0000000..549ffd5 --- /dev/null +++ b/src/bin/refmt/printer.rs @@ -0,0 +1,48 @@ +use std::io::Write; + +use failure::ResultExt; +use syntect::easy::HighlightLines; +use syntect::util::as_24_bit_terminal_escaped; + +use refmt::assets::HighlightAssets; +use refmt::errors; +use refmt::format::FormattedText; + +pub trait Printer { + fn print(&self, dest: &mut Write, text: &FormattedText) -> Result<(), errors::Error>; +} + +pub struct PlainTextPrinter {} + +impl Default for PlainTextPrinter { + fn default() -> Self { + PlainTextPrinter {} + } +} + +impl Printer for PlainTextPrinter { + fn print(&self, dest: &mut Write, text: &FormattedText) -> Result<(), errors::Error> { + Ok(writeln!(dest, "{}", text.text.as_str()).context(errors::ErrorKind::Io)?) + } +} + +pub struct HighlightTextPrinter<'a> { + assets: &'a HighlightAssets, +} + +impl<'a> HighlightTextPrinter<'a> { + pub fn new(assets: &'a HighlightAssets) -> Self { + HighlightTextPrinter { assets } + } +} + +impl<'a> Printer for HighlightTextPrinter<'a> { + fn print(&self, dest: &mut Write, text: &FormattedText) -> Result<(), errors::Error> { + let syntax = self.assets.get_syntax(text.format.preferred_extension()); + let theme = self.assets.get_default_theme(); + let mut highlight = HighlightLines::new(syntax, theme); + let ranges = highlight.highlight(&text.text, &self.assets.syntax_set); + let escaped = as_24_bit_terminal_escaped(&ranges, true); + Ok(writeln!(dest, "{}", escaped).context(errors::ErrorKind::Io)?) + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..4664eb8 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,65 @@ +use std::fmt::{self, Display, Formatter}; + +use failure::{Backtrace, Context, Fail}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Fail)] +pub enum ErrorKind { + #[fail(display = "IO Error")] + Io, + + #[fail(display = "Any errors occurred during serialization")] + Serialization, + + #[fail(display = "Any errors occurred during deserialization")] + Deserialization, + + #[fail(display = "Unsupported format name")] + FormatName, + + #[fail(display = "Cannot infer format. Please specify either FILE or FORMAT")] + InferFormat, + + #[fail(display = "Cannot create assets")] + CreatingAssets, +} + +#[derive(Debug)] +pub struct Error { + inner: Context, +} + +impl Error { + pub fn kind(&self) -> ErrorKind { + *self.inner.get_context() + } +} + +impl Fail for Error { + fn cause(&self) -> Option<&Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&Backtrace> { + self.inner.backtrace() + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Error { + inner: Context::new(kind), + } + } +} + +impl From> for Error { + fn from(context: Context) -> Self { + Error { inner: context } + } +} diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..9bece18 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,221 @@ +use std::str::FromStr; + +use failure::{Fail, ResultExt}; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + +use crate::errors; + +mod json; +mod toml; +mod yaml; + +// NOTE: Use serde_json::Value as intermediate type, it keeps field orders, and have enough type to convert to another format. +type Value = serde_json::Value; + +#[derive(Fail, Debug)] +#[fail(display = "Unsupported format: {}", name)] +struct UnsupportedFormatError { + name: String, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumIter)] +pub enum Format { + Json, + Toml, + Yaml, +} + +impl Format { + pub fn names() -> Vec<&'static str> { + Format::iter().map(|f| f.name()).collect() + } + + pub fn name(&self) -> &'static str { + match *self { + Format::Json => "json", + Format::Toml => "toml", + Format::Yaml => "yaml", + } + } + + pub fn extensions(&self) -> &[&'static str] { + match *self { + Format::Json => &["json"], + Format::Toml => &["toml"], + Format::Yaml => &["yaml", "yml"], + } + } + + pub fn is_extension(&self, s: &str) -> bool { + self.extensions().iter().find(|&&ext| ext == s).is_some() + } + + pub fn preferred_extension(&self) -> &'static str { + self.name() + } +} + +impl FromStr for Format { + type Err = errors::Error; + + fn from_str(s: &str) -> Result::Err> { + let lower = s.to_ascii_lowercase(); + Ok(Format::iter() + .find(|f| f.is_extension(&lower)) + .ok_or(UnsupportedFormatError { + name: s.to_string(), + }) + .context(errors::ErrorKind::FormatName)?) + } +} + +pub struct FormattedText { + pub format: Format, + pub text: String, +} + +impl FormattedText { + pub fn new(format: Format, text: String) -> FormattedText { + FormattedText { format, text } + } + + pub fn convert_to(&self, format: Format) -> Result { + let value = self.deserialize(&self.text)?; + Self::serialize(format, &value).map(|s| FormattedText::new(format, s)) + } + + fn serialize(format: Format, v: &Value) -> Result { + match format { + Format::Json => json::serialize(v), + Format::Toml => toml::serialize(v), + Format::Yaml => yaml::serialize(v), + } + } + + fn deserialize(&self, s: &str) -> Result { + match self.format { + Format::Json => json::deserialize(s), + Format::Toml => toml::deserialize(s), + Format::Yaml => yaml::deserialize(s), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + static JSON_TEXT: &'static str = r#"{ + "id": 123, + "title": "Lorem ipsum dolor sit amet", + "author": { + "id": 999, + "first_name": "John", + "last_name": "Doe" + } +}"#; + + static TOML_TEXT: &'static str = r#"id = 123 +title = "Lorem ipsum dolor sit amet" + +[author] +id = 999 +first_name = "John" +last_name = "Doe" +"#; + + static YAML_TEXT: &'static str = r#"--- +id: 123 +title: Lorem ipsum dolor sit amet +author: + id: 999 + first_name: John + last_name: Doe"#; + + #[test] + fn format_from_str() { + assert_eq!(Format::Json, Format::from_str("json").unwrap()); + assert_eq!(Format::Json, Format::from_str("JsOn").unwrap()); + + assert_eq!(Format::Toml, Format::from_str("toml").unwrap()); + assert_eq!(Format::Yaml, Format::from_str("yaml").unwrap()); + assert_eq!(Format::Yaml, Format::from_str("yml").unwrap()); + + let r = Format::from_str("conf"); // HOCON + assert!(r.is_err()); + assert_eq!(errors::ErrorKind::FormatName, r.err().unwrap().kind()); + + let r = Format::from_str("ini"); + assert!(r.is_err()); + assert_eq!(errors::ErrorKind::FormatName, r.err().unwrap().kind()); + + let r = Format::from_str("xml"); + assert!(r.is_err()); + assert_eq!(errors::ErrorKind::FormatName, r.err().unwrap().kind()); + } + + #[test] + fn convert_json() { + let text = FormattedText::new(Format::Json, JSON_TEXT.to_string()); + + // JSON => TOML + let r = text.convert_to(Format::Toml); + assert!(r.is_ok()); + assert_eq!(TOML_TEXT, r.as_ref().ok().unwrap().text); + + // JSON => YAML + let r = text.convert_to(Format::Yaml); + assert!(r.is_ok()); + assert_eq!(YAML_TEXT, r.as_ref().ok().unwrap().text); + + // Error + let text = FormattedText::new(Format::Json, YAML_TEXT.to_string()); + let r = text.convert_to(Format::Toml); + assert!(r.is_err()); + assert_eq!(errors::ErrorKind::Deserialization, r.err().unwrap().kind()); + } + + #[test] + fn convert_toml() { + let text = FormattedText::new(Format::Toml, TOML_TEXT.to_string()); + + // TOML => JSON + let r = text.convert_to(Format::Json); + assert!(r.is_ok()); + assert_eq!(JSON_TEXT, r.as_ref().ok().unwrap().text); + + // TOML => YAML + let r = text.convert_to(Format::Yaml); + assert!(r.is_ok()); + assert_eq!(YAML_TEXT, r.as_ref().ok().unwrap().text); + + // Error + let text = FormattedText::new(Format::Toml, JSON_TEXT.to_string()); + let r = text.convert_to(Format::Yaml); + assert!(r.is_err()); + assert_eq!(errors::ErrorKind::Deserialization, r.err().unwrap().kind()); + } + + #[test] + fn convert_yaml() { + let text = FormattedText::new(Format::Yaml, YAML_TEXT.to_string()); + + // YAML => JSON + let r = text.convert_to(Format::Json); + assert!(r.is_ok()); + assert_eq!(JSON_TEXT, r.as_ref().ok().unwrap().text); + + // YAML => TOML + let r = text.convert_to(Format::Toml); + assert!(r.is_ok()); + assert_eq!(TOML_TEXT, r.as_ref().ok().unwrap().text); + + // Error + // TODO: this test will be panicked on `r.is_err()`. need to survey why the panic occurs.. + // let text = FormattedText::new(Format::Yaml, TOML_TEXT.to_string()); + // let r = text.convert_to(Format::Json); + // assert!(r.is_err()); + // assert_eq!(errors::ErrorKind::Deserialization, r.err().unwrap().kind()); + } +} diff --git a/src/format/json.rs b/src/format/json.rs new file mode 100644 index 0000000..f79d6ec --- /dev/null +++ b/src/format/json.rs @@ -0,0 +1,88 @@ +use failure::ResultExt; +use serde::de; +use serde::ser; + +use crate::errors::{self, ErrorKind}; + +pub fn serialize(v: V) -> Result { + Ok(serde_json::to_string_pretty(&v).context(ErrorKind::Serialization)?) +} + +pub fn deserialize(s: &str) -> Result +where + V: for<'de> de::Deserialize<'de>, +{ + Ok(serde_json::from_str(s).context(ErrorKind::Deserialization)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_derive::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Eq, PartialEq)] + struct Author { + pub id: u64, + pub first_name: String, + pub last_name: String, + } + + #[derive(Serialize, Deserialize, Eq, PartialEq)] + struct Book { + pub id: u64, + pub author: Author, + pub title: String, + } + + #[test] + fn success() { + // normal case + let json_text = r#" + { + "id": 123, + "title": "Lorem ipsum dolor sit amet", + "author": { + "id": 999, + "first_name": "John", + "last_name": "Doe" + } + }"#; + + // deserialize + let book = deserialize::(json_text); + assert!(book.is_ok()); + let book = book.unwrap(); + assert_eq!(book.id, 123); + assert_eq!(book.title, "Lorem ipsum dolor sit amet"); + assert_eq!(book.author.id, 999); + assert_eq!(book.author.first_name, "John"); + assert_eq!(book.author.last_name, "Doe"); + + // serialize + let text = serialize(&book); + assert!(text.is_ok()); + let text = text.unwrap(); + assert_eq!( + text, + r#"{ + "id": 123, + "author": { + "id": 999, + "first_name": "John", + "last_name": "Doe" + }, + "title": "Lorem ipsum dolor sit amet" +}"# + ); + + // deserialize from serialized text + let book = deserialize::(&text); + assert!(book.is_ok()); + let book = book.unwrap(); + assert_eq!(book.id, 123); + assert_eq!(book.title, "Lorem ipsum dolor sit amet"); + assert_eq!(book.author.id, 999); + assert_eq!(book.author.first_name, "John"); + assert_eq!(book.author.last_name, "Doe"); + } +} diff --git a/src/translator/toml.rs b/src/format/toml.rs similarity index 59% rename from src/translator/toml.rs rename to src/format/toml.rs index 1352e17..db0605e 100644 --- a/src/translator/toml.rs +++ b/src/format/toml.rs @@ -1,24 +1,18 @@ -use serde::Deserialize; +use failure::ResultExt; +use serde::de; +use serde::ser; -use super::translator::{parse_as, to_translate_error, Format, TranslateError, Translator}; +use crate::errors::{self, ErrorKind}; -pub struct TomlTranslator {} - -impl Default for TomlTranslator { - fn default() -> Self { - TomlTranslator {} - } -} - -impl Translator for TomlTranslator { - fn translate(&self, s: &str, fmt: Format) -> Result { - let value = parse_as::(s, fmt)?; - toml::to_string_pretty(&value).map_err(to_translate_error) - } +pub fn serialize(v: V) -> Result { + Ok(toml::to_string(&v).context(ErrorKind::Serialization)?) } -pub fn parse_toml<'de, V: Deserialize<'de>>(s: &'de str) -> Result { - toml::from_str(s) +pub fn deserialize(s: &str) -> Result +where + V: for<'de> de::Deserialize<'de>, +{ + Ok(toml::from_str(s).context(ErrorKind::Deserialization)?) } #[cfg(test)] @@ -54,50 +48,7 @@ mod tests { } #[test] - fn test_translate() { - let translator = TomlTranslator {}; - let json_text = r#" - { - "id": 1, - "name": { - "first": "John", - "last": "Doe" - }, - "repos": [ - { "name": "zstd-codec", "language": "C++"}, - { "name": "refmt", "language": "Rust"} - ] - } - "#; - let toml = translator.translate(json_text, Format::Json); - assert!(toml.is_ok()); - let toml = toml.ok().unwrap(); - - // REF: https://github.com/alexcrichton/toml-rs/issues/232 - // toml-rs 0.4 doesn't support `preserve_order` feature, and using `BTreeMap` internally. - // So we need to compare items in alphabetical order. - // The `preserve_order` feature is planned to release on toml-rs 0.5. - assert_eq!( - &toml, - r#"id = 1 - -[[repos]] -language = 'C++' -name = 'zstd-codec' - -[[repos]] -language = 'Rust' -name = 'refmt' - -[name] -first = 'John' -last = 'Doe' -"# - ); - } - - #[test] - fn test_parse_toml() { + fn success() { let toml_text = r#" [package] name = "refmt" @@ -114,7 +65,8 @@ strum_macros = "0.13" toml = "0.4" "#; - let manifest = parse_toml::(toml_text); + // deserialize + let manifest = deserialize::(toml_text); assert!(manifest.is_ok()); let manifest = manifest.ok().unwrap(); assert_eq!(manifest.package.name, "refmt"); @@ -125,7 +77,7 @@ toml = "0.4" assert_eq!(manifest.package.edition, Some("2018".to_string())); assert!(manifest.dependencies.is_some()); - let dependencies = manifest.dependencies.unwrap(); + let dependencies = manifest.dependencies.as_ref().unwrap(); assert_eq!(dependencies.len(), 7); assert_eq!( dependencies.get("clap"), @@ -158,5 +110,20 @@ toml = "0.4" dependencies.get("toml"), Some(&Dependency::Simple("0.4".to_string())) ); + + // serialize + // TODO: cannot serialize manifest object due to `ValueAfterTable` error. I need to survey why the error occurs. + let toml_text = serialize(&manifest.package); + assert!(toml_text.is_ok()); + let toml_text = toml_text.ok().unwrap(); + + // NOTE: toml-rs uses BTreeMap internally, so the result will be alphabetical ordered. + assert_eq!( + r#"name = "refmt" +authors = ["yoshihitoh "] +edition = "2018" +"#, + toml_text + ); } } diff --git a/src/format/yaml.rs b/src/format/yaml.rs new file mode 100644 index 0000000..182d970 --- /dev/null +++ b/src/format/yaml.rs @@ -0,0 +1,83 @@ +use failure::ResultExt; +use serde::de; +use serde::ser; + +use crate::errors::{self, ErrorKind}; + +pub fn serialize(v: V) -> Result { + Ok(serde_yaml::to_string(&v).context(ErrorKind::Serialization)?) +} + +pub fn deserialize(s: &str) -> Result +where + V: for<'de> de::Deserialize<'de>, +{ + Ok(serde_yaml::from_str(s).context(ErrorKind::Deserialization)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_derive::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Eq, PartialEq)] + struct Author { + pub id: u64, + pub first_name: String, + pub last_name: String, + } + + #[derive(Serialize, Deserialize, Eq, PartialEq)] + struct Book { + pub id: u64, + pub author: Author, + pub title: String, + } + + #[test] + fn success() { + // normal case + let yaml_text = r#"--- +id: 123 +title: Lorem ipsum dolor sit amet +author: + id: 999 + first_name: John + last_name: Doe"#; + + // deserialize + let book = deserialize::(yaml_text); + assert!(book.is_ok()); + let book = book.unwrap(); + assert_eq!(book.id, 123); + assert_eq!(book.title, "Lorem ipsum dolor sit amet"); + assert_eq!(book.author.id, 999); + assert_eq!(book.author.first_name, "John"); + assert_eq!(book.author.last_name, "Doe"); + + // serialize + let text = serialize(&book); + assert!(text.is_ok()); + let text = text.unwrap(); + assert_eq!( + text, + r#"--- +id: 123 +author: + id: 999 + first_name: John + last_name: Doe +title: Lorem ipsum dolor sit amet"# + ); + + // deserialize from serialized text + let book = deserialize::(&text); + assert!(book.is_ok()); + let book = book.unwrap(); + assert_eq!(book.id, 123); + assert_eq!(book.title, "Lorem ipsum dolor sit amet"); + assert_eq!(book.author.id, 999); + assert_eq!(book.author.first_name, "John"); + assert_eq!(book.author.last_name, "Doe"); + } +} diff --git a/src/lib.rs b/src/lib.rs index ced5d26..c129855 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod assets; -pub mod translator; +pub mod errors; +pub mod format; diff --git a/src/translator.rs b/src/translator.rs deleted file mode 100644 index 5c491a4..0000000 --- a/src/translator.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod json; -pub mod toml; -pub mod translator; -pub mod yaml; diff --git a/src/translator/json.rs b/src/translator/json.rs deleted file mode 100644 index f98f469..0000000 --- a/src/translator/json.rs +++ /dev/null @@ -1,126 +0,0 @@ -use serde::Deserialize; - -use super::translator::{parse_as, to_translate_error, Format, TranslateError, Translator}; - -pub struct JsonTranslator {} - -impl Default for JsonTranslator { - fn default() -> Self { - JsonTranslator {} - } -} - -impl Translator for JsonTranslator { - fn translate(&self, s: &str, fmt: Format) -> Result { - let value = parse_as::(s, fmt)?; - serde_json::to_string_pretty(&value).map_err(to_translate_error) - } -} - -pub fn parse_json<'de, V: Deserialize<'de>>(s: &'de str) -> Result { - serde_json::from_str(s) -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_derive::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - struct Author { - pub id: u64, - pub first_name: String, - pub last_name: String, - } - - #[derive(Serialize, Deserialize)] - struct Book { - pub id: u64, - pub author: Author, - pub title: String, - } - - #[test] - fn test_translator() { - let translator = JsonTranslator::default(); - - // normal case - // NOTE: indent has meaning on yaml format, so do not indent the below text. - let yaml_text = r#"--- -id: 123 -title: Lorem ipsum dolor sit amet -author: - id: 999 - first_name: John - last_name: Doe -"#; - let r = translator.translate(yaml_text, Format::Yaml); - assert!(r.is_ok()); - assert_eq!( - r.ok().unwrap(), - r#"{ - "id": 123, - "title": "Lorem ipsum dolor sit amet", - "author": { - "id": 999, - "first_name": "John", - "last_name": "Doe" - } -}"# - ); - - // error case: syntax error (wrong indents) - let yaml_text = r#" - --- - id: 123 - title: Lorem ipsum dolor sit amet - author: - id: 999 - first_name: John - last_name: Doe - "#; - let r = translator.translate(yaml_text, Format::Yaml); - assert!(r.is_err()); - } - - #[test] - fn test_parse_json() { - // normal case - let json_text = r#" - { - "id": 123, - "title": "Lorem ipsum dolor sit amet", - "author": { - "id": 999, - "first_name": "John", - "last_name": "Doe" - } - } - "#; - - let book = parse_json::(json_text); - assert!(book.is_ok()); - let book = book.unwrap(); - assert_eq!(book.id, 123); - assert_eq!(book.title, "Lorem ipsum dolor sit amet"); - assert_eq!(book.author.id, 999); - assert_eq!(book.author.first_name, "John"); - assert_eq!(book.author.last_name, "Doe"); - - // error case: no quote on each field names - let json_text = r#" - { - id: 123, - title: "Lorem ipsum dolor sit amet", - author: { - id: 999, - first_name: "John", - last_name: "Doe" - } - } - "#; - let book = parse_json::(json_text); - assert!(book.is_err()); - assert!(book.err().unwrap().is_syntax()); - } -} diff --git a/src/translator/translator.rs b/src/translator/translator.rs deleted file mode 100644 index 4c8ca49..0000000 --- a/src/translator/translator.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::error::Error; -use std::str::FromStr; - -use serde::de::Deserialize; -use strum::IntoEnumIterator; -use strum_macros::EnumIter; - -use super::json::parse_json; -use super::toml::parse_toml; -use super::yaml::parse_yaml; - -#[derive(Copy, Clone, Debug, PartialEq, EnumIter)] -pub enum Format { - Json, - Toml, - Yaml, -} - -impl Format { - pub fn name(&self) -> &'static str { - match *self { - Format::Json => "json", - Format::Toml => "toml", - Format::Yaml => "yaml", - } - } - - pub fn names() -> Vec<&'static str> { - Format::iter().map(|x| x.name()).collect() - } - - pub fn preferred_extension(&self) -> &'static str { - self.name() - } -} - -impl FromStr for Format { - type Err = TranslateError; - - fn from_str(s: &str) -> Result::Err> { - match s.to_ascii_lowercase().as_ref() { - "json" => Some(Format::Json), - "toml" => Some(Format::Toml), - "yaml" => Some(Format::Yaml), - "yml" => Some(Format::Yaml), - _ => None, - } - .ok_or(TranslateError::Message(format!( - "unsupported format: {}", - s - ))) - } -} - -#[derive(Debug)] -pub enum TranslateError { - Message(String), -} - -pub trait Translator { - fn translate(&self, s: &str, fmt: Format) -> Result; -} - -pub fn parse_as(s: &str, fmt: Format) -> Result -where - V: for<'de> Deserialize<'de>, -{ - match fmt { - Format::Json => parse_json(s).map_err(to_translate_error), - Format::Toml => parse_toml(s).map_err(to_translate_error), - Format::Yaml => parse_yaml(s).map_err(to_translate_error), - } -} - -pub fn to_translate_error(e: E) -> TranslateError { - TranslateError::Message(format!("{}", e)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn format_name() { - assert_eq!(Format::Json.name(), "json"); - assert_eq!(Format::Toml.name(), "toml"); - assert_eq!(Format::Yaml.name(), "yaml"); - } - - #[test] - fn format_from_str() { - let r = Format::from_str("json"); - assert!(r.is_ok()); - assert_eq!(r.ok().unwrap(), Format::Json); - - let r = Format::from_str("toml"); - assert!(r.is_ok()); - assert_eq!(r.ok().unwrap(), Format::Toml); - - let r = Format::from_str("yaml"); - assert!(r.is_ok()); - assert_eq!(r.ok().unwrap(), Format::Yaml); - - let r = Format::from_str("yml"); - assert!(r.is_ok()); - assert_eq!(r.ok().unwrap(), Format::Yaml); - } -} diff --git a/src/translator/yaml.rs b/src/translator/yaml.rs deleted file mode 100644 index 1da706b..0000000 --- a/src/translator/yaml.rs +++ /dev/null @@ -1,122 +0,0 @@ -use serde::de::Deserialize; - -use super::translator::{parse_as, to_translate_error, Format, TranslateError, Translator}; - -pub struct YamlTranslator {} - -impl Default for YamlTranslator { - fn default() -> Self { - YamlTranslator {} - } -} - -impl Translator for YamlTranslator { - fn translate(&self, s: &str, fmt: Format) -> Result { - let value = parse_as::(s, fmt)?; - serde_yaml::to_string(&value).map_err(to_translate_error) - } -} - -pub fn parse_yaml(s: &str) -> Result -where - V: for<'de> Deserialize<'de>, -{ - serde_yaml::from_str(s) -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_derive::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - struct Author { - pub id: u64, - pub first_name: String, - pub last_name: String, - } - - #[derive(Serialize, Deserialize)] - struct Book { - pub id: u64, - pub author: Author, - pub title: String, - } - - #[test] - fn test_translator() { - let translator = YamlTranslator::default(); - - // normal case - // NOTE: indent has meaning on yaml format, so do not indent the below text. - let json_text = r#"{ - "id": 123, - "title": "Lorem ipsum dolor sit amet", - "author": { - "id": 999, - "first_name": "John", - "last_name": "Doe" - } -}"#; - - let r = translator.translate(json_text, Format::Json); - assert!(r.is_ok()); - assert_eq!( - r.ok().unwrap(), - r#"--- -id: 123 -title: Lorem ipsum dolor sit amet -author: - id: 999 - first_name: John - last_name: Doe"# - ); - - // error case: syntax error (no quote on each field names) - let json_text = r#"{ - id: 123, - title: "Lorem ipsum dolor sit amet", - author": { - id: 999, - first_name: "John", - last_name: "Doe" - } -}"#; - let r = translator.translate(json_text, Format::Json); - assert!(r.is_err()); - } - - #[test] - fn test_parse_yaml() { - // normal case - let yaml_text = r#"--- -id: 123 -title: Lorem ipsum dolor sit amet -author: - id: 999 - first_name: John - last_name: Doe"#; - - let book = parse_yaml::(yaml_text); - assert!(book.is_ok()); - let book = book.unwrap(); - assert_eq!(book.id, 123); - assert_eq!(book.title, "Lorem ipsum dolor sit amet"); - assert_eq!(book.author.id, 999); - assert_eq!(book.author.first_name, "John"); - assert_eq!(book.author.last_name, "Doe"); - - // error case: wrong indents - let yaml_text = r#" - --- - id: 123 - title: Lorem ipsum dolor sit amet - author: - id: 999 - first_name: John - last_name: Doe - "#; - let book = parse_yaml::(yaml_text); - assert!(book.is_err()); - } -}