commit a33ed2eb30b09e69e76aa59a6e1b37017e01b503 Author: loki5512344 Date: Wed Sep 9 21:43:42 2026 +0200 chore: project skeleton — module contracts, docs plan, GPLv3 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6111726 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/jmove +.jmove/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1e3aa6c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,695 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "assert_cmd" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + +[[package]] +name = "cc" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ignore" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jmove" +version = "0.1.0" +dependencies = [ + "anyhow", + "assert_cmd", + "clap", + "ignore", + "predicates", + "serde", + "serde_json", + "similar", + "tempfile", + "thiserror", + "tree-sitter", + "tree-sitter-typescript", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "similar" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f66ca1f7aca2474dc10c942eb22feffc897735f54cd1db90138c2fddb490987" +dependencies = [ + "bstr", +] + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tree-sitter" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2038684e0058edba0d17302619f62eabce4a8e11c6ac59506996a8d79848851d" +dependencies = [ + "cc", + "regex", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081" + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9f2f340 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "jmove" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.104" +clap = { version = "4.6.6", features = ["derive"] } +ignore = "0.4.33" +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +similar = "3.2.0" +thiserror = "2.0.20" +tree-sitter = "0.27.0" +tree-sitter-typescript = "0.23.2" + +[dev-dependencies] +assert_cmd = "2.2.2" +predicates = "3.1.4" +tempfile = "3.27.0" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a5628a --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# jmove + +**Move source files. Keep every import intact.** + +`jmove` is a fast, single-binary CLI that moves/renames source files in a +project and rewrites all import statements that reference them — no IDE, +no language server, no runtime dependencies. Built for humans *and* AI +agents. + +## Why + +`git mv` moves the file but breaks every relative import pointing at it. +IDE renames fix imports but need an IDE. Existing CLI alternatives either +shell out to heavy language servers (bun/ts-morph, Rope, gopls, +rust-analyzer) or refuse to preview what they will change. + +jmove's answer: + +- **Dry-run + unified diff** — see every change before it hits disk. +- **Atomic apply with rollback** — imports are rewritten first, the file + moves last; any failure restores everything. +- **AI-agent mode** — `--json` everywhere, stable error codes, exit codes, + and a machine-readable skill doc (`docs/SKILL.md`). +- **Pure Rust, one binary** — no external toolchains to install. +- **Java support** — the package/directory coupling nobody else automates + outside IntelliJ. + +## Install + +```sh +cargo install jmove +``` + +## Usage + +```sh +# Preview what a move would change +jmove mv src/utils/parser.ts src/core/parser.ts --dry-run + +# Apply it +jmove mv src/utils/parser.ts src/core/parser.ts + +# Java: jmove updates `package`, all `import`s and moves the file +jmove mv src/com/example/utils/Parser.java src/com/example/core/Parser.java + +# Find broken imports (exit code 2 if any) +jmove check + +# AI-agent workflow +jmove mv src/foo.ts src/bar/foo.ts --dry-run --json +jmove mv src/foo.ts src/bar/foo.ts --json +jmove check --json +``` + +See [docs/EXAMPLES.md](docs/EXAMPLES.md) for more, and +[docs/SKILL.md](docs/SKILL.md) if you are an AI agent. + +## Roadmap + +TypeScript/JavaScript first, then Java (open niche), Python, Go. +`split` (automatic file decomposition) is planned — no tool does it. +Full plan: [docs/PLAN.md](docs/PLAN.md). + +## License + +GPL-3.0-only. See [LICENSE](LICENSE). diff --git a/docs/PLAN.md b/docs/PLAN.md new file mode 100644 index 0000000..8cda0d3 --- /dev/null +++ b/docs/PLAN.md @@ -0,0 +1,90 @@ +# jmove — Product Plan + +## Vision + +The `mv` for source code that never breaks imports: fast, safe (dry-run + +atomic rollback), single static Rust binary — usable by humans in a +terminal and by AI agents over `--json`, with zero external toolchains. + +## Competitive landscape + +The closest analog is **refac / ai_refac** (github: jav-ed/ai_refac): +moves sources and updates imports/paths across the project, aimed at +AI agents without an IDE. + +| | refac/ai_refac | jmove | +|---|---|---| +| Languages | TS/JS (ts-morph+Bun), Python (Rope), Rust (rust-analyzer LSP), Go (gopls), Dart, Markdown | TS/JS → Java → Python/Go | +| Under the hood | spawns external toolchains/LSPs per run | pure Rust, tree-sitter grammars in-process | +| Dry-run | **none** — writes straight to disk | core feature, unified diff | +| Atomicity/rollback | not described | rewrite-then-move, rollback on failure | +| TS limit | 30 files per call | none (index-based) | +| Go behaviour | moves whole package for any file | per-file moves | +| Java | not supported, not planned | **open niche — headline feature** | +| UX | agent-oriented CLI | human UX + first-class `--json` agent mode | + +**Conclusion:** the idea is validated, the product is not. jmove wins on +dry-run, speed (no LSP startup), human UX, single-binary distribution, +the `split` command (nobody has it) and Java. + +## Why Java is the wedge + +Java imports are fully-qualified package paths hard-coupled to directory +layout: + +``` +src/com/example/utils/Parser.java +→ package com.example.utils; // must change +→ import com.example.utils.Parser; // must change in every importer +→ + physical file move +``` + +Three coordinated edits per move; outside IntelliJ nobody automates it. +In headless/CI/agent contexts there is no IntelliJ. Implementation: +tree-sitter Java grammar (mature) + directory⇄package convention + +source-root detection (`src/main/java`, `src/`, ...). This is the feature +that makes jmove *the* answer for "move a Java file in CI". + +## AI-agent design + +Existing tools ship a raw `.agents/skills/` folder; we do it properly: + +- `--json` on every command. Envelope: + `{ "status": "ok" | "dry_run" | "error", "operation": "...", ... }` +- Stable error codes + actionable hints: + `{ "status":"error", "code":"TARGET_EXISTS", "message":"...", "hint":"Use --force or choose another target" }` +- `mv --dry-run --json` → `{ would_move, would_update, affected_files }` + so agents preview → confirm → apply → `check --json`. +- Exit codes: `0` ok · `1` error · `2` broken imports (from `check`). +- `docs/SKILL.md` — machine-readable instructions an agent can drop into + context: short, no fluff, workflow-first. + +## Correctness principles + +1. Rewrite **only the specifier string** (byte span), never reformat the + statement — no formatter dependency, minimal diffs. +2. Resolution must match the language's own lookup: TS extension guessing + + `index.*`; Java package⇄path; (Phase 2) tsconfig `paths` aliases. +3. Imports are rewritten **before** the move; any error rolls back all + writes. Prefer `git mv` when inside a repo to preserve history. +4. Beyond imports, warn (don't silently rewrite) about other references: + `package.json` exports, jest mocks, `tsconfig` includes, markdown links. + +## Phases + +- **Phase 1 (MVP)** — as in `todo.md`: TS/JS scan+index, graph, mv with + dry-run/atomic apply. Add to MVP: relative-path resolution incl. + `index.*` (without it the tool is toy-grade); `check` as the self-test. +- **Phase 1.5 — Java.** The differentiator, as soon as the TS pipeline is + proven. Also: `--json` + SKILL.md ship *with* Java, not after. +- **Phase 2** — disk index cache (`.jmove/`), incremental reindex, + tsconfig paths, rayon parallelism, `--git` integration. +- **Phase 3** — Python, Go (per-file, unlike refac), `split` command. +- **Later** — LSP server mode (become the thing refac spawns), watch mode, + VS Code wrapper. + +## Rules (binding, see todo.md) + +KISS · DRY · SOLID · ≤250 lines/file · ≤4 files/folder · fmt+clippy +`-D warnings` per commit · no dead code, no `#[allow(dead_code)]` · +English Javadoc-style comments on all public API. diff --git a/docs/SKILL.md b/docs/SKILL.md new file mode 100644 index 0000000..413f23b --- /dev/null +++ b/docs/SKILL.md @@ -0,0 +1,59 @@ +# jmove skill (for AI agents) + +## What this tool does + +Moves or renames source files inside a project and updates every import +statement referencing them. Never breaks imports. Supported: TypeScript, +JavaScript; Java/Python/Go on the roadmap. Single binary, no LSP needed. + +## When to use + +- The user asks to move/rename/reorganize source files or packages. +- You refactored a directory layout and imports now point at nothing. +- You need to verify the project has no broken imports (`check`). + +## Commands + +### mv — move a file and rewrite its importers + +``` +jmove mv [--root DIR] [--dry-run] [--json] [--force] +``` + +Always run `--dry-run` first and confirm the change set looks right. + +### check — find broken imports + +``` +jmove check [--root DIR] [--json] +``` + +Run after any move (or any edit) to validate project consistency. + +## Recommended agent workflow + +1. `jmove mv --dry-run --json` — preview. +2. Inspect `affected_files`; if unexpected, abort and ask the user. +3. `jmove mv --json` — apply (atomic, rolls back on error). +4. `jmove check --json` — verify nothing is broken. + +## Output format (--json) + +Every response: `{ "status": "ok" | "dry_run" | "error", "operation": "...", ... }`. +Errors carry a stable `code` (`TARGET_EXISTS`, `SOURCE_NOT_FOUND`, ...) +and a `hint` describing the next action. On success, `mv` reports +`changed_files` with line-level `old`/`new` import diffs and counters +(`moved`, `updated_imports`). + +## Exit codes + +- `0` — success +- `1` — operation failed (read `--json` error or stderr) +- `2` — `check` found broken imports + +## Rules of use + +- Never run `mv` without a prior `--dry-run` in the same session. +- Never pass `--force` unless the user explicitly authorized overwriting. +- After every successful `mv`, run `check`; treat exit code `2` as + a failed refactor. diff --git a/src/cache/mod.rs b/src/cache/mod.rs new file mode 100644 index 0000000..e03d24a --- /dev/null +++ b/src/cache/mod.rs @@ -0,0 +1,5 @@ +//! On-disk index cache. +//! +//! Intentionally empty in Phase 1: the project is rescanned on every run. +//! Phase 2 will persist the import graph under `.jmove/` and support +//! incremental reindexing (see `todo.md`). diff --git a/src/cli/json.rs b/src/cli/json.rs new file mode 100644 index 0000000..74f5a1d --- /dev/null +++ b/src/cli/json.rs @@ -0,0 +1,36 @@ +//! JSON output shapes shared by all `--json` commands. +//! +//! Every response is an [`Envelope`] whose `status` is one of +//! `"ok" | "dry_run" | "error"`, plus a machine-readable error `code` +//! and a human `hint` on failure (see `docs/SKILL.md`). + +use serde::Serialize; + +/// Top-level envelope for every `--json` response. +#[derive(Debug, Serialize)] +pub struct Envelope { + /// One of `"ok"`, `"dry_run"`, `"error"`. + pub status: &'static str, + /// The operation that produced this response, e.g. `"mv"`, `"check"`. + pub operation: &'static str, + /// Command-specific payload. + #[serde(flatten)] + pub data: T, +} + +/// Error payload: stable `code`, human `message`, actionable `hint`. +#[derive(Debug, Serialize)] +pub struct ErrorData { + /// Machine-readable code, e.g. `TARGET_EXISTS`, `SOURCE_NOT_FOUND`. + pub code: String, + /// Human-readable explanation. + pub message: String, + /// What the caller should do next (never null in output; omit if none). + pub hint: Option, +} + +/// Serialize `value` as pretty JSON to stdout. +pub fn print(value: &T) { + let _ = value; + todo!("cli agent: println!(serde_json::to_string_pretty)") +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..bb3fd9f --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,96 @@ +//! Command line interface: argument parsing, dispatch and exit codes. +//! +//! Exit codes (mirrored in `docs/SKILL.md`): +//! `0` success · `1` operation error · `2` broken imports found. + +pub mod json; + +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; + +/// jmove — move source files, keep every import intact. +#[derive(Debug, Parser)] +#[command(name = "jmove", version, about, long_about = None)] +pub struct Args { + /// Subcommand to execute. + #[command(subcommand)] + pub command: Command, + /// Project root (defaults to the current directory). + #[arg(long, global = true, default_value = ".")] + pub root: PathBuf, +} + +/// Available subcommands (MVP: `mv`, `check`). +#[derive(Debug, Subcommand)] +pub enum Command { + /// Move a file and rewrite all imports referencing it. + Mv { + /// File being moved (project-relative or inside the root). + source: PathBuf, + /// Destination path. + target: PathBuf, + /// Preview changes without touching the disk. + #[arg(long)] + dry_run: bool, + /// Machine-readable JSON output (for AI agents). + #[arg(long)] + json: bool, + /// Allow overwriting an existing target file. + #[arg(long)] + force: bool, + }, + /// Report broken imports in the project. + Check { + /// Machine-readable JSON output. + #[arg(long)] + json: bool, + }, +} + +/// Process exit codes documented for humans and agents alike. +pub mod exit { + /// Operation completed successfully. + pub const OK: i32 = 0; + /// Invalid usage, rejected plan or IO failure. + pub const ERROR: i32 = 1; + /// `check` found at least one broken import. + pub const BROKEN: i32 = 2; +} + +/// Parse arguments and run the selected command. +/// Returns the process exit code; `Err` is reserved for unexpected failures. +pub fn run() -> anyhow::Result { + let args = Args::parse(); + match args.command { + Command::Mv { + source, + target, + dry_run, + json, + force, + } => mv(&args.root, &source, &target, dry_run, json, force), + Command::Check { json } => check(&args.root, json), + } +} + +/// `mv` handler: build index, plan, then dry-run-print or apply. +fn mv( + root: &Path, + source: &Path, + target: &Path, + dry_run: bool, + json: bool, + force: bool, +) -> anyhow::Result { + let _ = (root, source, target, dry_run, json, force); + todo!("cli agent: wire mv to core::index/plan/apply") +} + +/// `check` handler: report imports that resolve to nothing. +fn check(root: &Path, json: bool) -> anyhow::Result { + let _ = (root, json); + todo!("cli agent: wire check to core::index") +} + +use std::path::Path; diff --git a/src/core/apply.rs b/src/core/apply.rs new file mode 100644 index 0000000..01b2498 --- /dev/null +++ b/src/core/apply.rs @@ -0,0 +1,35 @@ +//! Atomic apply with rollback, plus unified-diff rendering for dry-run. +//! +//! Order matters: specifier rewrites are applied to importer files first +//! (each written atomically via temp-file + rename), and the actual +//! `source -> target` rename happens last. Any failure mid-way triggers +//! rollback of everything already written. + +use std::path::Path; + +use crate::core::JmoveResult; +use crate::core::plan::MovePlan; + +/// Summary of a successfully applied plan. +#[derive(Debug, Clone)] +pub struct Applied { + /// Number of files whose imports were rewritten. + pub files_rewritten: usize, + /// The moved file's new project-relative path. + pub new_path: std::path::PathBuf, +} + +/// Apply `plan` under `root` atomically (see module docs). Rollback is +/// best-effort: on restore failure the error message states which files +/// need manual recovery. +pub fn apply(root: &Path, plan: &MovePlan) -> JmoveResult { + let _ = (root, plan); + todo!("index agent: atomic apply + rollback") +} + +/// Render the plan as a unified diff (rewrites + file rename) for dry-run. +#[must_use] +pub fn render_diff(root: &Path, plan: &MovePlan) -> JmoveResult { + let _ = (root, plan); + todo!("index agent: diff rendering via `similar`") +} diff --git a/src/core/index.rs b/src/core/index.rs new file mode 100644 index 0000000..85e481f --- /dev/null +++ b/src/core/index.rs @@ -0,0 +1,79 @@ +//! Project indexing: gitignore-aware filesystem scan plus the import graph. +//! +//! Built fresh on every command (disk cache is Phase 2). `ignore::WalkBuilder` +//! handles `.gitignore`/hidden-file rules; every indexed TS/JS source file is +//! parsed through [`crate::parser`] and its specifiers resolved through +//! [`crate::parser::resolve`]. + +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; + +use crate::core::JmoveResult; +use crate::parser::ImportRecord; + +/// Indexed source files with O(1) membership lookups. +#[derive(Debug, Default)] +pub struct FileSet { + paths: HashSet, +} + +impl FileSet { + /// Add a normalized project-relative path; `false` if already present. + pub fn add(&mut self, path: PathBuf) -> bool { + self.paths.insert(path) + } + + /// Whether `path` is a known indexed source file. + #[must_use] + pub fn contains(&self, path: &Path) -> bool { + self.paths.contains(path) + } + + /// All files in deterministic sorted order (stable for tests and diffs). + #[must_use] + pub fn sorted(&self) -> Vec { + let mut v: Vec = self.paths.iter().cloned().collect(); + v.sort(); + v + } +} + +/// One import occurrence plus the project file it resolves to. +/// `target: None` means "external" — a bare package specifier or a path +/// that does not exist in the index. +#[derive(Debug, Clone)] +pub struct ResolvedImport { + /// Raw record from the parser (specifier text + byte span). + pub record: ImportRecord, + /// Project-relative resolved file, if any. + pub target: Option, +} + +/// Full in-memory project index: file set and forward import edges. +#[derive(Debug, Default)] +pub struct Index { + /// Absolute project root the index was built for. + pub root: PathBuf, + /// All indexed source files. + pub files: FileSet, + /// For each file, the imports it declares (in source order). + pub imports: HashMap>, +} + +impl Index { + /// Scan `root`, parse every supported source file and build the graph. + /// Unparseable files are skipped, not fatal. + pub fn build(root: &Path) -> JmoveResult { + let _ = root; + todo!( + "index agent: scan with `ignore`, parse via crate::parser, resolve via parser::resolve" + ) + } + + /// Reverse edge lookup: every indexed file that imports `target`. + #[must_use] + pub fn importers_of(&self, target: &Path) -> Vec { + let _ = target; + todo!("index agent: reverse-edge lookup") + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..f4f8245 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,103 @@ +//! Core engine: project indexing, dependency graph, move planning and +//! atomic apply with rollback. +//! +//! Path convention used across the crate: every `PathBuf` produced by +//! `jmove` is **project-root-relative, normalized** (no `.`/`..` segments). +//! Use [`normalize_rel_path`] to canonicalize paths coming from users or +//! from OS walking. + +pub mod apply; +pub mod index; +pub mod plan; + +use std::ffi::OsStr; +use std::io; +use std::path::{Component, Path, PathBuf}; + +use thiserror::Error; + +/// Crate-wide error type surfaced to the CLI layer. +#[derive(Debug, Error)] +pub enum JmoveError { + /// Filesystem or IO failure. + #[error("io error: {0}")] + Io(#[from] io::Error), + /// The user supplied a path that is invalid for the requested operation. + #[error("invalid argument: {0}")] + InvalidArgument(String), + /// Project index is stale (a file vanished or was moved externally). + #[error("index is stale: {0}")] + StaleIndex(String), + /// The planned move cannot be applied safely. + #[error("plan rejected: {0}")] + PlanRejected(String), +} + +/// Result alias used throughout the crate. +pub type JmoveResult = Result; + +/// Normalize a project-relative path: strip `.` segments, collapse `..` +/// where possible and reject paths that escape the project root. +/// Returns `None` if the result would be empty, absolute or above the root. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use jmove::core::normalize_rel_path; +/// +/// assert_eq!( +/// normalize_rel_path(Path::new("./src/../utils/foo.ts")), +/// Some(PathBuf::from("utils/foo.ts")) +/// ); +/// assert_eq!(normalize_rel_path(Path::new("../outside")), None); +/// ``` +#[must_use] +pub fn normalize_rel_path(path: &Path) -> Option { + let mut stack: Vec<&OsStr> = Vec::new(); + for comp in path.components() { + match comp { + Component::CurDir => {} + Component::ParentDir => { + if stack.pop().is_none() { + return None; // would escape the project root + } + } + Component::Normal(piece) => stack.push(piece), + // Absolute paths and Windows prefixes are not project-relative. + Component::RootDir | Component::Prefix(_) => return None, + } + } + (!stack.is_empty()).then(|| stack.iter().collect::()) +} + +#[cfg(test)] +mod tests { + use super::normalize_rel_path; + use std::path::{Path, PathBuf}; + + #[test] + fn normalizes_dots_and_parent_dirs() { + assert_eq!( + normalize_rel_path(Path::new("./src/../utils/foo.ts")), + Some(PathBuf::from("utils/foo.ts")) + ); + assert_eq!( + normalize_rel_path(Path::new("a/b/c/../../d.ts")), + Some(PathBuf::from("a/d.ts")) + ); + } + + #[test] + fn rejects_escaping_and_empty_paths() { + assert_eq!(normalize_rel_path(Path::new("../outside")), None); + assert_eq!(normalize_rel_path(Path::new("a/../../outside")), None); + assert_eq!(normalize_rel_path(Path::new("")), None); + assert_eq!(normalize_rel_path(Path::new("./")), None); + } + + #[test] + fn rejects_absolute_paths() { + assert_eq!(normalize_rel_path(Path::new("/etc/passwd")), None); + } +} diff --git a/src/core/plan.rs b/src/core/plan.rs new file mode 100644 index 0000000..ae7a9db --- /dev/null +++ b/src/core/plan.rs @@ -0,0 +1,46 @@ +//! Move planning: decide which import specifiers must be rewritten. +//! +//! A plan is pure data (no disk writes), so dry-run and `--json` can render +//! it without touching the filesystem. + +use std::ops::Range; +use std::path::Path; +use std::path::PathBuf; + +use crate::core::JmoveResult; +use crate::core::index::Index; + +/// One in-file replacement of an import specifier. Only the specifier text +/// between the quotes is touched — the statement layout is never reformatted. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Rewrite { + /// Project-relative file to modify. + pub file: PathBuf, + /// Byte range of the old specifier text (without quotes). + pub span: Range, + /// Specifier as currently written. + pub old_text: String, + /// Specifier after the move. + pub new_text: String, +} + +/// Complete plan for moving `source` to `target`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MovePlan { + /// Project-relative path being moved. + pub source: PathBuf, + /// Project-relative destination path. + pub target: PathBuf, + /// Specifier rewrites, grouped per importer file. + pub rewrites: Vec, +} + +/// Compute the rewrite plan for `source -> target`. +/// +/// Every indexed import whose resolved target is `source` gets a new +/// relative specifier computed from the *importer's* directory to `target`. +/// Rewrites whose result equals the old specifier are dropped. +pub fn plan_move(index: &Index, source: &Path, target: &Path) -> JmoveResult { + let _ = (index, source, target); + todo!("index agent: implement planner incl. relative-specifier math") +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bff461c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,17 @@ +//! `jmove` — a project-aware file mover for TypeScript/JavaScript. +//! +//! Moving a file inside a project invalidates every relative import that +//! points at it. `jmove` indexes the project's import graph, computes the +//! minimal set of specifier rewrites, and applies everything atomically +//! (with rollback), optionally in dry-run mode. +//! +//! Module map: +//! - [`cli`] — argument parsing, command dispatch, user-facing output. +//! - [`core`] — indexing, dependency graph, move planning, atomic apply. +//! - [`parser`] — language frontends (import extraction, path resolution). +//! - [`cache`] — on-disk index cache (Phase 2, intentionally empty for now). + +pub mod cache; +pub mod cli; +pub mod core; +pub mod parser; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7eaa560 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,19 @@ +//! Binary entry point for `jmove`. +//! +//! All logic lives in the library crate; this main only maps the outcome of +//! [`jmove::cli::run`] onto process exit codes: +//! - `0` — success, +//! - `1` — user-facing plan/validation error (already printed), +//! - `2` — unexpected failure. + +/// Parses arguments, runs the selected command and converts failures into a +/// non-zero exit code, printing a one-line message for the user. +fn main() { + match jmove::cli::run() { + Ok(code) => std::process::exit(code), + Err(err) => { + eprintln!("jmove: {err}"); + std::process::exit(2); + } + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..02717c6 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,83 @@ +//! Language frontends: import extraction and module specifier resolution. +//! +//! ## Contract (stable across submodules — implementers must not change it) +//! +//! - A [`Language`] parses one source file into [`ImportRecord`]s: every +//! *static-ish* module reference (TS `import`/`export from`/`require`/ +//! dynamic `import()`). +//! - [`crate::core::parser_support::resolve`] turns a specifier into a +//! project-relative file path using a resolver aware of the indexed file +//! set. Non-project (package/bare) specifiers resolve to `None`. +//! - Rewrites must touch **only the specifier string**, never the rest of +//! the statement (KISS + no formatter dependency): that is why +//! [`ImportRecord::span`] is a byte range into the original source. + +use std::path::Path; + +pub mod resolve; +pub mod ts; + +/// Source languages `jmove` understands (Phase 1: TypeScript/JavaScript). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SourceLanguage { + /// `.ts` (non-TSX) sources. + TypeScript, + /// `.tsx` / `.jsx` sources. + Tsx, + /// Plain `.js` / `.mjs` / `.cjs` sources. + JavaScript, +} + +impl SourceLanguage { + /// Map a file extension (lowercase, no dot) to a language, if supported. + #[must_use] + pub fn from_extension(ext: &str) -> Option { + match ext { + "ts" | "mts" | "cts" => Some(Self::TypeScript), + "tsx" | "jsx" => Some(Self::Tsx), + "js" | "mjs" | "cjs" => Some(Self::JavaScript), + _ => None, + } + } + + /// Detect the language from a file name. `None` means "not a source file + /// we index" (skip it). + #[must_use] + pub fn for_path(path: &Path) -> Option { + Self::from_extension( + path.extension() + .and_then(|e| e.to_str()) + .map(str::to_ascii_lowercase) + .as_deref()?, + ) + } +} + +/// One module reference found in a source file. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ImportRecord { + /// Raw specifier text as written, e.g. `"../utils/format"`. + pub specifier: String, + /// Byte range of the *specifier string contents* (inside the quotes, + /// without the quote characters) in the parsed file. The rewriter + /// replaces exactly this span and nothing else. + pub span: std::ops::Range, + /// `true` for dynamic `import("...")` / `require("...")` occurrences. + pub is_dynamic: bool, +} + +/// A language frontend that extracts imports from source text. +pub trait Language: Send + Sync { + /// The language this frontend handles. + fn language(&self) -> SourceLanguage; + + /// Extract all import records from `source` in byte-offset order. + /// Parse errors must not be fatal: return what was understood. + fn extract_imports(&self, source: &str) -> Vec; +} + +/// Build the default frontend for `lang`. +#[must_use] +pub fn frontend_for(lang: SourceLanguage) -> Box { + Box::new(ts::TreeSitterTs::new(lang)) +} diff --git a/src/parser/resolve.rs b/src/parser/resolve.rs new file mode 100644 index 0000000..dc91e8e --- /dev/null +++ b/src/parser/resolve.rs @@ -0,0 +1,26 @@ +//! Module specifier resolution for TS/JS projects. +//! +//! CONTRACT: see [`crate::parser`]. Given a *relative* specifier and the +//! importing file, find which indexed project file it refers to. Bare / +//! package specifiers (not starting with `.`) are out of project scope and +//! resolve to `None`. + +use std::path::{Path, PathBuf}; + +use crate::core::index::FileSet; + +/// Resolve `specifier` (e.g. `"../utils/fmt"`) written in the file at +/// `importer` (project-relative), against the indexed `files`. +/// +/// Resolution order for extensionless specifiers (Node/TS classic): +/// 1. exact path if indexed (e.g. `"./a.ts"`), +/// 2. `` + each supported extension (`.ts`, `.tsx`, `.js`, `.jsx`, +/// `.mjs`, `.cjs` — declaration files only when nothing else matches), +/// 3. `/index.`. +/// +/// Returns `None` for bare specifiers or unresolvable paths. +#[must_use] +pub fn resolve_module(importer: &Path, specifier: &str, files: &FileSet) -> Option { + let _ = (importer, specifier, files); + todo!("parser agent: implement resolver") +} diff --git a/src/parser/ts.rs b/src/parser/ts.rs new file mode 100644 index 0000000..2659ce6 --- /dev/null +++ b/src/parser/ts.rs @@ -0,0 +1,29 @@ +//! Tree-sitter based frontend for TypeScript/JavaScript. +//! +//! CONTRACT: see [`crate::parser`]. Implement `extract_imports` using +//! `tree-sitter-typescript` grammars. + +use super::{ImportRecord, Language, SourceLanguage}; + +/// Frontend backed by the tree-sitter TypeScript/TSX/JS grammar. +pub struct TreeSitterTs { + lang: SourceLanguage, +} + +impl TreeSitterTs { + /// Create a frontend for the given language variant. + #[must_use] + pub fn new(lang: SourceLanguage) -> Self { + Self { lang } + } +} + +impl Language for TreeSitterTs { + fn language(&self) -> SourceLanguage { + self.lang + } + + fn extract_imports(&self, _source: &str) -> Vec { + todo!("parser agent: implement tree-sitter extraction") + } +} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..b9fcd5c --- /dev/null +++ b/todo.md @@ -0,0 +1,39 @@ +# jmove — TODO + +## Правила +- KISS — simplest solution that works; no speculative abstractions. +- DRY — no duplicated logic; extract into a function/module instead. +- SOLID — single responsibility per module (`cli`/`core`/`parser`/`cache`), open for extension (new languages) via the parser trait, no deps on implementation details. +- Max 250 lines per file — split when exceeded. +- Max 4 files per folder (module) — split the module when exceeded. +- Каждый коммит проходит `cargo fmt --check` и `cargo clippy -- -D warnings`. +- Запрещён mёртвый код: unused code удаляется или реализуется; `#[allow(dead_code)]` не использовать. + +## MVP (Phase 1) +- [ ] CLI skeleton (clap) — команды: mv, index, check, --dry-run +- [ ] Сканер файлов проекта (уважать .gitignore через `ignore` крейт) +- [ ] Парсер импортов для TypeScript/JS через tree-sitter +- [ ] Построение графа зависимостей (файл → что импортирует) +- [ ] Инвертированный граф (файл → кто его импортирует) +- [ ] Вычисление нового относительного пути после mv +- [ ] Rewrite импортов в файлах +- [ ] Атомарный apply (сначала rewrite, потом mv) + rollback при ошибке +- [ ] Dry-run режим с diff выводом + +## Phase 2 +- [ ] Кэш индекса на диске (bincode/rkyv) → .jmove/index +- [ ] Инкрементальная переиндексация (только изменённые файлы) +- [ ] Поддержка tsconfig paths / алиасов (@/...) +- [ ] Параллельная индексация через rayon + +## Phase 3 +- [ ] Поддержка Python +- [ ] Поддержка Java +- [ ] Поддержка Go +- [ ] Команда split (авто-разбивка файла на несколько) +- [ ] Команда check (найти все битые импорты) + +## Идеи на потом +- [ ] LSP интеграция +- [ ] Watch mode +- [ ] VS Code расширение как обёртка над CLI