mirror of
https://github.com/RGBCube/dix
synced 2025-07-29 12:47:47 +00:00
Compare commits
36 commits
7e60d1c0a9
...
09e769d786
Author | SHA1 | Date | |
---|---|---|---|
![]() |
09e769d786 | ||
![]() |
7c0dcd142d | ||
![]() |
6384189a18 | ||
![]() |
5a532c2b82 | ||
50a9a673bd | |||
714fbbce6f | |||
6271e97882 | |||
e6b8f7b3a7 | |||
4372250ba2 | |||
![]() |
7aa726c056 | ||
646efcf351 | |||
f4357e1c50 | |||
60d7a4d9ae | |||
021c972a0f | |||
d85e9ec3b0 | |||
553e594968 | |||
70d4042a56 | |||
00f8783ce2 | |||
416dc0c455 | |||
baaf63fdf2 | |||
a1ccf88d0c | |||
4f0ee04e19 | |||
07dd524d30 | |||
47bd2d9657 | |||
faa6634142 | |||
fc73fa9722 | |||
078bc91cf8 | |||
066652cee3 | |||
c73c1ab330 | |||
af7f63d3f6 | |||
574142c4a0 | |||
db09147da6 | |||
![]() |
531fa0278f | ||
![]() |
8d36bf6dec | ||
![]() |
d89e57a60c | ||
![]() |
a00d6e6c4e |
26 changed files with 1923 additions and 2221 deletions
BIN
.github/dix.png
vendored
BIN
.github/dix.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
20
.github/workflows/build.yml
vendored
Normal file
20
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
name: Cargo Build & Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ $default-branch ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ $default-branch ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
name: dix - latest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: cargo build --verbose
|
||||||
|
- run: cargo test --verbose
|
||||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,9 +1,3 @@
|
||||||
/.direnv
|
/.direnv
|
||||||
/target
|
/target
|
||||||
|
/result
|
||||||
|
|
||||||
# Added by cargo
|
|
||||||
#
|
|
||||||
# already existing elements were commented out
|
|
||||||
|
|
||||||
#/target
|
|
||||||
|
|
30
.rustfmt.toml
Normal file
30
.rustfmt.toml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Taken from https://github.com/cull-os/carcass.
|
||||||
|
# Modified to have 2 space indents and 80 line width.
|
||||||
|
|
||||||
|
# float_literal_trailing_zero = "Always" # TODO: Warning for some reason?
|
||||||
|
condense_wildcard_suffixes = true
|
||||||
|
doc_comment_code_block_width = 80
|
||||||
|
edition = "2024" # Keep in sync with Cargo.toml.
|
||||||
|
enum_discrim_align_threshold = 60
|
||||||
|
force_explicit_abi = false
|
||||||
|
force_multiline_blocks = true
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
format_macro_matchers = true
|
||||||
|
format_strings = true
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
hex_literal_case = "Upper"
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
imports_layout = "Vertical"
|
||||||
|
inline_attribute_width = 60
|
||||||
|
match_block_trailing_comma = true
|
||||||
|
max_width = 80
|
||||||
|
newline_style = "Unix"
|
||||||
|
normalize_comments = true
|
||||||
|
normalize_doc_attributes = true
|
||||||
|
overflow_delimited_expr = true
|
||||||
|
struct_field_align_threshold = 60
|
||||||
|
tab_spaces = 2
|
||||||
|
unstable_features = true
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_try_shorthand = true
|
||||||
|
wrap_comments = true
|
15
.taplo.toml
Normal file
15
.taplo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Taken from https://github.com/cull-os/carcass.
|
||||||
|
|
||||||
|
[formatting]
|
||||||
|
align_entries = true
|
||||||
|
column_width = 100
|
||||||
|
compact_arrays = false
|
||||||
|
reorder_inline_tables = true
|
||||||
|
reorder_keys = true
|
||||||
|
|
||||||
|
[[rule]]
|
||||||
|
include = [ "**/Cargo.toml" ]
|
||||||
|
keys = [ "package" ]
|
||||||
|
|
||||||
|
[rule.formatting]
|
||||||
|
reorder_keys = false
|
483
Cargo.lock
generated
483
Cargo.lock
generated
|
@ -62,27 +62,10 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "anyhow"
|
||||||
version = "0.2.14"
|
version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
dependencies = [
|
|
||||||
"hermit-abi",
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "1.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
|
@ -90,18 +73,6 @@ version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cast"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.21"
|
version = "1.2.21"
|
||||||
|
@ -111,23 +82,6 @@ dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "2.34.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"textwrap",
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.37"
|
version = "4.5.37"
|
||||||
|
@ -138,6 +92,16 @@ dependencies = [
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap-verbosity-flag"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2678fade3b77aa3a8ff3aae87e9c008d3fb00473a41c71fbf74e91c8c7b37e84"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.37"
|
version = "4.5.37"
|
||||||
|
@ -175,85 +139,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion"
|
name = "convert_case"
|
||||||
version = "0.3.6"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
|
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"unicode-segmentation",
|
||||||
"cast",
|
|
||||||
"clap 2.34.0",
|
|
||||||
"criterion-plot",
|
|
||||||
"csv",
|
|
||||||
"itertools",
|
|
||||||
"lazy_static",
|
|
||||||
"num-traits",
|
|
||||||
"oorandom",
|
|
||||||
"plotters",
|
|
||||||
"rayon",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_cbor",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"tinytemplate",
|
|
||||||
"walkdir",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion-plot"
|
name = "derive_more"
|
||||||
version = "0.4.5"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
|
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cast",
|
"derive_more-impl",
|
||||||
"itertools",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "derive_more-impl"
|
||||||
version = "0.8.6"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-epoch",
|
"convert_case",
|
||||||
"crossbeam-utils",
|
"proc-macro2",
|
||||||
]
|
"quote",
|
||||||
|
"syn",
|
||||||
[[package]]
|
"unicode-xid",
|
||||||
name = "crossbeam-epoch"
|
|
||||||
version = "0.9.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-utils"
|
|
||||||
version = "0.8.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "csv"
|
|
||||||
version = "1.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
|
||||||
dependencies = [
|
|
||||||
"csv-core",
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "csv-core"
|
|
||||||
version = "0.1.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -266,15 +179,18 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
name = "dix"
|
name = "dix"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 4.5.37",
|
"anyhow",
|
||||||
"criterion",
|
"clap",
|
||||||
|
"clap-verbosity-flag",
|
||||||
|
"derive_more",
|
||||||
"diff",
|
"diff",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"libc",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"thiserror",
|
"size",
|
||||||
|
"unicode-width",
|
||||||
"yansi",
|
"yansi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -325,12 +241,6 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "half"
|
|
||||||
version = "1.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.3"
|
version = "0.15.3"
|
||||||
|
@ -357,11 +267,19 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -372,19 +290,13 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff"
|
name = "jiff"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
|
@ -409,22 +321,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "js-sys"
|
|
||||||
version = "0.3.77"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.172"
|
version = "0.2.172"
|
||||||
|
@ -454,61 +350,18 @@ version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "oorandom"
|
|
||||||
version = "11.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
"plotters-backend",
|
|
||||||
"plotters-svg",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters-backend"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters-svg"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
|
||||||
dependencies = [
|
|
||||||
"plotters-backend",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
@ -542,26 +395,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rayon"
|
|
||||||
version = "1.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"rayon-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rayon-core"
|
|
||||||
version = "1.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-deque",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
|
@ -597,7 +430,7 @@ version = "0.35.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b"
|
checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"fallible-streaming-iterator",
|
"fallible-streaming-iterator",
|
||||||
"hashlink",
|
"hashlink",
|
||||||
|
@ -605,27 +438,6 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
|
@ -635,16 +447,6 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_cbor"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
|
|
||||||
dependencies = [
|
|
||||||
"half",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
|
@ -656,24 +458,18 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.140"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "size"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
@ -697,45 +493,6 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "textwrap"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "2.0.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "2.0.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinytemplate"
|
|
||||||
version = "1.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
@ -743,10 +500,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-segmentation"
|
||||||
version = "0.1.14"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
|
@ -760,115 +529,6 @@ version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[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 = "wasm-bindgen"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"rustversion",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-backend"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"log",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-backend",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "web-sys"
|
|
||||||
version = "0.3.77"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu",
|
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-util"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
@ -947,3 +607,6 @@ name = "yansi"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
dependencies = [
|
||||||
|
"is-terminal",
|
||||||
|
]
|
||||||
|
|
132
Cargo.toml
132
Cargo.toml
|
@ -1,39 +1,105 @@
|
||||||
[package]
|
[package]
|
||||||
name = "dix"
|
name = "dix"
|
||||||
version = "0.1.0"
|
description = "Diff Nix"
|
||||||
edition = "2024"
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
[[bin]]
|
|
||||||
name = "dix"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "dixlib"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.37", features = ["derive"] }
|
anyhow = "1.0.98"
|
||||||
regex = "1.11.1"
|
clap = { version = "4.5.37", features = [ "derive" ] }
|
||||||
yansi = "1.0.1"
|
clap-verbosity-flag = "3.0.2"
|
||||||
thiserror = "2.0.12"
|
derive_more = { version = "2.0.1", features = [ "full" ] }
|
||||||
log = "0.4.20"
|
diff = "0.1.13"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
rusqlite = { version = "0.35.0", features = ["bundled"] }
|
itertools = "0.14.0"
|
||||||
diff = "0.1.13"
|
log = "0.4.20"
|
||||||
|
regex = "1.11.1"
|
||||||
|
rusqlite = { version = "0.35.0", features = [ "bundled" ] }
|
||||||
|
size = "0.5.0"
|
||||||
|
unicode-width = "0.2.0"
|
||||||
|
yansi = { version = "1.0.1", features = [ "detect-env", "detect-tty" ] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[lints.clippy]
|
||||||
criterion = "0.3"
|
pedantic = { level = "warn", priority = -1 }
|
||||||
libc = "0.2"
|
|
||||||
|
|
||||||
[[bench]]
|
blanket_clippy_restriction_lints = "allow"
|
||||||
name = "store"
|
restriction = { level = "warn", priority = -1 }
|
||||||
harness=false
|
|
||||||
|
|
||||||
[[bench]]
|
alloc_instead_of_core = "allow"
|
||||||
name = "print"
|
allow_attributes_without_reason = "allow"
|
||||||
harness=false
|
arbitrary_source_item_ordering = "allow"
|
||||||
|
arithmetic_side_effects = "allow"
|
||||||
[[bench]]
|
as_conversions = "allow"
|
||||||
name = "util"
|
as_pointer_underscore = "allow"
|
||||||
harness=false
|
as_underscore = "allow"
|
||||||
|
big_endian_bytes = "allow"
|
||||||
|
clone_on_ref_ptr = "allow"
|
||||||
|
dbg_macro = "allow"
|
||||||
|
disallowed_script_idents = "allow"
|
||||||
|
else_if_without_else = "allow"
|
||||||
|
error_impl_error = "allow"
|
||||||
|
exhaustive_enums = "allow"
|
||||||
|
exhaustive_structs = "allow"
|
||||||
|
expect_used = "allow"
|
||||||
|
field_scoped_visibility_modifiers = "allow"
|
||||||
|
float_arithmetic = "allow"
|
||||||
|
host_endian_bytes = "allow"
|
||||||
|
impl_trait_in_params = "allow"
|
||||||
|
implicit_return = "allow"
|
||||||
|
indexing_slicing = "allow"
|
||||||
|
inline_asm_x86_intel_syntax = "allow"
|
||||||
|
integer_division = "allow"
|
||||||
|
integer_division_remainder_used = "allow"
|
||||||
|
large_include_file = "allow"
|
||||||
|
let_underscore_must_use = "allow"
|
||||||
|
let_underscore_untyped = "allow"
|
||||||
|
little_endian_bytes = "allow"
|
||||||
|
map_err_ignore = "allow"
|
||||||
|
match_same_arms = "allow"
|
||||||
|
missing_assert_message = "allow"
|
||||||
|
missing_docs_in_private_items = "allow"
|
||||||
|
missing_errors_doc = "allow"
|
||||||
|
missing_inline_in_public_items = "allow"
|
||||||
|
missing_panics_doc = "allow"
|
||||||
|
missing_trait_methods = "allow"
|
||||||
|
mod_module_files = "allow"
|
||||||
|
multiple_inherent_impl = "allow"
|
||||||
|
mutex_atomic = "allow"
|
||||||
|
mutex_integer = "allow"
|
||||||
|
new_without_default = "allow"
|
||||||
|
non_ascii_literal = "allow"
|
||||||
|
panic = "allow"
|
||||||
|
panic_in_result_fn = "allow"
|
||||||
|
partial_pub_fields = "allow"
|
||||||
|
print_stderr = "allow"
|
||||||
|
print_stdout = "allow"
|
||||||
|
pub_use = "allow"
|
||||||
|
pub_with_shorthand = "allow"
|
||||||
|
pub_without_shorthand = "allow"
|
||||||
|
question_mark_used = "allow"
|
||||||
|
ref_patterns = "allow"
|
||||||
|
renamed_function_params = "allow"
|
||||||
|
same_name_method = "allow"
|
||||||
|
semicolon_outside_block = "allow"
|
||||||
|
separated_literal_suffix = "allow"
|
||||||
|
shadow_reuse = "allow"
|
||||||
|
shadow_same = "allow"
|
||||||
|
shadow_unrelated = "allow"
|
||||||
|
single_call_fn = "allow"
|
||||||
|
single_char_lifetime_names = "allow"
|
||||||
|
single_match_else = "allow"
|
||||||
|
std_instead_of_alloc = "allow"
|
||||||
|
std_instead_of_core = "allow"
|
||||||
|
string_add = "allow"
|
||||||
|
string_slice = "allow"
|
||||||
|
todo = "allow"
|
||||||
|
too_many_lines = "allow"
|
||||||
|
try_err = "allow"
|
||||||
|
unimplemented = "allow"
|
||||||
|
unnecessary_safety_comment = "allow"
|
||||||
|
unnecessary_safety_doc = "allow"
|
||||||
|
unreachable = "allow"
|
||||||
|
unwrap_in_result = "allow"
|
||||||
|
unwrap_used = "allow"
|
||||||
|
use_debug = "allow"
|
||||||
|
wildcard_enum_match_arm = "allow"
|
||||||
|
|
674
LICENSE
674
LICENSE
|
@ -1,674 +0,0 @@
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
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.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
<program> Copyright (C) <year> <name of author>
|
|
||||||
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
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
611
LICENSE.md
Normal file
611
LICENSE.md
Normal file
|
@ -0,0 +1,611 @@
|
||||||
|
# GNU GENERAL PUBLIC LICENSE
|
||||||
|
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
31
README.md
31
README.md
|
@ -1,13 +1,34 @@
|
||||||
# dix
|
# Diff Nix
|
||||||
|
|
||||||
## diff Nix stuff
|
A tool to diff any Nix related thing.
|
||||||
|
|
||||||
Currently only system closures
|
Currently only supports closures (a derivation graph, such as a system build or
|
||||||
|
package).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
`dix /nix/var/profiles/system-<version>-link /run/current-system`
|
`dix /nix/var/profiles/system-<version>-link /run/current-system`
|
||||||
|
|
||||||
# Why dix?
|
## Output
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
```
|
||||||
|
Dix: Diff Nix
|
||||||
|
Copyright (C) 2025-present bloxx12
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
```
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
use std::{
|
|
||||||
env,
|
|
||||||
fs::{self, DirEntry},
|
|
||||||
path::PathBuf,
|
|
||||||
sync::OnceLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
use dixlib::{store, util::PackageDiff};
|
|
||||||
|
|
||||||
/// tries to get the path of the oldest nixos system derivation
|
|
||||||
/// this function is pretty hacky and only used so that
|
|
||||||
/// you don't have to specify a specific derivation to
|
|
||||||
/// run the benchmarks
|
|
||||||
fn get_oldest_nixos_system() -> Option<PathBuf> {
|
|
||||||
let profile_dir = fs::read_dir("/nix/var/nix/profiles").ok()?;
|
|
||||||
|
|
||||||
let files = profile_dir.filter_map(Result::ok).filter_map(|entry| {
|
|
||||||
entry
|
|
||||||
.file_type()
|
|
||||||
.ok()
|
|
||||||
.and_then(|f| f.is_symlink().then_some(entry.path()))
|
|
||||||
});
|
|
||||||
|
|
||||||
files.min_by_key(|path| {
|
|
||||||
// extract all digits from the file name and use that as key
|
|
||||||
let p = path.as_os_str().to_str().unwrap_or_default();
|
|
||||||
let digits: String = p.chars().filter(|c| c.is_ascii_digit()).collect();
|
|
||||||
// if we are not able to produce a key (e.g. because the path does not contain digits)
|
|
||||||
// we put it last
|
|
||||||
digits.parse::<u32>().unwrap_or(u32::MAX)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_deriv_query() -> &'static PathBuf {
|
|
||||||
static _QUERY_DERIV: OnceLock<PathBuf> = OnceLock::new();
|
|
||||||
_QUERY_DERIV.get_or_init(|| {
|
|
||||||
let path = PathBuf::from(
|
|
||||||
env::var("DIX_BENCH_NEW_SYSTEM")
|
|
||||||
.unwrap_or_else(|_| "/run/current-system/system".into()),
|
|
||||||
);
|
|
||||||
path
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn get_deriv_query_old() -> &'static PathBuf {
|
|
||||||
static _QUERY_DERIV: OnceLock<PathBuf> = OnceLock::new();
|
|
||||||
_QUERY_DERIV.get_or_init(|| {
|
|
||||||
let path = env::var("DIX_BENCH_OLD_SYSTEM")
|
|
||||||
.ok()
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.or(get_oldest_nixos_system())
|
|
||||||
.unwrap_or_else(|| PathBuf::from("/run/current-system/system"));
|
|
||||||
path
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_packages() -> &'static (Vec<String>, Vec<String>) {
|
|
||||||
static _PKGS: OnceLock<(Vec<String>, Vec<String>)> = OnceLock::new();
|
|
||||||
_PKGS.get_or_init(|| {
|
|
||||||
let pkgs_before = store::get_packages(std::path::Path::new(get_deriv_query_old()))
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|(_, name)| name)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
let pkgs_after = store::get_packages(std::path::Path::new(get_deriv_query()))
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|(_, name)| name)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
(pkgs_before, pkgs_after)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_pkg_diff() -> &'static PackageDiff<'static> {
|
|
||||||
static _PKG_DIFF: OnceLock<PackageDiff> = OnceLock::new();
|
|
||||||
_PKG_DIFF.get_or_init(|| {
|
|
||||||
let (pkgs_before, pkgs_after) = get_packages();
|
|
||||||
PackageDiff::new(pkgs_before, pkgs_after)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// prints the old and new NixOs system used for benchmarking
|
|
||||||
///
|
|
||||||
/// is used to give information about the old and new system
|
|
||||||
pub fn print_used_nixos_systems() {
|
|
||||||
let old = get_deriv_query_old();
|
|
||||||
let new = get_deriv_query();
|
|
||||||
println!("old system used {:?}", old);
|
|
||||||
println!("new system used {:?}", new);
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
mod common;
|
|
||||||
|
|
||||||
use std::{fs::File, os::fd::AsRawFd};
|
|
||||||
|
|
||||||
use common::{get_pkg_diff, print_used_nixos_systems};
|
|
||||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
|
||||||
use dixlib::print;
|
|
||||||
|
|
||||||
/// reroutes stdout and stderr to the null device before
|
|
||||||
/// executing `f`
|
|
||||||
fn suppress_output<F: FnOnce()>(f: F) {
|
|
||||||
let stdout = std::io::stdout();
|
|
||||||
let stderr = std::io::stderr();
|
|
||||||
|
|
||||||
// Save original FDs
|
|
||||||
let orig_stdout_fd = stdout.as_raw_fd();
|
|
||||||
let orig_stderr_fd = stderr.as_raw_fd();
|
|
||||||
|
|
||||||
// Open /dev/null and get its FD
|
|
||||||
let devnull = File::create("/dev/null").unwrap();
|
|
||||||
let null_fd = devnull.as_raw_fd();
|
|
||||||
|
|
||||||
// Redirect stdout and stderr to /dev/null
|
|
||||||
let _ = unsafe { libc::dup2(null_fd, orig_stdout_fd) };
|
|
||||||
let _ = unsafe { libc::dup2(null_fd, orig_stderr_fd) };
|
|
||||||
|
|
||||||
f();
|
|
||||||
|
|
||||||
let _ = unsafe { libc::dup2(orig_stdout_fd, 1) };
|
|
||||||
let _ = unsafe { libc::dup2(orig_stderr_fd, 2) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bench_print_added(c: &mut Criterion) {
|
|
||||||
print_used_nixos_systems();
|
|
||||||
let diff = get_pkg_diff();
|
|
||||||
c.bench_function("print_added", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
suppress_output(|| {
|
|
||||||
print::print_added(
|
|
||||||
black_box(&diff.added),
|
|
||||||
black_box(&diff.pkg_to_versions_post),
|
|
||||||
30,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub fn bench_print_removed(c: &mut Criterion) {
|
|
||||||
print_used_nixos_systems();
|
|
||||||
let diff = get_pkg_diff();
|
|
||||||
c.bench_function("print_removed", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
suppress_output(|| {
|
|
||||||
print::print_removed(
|
|
||||||
black_box(&diff.removed),
|
|
||||||
black_box(&diff.pkg_to_versions_pre),
|
|
||||||
30,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub fn bench_print_changed(c: &mut Criterion) {
|
|
||||||
print_used_nixos_systems();
|
|
||||||
let diff = get_pkg_diff();
|
|
||||||
c.bench_function("print_changed", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
suppress_output(|| {
|
|
||||||
print::print_changes(
|
|
||||||
black_box(&diff.changed),
|
|
||||||
black_box(&diff.pkg_to_versions_pre),
|
|
||||||
black_box(&diff.pkg_to_versions_post),
|
|
||||||
30,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(
|
|
||||||
benches,
|
|
||||||
bench_print_added,
|
|
||||||
bench_print_removed,
|
|
||||||
bench_print_changed
|
|
||||||
);
|
|
||||||
criterion_main!(benches);
|
|
|
@ -1,36 +0,0 @@
|
||||||
mod common;
|
|
||||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
|
||||||
use dixlib::store;
|
|
||||||
|
|
||||||
// basic benchmarks using the current system
|
|
||||||
//
|
|
||||||
// problem: this is not reproducible at all
|
|
||||||
// since this is very depending on the current
|
|
||||||
// system and the nature of the system in general
|
|
||||||
//
|
|
||||||
// we might want to think about using a copy of the sqlite
|
|
||||||
// db to benchmark instead to make the results comparable
|
|
||||||
|
|
||||||
pub fn bench_get_packages(c: &mut Criterion) {
|
|
||||||
c.bench_function("get_packages", |b| {
|
|
||||||
b.iter(|| store::get_packages(black_box(common::get_deriv_query())));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub fn bench_get_closure_size(c: &mut Criterion) {
|
|
||||||
c.bench_function("get_closure_size", |b| {
|
|
||||||
b.iter(|| store::get_closure_size(black_box(common::get_deriv_query())));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub fn bench_get_dependency_graph(c: &mut Criterion) {
|
|
||||||
c.bench_function("get_dependency_graph", |b| {
|
|
||||||
b.iter(|| store::get_dependency_graph(black_box(common::get_deriv_query())));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(
|
|
||||||
benches,
|
|
||||||
bench_get_packages,
|
|
||||||
bench_get_closure_size,
|
|
||||||
bench_get_dependency_graph
|
|
||||||
);
|
|
||||||
criterion_main!(benches);
|
|
|
@ -1,15 +0,0 @@
|
||||||
mod common;
|
|
||||||
|
|
||||||
use common::get_packages;
|
|
||||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
|
||||||
use dixlib::util::PackageDiff;
|
|
||||||
|
|
||||||
pub fn bench_package_diff(c: &mut Criterion) {
|
|
||||||
let (pkgs_before, pkgs_after) = get_packages();
|
|
||||||
c.bench_function("PackageDiff::new", |b| {
|
|
||||||
b.iter(|| PackageDiff::new(black_box(pkgs_before), black_box(pkgs_after)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, bench_package_diff);
|
|
||||||
criterion_main!(benches);
|
|
31
flake.lock
generated
31
flake.lock
generated
|
@ -19,21 +19,42 @@
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay",
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1746758179,
|
||||||
|
"narHash": "sha256-JECUw1YBEsTsVauvupRzE5ykZaJoyhHCpoY87ZZJGas=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "4fd00513eac6b6140c5dced3e1b8133e2369a0f8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1689347949,
|
"lastModified": 1681028828,
|
||||||
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
"owner": "nix-systems",
|
"owner": "nix-systems",
|
||||||
"repo": "default-linux",
|
"repo": "default",
|
||||||
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-systems",
|
"owner": "nix-systems",
|
||||||
"repo": "default-linux",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
flake.nix
30
flake.nix
|
@ -1,8 +1,13 @@
|
||||||
{
|
{
|
||||||
description = "Nix version differ";
|
description = "Dix - Diff Nix";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
systems.url = "github:nix-systems/default-linux";
|
systems.url = "github:nix-systems/default";
|
||||||
|
rust-overlay = {
|
||||||
|
url = "github:oxalica/rust-overlay";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs: let
|
outputs = inputs: let
|
||||||
|
@ -10,8 +15,18 @@
|
||||||
pkgsFor = inputs.nixpkgs.legacyPackages;
|
pkgsFor = inputs.nixpkgs.legacyPackages;
|
||||||
in {
|
in {
|
||||||
packages = eachSystem (system: {
|
packages = eachSystem (system: {
|
||||||
default = inputs.self.packages.${system}.ralc;
|
default = inputs.self.packages.${system}.dix;
|
||||||
ralc = pkgsFor.${system}.callPackage ./nix/package.nix {};
|
dix = pkgsFor.${system}.callPackage ./nix/package.nix {};
|
||||||
|
});
|
||||||
|
|
||||||
|
apps = eachSystem (system: let
|
||||||
|
inherit (inputs.self.packages.${system}) dix;
|
||||||
|
in {
|
||||||
|
default = inputs.self.apps.${system}.dix;
|
||||||
|
dix = {
|
||||||
|
type = "app";
|
||||||
|
program = "${dix}/bin/dix";
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
devShells = eachSystem (system: {
|
devShells = eachSystem (system: {
|
||||||
|
@ -21,13 +36,18 @@
|
||||||
(pkgsFor.${system})
|
(pkgsFor.${system})
|
||||||
cargo
|
cargo
|
||||||
rustc
|
rustc
|
||||||
rustfmt
|
|
||||||
bacon
|
bacon
|
||||||
;
|
;
|
||||||
inherit
|
inherit
|
||||||
(pkgsFor.${system}.rustPackages)
|
(pkgsFor.${system}.rustPackages)
|
||||||
clippy
|
clippy
|
||||||
;
|
;
|
||||||
|
|
||||||
|
inherit
|
||||||
|
((pkgsFor.${system}.extend
|
||||||
|
inputs.rust-overlay.overlays.default).rust-bin.nightly.latest)
|
||||||
|
rustfmt
|
||||||
|
;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
BIN
images/dix.png
Normal file
BIN
images/dix.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 398 KiB |
18
nix/package.nix
Normal file
18
nix/package.nix
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
rustPlatform,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
toml = (lib.importTOML ../Cargo.toml).package;
|
||||||
|
pname = toml.name;
|
||||||
|
inherit (toml) version;
|
||||||
|
in
|
||||||
|
rustPlatform.buildRustPackage {
|
||||||
|
inherit pname version;
|
||||||
|
src = builtins.path {
|
||||||
|
name = "${pname}-${version}";
|
||||||
|
path = lib.sources.cleanSource ../.;
|
||||||
|
};
|
||||||
|
cargoLock.lockFile = ../Cargo.lock;
|
||||||
|
doCheck = true;
|
||||||
|
}
|
437
src/diff.rs
Normal file
437
src/diff.rs
Normal file
|
@ -0,0 +1,437 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt::{
|
||||||
|
self,
|
||||||
|
Write as _,
|
||||||
|
},
|
||||||
|
path::{
|
||||||
|
Path,
|
||||||
|
PathBuf,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{
|
||||||
|
Context as _,
|
||||||
|
Error,
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
use itertools::{
|
||||||
|
EitherOrBoth,
|
||||||
|
Itertools,
|
||||||
|
};
|
||||||
|
use size::Size;
|
||||||
|
use unicode_width::UnicodeWidthStr as _;
|
||||||
|
use yansi::Paint as _;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
StorePath,
|
||||||
|
Version,
|
||||||
|
store,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct Diff<T> {
|
||||||
|
old: T,
|
||||||
|
new: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum DiffStatus {
|
||||||
|
Changed,
|
||||||
|
Added,
|
||||||
|
Removed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffStatus {
|
||||||
|
fn char(self) -> impl fmt::Display {
|
||||||
|
match self {
|
||||||
|
Self::Added => "A".green(),
|
||||||
|
Self::Removed => "R".red(),
|
||||||
|
Self::Changed => "C".yellow(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the diff header (<<< out, >>>in) and package diff.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Will return the amount of package diffs written. Even when zero,
|
||||||
|
/// the header will be written.
|
||||||
|
pub fn write_paths_diffln(
|
||||||
|
writer: &mut impl fmt::Write,
|
||||||
|
path_old: &Path,
|
||||||
|
path_new: &Path,
|
||||||
|
) -> Result<usize> {
|
||||||
|
let mut connection = store::connect()?;
|
||||||
|
|
||||||
|
let paths_old = connection.query_dependents(path_old).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to query dependencies of path '{path}'",
|
||||||
|
path = path_old.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"found {count} packages in old closure",
|
||||||
|
count = paths_old.len(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let paths_new = connection.query_dependents(path_new).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to query dependencies of path '{path}'",
|
||||||
|
path = path_new.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
log::info!(
|
||||||
|
"found {count} packages in new closure",
|
||||||
|
count = paths_new.len(),
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(connection);
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"{arrows} {old}",
|
||||||
|
arrows = "<<<".bold(),
|
||||||
|
old = path_old.display(),
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"{arrows} {new}",
|
||||||
|
arrows = ">>>".bold(),
|
||||||
|
new = path_new.display(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
writeln!(writer)?;
|
||||||
|
|
||||||
|
#[expect(clippy::pattern_type_mismatch)]
|
||||||
|
Ok(write_packages_diffln(
|
||||||
|
writer,
|
||||||
|
paths_old.iter().map(|(_, path)| path),
|
||||||
|
paths_new.iter().map(|(_, path)| path),
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a list of versions which may contain duplicates and deduplicates it by
|
||||||
|
/// replacing multiple occurrences of an element with the same element plus the
|
||||||
|
/// amount it occurs.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rs
|
||||||
|
/// let mut versions = vec!["2.3", "1.0", "2.3", "4.8", "2.3", "1.0"];
|
||||||
|
///
|
||||||
|
/// deduplicate_versions(&mut versions);
|
||||||
|
/// assert_eq!(*versions, &["1.0 ×2", "2.3 ×3", "4.8"]);
|
||||||
|
/// ```
|
||||||
|
fn deduplicate_versions(versions: &mut Vec<Version>) {
|
||||||
|
versions.sort_unstable();
|
||||||
|
|
||||||
|
let mut deduplicated = Vec::new();
|
||||||
|
|
||||||
|
// Push a version onto the final vec. If it occurs more than once,
|
||||||
|
// we add a ×{count} to signify the amount of times it occurs.
|
||||||
|
let mut deduplicated_push = |mut version: Version, count: usize| {
|
||||||
|
if count > 1 {
|
||||||
|
write!(version, " ×{count}").unwrap();
|
||||||
|
}
|
||||||
|
deduplicated.push(version);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut last_version = None::<(Version, usize)>;
|
||||||
|
for version in versions.iter() {
|
||||||
|
#[expect(clippy::mixed_read_write_in_expression)]
|
||||||
|
let Some((last_version_value, count)) = last_version.take() else {
|
||||||
|
last_version = Some((version.clone(), 1));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the last version matches the current version, we increase the count by
|
||||||
|
// one. Otherwise, we push the last version to the result.
|
||||||
|
if last_version_value == *version {
|
||||||
|
last_version = Some((last_version_value, count + 1));
|
||||||
|
} else {
|
||||||
|
deduplicated_push(last_version_value, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the final element, if it exists.
|
||||||
|
if let Some((version, count)) = last_version.take() {
|
||||||
|
deduplicated_push(version, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
*versions = deduplicated;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::cognitive_complexity, clippy::too_many_lines)]
|
||||||
|
fn write_packages_diffln<'a>(
|
||||||
|
writer: &mut impl fmt::Write,
|
||||||
|
paths_old: impl Iterator<Item = &'a StorePath>,
|
||||||
|
paths_new: impl Iterator<Item = &'a StorePath>,
|
||||||
|
) -> Result<usize, fmt::Error> {
|
||||||
|
let mut paths = HashMap::<&str, Diff<Vec<Version>>>::new();
|
||||||
|
|
||||||
|
for path in paths_old {
|
||||||
|
match path.parse_name_and_version() {
|
||||||
|
Ok((name, version)) => {
|
||||||
|
log::debug!("parsed name: {name}");
|
||||||
|
log::debug!("parsed version: {version:?}");
|
||||||
|
|
||||||
|
paths
|
||||||
|
.entry(name)
|
||||||
|
.or_default()
|
||||||
|
.old
|
||||||
|
.push(version.unwrap_or_else(|| Version::from("<none>".to_owned())));
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("error parsing old path name and version: {error}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for path in paths_new {
|
||||||
|
match path.parse_name_and_version() {
|
||||||
|
Ok((name, version)) => {
|
||||||
|
log::debug!("parsed name: {name}");
|
||||||
|
log::debug!("parsed version: {version:?}");
|
||||||
|
|
||||||
|
paths
|
||||||
|
.entry(name)
|
||||||
|
.or_default()
|
||||||
|
.new
|
||||||
|
.push(version.unwrap_or_else(|| Version::from("<none>".to_owned())));
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("error parsing new path name and version: {error}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut diffs = paths
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(name, mut versions)| {
|
||||||
|
deduplicate_versions(&mut versions.old);
|
||||||
|
deduplicate_versions(&mut versions.new);
|
||||||
|
|
||||||
|
let status = match (versions.old.len(), versions.new.len()) {
|
||||||
|
(0, 0) => unreachable!(),
|
||||||
|
(0, _) => DiffStatus::Added,
|
||||||
|
(_, 0) => DiffStatus::Removed,
|
||||||
|
(..) if versions.old != versions.new => DiffStatus::Changed,
|
||||||
|
(..) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((name, versions, status))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
diffs.sort_by(|&(a_name, _, a_status), &(b_name, _, b_status)| {
|
||||||
|
a_status.cmp(&b_status).then_with(|| a_name.cmp(b_name))
|
||||||
|
});
|
||||||
|
|
||||||
|
let name_width = diffs
|
||||||
|
.iter()
|
||||||
|
.map(|&(name, ..)| name.width())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
let mut last_status = None::<DiffStatus>;
|
||||||
|
|
||||||
|
for &(name, ref versions, status) in &diffs {
|
||||||
|
if last_status != Some(status) {
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"{nl}{status}",
|
||||||
|
nl = if last_status.is_some() { "\n" } else { "" },
|
||||||
|
status = match status {
|
||||||
|
DiffStatus::Added => "ADDED",
|
||||||
|
DiffStatus::Removed => "REMOVED",
|
||||||
|
DiffStatus::Changed => "CHANGED",
|
||||||
|
}
|
||||||
|
.bold(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
last_status = Some(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
writer,
|
||||||
|
"[{status}] {name:<name_width$}",
|
||||||
|
status = status.char()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut oldacc = String::new();
|
||||||
|
let mut oldwrote = false;
|
||||||
|
let mut newacc = String::new();
|
||||||
|
let mut newwrote = false;
|
||||||
|
|
||||||
|
for diff in Itertools::zip_longest(versions.old.iter(), versions.new.iter())
|
||||||
|
{
|
||||||
|
match diff {
|
||||||
|
EitherOrBoth::Left(old_version) => {
|
||||||
|
if oldwrote {
|
||||||
|
write!(oldacc, ", ")?;
|
||||||
|
} else {
|
||||||
|
write!(oldacc, " ")?;
|
||||||
|
oldwrote = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for old_comp in old_version {
|
||||||
|
match old_comp {
|
||||||
|
Ok(old_comp) => write!(oldacc, "{old}", old = old_comp.red())?,
|
||||||
|
Err(ignored) => write!(oldacc, "{ignored}")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
EitherOrBoth::Right(new_version) => {
|
||||||
|
if newwrote {
|
||||||
|
write!(newacc, ", ")?;
|
||||||
|
} else {
|
||||||
|
write!(newacc, " ")?;
|
||||||
|
newwrote = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for new_comp in new_version {
|
||||||
|
match new_comp {
|
||||||
|
Ok(new_comp) => write!(newacc, "{new}", new = new_comp.green())?,
|
||||||
|
Err(ignored) => write!(newacc, "{ignored}")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
EitherOrBoth::Both(old_version, new_version) => {
|
||||||
|
if old_version == new_version {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldwrote {
|
||||||
|
write!(oldacc, ", ")?;
|
||||||
|
} else {
|
||||||
|
write!(oldacc, " ")?;
|
||||||
|
oldwrote = true;
|
||||||
|
}
|
||||||
|
if newwrote {
|
||||||
|
write!(newacc, ", ")?;
|
||||||
|
} else {
|
||||||
|
write!(newacc, " ")?;
|
||||||
|
newwrote = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for diff in Itertools::zip_longest(
|
||||||
|
old_version.into_iter(),
|
||||||
|
new_version.into_iter(),
|
||||||
|
) {
|
||||||
|
match diff {
|
||||||
|
EitherOrBoth::Left(old_comp) => {
|
||||||
|
match old_comp {
|
||||||
|
Ok(old_comp) => {
|
||||||
|
write!(oldacc, "{old}", old = old_comp.red())?;
|
||||||
|
},
|
||||||
|
Err(ignored) => {
|
||||||
|
write!(oldacc, "{ignored}")?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
EitherOrBoth::Right(new_comp) => {
|
||||||
|
match new_comp {
|
||||||
|
Ok(new_comp) => {
|
||||||
|
write!(newacc, "{new}", new = new_comp.green())?;
|
||||||
|
},
|
||||||
|
Err(ignored) => {
|
||||||
|
write!(newacc, "{ignored}")?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
EitherOrBoth::Both(old_comp, new_comp) => {
|
||||||
|
if let Err(ignored) = old_comp {
|
||||||
|
write!(oldacc, "{ignored}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(ignored) = new_comp {
|
||||||
|
write!(newacc, "{ignored}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Ok(old_comp), Ok(new_comp)) = (old_comp, new_comp) {
|
||||||
|
if old_comp == new_comp {
|
||||||
|
write!(oldacc, "{old}", old = old_comp.yellow())?;
|
||||||
|
write!(newacc, "{new}", new = new_comp.yellow())?;
|
||||||
|
} else {
|
||||||
|
write!(oldacc, "{old}", old = old_comp.red())?;
|
||||||
|
write!(newacc, "{new}", new = new_comp.green())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
writer,
|
||||||
|
"{oldacc}{arrow}{newacc}",
|
||||||
|
arrow = if !oldacc.is_empty() && !newacc.is_empty() {
|
||||||
|
" ->"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
|
writeln!(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(diffs.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns a task to compute the data required by [`write_size_diffln`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn spawn_size_diff(
|
||||||
|
path_old: PathBuf,
|
||||||
|
path_new: PathBuf,
|
||||||
|
) -> thread::JoinHandle<Result<(Size, Size)>> {
|
||||||
|
log::debug!("calculating closure sizes in background");
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut connection = store::connect()?;
|
||||||
|
|
||||||
|
Ok::<_, Error>((
|
||||||
|
connection.query_closure_size(&path_old)?,
|
||||||
|
connection.query_closure_size(&path_new)?,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the size difference.
|
||||||
|
pub fn write_size_diffln(
|
||||||
|
writer: &mut impl fmt::Write,
|
||||||
|
size_old: Size,
|
||||||
|
size_new: Size,
|
||||||
|
) -> fmt::Result {
|
||||||
|
let size_diff = size_new - size_old;
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"{header}: {size_old} -> {size_new}",
|
||||||
|
header = "SIZE".bold(),
|
||||||
|
size_old = size_old.red(),
|
||||||
|
size_new = size_new.green(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"{header}: {size_diff}",
|
||||||
|
header = "DIFF".bold(),
|
||||||
|
size_diff = if size_diff.bytes() > 0 {
|
||||||
|
size_diff.green()
|
||||||
|
} else {
|
||||||
|
size_diff.red()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
123
src/error.rs
123
src/error.rs
|
@ -1,123 +0,0 @@
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Application errors with thiserror
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum AppError {
|
|
||||||
#[error("Command failed: {command} {args:?} - {message}")]
|
|
||||||
CommandFailed {
|
|
||||||
command: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Failed to decode command output from {context}: {source}")]
|
|
||||||
CommandOutputError {
|
|
||||||
source: std::str::Utf8Error,
|
|
||||||
context: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Failed to parse data in {context}: {message}")]
|
|
||||||
ParseError {
|
|
||||||
message: String,
|
|
||||||
context: String,
|
|
||||||
#[source]
|
|
||||||
source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Regex error in {context}: {source}")]
|
|
||||||
RegexError {
|
|
||||||
source: regex::Error,
|
|
||||||
context: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("IO error in {context}: {source}")]
|
|
||||||
IoError {
|
|
||||||
source: std::io::Error,
|
|
||||||
context: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Database error: {source}")]
|
|
||||||
DatabaseError { source: rusqlite::Error },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implement From traits to support the ? operator
|
|
||||||
impl From<std::io::Error> for AppError {
|
|
||||||
fn from(source: std::io::Error) -> Self {
|
|
||||||
Self::IoError {
|
|
||||||
source,
|
|
||||||
context: "unknown context".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::str::Utf8Error> for AppError {
|
|
||||||
fn from(source: std::str::Utf8Error) -> Self {
|
|
||||||
Self::CommandOutputError {
|
|
||||||
source,
|
|
||||||
context: "command output".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rusqlite::Error> for AppError {
|
|
||||||
fn from(source: rusqlite::Error) -> Self {
|
|
||||||
Self::DatabaseError { source }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<regex::Error> for AppError {
|
|
||||||
fn from(source: regex::Error) -> Self {
|
|
||||||
Self::RegexError {
|
|
||||||
source,
|
|
||||||
context: "regex operation".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppError {
|
|
||||||
/// Create a command failure error with context
|
|
||||||
pub fn command_failed<S: Into<String>>(command: S, args: &[&str], message: S) -> Self {
|
|
||||||
Self::CommandFailed {
|
|
||||||
command: command.into(),
|
|
||||||
args: args.iter().map(|&s| s.to_string()).collect(),
|
|
||||||
message: message.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a parse error with context
|
|
||||||
pub fn parse_error<S: Into<String>, C: Into<String>>(
|
|
||||||
message: S,
|
|
||||||
context: C,
|
|
||||||
source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
|
||||||
) -> Self {
|
|
||||||
Self::ParseError {
|
|
||||||
message: message.into(),
|
|
||||||
context: context.into(),
|
|
||||||
source,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an IO error with context
|
|
||||||
pub fn io_error<C: Into<String>>(source: std::io::Error, context: C) -> Self {
|
|
||||||
Self::IoError {
|
|
||||||
source,
|
|
||||||
context: context.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a regex error with context
|
|
||||||
pub fn regex_error<C: Into<String>>(source: regex::Error, context: C) -> Self {
|
|
||||||
Self::RegexError {
|
|
||||||
source,
|
|
||||||
context: context.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a command output error with context
|
|
||||||
pub fn command_output_error<C: Into<String>>(source: std::str::Utf8Error, context: C) -> Self {
|
|
||||||
Self::CommandOutputError {
|
|
||||||
source,
|
|
||||||
context: context.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
102
src/lib.rs
102
src/lib.rs
|
@ -1,4 +1,98 @@
|
||||||
pub mod error;
|
use std::{
|
||||||
pub mod print;
|
path::PathBuf,
|
||||||
pub mod store;
|
sync,
|
||||||
pub mod util;
|
};
|
||||||
|
|
||||||
|
use anyhow::{
|
||||||
|
Context as _,
|
||||||
|
Error,
|
||||||
|
Result,
|
||||||
|
anyhow,
|
||||||
|
bail,
|
||||||
|
};
|
||||||
|
use derive_more::Deref;
|
||||||
|
|
||||||
|
mod diff;
|
||||||
|
pub use diff::{
|
||||||
|
spawn_size_diff,
|
||||||
|
write_paths_diffln,
|
||||||
|
write_size_diffln,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod store;
|
||||||
|
|
||||||
|
mod version;
|
||||||
|
use version::Version;
|
||||||
|
|
||||||
|
#[derive(Deref, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
struct DerivationId(i64);
|
||||||
|
|
||||||
|
/// A validated store path. Always starts with /nix/store.
|
||||||
|
///
|
||||||
|
/// Can be created using `StorePath::try_from(path_buf)`.
|
||||||
|
#[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct StorePath(PathBuf);
|
||||||
|
|
||||||
|
impl TryFrom<PathBuf> for StorePath {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(path: PathBuf) -> Result<Self> {
|
||||||
|
if !path.starts_with("/nix/store") {
|
||||||
|
bail!(
|
||||||
|
"path {path} must start with /nix/store",
|
||||||
|
path = path.display(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StorePath(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorePath {
|
||||||
|
/// Parses a Nix store path to extract the packages name and possibly its
|
||||||
|
/// version.
|
||||||
|
///
|
||||||
|
/// This function first drops the inputs first 44 chars, since that is exactly
|
||||||
|
/// the length of the `/nix/store/0004yybkm5hnwjyxv129js3mjp7kbrax-` prefix.
|
||||||
|
/// Then it matches that against our store path regex.
|
||||||
|
fn parse_name_and_version(&self) -> Result<(&str, Option<Version>)> {
|
||||||
|
static STORE_PATH_REGEX: sync::LazyLock<regex::Regex> =
|
||||||
|
sync::LazyLock::new(|| {
|
||||||
|
regex::Regex::new("(.+?)(-([0-9].*?))?$")
|
||||||
|
.expect("failed to compile regex for Nix store paths")
|
||||||
|
});
|
||||||
|
|
||||||
|
let path = self.to_str().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to convert path '{path}' to valid unicode",
|
||||||
|
path = self.display(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// We can strip the path since it _always_ follows the format:
|
||||||
|
//
|
||||||
|
// /nix/store/0004yybkm5hnwjyxv129js3mjp7kbrax-...
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
// This part is exactly 44 chars long, so we just remove it.
|
||||||
|
assert_eq!(&path[..11], "/nix/store/");
|
||||||
|
assert_eq!(&path[43..44], "-");
|
||||||
|
let path = &path[44..];
|
||||||
|
|
||||||
|
log::debug!("stripped path: {path}");
|
||||||
|
|
||||||
|
let captures = STORE_PATH_REGEX.captures(path).ok_or_else(|| {
|
||||||
|
anyhow!("path '{path}' does not match expected Nix store format")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let name = captures.get(1).map_or("", |capture| capture.as_str());
|
||||||
|
if name.is_empty() {
|
||||||
|
bail!("failed to extract name from path '{path}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
let version: Option<Version> = captures.get(2).map(|capture| {
|
||||||
|
Version::from(capture.as_str().trim_start_matches('-').to_owned())
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok((name, version))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
307
src/main.rs
307
src/main.rs
|
@ -1,214 +1,133 @@
|
||||||
use clap::Parser;
|
|
||||||
use core::str;
|
|
||||||
use dixlib::print;
|
|
||||||
use dixlib::store;
|
|
||||||
use dixlib::util::PackageDiff;
|
|
||||||
use log::{debug, error};
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
fmt::{
|
||||||
thread,
|
self,
|
||||||
|
Write as _,
|
||||||
|
},
|
||||||
|
io::{
|
||||||
|
self,
|
||||||
|
Write as _,
|
||||||
|
},
|
||||||
|
path::PathBuf,
|
||||||
|
process,
|
||||||
};
|
};
|
||||||
use yansi::Paint;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
use anyhow::{
|
||||||
#[command(name = "dix")]
|
Result,
|
||||||
#[command(version = "1.0")]
|
anyhow,
|
||||||
#[command(about = "Diff Nix stuff", long_about = None)]
|
};
|
||||||
#[command(version, about, long_about = None)]
|
use clap::Parser as _;
|
||||||
struct Args {
|
use yansi::Paint as _;
|
||||||
path: std::path::PathBuf,
|
|
||||||
path2: std::path::PathBuf,
|
|
||||||
|
|
||||||
/// Print the whole store paths
|
struct WriteFmt<W: io::Write>(W);
|
||||||
#[arg(short, long)]
|
|
||||||
paths: bool,
|
|
||||||
|
|
||||||
/// Print the closure size
|
impl<W: io::Write> fmt::Write for WriteFmt<W> {
|
||||||
#[arg(long, short)]
|
fn write_str(&mut self, string: &str) -> fmt::Result {
|
||||||
closure_size: bool,
|
self.0.write_all(string.as_bytes()).map_err(|_| fmt::Error)
|
||||||
|
}
|
||||||
/// Verbosity level: -v for debug, -vv for trace
|
|
||||||
#[arg(short, long, action = clap::ArgAction::Count)]
|
|
||||||
verbose: u8,
|
|
||||||
|
|
||||||
/// Silence all output except errors
|
|
||||||
#[arg(short, long)]
|
|
||||||
quiet: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(clap::Parser, Debug)]
|
||||||
struct Package<'a> {
|
#[command(version, about)]
|
||||||
name: &'a str,
|
struct Cli {
|
||||||
versions: HashSet<&'a str>,
|
old_path: PathBuf,
|
||||||
/// Save if a package is a dependency of another package
|
new_path: PathBuf,
|
||||||
is_dep: bool,
|
|
||||||
|
#[command(flatten)]
|
||||||
|
verbose: clap_verbosity_flag::Verbosity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Package<'a> {
|
fn real_main() -> Result<()> {
|
||||||
fn new(name: &'a str, version: &'a str, is_dep: bool) -> Self {
|
let Cli {
|
||||||
let mut versions = HashSet::new();
|
old_path,
|
||||||
versions.insert(version);
|
new_path,
|
||||||
Self {
|
verbose,
|
||||||
name,
|
} = Cli::parse();
|
||||||
versions,
|
|
||||||
is_dep,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_version(&mut self, version: &'a str) {
|
yansi::whenever(yansi::Condition::TTY_AND_COLOR);
|
||||||
self.versions.insert(version);
|
|
||||||
}
|
env_logger::Builder::new()
|
||||||
|
.filter_level(verbose.log_level_filter())
|
||||||
|
.format(|out, arguments| {
|
||||||
|
let header = match arguments.level() {
|
||||||
|
log::Level::Error => "error:".red(),
|
||||||
|
log::Level::Warn => "warn:".yellow(),
|
||||||
|
log::Level::Info => "info:".green(),
|
||||||
|
log::Level::Debug => "debug:".blue(),
|
||||||
|
log::Level::Trace => "trace:".cyan(),
|
||||||
|
};
|
||||||
|
|
||||||
|
writeln!(out, "{header} {message}", message = arguments.args())
|
||||||
|
})
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let mut out = WriteFmt(io::stdout());
|
||||||
|
|
||||||
|
// Handle to the thread collecting closure size information.
|
||||||
|
// We do this as early as possible because Nix is slow.
|
||||||
|
let closure_size_handle =
|
||||||
|
dix::spawn_size_diff(old_path.clone(), new_path.clone());
|
||||||
|
|
||||||
|
let wrote = dix::write_paths_diffln(&mut out, &old_path, &new_path)?;
|
||||||
|
|
||||||
|
let (size_old, size_new) = closure_size_handle
|
||||||
|
.join()
|
||||||
|
.map_err(|_| anyhow!("failed to get closure size due to thread error"))??;
|
||||||
|
|
||||||
|
if wrote > 0 {
|
||||||
|
writeln!(out)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
dix::write_size_diffln(&mut out, size_old, size_new)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
|
#[allow(clippy::allow_attributes, clippy::exit)]
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let Err(error) = real_main() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// Configure logger based on verbosity flags and environment variables
|
let mut err = io::stderr();
|
||||||
// Respects RUST_LOG environment variable if present.
|
|
||||||
// XXX:We can also dedicate a specific env variable for this tool, if we want to.
|
let mut message = String::new();
|
||||||
let env = env_logger::Env::default().filter_or(
|
let mut chain = error.chain().rev().peekable();
|
||||||
"RUST_LOG",
|
|
||||||
if args.quiet {
|
while let Some(error) = chain.next() {
|
||||||
"error"
|
let _ = write!(
|
||||||
} else {
|
err,
|
||||||
match args.verbose {
|
"{header} ",
|
||||||
0 => "info",
|
header = if chain.peek().is_none() {
|
||||||
1 => "debug",
|
"error:"
|
||||||
_ => "trace",
|
} else {
|
||||||
}
|
"cause:"
|
||||||
},
|
}
|
||||||
|
.red()
|
||||||
|
.bold(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build and initialize the logger
|
String::clear(&mut message);
|
||||||
env_logger::Builder::from_env(env)
|
let _ = write!(message, "{error}");
|
||||||
.format_timestamp(Some(env_logger::fmt::TimestampPrecision::Seconds))
|
|
||||||
.init();
|
|
||||||
|
|
||||||
// handles to the threads collecting closure size information
|
let mut chars = message.char_indices();
|
||||||
// We do this as early as possible because nix is slow.
|
|
||||||
let closure_size_handles = if args.closure_size {
|
let _ = match (chars.next(), chars.next()) {
|
||||||
debug!("Calculating closure sizes in background");
|
(Some((_, first)), Some((second_start, second)))
|
||||||
let path = args.path.clone();
|
if second.is_lowercase() =>
|
||||||
let path2 = args.path2.clone();
|
{
|
||||||
Some((
|
writeln!(
|
||||||
thread::spawn(move || store::get_closure_size(&path)),
|
err,
|
||||||
thread::spawn(move || store::get_closure_size(&path2)),
|
"{first_lowercase}{rest}",
|
||||||
))
|
first_lowercase = first.to_lowercase(),
|
||||||
} else {
|
rest = &message[second_start..],
|
||||||
None
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
writeln!(err, "{message}")
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Get package lists and handle potential errors
|
process::exit(1);
|
||||||
let package_list_pre = match store::get_packages(&args.path) {
|
|
||||||
Ok(packages) => {
|
|
||||||
debug!("Found {} packages in first closure", packages.len());
|
|
||||||
packages.into_iter().map(|(_, path)| path).collect()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Error getting packages from path {}: {}",
|
|
||||||
args.path.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
eprintln!(
|
|
||||||
"Error getting packages from path {}: {}",
|
|
||||||
args.path.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let package_list_post = match store::get_packages(&args.path2) {
|
|
||||||
Ok(packages) => {
|
|
||||||
debug!("Found {} packages in second closure", packages.len());
|
|
||||||
packages.into_iter().map(|(_, path)| path).collect()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Error getting packages from path {}: {}",
|
|
||||||
args.path2.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
eprintln!(
|
|
||||||
"Error getting packages from path {}: {}",
|
|
||||||
args.path2.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let PackageDiff {
|
|
||||||
pkg_to_versions_pre: pre,
|
|
||||||
pkg_to_versions_post: post,
|
|
||||||
pre_keys: _,
|
|
||||||
post_keys: _,
|
|
||||||
added,
|
|
||||||
removed,
|
|
||||||
changed,
|
|
||||||
} = PackageDiff::new(&package_list_pre, &package_list_post);
|
|
||||||
|
|
||||||
debug!("Added packages: {}", added.len());
|
|
||||||
debug!("Removed packages: {}", removed.len());
|
|
||||||
debug!(
|
|
||||||
"Changed packages: {}",
|
|
||||||
changed
|
|
||||||
.iter()
|
|
||||||
.filter(|p| !p.is_empty()
|
|
||||||
&& match (pre.get(*p), post.get(*p)) {
|
|
||||||
(Some(ver_pre), Some(ver_post)) => ver_pre != ver_post,
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.count()
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("Difference between the two generations:");
|
|
||||||
println!();
|
|
||||||
|
|
||||||
let width_changes = changed
|
|
||||||
.iter()
|
|
||||||
.filter(|&&p| match (pre.get(p), post.get(p)) {
|
|
||||||
(Some(version_pre), Some(version_post)) => version_pre != version_post,
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let col_width = added
|
|
||||||
.iter()
|
|
||||||
.chain(removed.iter())
|
|
||||||
.chain(width_changes)
|
|
||||||
.map(|p| p.len())
|
|
||||||
.max()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
println!("<<< {}", args.path.to_string_lossy());
|
|
||||||
println!(">>> {}", args.path2.to_string_lossy());
|
|
||||||
print::print_added(&added, &post, col_width);
|
|
||||||
print::print_removed(&removed, &pre, col_width);
|
|
||||||
print::print_changes(&changed, &pre, &post, col_width);
|
|
||||||
|
|
||||||
if let Some((pre_handle, post_handle)) = closure_size_handles {
|
|
||||||
match (pre_handle.join(), post_handle.join()) {
|
|
||||||
(Ok(Ok(pre_size)), Ok(Ok(post_size))) => {
|
|
||||||
let pre_size = pre_size / 1024 / 1024;
|
|
||||||
let post_size = post_size / 1024 / 1024;
|
|
||||||
debug!("Pre closure size: {pre_size} MiB");
|
|
||||||
debug!("Post closure size: {post_size} MiB");
|
|
||||||
|
|
||||||
println!("{}", "Closure Size:".underline().bold());
|
|
||||||
println!("Before: {pre_size} MiB");
|
|
||||||
println!("After: {post_size} MiB");
|
|
||||||
println!("Difference: {} MiB", post_size - pre_size);
|
|
||||||
}
|
|
||||||
(Ok(Err(e)), _) | (_, Ok(Err(e))) => {
|
|
||||||
error!("Error getting closure size: {e}");
|
|
||||||
eprintln!("Error getting closure size: {e}");
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!("Failed to get closure size information due to a thread error");
|
|
||||||
eprintln!("Error: Failed to get closure size information due to a thread error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
190
src/print.rs
190
src/print.rs
|
@ -1,190 +0,0 @@
|
||||||
use core::str;
|
|
||||||
use regex::Regex;
|
|
||||||
use std::{
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
string::ToString,
|
|
||||||
sync::OnceLock,
|
|
||||||
};
|
|
||||||
use yansi::Paint;
|
|
||||||
|
|
||||||
/// diffs two strings character by character, and returns a tuple of strings
|
|
||||||
/// colored in a way to represent the differences between the two input strings.
|
|
||||||
///
|
|
||||||
/// # Returns:
|
|
||||||
///
|
|
||||||
/// * (String, String) - The differing chars being red in the left, and green in the right one.
|
|
||||||
fn diff_versions(left: &str, right: &str) -> (String, String) {
|
|
||||||
let mut prev = "\x1b[33m".to_string();
|
|
||||||
let mut post = "\x1b[33m".to_string();
|
|
||||||
|
|
||||||
// We only have to filter the left once, since we stop if the left one is empty.
|
|
||||||
// We do this to display things like -man, -dev properly.
|
|
||||||
let matches = name_regex().captures(left);
|
|
||||||
let mut suffix = String::new();
|
|
||||||
|
|
||||||
if let Some(m) = matches {
|
|
||||||
let tmp = m.get(0).map_or("", |m| m.as_str());
|
|
||||||
suffix.push_str(tmp);
|
|
||||||
}
|
|
||||||
// string without the suffix
|
|
||||||
let filtered_left = &left[..left.len() - suffix.len()];
|
|
||||||
let filtered_right = &right[..right.len() - suffix.len()];
|
|
||||||
|
|
||||||
for diff in diff::chars(filtered_left, filtered_right) {
|
|
||||||
match diff {
|
|
||||||
diff::Result::Both(l, _) => {
|
|
||||||
let string_to_push = format!("{l}");
|
|
||||||
prev.push_str(&string_to_push);
|
|
||||||
post.push_str(&string_to_push);
|
|
||||||
}
|
|
||||||
diff::Result::Left(l) => {
|
|
||||||
let string_to_push = format!("\x1b[1;91m{l}");
|
|
||||||
prev.push_str(&string_to_push);
|
|
||||||
}
|
|
||||||
|
|
||||||
diff::Result::Right(r) => {
|
|
||||||
let string_to_push = format!("\x1b[1;92m{r}");
|
|
||||||
post.push_str(&string_to_push);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// push removed suffix
|
|
||||||
prev.push_str(&format!("\x1b[33m{}", &suffix));
|
|
||||||
post.push_str(&format!("\x1b[33m{}", &suffix));
|
|
||||||
|
|
||||||
//reset
|
|
||||||
prev.push_str("\x1b[0m");
|
|
||||||
post.push_str("\x1b[0m");
|
|
||||||
|
|
||||||
(prev, post)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// print the packages added between two closures.
|
|
||||||
pub fn print_added(set: &HashSet<&str>, post: &HashMap<&str, HashSet<&str>>, col_width: usize) {
|
|
||||||
println!("{}", "Packages added:".underline().bold());
|
|
||||||
|
|
||||||
// Use sorted outpu
|
|
||||||
let mut sorted: Vec<_> = set
|
|
||||||
.iter()
|
|
||||||
.filter_map(|p| post.get(p).map(|ver| (*p, ver)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Sort by package name for consistent output
|
|
||||||
sorted.sort_by(|(a, _), (b, _)| a.cmp(b));
|
|
||||||
|
|
||||||
for (p, ver) in sorted {
|
|
||||||
let mut version_vec = ver.iter().copied().collect::<Vec<_>>();
|
|
||||||
version_vec.sort_unstable();
|
|
||||||
let version_str = version_vec.join(", ");
|
|
||||||
println!(
|
|
||||||
"[{}] {:col_width$} \x1b[33m{}\x1b[0m",
|
|
||||||
"A:".green().bold(),
|
|
||||||
p,
|
|
||||||
version_str
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// print the packages removed between two closures.
|
|
||||||
pub fn print_removed(set: &HashSet<&str>, pre: &HashMap<&str, HashSet<&str>>, col_width: usize) {
|
|
||||||
println!("{}", "Packages removed:".underline().bold());
|
|
||||||
|
|
||||||
// Use sorted output for more predictable and readable results
|
|
||||||
let mut sorted: Vec<_> = set
|
|
||||||
.iter()
|
|
||||||
.filter_map(|p| pre.get(p).map(|ver| (*p, ver)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Sort by package name for consistent output
|
|
||||||
sorted.sort_by(|(a, _), (b, _)| a.cmp(b));
|
|
||||||
|
|
||||||
for (p, ver) in sorted {
|
|
||||||
let mut version_vec = ver.iter().copied().collect::<Vec<_>>();
|
|
||||||
version_vec.sort_unstable();
|
|
||||||
let version_str = version_vec.join(", ");
|
|
||||||
println!(
|
|
||||||
"[{}] {:col_width$} \x1b[33m{}\x1b[0m",
|
|
||||||
"R:".red().bold(),
|
|
||||||
p,
|
|
||||||
version_str
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_changes(
|
|
||||||
set: &HashSet<&str>,
|
|
||||||
pre: &HashMap<&str, HashSet<&str>>,
|
|
||||||
post: &HashMap<&str, HashSet<&str>>,
|
|
||||||
col_width: usize,
|
|
||||||
) {
|
|
||||||
println!("{}", "Version changes:".underline().bold());
|
|
||||||
|
|
||||||
// Use sorted output for more predictable and readable results
|
|
||||||
let mut changes = Vec::new();
|
|
||||||
|
|
||||||
for p in set.iter().filter(|p| !p.is_empty()) {
|
|
||||||
if let (Some(ver_pre), Some(ver_post)) = (pre.get(p), post.get(p)) {
|
|
||||||
if ver_pre != ver_post {
|
|
||||||
changes.push((*p, ver_pre, ver_post));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by package name for consistent output
|
|
||||||
changes.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
|
|
||||||
|
|
||||||
for (p, ver_pre, ver_post) in changes {
|
|
||||||
let mut version_vec_pre = ver_pre.difference(ver_post).copied().collect::<Vec<_>>();
|
|
||||||
let mut version_vec_post = ver_post.difference(ver_pre).copied().collect::<Vec<_>>();
|
|
||||||
|
|
||||||
version_vec_pre.sort_unstable();
|
|
||||||
version_vec_post.sort_unstable();
|
|
||||||
|
|
||||||
let mut diffed_pre: String;
|
|
||||||
let diffed_post: String;
|
|
||||||
|
|
||||||
if version_vec_pre.len() == version_vec_post.len() {
|
|
||||||
let mut diff_pre: Vec<String> = vec![];
|
|
||||||
let mut diff_post: Vec<String> = vec![];
|
|
||||||
|
|
||||||
for (pre, post) in version_vec_pre.iter().zip(version_vec_post.iter()) {
|
|
||||||
let (a, b) = diff_versions(pre, post);
|
|
||||||
diff_pre.push(a);
|
|
||||||
diff_post.push(b);
|
|
||||||
}
|
|
||||||
diffed_pre = diff_pre.join(", ");
|
|
||||||
diffed_post = diff_post.join(", ");
|
|
||||||
} else {
|
|
||||||
let version_str_pre = version_vec_pre.join(", ");
|
|
||||||
let version_str_post = version_vec_post.join(", ");
|
|
||||||
(diffed_pre, diffed_post) = diff_versions(&version_str_pre, &version_str_post);
|
|
||||||
}
|
|
||||||
|
|
||||||
// push a space to the diffed_pre, if it is non-empty, we do this here and not in the println
|
|
||||||
// in order to properly align the ±.
|
|
||||||
if !version_vec_pre.is_empty() {
|
|
||||||
let mut tmp = " ".to_string();
|
|
||||||
tmp.push_str(&diffed_pre);
|
|
||||||
diffed_pre = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"[{}] {:col_width$}{} \x1b[0m\u{00B1}\x1b[0m {}",
|
|
||||||
"C:".bold().bright_yellow(),
|
|
||||||
p,
|
|
||||||
diffed_pre,
|
|
||||||
diffed_post
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a reference to the compiled regex pattern.
|
|
||||||
// The regex is compiled only once.
|
|
||||||
fn name_regex() -> &'static Regex {
|
|
||||||
static REGEX: OnceLock<Regex> = OnceLock::new();
|
|
||||||
REGEX.get_or_init(|| {
|
|
||||||
Regex::new(r"(-man|-lib|-doc|-dev|-out|-terminfo)")
|
|
||||||
.expect("Failed to compile regex pattern for name")
|
|
||||||
})
|
|
||||||
}
|
|
283
src/store.rs
283
src/store.rs
|
@ -1,115 +1,200 @@
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::Path,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::error::AppError;
|
use anyhow::{
|
||||||
use rusqlite::Connection;
|
Context as _,
|
||||||
|
Result,
|
||||||
|
anyhow,
|
||||||
|
};
|
||||||
|
use derive_more::Deref;
|
||||||
|
use rusqlite::OpenFlags;
|
||||||
|
use size::Size;
|
||||||
|
|
||||||
// Use type alias for Result with our custom error type
|
use crate::{
|
||||||
type Result<T> = std::result::Result<T, AppError>;
|
DerivationId,
|
||||||
|
StorePath,
|
||||||
|
};
|
||||||
|
|
||||||
const DATABASE_URL: &str = "/nix/var/nix/db/db.sqlite";
|
#[derive(Deref)]
|
||||||
|
pub struct Connection(rusqlite::Connection);
|
||||||
|
|
||||||
const QUERY_PKGS: &str = "
|
/// Connects to the Nix database
|
||||||
WITH RECURSIVE
|
|
||||||
graph(p) AS (
|
|
||||||
SELECT id
|
|
||||||
FROM ValidPaths
|
|
||||||
WHERE path = ?
|
|
||||||
UNION
|
|
||||||
SELECT reference FROM Refs
|
|
||||||
JOIN graph ON referrer = p
|
|
||||||
)
|
|
||||||
SELECT id, path from graph
|
|
||||||
JOIN ValidPaths ON id = p;
|
|
||||||
";
|
|
||||||
|
|
||||||
const QUERY_CLOSURE_SIZE: &str = "
|
|
||||||
WITH RECURSIVE
|
|
||||||
graph(p) AS (
|
|
||||||
SELECT id
|
|
||||||
FROM ValidPaths
|
|
||||||
WHERE path = ?
|
|
||||||
UNION
|
|
||||||
SELECT reference FROM Refs
|
|
||||||
JOIN graph ON referrer = p
|
|
||||||
)
|
|
||||||
SELECT SUM(narSize) as sum from graph
|
|
||||||
JOIN ValidPaths ON p = id;
|
|
||||||
";
|
|
||||||
|
|
||||||
const QUERY_DEPENDENCY_GRAPH: &str = "
|
|
||||||
WITH RECURSIVE
|
|
||||||
graph(p, c) AS (
|
|
||||||
SELECT id as par, reference as chd
|
|
||||||
FROM ValidPaths
|
|
||||||
JOIN Refs ON referrer = id
|
|
||||||
WHERE path = ?
|
|
||||||
UNION
|
|
||||||
SELECT referrer as par, reference as chd FROM Refs
|
|
||||||
JOIN graph ON referrer = c
|
|
||||||
)
|
|
||||||
SELECT p, c from graph;
|
|
||||||
";
|
|
||||||
|
|
||||||
/// executes a query on the nix db directly
|
|
||||||
/// to gather all derivations that the derivation given by the path
|
|
||||||
/// depends on
|
|
||||||
///
|
///
|
||||||
/// The ids of the derivations in the database are returned as well, since these
|
/// and sets some basic settings
|
||||||
/// can be used to later convert nodes (represented by the the ids) of the
|
pub fn connect() -> Result<Connection> {
|
||||||
/// dependency graph to actual paths
|
const DATABASE_PATH: &str = "/nix/var/nix/db/db.sqlite";
|
||||||
///
|
|
||||||
/// in the future, we might wan't to switch to async
|
|
||||||
pub fn get_packages(path: &std::path::Path) -> Result<Vec<(i64, String)>> {
|
|
||||||
// resolve symlinks and convert to a string
|
|
||||||
let p: String = path.canonicalize()?.to_string_lossy().into_owned();
|
|
||||||
let conn = Connection::open(DATABASE_URL)?;
|
|
||||||
|
|
||||||
let mut stmt = conn.prepare_cached(QUERY_PKGS)?;
|
let inner = rusqlite::Connection::open_with_flags(
|
||||||
let queried_pkgs: std::result::Result<Vec<(i64, String)>, _> = stmt
|
DATABASE_PATH,
|
||||||
.query_map([p], |row| Ok((row.get(0)?, row.get(1)?)))?
|
OpenFlags::SQLITE_OPEN_READ_ONLY // We only run queries, safeguard against corrupting the DB.
|
||||||
.collect();
|
| OpenFlags::SQLITE_OPEN_NO_MUTEX // Part of the default flags, rusqlite takes care of locking anyways.
|
||||||
Ok(queried_pkgs?)
|
| OpenFlags::SQLITE_OPEN_URI,
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!("failed to connect to Nix database at {DATABASE_PATH}")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Perform a batched query to set some settings using PRAGMA
|
||||||
|
// the main performance bottleneck when dix was run before
|
||||||
|
// was that the database file has to be brought from disk into
|
||||||
|
// memory.
|
||||||
|
//
|
||||||
|
// We read a large part of the DB anyways in each query,
|
||||||
|
// so it makes sense to set aside a large region of memory-mapped
|
||||||
|
// I/O prevent incurring page faults which can be done using
|
||||||
|
// `mmap_size`.
|
||||||
|
//
|
||||||
|
// This made a performance difference of about 500ms (but only
|
||||||
|
// when it was first run for a long time!).
|
||||||
|
//
|
||||||
|
// The file pages of the store can be evicted from main memory
|
||||||
|
// using `dd of=/nix/var/nix/db/db.sqlite oflag=nocache conv=notrunc,fdatasync
|
||||||
|
// count=0` if you want to test this. Source: <https://unix.stackexchange.com/questions/36907/drop-a-specific-file-from-the-linux-filesystem-cache>.
|
||||||
|
//
|
||||||
|
// Documentation about the settings can be found here: <https://www.sqlite.org/pragma.html>
|
||||||
|
//
|
||||||
|
// [0]: 256MB, enough to fit the whole DB (at least on my system - Dragyx).
|
||||||
|
// [1]: Always store temporary tables ain memory.
|
||||||
|
inner
|
||||||
|
.execute_batch(
|
||||||
|
"
|
||||||
|
PRAGMA mmap_size=268435456; -- See [0].
|
||||||
|
PRAGMA temp_store=2; -- See [1].
|
||||||
|
PRAGMA query_only;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!("failed to cache Nix database at {DATABASE_PATH}")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Connection(inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// executes a query on the nix db directly
|
fn path_to_canonical_string(path: &Path) -> Result<String> {
|
||||||
/// to get the total closure size of the derivation
|
let path = path.canonicalize().with_context(|| {
|
||||||
/// by summing up the nar size of all derivations
|
format!(
|
||||||
/// depending on the derivation
|
"failed to canonicalize path '{path}'",
|
||||||
///
|
path = path.display(),
|
||||||
/// in the future, we might wan't to switch to async
|
)
|
||||||
pub fn get_closure_size(path: &std::path::Path) -> Result<i64> {
|
})?;
|
||||||
// resolve symlinks and convert to a string
|
|
||||||
let p: String = path.canonicalize()?.to_string_lossy().into_owned();
|
|
||||||
let conn = Connection::open(DATABASE_URL)?;
|
|
||||||
|
|
||||||
let mut stmt = conn.prepare_cached(QUERY_CLOSURE_SIZE)?;
|
let path = path.into_os_string().into_string().map_err(|path| {
|
||||||
let queried_sum = stmt.query_row([p], |row| row.get(0))?;
|
anyhow!(
|
||||||
Ok(queried_sum)
|
"failed to convert path '{path}' to valid unicode",
|
||||||
|
path = Path::new(&*path).display(), /* TODO: use .display() directly
|
||||||
|
* after Rust 1.87.0 in flake. */
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns the complete dependency graph of
|
impl Connection {
|
||||||
/// of the derivation as an adjacency list. The nodes are
|
/// Gets the total closure size of the given store path by summing up the nar
|
||||||
/// represented by the DB ids
|
/// size of all dependent derivations.
|
||||||
///
|
pub fn query_closure_size(&mut self, path: &Path) -> Result<Size> {
|
||||||
/// We might want to collect the paths in the graph directly as
|
const QUERY: &str = "
|
||||||
/// well in the future, depending on how much we use them
|
WITH RECURSIVE
|
||||||
/// in the operations on the graph
|
graph(p) AS (
|
||||||
///
|
SELECT id
|
||||||
/// The mapping from id to graph can be obtained by using [``get_packages``]
|
FROM ValidPaths
|
||||||
pub fn get_dependency_graph(path: &std::path::Path) -> Result<HashMap<i64, Vec<i64>>> {
|
WHERE path = ?
|
||||||
// resolve symlinks and convert to a string
|
UNION
|
||||||
let p: String = path.canonicalize()?.to_string_lossy().into_owned();
|
SELECT reference FROM Refs
|
||||||
let conn = Connection::open(DATABASE_URL)?;
|
JOIN graph ON referrer = p
|
||||||
|
)
|
||||||
|
SELECT SUM(narSize) as sum from graph
|
||||||
|
JOIN ValidPaths ON p = id;
|
||||||
|
";
|
||||||
|
|
||||||
let mut stmt = conn.prepare_cached(QUERY_DEPENDENCY_GRAPH)?;
|
let path = path_to_canonical_string(path)?;
|
||||||
let mut adj = HashMap::<i64, Vec<i64>>::new();
|
|
||||||
let queried_edges =
|
let closure_size = self
|
||||||
stmt.query_map([p], |row| Ok::<(i64, i64), _>((row.get(0)?, row.get(1)?)))?;
|
.prepare_cached(QUERY)?
|
||||||
for row in queried_edges {
|
.query_row([path], |row| Ok(Size::from_bytes(row.get::<_, i64>(0)?)))?;
|
||||||
let (from, to) = row?;
|
|
||||||
adj.entry(from).or_default().push(to);
|
Ok(closure_size)
|
||||||
adj.entry(to).or_default();
|
}
|
||||||
|
|
||||||
|
/// Gathers all derivations that the given profile path depends on.
|
||||||
|
pub fn query_dependents(
|
||||||
|
&mut self,
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<Vec<(DerivationId, StorePath)>> {
|
||||||
|
const QUERY: &str = "
|
||||||
|
WITH RECURSIVE
|
||||||
|
graph(p) AS (
|
||||||
|
SELECT id
|
||||||
|
FROM ValidPaths
|
||||||
|
WHERE path = ?
|
||||||
|
UNION
|
||||||
|
SELECT reference FROM Refs
|
||||||
|
JOIN graph ON referrer = p
|
||||||
|
)
|
||||||
|
SELECT id, path from graph
|
||||||
|
JOIN ValidPaths ON id = p;
|
||||||
|
";
|
||||||
|
|
||||||
|
let path = path_to_canonical_string(path)?;
|
||||||
|
|
||||||
|
let packages: result::Result<Vec<(DerivationId, StorePath)>, _> = self
|
||||||
|
.prepare_cached(QUERY)?
|
||||||
|
.query_map([path], |row| {
|
||||||
|
Ok((
|
||||||
|
DerivationId(row.get(0)?),
|
||||||
|
StorePath(row.get::<_, String>(1)?.into()),
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(packages?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gathers the complete dependency graph of of the store path as an adjacency
|
||||||
|
/// list.
|
||||||
|
///
|
||||||
|
/// We might want to collect the paths in the graph directly as
|
||||||
|
/// well in the future, depending on how much we use them
|
||||||
|
/// in the operations on the graph.
|
||||||
|
#[expect(dead_code)]
|
||||||
|
pub fn query_dependency_graph(
|
||||||
|
&mut self,
|
||||||
|
path: &StorePath,
|
||||||
|
) -> Result<HashMap<DerivationId, Vec<DerivationId>>> {
|
||||||
|
const QUERY: &str = "
|
||||||
|
WITH RECURSIVE
|
||||||
|
graph(p, c) AS (
|
||||||
|
SELECT id as par, reference as chd
|
||||||
|
FROM ValidPaths
|
||||||
|
JOIN Refs ON referrer = id
|
||||||
|
WHERE path = ?
|
||||||
|
UNION
|
||||||
|
SELECT referrer as par, reference as chd FROM Refs
|
||||||
|
JOIN graph ON referrer = c
|
||||||
|
)
|
||||||
|
SELECT p, c from graph;
|
||||||
|
";
|
||||||
|
|
||||||
|
let path = path_to_canonical_string(path)?;
|
||||||
|
|
||||||
|
let mut adj = HashMap::<DerivationId, Vec<DerivationId>>::new();
|
||||||
|
|
||||||
|
let mut statement = self.prepare_cached(QUERY)?;
|
||||||
|
|
||||||
|
let edges = statement.query_map([path], |row| {
|
||||||
|
Ok((DerivationId(row.get(0)?), DerivationId(row.get(1)?)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for row in edges {
|
||||||
|
let (from, to) = row?;
|
||||||
|
|
||||||
|
adj.entry(from).or_default().push(to);
|
||||||
|
adj.entry(to).or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(adj)
|
Ok(adj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
246
src/util.rs
246
src/util.rs
|
@ -1,246 +0,0 @@
|
||||||
use std::{
|
|
||||||
cmp::Ordering,
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
sync::OnceLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::error::AppError;
|
|
||||||
use log::debug;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
// Use type alias for Result with our custom error type
|
|
||||||
type Result<T> = std::result::Result<T, AppError>;
|
|
||||||
|
|
||||||
use std::string::ToString;
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Debug)]
|
|
||||||
enum VersionComponent {
|
|
||||||
Number(u64),
|
|
||||||
Text(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::cmp::Ord for VersionComponent {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
use VersionComponent::{Number, Text};
|
|
||||||
match (self, other) {
|
|
||||||
(Number(x), Number(y)) => x.cmp(y),
|
|
||||||
(Text(x), Text(y)) => match (x.as_str(), y.as_str()) {
|
|
||||||
("pre", _) => Ordering::Less,
|
|
||||||
(_, "pre") => Ordering::Greater,
|
|
||||||
_ => x.cmp(y),
|
|
||||||
},
|
|
||||||
(Text(_), Number(_)) => Ordering::Less,
|
|
||||||
(Number(_), Text(_)) => Ordering::Greater,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for VersionComponent {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes a version string and outputs the different components
|
|
||||||
//
|
|
||||||
// a component is delimited by '-' or '.' and consists of just digits or letters
|
|
||||||
struct VersionComponentIterator<'a> {
|
|
||||||
v: &'a [u8],
|
|
||||||
pos: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VersionComponentIterator<'a> {
|
|
||||||
pub fn new<I: Into<&'a str>>(v: I) -> Self {
|
|
||||||
Self {
|
|
||||||
v: v.into().as_bytes(),
|
|
||||||
pos: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for VersionComponentIterator<'_> {
|
|
||||||
type Item = VersionComponent;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
// skip all '-' and '.' in the beginning
|
|
||||||
while let Some(b'.' | b'-') = self.v.get(self.pos) {
|
|
||||||
self.pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the next character and decide if it is a digit or char
|
|
||||||
let c = self.v.get(self.pos)?;
|
|
||||||
let is_digit = c.is_ascii_digit();
|
|
||||||
// based on this collect characters after this into the component
|
|
||||||
let component_len = self.v[self.pos..]
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.take_while(|&c| c.is_ascii_digit() == is_digit && c != b'.' && c != b'-')
|
|
||||||
.count();
|
|
||||||
let component =
|
|
||||||
String::from_utf8_lossy(&self.v[self.pos..(self.pos + component_len)]).into_owned();
|
|
||||||
|
|
||||||
// remember what chars we used
|
|
||||||
self.pos += component_len;
|
|
||||||
|
|
||||||
if component.is_empty() {
|
|
||||||
None
|
|
||||||
} else if is_digit {
|
|
||||||
component.parse::<u64>().ok().map(VersionComponent::Number)
|
|
||||||
} else {
|
|
||||||
Some(VersionComponent::Text(component))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compares two strings of package versions, and figures out the greater one.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * Ordering
|
|
||||||
pub fn compare_versions(a: &str, b: &str) -> Ordering {
|
|
||||||
let iter_a = VersionComponentIterator::new(a);
|
|
||||||
let iter_b = VersionComponentIterator::new(b);
|
|
||||||
|
|
||||||
iter_a.cmp(iter_b)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses a nix store path to extract the packages name and version
|
|
||||||
///
|
|
||||||
/// This function first drops the inputs first 44 chars, since that is exactly the length of the /nix/store/... prefix. Then it matches that against our store path regex.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * Result<(&'a str, &'a str)> - The Package's name and version, or an error if
|
|
||||||
/// one or both cannot be retrieved.
|
|
||||||
pub fn get_version<'a>(pack: impl Into<&'a str>) -> Result<(&'a str, &'a str)> {
|
|
||||||
let path = pack.into();
|
|
||||||
|
|
||||||
// We can strip the path since it _always_ follows the format
|
|
||||||
// /nix/store/<...>-<program_name>-......
|
|
||||||
// This part is exactly 44 chars long, so we just remove it.
|
|
||||||
let stripped_path = &path[44..];
|
|
||||||
debug!("Stripped path: {stripped_path}");
|
|
||||||
|
|
||||||
// Match the regex against the input
|
|
||||||
if let Some(cap) = store_path_regex().captures(stripped_path) {
|
|
||||||
// Handle potential missing captures safely
|
|
||||||
let name = cap.get(1).map_or("", |m| m.as_str());
|
|
||||||
let mut version = cap.get(2).map_or("<none>", |m| m.as_str());
|
|
||||||
|
|
||||||
if version.starts_with('-') {
|
|
||||||
version = &version[1..];
|
|
||||||
}
|
|
||||||
|
|
||||||
if name.is_empty() {
|
|
||||||
return Err(AppError::ParseError {
|
|
||||||
message: format!("Failed to extract name from path: {path}"),
|
|
||||||
context: "get_version".to_string(),
|
|
||||||
source: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok((name, version));
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(AppError::ParseError {
|
|
||||||
message: format!("Path does not match expected nix store format: {path}"),
|
|
||||||
context: "get_version".to_string(),
|
|
||||||
source: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a reference to the compiled regex pattern.
|
|
||||||
// The regex is compiled only once.
|
|
||||||
pub fn store_path_regex() -> &'static Regex {
|
|
||||||
static REGEX: OnceLock<Regex> = OnceLock::new();
|
|
||||||
REGEX.get_or_init(|| {
|
|
||||||
Regex::new(r"(.+?)(-([0-9].*?))?$")
|
|
||||||
.expect("Failed to compile regex pattern for nix store paths")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move this somewhere else, this does not really
|
|
||||||
// belong into this file
|
|
||||||
pub struct PackageDiff<'a> {
|
|
||||||
pub pkg_to_versions_pre: HashMap<&'a str, HashSet<&'a str>>,
|
|
||||||
pub pkg_to_versions_post: HashMap<&'a str, HashSet<&'a str>>,
|
|
||||||
pub pre_keys: HashSet<&'a str>,
|
|
||||||
pub post_keys: HashSet<&'a str>,
|
|
||||||
pub added: HashSet<&'a str>,
|
|
||||||
pub removed: HashSet<&'a str>,
|
|
||||||
pub changed: HashSet<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PackageDiff<'a> {
|
|
||||||
pub fn new<S: AsRef<str> + 'a>(pkgs_pre: &'a [S], pkgs_post: &'a [S]) -> Self {
|
|
||||||
// Map from packages of the first closure to their version
|
|
||||||
let mut pre = HashMap::<&str, HashSet<&str>>::new();
|
|
||||||
let mut post = HashMap::<&str, HashSet<&str>>::new();
|
|
||||||
|
|
||||||
for p in pkgs_pre {
|
|
||||||
match get_version(p.as_ref()) {
|
|
||||||
Ok((name, version)) => {
|
|
||||||
pre.entry(name).or_default().insert(version);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Error parsing package version: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for p in pkgs_post {
|
|
||||||
match get_version(p.as_ref()) {
|
|
||||||
Ok((name, version)) => {
|
|
||||||
post.entry(name).or_default().insert(version);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Error parsing package version: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare the package names of both versions
|
|
||||||
let pre_keys: HashSet<&str> = pre.keys().copied().collect();
|
|
||||||
let post_keys: HashSet<&str> = post.keys().copied().collect();
|
|
||||||
|
|
||||||
// Difference gives us added and removed packages
|
|
||||||
let added: HashSet<&str> = &post_keys - &pre_keys;
|
|
||||||
|
|
||||||
let removed: HashSet<&str> = &pre_keys - &post_keys;
|
|
||||||
// Get the intersection of the package names for version changes
|
|
||||||
let changed: HashSet<&str> = &pre_keys & &post_keys;
|
|
||||||
Self {
|
|
||||||
pkg_to_versions_pre: pre,
|
|
||||||
pkg_to_versions_post: post,
|
|
||||||
pre_keys,
|
|
||||||
post_keys,
|
|
||||||
added,
|
|
||||||
removed,
|
|
||||||
changed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod test {
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_version_component_iter() {
|
|
||||||
use super::VersionComponent::{Number, Text};
|
|
||||||
use crate::util::VersionComponentIterator;
|
|
||||||
let v = "132.1.2test234-1-man----.--.......---------..---";
|
|
||||||
|
|
||||||
let comp: Vec<_> = VersionComponentIterator::new(v).collect();
|
|
||||||
assert_eq!(
|
|
||||||
comp,
|
|
||||||
[
|
|
||||||
Number(132),
|
|
||||||
Number(1),
|
|
||||||
Number(2),
|
|
||||||
Text("test".into()),
|
|
||||||
Number(234),
|
|
||||||
Number(1),
|
|
||||||
Text("man".into())
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
147
src/version.rs
Normal file
147
src/version.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
|
use derive_more::{
|
||||||
|
Deref,
|
||||||
|
DerefMut,
|
||||||
|
Display,
|
||||||
|
From,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deref, DerefMut, Display, Debug, Clone, PartialEq, Eq, From)]
|
||||||
|
pub struct Version(String);
|
||||||
|
|
||||||
|
impl PartialOrd for Version {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::Ord for Version {
|
||||||
|
fn cmp(&self, that: &Self) -> cmp::Ordering {
|
||||||
|
let this = VersionComponentIter::from(&***self).filter_map(Result::ok);
|
||||||
|
let that = VersionComponentIter::from(&***that).filter_map(Result::ok);
|
||||||
|
|
||||||
|
this.cmp(that)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a Version {
|
||||||
|
type Item = Result<VersionComponent<'a>, &'a str>;
|
||||||
|
|
||||||
|
type IntoIter = VersionComponentIter<'a>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
VersionComponentIter::from(&***self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Display, Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub enum VersionComponent<'a> {
|
||||||
|
Number(u64),
|
||||||
|
Text(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for VersionComponent<'_> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::Ord for VersionComponent<'_> {
|
||||||
|
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||||
|
use VersionComponent::{
|
||||||
|
Number,
|
||||||
|
Text,
|
||||||
|
};
|
||||||
|
|
||||||
|
match (*self, *other) {
|
||||||
|
(Number(this), Number(that)) => this.cmp(&that),
|
||||||
|
(Text(this), Text(that)) => {
|
||||||
|
match (this, that) {
|
||||||
|
("pre", _) => cmp::Ordering::Less,
|
||||||
|
(_, "pre") => cmp::Ordering::Greater,
|
||||||
|
_ => this.cmp(that),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(Text(_), Number(_)) => cmp::Ordering::Less,
|
||||||
|
(Number(_), Text(_)) => cmp::Ordering::Greater,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Yields [`VertionComponent`] from a version string.
|
||||||
|
#[derive(Deref, DerefMut, From)]
|
||||||
|
pub struct VersionComponentIter<'a>(&'a str);
|
||||||
|
|
||||||
|
impl<'a> Iterator for VersionComponentIter<'a> {
|
||||||
|
type Item = Result<VersionComponent<'a>, &'a str>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.starts_with(['.', '-', '*', '×', ' ']) {
|
||||||
|
let len = self.chars().next().unwrap().len_utf8();
|
||||||
|
let (this, rest) = self.split_at(len);
|
||||||
|
|
||||||
|
**self = rest;
|
||||||
|
return Some(Err(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next character and decide if it is a digit.
|
||||||
|
let is_digit = self.chars().next()?.is_ascii_digit();
|
||||||
|
|
||||||
|
// Based on this collect characters after this into the component.
|
||||||
|
let component_len = self
|
||||||
|
.chars()
|
||||||
|
.take_while(|&char| {
|
||||||
|
char.is_ascii_digit() == is_digit
|
||||||
|
&& !matches!(char, '.' | '-' | '*' | ' ' | '×')
|
||||||
|
})
|
||||||
|
.map(char::len_utf8)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let component = &self[..component_len];
|
||||||
|
**self = &self[component_len..];
|
||||||
|
|
||||||
|
assert!(!component.is_empty());
|
||||||
|
|
||||||
|
if is_digit {
|
||||||
|
component
|
||||||
|
.parse::<u64>()
|
||||||
|
.ok()
|
||||||
|
.map(VersionComponent::Number)
|
||||||
|
.map(Ok)
|
||||||
|
} else {
|
||||||
|
Some(Ok(VersionComponent::Text(component)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::version::{
|
||||||
|
VersionComponent::{
|
||||||
|
Number,
|
||||||
|
Text,
|
||||||
|
},
|
||||||
|
VersionComponentIter,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_component_iter() {
|
||||||
|
let version = "132.1.2test234-1-man----.--.......---------..---";
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
VersionComponentIter::from(version)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[
|
||||||
|
Number(132),
|
||||||
|
Number(1),
|
||||||
|
Number(2),
|
||||||
|
Text("test"),
|
||||||
|
Number(234),
|
||||||
|
Number(1),
|
||||||
|
Text("man")
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue