aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--COPYING21
-rw-r--r--Cargo.lock437
-rw-r--r--Cargo.toml25
-rw-r--r--README.md24
-rw-r--r--src/db.rs258
-rw-r--r--src/main.rs69
-rw-r--r--src/table.rs235
8 files changed, 1072 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..421b147
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/target
+**/*.rs.bk
+/db
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..9cd9159
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,21 @@
+This project is licensed under the MIT license.
+
+Copyright (C) 2019 Nicolas Schodet
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..eee2d38
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,437 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "adler32"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "build_const"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cc"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "clap"
+version = "2.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crc"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "csv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "either"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "flate2"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
+ "miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itertools"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lazy_static"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "miniz-sys"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "miniz_oxide_c_api"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
+ "miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "numtoa"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "pinmap"
+version = "0.1.0"
+dependencies = [
+ "csv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "roxmltree 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "redox_termios"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex"
+version = "1.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.6.6 (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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "roxmltree"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "xmlparser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ryu"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "structopt"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syn"
+version = "0.15.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.12 (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 = "termion"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
+ "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ucd-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+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 = "winapi"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "xmlparser"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
+"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c"
+"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 bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
+"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
+"checksum cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c56216487bb80eec9c4516337b2588a4f2a2290d72a1416d930e4dcdb0c90d"
+"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
+"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
+"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
+"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
+"checksum csv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9044e25afb0924b5a5fc5511689b0918629e85d68ea591e5e87fbf1e85ea1b3b"
+"checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65"
+"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
+"checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa"
+"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
+"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
+"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
+"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6"
+"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
+"checksum miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0300eafb20369952951699b68243ab4334f4b10a88f411c221d444b36c40e649"
+"checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e"
+"checksum miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fe927a42e3807ef71defb191dc87d4e24479b221e67015fe38ae2b7b447bab"
+"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
+"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
+"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"
+"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252"
+"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
+"checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58"
+"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96"
+"checksum roxmltree 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "53b0200cbfa8b3f6cfd6076592717d697a1ddc57cb2a8fbfd3d133c06011b579"
+"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"
+"checksum serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd"
+"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+"checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1"
+"checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6"
+"checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe"
+"checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea"
+"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
+"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 winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+"checksum xmlparser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ecec95f00fb0ff019153e64ea520f87d1409769db3e8f4db3ea588638a3e1cee"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9030e37
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "pinmap"
+version = "0.1.0"
+authors = ["Nicolas Schodet <nico@ni.fr.eu.org>"]
+description = """
+pinmap is a tool to help pin mapping when designing a board with an STM32
+microcontroller. It uses a database extracted from CubeMX to construct a
+table of all pins and associated signals. This table can be open using your
+favorite spreadsheet to assign functions to pins.
+"""
+homepage = "http://git.ni.fr.eu.org/nicolas/pinmap.git/about/"
+repository = "http://git.ni.fr.eu.org/nicolas/pinmap.git/"
+readme = "README.md"
+keywords = ["embedded", "stm32"]
+categories = ["command-line-utilities", "embedded"]
+license = "MIT"
+edition = "2018"
+
+[dependencies]
+csv = "1.0.7"
+flate2 = "1.0"
+itertools = "0.8.0"
+regex = "1"
+roxmltree = "0.6"
+structopt = "0.2"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dba9bbd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
+pinmap
+------
+
+pinmap is a tool to help pin mapping when designing a board with an STM32
+microcontroller.
+
+It uses a database extracted from CubeMX to construct a table of all pins and
+associated signals. This table can be open using your favorite spreadsheet to
+assign functions to pins.
+
+pinmap supports GPIO mapping using AF (alternate functions) and the old system
+using REMAP. The output table format differ to accommodate the differences.
+
+### Extracting the database
+
+Download CubeMX from st.com, and run the installation. In the installed
+directory, there is a db directory, copy this to pinmap directory.
+
+To reduce disk usage, pinmap uses a compressed database, run the following
+command to compress it:
+
+```
+find . -exec gzip '{}' +
+```
diff --git a/src/db.rs b/src/db.rs
new file mode 100644
index 0000000..41ef85e
--- /dev/null
+++ b/src/db.rs
@@ -0,0 +1,258 @@
+// Copyright (C) 2019 Nicolas Schodet
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+// and associated documentation files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+//! This module handles loading parts information from database.
+use flate2::read::GzDecoder;
+use roxmltree::{Document, Node};
+use std::collections::HashMap;
+use std::error::Error;
+use std::fs::File;
+use std::io::Read;
+use std::path::Path;
+
+static EXT: &str = ".xml.gz";
+
+type Result<T> = std::result::Result<T, Box<Error>>;
+
+/// Information about a part.
+#[derive(Debug)]
+pub struct PartInfo<'a> {
+ /// Part.
+ pub part: &'a str,
+ /// Product line.
+ pub line: String,
+ /// Package.
+ pub package: String,
+ /// GPIO mapping mode.
+ pub gpio_mode: GpioMode,
+ /// Information for all pins.
+ pub pins: Vec<PinInfo>,
+}
+
+/// Information about one pin.
+#[derive(Debug)]
+pub struct PinInfo {
+ /// Name.
+ pub name: String,
+ /// Position in package. This can be a number or a letter with a number.
+ pub position: String,
+ /// Signals.
+ pub signals: Vec<SignalInfo>,
+}
+
+/// Information about one signal.
+#[derive(Debug)]
+pub struct SignalInfo {
+ /// Name.
+ pub name: String,
+ /// Mapping information.
+ pub map: SignalMap,
+}
+
+/// Information on how to map a signal to a pin.
+#[derive(Clone, Debug)]
+pub enum SignalMap {
+ /// Alternate function, with its AF number.
+ AF(u8),
+ /// Additional function, no AF setup to do.
+ AddF,
+ /// Remap, used on older parts without the AF system. The signal can be available on several
+ /// remaps.
+ Remap(Vec<u8>),
+}
+
+/// Mode of GPIO mapping.
+#[derive(Clone, Copy, Debug)]
+pub enum GpioMode {
+ /// Alternate function based mapping.
+ AF,
+ /// Remap based mapping.
+ Remap,
+}
+
+/// Map pins and signals to mapping information. This is used temporarily when loading it from a
+/// separated file.
+type GpiosInfo = HashMap<String, HashMap<String, SignalMap>>;
+
+impl<'a> PartInfo<'a> {
+ /// Extract information from XML file in database.
+ pub fn new(database: &Path, part: &'a str) -> Result<PartInfo<'a>> {
+ // Read XML.
+ let xml_name = database.join(["mcu/", part, EXT].concat());
+ let xml = read_gziped(&xml_name)?;
+ let doc = Document::parse(&xml)?;
+ let doc_root = doc.root_element();
+ // Basic attributes.
+ let line = attribute_or_error(&doc_root, "Line")?;
+ let package = attribute_or_error(&doc_root, "Package")?;
+ // GPIO.
+ let gpio_ip = doc_root
+ .children()
+ .find(|n| n.has_tag_name("IP") && n.attribute("Name") == Some("GPIO"))
+ .ok_or("missing GPIO")?;
+ let gpio_version = attribute_or_error(&gpio_ip, "Version")?;
+ let (gpio_mode, gpios_info) = load_gpios(database, &gpio_version)?;
+ // Pins.
+ fn parse_signal(
+ signals_map: Option<&HashMap<String, SignalMap>>,
+ s: Node,
+ ) -> Result<SignalInfo> {
+ let name = attribute_or_error(&s, "Name")?;
+ let map = match signals_map {
+ None => SignalMap::AddF,
+ Some(signals_map) => signals_map.get(&name).unwrap_or(&SignalMap::AddF).clone(),
+ };
+ Ok(SignalInfo { name, map })
+ };
+ fn parse_pin(gpios_info: &GpiosInfo, n: Node) -> Result<PinInfo> {
+ let name = attribute_or_error(&n, "Name")?;
+ let position = attribute_or_error(&n, "Position")?;
+ let signals = n
+ .children()
+ .filter(|s| s.has_tag_name("Signal") && s.attribute("Name") != Some("GPIO"))
+ .map(|s| {
+ let signals_map = gpios_info.get(&name);
+ parse_signal(signals_map, s)
+ })
+ .collect::<Result<_>>()?;
+ Ok(PinInfo {
+ name,
+ position,
+ signals,
+ })
+ };
+ let pins = doc_root
+ .children()
+ .filter(|n| n.has_tag_name("Pin"))
+ .map(|n| parse_pin(&gpios_info, n))
+ .collect::<Result<_>>()?;
+ // Done.
+ Ok(PartInfo {
+ part,
+ line,
+ package,
+ gpio_mode,
+ pins,
+ })
+ }
+ /// Produce a one-line part summary.
+ pub fn summary(self: &Self) -> String {
+ format!("{}: {} {}", self.part, self.line, self.package)
+ }
+}
+
+/// List all parts in database matching a given regex.
+pub fn list_parts(database: &Path, pattern: &str) -> Result<Vec<String>> {
+ let re = regex::Regex::new(pattern)?;
+ let mut list = Vec::new();
+ for entry in database.join("mcu").read_dir()? {
+ if let Some(name) = entry?.file_name().to_str() {
+ if name.ends_with(EXT) {
+ let part = &name[..(name.len() - EXT.len())];
+ if re.is_match(part) {
+ list.push(part.to_owned());
+ }
+ }
+ }
+ }
+ Ok(list)
+}
+
+/// Load information on GPIOs from XML file in database. Return a hash indexed by pin and signal,
+/// giving signal mapping information.
+fn load_gpios(database: &Path, gpio_version: &str) -> Result<(GpioMode, GpiosInfo)> {
+ // Read XML.
+ let xml_name = database.join(["mcu/IP/GPIO-", gpio_version, "_Modes", EXT].concat());
+ let xml = read_gziped(&xml_name)?;
+ let doc = Document::parse(&xml)?;
+ let doc_root = doc.root_element();
+ // Decode document.
+ fn parse_af(signal: Node) -> Result<SignalMap> {
+ let af = signal
+ .descendants()
+ .find(|n| n.has_tag_name("PossibleValue"))
+ .ok_or("no AF found")?
+ .text()
+ .ok_or("no AF text")?;
+ let k = "GPIO_AF";
+ if !af.starts_with(k) {
+ return Err("not an AF".into());
+ }
+ let i = af[k.len()..].find('_').ok_or("not an AF")?;
+ let af = &af[k.len()..k.len() + i];
+ let af = af.parse::<u8>()?;
+ Ok(SignalMap::AF(af))
+ }
+ fn parse_remaps(signal: Node) -> Result<SignalMap> {
+ let remap_blocks = signal.children().filter(|n| n.has_tag_name("RemapBlock"));
+ fn parse_remap(n: Node) -> Result<u8> {
+ let name = attribute_or_error(&n, "Name")?;
+ let k = "REMAP";
+ let i = name.rfind(k).ok_or("missing REMAP")?;
+ let remap = name[i + k.len()..].parse::<u8>()?;
+ Ok(remap)
+ }
+ let remaps = remap_blocks.map(parse_remap).collect::<Result<_>>()?;
+ Ok(SignalMap::Remap(remaps))
+ }
+ let mut gpios = HashMap::new();
+ let pins = doc_root.children().filter(|n| n.has_tag_name("GPIO_Pin"));
+ let mut mode = None;
+ for pin in pins {
+ let pin_name = attribute_or_error(&pin, "Name")?;
+ let signals = pin.children().filter(|n| n.has_tag_name("PinSignal"));
+ let mut signals_map = HashMap::new();
+ for signal in signals {
+ // First try to parse a remap until this fails once.
+ if mode.is_none() {
+ let one_remap_block = signal.children().find(|n| n.has_tag_name("RemapBlock"));
+ mode = if one_remap_block.is_some() {
+ Some(GpioMode::Remap)
+ } else {
+ Some(GpioMode::AF)
+ }
+ }
+ let map = match mode.unwrap() {
+ GpioMode::AF => parse_af(signal),
+ GpioMode::Remap => parse_remaps(signal),
+ }?;
+ let signal_name = attribute_or_error(&signal, "Name")?;
+ signals_map.insert(signal_name, map);
+ }
+ gpios.insert(pin_name, signals_map);
+ }
+ Ok((mode.unwrap_or(GpioMode::AF), gpios))
+}
+
+/// Read gziped file to string.
+fn read_gziped(path: &Path) -> Result<String> {
+ let mut gunzip = GzDecoder::new(File::open(path)?);
+ let mut xml = String::new();
+ gunzip.read_to_string(&mut xml)?;
+ Ok(xml)
+}
+
+/// Factorize attribute getter, return an error if not found.
+fn attribute_or_error(node: &Node, name: &str) -> Result<String> {
+ match node.attribute(name) {
+ Some(v) => Ok(v.to_owned()),
+ None => {
+ let tag = node.tag_name().name();
+ Err(format!("{} missing a {} attribute", tag, name).into())
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..3ac9d28
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,69 @@
+// Copyright (C) 2019 Nicolas Schodet
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+// and associated documentation files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+//! Help with pin assignment on STM32.
+//!
+//! This reads database extracted from CubeMX and produce a table of all signals that can be mapped
+//! to the microcontroller pins. This table can be open with a spreadsheet.
+use std::error::Error;
+use std::io;
+use std::path::PathBuf;
+use structopt::StructOpt;
+
+mod db;
+mod table;
+
+/// MCU pins mapper.
+#[derive(StructOpt, Debug)]
+struct Opt {
+ /// Database path
+ #[structopt(short = "d", long, default_value = "db", parse(from_os_str))]
+ database: PathBuf,
+ /// Exclude component
+ #[structopt(short = "x", long, number_of_values = 1)]
+ exclude: Vec<String>,
+ #[structopt(subcommand)]
+ command: OptCommand,
+}
+
+#[derive(StructOpt, Debug)]
+enum OptCommand {
+ /// Search the database for MCUs matching the given regex.
+ #[structopt(name = "parts")]
+ Parts { pattern: String },
+ /// Output a pin out table for a given part.
+ #[structopt(name = "table")]
+ Table { part: String },
+}
+
+fn main() -> Result<(), Box<Error>> {
+ let opt = Opt::from_args();
+ match opt.command {
+ OptCommand::Parts { pattern } => {
+ for part in db::list_parts(&opt.database, &pattern)? {
+ let part_info = db::PartInfo::new(&opt.database, &part)?;
+ println!("{}", part_info.summary());
+ }
+ }
+ OptCommand::Table { part } => {
+ let part_info = db::PartInfo::new(&opt.database, &part)?;
+ let filter = table::SignalFilter::new(&opt.exclude)?;
+ table::write_pin_out(&part_info, io::stdout(), &filter)?;
+ }
+ }
+ Ok(())
+}
diff --git a/src/table.rs b/src/table.rs
new file mode 100644
index 0000000..efdc23d
--- /dev/null
+++ b/src/table.rs
@@ -0,0 +1,235 @@
+// Copyright (C) 2019 Nicolas Schodet
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+// and associated documentation files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+//! Handle table output.
+use crate::db;
+use itertools::Itertools;
+use regex::{Regex, RegexSet};
+use std::collections::hash_set::HashSet;
+use std::collections::HashMap;
+use std::error::Error;
+use std::io::Write;
+use std::result::Result as StdResult;
+
+type Result<T> = StdResult<T, Box<Error>>;
+
+/// Filter signals to reduce pin out table size.
+pub struct SignalFilter {
+ /// Signals to exclude from table.
+ excludes: RegexSet,
+ /// Substitutions to shorten signal names.
+ subs: Vec<Regex>,
+ /// Factorizations to reduce the number of similar signals, with the associated separator.
+ facts_sep: Vec<(Regex, &'static str)>,
+}
+
+/// Produce a pin out table.
+pub fn write_pin_out(
+ part_info: &db::PartInfo,
+ writer: impl Write,
+ filter: &SignalFilter,
+) -> Result<()> {
+ match part_info.gpio_mode {
+ db::GpioMode::AF => write_pin_out_af(part_info, writer, filter),
+ db::GpioMode::Remap => write_pin_out_remap(part_info, writer, filter),
+ }
+}
+
+/// Produce a pin out table for AF based parts.
+fn write_pin_out_af(
+ part_info: &db::PartInfo,
+ writer: impl Write,
+ filter: &SignalFilter,
+) -> Result<()> {
+ let mut writer = csv::Writer::from_writer(writer);
+ for pin in &part_info.pins {
+ let mut signals: [Vec<_>; 17] = Default::default();
+ for signal in &pin.signals {
+ let index = match signal.map {
+ db::SignalMap::AF(af) => af as usize,
+ db::SignalMap::AddF => signals.len() - 1,
+ _ => panic!("Bad signal map"),
+ };
+ signals[index].push(signal.name.as_str());
+ }
+ let signals = filter.signal_filter(&pin.name, &pin.position, &signals);
+ let mut row = Vec::new();
+ row.push(pin.name.clone());
+ row.push(pin.position.clone());
+ for i in &signals {
+ row.push(i.join(" "));
+ }
+ writer.write_record(row)?;
+ }
+ Ok(())
+}
+
+/// Produce a pin out table for Remap based parts.
+fn write_pin_out_remap(
+ part_info: &db::PartInfo,
+ writer: impl Write,
+ filter: &SignalFilter,
+) -> Result<()> {
+ let mut lines = Vec::new();
+ let mut allcats = HashSet::new();
+ for pin in &part_info.pins {
+ let signals = pin
+ .signals
+ .iter()
+ .map(|signal| {
+ if let db::SignalMap::Remap(remaps) = &signal.map {
+ let remaps = remaps.iter().sorted().map(|x| x.to_string()).join(",");
+ format!("{}({})", signal.name, remaps)
+ } else {
+ signal.name.clone()
+ }
+ })
+ .collect::<Vec<_>>();
+ let signals = filter
+ .signal_filter(&pin.name, &pin.position, &[signals])
+ .into_iter()
+ .next()
+ .unwrap();
+ let mut signals_hash = HashMap::new();
+ for signal in signals {
+ let cat = signal.split('_').next().unwrap().to_owned();
+ allcats.insert(cat.clone());
+ signals_hash.entry(cat).or_insert(Vec::new()).push(signal);
+ }
+ lines.push((&pin.name, &pin.position, signals_hash));
+ }
+ let mut writer = csv::Writer::from_writer(writer);
+ let mut allcats = allcats.into_iter().collect::<Vec<_>>();
+ allcats.sort();
+ for (name, position, signals_hash) in lines {
+ let mut row = Vec::new();
+ row.push(name.clone());
+ row.push(position.clone());
+ for cat in &allcats {
+ let col = signals_hash.get(cat);
+ if let Some(col) = col {
+ row.push(col.iter().join(" "));
+ } else {
+ row.push(String::from(""));
+ }
+ }
+ writer.write_record(row)?;
+ }
+ Ok(())
+}
+
+impl SignalFilter {
+ /// Prepare a new filter.
+ pub fn new(exclude: &Vec<String>) -> StdResult<SignalFilter, regex::Error> {
+ let excludes = RegexSet::new(exclude.iter().map(|x| format!(r"^(?:{})[0-9_]", x)))?;
+ let subs = [
+ "((?:HR|LP)?T)IM",
+ "((?:LP)?U)S?ART",
+ "(D)FSDM",
+ "(F)S?MC",
+ "(Q)UADSPI(?:_BK)?",
+ "(S)PI",
+ "(SW)PMI",
+ "I2(S)",
+ "(SD)MMC",
+ "(SP)DIFRX",
+ "FD(C)AN",
+ "USB_OTG_([FH]S)",
+ r"(T\d_B)KIN",
+ ]
+ .iter()
+ .map(|x| Regex::new(&format!(r"^{}([0-9_])", x)))
+ .collect::<StdResult<_, _>>()?;
+ let facts_sep = [
+ (r"T\d_B\d?_COMP(\d+)", ""),
+ (r"ADC(\d)_IN[NP]?\d+", ""),
+ (r"ADC\d+_IN([NP]?\d+)", ""),
+ (r"[SUT]\d_(.+)", "/"),
+ ]
+ .iter()
+ .map(|(fact, sep)| Ok((Regex::new(fact)?, *sep)))
+ .collect::<StdResult<_, _>>()?;
+ Ok(SignalFilter {
+ excludes,
+ subs,
+ facts_sep,
+ })
+ }
+ /// Filter a list of signal.
+ fn signal_filter<'a, I, J, S>(
+ self: &Self,
+ _name: &str,
+ _position: &str,
+ cols: I,
+ ) -> Vec<Vec<String>>
+ where
+ S: ToString,
+ J: IntoIterator<Item = S>,
+ I: IntoIterator<Item = J>,
+ {
+ let mut res = Vec::new();
+ for signals in cols {
+ let signals = signals
+ .into_iter()
+ .map(|s| {
+ self.subs
+ .iter()
+ .fold(s.to_string(), |s, re| re.replace(&s, "$1$2").to_string())
+ })
+ .collect();
+ let signals = self.facts_sep.iter().fold(signals, |signals, (fact, sep)| {
+ factorize(&signals, &fact, sep)
+ });
+ let signals = signals
+ .into_iter()
+ .filter(|s| !self.excludes.is_match(s))
+ .collect();
+ res.push(signals);
+ }
+ res
+ }
+}
+
+/// For a given iterable, match each items with the given regex, if there are several matches they
+/// are factorized on the first subgroup.
+fn factorize<I, S>(it: I, re: &Regex, sep: &str) -> Vec<String>
+where
+ S: ToString,
+ I: IntoIterator<Item = S>,
+{
+ let mut others = Vec::new();
+ let mut facts: HashMap<_, Vec<_>> = HashMap::new();
+ for i in it {
+ let i = i.to_string();
+ if let Some(c) = re.captures(&i) {
+ let g = c.get(1).expect("first group should match");
+ let termout = (&i[..g.start()], &i[g.end()..]);
+ let termout = (termout.0.to_owned(), termout.1.to_owned());
+ let term = g.as_str().to_owned();
+ facts.entry(termout).or_insert(Vec::new()).push(term);
+ } else {
+ others.push(i);
+ }
+ }
+ let mut r = Vec::new();
+ for (termout, terms) in facts {
+ let terms = terms.join(sep);
+ r.push(format!("{}{}{}", termout.0, terms, termout.1));
+ }
+ r.extend(others);
+ r
+}