From e7d0b06c782e9b3446ea936de17e568610c3cd0f Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:03:16 +0200 Subject: [PATCH 1/9] Add a black-box conformance suite for the lintus command line A Cargo workspace with two crates, jev and lintus, still empty. The lintus crate carries a conformance suite that runs a lintus binary against temporary projects and a fake Jev API, then checks its output, exit status and requests. LINTUS_BIN picks the binary, so the suite runs against the Ruby implementation through test/support/lintus-ruby, where it passes. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 2 + Cargo.lock | 274 ++++++++++++++++ Cargo.toml | 17 + crates/jev/Cargo.toml | 10 + crates/jev/src/lib.rs | 1 + crates/lintus/Cargo.toml | 16 + crates/lintus/src/main.rs | 1 + crates/lintus/tests/conformance/cli.rs | 229 +++++++++++++ crates/lintus/tests/conformance/config.rs | 255 +++++++++++++++ crates/lintus/tests/conformance/files.rs | 265 +++++++++++++++ crates/lintus/tests/conformance/main.rs | 24 ++ crates/lintus/tests/conformance/output.rs | 199 ++++++++++++ crates/lintus/tests/conformance/runner.rs | 209 ++++++++++++ crates/lintus/tests/conformance/support.rs | 355 +++++++++++++++++++++ test/support/jev_api_url.rb | 10 + test/support/lintus-ruby | 7 + 16 files changed, 1874 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 crates/jev/Cargo.toml create mode 100644 crates/jev/src/lib.rs create mode 100644 crates/lintus/Cargo.toml create mode 100644 crates/lintus/src/main.rs create mode 100644 crates/lintus/tests/conformance/cli.rs create mode 100644 crates/lintus/tests/conformance/config.rs create mode 100644 crates/lintus/tests/conformance/files.rs create mode 100644 crates/lintus/tests/conformance/main.rs create mode 100644 crates/lintus/tests/conformance/output.rs create mode 100644 crates/lintus/tests/conformance/runner.rs create mode 100644 crates/lintus/tests/conformance/support.rs create mode 100644 test/support/jev_api_url.rb create mode 100755 test/support/lintus-ruby diff --git a/.gitignore b/.gitignore index e3ae5c6..0fd6508 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ *.gem /.irbrc Gemfile.lock + +/target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..966ed56 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,274 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jev" +version = "0.1.0" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "lintus" +version = "0.3.0" +dependencies = [ + "serde_json", + "tempfile", + "tiny_http", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..063b663 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[workspace] +members = ["crates/jev", "crates/lintus"] +resolver = "3" + +[workspace.package] +edition = "2024" +license = "MIT" +authors = ["Vincent Rolea"] +repository = "https://github.com/virolea/lintus" + +[workspace.dependencies] +jev = { path = "crates/jev", version = "0.1.0" } +indexmap = "2" +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order"] } +tempfile = "3" +tiny_http = "0.12" diff --git a/crates/jev/Cargo.toml b/crates/jev/Cargo.toml new file mode 100644 index 0000000..dc600a8 --- /dev/null +++ b/crates/jev/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "jev" +version = "0.1.0" +description = "A typed client for the Typesafe Jev model API." +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[dependencies] diff --git a/crates/jev/src/lib.rs b/crates/jev/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/jev/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/lintus/Cargo.toml b/crates/lintus/Cargo.toml new file mode 100644 index 0000000..90e61c0 --- /dev/null +++ b/crates/lintus/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "lintus" +version = "0.3.0" +description = "A linter whose rules are plain-language questions, answered by the Jev model." +readme = "../../README.md" +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[dependencies] + +[dev-dependencies] +serde_json.workspace = true +tempfile.workspace = true +tiny_http.workspace = true diff --git a/crates/lintus/src/main.rs b/crates/lintus/src/main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/crates/lintus/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/crates/lintus/tests/conformance/cli.rs b/crates/lintus/tests/conformance/cli.rs new file mode 100644 index 0000000..80a0777 --- /dev/null +++ b/crates/lintus/tests/conformance/cli.rs @@ -0,0 +1,229 @@ +//! Arguments, subcommands and exit statuses. + +use crate::CONFIG; +use crate::support::{FakeJev, Project}; + +#[test] +fn version() { + let out = Project::new().lintus(&["--version"]).run(); + + assert_eq!(out.code, 0); + let version = out.stdout.strip_prefix("lintus ").unwrap().trim_end(); + assert!(version.split('.').count() == 3 && version.split('.').all(|n| n.parse::().is_ok()), "{out:?}"); + assert!(out.stdout.ends_with('\n')); +} + +#[test] +fn help_lists_every_option() { + let out = Project::new().lintus(&["--help"]).run(); + + assert_eq!(out.code, 0); + assert!(out.stdout.contains("Usage: lintus"), "{}", out.stdout); + for flag in ["--diff", "--staged", "--config", "--format", "--jobs", "--api-key", "--list", "--version", "--help"] { + assert!(out.stdout.contains(flag), "help does not mention {flag}:\n{}", out.stdout); + } +} + +#[test] +fn init_writes_a_working_config_and_refuses_to_overwrite() { + let project = Project::new(); + + let out = project.lintus(&["init"]).run(); + assert_eq!(out.code, 0, "{out:?}"); + let path = project.path(".lintus.yml"); + assert_eq!(out.stdout, format!("Wrote {}\n", path.display())); + + project.write("lib/a.rb", "class A; end\n"); + project.write("vendor/b.rb", "class B; end\n"); + let out = project.lintus(&["--list"]).run(); + assert_eq!( + out.stdout, + "lib/a.rb: no_debugging_leftovers, no_hardcoded_secrets, classes_are_documented\n1 file(s) would be checked\n" + ); + + let out = project.lintus(&["init"]).run(); + assert_eq!(out.code, 2); + assert_eq!(out.stderr, format!("lintus: {} already exists\n", path.display())); +} + +#[test] +fn list_shows_files_and_rules_without_an_api_key() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("app/jobs/a_job.rb", "class AJob; end\n"); + project.write("lib/b.rb", "class B; end\n"); + project.write("README.md", "hi\n"); + + let out = project.lintus(&["--list"]).run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(out.stdout, "app/jobs/a_job.rb: no_sleep, documented\nlib/b.rb: documented\n2 file(s) would be checked\n"); + assert_eq!(out.stderr, ""); +} + +#[test] +fn a_missing_api_key_is_a_usage_error() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::nouls(&[]); + + let out = project.lintus(&[]).api_without_key(&server).run(); + + assert_eq!(out.code, 2); + assert!(out.stderr.starts_with("lintus: "), "{}", out.stderr); + assert!(out.stderr.contains("JEV_API_KEY"), "{}", out.stderr); + assert!(server.requests().is_empty()); +} + +#[test] +fn an_empty_api_key_counts_as_missing() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::nouls(&[]); + + let out = project.lintus(&[]).api_without_key(&server).env("JEV_API_KEY", "").run(); + + assert_eq!(out.code, 2); + assert!(out.stderr.contains("JEV_API_KEY"), "{}", out.stderr); +} + +#[test] +fn a_full_run_reports_offenses_and_exits_1() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("app/jobs/a_job.rb", "sleep 1\n"); + let server = FakeJev::nouls(&[("no_sleep", 0.95), ("documented", 0.9)]); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 1, "{out:?}"); + assert_eq!(out.stdout, "app/jobs/a_job.rb: [no_sleep] Jobs must not sleep. (noul 0.95)\n\n1 file inspected, 1 offense detected\n"); + assert_eq!(out.stderr, "Checking 1 file...\n"); +} + +#[test] +fn the_api_key_flag_wins_over_the_environment() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + let out = project.lintus(&["--api-key", "from-flag"]).api(&server).run(); + assert_eq!(out.code, 0, "{out:?}"); + let out = project.lintus(&[]).api(&server).run(); + assert_eq!(out.code, 0, "{out:?}"); + + let auth: Vec<_> = server.requests().into_iter().map(|r| r.authorization.unwrap()).collect(); + assert_eq!(auth, ["Bearer from-flag", "Bearer test-key"]); +} + +#[test] +fn a_clean_run_exits_0() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "# B\nclass B; end\n"); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(out.stdout, "1 file inspected, 0 offenses detected\n"); +} + +#[test] +fn nothing_to_check_sends_nothing() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("README.md", "hi\n"); + let server = FakeJev::nouls(&[]); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(out.stdout, "0 files inspected, 0 offenses detected\n"); + assert_eq!(out.stderr, ""); + assert!(server.requests().is_empty()); +} + +#[test] +fn file_arguments_cannot_be_combined_with_diff_or_staged() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + + for flag in ["--staged", "--diff"] { + let out = project.lintus(&[flag, "--list", "--", "lib/a.rb"]).run(); + assert_eq!(out.code, 2, "{flag}: {out:?}"); + assert!(out.stderr.contains("cannot be combined"), "{flag}: {}", out.stderr); + } +} + +#[test] +fn invalid_arguments_exit_2_with_a_message() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + + for args in [ + &["--format", "xml"][..], + &["--jobs", "many"], + &["--no-such-flag"], + &["init", "extra"], + &["--config"], + ] { + let out = project.lintus(args).run(); + assert_eq!(out.code, 2, "{args:?}: {out:?}"); + assert!(out.stderr.starts_with("lintus: "), "{args:?}: {}", out.stderr); + assert_eq!(out.stdout, "", "{args:?}"); + } +} + +#[test] +fn format_takes_text_github_or_json() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + for format in ["text", "github", "json"] { + let out = project.lintus(&["--format", format]).api(&server).run(); + assert_eq!(out.code, 0, "{format}: {out:?}"); + } + let out = project.lintus(&["-f", "json"]).api(&server).run(); + assert!(out.stdout.starts_with('{'), "{out:?}"); +} + +#[test] +fn the_github_format_is_the_default_under_github_actions() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::nouls(&[("documented", 0.1)]); + + let out = project.lintus(&[]).api(&server).env("GITHUB_ACTIONS", "true").run(); + + assert_eq!(out.code, 1); + assert_eq!( + out.stdout, + "::error file=lib/b.rb,title=lintus%3A documented::Is every class documented?\n1 file inspected, 1 offense detected\n" + ); + + let out = project.lintus(&["--format", "text"]).api(&server).env("GITHUB_ACTIONS", "true").run(); + assert!(out.stdout.starts_with("lib/b.rb: [documented]"), "{out:?}"); +} + +#[test] +fn short_flags() { + let project = Project::git_repo(); + project.write("conf/custom.yml", CONFIG); + project.write("conf/lib/b.rb", "class B; end\n"); + project.write("conf/lib/c.rb", "class C; end\n"); + project.git(&["add", "conf/lib/b.rb"]); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + let out = project.lintus(&["-c", "conf/custom.yml", "-l"]).run(); + assert_eq!(out.stdout, "lib/b.rb: documented\nlib/c.rb: documented\n2 file(s) would be checked\n"); + + let out = project.lintus(&["-c", "conf/custom.yml", "-j", "2", "-f", "json"]).api(&server).run(); + assert_eq!(out.code, 0, "{out:?}"); +} diff --git a/crates/lintus/tests/conformance/config.rs b/crates/lintus/tests/conformance/config.rs new file mode 100644 index 0000000..8acae26 --- /dev/null +++ b/crates/lintus/tests/conformance/config.rs @@ -0,0 +1,255 @@ +//! Finding, parsing and validating the config file. + +use crate::CONFIG; +use crate::support::{FakeJev, Project}; + +#[test] +fn the_config_is_found_by_walking_up_and_its_directory_is_the_root() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/deep/b.rb", "class B; end\n"); + + let out = project.lintus(&["--list"]).in_dir("lib/deep").run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(out.stdout, "lib/deep/b.rb: documented\n1 file(s) would be checked\n"); +} + +#[test] +fn every_config_file_name_is_recognised_in_order() { + let project = Project::new(); + project.write("a.rb", "class A; end\n"); + let names = ["lintus.yaml", ".lintus.yaml", "lintus.yml", ".lintus.yml"]; + + for (i, name) in names.iter().enumerate() { + project.write(name, format!("rules:\n from_{i}:\n question: q\n")); + let out = project.lintus(&["--list", "a.rb"]).run(); + assert_eq!(out.stdout, format!("a.rb: from_{i}\n1 file(s) would be checked\n"), "{name}"); + } +} + +#[test] +fn an_explicit_config_path_makes_its_directory_the_root() { + let project = Project::new(); + project.write("elsewhere/custom.yml", CONFIG); + project.write("elsewhere/lib/b.rb", "class B; end\n"); + project.write("lib/outside.rb", "class O; end\n"); + + let out = project.lintus(&["--config", "elsewhere/custom.yml", "--list"]).run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(out.stdout, "lib/b.rb: documented\n1 file(s) would be checked\n"); +} + +#[test] +fn a_missing_config_is_reported() { + let project = Project::new(); + + let out = project.lintus(&["--list"]).run(); + assert_eq!(out.code, 2); + assert!(out.stderr.starts_with("lintus: No config file found: looked for .lintus.yml, lintus.yml, .lintus.yaml, lintus.yaml in "), "{}", out.stderr); + + let out = project.lintus(&["--config", "nope.yml", "--list"]).run(); + assert_eq!(out.code, 2); + assert_eq!(out.stderr, "lintus: Config file not found: nope.yml\n"); +} + +#[test] +fn invalid_yaml_is_reported() { + let project = Project::new(); + project.write(".lintus.yml", "rules: [\n"); + + let out = project.lintus(&["--list"]).run(); + + assert_eq!(out.code, 2); + let expected = format!("lintus: {} is not valid YAML: ", project.path(".lintus.yml").display()); + assert!(out.stderr.starts_with(&expected), "{}", out.stderr); +} + +/// Each config and the exact error it produces. +#[test] +fn invalid_configs_are_rejected_with_a_precise_message() { + let cases: &[(&str, &str)] = &[ + ("", "`rules` must be a map of rule id => attributes"), + ("- a\n", "config must be a map, got Array"), + ("just text\n", "config must be a map, got String"), + ("rules: []\n", "`rules` must be a map of rule id => attributes"), + ("rules: {}\n", "`rules` is empty: nothing to lint"), + ( + "rule: {}\nrules:\n r:\n question: q\n", + "config: unknown key(s) rule (expected rules, paths, exclude, max_file_size)", + ), + ("max_file_size: big\nrules:\n r:\n question: q\n", "`max_file_size` must be a positive integer of bytes"), + ("max_file_size: 0\nrules:\n r:\n question: q\n", "`max_file_size` must be a positive integer of bytes"), + ("max_file_size: 1.5\nrules:\n r:\n question: q\n", "`max_file_size` must be a positive integer of bytes"), + ( + "rules:\n No-Sleep:\n question: q\n", + "invalid rule id \"No-Sleep\": use snake_case (letters, digits, underscores)", + ), + ("rules:\n 1abc:\n question: q\n", "invalid rule id \"1abc\": use snake_case (letters, digits, underscores)"), + ("rules:\n r:\n description: d\n", "rule r: `question` is required"), + ("rules:\n r:\n question: ' '\n", "rule r: `question` is required"), + ("rules:\n r: just a string\n", "rule r: expected a map of attributes, got \"just a string\""), + ("rules:\n r:\n", "rule r: expected a map of attributes, got nil"), + ( + "rules:\n r:\n question: q\n severity: error\n", + "rule r: unknown key(s) severity (expected question, description, criteria, threshold, paths, exclude, offense_when)", + ), + ("rules:\n r:\n question: q\n threshold: 2\n", "rule r: `threshold` must be a number between 0 and 1"), + ("rules:\n r:\n question: q\n threshold: high\n", "rule r: `threshold` must be a number between 0 and 1"), + ("rules:\n r:\n question: q\n criteria: x\n", "rule r: `criteria` must be a map with `true` and `false` keys"), + ("rules:\n r:\n question: q\n offense_when: maybe\n", "rule r: `offense_when` must be true or false"), + ]; + + let project = Project::new(); + project.write("a.rb", "class A; end\n"); + for (yaml, message) in cases { + project.write(".lintus.yml", yaml); + let out = project.lintus(&["--list"]).run(); + assert_eq!(out.code, 2, "{yaml:?}: {out:?}"); + assert_eq!(out.stderr, format!("lintus: {message}\n"), "{yaml:?}"); + } +} + +#[test] +fn optional_rule_attributes_have_defaults_and_accept_valid_values() { + let project = Project::new(); + project.write("a.rb", "class A; end\n"); + project.write( + ".lintus.yml", + r#" +paths: ["*.rb"] +max_file_size: 5000 +rules: + bare: + question: " Is this padded? " + full: + description: Full rule. + question: Is this full? + criteria: + "true": It is. + "false": It is not. + threshold: 1 + paths: "*.rb" + exclude: ["b.rb"] + offense_when: false +"#, + ); + + let out = project.lintus(&["--list"]).run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(out.stdout, "a.rb: bare, full\n1 file(s) would be checked\n"); +} + +#[test] +fn yaml_anchors_and_aliases_are_supported() { + let project = Project::new(); + project.write("lib/a.rb", "class A; end\n"); + project.write("app/b.rb", "class B; end\n"); + project.write( + ".lintus.yml", + r#" +rules: + one: + question: q1 + paths: &ruby ["lib/**/*.rb"] + two: + question: q2 + paths: *ruby +"#, + ); + + let out = project.lintus(&["--list"]).run(); + + assert_eq!(out.stdout, "lib/a.rb: one, two\n1 file(s) would be checked\n", "{out:?}"); +} + +#[test] +fn rules_are_asked_in_config_order_with_their_question_criteria_and_nothing_else() { + let project = Project::new(); + project.write("a.rb", "class A; end\n"); + project.write( + ".lintus.yml", + r#" +rules: + zeta: + question: " Is this the last letter? " + threshold: 0.9 + alpha: + description: Described. + question: Is this the first letter? + criteria: + "true": Yes it is. + false: No it is not. +"#, + ); + let server = FakeJev::nouls(&[("zeta", 0.1), ("alpha", 0.1)]); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 0, "{out:?}"); + let request = &server.requests()[0]; + assert_eq!(request.question_ids(), ["zeta", "alpha"]); + assert_eq!(request.question("zeta"), &serde_json::json!({ "type": "noul", "instructions": "Is this the last letter?" })); + assert_eq!( + request.question("alpha"), + &serde_json::json!({ + "type": "noul", + "instructions": "Is this the first letter?", + "criteria": { "true": "Yes it is.", "false": "No it is not." } + }) + ); +} + +#[test] +fn threshold_and_offense_when_decide_the_offense() { + let project = Project::new(); + project.write("a.rb", "class A; end\n"); + project.write( + ".lintus.yml", + r#" +paths: ["*.rb"] +rules: + default_below: + question: q + default_at: + question: q + default_above: + question: q + strict_below: + question: q + threshold: 0.7 + strict_above: + question: q + threshold: 0.7 + negative_true: + question: q + offense_when: false + negative_false: + question: q + offense_when: false +"#, + ); + let server = FakeJev::nouls(&[ + ("default_below", 0.4), + ("default_at", 0.5), + ("default_above", 0.51), + ("strict_below", 0.69), + ("strict_above", 0.71), + ("negative_true", 0.9), + ("negative_false", 0.2), + ]); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 1, "{out:?}"); + assert_eq!( + out.stdout, + "a.rb: [default_above] q (noul 0.51)\n\ + a.rb: [negative_false] q (noul 0.2)\n\ + a.rb: [strict_above] q (noul 0.71)\n\ + \n\ + 1 file inspected, 3 offenses detected\n" + ); +} diff --git a/crates/lintus/tests/conformance/files.rs b/crates/lintus/tests/conformance/files.rs new file mode 100644 index 0000000..21ae73b --- /dev/null +++ b/crates/lintus/tests/conformance/files.rs @@ -0,0 +1,265 @@ +//! Which files a run looks at: the whole tree, --diff, --staged, explicit paths, and globs. + +use crate::CONFIG; +use crate::support::{FakeJev, Project}; + +const ALL_FILES: &str = "rules:\n r:\n question: q\n"; + +fn listed(out: &crate::support::Output) -> Vec<&str> { + assert_eq!(out.code, 0, "{out:?}"); + let mut lines: Vec<&str> = out.stdout.lines().collect(); + lines.pop(); // the "N file(s) would be checked" summary + lines.into_iter().map(|line| line.rsplit_once(": ").unwrap().0).collect() +} + +#[test] +fn the_whole_tree_is_tracked_and_untracked_files_minus_ignored_ones() { + let project = Project::git_repo(); + project.write(".lintus.yml", ALL_FILES); + project.write("lib/a.rb", "a\n"); + project.write(".gitignore", "ignored.rb\n"); + project.commit_all("init"); + project.write("untracked.rb", "u\n"); + project.write("ignored.rb", "i\n"); + + let out = project.lintus(&["--list"]).run(); + + assert_eq!(listed(&out), [".gitignore", ".lintus.yml", "lib/a.rb", "untracked.rb"]); +} + +#[test] +fn deleted_tracked_files_are_skipped() { + let project = Project::git_repo(); + project.write(".lintus.yml", ALL_FILES); + project.write("lib/a.rb", "a\n"); + project.write("lib/b.rb", "b\n"); + project.commit_all("init"); + project.remove("lib/a.rb"); + + assert_eq!(listed(&project.lintus(&["--list"]).run()), [".lintus.yml", "lib/b.rb"]); + assert_eq!(listed(&project.lintus(&["--list", "--diff"]).run()), Vec::<&str>::new()); +} + +#[test] +fn outside_git_the_tree_is_walked_including_dotfiles() { + let project = Project::new(); + project.write(".lintus.yml", ALL_FILES); + project.write("a.rb", "a\n"); + project.write("sub/.hidden", "h\n"); + project.write("sub/deeper/c.rb", "c\n"); + project.mkdir("empty/dir"); + + assert_eq!(listed(&project.lintus(&["--list"]).run()), [".lintus.yml", "a.rb", "sub/.hidden", "sub/deeper/c.rb"]); +} + +#[test] +fn diff_uses_the_merge_base_plus_uncommitted_and_untracked_changes() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/a.rb", "a\n"); + project.write("lib/b.rb", "b\n"); + project.commit_all("init"); + let main = project.git(&["rev-parse", "--abbrev-ref", "HEAD"]); + let main = main.trim(); + project.git(&["checkout", "-qb", "feature"]); + project.write("lib/a.rb", "changed on feature\n"); + project.commit_all("change a"); + project.git(&["checkout", "-q", main]); + project.write("lib/b.rb", "changed on main\n"); + project.commit_all("change b on main"); + project.git(&["checkout", "-q", "feature"]); + project.write("lib/c.rb", "staged\n"); + project.write("lib/d.rb", "untracked\n"); + project.git(&["add", "lib/c.rb"]); + + assert_eq!(listed(&project.lintus(&["--list", "--diff", main]).run()), ["lib/a.rb", "lib/c.rb", "lib/d.rb"]); + assert_eq!(listed(&project.lintus(&["--list", &format!("--diff={main}")]).run()), ["lib/a.rb", "lib/c.rb", "lib/d.rb"]); + assert_eq!(listed(&project.lintus(&["--list", "--diff"]).run()), ["lib/c.rb", "lib/d.rb"]); + assert_eq!(listed(&project.lintus(&["--list", "-d"]).run()), ["lib/c.rb", "lib/d.rb"]); + assert_eq!(listed(&project.lintus(&["--diff", "HEAD", "--list"]).run()), ["lib/c.rb", "lib/d.rb"]); +} + +#[test] +fn diff_against_an_unknown_ref_fails() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/a.rb", "a\n"); + project.commit_all("init"); + + let out = project.lintus(&["--list", "--diff", "no-such-ref"]).run(); + + assert_eq!(out.code, 2); + assert!(out.stderr.starts_with("lintus: git diff"), "{}", out.stderr); + assert!(out.stderr.contains("failed"), "{}", out.stderr); +} + +#[test] +fn staged_lints_the_index_not_the_working_tree() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/a.rb", "staged\n"); + project.git(&["add", "lib/a.rb"]); + project.write("lib/a.rb", "working tree\n"); + project.write("lib/unstaged.rb", "u\n"); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + assert_eq!(listed(&project.lintus(&["--list", "--staged"]).run()), ["lib/a.rb"]); + assert_eq!(listed(&project.lintus(&["--list", "-s"]).run()), ["lib/a.rb"]); + + let out = project.lintus(&["--staged"]).api(&server).run(); + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(out.stdout, "1 file inspected, 0 offenses detected\n"); + assert_eq!(server.requests()[0].state(), "File: lib/a.rb\n\nstaged\n"); +} + +#[test] +fn staged_lints_a_file_deleted_from_the_working_tree_but_not_a_deleted_one() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("lib/gone.rb", "committed\n"); + project.commit_all("init"); + project.write("lib/new.rb", "new\n"); + project.git(&["add", "lib/new.rb"]); + project.remove("lib/new.rb"); + project.git(&["rm", "-q", "lib/gone.rb"]); + + assert_eq!(listed(&project.lintus(&["--list", "--staged"]).run()), ["lib/new.rb"]); +} + +#[test] +fn diff_and_staged_need_a_git_repository() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + + for flag in ["--staged", "--diff"] { + let out = project.lintus(&["--list", flag]).run(); + assert_eq!(out.code, 2); + assert_eq!( + out.stderr, + format!("lintus: {} is not a git repository: --diff and --staged need git\n", project.root().display()) + ); + } +} + +#[test] +fn explicit_paths_are_resolved_from_the_current_directory() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("app/jobs/a.rb", "a\n"); + project.write("app/b.rb", "b\n"); + project.write("lib/c.rb", "c\n"); + + let out = project.lintus(&["--list", "jobs", "../lib/c.rb", "missing.rb", "jobs/a.rb"]).in_dir("app").run(); + + assert_eq!(listed(&out), ["app/jobs/a.rb", "lib/c.rb"]); +} + +#[test] +fn explicit_paths_outside_the_root_are_rejected() { + let project = Project::new(); + project.write("inner/.lintus.yml", CONFIG); + project.write("outer.rb", "o\n"); + + let out = project.lintus(&["--list", "../outer.rb"]).in_dir("inner").run(); + + assert_eq!(out.code, 2); + assert_eq!( + out.stderr, + format!("lintus: {} is outside the config root {}\n", project.path("outer.rb").display(), project.path("inner").display()) + ); +} + +#[test] +fn explicit_directories_honour_gitignore_inside_a_repository() { + let project = Project::git_repo(); + project.write(".lintus.yml", ALL_FILES); + project.write("app/a.rb", "a\n"); + project.write("app/ignored.rb", "i\n"); + project.write(".gitignore", "ignored.rb\n"); + project.commit_all("init"); + project.write("app/untracked.rb", "u\n"); + + let out = project.lintus(&["--list", "app", "."]).run(); + + assert_eq!(listed(&out), [".gitignore", ".lintus.yml", "app/a.rb", "app/untracked.rb"]); +} + +#[test] +fn globs_follow_the_documented_rules() { + let project = Project::new(); + project.write( + ".lintus.yml", + r#" +exclude: ["tmp"] +rules: + double_star: + question: q + paths: ["app/**/*.rb"] + single_star: + question: q + paths: ["app/*.rb"] + bare_dir: + question: q + paths: ["./vendor/"] + trailing_double_star: + question: q + paths: ["config/**"] + braces: + question: q + paths: ["**/*.{yml,yaml}"] + exact: + question: q + paths: ["Gemfile"] + classes: + question: q + paths: ["lib/[a-c]?.rb", "lib/[!a-c]x.rb"] + excluded: + question: q + paths: ["**/*"] + exclude: ["**/*_test.rb", "vendor", "config/**", "*.{yml,yaml}", ".*/**", "lib", "app", "Gemfile"] +"#, + ); + for file in [ + "app/top.rb", + "app/jobs/nested.rb", + "vendor/gem/lib/v.rb", + "vendored/x.rb", + "config/a.yaml", + "config/deep/b.yml", + ".github/workflows/ci.yml", + "Gemfile", + "sub/Gemfile", + "lib/ab.rb", + "lib/dx.rb", + "lib/ax.rb", + "unit_test.rb", + "tmp/cache.rb", + "other.txt", + ] { + project.write(file, "x\n"); + } + + let out = project.lintus(&["--list"]).run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!( + out.stdout, + "\ +.github/workflows/ci.yml: braces +.lintus.yml: braces +Gemfile: exact +app/jobs/nested.rb: double_star +app/top.rb: double_star, single_star +config/a.yaml: trailing_double_star, braces +config/deep/b.yml: trailing_double_star, braces +lib/ab.rb: classes +lib/ax.rb: classes +lib/dx.rb: classes +other.txt: excluded +sub/Gemfile: excluded +vendor/gem/lib/v.rb: bare_dir +vendored/x.rb: excluded +14 file(s) would be checked +" + ); +} diff --git a/crates/lintus/tests/conformance/main.rs b/crates/lintus/tests/conformance/main.rs new file mode 100644 index 0000000..56b61f3 --- /dev/null +++ b/crates/lintus/tests/conformance/main.rs @@ -0,0 +1,24 @@ +//! Black-box tests of the `lintus` command line: they run the binary against +//! temporary projects and a fake Jev API, and check what it prints, the exit +//! status, and what it sent. See support.rs for how to point them at another +//! implementation. + +mod support; + +mod cli; +mod config; +mod files; +mod output; +mod runner; + +/// The config most tests use: one rule scoped to jobs, one negative rule for every Ruby file. +pub const CONFIG: &str = r#"paths: ["**/*.rb"] +rules: + no_sleep: + description: Jobs must not sleep. + question: Does this file call sleep? + paths: ["app/jobs/**/*.rb"] + documented: + question: Is every class documented? + offense_when: false +"#; diff --git a/crates/lintus/tests/conformance/output.rs b/crates/lintus/tests/conformance/output.rs new file mode 100644 index 0000000..ba4bf5b --- /dev/null +++ b/crates/lintus/tests/conformance/output.rs @@ -0,0 +1,199 @@ +//! The text, github and json formats, byte for byte. + +use crate::CONFIG; +use crate::support::{FakeJev, Project, Reply}; + +/// A project with one file of each outcome: offenses, clean, skipped, failed. +fn mixed_run(format: &str) -> crate::support::Output { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("app/jobs/a_job.rb", "sleep 1\n"); + project.write("lib/ok.rb", "# Ok\nclass Ok; end\n"); + project.write("lib/blob.rb", b"\0\0"); + project.write("lib/bad.rb", "class Bad; end\n"); + let server = FakeJev::start(|request, _| match request.file() { + "lib/bad.rb" => Reply::status(500, "boom"), + "lib/ok.rb" => Reply::nouls(request, &[("documented", 0.9)]), + _ => Reply::nouls(request, &[("no_sleep", 0.91234), ("documented", 0.3)]), + }); + project.lintus(&["--format", format]).api(&server).run() +} + +#[test] +fn text() { + let out = mixed_run("text"); + + assert_eq!(out.code, 2); + assert_eq!( + out.stdout, + "\ +app/jobs/a_job.rb: [documented] Is every class documented? (noul 0.3) +app/jobs/a_job.rb: [no_sleep] Jobs must not sleep. (noul 0.91) +lib/blob.rb: skipped, binary file +lib/bad.rb: failed, Jev API error 500: boom + +2 files inspected, 2 offenses detected, 1 file skipped, 1 file failed +" + ); +} + +#[test] +fn github() { + let out = mixed_run("github"); + + assert_eq!(out.code, 2); + assert_eq!( + out.stdout, + "\ +::error file=app/jobs/a_job.rb,title=lintus%3A documented::Is every class documented? +::error file=app/jobs/a_job.rb,title=lintus%3A no_sleep::Jobs must not sleep. +::notice file=lib/blob.rb,title=lintus::Skipped: binary file +::error file=lib/bad.rb,title=lintus%3A request failed::Jev API error 500: boom +2 files inspected, 2 offenses detected, 1 file skipped, 1 file failed +" + ); +} + +#[test] +fn json() { + let out = mixed_run("json"); + + assert_eq!(out.code, 2); + assert_eq!( + out.stdout, + r#"{ + "summary": { + "files_inspected": 2, + "offenses": 2, + "skipped": 1, + "failures": 1 + }, + "offenses": [ + { + "path": "app/jobs/a_job.rb", + "rule": "documented", + "message": "Is every class documented?", + "noul": 0.3 + }, + { + "path": "app/jobs/a_job.rb", + "rule": "no_sleep", + "message": "Jobs must not sleep.", + "noul": 0.912 + } + ], + "files": [ + { + "path": "app/jobs/a_job.rb", + "answers": { + "no_sleep": 0.912, + "documented": 0.3 + } + }, + { + "path": "lib/ok.rb", + "answers": { + "documented": 0.9 + } + } + ], + "skipped": [ + { + "path": "lib/blob.rb", + "reason": "binary file" + } + ], + "failures": [ + { + "path": "lib/bad.rb", + "error": "Jev API error 500: boom" + } + ] +} +"# + ); +} + +#[test] +fn json_when_clean() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + let server = FakeJev::nouls(&[]); + + let out = project.lintus(&["--format", "json"]).api(&server).run(); + + assert_eq!(out.code, 0); + assert_eq!( + out.stdout, + r#"{ + "summary": { + "files_inspected": 0, + "offenses": 0, + "skipped": 0, + "failures": 0 + }, + "offenses": [], + "files": [], + "skipped": [], + "failures": [] +} +"# + ); +} + +#[test] +fn github_escapes_messages_and_properties() { + let project = Project::new(); + project.write( + ".lintus.yml", + "paths: [lib/**]\nrules:\n r:\n question: q\n description: \"100%\\nsure, really: yes\\r\"\n", + ); + project.write("lib/a,b:c.rb", "x\n"); + project.write("lib/fail.rb", "x\n"); + let server = FakeJev::start(|request, _| match request.file() { + "lib/fail.rb" => Reply::status(500, "line 1\nline 2: 50%"), + _ => Reply::nouls(request, &[("r", 1.0)]), + }); + + let out = project.lintus(&["--format", "github"]).api(&server).run(); + + assert_eq!( + out.stdout, + "\ +::error file=lib/a%2Cb%3Ac.rb,title=lintus%3A r::100%25%0Asure, really: yes%0D +::error file=lib/fail.rb,title=lintus%3A request failed::Jev API error 500: line 1%0Aline 2: 50%25 +1 file inspected, 1 offense detected, 1 file failed +" + ); +} + +#[test] +fn probabilities_are_rounded_like_ruby_floats() { + let cases: &[(f64, &str, &str)] = &[ + (1.0, "1.0", "1.0"), + (0.0, "0.0", "0.0"), + (0.999, "1.0", "0.999"), + (0.9996, "1.0", "1.0"), + (0.125, "0.13", "0.125"), + (0.1, "0.1", "0.1"), + (0.51, "0.51", "0.51"), + (0.5049, "0.5", "0.505"), + (0.0001, "0.0", "0.0"), + (0.7, "0.7", "0.7"), + ]; + let project = Project::new(); + project.write(".lintus.yml", "paths: [\"*.rb\"]\nrules:\n r:\n question: q\n threshold: 0\n offense_when: true\n"); + project.write("a.rb", "x\n"); + + for &(noul, text, json) in cases { + let server = FakeJev::start(move |request, _| Reply::nouls(request, &[("r", noul)])); + // A threshold of 0 makes everything above 0 an offense; 0.0 itself is clean. + let out = project.lintus(&[]).api(&server).run(); + if noul > 0.0 { + assert_eq!(out.stdout.lines().next().unwrap(), format!("a.rb: [r] q (noul {text})"), "{noul}"); + } + let out = project.lintus(&["--format", "json"]).api(&server).run(); + let files = out.stdout.split("\"files\": [").nth(1).unwrap(); + assert!(files.contains(&format!("\"r\": {json}\n")), "{noul}: {}", out.stdout); + } +} diff --git a/crates/lintus/tests/conformance/runner.rs b/crates/lintus/tests/conformance/runner.rs new file mode 100644 index 0000000..d85e37b --- /dev/null +++ b/crates/lintus/tests/conformance/runner.rs @@ -0,0 +1,209 @@ +//! What is sent to the API, and how answers, skips, errors and retries turn into results. + +use std::time::Duration; + +use crate::CONFIG; +use crate::support::{FakeJev, Project, Reply}; + +#[test] +fn one_request_per_file_with_every_rule_that_applies() { + let project = Project::git_repo(); + project.write(".lintus.yml", CONFIG); + project.write("app/jobs/a_job.rb", "sleep 1\n"); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::nouls(&[("no_sleep", 0.1), ("documented", 0.9)]); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 0, "{out:?}"); + let requests = server.requests_by_file(); + assert_eq!(requests.len(), 2); + + let job = &requests[0]; + assert_eq!(job.method, "POST"); + assert_eq!(job.url, "/v1/systemone"); + assert_eq!(job.authorization.as_deref(), Some("Bearer test-key")); + assert_eq!(job.content_type.as_deref(), Some("application/json")); + assert_eq!(job.body["model"], "jev-latest"); + assert_eq!(job.state(), "File: app/jobs/a_job.rb\n\nsleep 1\n"); + assert_eq!(job.question_ids(), ["no_sleep", "documented"]); + assert_eq!(job.question("no_sleep"), &serde_json::json!({ "type": "noul", "instructions": "Does this file call sleep?" })); + assert_eq!(job.body.as_object().unwrap().keys().collect::>(), ["model", "state", "questions"]); + + assert_eq!(requests[1].state(), "File: lib/b.rb\n\nclass B; end\n"); + assert_eq!(requests[1].question_ids(), ["documented"]); +} + +#[test] +fn binary_and_oversized_files_are_skipped_without_a_request() { + let project = Project::new(); + project.write(".lintus.yml", format!("max_file_size: 10\n{CONFIG}")); + project.write("lib/bin.rb", b"ab\0cd"); + project.write("lib/latin1.rb", b"caf\xe9\n"); + project.write("lib/big.rb", "x".repeat(11)); + project.write("lib/limit.rb", "x".repeat(10)); + project.write("lib/utf8.rb", "café\n"); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + let out = project.lintus(&["--jobs", "1"]).api(&server).run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!( + out.stdout, + "\ +lib/big.rb: skipped, larger than max_file_size (11 > 10 bytes) +lib/bin.rb: skipped, binary file +lib/latin1.rb: skipped, binary file + +2 files inspected, 0 offenses detected, 3 files skipped +" + ); + let files: Vec<_> = server.requests_by_file().iter().map(|r| r.file().to_string()).collect(); + assert_eq!(files, ["lib/limit.rb", "lib/utf8.rb"]); +} + +#[test] +fn the_default_max_file_size_is_100000_bytes() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("lib/big.rb", "x".repeat(100_001)); + project.write("lib/ok.rb", "x".repeat(100_000)); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + let out = project.lintus(&["-j", "1"]).api(&server).run(); + + assert_eq!(out.stdout, "lib/big.rb: skipped, larger than max_file_size (100001 > 100000 bytes)\n\n1 file inspected, 0 offenses detected, 1 file skipped\n"); +} + +#[test] +fn rate_limits_are_retried_then_succeed() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::start(|request, index| match index { + 0 => Reply::status(429, "slow down"), + _ => Reply::nouls(request, &[("documented", 0.9)]), + }); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(out.stdout, "1 file inspected, 0 offenses detected\n"); + assert_eq!(server.requests().len(), 2); +} + +#[test] +fn overloaded_responses_are_retried_then_succeed() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::start(|request, index| match index { + 0 => Reply::status(529, "overloaded"), + _ => Reply::nouls(request, &[("documented", 0.1)]), + }); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 1, "{out:?}"); + assert_eq!(server.requests().len(), 2); +} + +#[test] +fn other_api_errors_fail_the_file_without_retrying() { + for (status, body) in [(401, "bad key"), (422, "{\"error\":\"invalid\"}"), (500, "boom")] { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::start(move |_, _| Reply::status(status, body)); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 2, "{status}: {out:?}"); + assert_eq!( + out.stdout, + format!("lib/b.rb: failed, Jev API error {status}: {body}\n\n0 files inspected, 0 offenses detected, 1 file failed\n") + ); + assert_eq!(server.requests().len(), 1, "{status}"); + } +} + +#[test] +fn a_failed_file_does_not_stop_the_others() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("lib/a.rb", "class A; end\n"); + project.write("lib/b.rb", "class B; end\n"); + project.write("lib/c.rb", "class C; end\n"); + let server = FakeJev::start(|request, _| match request.file() { + "lib/b.rb" => Reply::status(500, "boom"), + _ => Reply::nouls(request, &[("documented", 0.1)]), + }); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 2, "{out:?}"); + assert_eq!( + out.stdout, + "\ +lib/a.rb: [documented] Is every class documented? (noul 0.1) +lib/c.rb: [documented] Is every class documented? (noul 0.1) +lib/b.rb: failed, Jev API error 500: boom + +2 files inspected, 2 offenses detected, 1 file failed +" + ); +} + +#[test] +fn a_rule_left_unanswered_fails_the_file() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("app/jobs/a_job.rb", "class AJob; end\n"); + let server = FakeJev::nouls(&[("no_sleep", 0.9)]); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.code, 2, "{out:?}"); + assert_eq!( + out.stdout, + "app/jobs/a_job.rb: failed, documented has not been answered\n\n0 files inspected, 0 offenses detected, 1 file failed\n" + ); +} + +#[test] +fn files_are_checked_concurrently_up_to_jobs() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + for i in 0..6 { + project.write(&format!("lib/f{i}.rb"), "class F; end\n"); + } + let slow = || FakeJev::start(|request, _| Reply::nouls(request, &[("documented", 0.9)]).after(Duration::from_millis(150))); + + let server = slow(); + let out = project.lintus(&["--jobs", "3"]).api(&server).run(); + assert_eq!(out.stdout, "6 files inspected, 0 offenses detected\n", "{out:?}"); + assert_eq!(server.requests().len(), 6); + assert!((2..=3).contains(&server.max_in_flight()), "{} in flight", server.max_in_flight()); + + let server = slow(); + project.lintus(&["--jobs", "1"]).api(&server).run(); + assert_eq!(server.max_in_flight(), 1); + + let server = slow(); + project.lintus(&["--jobs", "0"]).api(&server).run(); + assert_eq!(server.max_in_flight(), 1); + assert_eq!(server.requests().len(), 6); +} + +#[test] +fn progress_is_one_line_on_stderr_off_a_terminal() { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("lib/a.rb", "class A; end\n"); + project.write("lib/b.rb", "class B; end\n"); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + let out = project.lintus(&[]).api(&server).run(); + + assert_eq!(out.stderr, "Checking 2 files...\n"); +} diff --git a/crates/lintus/tests/conformance/support.rs b/crates/lintus/tests/conformance/support.rs new file mode 100644 index 0000000..a5036e9 --- /dev/null +++ b/crates/lintus/tests/conformance/support.rs @@ -0,0 +1,355 @@ +//! What the conformance tests are built on: throwaway projects, a fake Jev API, +//! and a way to run the lintus binary under test. +//! +//! The binary is the one cargo builds, unless `LINTUS_BIN` names another one +//! (a path relative to the workspace root works), so the same suite can check +//! any implementation of the command line. + +#![allow(dead_code)] + +use std::path::{Path, PathBuf}; +use std::process; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +use serde_json::{Value, json}; +use tempfile::TempDir; + +pub const API_KEY: &str = "test-key"; + +pub fn lintus_bin() -> PathBuf { + match std::env::var_os("LINTUS_BIN") { + Some(bin) => Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").join(bin), + None => PathBuf::from(env!("CARGO_BIN_EXE_lintus")), + } +} + +/// A temporary directory to run lintus in, with a separate one standing in for +/// the user's config directory so no real credentials or git config leak in. +pub struct Project { + dir: TempDir, + home: TempDir, + root: PathBuf, +} + +impl Project { + pub fn new() -> Self { + let dir = tempfile::Builder::new().prefix("lintus").tempdir().unwrap(); + let home = tempfile::Builder::new().prefix("lintus-home").tempdir().unwrap(); + let root = dir.path().canonicalize().unwrap(); + Project { dir, home, root } + } + + /// A project that is also a git repository, with no commits yet. + pub fn git_repo() -> Self { + let project = Project::new(); + project.git(&["init", "-q"]); + project.git(&["config", "user.email", "test@example.com"]); + project.git(&["config", "user.name", "Lintus Test"]); + project.git(&["config", "commit.gpgsign", "false"]); + project + } + + pub fn root(&self) -> &Path { + &self.root + } + + /// The stand-in for the user's config directory (`$XDG_CONFIG_HOME`). + pub fn config_home(&self) -> &Path { + self.home.path() + } + + pub fn path(&self, relative: &str) -> PathBuf { + self.root.join(relative) + } + + pub fn write(&self, relative: &str, content: impl AsRef<[u8]>) -> &Self { + let path = self.path(relative); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(path, content).unwrap(); + self + } + + pub fn read(&self, relative: &str) -> String { + std::fs::read_to_string(self.path(relative)).unwrap() + } + + pub fn remove(&self, relative: &str) { + std::fs::remove_file(self.path(relative)).unwrap(); + } + + pub fn mkdir(&self, relative: &str) { + std::fs::create_dir_all(self.path(relative)).unwrap(); + } + + pub fn git(&self, args: &[&str]) -> String { + let output = isolate_git(process::Command::new("git")) + .args(args) + .current_dir(&self.root) + .output() + .unwrap(); + assert!( + output.status.success(), + "git {} failed: {}", + args.join(" "), + String::from_utf8_lossy(&output.stderr) + ); + String::from_utf8(output.stdout).unwrap() + } + + pub fn commit_all(&self, message: &str) { + self.git(&["add", "-A"]); + self.git(&["commit", "-qm", message]); + } + + pub fn lintus(&self, args: &[&str]) -> Lintus { + Lintus { + args: args.iter().map(|arg| arg.to_string()).collect(), + cwd: self.root.clone(), + env: vec![("XDG_CONFIG_HOME".into(), Some(self.home.path().display().to_string()))], + stdin: None, + } + } + + // Keeps the TempDir alive as long as the project. + fn _dir(&self) -> &TempDir { + &self.dir + } +} + +fn isolate_git(mut command: process::Command) -> process::Command { + command.env("GIT_CONFIG_GLOBAL", "/dev/null").env("GIT_CONFIG_NOSYSTEM", "1"); + command +} + +/// One invocation of the binary under test, built up then `run`. +pub struct Lintus { + args: Vec, + cwd: PathBuf, + env: Vec<(String, Option)>, + stdin: Option, +} + +impl Lintus { + /// Runs from a subdirectory of the project instead of its root. + pub fn in_dir(mut self, relative: &str) -> Self { + self.cwd = self.cwd.join(relative); + self + } + + pub fn env(mut self, key: &str, value: &str) -> Self { + self.env.push((key.into(), Some(value.into()))); + self + } + + pub fn stdin(mut self, input: &str) -> Self { + self.stdin = Some(input.into()); + self + } + + /// Talks to the given fake server with the test API key. + pub fn api(self, server: &FakeJev) -> Self { + self.env("JEV_API_URL", &server.url()).env("JEV_API_KEY", API_KEY) + } + + /// Talks to the given fake server without an API key in the environment. + pub fn api_without_key(self, server: &FakeJev) -> Self { + self.env("JEV_API_URL", &server.url()) + } + + pub fn run(self) -> Output { + let mut command = isolate_git(process::Command::new(lintus_bin())); + command.args(&self.args).current_dir(&self.cwd); + for key in ["JEV_API_KEY", "JEV_API_URL", "GITHUB_ACTIONS", "APPDATA"] { + command.env_remove(key); + } + for (key, value) in &self.env { + match value { + Some(value) => command.env(key, value), + None => command.env_remove(key), + }; + } + command.stdin(if self.stdin.is_some() { process::Stdio::piped() } else { process::Stdio::null() }); + command.stdout(process::Stdio::piped()).stderr(process::Stdio::piped()); + + let mut child = command.spawn().unwrap_or_else(|e| panic!("could not run {}: {e}", lintus_bin().display())); + if let Some(input) = &self.stdin { + use std::io::Write; + child.stdin.take().unwrap().write_all(input.as_bytes()).unwrap(); + } + let output = child.wait_with_output().unwrap(); + Output { + code: output.status.code().expect("lintus was killed by a signal"), + stdout: String::from_utf8(output.stdout).unwrap(), + stderr: String::from_utf8(output.stderr).unwrap(), + } + } +} + +#[derive(Debug)] +pub struct Output { + pub code: i32, + pub stdout: String, + pub stderr: String, +} + +/// A request the fake server received. +#[derive(Debug, Clone)] +pub struct Request { + pub method: String, + pub url: String, + pub authorization: Option, + pub content_type: Option, + pub body: Value, +} + +impl Request { + pub fn state(&self) -> &str { + self.body["state"].as_str().unwrap() + } + + /// The file path lintus put at the top of the state. + pub fn file(&self) -> &str { + self.state().strip_prefix("File: ").unwrap().split("\n\n").next().unwrap() + } + + pub fn question_ids(&self) -> Vec { + self.body["questions"].as_object().unwrap().keys().cloned().collect() + } + + pub fn question(&self, id: &str) -> &Value { + &self.body["questions"][id] + } +} + +/// What the fake server answers to one request. +pub struct Reply { + status: u16, + body: String, + delay: Duration, +} + +impl Reply { + /// A successful answer giving each listed question its noul, for the + /// questions the request actually asked. + pub fn nouls(request: &Request, nouls: &[(&str, f64)]) -> Reply { + let asked = request.question_ids(); + let answers: serde_json::Map = nouls + .iter() + .filter(|(id, _)| asked.iter().any(|asked| asked == id)) + .map(|(id, noul)| (id.to_string(), json!({ "type": "noul", "noul": noul }))) + .collect(); + Reply::json(200, json!({ "model": "jev-1.13.0", "answers": answers })) + } + + pub fn json(status: u16, body: Value) -> Reply { + Reply::status(status, &body.to_string()) + } + + pub fn status(status: u16, body: &str) -> Reply { + Reply { status, body: body.into(), delay: Duration::ZERO } + } + + /// Holds the response back, so concurrent requests overlap. + pub fn after(mut self, delay: Duration) -> Reply { + self.delay = delay; + self + } +} + +type Handler = dyn Fn(&Request, usize) -> Reply + Send + Sync; + +/// A local stand-in for the Jev API. It records every request and answers with +/// a handler, which also gets the request's index (0 for the first). +pub struct FakeJev { + server: Arc, + requests: Arc>>, + max_in_flight: Arc, +} + +impl FakeJev { + pub fn start(handler: impl Fn(&Request, usize) -> Reply + Send + Sync + 'static) -> FakeJev { + let server = Arc::new(tiny_http::Server::http("127.0.0.1:0").unwrap()); + let requests = Arc::new(Mutex::new(Vec::new())); + let in_flight = Arc::new(AtomicUsize::new(0)); + let max_in_flight = Arc::new(AtomicUsize::new(0)); + let handler: Arc = Arc::new(handler); + + let fake = FakeJev { server: server.clone(), requests: requests.clone(), max_in_flight: max_in_flight.clone() }; + thread::spawn(move || { + for mut incoming in server.incoming_requests() { + let (requests, in_flight, max_in_flight, handler) = + (requests.clone(), in_flight.clone(), max_in_flight.clone(), handler.clone()); + thread::spawn(move || { + let now = in_flight.fetch_add(1, Ordering::SeqCst) + 1; + max_in_flight.fetch_max(now, Ordering::SeqCst); + + let mut body = String::new(); + incoming.as_reader().read_to_string(&mut body).unwrap(); + let header = |name: &str| { + incoming + .headers() + .iter() + .find(|h| h.field.to_string().eq_ignore_ascii_case(name)) + .map(|h| h.value.as_str().to_string()) + }; + let request = Request { + method: incoming.method().as_str().to_string(), + url: incoming.url().to_string(), + authorization: header("Authorization"), + content_type: header("Content-Type"), + body: serde_json::from_str(&body).unwrap_or(Value::Null), + }; + let index = { + let mut requests = requests.lock().unwrap(); + requests.push(request.clone()); + requests.len() - 1 + }; + + let reply = handler(&request, index); + thread::sleep(reply.delay); + in_flight.fetch_sub(1, Ordering::SeqCst); + let response = tiny_http::Response::from_string(reply.body) + .with_status_code(reply.status) + .with_header("Content-Type: application/json".parse::().unwrap()); + let _ = incoming.respond(response); + }); + } + }); + fake + } + + /// Answers every request with the given nouls. + pub fn nouls(nouls: &[(&'static str, f64)]) -> FakeJev { + let nouls = nouls.to_vec(); + FakeJev::start(move |request, _| Reply::nouls(request, &nouls)) + } + + pub fn url(&self) -> String { + format!("http://{}/v1/systemone", self.server.server_addr().to_ip().unwrap()) + } + + pub fn requests(&self) -> Vec { + self.requests.lock().unwrap().clone() + } + + /// Requests sorted by the file they were about, since workers finish in any order. + pub fn requests_by_file(&self) -> Vec { + let mut requests = self.requests(); + requests.sort_by(|a, b| a.file().cmp(b.file())); + requests + } + + /// The most requests the server was handling at the same time. + pub fn max_in_flight(&self) -> usize { + self.max_in_flight.load(Ordering::SeqCst) + } +} + +impl Drop for FakeJev { + fn drop(&mut self) { + self.server.unblock(); + } +} diff --git a/test/support/jev_api_url.rb b/test/support/jev_api_url.rb new file mode 100644 index 0000000..0e3684b --- /dev/null +++ b/test/support/jev_api_url.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Loaded through RUBYOPT by test/support/lintus-ruby: points the Jev client at +# $JEV_API_URL so the conformance suite can run the Ruby lintus against a fake server. +require "jev" + +if (url = ENV.fetch("JEV_API_URL", nil)) + Jev::Client.send(:remove_const, :API_URL) + Jev::Client.const_set(:API_URL, url) +end diff --git a/test/support/lintus-ruby b/test/support/lintus-ruby new file mode 100755 index 0000000..68daca1 --- /dev/null +++ b/test/support/lintus-ruby @@ -0,0 +1,7 @@ +#!/bin/sh +# Runs the Ruby lintus from this checkout, honouring $JEV_API_URL. Used to check +# the conformance suite against the Ruby implementation: +# +# LINTUS_BIN=test/support/lintus-ruby cargo test -p lintus --test conformance +root=$(cd "$(dirname "$0")/../.." && pwd) +RUBYOPT="-I$root/lib -r$root/test/support/jev_api_url.rb $RUBYOPT" exec ruby "$root/exe/lintus" "$@" From 84f0b8c65bbd1de8b5a9ddcdb97ce12e1f8ac521 Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:06:40 +0200 Subject: [PATCH 2/9] Add the jev crate, a typed client for the Jev API Noul, choice and score questions, with the answers typed after the question they answer. API error statuses map to the same kinds as the Ruby gem, and rate limits are left to the caller to retry. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 902 ++++++++++++++++++++++++++++++++++++- crates/jev/Cargo.toml | 7 + crates/jev/README.md | 42 ++ crates/jev/src/client.rs | 103 +++++ crates/jev/src/error.rs | 102 +++++ crates/jev/src/lib.rs | 40 ++ crates/jev/src/query.rs | 70 +++ crates/jev/src/question.rs | 230 ++++++++++ crates/jev/src/response.rs | 316 +++++++++++++ crates/jev/tests/client.rs | 118 +++++ 10 files changed, 1925 insertions(+), 5 deletions(-) create mode 100644 crates/jev/README.md create mode 100644 crates/jev/src/client.rs create mode 100644 crates/jev/src/error.rs create mode 100644 crates/jev/src/query.rs create mode 100644 crates/jev/src/question.rs create mode 100644 crates/jev/src/response.rs create mode 100644 crates/jev/tests/client.rs diff --git a/Cargo.lock b/Cargo.lock index 966ed56..3d2ea23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,34 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bitflags" version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.5" @@ -26,6 +48,87 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "cookie" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -39,7 +142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -48,6 +151,32 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "find-msvc-tools" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -65,12 +194,132 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.14.2" @@ -79,6 +328,8 @@ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown", + "serde", + "serde_core", ] [[package]] @@ -90,6 +341,62 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jev" version = "0.1.0" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "tiny_http", + "ureq", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] [[package]] name = "libc" @@ -112,6 +419,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "log" version = "0.4.34" @@ -124,12 +443,45 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.107" @@ -154,6 +506,29 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.5" @@ -164,9 +539,130 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.229" @@ -174,6 +670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -193,7 +690,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] @@ -210,6 +707,57 @@ dependencies = [ "zmij", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "smallvec" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "3.0.6" @@ -221,6 +769,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -228,10 +787,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -246,18 +855,148 @@ dependencies = [ "log", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "unicode-ident" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" +dependencies = [ + "base64", + "cookie_store", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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 = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -267,6 +1006,159 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "zmij" version = "1.0.23" diff --git a/crates/jev/Cargo.toml b/crates/jev/Cargo.toml index dc600a8..d6aac1e 100644 --- a/crates/jev/Cargo.toml +++ b/crates/jev/Cargo.toml @@ -8,3 +8,10 @@ authors.workspace = true repository.workspace = true [dependencies] +indexmap = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, features = ["preserve_order"] } +ureq = { version = "3", default-features = false, features = ["rustls", "platform-verifier", "json"] } + +[dev-dependencies] +tiny_http.workspace = true diff --git a/crates/jev/README.md b/crates/jev/README.md new file mode 100644 index 0000000..a77c75b --- /dev/null +++ b/crates/jev/README.md @@ -0,0 +1,42 @@ +# jev + +A typed Rust client for the [Typesafe](https://typesafe.ai) Jev model API. It is the Rust +counterpart of the [`jev` gem](https://github.com/virolea/jev), and what +[Lintus](https://github.com/virolea/lintus) talks to the API with. + +```rust +use jev::{Client, Noul, Query, Score}; + +let client = Client::new(std::env::var("JEV_API_KEY")?)?; + +let mut query = Query::new("Help! My payouts have been failing for 3 days."); +query.ask("is_urgent", Noul::new("Does this convey urgency?").with_threshold(0.8))?; +query.ask("frustration", Score::new("How frustrated is the customer?", ["Calm", "Frustrated", "Very angry"])?)?; + +let response = client.perform(&query)?; +response.noul("is_urgent")?.result(); // true: 0.95 is above 0.8 +response.noul("is_urgent")?.noul; // 0.95 +response.score("frustration")?.label(); // Some("Frustrated") +``` + +## Questions + +| Type | Answer | Built with | +| ------------------- | ---------------------------------------------------------- | ---------- | +| `Noul` | A probability that the statement is true, and `result()` against a threshold (0.5 by default). | `Noul::new(text)`, then `.with_criteria(when_true, when_false)` and `.with_threshold(t)` | +| `Choice` | The option picked, with a probability for each. | `Choice::new(text, [(name, description), ...])`, 1 to 255 options | +| `Score` | The probability-weighted level (`result()`), and the most probable `level()` and `label()`. | `Score::new(text, [lowest, ..., highest])`, 2 to 10 levels | + +Every question of a query is answered in one request, so several narrow questions cost far +less than several calls. + +Asking for an answer that is not there is an error, never a default: `Error::Unanswered` when +the API gave none, `Error::WrongType` when the question was asked as another type. + +## Errors + +API error statuses come back as `Error::Api`, whose `kind()` is `Authentication` (401), +`Validation` (422), `RateLimit` (429), `Overloaded` (529) or `Other`. Rate limits are not +retried for you; `error.is_retryable()` tells you when backing off and retrying makes sense. + +`Client::with_api_url` points the client at a proxy or a test server. diff --git a/crates/jev/src/client.rs b/crates/jev/src/client.rs new file mode 100644 index 0000000..72fe427 --- /dev/null +++ b/crates/jev/src/client.rs @@ -0,0 +1,103 @@ +use std::fmt; +use std::time::Duration; + +use serde::Serialize; + +use crate::{ApiError, Error, Query, Response, Result}; + +pub const API_URL: &str = "https://api.typesafe.ai/v1/systemone"; +/// The model every request asks for; [`Response::model`] says which version answered. +pub const MODEL: &str = "jev-latest"; + +/// Sends queries to the API. Cheap to clone, and safe to share between threads. +/// +/// Rate limits (429) and overload (529) are not retried: the error says so +/// through [`Error::is_retryable`], and the retry policy is the caller's. +#[derive(Clone)] +pub struct Client { + api_key: String, + api_url: String, + agent: ureq::Agent, +} + +impl Client { + pub fn new(api_key: impl Into) -> Result { + let api_key = api_key.into(); + if api_key.is_empty() { + return Err(Error::MissingApiKey); + } + + let tls = ureq::tls::TlsConfig::builder().root_certs(ureq::tls::RootCerts::PlatformVerifier).build(); + let agent = ureq::Agent::config_builder() + .http_status_as_error(false) + .timeout_connect(Some(Duration::from_secs(60))) + .timeout_recv_response(Some(Duration::from_secs(120))) + .user_agent(concat!("jev-rust/", env!("CARGO_PKG_VERSION"))) + .tls_config(tls) + .build() + .into(); + Ok(Client { api_key, api_url: API_URL.to_string(), agent }) + } + + /// Sends requests somewhere other than the public API, such as a proxy or a test server. + pub fn with_api_url(mut self, api_url: impl Into) -> Client { + self.api_url = api_url.into(); + self + } + + pub fn api_url(&self) -> &str { + &self.api_url + } + + /// Asks every question of the query in one request. + pub fn perform(&self, query: &Query) -> Result { + #[derive(Serialize)] + struct Body<'a> { + model: &'static str, + state: &'a str, + questions: &'a indexmap::IndexMap, + } + + let body = serde_json::to_vec(&Body { model: MODEL, state: query.state(), questions: query.question_map() }) + .map_err(|e| Error::InvalidQuestion(e.to_string()))?; + + let mut response = self + .agent + .post(&self.api_url) + .header("Content-Type", "application/json") + .header("Authorization", &format!("Bearer {}", self.api_key)) + .send(&body[..]) + .map_err(|e| Error::Transport(e.to_string()))?; + + let status = response.status().as_u16(); + let text = response.body_mut().read_to_string().map_err(|e| Error::Transport(e.to_string()))?; + if !(200..300).contains(&status) { + return Err(Error::Api(ApiError { status, body: text })); + } + Response::parse(query, &text) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client").field("api_url", &self.api_url).field("api_key", &"[redacted]").finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn an_empty_api_key_is_refused() { + assert!(matches!(Client::new(""), Err(Error::MissingApiKey))); + } + + #[test] + fn debug_output_hides_the_api_key() { + let client = Client::new("secret-key").unwrap(); + let debug = format!("{client:?}"); + assert!(!debug.contains("secret-key"), "{debug}"); + assert!(debug.contains(API_URL)); + } +} diff --git a/crates/jev/src/error.rs b/crates/jev/src/error.rs new file mode 100644 index 0000000..e5b659c --- /dev/null +++ b/crates/jev/src/error.rs @@ -0,0 +1,102 @@ +use std::fmt; + +pub type Result = std::result::Result; + +/// Everything that can go wrong building a query or talking to the API. +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + /// The client was given an empty API key. + MissingApiKey, + /// The API answered with an error status. + Api(ApiError), + /// The API could not be reached, or the connection broke. + Transport(String), + /// The API answered successfully with something that is not a Jev response. + InvalidResponse(String), + /// A question was badly formed, such as a choice with no options. + InvalidQuestion(String), + /// Two questions of one query share an identifier. + DuplicateQuestion(String), + /// The response has no answer for this question. + Unanswered(String), + /// The question was asked as another type than the one requested. + WrongType { id: String, asked: &'static str, requested: &'static str }, +} + +impl Error { + /// The API error behind this error, if it is one. + pub fn api(&self) -> Option<&ApiError> { + match self { + Error::Api(error) => Some(error), + _ => None, + } + } + + /// Whether the same request may succeed if sent again later: the API was + /// rate limiting or overloaded. + pub fn is_retryable(&self) -> bool { + self.api().is_some_and(|error| matches!(error.kind(), ApiErrorKind::RateLimit | ApiErrorKind::Overloaded)) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::MissingApiKey => write!(f, "Missing API key"), + Error::Api(error) => error.fmt(f), + Error::Transport(message) => write!(f, "could not reach the Jev API: {message}"), + Error::InvalidResponse(message) => write!(f, "unexpected response from the Jev API: {message}"), + Error::InvalidQuestion(message) => write!(f, "{message}"), + Error::DuplicateQuestion(id) => write!(f, "{id} has already been asked"), + Error::Unanswered(id) => write!(f, "{id} has not been answered"), + Error::WrongType { id, asked, requested } => { + write!(f, "{id} was asked as a {asked} question, not a {requested} one") + } + } + } +} + +impl std::error::Error for Error {} + +/// An error status from the API, with the body it came with. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ApiError { + pub status: u16, + pub body: String, +} + +/// What an [`ApiError`] status means. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ApiErrorKind { + /// 401: the API key was refused. + Authentication, + /// 422: the query was refused. + Validation, + /// 429: too many requests. + RateLimit, + /// 529: the model is overloaded. + Overloaded, + /// Any other error status. + Other, +} + +impl ApiError { + pub fn kind(&self) -> ApiErrorKind { + match self.status { + 401 => ApiErrorKind::Authentication, + 422 => ApiErrorKind::Validation, + 429 => ApiErrorKind::RateLimit, + 529 => ApiErrorKind::Overloaded, + _ => ApiErrorKind::Other, + } + } +} + +impl fmt::Display for ApiError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Jev API error {}: {}", self.status, self.body) + } +} + +impl std::error::Error for ApiError {} diff --git a/crates/jev/src/lib.rs b/crates/jev/src/lib.rs index 8b13789..c3c1739 100644 --- a/crates/jev/src/lib.rs +++ b/crates/jev/src/lib.rs @@ -1 +1,41 @@ +//! A typed client for the [Typesafe](https://typesafe.ai) Jev model API. +//! +//! A [`Query`] is a piece of text (the *state*) and a set of named questions +//! about it. Each question has a type, which fixes the shape of its answer: +//! +//! - [`Noul`]: a true/false judgement, answered with a probability. +//! - [`Choice`]: one option out of a set, with a probability for each. +//! - [`Score`]: a level on a scale of 2 to 10. +//! +//! ```no_run +//! use jev::{Choice, Client, Noul, Query}; +//! +//! # fn main() -> Result<(), jev::Error> { +//! let client = Client::new(std::env::var("JEV_API_KEY").unwrap_or_default())?; +//! +//! let mut query = Query::new("Help! My payouts have been failing for 3 days."); +//! query.ask("urgent", Noul::new("Does this message need an answer today?").with_threshold(0.7))?; +//! query.ask("topic", Choice::new("What is this message about?", [ +//! ("billing", "Invoices, charges or refunds"), +//! ("payouts", "Money sent to the customer"), +//! ])?)?; +//! +//! let response = client.perform(&query)?; +//! if response.noul("urgent")?.result() { +//! println!("urgent, about {}", response.choice("topic")?.choice); +//! } +//! # Ok(()) +//! # } +//! ``` +mod client; +mod error; +mod query; +mod question; +mod response; + +pub use client::{API_URL, Client, MODEL}; +pub use error::{ApiError, ApiErrorKind, Error, Result}; +pub use query::Query; +pub use question::{Choice, Noul, Question, Score}; +pub use response::{Answer, ChoiceAnswer, NoulAnswer, Response, ScoreAnswer, Usage}; diff --git a/crates/jev/src/query.rs b/crates/jev/src/query.rs new file mode 100644 index 0000000..127ab18 --- /dev/null +++ b/crates/jev/src/query.rs @@ -0,0 +1,70 @@ +use indexmap::IndexMap; + +use crate::{Error, Question, Result}; + +/// A state (the text the questions are about) and the questions to ask of it, +/// each under an identifier the answers come back keyed by. +#[derive(Debug, Clone, PartialEq)] +pub struct Query { + state: String, + questions: IndexMap, +} + +impl Query { + pub fn new(state: impl Into) -> Query { + Query { state: state.into(), questions: IndexMap::new() } + } + + /// Adds a question. Identifiers must be unique within a query. + pub fn ask(&mut self, id: impl Into, question: impl Into) -> Result<&mut Query> { + let id = id.into(); + if self.questions.contains_key(&id) { + return Err(Error::DuplicateQuestion(id)); + } + self.questions.insert(id, question.into()); + Ok(self) + } + + pub fn state(&self) -> &str { + &self.state + } + + pub fn question(&self, id: &str) -> Option<&Question> { + self.questions.get(id) + } + + /// The questions in the order they were asked. + pub fn questions(&self) -> impl Iterator { + self.questions.iter().map(|(id, question)| (id.as_str(), question)) + } + + pub(crate) fn question_map(&self) -> &IndexMap { + &self.questions + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Noul; + + #[test] + fn questions_keep_the_order_they_were_asked_in() { + let mut query = Query::new("state"); + query.ask("b", Noul::new("q1")).unwrap().ask("a", Noul::new("q2")).unwrap(); + + let ids: Vec<_> = query.questions().map(|(id, _)| id).collect(); + assert_eq!(ids, ["b", "a"]); + assert_eq!(query.question("a").unwrap().instructions(), "q2"); + } + + #[test] + fn an_identifier_can_only_be_asked_once() { + let mut query = Query::new("state"); + query.ask("a", Noul::new("q")).unwrap(); + + let error = query.ask("a", Noul::new("again")).unwrap_err(); + assert_eq!(error.to_string(), "a has already been asked"); + assert_eq!(query.question("a").unwrap().instructions(), "q"); + } +} diff --git a/crates/jev/src/question.rs b/crates/jev/src/question.rs new file mode 100644 index 0000000..08bdef1 --- /dev/null +++ b/crates/jev/src/question.rs @@ -0,0 +1,230 @@ +use indexmap::IndexMap; +use serde::Serialize; + +use crate::{Error, Result}; + +/// A question of any type, as it is sent to the API. +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum Question { + Noul(Noul), + Choice(Choice), + Score(Score), +} + +impl Question { + /// The type's name in the API: "noul", "choice" or "score". + pub fn type_name(&self) -> &'static str { + match self { + Question::Noul(_) => "noul", + Question::Choice(_) => "choice", + Question::Score(_) => "score", + } + } + + pub fn instructions(&self) -> &str { + match self { + Question::Noul(q) => &q.instructions, + Question::Choice(q) => &q.instructions, + Question::Score(q) => &q.instructions, + } + } +} + +/// A true/false question. The answer is the probability that it is true, +/// and its [result](crate::NoulAnswer::result) is whether that probability +/// is above the threshold (0.5 unless set). +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct Noul { + instructions: String, + #[serde(skip_serializing_if = "Option::is_none")] + criteria: Option>, + #[serde(skip)] + threshold: f64, +} + +impl Noul { + pub const DEFAULT_THRESHOLD: f64 = 0.5; + + pub fn new(instructions: impl Into) -> Noul { + Noul { instructions: instructions.into(), criteria: None, threshold: Self::DEFAULT_THRESHOLD } + } + + /// Spells out what a true and a false answer mean. + pub fn with_criteria(self, when_true: impl Into, when_false: impl Into) -> Noul { + self.with_criterion("true", when_true).with_criterion("false", when_false) + } + + /// Adds one entry to the criteria, keyed by the answer it describes. + pub fn with_criterion(mut self, answer: impl Into, meaning: impl Into) -> Noul { + self.criteria.get_or_insert_with(IndexMap::new).insert(answer.into(), meaning.into()); + self + } + + /// The probability above which the answer counts as true. It stays on + /// this side: the API is never sent the threshold. + pub fn with_threshold(mut self, threshold: f64) -> Noul { + self.threshold = threshold; + self + } + + pub fn instructions(&self) -> &str { + &self.instructions + } + + pub fn criteria(&self) -> Option<&IndexMap> { + self.criteria.as_ref() + } + + pub fn threshold(&self) -> f64 { + self.threshold + } +} + +/// A question answered by picking one of a set of options, each described in words. +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct Choice { + instructions: String, + #[serde(rename = "criteria")] + options: IndexMap, +} + +impl Choice { + pub const MAX_OPTIONS: usize = 255; + + /// Takes the options as (name, description) pairs, 1 to 255 of them. + pub fn new(instructions: impl Into, options: impl IntoIterator) -> Result + where + K: Into, + V: Into, + { + let options: IndexMap = options.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); + if options.is_empty() || options.len() > Self::MAX_OPTIONS { + return Err(Error::InvalidQuestion(format!( + "a choice question takes 1 to {} options, got {}", + Self::MAX_OPTIONS, + options.len() + ))); + } + Ok(Choice { instructions: instructions.into(), options }) + } + + pub fn instructions(&self) -> &str { + &self.instructions + } + + pub fn options(&self) -> &IndexMap { + &self.options + } +} + +/// A question answered with a level on a scale, each level described in words. +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct Score { + instructions: String, + #[serde(rename = "criteria")] + levels: Vec, +} + +impl Score { + pub const LEVELS: std::ops::RangeInclusive = 2..=10; + + /// Takes the levels from lowest to highest, 2 to 10 of them. + pub fn new>(instructions: impl Into, levels: impl IntoIterator) -> Result { + let levels: Vec = levels.into_iter().map(Into::into).collect(); + if !Self::LEVELS.contains(&levels.len()) { + return Err(Error::InvalidQuestion(format!( + "a score question takes {} to {} levels, got {}", + Self::LEVELS.start(), + Self::LEVELS.end(), + levels.len() + ))); + } + Ok(Score { instructions: instructions.into(), levels }) + } + + pub fn instructions(&self) -> &str { + &self.instructions + } + + pub fn levels(&self) -> &[String] { + &self.levels + } +} + +impl From for Question { + fn from(question: Noul) -> Question { + Question::Noul(question) + } +} + +impl From for Question { + fn from(question: Choice) -> Question { + Question::Choice(question) + } +} + +impl From for Question { + fn from(question: Score) -> Question { + Question::Score(question) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn to_json(question: impl Into) -> serde_json::Value { + serde_json::to_value(question.into()).unwrap() + } + + #[test] + fn a_noul_is_sent_without_its_threshold_and_with_criteria_only_when_set() { + assert_eq!(to_json(Noul::new("q?").with_threshold(0.8)), json!({ "type": "noul", "instructions": "q?" })); + assert_eq!( + to_json(Noul::new("q?").with_criteria("yes", "no")), + json!({ "type": "noul", "instructions": "q?", "criteria": { "true": "yes", "false": "no" } }) + ); + } + + #[test] + fn criteria_keep_their_order() { + let noul = Noul::new("q").with_criterion("false", "f").with_criterion("true", "t"); + let json = serde_json::to_string(&Question::from(noul)).unwrap(); + assert_eq!(json, r#"{"type":"noul","instructions":"q","criteria":{"false":"f","true":"t"}}"#); + } + + #[test] + fn a_choice_sends_its_options_as_criteria() { + let choice = Choice::new("which?", [("a", "first"), ("b", "second")]).unwrap(); + assert_eq!( + to_json(choice), + json!({ "type": "choice", "instructions": "which?", "criteria": { "a": "first", "b": "second" } }) + ); + } + + #[test] + fn a_choice_takes_1_to_255_options() { + let none: [(&str, &str); 0] = []; + let error = Choice::new("q", none).unwrap_err(); + assert_eq!(error.to_string(), "a choice question takes 1 to 255 options, got 0"); + + let many = (0..256).map(|i| (i.to_string(), "x")); + assert!(Choice::new("q", many).is_err()); + assert!(Choice::new("q", (0..255).map(|i| (i.to_string(), "x"))).is_ok()); + } + + #[test] + fn a_score_sends_its_levels_as_criteria() { + let score = Score::new("how much?", ["low", "high"]).unwrap(); + assert_eq!(to_json(score), json!({ "type": "score", "instructions": "how much?", "criteria": ["low", "high"] })); + } + + #[test] + fn a_score_takes_2_to_10_levels() { + assert_eq!(Score::new("q", ["one"]).unwrap_err().to_string(), "a score question takes 2 to 10 levels, got 1"); + assert!(Score::new("q", (0..11).map(|i| i.to_string())).is_err()); + assert!(Score::new("q", (0..10).map(|i| i.to_string())).is_ok()); + } +} diff --git a/crates/jev/src/response.rs b/crates/jev/src/response.rs new file mode 100644 index 0000000..0243133 --- /dev/null +++ b/crates/jev/src/response.rs @@ -0,0 +1,316 @@ +use indexmap::IndexMap; +use serde::Deserialize; +use serde::de::DeserializeOwned; + +use crate::{Error, Query, Question, Result}; + +/// The API's answers to a [`Query`], each typed after the question it answers. +#[derive(Debug, Clone, PartialEq)] +pub struct Response { + model: Option, + usage: Option, + answers: IndexMap, + asked: IndexMap, +} + +/// Tokens the request consumed, when the API reports them. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +pub struct Usage { + pub input_tokens: Option, + pub output_tokens: Option, +} + +/// The answer to one question. +#[derive(Debug, Clone, PartialEq)] +pub enum Answer { + Noul(NoulAnswer), + Choice(ChoiceAnswer), + Score(ScoreAnswer), +} + +/// The answer to a [`Noul`](crate::Noul): the probability that it is true. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct NoulAnswer { + pub noul: f64, + /// The threshold the question was asked with. + pub threshold: f64, +} + +impl NoulAnswer { + /// Whether the probability is above the question's threshold. + pub fn result(&self) -> bool { + self.noul > self.threshold + } +} + +/// The answer to a [`Choice`](crate::Choice): the option picked, and the +/// probability the model gave each option. +#[derive(Debug, Clone, PartialEq)] +pub struct ChoiceAnswer { + pub choice: String, + pub probabilities: IndexMap, + pub confidence: Option, +} + +impl ChoiceAnswer { + pub fn result(&self) -> &str { + &self.choice + } +} + +/// The answer to a [`Score`](crate::Score). +/// +/// `score` is the probability-weighted level, good for ranking and averaging. +/// For a decision, [`level`](ScoreAnswer::level) and [`label`](ScoreAnswer::label) +/// give the single most probable level instead. +#[derive(Debug, Clone, PartialEq)] +pub struct ScoreAnswer { + pub score: f64, + /// The label of each level, keyed by the level's index ("0", "1", ...). + pub legend: IndexMap, + /// The probability of each level, keyed by the level's index. + pub probabilities: IndexMap, + pub confidence: Option, +} + +impl ScoreAnswer { + pub fn result(&self) -> f64 { + self.score + } + + /// The most probable level; ties go to the lower level. + pub fn level(&self) -> Option { + let mut best: Option<(usize, f64)> = None; + for (level, &probability) in &self.probabilities { + let Ok(level) = level.parse::() else { continue }; + let better = match best { + None => true, + Some((best_level, best_probability)) => { + probability > best_probability || (probability == best_probability && level < best_level) + } + }; + if better { + best = Some((level, probability)); + } + } + best.map(|(level, _)| level) + } + + /// The label of the most probable level, from the legend. + pub fn label(&self) -> Option<&str> { + self.legend.get(&self.level()?.to_string()).map(String::as_str) + } +} + +impl Response { + /// Reads a successful response body, pairing each answer with the question it answers. + /// Answers to questions the query did not ask are ignored. + pub(crate) fn parse(query: &Query, body: &str) -> Result { + #[derive(Deserialize)] + struct Payload { + model: Option, + usage: Option, + answers: Option>, + } + + let payload: Payload = serde_json::from_str(body).map_err(|e| Error::InvalidResponse(e.to_string()))?; + let mut raw_answers = payload.answers.unwrap_or_default(); + + let mut answers = IndexMap::new(); + for (id, question) in query.question_map() { + let Some(raw) = raw_answers.shift_remove(id) else { continue }; + if let Some(answer) = decode(id, question, raw)? { + answers.insert(id.clone(), answer); + } + } + + let asked = query.questions().map(|(id, question)| (id.to_string(), question.type_name())).collect(); + Ok(Response { model: payload.model, usage: payload.usage, answers, asked }) + } + + /// The model version that answered: requests ask for the latest one. + pub fn model(&self) -> Option<&str> { + self.model.as_deref() + } + + pub fn usage(&self) -> Option<&Usage> { + self.usage.as_ref() + } + + /// The answers, in the order the questions were asked. + pub fn answers(&self) -> impl Iterator { + self.answers.iter().map(|(id, answer)| (id.as_str(), answer)) + } + + pub fn answer(&self, id: &str) -> Option<&Answer> { + self.answers.get(id) + } + + pub fn noul(&self, id: &str) -> Result<&NoulAnswer> { + match self.answers.get(id) { + Some(Answer::Noul(answer)) => Ok(answer), + _ => Err(self.missing(id, "noul")), + } + } + + pub fn choice(&self, id: &str) -> Result<&ChoiceAnswer> { + match self.answers.get(id) { + Some(Answer::Choice(answer)) => Ok(answer), + _ => Err(self.missing(id, "choice")), + } + } + + pub fn score(&self, id: &str) -> Result<&ScoreAnswer> { + match self.answers.get(id) { + Some(Answer::Score(answer)) => Ok(answer), + _ => Err(self.missing(id, "score")), + } + } + + fn missing(&self, id: &str, requested: &'static str) -> Error { + match self.asked.get(id) { + Some(&asked) if asked != requested => Error::WrongType { id: id.to_string(), asked, requested }, + _ => Error::Unanswered(id.to_string()), + } + } +} + +/// Decodes one raw answer. An answer missing its value counts as no answer. +fn decode(id: &str, question: &Question, raw: serde_json::Value) -> Result> { + #[derive(Deserialize)] + struct RawNoul { + noul: Option, + } + + #[derive(Deserialize)] + struct RawChoice { + choice: Option, + #[serde(default)] + probabilities: IndexMap, + confidence: Option, + } + + #[derive(Deserialize)] + struct RawScore { + score: Option, + #[serde(default)] + legend: IndexMap, + #[serde(default)] + probabilities: IndexMap, + confidence: Option, + } + + fn read(id: &str, raw: serde_json::Value) -> Result { + serde_json::from_value(raw).map_err(|e| Error::InvalidResponse(format!("answer to {id}: {e}"))) + } + + Ok(match question { + Question::Noul(noul) => read::(id, raw)? + .noul + .map(|value| Answer::Noul(NoulAnswer { noul: value, threshold: noul.threshold() })), + Question::Choice(_) => { + let raw: RawChoice = read(id, raw)?; + raw.choice.map(|choice| { + Answer::Choice(ChoiceAnswer { choice, probabilities: raw.probabilities, confidence: raw.confidence }) + }) + } + Question::Score(_) => { + let raw: RawScore = read(id, raw)?; + raw.score.map(|score| { + Answer::Score(ScoreAnswer { + score, + legend: raw.legend, + probabilities: raw.probabilities, + confidence: raw.confidence, + }) + }) + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Choice, Noul, Score}; + + fn query() -> Query { + let mut query = Query::new("state"); + query + .ask("urgent", Noul::new("urgent?").with_threshold(0.8)) + .unwrap() + .ask("team", Choice::new("which team?", [("billing", "b"), ("tech", "t")]).unwrap()) + .unwrap() + .ask("mood", Score::new("how angry?", ["calm", "angry", "furious"]).unwrap()) + .unwrap(); + query + } + + const BODY: &str = r#"{ + "model": "jev-1.13.0", + "usage": { "input_tokens": 318, "output_tokens": 34 }, + "answers": { + "mood": { "type": "score", "score": 1.05, "legend": { "0": "calm", "1": "angry", "2": "furious" }, + "probabilities": { "0": 0.0, "1": 0.95, "2": 0.05 }, "confidence": 0.9 }, + "urgent": { "type": "noul", "noul": 0.75 }, + "team": { "type": "choice", "choice": "billing", "probabilities": { "billing": 0.88, "tech": 0.12 }, + "confidence": 0.81 }, + "unasked": { "type": "noul", "noul": 1.0 } + } + }"#; + + #[test] + fn answers_are_typed_by_their_question_and_kept_in_asking_order() { + let response = Response::parse(&query(), BODY).unwrap(); + + assert_eq!(response.model(), Some("jev-1.13.0")); + assert_eq!(response.usage().unwrap().input_tokens, Some(318)); + assert_eq!(response.answers().map(|(id, _)| id).collect::>(), ["urgent", "team", "mood"]); + + let urgent = response.noul("urgent").unwrap(); + assert_eq!(urgent.noul, 0.75); + assert!(!urgent.result(), "0.75 is under the 0.8 threshold"); + + let team = response.choice("team").unwrap(); + assert_eq!(team.result(), "billing"); + assert_eq!(team.probabilities["tech"], 0.12); + assert_eq!(team.confidence, Some(0.81)); + + let mood = response.score("mood").unwrap(); + assert_eq!(mood.result(), 1.05); + assert_eq!(mood.level(), Some(1)); + assert_eq!(mood.label(), Some("angry")); + } + + #[test] + fn a_missing_answer_and_a_wrong_type_are_errors() { + let response = Response::parse(&query(), r#"{ "answers": { "urgent": { "noul": null } } }"#).unwrap(); + + assert_eq!(response.noul("urgent").unwrap_err().to_string(), "urgent has not been answered"); + assert_eq!(response.noul("never_asked").unwrap_err().to_string(), "never_asked has not been answered"); + assert_eq!( + response.noul("team").unwrap_err().to_string(), + "team was asked as a choice question, not a noul one" + ); + assert!(response.model().is_none()); + } + + #[test] + fn a_malformed_body_or_answer_is_an_invalid_response() { + assert!(matches!(Response::parse(&query(), "not json"), Err(Error::InvalidResponse(_)))); + + let error = Response::parse(&query(), r#"{ "answers": { "urgent": { "noul": "high" } } }"#).unwrap_err(); + assert!(error.to_string().starts_with("unexpected response from the Jev API: answer to urgent:"), "{error}"); + } + + #[test] + fn score_level_ties_go_to_the_lower_level() { + let answer = ScoreAnswer { + score: 1.0, + legend: IndexMap::new(), + probabilities: [("2", 0.4), ("0", 0.4), ("1", 0.2)].into_iter().map(|(k, v)| (k.to_string(), v)).collect(), + confidence: None, + }; + assert_eq!(answer.level(), Some(0)); + assert_eq!(answer.label(), None); + } +} diff --git a/crates/jev/tests/client.rs b/crates/jev/tests/client.rs new file mode 100644 index 0000000..4bb8057 --- /dev/null +++ b/crates/jev/tests/client.rs @@ -0,0 +1,118 @@ +//! The client against a local HTTP server standing in for the API. + +use std::sync::mpsc; +use std::thread; + +use jev::{ApiErrorKind, Client, Error, Noul, Query}; +use serde_json::{Value, json}; + +struct Received { + method: String, + url: String, + headers: Vec<(String, String)>, + body: Value, +} + +impl Received { + fn header(&self, name: &str) -> Option<&str> { + self.headers.iter().find(|(k, _)| k.eq_ignore_ascii_case(name)).map(|(_, v)| v.as_str()) + } +} + +/// Serves one request with the given status and body, and hands back what it received. +fn serve_once(status: u16, body: &str) -> (String, mpsc::Receiver) { + let server = tiny_http::Server::http("127.0.0.1:0").unwrap(); + let url = format!("http://{}/v1/systemone", server.server_addr().to_ip().unwrap()); + let (sender, receiver) = mpsc::channel(); + let body = body.to_string(); + thread::spawn(move || { + let mut request = server.recv().unwrap(); + let mut text = String::new(); + request.as_reader().read_to_string(&mut text).unwrap(); + sender + .send(Received { + method: request.method().to_string(), + url: request.url().to_string(), + headers: request.headers().iter().map(|h| (h.field.to_string(), h.value.to_string())).collect(), + body: serde_json::from_str(&text).unwrap(), + }) + .unwrap(); + request.respond(tiny_http::Response::from_string(body).with_status_code(status)).unwrap(); + }); + (url, receiver) +} + +fn query() -> Query { + let mut query = Query::new("Help! My payouts have been failing for 3 days."); + query.ask("is_urgent", Noul::new("Does this convey urgency?").with_criteria("Time-sensitive", "Not")).unwrap(); + query +} + +#[test] +fn sends_the_query_as_json_with_the_api_key() { + let (url, received) = serve_once(200, r#"{"model":"jev-1.13.0","answers":{"is_urgent":{"type":"noul","noul":0.95}}}"#); + + let response = Client::new("secret").unwrap().with_api_url(&url).perform(&query()).unwrap(); + + let request = received.recv().unwrap(); + assert_eq!(request.method, "POST"); + assert_eq!(request.url, "/v1/systemone"); + assert_eq!(request.header("Authorization"), Some("Bearer secret")); + assert_eq!(request.header("Content-Type"), Some("application/json")); + assert_eq!( + request.body, + json!({ + "model": "jev-latest", + "state": "Help! My payouts have been failing for 3 days.", + "questions": { + "is_urgent": { + "type": "noul", + "instructions": "Does this convey urgency?", + "criteria": { "true": "Time-sensitive", "false": "Not" } + } + } + }) + ); + + assert_eq!(response.model(), Some("jev-1.13.0")); + assert!(response.noul("is_urgent").unwrap().result()); +} + +#[test] +fn error_statuses_become_api_errors() { + for (status, kind, retryable) in [ + (401, ApiErrorKind::Authentication, false), + (422, ApiErrorKind::Validation, false), + (429, ApiErrorKind::RateLimit, true), + (529, ApiErrorKind::Overloaded, true), + (500, ApiErrorKind::Other, false), + ] { + let (url, _received) = serve_once(status, "nope"); + + let error = Client::new("key").unwrap().with_api_url(&url).perform(&query()).unwrap_err(); + + let api = error.api().unwrap_or_else(|| panic!("{status}: {error:?}")); + assert_eq!((api.status, api.body.as_str(), api.kind()), (status, "nope", kind)); + assert_eq!(error.is_retryable(), retryable, "{status}"); + assert_eq!(error.to_string(), format!("Jev API error {status}: nope")); + } +} + +#[test] +fn an_unreachable_api_is_a_transport_error() { + let port = std::net::TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port(); + + let error = Client::new("key").unwrap().with_api_url(format!("http://127.0.0.1:{port}/")).perform(&query()).unwrap_err(); + + assert!(matches!(error, Error::Transport(_)), "{error:?}"); + assert!(!error.is_retryable()); +} + +#[test] +fn a_success_that_is_not_json_is_an_invalid_response() { + let (url, _received) = serve_once(200, "maintenance"); + + let error = Client::new("key").unwrap().with_api_url(&url).perform(&query()).unwrap_err(); + + assert!(matches!(error, Error::InvalidResponse(_)), "{error:?}"); +} From 87a1b08cf7c735ebb8398d317949f05388620c84 Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:19:56 +0200 Subject: [PATCH 3/9] Port lintus to Rust The same command line, config file and output as the Ruby gem, as a single binary. The conformance suite passes against both implementations. Glob matching is a port of Ruby's File.fnmatch, and probabilities are rounded and printed as Ruby floats; both are checked against fixtures generated from Ruby. The config file is read with Psych's YAML 1.1 rules (yes/no booleans, 100_000 integers, merge keys), so existing configs mean the same thing. New: `lintus auth login|status|logout` saves the API key in the user's config directory, readable by its owner only. A run takes the key from --api-key, then JEV_API_KEY, then the saved key. JEV_API_URL points the client at another endpoint. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 211 +- crates/jev/src/question.rs | 5 +- crates/jev/tests/client.rs | 6 +- crates/lintus/Cargo.toml | 6 + crates/lintus/src/cli.rs | 342 + crates/lintus/src/config.rs | 356 + crates/lintus/src/credentials.rs | 139 + crates/lintus/src/error.rs | 29 + crates/lintus/src/files.rs | 126 + crates/lintus/src/format.rs | 308 + crates/lintus/src/git.rs | 81 + crates/lintus/src/glob.rs | 358 + crates/lintus/src/main.rs | 23 +- crates/lintus/src/paths.rs | 58 + crates/lintus/src/progress.rs | 96 + crates/lintus/src/report.rs | 93 + crates/lintus/src/runner.rs | 306 + crates/lintus/src/template.rs | 44 + crates/lintus/src/yaml.rs | 449 + crates/lintus/tests/auth.rs | 154 + crates/lintus/tests/conformance/cli.rs | 19 +- crates/lintus/tests/conformance/config.rs | 28 +- crates/lintus/tests/conformance/files.rs | 11 +- crates/lintus/tests/conformance/output.rs | 5 +- crates/lintus/tests/conformance/runner.rs | 17 +- crates/lintus/tests/conformance/support.rs | 13 +- .../tests/fixtures/generate_glob_cases.rb | 42 + .../tests/fixtures/generate_round_cases.rb | 26 + crates/lintus/tests/fixtures/glob_cases.tsv | 3942 +++++++ crates/lintus/tests/fixtures/round_cases.tsv | 9908 +++++++++++++++++ rustfmt.toml | 2 + 31 files changed, 17165 insertions(+), 38 deletions(-) create mode 100644 crates/lintus/src/cli.rs create mode 100644 crates/lintus/src/config.rs create mode 100644 crates/lintus/src/credentials.rs create mode 100644 crates/lintus/src/error.rs create mode 100644 crates/lintus/src/files.rs create mode 100644 crates/lintus/src/format.rs create mode 100644 crates/lintus/src/git.rs create mode 100644 crates/lintus/src/glob.rs create mode 100644 crates/lintus/src/paths.rs create mode 100644 crates/lintus/src/progress.rs create mode 100644 crates/lintus/src/report.rs create mode 100644 crates/lintus/src/runner.rs create mode 100644 crates/lintus/src/template.rs create mode 100644 crates/lintus/src/yaml.rs create mode 100644 crates/lintus/tests/auth.rs create mode 100644 crates/lintus/tests/fixtures/generate_glob_cases.rb create mode 100644 crates/lintus/tests/fixtures/generate_round_cases.rb create mode 100644 crates/lintus/tests/fixtures/glob_cases.tsv create mode 100644 crates/lintus/tests/fixtures/round_cases.tsv create mode 100644 rustfmt.toml diff --git a/Cargo.lock b/Cargo.lock index 3d2ea23..5c6b3e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,62 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + [[package]] name = "ascii" version = "1.1.0" @@ -48,6 +104,53 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" version = "4.6.8" @@ -129,6 +232,15 @@ dependencies = [ "litrs", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -157,6 +269,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -193,6 +311,24 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" @@ -332,6 +468,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.18" @@ -408,9 +550,14 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" name = "lintus" version = "0.3.0" dependencies = [ + "clap", + "indexmap", + "jev", + "rpassword", "serde_json", "tempfile", "tiny_http", + "yaml-rust2", ] [[package]] @@ -455,6 +602,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "openssl-probe" version = "0.2.1" @@ -520,6 +673,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rpassword" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.61.2", +] + +[[package]] +name = "rtoolbox" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -596,7 +770,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -741,6 +915,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -793,6 +973,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "2.0.20" @@ -933,6 +1123,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.5" @@ -979,7 +1175,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1076,6 +1272,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +[[package]] +name = "yaml-rust2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e5b818a27a4cd30884ea380857a5e56f7ec3ba24a3990a3cc0b95af3238e18" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + [[package]] name = "yoke" version = "0.8.3" diff --git a/crates/jev/src/question.rs b/crates/jev/src/question.rs index 08bdef1..45a1976 100644 --- a/crates/jev/src/question.rs +++ b/crates/jev/src/question.rs @@ -218,7 +218,10 @@ mod tests { #[test] fn a_score_sends_its_levels_as_criteria() { let score = Score::new("how much?", ["low", "high"]).unwrap(); - assert_eq!(to_json(score), json!({ "type": "score", "instructions": "how much?", "criteria": ["low", "high"] })); + assert_eq!( + to_json(score), + json!({ "type": "score", "instructions": "how much?", "criteria": ["low", "high"] }) + ); } #[test] diff --git a/crates/jev/tests/client.rs b/crates/jev/tests/client.rs index 4bb8057..1966e5e 100644 --- a/crates/jev/tests/client.rs +++ b/crates/jev/tests/client.rs @@ -50,7 +50,8 @@ fn query() -> Query { #[test] fn sends_the_query_as_json_with_the_api_key() { - let (url, received) = serve_once(200, r#"{"model":"jev-1.13.0","answers":{"is_urgent":{"type":"noul","noul":0.95}}}"#); + let (url, received) = + serve_once(200, r#"{"model":"jev-1.13.0","answers":{"is_urgent":{"type":"noul","noul":0.95}}}"#); let response = Client::new("secret").unwrap().with_api_url(&url).perform(&query()).unwrap(); @@ -102,7 +103,8 @@ fn error_statuses_become_api_errors() { fn an_unreachable_api_is_a_transport_error() { let port = std::net::TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port(); - let error = Client::new("key").unwrap().with_api_url(format!("http://127.0.0.1:{port}/")).perform(&query()).unwrap_err(); + let error = + Client::new("key").unwrap().with_api_url(format!("http://127.0.0.1:{port}/")).perform(&query()).unwrap_err(); assert!(matches!(error, Error::Transport(_)), "{error:?}"); assert!(!error.is_retryable()); diff --git a/crates/lintus/Cargo.toml b/crates/lintus/Cargo.toml index 90e61c0..bf149a0 100644 --- a/crates/lintus/Cargo.toml +++ b/crates/lintus/Cargo.toml @@ -9,6 +9,12 @@ authors.workspace = true repository.workspace = true [dependencies] +clap = { version = "4.6.7", features = ["derive", "wrap_help"] } +indexmap.workspace = true +jev = { version = "0.1.0", path = "../jev" } +rpassword = "7.5.4" +serde_json.workspace = true +yaml-rust2 = "0.13.0" [dev-dependencies] serde_json.workspace = true diff --git a/crates/lintus/src/cli.rs b/crates/lintus/src/cli.rs new file mode 100644 index 0000000..9c9ce70 --- /dev/null +++ b/crates/lintus/src/cli.rs @@ -0,0 +1,342 @@ +//! The `lintus` command: parses the arguments, and runs a lint, `init` or `auth`. + +use std::ffi::OsString; +use std::io::{IsTerminal, Read, Write}; +use std::path::PathBuf; +use std::sync::Mutex; + +use clap::{ArgAction, Parser, Subcommand}; + +use crate::config::{CONFIG_FILENAMES, Config}; +use crate::credentials::{self, ApiKey, Source}; +use crate::error::{Error, Result}; +use crate::files::FileFinder; +use crate::format::Format; +use crate::progress::Progress; +use crate::runner::{self, Runner}; +use crate::template; + +const ABOUT: &str = "A linter whose rules are plain-language questions, answered by the Jev model."; + +const AFTER_HELP: &str = "\ +With no FILE, lints every file in the tree. FILE may be a file or a directory. + +The API key comes from --api-key, else $JEV_API_KEY, else the key saved by +`lintus auth login`. + +Exit status is 0 when clean, 1 when there are offenses, and 2 when a request +failed or the invocation was wrong."; + +#[derive(Parser, Debug)] +#[command( + name = "lintus", + version, + about = ABOUT, + after_help = AFTER_HELP, + override_usage = "lintus [OPTIONS] [FILE]...\n lintus init\n lintus auth ", + disable_version_flag = true, + infer_long_args = true, + args_conflicts_with_subcommands = true +)] +struct Args { + #[command(subcommand)] + command: Option, + + /// Files or directories to lint + #[arg(value_name = "FILE")] + files: Vec, + + /// Only files changed since REF [default: HEAD, the uncommitted changes] + #[arg( + short, + long, + value_name = "REF", + num_args = 0..=1, + default_missing_value = "HEAD", + help_heading = "Which files" + )] + diff: Option, + + /// Only staged files, reading their staged content (for pre-commit hooks) + #[arg(short, long, conflicts_with = "diff", help_heading = "Which files")] + staged: bool, + + /// Config file [default: the nearest .lintus.yml from the current directory up] + #[arg(short, long, value_name = "PATH", help_heading = "How to run")] + config: Option, + + /// Output format [default: text, or github under GitHub Actions] + #[arg(short, long, value_name = "FORMAT", value_parser = Format::NAMES, help_heading = "How to run")] + format: Option, + + /// Concurrent requests to the Jev API + #[arg( + short, + long, + value_name = "N", + default_value_t = 4, + allow_negative_numbers = true, + help_heading = "How to run" + )] + jobs: i64, + + /// Jev API key [default: $JEV_API_KEY, then the key saved by `lintus auth login`] + #[arg(long, value_name = "KEY", help_heading = "How to run")] + api_key: Option, + + /// List the files and rules that would be checked, without calling the API + #[arg(short, long, help_heading = "How to run")] + list: bool, + + /// Print the version + #[arg(short = 'v', long, action = ArgAction::Version)] + version: Option, +} + +#[derive(Subcommand, Debug)] +enum Command { + /// Write a starter .lintus.yml in the current directory + Init, + /// Save, show or delete the Jev API key lintus uses + #[command(subcommand_required = true, arg_required_else_help = true)] + Auth { + #[command(subcommand)] + command: AuthCommand, + }, +} + +#[derive(Subcommand, Debug)] +enum AuthCommand { + /// Save an API key, typed at a prompt or piped on stdin + Login, + /// Show which API key lintus would use, and where it comes from + Status, + /// Delete the saved API key + Logout, +} + +/// Runs the command and returns its exit status. +pub fn run(argv: Vec) -> i32 { + let code = match Args::try_parse_from(argv) { + Ok(args) => { + let result = match &args.command { + Some(Command::Init) => init(), + Some(Command::Auth { command }) => auth(command), + None => lint(&args), + }; + result.unwrap_or_else(|error| { + eprintln!("lintus: {error}"); + 2 + }) + } + Err(error) => usage_error(error), + }; + let _ = std::io::stdout().flush(); + code +} + +/// Help and version go to stdout; anything else is a usage error on stderr. +fn usage_error(error: clap::Error) -> i32 { + use clap::error::ErrorKind; + match error.kind() { + ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => { + let _ = error.print(); + 0 + } + ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => { + eprint!("{}", error.render()); + 2 + } + _ => { + let rendered = error.render().to_string(); + let message = rendered.split("\n\n").next().unwrap_or_default().trim_end(); + eprintln!("lintus: {}", message.strip_prefix("error: ").unwrap_or(message)); + 2 + } + } +} + +fn lint(args: &Args) -> Result { + if !args.files.is_empty() && (args.diff.is_some() || args.staged) { + return Err(Error::new("FILE arguments cannot be combined with --diff or --staged")); + } + let cwd = current_dir()?; + let format = match &args.format { + Some(name) => Format::from_name(name).expect("clap only accepts known formats"), + None if std::env::var("GITHUB_ACTIONS").as_deref() == Ok("true") => Format::Github, + None => Format::Text, + }; + + let config = Config::load(args.config.as_deref(), &cwd)?; + let finder = FileFinder::new(config.root.clone()); + let files = if args.staged { + finder.staged()? + } else if let Some(reference) = &args.diff { + finder.changed_since(reference)? + } else if !args.files.is_empty() { + finder.explicit(&args.files, &cwd)? + } else { + finder.all()? + }; + let tasks = runner::plan(&config, files); + + if args.list { + let mut out = String::new(); + for task in &tasks { + let rules: Vec<&str> = task.rules.iter().map(|rule| rule.id.as_str()).collect(); + out += &format!("{}: {}\n", task.file.path, rules.join(", ")); + } + out += &format!("{} file(s) would be checked\n", tasks.len()); + print(&out); + return Ok(0); + } + + let api_key = + credentials::resolve(args.api_key.as_deref())?.filter(|api_key| !api_key.key.is_empty()).ok_or_else(|| { + Error::new(format!( + "no API key: set {}, pass --api-key, or run `lintus auth login` to save one", + credentials::ENV_VAR + )) + })?; + let mut client = jev::Client::new(api_key.key)?; + if let Some(url) = std::env::var("JEV_API_URL").ok().filter(|url| !url.is_empty()) { + client = client.with_api_url(url); + } + + let stderr = std::io::stderr(); + let tty = stderr.is_terminal(); + let progress = Mutex::new(Progress::new(stderr, tasks.len(), tty)); + progress.lock().unwrap().start(); + let report = Runner::new(&config, client, args.jobs).run(&tasks, |done| progress.lock().unwrap().update(done)); + progress.lock().unwrap().finish(); + + print(&format.render(&report)); + Ok(report.exit_status()) +} + +fn init() -> Result { + let path = current_dir()?.join(CONFIG_FILENAMES[0]); + if path.exists() { + return Err(Error::new(format!("{} already exists", path.display()))); + } + std::fs::write(&path, template::CONFIG) + .map_err(|e| Error::new(format!("could not write {}: {e}", path.display())))?; + print(&format!("Wrote {}\n", path.display())); + Ok(0) +} + +fn auth(command: &AuthCommand) -> Result { + match command { + AuthCommand::Login => login(), + AuthCommand::Status => status(), + AuthCommand::Logout => logout(), + } +} + +fn login() -> Result { + let key = if std::io::stdin().is_terminal() { + rpassword::prompt_password("Paste your Jev API key (it will not be shown): ") + .map_err(|e| Error::new(format!("could not read the API key: {e}")))? + } else { + let mut input = String::new(); + std::io::stdin() + .read_to_string(&mut input) + .map_err(|e| Error::new(format!("could not read the API key from stdin: {e}")))?; + input.lines().next().unwrap_or_default().to_string() + }; + let key = key.trim(); + if key.is_empty() { + return Err(Error::new( + "no API key given: paste it at the prompt, or pipe it in with `lintus auth login < key.txt`", + )); + } + + let path = credentials::save(key)?; + print(&format!("Saved the API key ({}) to {}\n", credentials::mask(key), path.display())); + if env_key_is_set() { + eprintln!("Note: {} is set in this environment, and is used instead of the saved key.", credentials::ENV_VAR); + } + Ok(0) +} + +fn status() -> Result { + let Some(ApiKey { key, source }) = credentials::resolve(None)? else { + eprintln!("No API key: run `lintus auth login`, or set {}.", credentials::ENV_VAR); + return Ok(1); + }; + match &source { + Source::File(path) => { + print(&format!("Using the API key saved in {} ({}).\n", path.display(), credentials::mask(&key))) + } + other => { + print(&format!("Using the API key from {other} ({}).\n", credentials::mask(&key))); + if let Ok(Some((_, path))) = credentials::load() { + print(&format!("The key saved in {} is not used while {other} is set.\n", path.display())); + } + } + } + Ok(0) +} + +fn logout() -> Result { + match credentials::delete()? { + Some(path) => print(&format!("Deleted the API key saved in {}\n", path.display())), + None => print("No API key was saved.\n"), + } + if env_key_is_set() { + eprintln!("Note: {} is still set in this environment.", credentials::ENV_VAR); + } + Ok(0) +} + +fn env_key_is_set() -> bool { + std::env::var(credentials::ENV_VAR).is_ok_and(|key| !key.is_empty()) +} + +fn current_dir() -> Result { + std::env::current_dir().map_err(|e| Error::new(format!("could not read the current directory: {e}"))) +} + +/// Writes to stdout, ignoring a closed pipe (`lintus | head`). +fn print(text: &str) { + let _ = std::io::stdout().lock().write_all(text.as_bytes()); +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + use std::path::Path; + + #[test] + fn the_argument_definitions_are_consistent() { + Args::command().debug_assert(); + } + + fn parse(args: &[&str]) -> Args { + Args::try_parse_from(std::iter::once("lintus").chain(args.iter().copied())).unwrap() + } + + #[test] + fn diff_takes_an_optional_ref() { + assert_eq!(parse(&["--diff"]).diff.as_deref(), Some("HEAD")); + assert_eq!(parse(&["--diff", "main"]).diff.as_deref(), Some("main")); + assert_eq!(parse(&["--diff", "--list"]).diff.as_deref(), Some("HEAD")); + assert_eq!(parse(&["-d"]).diff.as_deref(), Some("HEAD")); + assert_eq!(parse(&[]).diff, None); + } + + #[test] + fn long_options_can_be_abbreviated() { + let args = parse(&["--stag", "--form", "json"]); + assert!(args.staged); + assert_eq!(args.format.as_deref(), Some("json")); + } + + #[test] + fn subcommands_and_files() { + assert!(matches!(parse(&["init"]).command, Some(Command::Init))); + assert!(matches!(parse(&["auth", "login"]).command, Some(Command::Auth { command: AuthCommand::Login }))); + assert_eq!(parse(&["lib", "--", "init"]).files, [Path::new("lib"), Path::new("init")]); + } +} diff --git a/crates/lintus/src/config.rs b/crates/lintus/src/config.rs new file mode 100644 index 0000000..79d4113 --- /dev/null +++ b/crates/lintus/src/config.rs @@ -0,0 +1,356 @@ +//! The parsed config file. Its directory is the root every path is relative to. + +use std::path::{Path, PathBuf}; + +use indexmap::IndexMap; + +use crate::error::{Error, Result}; +use crate::glob::{self, Glob}; +use crate::paths; +use crate::yaml::{self, Value}; + +pub const CONFIG_FILENAMES: [&str; 4] = [".lintus.yml", "lintus.yml", ".lintus.yaml", "lintus.yaml"]; +pub const DEFAULT_MAX_FILE_SIZE: u64 = 100_000; +const KEYS: [&str; 4] = ["rules", "paths", "exclude", "max_file_size"]; + +#[derive(Debug)] +pub struct Config { + pub root: PathBuf, + pub max_file_size: u64, + pub rules: Vec, +} + +/// One entry of the `rules:` map. +/// +/// A rule is a noul question, asked against every file whose path matches its +/// globs. The file is an offense when the answer equals `offense_when` (true by +/// default, so questions are phrased to describe the offense: "Does this file +/// call sleep?"). +#[derive(Debug)] +pub struct Rule { + pub id: String, + pub description: String, + pub question: String, + pub criteria: Option>, + pub threshold: Option, + pub offense_when: bool, + paths: Vec, + exclude: Vec, +} + +impl Config { + /// Reads the config at `path`, or else the nearest one found by walking up from `dir`. + pub fn load(path: Option<&Path>, dir: &Path) -> Result { + let path = match path { + Some(path) => { + if !dir.join(path).is_file() { + return Err(Error::new(format!("Config file not found: {}", path.display()))); + } + paths::normalize(&dir.join(path)) + } + None => Config::locate(dir).ok_or_else(|| { + Error::new(format!( + "No config file found: looked for {} in {} and its parents", + CONFIG_FILENAMES.join(", "), + dir.display() + )) + })?, + }; + + let source = std::fs::read_to_string(&path) + .map_err(|e| Error::new(format!("could not read {}: {e}", path.display())))?; + let data = yaml::load(&source).map_err(|e| Error::new(format!("{} is not valid YAML: {e}", path.display())))?; + let root = path.parent().expect("a config file is in a directory").to_path_buf(); + Config::from_yaml(data, root) + } + + /// Walks up from `dir` and returns the first config file found, like git does for .git. + pub fn locate(dir: &Path) -> Option { + dir.ancestors() + .flat_map(|ancestor| CONFIG_FILENAMES.iter().map(move |name| ancestor.join(name))) + .find(|candidate| candidate.is_file()) + } + + pub fn from_yaml(data: Value, root: PathBuf) -> Result { + let data = match data { + // Ruby reads the file with `YAML.safe_load_file(path) || {}`. + Value::Null | Value::Bool(false) => Vec::new(), + Value::Map(map) => string_keys(&map), + other => return Err(Error::new(format!("config must be a map, got {}", other.class_name()))), + }; + reject_unknown_keys(&data, &KEYS, "config")?; + + let paths = string_list(get(&data, "paths")); + let exclude = string_list(get(&data, "exclude")); + let max_file_size = match get(&data, "max_file_size") { + None => DEFAULT_MAX_FILE_SIZE, + Some(Value::Int(size)) if *size > 0 => *size as u64, + Some(_) => return Err(Error::new("`max_file_size` must be a positive integer of bytes")), + }; + + let rules = match get(&data, "rules") { + Some(Value::Map(rules)) if rules.is_empty() => return Err(Error::new("`rules` is empty: nothing to lint")), + Some(Value::Map(rules)) => rules + .iter() + .map(|(id, attrs)| Rule::new(&id.to_s(), attrs, &paths, &exclude)) + .collect::>>()?, + _ => return Err(Error::new("`rules` must be a map of rule id => attributes")), + }; + + Ok(Config { root, max_file_size, rules }) + } + + /// The rules that apply to a repository-relative path, in config order. + pub fn rules_for(&self, path: &str) -> Vec<&Rule> { + self.rules.iter().filter(|rule| rule.applies_to(path)).collect() + } +} + +impl Rule { + const KEYS: [&str; 7] = ["question", "description", "criteria", "threshold", "paths", "exclude", "offense_when"]; + + fn new(id: &str, attrs: &Value, default_paths: &[String], default_exclude: &[String]) -> Result { + let valid_id = id.starts_with(|c: char| c.is_ascii_lowercase()) + && id.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'); + if !valid_id { + return Err(Error::new(format!( + "invalid rule id {}: use snake_case (letters, digits, underscores)", + Value::Str(id.into()).inspect() + ))); + } + let fail = |message: &str| Error::new(format!("rule {id}: {message}")); + + let Value::Map(attrs) = attrs else { + return Err(fail(&format!("expected a map of attributes, got {}", attrs.inspect()))); + }; + let attrs = string_keys(attrs); + reject_unknown_keys(&attrs, &Rule::KEYS, &format!("rule {id}"))?; + + let question = get(&attrs, "question").map(|q| ruby_strip(&q.to_s()).to_string()).unwrap_or_default(); + if question.is_empty() { + return Err(fail("`question` is required")); + } + let description = get(&attrs, "description").map_or_else(|| question.clone(), Value::to_s); + + let criteria = match get(&attrs, "criteria") { + None | Some(Value::Null) => None, + Some(Value::Map(criteria)) => Some(criteria.iter().map(|(k, v)| (k.to_s(), v.to_s())).collect()), + Some(_) => return Err(fail("`criteria` must be a map with `true` and `false` keys")), + }; + + let threshold = match get(&attrs, "threshold") { + None | Some(Value::Null) => None, + Some(Value::Int(t)) if (0..=1).contains(t) => Some(*t as f64), + Some(Value::Float(t)) if (0.0..=1.0).contains(t) => Some(*t), + Some(_) => return Err(fail("`threshold` must be a number between 0 and 1")), + }; + + let paths = match get(&attrs, "paths") { + Some(paths) => string_list(Some(paths)), + None => default_paths.to_vec(), + }; + let mut exclude = default_exclude.to_vec(); + exclude.extend(string_list(get(&attrs, "exclude"))); + + let offense_when = match get(&attrs, "offense_when") { + None => true, + Some(Value::Bool(value)) => *value, + Some(_) => return Err(fail("`offense_when` must be true or false")), + }; + + Ok(Rule { + id: id.to_string(), + description, + question, + criteria, + threshold, + offense_when, + paths: paths.iter().map(|p| Glob::new(p)).collect(), + exclude: exclude.iter().map(|p| Glob::new(p)).collect(), + }) + } + + pub fn applies_to(&self, path: &str) -> bool { + if glob::matches_any(&self.exclude, path) { + return false; + } + self.paths.is_empty() || glob::matches_any(&self.paths, path) + } + + /// The question as it is sent to the model. + pub fn to_noul(&self) -> jev::Noul { + let mut noul = jev::Noul::new(&self.question); + if let Some(criteria) = &self.criteria { + for (answer, meaning) in criteria { + noul = noul.with_criterion(answer, meaning); + } + } + match self.threshold { + Some(threshold) => noul.with_threshold(threshold), + None => noul, + } + } + + /// Whether an answer makes the file an offense. + pub fn is_offense(&self, answer: &jev::NoulAnswer) -> bool { + answer.result() == self.offense_when + } +} + +/// A map's entries with their keys as strings. Keys equal once stringified +/// collapse into one, keeping the first position and the last value. +fn string_keys(map: &yaml::Map) -> Vec<(String, Value)> { + let mut entries: Vec<(String, Value)> = Vec::new(); + for (key, value) in map.iter() { + let key = key.to_s(); + match entries.iter_mut().find(|(existing, _)| *existing == key) { + Some(entry) => entry.1 = value.clone(), + None => entries.push((key, value.clone())), + } + } + entries +} + +/// Ruby's `String#strip`, which only knows ASCII whitespace (and trailing NULs). +fn ruby_strip(text: &str) -> &str { + let whitespace = |c: char| matches!(c, ' ' | '\t' | '\n' | '\x0B' | '\x0C' | '\r'); + text.trim_start_matches(whitespace).trim_end_matches(|c| whitespace(c) || c == '\0') +} + +fn get<'a>(entries: &'a [(String, Value)], key: &str) -> Option<&'a Value> { + entries.iter().find(|(k, _)| k == key).map(|(_, v)| v) +} + +fn reject_unknown_keys(entries: &[(String, Value)], allowed: &[&str], context: &str) -> Result<()> { + let unknown: Vec<&str> = entries.iter().map(|(k, _)| k.as_str()).filter(|k| !allowed.contains(k)).collect(); + if unknown.is_empty() { + return Ok(()); + } + Err(Error::new(format!("{context}: unknown key(s) {} (expected {})", unknown.join(", "), allowed.join(", ")))) +} + +/// Ruby's `Array(value).map(&:to_s)`: nothing, one value, or a list of them. +fn string_list(value: Option<&Value>) -> Vec { + match value { + None | Some(Value::Null) => Vec::new(), + Some(Value::Seq(items)) => items.iter().map(Value::to_s).collect(), + Some(Value::Map(map)) => map.iter().map(|(k, v)| Value::Seq(vec![k.clone(), v.clone()]).to_s()).collect(), + Some(other) => vec![other.to_s()], + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const RULES: &str = r#" +paths: ["**/*.rb"] +exclude: ["vendor/**"] +rules: + no_sleep: + description: Jobs must not sleep. + question: Does this file call sleep? + paths: ["app/jobs/**/*.rb"] + documented: + question: Is every class documented? + offense_when: false + threshold: 0.7 +"#; + + fn config(yaml: &str) -> Result { + Config::from_yaml(yaml::load(yaml).unwrap(), PathBuf::from("/project")) + } + + fn ids(rules: Vec<&Rule>) -> Vec<&str> { + rules.into_iter().map(|rule| rule.id.as_str()).collect() + } + + #[test] + fn rules_for_applies_global_exclude_then_rule_globs() { + let config = config(RULES).unwrap(); + + assert_eq!(ids(config.rules_for("app/jobs/a_job.rb")), ["no_sleep", "documented"]); + assert_eq!(ids(config.rules_for("lib/a.rb")), ["documented"]); + assert!(config.rules_for("vendor/gem/a.rb").is_empty()); + assert!(config.rules_for("README.md").is_empty()); + } + + #[test] + fn defaults() { + let config = config("rules:\n no_sleep:\n question: Does this file call sleep?\n").unwrap(); + let rule = &config.rules[0]; + + assert_eq!(config.max_file_size, DEFAULT_MAX_FILE_SIZE); + assert_eq!(rule.description, "Does this file call sleep?"); + assert!(rule.offense_when); + assert_eq!(rule.threshold, None); + assert_eq!(rule.criteria, None); + assert!(rule.applies_to("anything/at/all.txt")); + } + + #[test] + fn rule_paths_override_default_paths_and_exclude_adds_to_it() { + let config = config( + "paths: ['**/*']\nexclude: [tmp]\nrules:\n r:\n question: q\n paths: app/**/*.rb\n exclude: ['**/*_test.rb']\n", + ) + .unwrap(); + let rule = &config.rules[0]; + + assert!(rule.applies_to("app/x.rb")); + assert!(!rule.applies_to("lib/x.rb")); + assert!(!rule.applies_to("app/x_test.rb")); + assert!(!rule.applies_to("tmp/app/x.rb")); + } + + #[test] + fn an_explicitly_empty_paths_applies_everywhere() { + let config = config("paths: ['lib/**']\nrules:\n r:\n question: q\n paths:\n").unwrap(); + assert!(config.rules[0].applies_to("app/x.rb")); + } + + #[test] + fn yaml_1_1_values_read_like_ruby() { + let config = config("max_file_size: 100_000\nrules:\n r:\n question: q\n offense_when: no\n").unwrap(); + assert_eq!(config.max_file_size, 100_000); + assert!(!config.rules[0].offense_when); + } + + #[test] + fn to_noul_forwards_criteria_and_threshold() { + let config = config( + "rules:\n r:\n question: q?\n criteria:\n true: t\n 'false': f\n threshold: 0.8\n", + ) + .unwrap(); + let noul = config.rules[0].to_noul(); + + assert_eq!(noul.instructions(), "q?"); + assert_eq!( + noul.criteria().unwrap().iter().collect::>(), + [(&"true".to_string(), &"t".to_string()), (&"false".to_string(), &"f".to_string())] + ); + assert_eq!(noul.threshold(), 0.8); + } + + #[test] + fn offense_follows_offense_when() { + let config = + config("rules:\n positive:\n question: q\n negative:\n question: q\n offense_when: false\n") + .unwrap(); + let yes = jev::NoulAnswer { noul: 0.9, threshold: 0.5 }; + + assert!(config.rules[0].is_offense(&yes)); + assert!(!config.rules[1].is_offense(&yes)); + } + + #[test] + fn locate_walks_up_and_prefers_names_in_order() { + let dir = tempfile::tempdir().unwrap(); + let root = dir.path().canonicalize().unwrap(); + std::fs::create_dir_all(root.join("a/b")).unwrap(); + std::fs::write(root.join("lintus.yaml"), "").unwrap(); + std::fs::write(root.join(".lintus.yml"), "").unwrap(); + + assert_eq!(Config::locate(&root.join("a/b")), Some(root.join(".lintus.yml"))); + assert_eq!(Config::locate(Path::new("/")), None); + } +} diff --git a/crates/lintus/src/credentials.rs b/crates/lintus/src/credentials.rs new file mode 100644 index 0000000..70f7304 --- /dev/null +++ b/crates/lintus/src/credentials.rs @@ -0,0 +1,139 @@ +//! Where `lintus auth login` keeps the Jev API key, and how a run finds one. +//! +//! The key comes from, in order: `--api-key`, the `JEV_API_KEY` environment +//! variable, then the credentials file, which is +//! `$XDG_CONFIG_HOME/lintus/credentials.json` when that variable is set, and +//! otherwise `~/.config/lintus/credentials.json` (`%APPDATA%\lintus\credentials.json` +//! on Windows). The file is readable by its owner only. + +use std::path::{Path, PathBuf}; + +use serde_json::json; + +use crate::error::{Error, Result}; + +pub const ENV_VAR: &str = "JEV_API_KEY"; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Source { + Flag, + Env, + File(PathBuf), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ApiKey { + pub key: String, + pub source: Source, +} + +impl std::fmt::Display for Source { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Source::Flag => write!(f, "--api-key"), + Source::Env => write!(f, "{ENV_VAR}"), + Source::File(path) => write!(f, "{}", path.display()), + } + } +} + +/// The key a run should use: the flag, else the environment, else the saved one. +pub fn resolve(flag: Option<&str>) -> Result> { + if let Some(key) = flag { + return Ok(Some(ApiKey { key: key.to_string(), source: Source::Flag })); + } + if let Some(key) = std::env::var(ENV_VAR).ok().filter(|key| !key.is_empty()) { + return Ok(Some(ApiKey { key, source: Source::Env })); + } + Ok(load()?.map(|(key, path)| ApiKey { key, source: Source::File(path) })) +} + +/// The credentials file's location, whether or not it exists. +pub fn path() -> Option { + let from_env = |name: &str| std::env::var_os(name).map(PathBuf::from).filter(|dir| dir.is_absolute()); + let config_dir = from_env("XDG_CONFIG_HOME").or_else(|| { + if cfg!(windows) { from_env("APPDATA") } else { from_env("HOME").map(|home| home.join(".config")) } + })?; + Some(config_dir.join("lintus").join("credentials.json")) +} + +/// The saved key and the file it was read from, if there is one. +pub fn load() -> Result> { + let Some(path) = path() else { return Ok(None) }; + let text = match std::fs::read_to_string(&path) { + Ok(text) => text, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(e) => return Err(Error::new(format!("could not read {}: {e}", path.display()))), + }; + let data: serde_json::Value = serde_json::from_str(&text).map_err(|e| unreadable(&path, &e.to_string()))?; + match data.get("jev_api_key").and_then(|key| key.as_str()) { + Some(key) if !key.is_empty() => Ok(Some((key.to_string(), path))), + _ => Err(unreadable(&path, "no `jev_api_key` in it")), + } +} + +fn unreadable(path: &Path, reason: &str) -> Error { + Error::new(format!( + "could not read the saved API key in {}: {reason}. Run `lintus auth login` to save it again", + path.display() + )) +} + +/// Saves the key, readable by the current user only, and returns where. +pub fn save(key: &str) -> Result { + let path = path().ok_or_else(|| Error::new("could not find your home directory to save the API key in"))?; + let fail = |e: std::io::Error| Error::new(format!("could not save the API key to {}: {e}", path.display())); + + let dir = path.parent().expect("the credentials file is in a directory"); + let mut builder = std::fs::DirBuilder::new(); + builder.recursive(true); + #[cfg(unix)] + std::os::unix::fs::DirBuilderExt::mode(&mut builder, 0o700); + builder.create(dir).map_err(fail)?; + + let mut options = std::fs::OpenOptions::new(); + options.write(true).create(true).truncate(true); + #[cfg(unix)] + std::os::unix::fs::OpenOptionsExt::mode(&mut options, 0o600); + let mut file = options.open(&path).map_err(fail)?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + file.set_permissions(std::fs::Permissions::from_mode(0o600)).map_err(fail)?; + } + + let content = serde_json::to_string_pretty(&json!({ "jev_api_key": key })).expect("a string serializes") + "\n"; + std::io::Write::write_all(&mut file, content.as_bytes()).map_err(fail)?; + Ok(path) +} + +/// Deletes the saved key. Returns the file it was in, or `None` if there was none. +pub fn delete() -> Result> { + let Some(path) = path() else { return Ok(None) }; + match std::fs::remove_file(&path) { + Ok(()) => Ok(Some(path)), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(Error::new(format!("could not delete {}: {e}", path.display()))), + } +} + +/// Enough of a key to recognise it, never enough to use it. +pub fn mask(key: &str) -> String { + let chars: Vec = key.chars().collect(); + if chars.len() < 12 { + return "*".repeat(chars.len().min(8)); + } + format!("{}…{}", chars[..3].iter().collect::(), chars[chars.len() - 4..].iter().collect::()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mask_shows_only_the_ends_of_long_keys() { + assert_eq!(mask("jev_1234567890abcdef"), "jev…cdef"); + assert_eq!(mask("short"), "*****"); + assert_eq!(mask("elevenchars"), "********"); + } +} diff --git a/crates/lintus/src/error.rs b/crates/lintus/src/error.rs new file mode 100644 index 0000000..cde7fc6 --- /dev/null +++ b/crates/lintus/src/error.rs @@ -0,0 +1,29 @@ +use std::fmt; + +/// An error that stops the run before or instead of linting: a bad argument, +/// config file, git command or credentials file. It is printed as +/// `lintus: ` and the command exits with status 2. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Error(String); + +pub type Result = std::result::Result; + +impl Error { + pub fn new(message: impl Into) -> Error { + Error(message.into()) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl std::error::Error for Error {} + +impl From for Error { + fn from(error: jev::Error) -> Error { + Error(error.to_string()) + } +} diff --git a/crates/lintus/src/files.rs b/crates/lintus/src/files.rs new file mode 100644 index 0000000..26b89b6 --- /dev/null +++ b/crates/lintus/src/files.rs @@ -0,0 +1,126 @@ +//! Resolves which files a run looks at, always as paths relative to the config +//! root with forward slashes, in a stable order. + +use std::path::{Path, PathBuf}; + +use crate::error::{Error, Result}; +use crate::git::Git; +use crate::paths; + +/// A file to lint: a root-relative path, and where to read its content from. +/// Reading from the index is what lets `--staged` lint what is being committed +/// rather than the working tree. +#[derive(Debug, Clone, PartialEq)] +pub struct SourceFile { + pub path: String, + from_index: bool, +} + +impl SourceFile { + #[cfg(test)] + pub fn in_working_tree(path: &str) -> SourceFile { + SourceFile { path: path.to_string(), from_index: false } + } + + /// Reads the content afresh on every call, so a run holds only the files in flight. + pub fn content(&self, root: &Path) -> Result, String> { + let read = if self.from_index { + Git::new(root.to_path_buf()).staged_content(&self.path).map_err(|e| e.to_string()) + } else { + std::fs::read(root.join(&self.path)).map_err(|e| e.to_string()) + }; + read.map_err(|e| format!("could not read {}: {e}", self.path)) + } +} + +pub struct FileFinder { + root: PathBuf, + git: Git, +} + +impl FileFinder { + pub fn new(root: PathBuf) -> FileFinder { + FileFinder { git: Git::new(root.clone()), root } + } + + /// Every file in the tree: git's view when inside a repository, else a walk. + pub fn all(&self) -> Result> { + Ok(self.sources(self.files_under(&self.root)?, false)) + } + + pub fn changed_since(&self, reference: &str) -> Result> { + self.require_repository()?; + Ok(self.sources(self.git.changed_files_since(reference)?, false)) + } + + pub fn staged(&self) -> Result> { + self.require_repository()?; + Ok(self.sources(self.git.staged_files()?, true)) + } + + /// Paths given on the command line, resolved against `from`. + pub fn explicit(&self, paths: &[PathBuf], from: &Path) -> Result> { + let mut files = Vec::new(); + for path in paths { + files.extend(self.files_under(&paths::normalize(&from.join(path)))?); + } + Ok(self.sources(files, false)) + } + + fn require_repository(&self) -> Result<()> { + if self.git.is_repository() { + return Ok(()); + } + Err(Error::new(format!("{} is not a git repository: --diff and --staged need git", self.root.display()))) + } + + /// Root-relative paths of the files at or beneath an absolute path. + fn files_under(&self, absolute: &Path) -> Result> { + let relative = paths::relative(absolute, &self.root) + .filter(|relative| relative != ".." && !relative.starts_with("../")) + .ok_or_else(|| { + Error::new(format!("{} is outside the config root {}", absolute.display(), self.root.display())) + })?; + if !absolute.is_dir() { + return Ok(vec![relative]); + } + if self.git.is_repository() { + return self.git.files(&[&relative]); + } + + let mut found = Vec::new(); + walk(absolute, "", &mut found); + let prefix = if relative == "." { String::new() } else { format!("{relative}/") }; + Ok(found + .into_iter() + .filter(|path| path != ".git" && !path.starts_with(".git/")) + .map(|path| format!("{prefix}{path}")) + .collect()) + } + + /// Sorted and deduplicated, keeping only files that exist in the working + /// tree unless they are read from the index. + fn sources(&self, mut paths: Vec, from_index: bool) -> Vec { + paths.sort(); + paths.dedup(); + paths + .into_iter() + .filter(|path| from_index || self.root.join(path).is_file()) + .map(|path| SourceFile { path, from_index }) + .collect() + } +} + +/// Everything beneath `dir`, dotfiles included, as `/`-separated paths +/// relative to it. Symlinked directories are listed but not entered. +fn walk(dir: &Path, prefix: &str, found: &mut Vec) { + let Ok(entries) = std::fs::read_dir(dir) else { return }; + for entry in entries.flatten() { + let name = entry.file_name().to_string_lossy().into_owned(); + let path = format!("{prefix}{name}"); + if entry.file_type().is_ok_and(|kind| kind.is_dir()) { + walk(&entry.path(), &format!("{path}/"), found); + } + found.push(path); + } +} diff --git a/crates/lintus/src/format.rs b/crates/lintus/src/format.rs new file mode 100644 index 0000000..b7dc47c --- /dev/null +++ b/crates/lintus/src/format.rs @@ -0,0 +1,308 @@ +//! Renders a report as text, GitHub Actions workflow commands, or JSON. + +use serde_json::json; + +use crate::report::Report; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Format { + /// One line per offense, for people. + Text, + /// Workflow commands, so each offense becomes an annotation on the pull request. + Github, + /// For other tools; also lists the probability every rule gave every file. + Json, +} + +impl Format { + pub const NAMES: [&str; 3] = ["text", "github", "json"]; + + pub fn from_name(name: &str) -> Option { + match name { + "text" => Some(Format::Text), + "github" => Some(Format::Github), + "json" => Some(Format::Json), + _ => None, + } + } + + /// Renders a sorted report, ending with a newline. + pub fn render(self, report: &Report) -> String { + match self { + Format::Text => text(report), + Format::Github => github(report), + Format::Json => json(report), + } + } +} + +fn text(report: &Report) -> String { + let mut out = String::new(); + for offense in &report.offenses { + out += &format!( + "{}: [{}] {} (noul {})\n", + offense.path, + offense.rule, + offense.message, + ruby_float(ruby_round(offense.noul, 2)) + ); + } + for skipped in &report.skipped { + out += &format!("{}: skipped, {}\n", skipped.path, skipped.reason); + } + for failure in &report.failures { + out += &format!("{}: failed, {}\n", failure.path, failure.error); + } + if !report.offenses.is_empty() || !report.skipped.is_empty() || !report.failures.is_empty() { + out.push('\n'); + } + out + &summary(report) + "\n" +} + +fn github(report: &Report) -> String { + let mut out = String::new(); + for offense in &report.offenses { + let title = format!("lintus: {}", offense.rule); + out += &command("error", &offense.message, &offense.path, &title); + } + for skipped in &report.skipped { + out += &command("notice", &format!("Skipped: {}", skipped.reason), &skipped.path, "lintus"); + } + for failure in &report.failures { + out += &command("error", &failure.error, &failure.path, "lintus: request failed"); + } + out + &summary(report) + "\n" +} + +fn command(level: &str, message: &str, file: &str, title: &str) -> String { + format!("::{level} file={},title={}::{}\n", escape_property(file), escape_property(title), escape_data(message)) +} + +fn escape_data(value: &str) -> String { + value.replace('%', "%25").replace('\r', "%0D").replace('\n', "%0A") +} + +fn escape_property(value: &str) -> String { + escape_data(value).replace(':', "%3A").replace(',', "%2C") +} + +fn json(report: &Report) -> String { + let round = |noul: f64| json!(ruby_round(noul, 3)); + let value = json!({ + "summary": { + "files_inspected": report.checked.len(), + "offenses": report.offenses.len(), + "skipped": report.skipped.len(), + "failures": report.failures.len(), + }, + "offenses": report.offenses.iter().map(|o| json!({ + "path": o.path, "rule": o.rule, "message": o.message, "noul": round(o.noul), + })).collect::>(), + "files": report.checked.iter().map(|c| json!({ + "path": c.path, + "answers": c.answers.iter().map(|(rule, noul)| (rule.clone(), round(*noul))).collect::>(), + })).collect::>(), + "skipped": report.skipped.iter().map(|s| json!({ "path": s.path, "reason": s.reason })).collect::>(), + "failures": report.failures.iter().map(|f| json!({ "path": f.path, "error": f.error })).collect::>(), + }); + serde_json::to_string_pretty(&value).expect("a report always serializes") + "\n" +} + +fn summary(report: &Report) -> String { + let mut parts = vec![ + format!("{} inspected", pluralize(report.checked.len(), "file")), + format!("{} detected", pluralize(report.offenses.len(), "offense")), + ]; + if !report.skipped.is_empty() { + parts.push(format!("{} skipped", pluralize(report.skipped.len(), "file"))); + } + if !report.failures.is_empty() { + parts.push(format!("{} failed", pluralize(report.failures.len(), "file"))); + } + parts.join(", ") +} + +fn pluralize(count: usize, noun: &str) -> String { + format!("{count} {noun}{}", if count == 1 { "" } else { "s" }) +} + +/// Ruby's `Float#round(digits)` for positive `digits`: rounds half away from +/// zero, correcting for the error of scaling by a power of ten. +pub fn ruby_round(x: f64, digits: i32) -> f64 { + if x == 0.0 || !x.is_finite() { + return x; + } + let binexp = frexp_exponent(x); + // float_round_overflow: the value already has fewer digits than asked for. + if digits >= (f64::DIGITS as i32 + 2) - if binexp > 0 { binexp / 4 } else { binexp / 3 - 1 } { + return x; + } + // float_round_underflow: the value is too small to show up at all. + if x > 0.0 && digits < -(if binexp > 0 { binexp / 3 + 1 } else { binexp / 4 }) { + return 0.0; + } + let scale = 10f64.powi(digits); + let mut rounded = (x * scale).round(); + if x > 0.0 { + if (rounded + 0.5) / scale <= x { + rounded += 1.0; + } + } else if (rounded - 0.5) / scale >= x { + rounded -= 1.0; + } + rounded / scale +} + +/// The exponent C's `frexp` gives: `x = m * 2^e` with `0.5 <= |m| < 1`. +fn frexp_exponent(x: f64) -> i32 { + let bits = x.to_bits(); + let exponent = ((bits >> 52) & 0x7ff) as i32; + if exponent == 0 { + // Subnormal: scale into the normal range first. + return frexp_exponent(x * 2f64.powi(64)) - 64; + } + exponent - 1022 +} + +/// Ruby's `Float#to_s`: the shortest representation that reads back as the +/// same float, with at least one decimal, in exponent form outside 1e-4..1e16. +pub fn ruby_float(x: f64) -> String { + if x.is_nan() { + return "NaN".into(); + } + if x.is_infinite() { + return if x > 0.0 { "Infinity" } else { "-Infinity" }.into(); + } + let debug = format!("{x:?}"); + let Some((mantissa, exponent)) = debug.split_once('e') else { return debug }; + let mantissa = if mantissa.contains('.') { mantissa.to_string() } else { format!("{mantissa}.0") }; + let (sign, digits) = match exponent.strip_prefix('-') { + Some(digits) => ('-', digits), + None => ('+', exponent), + }; + format!("{mantissa}e{sign}{digits:0>2}") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::report::{Checked, Failure, Offense, Skipped}; + + fn report() -> Report { + let mut report = Report { + checked: vec![ + Checked { + path: "lib/ok.rb".into(), + answers: vec![("no_sleep".into(), 0.0123), ("documented".into(), 0.9)], + }, + Checked { path: "app/jobs/a_job.rb".into(), answers: vec![] }, + ], + offenses: vec![ + Offense { + path: "app/jobs/a_job.rb".into(), + rule: "no_sleep".into(), + message: "Jobs must not sleep.".into(), + noul: 0.912, + }, + Offense { + path: "app/jobs/a_job.rb".into(), + rule: "documented".into(), + message: "Classes carry a comment.".into(), + noul: 0.3, + }, + ], + skipped: vec![Skipped { path: "lib/blob.rb".into(), reason: "binary file".into() }], + failures: vec![Failure { path: "lib/bad.rb".into(), error: "Jev API error 500: boom".into() }], + }; + report.sort(); + report + } + + #[test] + fn text() { + assert_eq!( + Format::Text.render(&report()), + "\ +app/jobs/a_job.rb: [documented] Classes carry a comment. (noul 0.3) +app/jobs/a_job.rb: [no_sleep] Jobs must not sleep. (noul 0.91) +lib/blob.rb: skipped, binary file +lib/bad.rb: failed, Jev API error 500: boom + +2 files inspected, 2 offenses detected, 1 file skipped, 1 file failed +" + ); + assert_eq!(Format::Text.render(&Report::default()), "0 files inspected, 0 offenses detected\n"); + } + + #[test] + fn github_emits_workflow_commands() { + let output = Format::Github.render(&report()); + let lines: Vec<_> = output.lines().collect(); + + assert_eq!(lines[0], "::error file=app/jobs/a_job.rb,title=lintus%3A documented::Classes carry a comment."); + assert_eq!(lines[1], "::error file=app/jobs/a_job.rb,title=lintus%3A no_sleep::Jobs must not sleep."); + assert_eq!(lines[2], "::notice file=lib/blob.rb,title=lintus::Skipped: binary file"); + assert_eq!(lines[3], "::error file=lib/bad.rb,title=lintus%3A request failed::Jev API error 500: boom"); + assert_eq!(lines[4], "2 files inspected, 2 offenses detected, 1 file skipped, 1 file failed"); + } + + #[test] + fn github_escapes_newlines_and_percent_in_messages() { + let report = Report { + checked: vec![Checked { path: "a.rb".into(), answers: vec![] }], + offenses: vec![Offense { path: "a.rb".into(), rule: "r".into(), message: "100%\nsure".into(), noul: 1.0 }], + ..Report::default() + }; + assert_eq!( + Format::Github.render(&report), + "::error file=a.rb,title=lintus%3A r::100%25%0Asure\n1 file inspected, 1 offense detected\n" + ); + } + + #[test] + fn json() { + let data: serde_json::Value = serde_json::from_str(&Format::Json.render(&report())).unwrap(); + + assert_eq!(data["summary"], json!({ "files_inspected": 2, "offenses": 2, "skipped": 1, "failures": 1 })); + assert_eq!( + data["offenses"][1], + json!({ "path": "app/jobs/a_job.rb", "rule": "no_sleep", "message": "Jobs must not sleep.", "noul": 0.912 }) + ); + assert_eq!( + data["files"], + json!([ + { "path": "app/jobs/a_job.rb", "answers": {} }, + { "path": "lib/ok.rb", "answers": { "no_sleep": 0.012, "documented": 0.9 } } + ]) + ); + assert_eq!(data["skipped"], json!([{ "path": "lib/blob.rb", "reason": "binary file" }])); + assert_eq!(data["failures"][0]["error"], "Jev API error 500: boom"); + } + + #[test] + fn ruby_float_prints_like_ruby() { + assert_eq!(ruby_float(1.0), "1.0"); + assert_eq!(ruby_float(0.0), "0.0"); + assert_eq!(ruby_float(0.95), "0.95"); + assert_eq!(ruby_float(0.0001), "0.0001"); + assert_eq!(ruby_float(0.00001), "1.0e-05"); + assert_eq!(ruby_float(1.5e16), "1.5e+16"); + assert_eq!(ruby_float(1e15), "1000000000000000.0"); + } + + /// Every value Ruby rounded and printed, as generated by + /// `tests/fixtures/generate_round_cases.rb`. + #[test] + fn rounds_and_prints_like_ruby_on_every_generated_case() { + let cases = include_str!("../tests/fixtures/round_cases.tsv"); + let mut checked = 0; + for line in cases.lines().filter(|line| !line.is_empty()) { + let fields: Vec<_> = line.split('\t').collect(); + let x = f64::from_bits(u64::from_str_radix(fields[0], 16).unwrap()); + assert_eq!(ruby_float(ruby_round(x, 2)), fields[1], "{x:?}.round(2)"); + assert_eq!(ruby_float(ruby_round(x, 3)), fields[2], "{x:?}.round(3)"); + checked += 1; + } + assert!(checked > 5000, "only {checked} cases"); + } +} diff --git a/crates/lintus/src/git.rs b/crates/lintus/src/git.rs new file mode 100644 index 0000000..c424b9e --- /dev/null +++ b/crates/lintus/src/git.rs @@ -0,0 +1,81 @@ +//! A thin wrapper around the git commands file discovery needs. + +use std::path::PathBuf; +use std::process::Command; + +use crate::error::{Error, Result}; + +/// Lists added, copied, modified and renamed files, but not deleted ones. +const CHANGE_FILTER: &str = "--diff-filter=ACMR"; + +pub struct Git { + root: PathBuf, +} + +impl Git { + pub fn new(root: PathBuf) -> Git { + Git { root } + } + + pub fn is_repository(&self) -> bool { + self.run(&["rev-parse", "--is-inside-work-tree"]).is_ok_and(|out| out.trim_end() == "true") + } + + /// Tracked and untracked files, honouring .gitignore, under the given paths. + pub fn files(&self, pathspecs: &[&str]) -> Result> { + let mut args = vec!["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--"]; + args.extend_from_slice(pathspecs); + self.lines(&args) + } + + pub fn staged_files(&self) -> Result> { + self.lines(&["diff", "-z", "--cached", "--name-only", CHANGE_FILTER]) + } + + /// Files that differ between the working tree and the merge base of + /// `reference` and HEAD, plus untracked files. With `reference` at HEAD + /// this is "what I have not committed yet". + pub fn changed_files_since(&self, reference: &str) -> Result> { + let base = self + .run(&["merge-base", reference, "HEAD"]) + .map(|out| out.trim_end().to_string()) + .unwrap_or_else(|_| reference.to_string()); + let mut files = self.lines(&["diff", "-z", "--name-only", CHANGE_FILTER, &base])?; + files.extend(self.lines(&["ls-files", "-z", "--others", "--exclude-standard"])?); + Ok(files) + } + + /// The content of a file as staged in the index. + pub fn staged_content(&self, path: &str) -> Result> { + self.run_bytes(&["show", &format!(":{path}")]) + } + + fn lines(&self, args: &[&str]) -> Result> { + let out = self.run_bytes(args)?; + Ok(out + .split(|&b| b == 0) + .filter(|path| !path.is_empty()) + .map(|path| String::from_utf8_lossy(path).into_owned()) + .collect()) + } + + fn run(&self, args: &[&str]) -> Result { + self.run_bytes(args).map(|out| String::from_utf8_lossy(&out).into_owned()) + } + + fn run_bytes(&self, args: &[&str]) -> Result> { + let output = Command::new("git") + .args(args) + .current_dir(&self.root) + .output() + .map_err(|e| Error::new(format!("could not run git: {e}")))?; + if !output.status.success() { + return Err(Error::new(format!( + "git {} failed: {}", + args.join(" "), + String::from_utf8_lossy(&output.stderr).trim() + ))); + } + Ok(output.stdout) + } +} diff --git a/crates/lintus/src/glob.rs b/crates/lintus/src/glob.rs new file mode 100644 index 0000000..06df4da --- /dev/null +++ b/crates/lintus/src/glob.rs @@ -0,0 +1,358 @@ +//! Matches repository-relative paths against the globs used in the config file. +//! +//! Patterns follow Ruby's `File.fnmatch` with `FNM_PATHNAME | FNM_EXTGLOB | +//! FNM_DOTMATCH`: `*`, `?` and `[...]` never cross a `/`, `**/` spans any +//! number of directories, `{a,b}` is alternation, and wildcards match dotfiles. +//! On top of that, a bare directory (`vendor` or `vendor/`) and a trailing `**` +//! (`vendor/**`) match everything beneath it. +//! +//! The matcher is a port of the one in Ruby's dir.c rather than a glob crate, +//! so that a config file matches exactly the files it matched under Ruby. + +#[derive(Debug, Clone, PartialEq)] +pub struct Glob { + /// Every pattern this glob stands for, after the directory conveniences + /// and brace expansion. A path matches if it matches any of them. + alternatives: Vec>, +} + +impl Glob { + pub fn new(pattern: &str) -> Glob { + let mut alternatives = Vec::new(); + for expanded in expand(pattern) { + brace_expand(&expanded.chars().collect::>(), &mut alternatives); + } + Glob { alternatives } + } + + pub fn matches(&self, path: &str) -> bool { + let path: Vec = path.chars().collect(); + self.alternatives.iter().any(|pattern| fnmatch(pattern, &path)) + } +} + +pub fn matches_any(globs: &[Glob], path: &str) -> bool { + globs.iter().any(|glob| glob.matches(path)) +} + +/// The directory conveniences: "./vendor/" is "vendor", which also matches +/// "vendor/**/*"; "vendor/**" also matches "vendor/**/*". +fn expand(pattern: &str) -> Vec { + let pattern = pattern.strip_prefix("./").unwrap_or(pattern); + let pattern = pattern.strip_suffix('/').unwrap_or(pattern); + + if pattern.ends_with("**") { + vec![pattern.to_string(), format!("{pattern}/*")] + } else if pattern.contains(['*', '?', '[', '{']) { + vec![pattern.to_string()] + } else { + vec![pattern.to_string(), format!("{pattern}/**/*")] + } +} + +/// Expands the first top-level `{...}` group into one pattern per +/// alternative, recursively. A `{` without its `}` yields nothing at all, +/// as in Ruby, so such a pattern never matches. +fn brace_expand(pattern: &[char], out: &mut Vec>) { + let (mut lbrace, mut rbrace) = (None, None); + let mut nest = 0; + let mut i = 0; + while i < pattern.len() { + let c = pattern[i]; + if c == '{' { + if nest == 0 { + lbrace = Some(i); + } + nest += 1; + } + if c == '}' && lbrace.is_some() { + nest -= 1; + if nest == 0 { + rbrace = Some(i); + break; + } + } + if c == '\\' { + i += 1; + if i >= pattern.len() { + break; + } + } + i += 1; + } + + match (lbrace, rbrace) { + (Some(left), Some(right)) => { + let mut p = left; + while p < right { + p += 1; + let start = p; + let mut nest = 0; + while p < right && !(pattern[p] == ',' && nest == 0) { + match pattern[p] { + '{' => nest += 1, + '}' => nest -= 1, + '\\' => { + p += 1; + if p == right { + break; + } + } + _ => {} + } + p += 1; + } + let mut alternative = pattern[..left].to_vec(); + alternative.extend_from_slice(&pattern[start..p]); + alternative.extend_from_slice(&pattern[right + 1..]); + brace_expand(&alternative, out); + } + } + (None, None) => out.push(pattern.to_vec()), + _ => {} + } +} + +/// Ruby's `fnmatch` in pathname mode: matches segment by segment, and on a +/// `**/` retries the rest of the pattern at each deeper directory. +fn fnmatch(pattern: &[char], path: &[char]) -> bool { + let (mut p, mut s) = (0, 0); + let mut star_star: Option<(usize, usize)> = None; + + loop { + if pattern[p..].starts_with(&['*', '*', '/']) { + while pattern[p..].starts_with(&['*', '*', '/']) { + p += 3; + } + star_star = Some((p, s)); + } + + let (matched, next_p, mut next_s) = match_segment(pattern, p, path, s); + if matched { + while next_s < path.len() && path[next_s] != '/' { + next_s += 1; + } + if next_p < pattern.len() && next_s < path.len() { + p = next_p + 1; + s = next_s + 1; + continue; + } + if next_p >= pattern.len() && next_s >= path.len() { + return true; + } + } + + // Let the last `**/` swallow one more directory, and try again. + if let Some((retry_p, mut retry_s)) = star_star { + while retry_s < path.len() && path[retry_s] != '/' { + retry_s += 1; + } + if retry_s < path.len() { + retry_s += 1; + p = retry_p; + s = retry_s; + star_star = Some((retry_p, retry_s)); + continue; + } + } + return false; + } +} + +/// Ruby's `fnmatch_helper`: matches one path segment against one pattern +/// segment, both ending at a `/` or at their end. Returns whether they +/// matched and where each one stopped. +fn match_segment(pattern: &[char], mut p: usize, path: &[char], mut s: usize) -> (bool, usize, usize) { + let p_end = |i: usize| i >= pattern.len() || pattern[i] == '/'; + let s_end = |i: usize| i >= path.len() || path[i] == '/'; + let unescape = |i: usize| if pattern.get(i) == Some(&'\\') { i + 1 } else { i }; + let mut star: Option<(usize, usize)> = None; + + loop { + let mut failed = false; + match pattern.get(p) { + Some('*') => { + while pattern.get(p) == Some(&'*') { + p += 1; + } + if p_end(unescape(p)) { + return (true, unescape(p), s); + } + if s_end(s) { + return (false, p, s); + } + star = Some((p, s)); + continue; + } + Some('?') => { + if s_end(s) { + return (false, p, s); + } + p += 1; + s += 1; + continue; + } + Some('[') => { + if s_end(s) { + return (false, p, s); + } + match bracket(pattern, p + 1, path[s]) { + Some(after) => { + p = after; + s += 1; + continue; + } + None => failed = true, + } + } + _ => {} + } + + if !failed { + p = unescape(p); + if s_end(s) { + return (p_end(p), p, s); + } + if !p_end(p) && pattern[p] == path[s] { + p += 1; + s += 1; + continue; + } + } + + // Let the last `*` swallow one more character, and try again. + match star { + Some((star_p, star_s)) => { + p = star_p; + s = star_s + 1; + star = Some((star_p, s)); + } + None => return (false, p, s), + } + } +} + +/// Ruby's `bracket`: matches `c` against the class starting right after a +/// `[`, and returns the position after its `]`. `None` when `c` is not in the +/// class, or the class is not closed. +fn bracket(pattern: &[char], mut p: usize, c: char) -> Option { + let end = pattern.len(); + if p >= end { + return None; + } + let negated = pattern[p] == '!' || pattern[p] == '^'; + if negated { + p += 1; + } + + let mut ok = false; + while pattern.get(p) != Some(&']') { + let mut first = p; + if pattern.get(first) == Some(&'\\') { + first += 1; + } + if first >= end { + return None; + } + p = first + 1; + if p >= end { + return None; + } + + if pattern[p] == '-' && pattern.get(p + 1) != Some(&']') { + let mut last = p + 1; + if pattern.get(last) == Some(&'\\') { + last += 1; + } + if last >= end { + return None; + } + p = last + 1; + if ok { + continue; + } + if pattern[first] == c || pattern[last] == c { + ok = true; + continue; + } + if c < pattern[first] || c > pattern[last] { + continue; + } + } else { + if ok { + continue; + } + if pattern[first] == c { + ok = true; + } + continue; + } + ok = true; + } + + if ok == negated { None } else { Some(p + 1) } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn matches(pattern: &str, path: &str) -> bool { + Glob::new(pattern).matches(path) + } + + #[test] + fn double_star_spans_directories() { + assert!(matches("app/**/*.rb", "app/jobs/foo_job.rb")); + assert!(matches("app/**/*.rb", "app/foo.rb")); + assert!(!matches("app/**/*.rb", "lib/foo.rb")); + } + + #[test] + fn bare_directory_matches_everything_beneath_it() { + assert!(matches("vendor", "vendor/gem/lib/x.rb")); + assert!(matches("vendor/", "vendor/x.rb")); + assert!(matches("./vendor", "vendor/x.rb")); + assert!(!matches("vendor", "vendored/x.rb")); + } + + #[test] + fn trailing_double_star_matches_nested_files() { + assert!(matches("vendor/**", "vendor/a/b/c.rb")); + assert!(matches("vendor/**", "vendor/c.rb")); + } + + #[test] + fn exact_file() { + assert!(matches("Gemfile", "Gemfile")); + assert!(!matches("Gemfile", "sub/Gemfile")); + } + + #[test] + fn braces_and_dotfiles() { + assert!(matches("**/*.{yml,yaml}", "config/a.yaml")); + assert!(matches("**/*.yml", ".github/workflows/ci.yml")); + assert!(!matches("{a,b", "a")); + } + + #[test] + fn single_star_does_not_cross_directories() { + assert!(!matches("app/*.rb", "app/jobs/foo.rb")); + } + + /// Every (pattern, path) pair of a grid, and whether Ruby's + /// `Lintus::Glob.match?` matched it, as generated by + /// `tests/fixtures/generate_glob_cases.rb`. + #[test] + fn agrees_with_ruby_on_every_generated_case() { + let cases = include_str!("../tests/fixtures/glob_cases.tsv"); + let mut checked = 0; + for line in cases.lines().filter(|line| !line.is_empty()) { + let mut fields = line.split('\t'); + let (pattern, path, expected) = (fields.next().unwrap(), fields.next().unwrap(), fields.next().unwrap()); + assert_eq!(matches(pattern, path), expected == "1", "pattern {pattern:?} against {path:?}"); + checked += 1; + } + assert!(checked > 1000, "only {checked} cases"); + } +} diff --git a/crates/lintus/src/main.rs b/crates/lintus/src/main.rs index f328e4d..27e0d30 100644 --- a/crates/lintus/src/main.rs +++ b/crates/lintus/src/main.rs @@ -1 +1,22 @@ -fn main() {} +//! Lintus: a linter whose rules are plain-language questions, answered by the +//! Jev model. See the README for the command line and the config file. + +mod cli; +mod config; +mod credentials; +mod error; +mod files; +mod format; +mod git; +mod glob; +mod paths; +mod progress; +mod report; +mod runner; +mod template; +mod yaml; + +fn main() { + let code = cli::run(std::env::args_os().collect()); + std::process::exit(code); +} diff --git a/crates/lintus/src/paths.rs b/crates/lintus/src/paths.rs new file mode 100644 index 0000000..f7dd344 --- /dev/null +++ b/crates/lintus/src/paths.rs @@ -0,0 +1,58 @@ +//! Path arithmetic done on the path's text, the way Ruby's `File.expand_path` +//! and `Pathname#relative_path_from` do it: `..` removes the component before +//! it, and symlinks are never resolved. + +use std::path::{Component, Path, PathBuf}; + +/// Removes `.` components and folds `..` into the component before it. +pub fn normalize(path: &Path) -> PathBuf { + let mut out = PathBuf::new(); + for component in path.components() { + match component { + Component::CurDir => {} + Component::ParentDir => { + if !out.pop() { + out.push(component); + } + } + other => out.push(other), + } + } + out +} + +/// The path to `path` from `base`, both absolute and normalized, with `/` +/// between components: "." for `base` itself, and starting with ".." when +/// `path` is outside `base`. `None` when they share no root, such as two +/// Windows drives. +pub fn relative(path: &Path, base: &Path) -> Option { + let path: Vec = path.components().collect(); + let base: Vec = base.components().collect(); + if path.first() != base.first() { + return None; + } + let common = path.iter().zip(&base).take_while(|(a, b)| a == b).count(); + + let mut parts: Vec = vec!["..".to_string(); base.len() - common]; + parts.extend(path[common..].iter().map(|c| c.as_os_str().to_string_lossy().into_owned())); + Some(if parts.is_empty() { ".".to_string() } else { parts.join("/") }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn normalize_folds_dots_without_touching_the_filesystem() { + assert_eq!(normalize(Path::new("/a/b/../c/./d")), PathBuf::from("/a/c/d")); + assert_eq!(normalize(Path::new("/a/..")), PathBuf::from("/")); + } + + #[test] + fn relative_paths_use_forward_slashes() { + assert_eq!(relative(Path::new("/root/app/x.rb"), Path::new("/root")).as_deref(), Some("app/x.rb")); + assert_eq!(relative(Path::new("/root"), Path::new("/root")).as_deref(), Some(".")); + assert_eq!(relative(Path::new("/other/x.rb"), Path::new("/root")).as_deref(), Some("../other/x.rb")); + assert_eq!(relative(Path::new("/rooted/x.rb"), Path::new("/root")).as_deref(), Some("../rooted/x.rb")); + } +} diff --git a/crates/lintus/src/progress.rs b/crates/lintus/src/progress.rs new file mode 100644 index 0000000..1bf4961 --- /dev/null +++ b/crates/lintus/src/progress.rs @@ -0,0 +1,96 @@ +//! Tells the person waiting that something is happening. On a terminal it is +//! a counter updated in place and erased when done; elsewhere (CI logs, pipes) +//! it is a single line announcing the run, so nothing garbles captured output. + +use std::io::Write; + +pub struct Progress { + out: W, + total: usize, + tty: bool, + width: usize, +} + +impl Progress { + pub fn new(out: W, total: usize, tty: bool) -> Progress { + Progress { out, total, tty, width: 0 } + } + + pub fn start(&mut self) { + if self.total == 0 { + return; + } + if self.tty { + self.draw(0); + } else { + let noun = if self.total == 1 { "file" } else { "files" }; + let _ = writeln!(self.out, "Checking {} {noun}...", self.total); + } + } + + pub fn update(&mut self, done: usize) { + if self.tty { + self.draw(done); + } + } + + pub fn finish(&mut self) { + if !self.tty || self.width == 0 { + return; + } + let _ = write!(self.out, "\r{}\r", " ".repeat(self.width)); + let _ = self.out.flush(); + } + + fn draw(&mut self, done: usize) { + let line = format!("Checking {done}/{} files", self.total); + self.width = self.width.max(line.chars().count()); + let _ = write!(self.out, "\r{line: = out.split('\r').filter(|frame| !frame.is_empty()).collect(); + assert_eq!(frames, ["Checking 0/12 files", "Checking 1/12 files", "Checking 12/12 files", &" ".repeat(20)]); + assert!(out.ends_with('\r')); + } + + #[test] + fn off_a_terminal_prints_one_line_and_never_redraws() { + let mut out = Vec::new(); + let mut progress = Progress::new(&mut out, 3, false); + + progress.start(); + progress.update(1); + progress.update(3); + progress.finish(); + + assert_eq!(String::from_utf8(out).unwrap(), "Checking 3 files...\n"); + } + + #[test] + fn says_nothing_when_there_is_nothing_to_check() { + let mut out = Vec::new(); + let mut progress = Progress::new(&mut out, 0, true); + + progress.start(); + progress.finish(); + + assert!(out.is_empty()); + } +} diff --git a/crates/lintus/src/report.rs b/crates/lintus/src/report.rs new file mode 100644 index 0000000..ed12b25 --- /dev/null +++ b/crates/lintus/src/report.rs @@ -0,0 +1,93 @@ +//! Everything a run produced: what was checked, what was flagged, what was skipped, what broke. + +/// A file the model answered about, with the probability each rule got, +/// offense or not, so a run can be read for confidence and not just verdicts. +#[derive(Debug, Clone, PartialEq)] +pub struct Checked { + pub path: String, + pub answers: Vec<(String, f64)>, +} + +/// A rule the model flagged on a file, with the probability it gave. +#[derive(Debug, Clone, PartialEq)] +pub struct Offense { + pub path: String, + pub rule: String, + pub message: String, + pub noul: f64, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Skipped { + pub path: String, + pub reason: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Failure { + pub path: String, + pub error: String, +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Report { + pub checked: Vec, + pub offenses: Vec, + pub skipped: Vec, + pub failures: Vec, +} + +impl Report { + /// Puts every list in a stable order, whatever order the files finished in: + /// by path, and offenses by path then rule. + pub fn sort(&mut self) { + self.checked.sort_by(|a, b| a.path.cmp(&b.path)); + self.offenses.sort_by(|a, b| (&a.path, &a.rule).cmp(&(&b.path, &b.rule))); + self.skipped.sort_by(|a, b| a.path.cmp(&b.path)); + self.failures.sort_by(|a, b| a.path.cmp(&b.path)); + } + + /// 2 when a file could not be checked, 1 when there are offenses, else 0. + pub fn exit_status(&self) -> i32 { + if !self.failures.is_empty() { + 2 + } else if !self.offenses.is_empty() { + 1 + } else { + 0 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn offense(path: &str, rule: &str) -> Offense { + Offense { path: path.into(), rule: rule.into(), message: String::new(), noul: 0.9 } + } + + #[test] + fn exit_status() { + let mut report = Report::default(); + assert_eq!(report.exit_status(), 0); + + report.offenses.push(offense("a.rb", "r")); + assert_eq!(report.exit_status(), 1); + + report.failures.push(Failure { path: "b.rb".into(), error: "boom".into() }); + assert_eq!(report.exit_status(), 2); + } + + #[test] + fn offenses_sort_by_path_then_rule() { + let mut report = Report { + offenses: vec![offense("b.rb", "no_sleep"), offense("a.rb", "no_sleep"), offense("a.rb", "documented")], + ..Report::default() + }; + report.sort(); + + let order: Vec<_> = report.offenses.iter().map(|o| (o.path.as_str(), o.rule.as_str())).collect(); + assert_eq!(order, [("a.rb", "documented"), ("a.rb", "no_sleep"), ("b.rb", "no_sleep")]); + } +} diff --git a/crates/lintus/src/runner.rs b/crates/lintus/src/runner.rs new file mode 100644 index 0000000..c63bc3a --- /dev/null +++ b/crates/lintus/src/runner.rs @@ -0,0 +1,306 @@ +//! Sends each file to Jev once, with every rule that applies to it as a +//! separate noul question, and turns the answers into offenses. + +use std::hash::{BuildHasher, Hasher}; +use std::sync::Mutex; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +use crate::config::{Config, Rule}; +use crate::files::SourceFile; +use crate::report::{Checked, Failure, Offense, Report, Skipped}; + +/// A file and the rules that apply to it. +pub struct Task<'c> { + pub file: SourceFile, + pub rules: Vec<&'c Rule>, +} + +/// Pairs every file with the rules that apply to it, dropping files no rule covers. +pub fn plan(config: &Config, files: Vec) -> Vec> { + files + .into_iter() + .filter_map(|file| { + let rules = config.rules_for(&file.path); + (!rules.is_empty()).then_some(Task { file, rules }) + }) + .collect() +} + +pub struct Runner<'c> { + config: &'c Config, + client: jev::Client, + jobs: usize, + retries: u32, + sleep: Box, +} + +enum Outcome { + Checked(Checked, Vec), + Skipped(Skipped), + Failed(Failure), +} + +impl<'c> Runner<'c> { + pub const RETRIES: u32 = 3; + + /// `jobs` is how many requests may be in flight at once; anything below 1 means 1. + pub fn new(config: &'c Config, client: jev::Client, jobs: i64) -> Runner<'c> { + Runner { + config, + client, + jobs: jobs.clamp(1, 1024) as usize, + retries: Self::RETRIES, + sleep: Box::new(std::thread::sleep), + } + } + + #[cfg(test)] + fn with_retries(mut self, retries: u32, sleep: impl Fn(Duration) + Sync + 'c) -> Runner<'c> { + self.retries = retries; + self.sleep = Box::new(sleep); + self + } + + /// Checks the tasks concurrently. `on_done` is called with the number of + /// files done after each one finishes, one call at a time. + pub fn run(&self, tasks: &[Task], on_done: impl Fn(usize) + Sync) -> Report { + let next = AtomicUsize::new(0); + let report = Mutex::new(Report::default()); + let done = Mutex::new(0); + + std::thread::scope(|scope| { + for _ in 0..self.jobs.min(tasks.len()) { + scope.spawn(|| { + while let Some(task) = tasks.get(next.fetch_add(1, Ordering::SeqCst)) { + let outcome = self.check(task); + { + let mut report = report.lock().unwrap(); + match outcome { + Outcome::Checked(checked, offenses) => { + report.checked.push(checked); + report.offenses.extend(offenses); + } + Outcome::Skipped(skipped) => report.skipped.push(skipped), + Outcome::Failed(failure) => report.failures.push(failure), + } + } + let mut done = done.lock().unwrap(); + *done += 1; + on_done(*done); + } + }); + } + }); + + let mut report = report.into_inner().unwrap(); + report.sort(); + report + } + + fn check(&self, task: &Task) -> Outcome { + let path = &task.file.path; + let failed = |error: String| Outcome::Failed(Failure { path: path.clone(), error }); + let skipped = |reason: String| Outcome::Skipped(Skipped { path: path.clone(), reason }); + + let content = match task.file.content(&self.config.root) { + Ok(content) => content, + Err(error) => return failed(error), + }; + if content.contains(&0) { + return skipped("binary file".into()); + } + let Ok(content) = String::from_utf8(content) else { return skipped("binary file".into()) }; + if content.len() as u64 > self.config.max_file_size { + return skipped(format!( + "larger than max_file_size ({} > {} bytes)", + content.len(), + self.config.max_file_size + )); + } + + let mut query = jev::Query::new(format!("File: {path}\n\n{content}")); + for rule in &task.rules { + if let Err(error) = query.ask(&rule.id, rule.to_noul()) { + return failed(error.to_string()); + } + } + let response = match self.retrying(|| self.client.perform(&query)) { + Ok(response) => response, + Err(error) => return failed(error.to_string()), + }; + + let mut answers = Vec::new(); + let mut offenses = Vec::new(); + for rule in &task.rules { + let answer = match response.noul(&rule.id) { + Ok(answer) => answer, + Err(error) => return failed(error.to_string()), + }; + answers.push((rule.id.clone(), answer.noul)); + if rule.is_offense(answer) { + offenses.push(Offense { + path: path.clone(), + rule: rule.id.clone(), + message: rule.description.clone(), + noul: answer.noul, + }); + } + } + Outcome::Checked(Checked { path: path.clone(), answers }, offenses) + } + + /// Retries rate-limited and overloaded requests with exponential backoff. + fn retrying(&self, mut request: impl FnMut() -> jev::Result) -> jev::Result { + let mut attempt = 0; + loop { + match request() { + Err(error) if error.is_retryable() && attempt < self.retries => { + attempt += 1; + (self.sleep)(backoff(attempt)); + } + result => return result, + } + } + } +} + +/// 2^attempt seconds, give or take half, so parallel workers do not retry in lockstep. +fn backoff(attempt: u32) -> Duration { + let jitter = 0.5 + random_fraction() / 2.0; + Duration::from_secs_f64(2f64.powi(attempt as i32) * jitter) +} + +/// A number in [0, 1), from the random keys std seeds its hash maps with. +fn random_fraction() -> f64 { + let bits = std::collections::hash_map::RandomState::new().build_hasher().finish(); + (bits >> 11) as f64 / (1u64 << 53) as f64 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::yaml; + use std::sync::Arc; + use std::sync::atomic::AtomicUsize; + + const CONFIG: &str = "paths: ['**/*.rb']\nrules:\n documented:\n question: Is every class documented?\n offense_when: false\n"; + + /// A server answering every request with `status` and `body`, counting them. + fn server(status: u16, body: &'static str) -> (String, Arc) { + let server = tiny_http::Server::http("127.0.0.1:0").unwrap(); + let url = format!("http://{}/", server.server_addr().to_ip().unwrap()); + let count = Arc::new(AtomicUsize::new(0)); + let counter = count.clone(); + std::thread::spawn(move || { + for request in server.incoming_requests() { + counter.fetch_add(1, Ordering::SeqCst); + let _ = request.respond(tiny_http::Response::from_string(body).with_status_code(status)); + } + }); + (url, count) + } + + fn project(files: &[&str]) -> (tempfile::TempDir, Config) { + let dir = tempfile::tempdir().unwrap(); + for file in files { + std::fs::write(dir.path().join(file), "class Foo; end\n").unwrap(); + } + let config = Config::from_yaml(yaml::load(CONFIG).unwrap(), dir.path().to_path_buf()).unwrap(); + (dir, config) + } + + fn sources(paths: &[&str]) -> Vec { + paths.iter().map(|path| SourceFile::in_working_tree(path)).collect() + } + + fn client(url: &str) -> jev::Client { + jev::Client::new("key").unwrap().with_api_url(url) + } + + #[test] + fn plan_drops_files_no_rule_covers() { + let (_dir, config) = project(&[]); + let tasks = plan(&config, sources(&["a.rb", "README.md", "lib/b.rb"])); + + let planned: Vec<_> = tasks.iter().map(|task| task.file.path.as_str()).collect(); + assert_eq!(planned, ["a.rb", "lib/b.rb"]); + } + + #[test] + fn gives_up_after_the_retries_with_growing_backoff() { + let (_dir, config) = project(&["a.rb"]); + let (url, requests) = server(529, "overloaded"); + let slept = Mutex::new(Vec::new()); + + let runner = Runner::new(&config, client(&url), 1).with_retries(3, |d| slept.lock().unwrap().push(d)); + let report = runner.run(&plan(&config, sources(&["a.rb"])), |_| {}); + + assert_eq!(report.failures, [Failure { path: "a.rb".into(), error: "Jev API error 529: overloaded".into() }]); + assert_eq!(requests.load(Ordering::SeqCst), 4); + let slept = slept.lock().unwrap().clone(); + assert_eq!(slept.len(), 3); + for (attempt, duration) in slept.iter().enumerate() { + let base = 2f64.powi(attempt as i32 + 1); + assert!((base * 0.5..base).contains(&duration.as_secs_f64()), "attempt {attempt}: {duration:?}"); + } + } + + #[test] + fn other_errors_are_not_retried() { + let (_dir, config) = project(&["a.rb"]); + let (url, requests) = server(401, "bad key"); + + let runner = Runner::new(&config, client(&url), 1).with_retries(3, |_| panic!("should not sleep")); + let report = runner.run(&plan(&config, sources(&["a.rb"])), |_| {}); + + assert_eq!(report.failures[0].error, "Jev API error 401: bad key"); + assert_eq!(requests.load(Ordering::SeqCst), 1); + assert_eq!(report.exit_status(), 2); + } + + #[test] + fn an_unreachable_api_fails_each_file() { + let (_dir, config) = project(&["a.rb"]); + let port = std::net::TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port(); + + let runner = Runner::new(&config, client(&format!("http://127.0.0.1:{port}/")), 1); + let report = runner.run(&plan(&config, sources(&["a.rb"])), |_| {}); + + assert!(report.failures[0].error.starts_with("could not reach the Jev API"), "{:?}", report.failures); + } + + #[test] + fn unreadable_files_fail_without_a_request() { + let (_dir, config) = project(&[]); + let (url, requests) = server(200, "{}"); + + let report = Runner::new(&config, client(&url), 1).run(&plan(&config, sources(&["gone.rb"])), |_| {}); + + assert!(report.failures[0].error.starts_with("could not read gone.rb: "), "{:?}", report.failures); + assert_eq!(requests.load(Ordering::SeqCst), 0); + } + + #[test] + fn reports_progress_after_each_file() { + let (_dir, config) = project(&["a.rb", "b.rb", "c.rb", "d.rb"]); + let (url, _) = server(200, r#"{"answers":{"documented":{"type":"noul","noul":0.9}}}"#); + let seen = Mutex::new(Vec::new()); + + let tasks = plan(&config, sources(&["a.rb", "b.rb", "c.rb", "d.rb"])); + let report = Runner::new(&config, client(&url), 2).run(&tasks, |done| seen.lock().unwrap().push(done)); + + assert_eq!(seen.into_inner().unwrap(), [1, 2, 3, 4]); + assert_eq!(report.checked.len(), 4); + assert_eq!(report.checked[0].answers, [("documented".to_string(), 0.9)]); + } + + #[test] + fn backoff_doubles_with_jitter() { + for attempt in 1..=3 { + let base = 2f64.powi(attempt as i32); + let delay = backoff(attempt).as_secs_f64(); + assert!(delay >= base * 0.5 && delay < base, "{attempt}: {delay}"); + } + } +} diff --git a/crates/lintus/src/template.rs b/crates/lintus/src/template.rs new file mode 100644 index 0000000..2c14e87 --- /dev/null +++ b/crates/lintus/src/template.rs @@ -0,0 +1,44 @@ +//! The starter config written by `lintus init`. + +pub const CONFIG: &str = r#"# Lintus configuration. Every rule is a question asked of the Jev model +# about each matching file. Answer "true" means the file is an offense. +# +# Top-level `paths` and `exclude` apply to every rule; a rule can narrow +# them with its own `paths` and `exclude`. Globs are relative to this file. + +paths: + - "**/*.rb" + +exclude: + - "vendor/**" + - "node_modules/**" + - "tmp/**" + +# Files larger than this (in bytes) are skipped rather than sent to the model. +max_file_size: 100000 + +rules: + no_debugging_leftovers: + description: Debugging statements must not be committed. + question: Does this file contain leftover debugging code, such as binding.irb, debugger, byebug, or a puts/p/pp used for debugging? + criteria: + "true": A debugger breakpoint or a throwaway print statement is present. + "false": Any output is deliberate program behaviour, or there is none. + + no_hardcoded_secrets: + description: Secrets must come from the environment or a credentials store, never source code. + question: Does this file contain a hardcoded secret, such as an API key, password, or private token? + criteria: + "true": A literal credential value is written in the source. + "false": Credentials are read from configuration, or there are none. + threshold: 0.7 + + classes_are_documented: + description: Classes should open with a short comment explaining their purpose. + question: Is there a class or module defined in this file whose definition is not preceded by a comment describing what it is for? + criteria: + "true": A class or module with a body of its own has no descriptive comment right above its definition. + "false": >- + Every class or module that carries behaviour has such a comment, or the file defines none. + Ignore namespace wrappers that only nest other definitions and one-line subclasses. +"#; diff --git a/crates/lintus/src/yaml.rs b/crates/lintus/src/yaml.rs new file mode 100644 index 0000000..634f06c --- /dev/null +++ b/crates/lintus/src/yaml.rs @@ -0,0 +1,449 @@ +//! Loads the config file's YAML the way Ruby's Psych does, so config files +//! written for the Ruby lintus mean the same thing here. +//! +//! yaml-rust2 does the parsing, but its values follow YAML 1.2. Psych follows +//! YAML 1.1 instead: `yes`, `no`, `on` and `off` are booleans, integers may +//! contain `_` and `,` and have `0x`, `0b` and leading-`0` octal forms, a +//! leading `:` makes a symbol, `<<` merges mappings, and a repeated key keeps +//! the last value instead of being an error. This module builds values from +//! the parser's events with Psych's rules. + +use std::collections::HashMap; +use std::fmt::Write; + +use yaml_rust2::parser::{Event, MarkedEventReceiver, Parser, Tag}; +use yaml_rust2::scanner::{Marker, TScalarStyle}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Value { + Null, + Bool(bool), + Int(i64), + Float(f64), + Str(String), + Seq(Vec), + Map(Map), +} + +/// A mapping that keeps insertion order. Setting a key that is already +/// present replaces its value in place, as a Ruby Hash does. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Map(Vec<(Value, Value)>); + +impl Map { + pub fn insert(&mut self, key: Value, value: Value) { + match self.0.iter_mut().find(|(existing, _)| *existing == key) { + Some(entry) => entry.1 = value, + None => self.0.push((key, value)), + } + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + #[cfg(test)] + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +/// Parses the first document of `source`. An empty document is `Null`. +pub fn load(source: &str) -> Result { + let mut builder = Builder::default(); + let mut parser = Parser::new_from_str(source); + parser.load(&mut builder, false).map_err(|e| e.to_string())?; + match builder.error { + Some(error) => Err(error), + None => Ok(builder.root.unwrap_or(Value::Null)), + } +} + +impl Value { + /// Ruby's `to_s`. + pub fn to_s(&self) -> String { + match self { + Value::Null => String::new(), + Value::Str(s) => s.clone(), + Value::Bool(b) => b.to_string(), + Value::Int(i) => i.to_string(), + Value::Float(f) => crate::format::ruby_float(*f), + Value::Seq(_) | Value::Map(_) => self.inspect(), + } + } + + /// Ruby's `inspect`, as printed in error messages. + pub fn inspect(&self) -> String { + match self { + Value::Null => "nil".into(), + Value::Str(s) => inspect_str(s), + Value::Seq(items) => format!("[{}]", items.iter().map(Value::inspect).collect::>().join(", ")), + Value::Map(map) => { + if map.is_empty() { + return "{}".into(); + } + let pairs: Vec<_> = map.iter().map(|(k, v)| format!("{} => {}", k.inspect(), v.inspect())).collect(); + format!("{{{}}}", pairs.join(", ")) + } + other => other.to_s(), + } + } + + /// The name of the Ruby class this value would have. + pub fn class_name(&self) -> &'static str { + match self { + Value::Null => "NilClass", + Value::Bool(true) => "TrueClass", + Value::Bool(false) => "FalseClass", + Value::Int(_) => "Integer", + Value::Float(_) => "Float", + Value::Str(_) => "String", + Value::Seq(_) => "Array", + Value::Map(_) => "Hash", + } + } +} + +fn inspect_str(s: &str) -> String { + let mut out = String::from("\""); + for c in s.chars() { + match c { + '"' => out.push_str("\\\""), + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\t' => out.push_str("\\t"), + '\r' => out.push_str("\\r"), + '#' => out.push('#'), + c if c.is_control() => { + let _ = write!(out, "\\u{:04X}", c as u32); + } + c => out.push(c), + } + } + out.push('"'); + out +} + +#[derive(Default)] +struct Builder { + stack: Vec, + anchors: HashMap, + root: Option, + error: Option, +} + +enum Frame { + Seq { items: Vec, anchor: usize }, + Map { pairs: Vec<(Value, Value)>, key: Option, anchor: usize }, +} + +impl MarkedEventReceiver for Builder { + fn on_event(&mut self, event: Event, _mark: Marker) { + match event { + Event::Scalar(value, style, anchor, tag) => self.push(scalar(value, style, tag.as_ref()), anchor), + Event::Alias(id) => match self.anchors.get(&id) { + Some(value) => self.push(value.clone(), 0), + None => { + self.error.get_or_insert(format!("unknown alias {id}")); + } + }, + Event::SequenceStart(anchor, _) => self.stack.push(Frame::Seq { items: Vec::new(), anchor }), + Event::MappingStart(anchor, _) => self.stack.push(Frame::Map { pairs: Vec::new(), key: None, anchor }), + Event::SequenceEnd => { + if let Some(Frame::Seq { items, anchor }) = self.stack.pop() { + self.push(Value::Seq(items), anchor); + } + } + Event::MappingEnd => { + if let Some(Frame::Map { pairs, anchor, .. }) = self.stack.pop() { + self.push(Value::Map(revive_map(pairs)), anchor); + } + } + _ => {} + } + } +} + +impl Builder { + fn push(&mut self, value: Value, anchor: usize) { + if anchor > 0 { + self.anchors.insert(anchor, value.clone()); + } + match self.stack.last_mut() { + None => { + self.root.get_or_insert(value); + } + Some(Frame::Seq { items, .. }) => items.push(value), + Some(Frame::Map { pairs, key, .. }) => match key.take() { + None => *key = Some(value), + Some(k) => pairs.push((k, value)), + }, + } + } +} + +/// Builds a mapping from its pairs in order, applying `<<` merge keys the way +/// Psych does: a merged mapping overrides the keys before it, and the keys +/// after it override it. +fn revive_map(pairs: Vec<(Value, Value)>) -> Map { + let mut map = Map::default(); + for (key, value) in pairs { + if key != Value::Str("<<".into()) { + map.insert(key, value); + continue; + } + match value { + Value::Map(merged) => { + for (k, v) in merged.0 { + map.insert(k, v); + } + } + Value::Seq(items) if items.iter().all(|item| matches!(item, Value::Map(_))) => { + let mut merged = Map::default(); + for item in items.into_iter().rev() { + if let Value::Map(m) = item { + for (k, v) in m.0 { + merged.insert(k, v); + } + } + } + for (k, v) in merged.0 { + map.insert(k, v); + } + } + other => map.insert(key, other), + } + } + map +} + +fn scalar(value: String, style: TScalarStyle, tag: Option<&Tag>) -> Value { + if let Some(tag) = tag.filter(|tag| tag.handle == "tag:yaml.org,2002:") { + return match tag.suffix.as_str() { + "str" => Value::Str(value), + "null" => Value::Null, + "bool" => resolve_plain(&value), + "int" => parse_int(&value).map_or(Value::Str(value), Value::Int), + "float" => match resolve_plain(&value) { + Value::Int(i) => Value::Float(i as f64), + other => other, + }, + _ => Value::Str(value), + }; + } + if style != TScalarStyle::Plain { + return Value::Str(value); + } + resolve_plain(&value) +} + +/// Psych's ScalarScanner#tokenize, for the types a config file can hold. +/// Dates and times, which Psych refuses to load safely, stay strings. +fn resolve_plain(s: &str) -> Value { + if s.is_empty() || s == "~" || s.eq_ignore_ascii_case("null") { + return Value::Null; + } + for word in ["yes", "true", "on"] { + if s.eq_ignore_ascii_case(word) { + return Value::Bool(true); + } + } + for word in ["no", "false", "off"] { + if s.eq_ignore_ascii_case(word) { + return Value::Bool(false); + } + } + let (sign, unsigned) = match s.strip_prefix('-') { + Some(rest) => (-1.0, rest), + None => (1.0, s.strip_prefix('+').unwrap_or(s)), + }; + if unsigned.eq_ignore_ascii_case(".inf") { + return Value::Float(sign * f64::INFINITY); + } + if s.eq_ignore_ascii_case(".nan") { + return Value::Float(f64::NAN); + } + if let Some(symbol) = s.strip_prefix(':').filter(|rest| !rest.is_empty()) { + let unquoted = [('"', '"'), ('\'', '\'')] + .iter() + .find_map(|(open, close)| symbol.strip_prefix(*open)?.strip_suffix(*close)); + return Value::Str(unquoted.unwrap_or(symbol).to_string()); + } + if let Some(float) = parse_float(s) { + return float; + } + if let Some(int) = parse_int(s) { + return Value::Int(int); + } + Value::Str(s.to_string()) +} + +/// Psych's FLOAT: `[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?` +fn parse_float(s: &str) -> Option { + let body = s.strip_prefix(['+', '-']).unwrap_or(s); + let (int_part, rest) = body.split_once('.')?; + if !int_part.is_empty() + && !(int_part.starts_with(|c: char| c.is_ascii_digit()) + && int_part.chars().all(|c| c.is_ascii_digit() || c == '_' || c == ',')) + { + return None; + } + let (fraction, exponent) = match rest.find(['e', 'E']) { + Some(i) => (&rest[..i], Some(&rest[i + 1..])), + None => (rest, None), + }; + if !fraction.chars().all(|c| c.is_ascii_digit()) { + return None; + } + if let Some(exponent) = exponent { + let digits = exponent.strip_prefix(['+', '-'])?; + if digits.is_empty() || !digits.chars().all(|c| c.is_ascii_digit()) { + return None; + } + } + if int_part.is_empty() && fraction.is_empty() && exponent.is_none() { + return Some(Value::Str(s.to_string())); // a lone "." or "-." + } + let cleaned: String = s.chars().filter(|&c| c != '_' && c != ',').collect(); + cleaned.parse::().ok().map(Value::Float) +} + +/// Psych's INTEGER_LEGACY: binary, octal (leading 0), decimal and hex, with `_` and `,` allowed. +fn parse_int(s: &str) -> Option { + let (negative, body) = match s.strip_prefix('-') { + Some(rest) => (true, rest), + None => (false, s.strip_prefix('+').unwrap_or(s)), + }; + let separator = |c: char| c == '_' || c == ','; + let digits_in = |text: &str, radix: u32| -> Option { + let first = text.trim_start_matches(separator); + if !first.starts_with(|c: char| c.is_digit(radix)) || !text.chars().all(|c| c.is_digit(radix) || separator(c)) { + return None; + } + Some(text.chars().filter(|&c| !separator(c)).collect()) + }; + + let (digits, radix) = if let Some(bin) = body.strip_prefix("0b") { + (digits_in(bin, 2)?, 2) + } else if let Some(hex) = body.strip_prefix("0x") { + (digits_in(hex, 16)?, 16) + } else if body == "0" { + ("0".to_string(), 10) + } else if let Some(octal) = body.strip_prefix('0') { + (digits_in(octal, 8)?, 8) + } else { + // Decimal: a separator must sit between two digits. + let chars: Vec = body.chars().collect(); + let valid = chars.first().is_some_and(|c| c.is_ascii_digit()) + && chars.iter().enumerate().all(|(i, &c)| { + c.is_ascii_digit() || (separator(c) && chars.get(i + 1).is_some_and(|n| n.is_ascii_digit())) + }); + if !valid { + return None; + } + (chars.into_iter().filter(|&c| !separator(c)).collect(), 10) + }; + let value = i64::from_str_radix(&digits, radix).ok()?; + Some(if negative { -value } else { value }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn one(yaml: &str) -> Value { + match load(&format!("v: {yaml}\n")).unwrap() { + Value::Map(map) => map.iter().next().unwrap().1.clone(), + other => panic!("{other:?}"), + } + } + + fn s(text: &str) -> Value { + Value::Str(text.into()) + } + + #[test] + fn plain_scalars_are_typed_like_psych() { + assert_eq!(one(""), Value::Null); + assert_eq!(one("~"), Value::Null); + assert_eq!(one("NULL"), Value::Null); + for truthy in ["true", "True", "yes", "YES", "on", "On"] { + assert_eq!(one(truthy), Value::Bool(true), "{truthy}"); + } + for falsy in ["false", "no", "No", "off", "OFF"] { + assert_eq!(one(falsy), Value::Bool(false), "{falsy}"); + } + assert_eq!(one("y"), s("y")); + assert_eq!(one("42"), Value::Int(42)); + assert_eq!(one("-42"), Value::Int(-42)); + assert_eq!(one("100_000"), Value::Int(100_000)); + assert_eq!(one("1,000"), Value::Int(1000)); + assert_eq!(one("0x1f"), Value::Int(31)); + assert_eq!(one("0b101"), Value::Int(5)); + assert_eq!(one("017"), Value::Int(15)); + assert_eq!(one("09"), s("09")); + assert_eq!(one("1_"), s("1_")); + assert_eq!(one("0.7"), Value::Float(0.7)); + assert_eq!(one(".5"), Value::Float(0.5)); + assert_eq!(one("1."), Value::Float(1.0)); + assert_eq!(one("1.5e+3"), Value::Float(1500.0)); + assert_eq!(one("1e3"), s("1e3")); + assert_eq!(one("-.inf"), Value::Float(f64::NEG_INFINITY)); + assert!(matches!(one(".NaN"), Value::Float(f) if f.is_nan())); + assert_eq!(one(":sym"), s("sym")); + assert_eq!(one("2024-01-01"), s("2024-01-01")); + assert_eq!(one("app/**/*.rb"), s("app/**/*.rb")); + } + + #[test] + fn quoted_and_tagged_scalars() { + assert_eq!(one("\"yes\""), s("yes")); + assert_eq!(one("'42'"), s("42")); + assert_eq!(one("!!str 42"), s("42")); + assert_eq!(one("!!float 1"), Value::Float(1.0)); + } + + #[test] + fn repeated_keys_keep_the_first_position_and_the_last_value() { + let Value::Map(map) = load("a: 1\nb: 2\na: 3\n").unwrap() else { panic!() }; + assert_eq!(map.0, vec![(s("a"), Value::Int(3)), (s("b"), Value::Int(2))]); + } + + #[test] + fn anchors_aliases_and_merge_keys() { + let yaml = "base: &base\n a: 1\n b: 2\nother: &other\n c: 3\none:\n b: 0\n <<: *base\n a: 9\nmany:\n <<: [*other, *base]\nlist: &l [x]\nsame: *l\n"; + let Value::Map(map) = load(yaml).unwrap() else { panic!() }; + let get = |key: &str| map.iter().find(|(k, _)| *k == s(key)).unwrap().1.clone(); + + let Value::Map(one) = get("one") else { panic!() }; + assert_eq!(one.0, vec![(s("b"), Value::Int(2)), (s("a"), Value::Int(9))]); + let Value::Map(many) = get("many") else { panic!() }; + assert_eq!(many.len(), 3); + assert_eq!(get("same"), Value::Seq(vec![s("x")])); + } + + #[test] + fn only_the_first_document_is_read() { + assert_eq!(load("a: 1\n---\nb: 2\n").unwrap(), Value::Map(Map(vec![(s("a"), Value::Int(1))]))); + assert_eq!(load("").unwrap(), Value::Null); + assert_eq!(load("# just a comment\n").unwrap(), Value::Null); + } + + #[test] + fn syntax_errors_are_reported() { + assert!(load("rules: [\n").is_err()); + } + + #[test] + fn to_s_and_inspect_follow_ruby() { + assert_eq!(Value::Null.to_s(), ""); + assert_eq!(Value::Float(1.0).to_s(), "1.0"); + assert_eq!(Value::Str("just a string".into()).inspect(), "\"just a string\""); + assert_eq!(Value::Seq(vec![Value::Int(1), Value::Null]).inspect(), "[1, nil]"); + assert_eq!(Value::Map(Map(vec![(s("a"), Value::Bool(true))])).inspect(), "{\"a\" => true}"); + } +} diff --git a/crates/lintus/tests/auth.rs b/crates/lintus/tests/auth.rs new file mode 100644 index 0000000..45a085e --- /dev/null +++ b/crates/lintus/tests/auth.rs @@ -0,0 +1,154 @@ +//! `lintus auth`, and how a run picks its API key. Unlike the conformance +//! suite, these have no Ruby counterpart. + +#[path = "conformance/support.rs"] +mod support; + +use support::{FakeJev, Project}; + +const CONFIG: &str = + "paths: ['**/*.rb']\nrules:\n documented:\n question: Is every class documented?\n offense_when: false\n"; + +fn project() -> Project { + let project = Project::new(); + project.write(".lintus.yml", CONFIG); + project.write("lib/a.rb", "class A; end\n"); + project +} + +fn credentials_file(project: &Project) -> std::path::PathBuf { + project.config_home().join("lintus/credentials.json") +} + +#[test] +fn login_saves_the_key_that_runs_then_use_and_logout_deletes_it() { + let project = project(); + let server = FakeJev::nouls(&[("documented", 0.9)]); + + let out = project.lintus(&["auth", "login"]).stdin("jev_saved_key_1234\n").run(); + assert_eq!(out.code, 0, "{out:?}"); + let file = credentials_file(&project); + assert_eq!(out.stdout, format!("Saved the API key (jev…1234) to {}\n", file.display())); + assert_eq!( + serde_json::from_str::(&std::fs::read_to_string(&file).unwrap()).unwrap(), + serde_json::json!({ "jev_api_key": "jev_saved_key_1234" }) + ); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + assert_eq!(std::fs::metadata(&file).unwrap().permissions().mode() & 0o777, 0o600); + assert_eq!(std::fs::metadata(file.parent().unwrap()).unwrap().permissions().mode() & 0o777, 0o700); + } + + let out = project.lintus(&["auth", "status"]).run(); + assert_eq!(out.code, 0); + assert_eq!(out.stdout, format!("Using the API key saved in {} (jev…1234).\n", file.display())); + + let out = project.lintus(&[]).api_without_key(&server).run(); + assert_eq!(out.code, 0, "{out:?}"); + assert_eq!(server.requests()[0].authorization.as_deref(), Some("Bearer jev_saved_key_1234")); + + let out = project.lintus(&["auth", "logout"]).run(); + assert_eq!(out.code, 0); + assert_eq!(out.stdout, format!("Deleted the API key saved in {}\n", file.display())); + assert!(!file.exists()); + + let out = project.lintus(&["auth", "logout"]).run(); + assert_eq!(out.stdout, "No API key was saved.\n"); + + let out = project.lintus(&["auth", "status"]).run(); + assert_eq!(out.code, 1); + assert_eq!(out.stderr, "No API key: run `lintus auth login`, or set JEV_API_KEY.\n"); +} + +#[test] +fn logging_in_again_replaces_the_key() { + let project = project(); + + project.lintus(&["auth", "login"]).stdin("first_key_000000").run(); + project.lintus(&["auth", "login"]).stdin("second_key_11111").run(); + + let saved = std::fs::read_to_string(credentials_file(&project)).unwrap(); + assert!(saved.contains("second_key_11111") && !saved.contains("first_key"), "{saved}"); +} + +#[test] +fn the_environment_and_the_flag_win_over_the_saved_key() { + let project = project(); + let server = FakeJev::nouls(&[("documented", 0.9)]); + project.lintus(&["auth", "login"]).stdin("jev_saved_key_1234").run(); + + let out = project.lintus(&["auth", "status"]).env("JEV_API_KEY", "jev_from_env_5678").run(); + assert_eq!( + out.stdout, + format!( + "Using the API key from JEV_API_KEY (jev…5678).\nThe key saved in {} is not used while JEV_API_KEY is set.\n", + credentials_file(&project).display() + ) + ); + + project.lintus(&[]).api_without_key(&server).env("JEV_API_KEY", "jev_from_env_5678").run(); + project.lintus(&["--api-key", "from-flag"]).api_without_key(&server).env("JEV_API_KEY", "env").run(); + let used: Vec<_> = server.requests().into_iter().map(|r| r.authorization.unwrap()).collect(); + assert_eq!(used, ["Bearer jev_from_env_5678", "Bearer from-flag"]); + + let out = project.lintus(&["auth", "login"]).stdin("another_key_9999").env("JEV_API_KEY", "x").run(); + assert_eq!(out.code, 0); + assert_eq!(out.stderr, "Note: JEV_API_KEY is set in this environment, and is used instead of the saved key.\n"); +} + +#[test] +fn an_empty_key_is_refused() { + let project = project(); + + let out = project.lintus(&["auth", "login"]).stdin("\n").run(); + + assert_eq!(out.code, 2); + assert!(out.stderr.starts_with("lintus: no API key given"), "{}", out.stderr); + assert!(!credentials_file(&project).exists()); +} + +#[test] +fn without_any_key_the_error_says_how_to_get_one() { + let project = project(); + let server = FakeJev::nouls(&[]); + + let out = project.lintus(&[]).api_without_key(&server).run(); + + assert_eq!(out.code, 2); + assert_eq!( + out.stderr, + "lintus: no API key: set JEV_API_KEY, pass --api-key, or run `lintus auth login` to save one\n" + ); +} + +#[test] +fn a_damaged_credentials_file_is_reported_when_the_key_is_needed() { + let project = project(); + let file = credentials_file(&project); + std::fs::create_dir_all(file.parent().unwrap()).unwrap(); + std::fs::write(&file, "not json").unwrap(); + let server = FakeJev::nouls(&[]); + + let out = project.lintus(&["--list"]).run(); + assert_eq!(out.code, 0, "--list needs no key: {out:?}"); + + let out = project.lintus(&[]).api_without_key(&server).run(); + assert_eq!(out.code, 2); + assert!( + out.stderr.starts_with(&format!("lintus: could not read the saved API key in {}: ", file.display())), + "{}", + out.stderr + ); + assert!(out.stderr.contains("lintus auth login"), "{}", out.stderr); +} + +#[test] +fn auth_without_a_subcommand_shows_its_help() { + let out = Project::new().lintus(&["auth"]).run(); + + assert_eq!(out.code, 2); + for command in ["login", "status", "logout"] { + assert!(out.stderr.contains(command), "{}", out.stderr); + } +} diff --git a/crates/lintus/tests/conformance/cli.rs b/crates/lintus/tests/conformance/cli.rs index 80a0777..16f8ae1 100644 --- a/crates/lintus/tests/conformance/cli.rs +++ b/crates/lintus/tests/conformance/cli.rs @@ -57,7 +57,10 @@ fn list_shows_files_and_rules_without_an_api_key() { let out = project.lintus(&["--list"]).run(); assert_eq!(out.code, 0, "{out:?}"); - assert_eq!(out.stdout, "app/jobs/a_job.rb: no_sleep, documented\nlib/b.rb: documented\n2 file(s) would be checked\n"); + assert_eq!( + out.stdout, + "app/jobs/a_job.rb: no_sleep, documented\nlib/b.rb: documented\n2 file(s) would be checked\n" + ); assert_eq!(out.stderr, ""); } @@ -99,7 +102,10 @@ fn a_full_run_reports_offenses_and_exits_1() { let out = project.lintus(&[]).api(&server).run(); assert_eq!(out.code, 1, "{out:?}"); - assert_eq!(out.stdout, "app/jobs/a_job.rb: [no_sleep] Jobs must not sleep. (noul 0.95)\n\n1 file inspected, 1 offense detected\n"); + assert_eq!( + out.stdout, + "app/jobs/a_job.rb: [no_sleep] Jobs must not sleep. (noul 0.95)\n\n1 file inspected, 1 offense detected\n" + ); assert_eq!(out.stderr, "Checking 1 file...\n"); } @@ -164,13 +170,8 @@ fn invalid_arguments_exit_2_with_a_message() { let project = Project::git_repo(); project.write(".lintus.yml", CONFIG); - for args in [ - &["--format", "xml"][..], - &["--jobs", "many"], - &["--no-such-flag"], - &["init", "extra"], - &["--config"], - ] { + for args in [&["--format", "xml"][..], &["--jobs", "many"], &["--no-such-flag"], &["init", "extra"], &["--config"]] + { let out = project.lintus(args).run(); assert_eq!(out.code, 2, "{args:?}: {out:?}"); assert!(out.stderr.starts_with("lintus: "), "{args:?}: {}", out.stderr); diff --git a/crates/lintus/tests/conformance/config.rs b/crates/lintus/tests/conformance/config.rs index 8acae26..ca7277e 100644 --- a/crates/lintus/tests/conformance/config.rs +++ b/crates/lintus/tests/conformance/config.rs @@ -47,7 +47,13 @@ fn a_missing_config_is_reported() { let out = project.lintus(&["--list"]).run(); assert_eq!(out.code, 2); - assert!(out.stderr.starts_with("lintus: No config file found: looked for .lintus.yml, lintus.yml, .lintus.yaml, lintus.yaml in "), "{}", out.stderr); + assert!( + out.stderr.starts_with( + "lintus: No config file found: looked for .lintus.yml, lintus.yml, .lintus.yaml, lintus.yaml in " + ), + "{}", + out.stderr + ); let out = project.lintus(&["--config", "nope.yml", "--list"]).run(); assert_eq!(out.code, 2); @@ -86,7 +92,10 @@ fn invalid_configs_are_rejected_with_a_precise_message() { "rules:\n No-Sleep:\n question: q\n", "invalid rule id \"No-Sleep\": use snake_case (letters, digits, underscores)", ), - ("rules:\n 1abc:\n question: q\n", "invalid rule id \"1abc\": use snake_case (letters, digits, underscores)"), + ( + "rules:\n 1abc:\n question: q\n", + "invalid rule id \"1abc\": use snake_case (letters, digits, underscores)", + ), ("rules:\n r:\n description: d\n", "rule r: `question` is required"), ("rules:\n r:\n question: ' '\n", "rule r: `question` is required"), ("rules:\n r: just a string\n", "rule r: expected a map of attributes, got \"just a string\""), @@ -96,8 +105,14 @@ fn invalid_configs_are_rejected_with_a_precise_message() { "rule r: unknown key(s) severity (expected question, description, criteria, threshold, paths, exclude, offense_when)", ), ("rules:\n r:\n question: q\n threshold: 2\n", "rule r: `threshold` must be a number between 0 and 1"), - ("rules:\n r:\n question: q\n threshold: high\n", "rule r: `threshold` must be a number between 0 and 1"), - ("rules:\n r:\n question: q\n criteria: x\n", "rule r: `criteria` must be a map with `true` and `false` keys"), + ( + "rules:\n r:\n question: q\n threshold: high\n", + "rule r: `threshold` must be a number between 0 and 1", + ), + ( + "rules:\n r:\n question: q\n criteria: x\n", + "rule r: `criteria` must be a map with `true` and `false` keys", + ), ("rules:\n r:\n question: q\n offense_when: maybe\n", "rule r: `offense_when` must be true or false"), ]; @@ -191,7 +206,10 @@ rules: assert_eq!(out.code, 0, "{out:?}"); let request = &server.requests()[0]; assert_eq!(request.question_ids(), ["zeta", "alpha"]); - assert_eq!(request.question("zeta"), &serde_json::json!({ "type": "noul", "instructions": "Is this the last letter?" })); + assert_eq!( + request.question("zeta"), + &serde_json::json!({ "type": "noul", "instructions": "Is this the last letter?" }) + ); assert_eq!( request.question("alpha"), &serde_json::json!({ diff --git a/crates/lintus/tests/conformance/files.rs b/crates/lintus/tests/conformance/files.rs index 21ae73b..331a7d5 100644 --- a/crates/lintus/tests/conformance/files.rs +++ b/crates/lintus/tests/conformance/files.rs @@ -73,7 +73,10 @@ fn diff_uses_the_merge_base_plus_uncommitted_and_untracked_changes() { project.git(&["add", "lib/c.rb"]); assert_eq!(listed(&project.lintus(&["--list", "--diff", main]).run()), ["lib/a.rb", "lib/c.rb", "lib/d.rb"]); - assert_eq!(listed(&project.lintus(&["--list", &format!("--diff={main}")]).run()), ["lib/a.rb", "lib/c.rb", "lib/d.rb"]); + assert_eq!( + listed(&project.lintus(&["--list", &format!("--diff={main}")]).run()), + ["lib/a.rb", "lib/c.rb", "lib/d.rb"] + ); assert_eq!(listed(&project.lintus(&["--list", "--diff"]).run()), ["lib/c.rb", "lib/d.rb"]); assert_eq!(listed(&project.lintus(&["--list", "-d"]).run()), ["lib/c.rb", "lib/d.rb"]); assert_eq!(listed(&project.lintus(&["--diff", "HEAD", "--list"]).run()), ["lib/c.rb", "lib/d.rb"]); @@ -165,7 +168,11 @@ fn explicit_paths_outside_the_root_are_rejected() { assert_eq!(out.code, 2); assert_eq!( out.stderr, - format!("lintus: {} is outside the config root {}\n", project.path("outer.rb").display(), project.path("inner").display()) + format!( + "lintus: {} is outside the config root {}\n", + project.path("outer.rb").display(), + project.path("inner").display() + ) ); } diff --git a/crates/lintus/tests/conformance/output.rs b/crates/lintus/tests/conformance/output.rs index ba4bf5b..48ee3cb 100644 --- a/crates/lintus/tests/conformance/output.rs +++ b/crates/lintus/tests/conformance/output.rs @@ -182,7 +182,10 @@ fn probabilities_are_rounded_like_ruby_floats() { (0.7, "0.7", "0.7"), ]; let project = Project::new(); - project.write(".lintus.yml", "paths: [\"*.rb\"]\nrules:\n r:\n question: q\n threshold: 0\n offense_when: true\n"); + project.write( + ".lintus.yml", + "paths: [\"*.rb\"]\nrules:\n r:\n question: q\n threshold: 0\n offense_when: true\n", + ); project.write("a.rb", "x\n"); for &(noul, text, json) in cases { diff --git a/crates/lintus/tests/conformance/runner.rs b/crates/lintus/tests/conformance/runner.rs index d85e37b..71ca1fb 100644 --- a/crates/lintus/tests/conformance/runner.rs +++ b/crates/lintus/tests/conformance/runner.rs @@ -27,7 +27,10 @@ fn one_request_per_file_with_every_rule_that_applies() { assert_eq!(job.body["model"], "jev-latest"); assert_eq!(job.state(), "File: app/jobs/a_job.rb\n\nsleep 1\n"); assert_eq!(job.question_ids(), ["no_sleep", "documented"]); - assert_eq!(job.question("no_sleep"), &serde_json::json!({ "type": "noul", "instructions": "Does this file call sleep?" })); + assert_eq!( + job.question("no_sleep"), + &serde_json::json!({ "type": "noul", "instructions": "Does this file call sleep?" }) + ); assert_eq!(job.body.as_object().unwrap().keys().collect::>(), ["model", "state", "questions"]); assert_eq!(requests[1].state(), "File: lib/b.rb\n\nclass B; end\n"); @@ -72,7 +75,10 @@ fn the_default_max_file_size_is_100000_bytes() { let out = project.lintus(&["-j", "1"]).api(&server).run(); - assert_eq!(out.stdout, "lib/big.rb: skipped, larger than max_file_size (100001 > 100000 bytes)\n\n1 file inspected, 0 offenses detected, 1 file skipped\n"); + assert_eq!( + out.stdout, + "lib/big.rb: skipped, larger than max_file_size (100001 > 100000 bytes)\n\n1 file inspected, 0 offenses detected, 1 file skipped\n" + ); } #[test] @@ -121,7 +127,9 @@ fn other_api_errors_fail_the_file_without_retrying() { assert_eq!(out.code, 2, "{status}: {out:?}"); assert_eq!( out.stdout, - format!("lib/b.rb: failed, Jev API error {status}: {body}\n\n0 files inspected, 0 offenses detected, 1 file failed\n") + format!( + "lib/b.rb: failed, Jev API error {status}: {body}\n\n0 files inspected, 0 offenses detected, 1 file failed\n" + ) ); assert_eq!(server.requests().len(), 1, "{status}"); } @@ -177,7 +185,8 @@ fn files_are_checked_concurrently_up_to_jobs() { for i in 0..6 { project.write(&format!("lib/f{i}.rb"), "class F; end\n"); } - let slow = || FakeJev::start(|request, _| Reply::nouls(request, &[("documented", 0.9)]).after(Duration::from_millis(150))); + let slow = + || FakeJev::start(|request, _| Reply::nouls(request, &[("documented", 0.9)]).after(Duration::from_millis(150))); let server = slow(); let out = project.lintus(&["--jobs", "3"]).api(&server).run(); diff --git a/crates/lintus/tests/conformance/support.rs b/crates/lintus/tests/conformance/support.rs index a5036e9..f6a713e 100644 --- a/crates/lintus/tests/conformance/support.rs +++ b/crates/lintus/tests/conformance/support.rs @@ -85,17 +85,8 @@ impl Project { } pub fn git(&self, args: &[&str]) -> String { - let output = isolate_git(process::Command::new("git")) - .args(args) - .current_dir(&self.root) - .output() - .unwrap(); - assert!( - output.status.success(), - "git {} failed: {}", - args.join(" "), - String::from_utf8_lossy(&output.stderr) - ); + let output = isolate_git(process::Command::new("git")).args(args).current_dir(&self.root).output().unwrap(); + assert!(output.status.success(), "git {} failed: {}", args.join(" "), String::from_utf8_lossy(&output.stderr)); String::from_utf8(output.stdout).unwrap() } diff --git a/crates/lintus/tests/fixtures/generate_glob_cases.rb b/crates/lintus/tests/fixtures/generate_glob_cases.rb new file mode 100644 index 0000000..d206287 --- /dev/null +++ b/crates/lintus/tests/fixtures/generate_glob_cases.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +# Writes glob_cases.tsv: whether the Ruby lintus matched each pattern against +# each path, one "patternpath0|1" line per pair. The Rust matcher is +# tested against it (src/glob.rs). Run from the repository root while the Ruby +# implementation is still there: +# +# ruby -Ilib crates/lintus/tests/fixtures/generate_glob_cases.rb +require "lintus" + +PATTERNS = [ + "*", "**", "**/*", "*.rb", "**/*.rb", "app/**/*.rb", "app/*.rb", "app/**", "app/**/", "vendor", "vendor/", + "./vendor", "./vendor/**", "vendor/*", "**/vendor/**", "**/*_test.rb", "test/**/*_test.rb", + "{app,lib}/**/*.rb", "**/*.{yml,yaml}", "*.{rb,rake}", "{a,b", "a}", "{a,{b,c}}/*.rb", "{,lib/}x.rb", + "lib/[a-c]*.rb", "lib/[!a-c]*.rb", "lib/[^a]*.rb", "lib/[a-]*.rb", "lib/[]]*.rb", "lib/[", "lib/[a", + "lib/?.rb", "lib/??.rb", "?", ".*", ".*/**", "**/.*", ".github/**/*.yml", "**/**/*.rb", "a/**/**/b.rb", + "a/**/b/**/c.rb", "**/b", "a*b*c.rb", "*a*", "lib/\\*.rb", "lib\\/x.rb", "lib/\\[.rb", "Gemfile", + "Gemfile*", "db/schema.rb", "db", "db/", "exe/*", "*/", "a/**/*/c.rb", "**/*/", "lib/x.rb/", "*.RB", + "é*.rb", "**/é.rb", "[é]*.rb", "a/*/c.rb", "**", "**/", "a/**", "*/*.rb", "**/x.rb", "{}x.rb", + "lib/{a,b}.rb", "lib/{a,}b.rb", "{app,{lib,test}}", "\\{a}.rb", "**/{a,b}/**/*.rb" +].freeze + +PATHS = [ + "a.rb", "app/a.rb", "app/jobs/a_job.rb", "app/jobs/deep/x.rb", "lib/a.rb", "lib/b.rb", "lib/ab.rb", + "lib/d.rb", "lib/-.rb", "lib/].rb", "lib/*.rb", "lib/x.rb", "lib/[.rb", "vendor/x.rb", + "vendor/gem/lib/x.rb", "vendored/x.rb", "vendor", "test/a_test.rb", "test/unit/b_test.rb", "a_test.rb", + ".github/workflows/ci.yml", ".hidden", ".lintus.yml", "config/a.yaml", "config/deep/b.yml", "Gemfile", + "Gemfile.lock", "sub/Gemfile", "db/schema.rb", "db/migrate/1.rb", "exe/lintus", "a/b.rb", "a/x/b.rb", + "a/x/y/b.rb", "a/b/c.rb", "a/x/b/y/c.rb", "abc.rb", "aXbYc.rb", "x.RB", "x.rb", "é.rb", "dir/é.rb", + "éa.rb", "x.rake", "b/x.rb", "a/c.rb", "a/b/x/c.rb", "{a}.rb", "b.rb", "a", "b", "lib/a/b.rb", + "test/lib/x.rb", ".a/.b/c.rb" +].freeze + +path = File.join(__dir__, "glob_cases.tsv") +File.open(path, "w") do |file| + PATTERNS.each do |pattern| + PATHS.each do |candidate| + file.puts [pattern, candidate, Lintus::Glob.match?(pattern, candidate) ? 1 : 0].join("\t") + end + end +end +puts "Wrote #{PATTERNS.size * PATHS.size} cases to #{path}" diff --git a/crates/lintus/tests/fixtures/generate_round_cases.rb b/crates/lintus/tests/fixtures/generate_round_cases.rb new file mode 100644 index 0000000..e6afaa0 --- /dev/null +++ b/crates/lintus/tests/fixtures/generate_round_cases.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Writes round_cases.tsv: how Ruby rounds and prints probabilities, as the +# text format (round(2)) and the json format (round(3)) do. One line per value: +# "". The Rust +# formatter is tested against it (src/format.rs). Run with any Ruby: +# +# ruby crates/lintus/tests/fixtures/generate_round_cases.rb + +def bits(float) = [float].pack("E").unpack1("Q<") +def next_float(float, direction) = [bits(float) + direction].pack("Q<").unpack1("E") + +values = [0.0, 1.0, 0.5, 0.125, 0.995, 0.9995, 0.0049, 0.005, 0.0005, 1e-5, 1e-20, 2.0, 12.345] +(0..1000).each do |k| + exact = k / 1000.0 + values.push(exact, (k + 0.5) / 1000.0, (k + 0.5) / 100.0) + values.push(next_float(exact, 1), next_float(exact, -1)) if k.positive? +end +random = Random.new(20_260_923) +5000.times { values << random.rand } + +path = File.join(__dir__, "round_cases.tsv") +File.open(path, "w") do |file| + values.uniq.each { |value| file.puts format("%016x\t%s\t%s", bits(value), value.round(2), value.round(3)) } +end +puts "Wrote #{values.uniq.size} cases to #{path}" diff --git a/crates/lintus/tests/fixtures/glob_cases.tsv b/crates/lintus/tests/fixtures/glob_cases.tsv new file mode 100644 index 0000000..50e86c8 --- /dev/null +++ b/crates/lintus/tests/fixtures/glob_cases.tsv @@ -0,0 +1,3942 @@ +* a.rb 1 +* app/a.rb 0 +* app/jobs/a_job.rb 0 +* app/jobs/deep/x.rb 0 +* lib/a.rb 0 +* lib/b.rb 0 +* lib/ab.rb 0 +* lib/d.rb 0 +* lib/-.rb 0 +* lib/].rb 0 +* lib/*.rb 0 +* lib/x.rb 0 +* lib/[.rb 0 +* vendor/x.rb 0 +* vendor/gem/lib/x.rb 0 +* vendored/x.rb 0 +* vendor 1 +* test/a_test.rb 0 +* test/unit/b_test.rb 0 +* a_test.rb 1 +* .github/workflows/ci.yml 0 +* .hidden 1 +* .lintus.yml 1 +* config/a.yaml 0 +* config/deep/b.yml 0 +* Gemfile 1 +* Gemfile.lock 1 +* sub/Gemfile 0 +* db/schema.rb 0 +* db/migrate/1.rb 0 +* exe/lintus 0 +* a/b.rb 0 +* a/x/b.rb 0 +* a/x/y/b.rb 0 +* a/b/c.rb 0 +* a/x/b/y/c.rb 0 +* abc.rb 1 +* aXbYc.rb 1 +* x.RB 1 +* x.rb 1 +* é.rb 1 +* dir/é.rb 0 +* éa.rb 1 +* x.rake 1 +* b/x.rb 0 +* a/c.rb 0 +* a/b/x/c.rb 0 +* {a}.rb 1 +* b.rb 1 +* a 1 +* b 1 +* lib/a/b.rb 0 +* test/lib/x.rb 0 +* .a/.b/c.rb 0 +** a.rb 1 +** app/a.rb 1 +** app/jobs/a_job.rb 1 +** app/jobs/deep/x.rb 1 +** lib/a.rb 1 +** lib/b.rb 1 +** lib/ab.rb 1 +** lib/d.rb 1 +** lib/-.rb 1 +** lib/].rb 1 +** lib/*.rb 1 +** lib/x.rb 1 +** lib/[.rb 1 +** vendor/x.rb 1 +** vendor/gem/lib/x.rb 1 +** vendored/x.rb 1 +** vendor 1 +** test/a_test.rb 1 +** test/unit/b_test.rb 1 +** a_test.rb 1 +** .github/workflows/ci.yml 1 +** .hidden 1 +** .lintus.yml 1 +** config/a.yaml 1 +** config/deep/b.yml 1 +** Gemfile 1 +** Gemfile.lock 1 +** sub/Gemfile 1 +** db/schema.rb 1 +** db/migrate/1.rb 1 +** exe/lintus 1 +** a/b.rb 1 +** a/x/b.rb 1 +** a/x/y/b.rb 1 +** a/b/c.rb 1 +** a/x/b/y/c.rb 1 +** abc.rb 1 +** aXbYc.rb 1 +** x.RB 1 +** x.rb 1 +** é.rb 1 +** dir/é.rb 1 +** éa.rb 1 +** x.rake 1 +** b/x.rb 1 +** a/c.rb 1 +** a/b/x/c.rb 1 +** {a}.rb 1 +** b.rb 1 +** a 1 +** b 1 +** lib/a/b.rb 1 +** test/lib/x.rb 1 +** .a/.b/c.rb 1 +**/* a.rb 1 +**/* app/a.rb 1 +**/* app/jobs/a_job.rb 1 +**/* app/jobs/deep/x.rb 1 +**/* lib/a.rb 1 +**/* lib/b.rb 1 +**/* lib/ab.rb 1 +**/* lib/d.rb 1 +**/* lib/-.rb 1 +**/* lib/].rb 1 +**/* lib/*.rb 1 +**/* lib/x.rb 1 +**/* lib/[.rb 1 +**/* vendor/x.rb 1 +**/* vendor/gem/lib/x.rb 1 +**/* vendored/x.rb 1 +**/* vendor 1 +**/* test/a_test.rb 1 +**/* test/unit/b_test.rb 1 +**/* a_test.rb 1 +**/* .github/workflows/ci.yml 1 +**/* .hidden 1 +**/* .lintus.yml 1 +**/* config/a.yaml 1 +**/* config/deep/b.yml 1 +**/* Gemfile 1 +**/* Gemfile.lock 1 +**/* sub/Gemfile 1 +**/* db/schema.rb 1 +**/* db/migrate/1.rb 1 +**/* exe/lintus 1 +**/* a/b.rb 1 +**/* a/x/b.rb 1 +**/* a/x/y/b.rb 1 +**/* a/b/c.rb 1 +**/* a/x/b/y/c.rb 1 +**/* abc.rb 1 +**/* aXbYc.rb 1 +**/* x.RB 1 +**/* x.rb 1 +**/* é.rb 1 +**/* dir/é.rb 1 +**/* éa.rb 1 +**/* x.rake 1 +**/* b/x.rb 1 +**/* a/c.rb 1 +**/* a/b/x/c.rb 1 +**/* {a}.rb 1 +**/* b.rb 1 +**/* a 1 +**/* b 1 +**/* lib/a/b.rb 1 +**/* test/lib/x.rb 1 +**/* .a/.b/c.rb 1 +*.rb a.rb 1 +*.rb app/a.rb 0 +*.rb app/jobs/a_job.rb 0 +*.rb app/jobs/deep/x.rb 0 +*.rb lib/a.rb 0 +*.rb lib/b.rb 0 +*.rb lib/ab.rb 0 +*.rb lib/d.rb 0 +*.rb lib/-.rb 0 +*.rb lib/].rb 0 +*.rb lib/*.rb 0 +*.rb lib/x.rb 0 +*.rb lib/[.rb 0 +*.rb vendor/x.rb 0 +*.rb vendor/gem/lib/x.rb 0 +*.rb vendored/x.rb 0 +*.rb vendor 0 +*.rb test/a_test.rb 0 +*.rb test/unit/b_test.rb 0 +*.rb a_test.rb 1 +*.rb .github/workflows/ci.yml 0 +*.rb .hidden 0 +*.rb .lintus.yml 0 +*.rb config/a.yaml 0 +*.rb config/deep/b.yml 0 +*.rb Gemfile 0 +*.rb Gemfile.lock 0 +*.rb sub/Gemfile 0 +*.rb db/schema.rb 0 +*.rb db/migrate/1.rb 0 +*.rb exe/lintus 0 +*.rb a/b.rb 0 +*.rb a/x/b.rb 0 +*.rb a/x/y/b.rb 0 +*.rb a/b/c.rb 0 +*.rb a/x/b/y/c.rb 0 +*.rb abc.rb 1 +*.rb aXbYc.rb 1 +*.rb x.RB 0 +*.rb x.rb 1 +*.rb é.rb 1 +*.rb dir/é.rb 0 +*.rb éa.rb 1 +*.rb x.rake 0 +*.rb b/x.rb 0 +*.rb a/c.rb 0 +*.rb a/b/x/c.rb 0 +*.rb {a}.rb 1 +*.rb b.rb 1 +*.rb a 0 +*.rb b 0 +*.rb lib/a/b.rb 0 +*.rb test/lib/x.rb 0 +*.rb .a/.b/c.rb 0 +**/*.rb a.rb 1 +**/*.rb app/a.rb 1 +**/*.rb app/jobs/a_job.rb 1 +**/*.rb app/jobs/deep/x.rb 1 +**/*.rb lib/a.rb 1 +**/*.rb lib/b.rb 1 +**/*.rb lib/ab.rb 1 +**/*.rb lib/d.rb 1 +**/*.rb lib/-.rb 1 +**/*.rb lib/].rb 1 +**/*.rb lib/*.rb 1 +**/*.rb lib/x.rb 1 +**/*.rb lib/[.rb 1 +**/*.rb vendor/x.rb 1 +**/*.rb vendor/gem/lib/x.rb 1 +**/*.rb vendored/x.rb 1 +**/*.rb vendor 0 +**/*.rb test/a_test.rb 1 +**/*.rb test/unit/b_test.rb 1 +**/*.rb a_test.rb 1 +**/*.rb .github/workflows/ci.yml 0 +**/*.rb .hidden 0 +**/*.rb .lintus.yml 0 +**/*.rb config/a.yaml 0 +**/*.rb config/deep/b.yml 0 +**/*.rb Gemfile 0 +**/*.rb Gemfile.lock 0 +**/*.rb sub/Gemfile 0 +**/*.rb db/schema.rb 1 +**/*.rb db/migrate/1.rb 1 +**/*.rb exe/lintus 0 +**/*.rb a/b.rb 1 +**/*.rb a/x/b.rb 1 +**/*.rb a/x/y/b.rb 1 +**/*.rb a/b/c.rb 1 +**/*.rb a/x/b/y/c.rb 1 +**/*.rb abc.rb 1 +**/*.rb aXbYc.rb 1 +**/*.rb x.RB 0 +**/*.rb x.rb 1 +**/*.rb é.rb 1 +**/*.rb dir/é.rb 1 +**/*.rb éa.rb 1 +**/*.rb x.rake 0 +**/*.rb b/x.rb 1 +**/*.rb a/c.rb 1 +**/*.rb a/b/x/c.rb 1 +**/*.rb {a}.rb 1 +**/*.rb b.rb 1 +**/*.rb a 0 +**/*.rb b 0 +**/*.rb lib/a/b.rb 1 +**/*.rb test/lib/x.rb 1 +**/*.rb .a/.b/c.rb 1 +app/**/*.rb a.rb 0 +app/**/*.rb app/a.rb 1 +app/**/*.rb app/jobs/a_job.rb 1 +app/**/*.rb app/jobs/deep/x.rb 1 +app/**/*.rb lib/a.rb 0 +app/**/*.rb lib/b.rb 0 +app/**/*.rb lib/ab.rb 0 +app/**/*.rb lib/d.rb 0 +app/**/*.rb lib/-.rb 0 +app/**/*.rb lib/].rb 0 +app/**/*.rb lib/*.rb 0 +app/**/*.rb lib/x.rb 0 +app/**/*.rb lib/[.rb 0 +app/**/*.rb vendor/x.rb 0 +app/**/*.rb vendor/gem/lib/x.rb 0 +app/**/*.rb vendored/x.rb 0 +app/**/*.rb vendor 0 +app/**/*.rb test/a_test.rb 0 +app/**/*.rb test/unit/b_test.rb 0 +app/**/*.rb a_test.rb 0 +app/**/*.rb .github/workflows/ci.yml 0 +app/**/*.rb .hidden 0 +app/**/*.rb .lintus.yml 0 +app/**/*.rb config/a.yaml 0 +app/**/*.rb config/deep/b.yml 0 +app/**/*.rb Gemfile 0 +app/**/*.rb Gemfile.lock 0 +app/**/*.rb sub/Gemfile 0 +app/**/*.rb db/schema.rb 0 +app/**/*.rb db/migrate/1.rb 0 +app/**/*.rb exe/lintus 0 +app/**/*.rb a/b.rb 0 +app/**/*.rb a/x/b.rb 0 +app/**/*.rb a/x/y/b.rb 0 +app/**/*.rb a/b/c.rb 0 +app/**/*.rb a/x/b/y/c.rb 0 +app/**/*.rb abc.rb 0 +app/**/*.rb aXbYc.rb 0 +app/**/*.rb x.RB 0 +app/**/*.rb x.rb 0 +app/**/*.rb é.rb 0 +app/**/*.rb dir/é.rb 0 +app/**/*.rb éa.rb 0 +app/**/*.rb x.rake 0 +app/**/*.rb b/x.rb 0 +app/**/*.rb a/c.rb 0 +app/**/*.rb a/b/x/c.rb 0 +app/**/*.rb {a}.rb 0 +app/**/*.rb b.rb 0 +app/**/*.rb a 0 +app/**/*.rb b 0 +app/**/*.rb lib/a/b.rb 0 +app/**/*.rb test/lib/x.rb 0 +app/**/*.rb .a/.b/c.rb 0 +app/*.rb a.rb 0 +app/*.rb app/a.rb 1 +app/*.rb app/jobs/a_job.rb 0 +app/*.rb app/jobs/deep/x.rb 0 +app/*.rb lib/a.rb 0 +app/*.rb lib/b.rb 0 +app/*.rb lib/ab.rb 0 +app/*.rb lib/d.rb 0 +app/*.rb lib/-.rb 0 +app/*.rb lib/].rb 0 +app/*.rb lib/*.rb 0 +app/*.rb lib/x.rb 0 +app/*.rb lib/[.rb 0 +app/*.rb vendor/x.rb 0 +app/*.rb vendor/gem/lib/x.rb 0 +app/*.rb vendored/x.rb 0 +app/*.rb vendor 0 +app/*.rb test/a_test.rb 0 +app/*.rb test/unit/b_test.rb 0 +app/*.rb a_test.rb 0 +app/*.rb .github/workflows/ci.yml 0 +app/*.rb .hidden 0 +app/*.rb .lintus.yml 0 +app/*.rb config/a.yaml 0 +app/*.rb config/deep/b.yml 0 +app/*.rb Gemfile 0 +app/*.rb Gemfile.lock 0 +app/*.rb sub/Gemfile 0 +app/*.rb db/schema.rb 0 +app/*.rb db/migrate/1.rb 0 +app/*.rb exe/lintus 0 +app/*.rb a/b.rb 0 +app/*.rb a/x/b.rb 0 +app/*.rb a/x/y/b.rb 0 +app/*.rb a/b/c.rb 0 +app/*.rb a/x/b/y/c.rb 0 +app/*.rb abc.rb 0 +app/*.rb aXbYc.rb 0 +app/*.rb x.RB 0 +app/*.rb x.rb 0 +app/*.rb é.rb 0 +app/*.rb dir/é.rb 0 +app/*.rb éa.rb 0 +app/*.rb x.rake 0 +app/*.rb b/x.rb 0 +app/*.rb a/c.rb 0 +app/*.rb a/b/x/c.rb 0 +app/*.rb {a}.rb 0 +app/*.rb b.rb 0 +app/*.rb a 0 +app/*.rb b 0 +app/*.rb lib/a/b.rb 0 +app/*.rb test/lib/x.rb 0 +app/*.rb .a/.b/c.rb 0 +app/** a.rb 0 +app/** app/a.rb 1 +app/** app/jobs/a_job.rb 1 +app/** app/jobs/deep/x.rb 1 +app/** lib/a.rb 0 +app/** lib/b.rb 0 +app/** lib/ab.rb 0 +app/** lib/d.rb 0 +app/** lib/-.rb 0 +app/** lib/].rb 0 +app/** lib/*.rb 0 +app/** lib/x.rb 0 +app/** lib/[.rb 0 +app/** vendor/x.rb 0 +app/** vendor/gem/lib/x.rb 0 +app/** vendored/x.rb 0 +app/** vendor 0 +app/** test/a_test.rb 0 +app/** test/unit/b_test.rb 0 +app/** a_test.rb 0 +app/** .github/workflows/ci.yml 0 +app/** .hidden 0 +app/** .lintus.yml 0 +app/** config/a.yaml 0 +app/** config/deep/b.yml 0 +app/** Gemfile 0 +app/** Gemfile.lock 0 +app/** sub/Gemfile 0 +app/** db/schema.rb 0 +app/** db/migrate/1.rb 0 +app/** exe/lintus 0 +app/** a/b.rb 0 +app/** a/x/b.rb 0 +app/** a/x/y/b.rb 0 +app/** a/b/c.rb 0 +app/** a/x/b/y/c.rb 0 +app/** abc.rb 0 +app/** aXbYc.rb 0 +app/** x.RB 0 +app/** x.rb 0 +app/** é.rb 0 +app/** dir/é.rb 0 +app/** éa.rb 0 +app/** x.rake 0 +app/** b/x.rb 0 +app/** a/c.rb 0 +app/** a/b/x/c.rb 0 +app/** {a}.rb 0 +app/** b.rb 0 +app/** a 0 +app/** b 0 +app/** lib/a/b.rb 0 +app/** test/lib/x.rb 0 +app/** .a/.b/c.rb 0 +app/**/ a.rb 0 +app/**/ app/a.rb 1 +app/**/ app/jobs/a_job.rb 1 +app/**/ app/jobs/deep/x.rb 1 +app/**/ lib/a.rb 0 +app/**/ lib/b.rb 0 +app/**/ lib/ab.rb 0 +app/**/ lib/d.rb 0 +app/**/ lib/-.rb 0 +app/**/ lib/].rb 0 +app/**/ lib/*.rb 0 +app/**/ lib/x.rb 0 +app/**/ lib/[.rb 0 +app/**/ vendor/x.rb 0 +app/**/ vendor/gem/lib/x.rb 0 +app/**/ vendored/x.rb 0 +app/**/ vendor 0 +app/**/ test/a_test.rb 0 +app/**/ test/unit/b_test.rb 0 +app/**/ a_test.rb 0 +app/**/ .github/workflows/ci.yml 0 +app/**/ .hidden 0 +app/**/ .lintus.yml 0 +app/**/ config/a.yaml 0 +app/**/ config/deep/b.yml 0 +app/**/ Gemfile 0 +app/**/ Gemfile.lock 0 +app/**/ sub/Gemfile 0 +app/**/ db/schema.rb 0 +app/**/ db/migrate/1.rb 0 +app/**/ exe/lintus 0 +app/**/ a/b.rb 0 +app/**/ a/x/b.rb 0 +app/**/ a/x/y/b.rb 0 +app/**/ a/b/c.rb 0 +app/**/ a/x/b/y/c.rb 0 +app/**/ abc.rb 0 +app/**/ aXbYc.rb 0 +app/**/ x.RB 0 +app/**/ x.rb 0 +app/**/ é.rb 0 +app/**/ dir/é.rb 0 +app/**/ éa.rb 0 +app/**/ x.rake 0 +app/**/ b/x.rb 0 +app/**/ a/c.rb 0 +app/**/ a/b/x/c.rb 0 +app/**/ {a}.rb 0 +app/**/ b.rb 0 +app/**/ a 0 +app/**/ b 0 +app/**/ lib/a/b.rb 0 +app/**/ test/lib/x.rb 0 +app/**/ .a/.b/c.rb 0 +vendor a.rb 0 +vendor app/a.rb 0 +vendor app/jobs/a_job.rb 0 +vendor app/jobs/deep/x.rb 0 +vendor lib/a.rb 0 +vendor lib/b.rb 0 +vendor lib/ab.rb 0 +vendor lib/d.rb 0 +vendor lib/-.rb 0 +vendor lib/].rb 0 +vendor lib/*.rb 0 +vendor lib/x.rb 0 +vendor lib/[.rb 0 +vendor vendor/x.rb 1 +vendor vendor/gem/lib/x.rb 1 +vendor vendored/x.rb 0 +vendor vendor 1 +vendor test/a_test.rb 0 +vendor test/unit/b_test.rb 0 +vendor a_test.rb 0 +vendor .github/workflows/ci.yml 0 +vendor .hidden 0 +vendor .lintus.yml 0 +vendor config/a.yaml 0 +vendor config/deep/b.yml 0 +vendor Gemfile 0 +vendor Gemfile.lock 0 +vendor sub/Gemfile 0 +vendor db/schema.rb 0 +vendor db/migrate/1.rb 0 +vendor exe/lintus 0 +vendor a/b.rb 0 +vendor a/x/b.rb 0 +vendor a/x/y/b.rb 0 +vendor a/b/c.rb 0 +vendor a/x/b/y/c.rb 0 +vendor abc.rb 0 +vendor aXbYc.rb 0 +vendor x.RB 0 +vendor x.rb 0 +vendor é.rb 0 +vendor dir/é.rb 0 +vendor éa.rb 0 +vendor x.rake 0 +vendor b/x.rb 0 +vendor a/c.rb 0 +vendor a/b/x/c.rb 0 +vendor {a}.rb 0 +vendor b.rb 0 +vendor a 0 +vendor b 0 +vendor lib/a/b.rb 0 +vendor test/lib/x.rb 0 +vendor .a/.b/c.rb 0 +vendor/ a.rb 0 +vendor/ app/a.rb 0 +vendor/ app/jobs/a_job.rb 0 +vendor/ app/jobs/deep/x.rb 0 +vendor/ lib/a.rb 0 +vendor/ lib/b.rb 0 +vendor/ lib/ab.rb 0 +vendor/ lib/d.rb 0 +vendor/ lib/-.rb 0 +vendor/ lib/].rb 0 +vendor/ lib/*.rb 0 +vendor/ lib/x.rb 0 +vendor/ lib/[.rb 0 +vendor/ vendor/x.rb 1 +vendor/ vendor/gem/lib/x.rb 1 +vendor/ vendored/x.rb 0 +vendor/ vendor 1 +vendor/ test/a_test.rb 0 +vendor/ test/unit/b_test.rb 0 +vendor/ a_test.rb 0 +vendor/ .github/workflows/ci.yml 0 +vendor/ .hidden 0 +vendor/ .lintus.yml 0 +vendor/ config/a.yaml 0 +vendor/ config/deep/b.yml 0 +vendor/ Gemfile 0 +vendor/ Gemfile.lock 0 +vendor/ sub/Gemfile 0 +vendor/ db/schema.rb 0 +vendor/ db/migrate/1.rb 0 +vendor/ exe/lintus 0 +vendor/ a/b.rb 0 +vendor/ a/x/b.rb 0 +vendor/ a/x/y/b.rb 0 +vendor/ a/b/c.rb 0 +vendor/ a/x/b/y/c.rb 0 +vendor/ abc.rb 0 +vendor/ aXbYc.rb 0 +vendor/ x.RB 0 +vendor/ x.rb 0 +vendor/ é.rb 0 +vendor/ dir/é.rb 0 +vendor/ éa.rb 0 +vendor/ x.rake 0 +vendor/ b/x.rb 0 +vendor/ a/c.rb 0 +vendor/ a/b/x/c.rb 0 +vendor/ {a}.rb 0 +vendor/ b.rb 0 +vendor/ a 0 +vendor/ b 0 +vendor/ lib/a/b.rb 0 +vendor/ test/lib/x.rb 0 +vendor/ .a/.b/c.rb 0 +./vendor a.rb 0 +./vendor app/a.rb 0 +./vendor app/jobs/a_job.rb 0 +./vendor app/jobs/deep/x.rb 0 +./vendor lib/a.rb 0 +./vendor lib/b.rb 0 +./vendor lib/ab.rb 0 +./vendor lib/d.rb 0 +./vendor lib/-.rb 0 +./vendor lib/].rb 0 +./vendor lib/*.rb 0 +./vendor lib/x.rb 0 +./vendor lib/[.rb 0 +./vendor vendor/x.rb 1 +./vendor vendor/gem/lib/x.rb 1 +./vendor vendored/x.rb 0 +./vendor vendor 1 +./vendor test/a_test.rb 0 +./vendor test/unit/b_test.rb 0 +./vendor a_test.rb 0 +./vendor .github/workflows/ci.yml 0 +./vendor .hidden 0 +./vendor .lintus.yml 0 +./vendor config/a.yaml 0 +./vendor config/deep/b.yml 0 +./vendor Gemfile 0 +./vendor Gemfile.lock 0 +./vendor sub/Gemfile 0 +./vendor db/schema.rb 0 +./vendor db/migrate/1.rb 0 +./vendor exe/lintus 0 +./vendor a/b.rb 0 +./vendor a/x/b.rb 0 +./vendor a/x/y/b.rb 0 +./vendor a/b/c.rb 0 +./vendor a/x/b/y/c.rb 0 +./vendor abc.rb 0 +./vendor aXbYc.rb 0 +./vendor x.RB 0 +./vendor x.rb 0 +./vendor é.rb 0 +./vendor dir/é.rb 0 +./vendor éa.rb 0 +./vendor x.rake 0 +./vendor b/x.rb 0 +./vendor a/c.rb 0 +./vendor a/b/x/c.rb 0 +./vendor {a}.rb 0 +./vendor b.rb 0 +./vendor a 0 +./vendor b 0 +./vendor lib/a/b.rb 0 +./vendor test/lib/x.rb 0 +./vendor .a/.b/c.rb 0 +./vendor/** a.rb 0 +./vendor/** app/a.rb 0 +./vendor/** app/jobs/a_job.rb 0 +./vendor/** app/jobs/deep/x.rb 0 +./vendor/** lib/a.rb 0 +./vendor/** lib/b.rb 0 +./vendor/** lib/ab.rb 0 +./vendor/** lib/d.rb 0 +./vendor/** lib/-.rb 0 +./vendor/** lib/].rb 0 +./vendor/** lib/*.rb 0 +./vendor/** lib/x.rb 0 +./vendor/** lib/[.rb 0 +./vendor/** vendor/x.rb 1 +./vendor/** vendor/gem/lib/x.rb 1 +./vendor/** vendored/x.rb 0 +./vendor/** vendor 0 +./vendor/** test/a_test.rb 0 +./vendor/** test/unit/b_test.rb 0 +./vendor/** a_test.rb 0 +./vendor/** .github/workflows/ci.yml 0 +./vendor/** .hidden 0 +./vendor/** .lintus.yml 0 +./vendor/** config/a.yaml 0 +./vendor/** config/deep/b.yml 0 +./vendor/** Gemfile 0 +./vendor/** Gemfile.lock 0 +./vendor/** sub/Gemfile 0 +./vendor/** db/schema.rb 0 +./vendor/** db/migrate/1.rb 0 +./vendor/** exe/lintus 0 +./vendor/** a/b.rb 0 +./vendor/** a/x/b.rb 0 +./vendor/** a/x/y/b.rb 0 +./vendor/** a/b/c.rb 0 +./vendor/** a/x/b/y/c.rb 0 +./vendor/** abc.rb 0 +./vendor/** aXbYc.rb 0 +./vendor/** x.RB 0 +./vendor/** x.rb 0 +./vendor/** é.rb 0 +./vendor/** dir/é.rb 0 +./vendor/** éa.rb 0 +./vendor/** x.rake 0 +./vendor/** b/x.rb 0 +./vendor/** a/c.rb 0 +./vendor/** a/b/x/c.rb 0 +./vendor/** {a}.rb 0 +./vendor/** b.rb 0 +./vendor/** a 0 +./vendor/** b 0 +./vendor/** lib/a/b.rb 0 +./vendor/** test/lib/x.rb 0 +./vendor/** .a/.b/c.rb 0 +vendor/* a.rb 0 +vendor/* app/a.rb 0 +vendor/* app/jobs/a_job.rb 0 +vendor/* app/jobs/deep/x.rb 0 +vendor/* lib/a.rb 0 +vendor/* lib/b.rb 0 +vendor/* lib/ab.rb 0 +vendor/* lib/d.rb 0 +vendor/* lib/-.rb 0 +vendor/* lib/].rb 0 +vendor/* lib/*.rb 0 +vendor/* lib/x.rb 0 +vendor/* lib/[.rb 0 +vendor/* vendor/x.rb 1 +vendor/* vendor/gem/lib/x.rb 0 +vendor/* vendored/x.rb 0 +vendor/* vendor 0 +vendor/* test/a_test.rb 0 +vendor/* test/unit/b_test.rb 0 +vendor/* a_test.rb 0 +vendor/* .github/workflows/ci.yml 0 +vendor/* .hidden 0 +vendor/* .lintus.yml 0 +vendor/* config/a.yaml 0 +vendor/* config/deep/b.yml 0 +vendor/* Gemfile 0 +vendor/* Gemfile.lock 0 +vendor/* sub/Gemfile 0 +vendor/* db/schema.rb 0 +vendor/* db/migrate/1.rb 0 +vendor/* exe/lintus 0 +vendor/* a/b.rb 0 +vendor/* a/x/b.rb 0 +vendor/* a/x/y/b.rb 0 +vendor/* a/b/c.rb 0 +vendor/* a/x/b/y/c.rb 0 +vendor/* abc.rb 0 +vendor/* aXbYc.rb 0 +vendor/* x.RB 0 +vendor/* x.rb 0 +vendor/* é.rb 0 +vendor/* dir/é.rb 0 +vendor/* éa.rb 0 +vendor/* x.rake 0 +vendor/* b/x.rb 0 +vendor/* a/c.rb 0 +vendor/* a/b/x/c.rb 0 +vendor/* {a}.rb 0 +vendor/* b.rb 0 +vendor/* a 0 +vendor/* b 0 +vendor/* lib/a/b.rb 0 +vendor/* test/lib/x.rb 0 +vendor/* .a/.b/c.rb 0 +**/vendor/** a.rb 0 +**/vendor/** app/a.rb 0 +**/vendor/** app/jobs/a_job.rb 0 +**/vendor/** app/jobs/deep/x.rb 0 +**/vendor/** lib/a.rb 0 +**/vendor/** lib/b.rb 0 +**/vendor/** lib/ab.rb 0 +**/vendor/** lib/d.rb 0 +**/vendor/** lib/-.rb 0 +**/vendor/** lib/].rb 0 +**/vendor/** lib/*.rb 0 +**/vendor/** lib/x.rb 0 +**/vendor/** lib/[.rb 0 +**/vendor/** vendor/x.rb 1 +**/vendor/** vendor/gem/lib/x.rb 1 +**/vendor/** vendored/x.rb 0 +**/vendor/** vendor 0 +**/vendor/** test/a_test.rb 0 +**/vendor/** test/unit/b_test.rb 0 +**/vendor/** a_test.rb 0 +**/vendor/** .github/workflows/ci.yml 0 +**/vendor/** .hidden 0 +**/vendor/** .lintus.yml 0 +**/vendor/** config/a.yaml 0 +**/vendor/** config/deep/b.yml 0 +**/vendor/** Gemfile 0 +**/vendor/** Gemfile.lock 0 +**/vendor/** sub/Gemfile 0 +**/vendor/** db/schema.rb 0 +**/vendor/** db/migrate/1.rb 0 +**/vendor/** exe/lintus 0 +**/vendor/** a/b.rb 0 +**/vendor/** a/x/b.rb 0 +**/vendor/** a/x/y/b.rb 0 +**/vendor/** a/b/c.rb 0 +**/vendor/** a/x/b/y/c.rb 0 +**/vendor/** abc.rb 0 +**/vendor/** aXbYc.rb 0 +**/vendor/** x.RB 0 +**/vendor/** x.rb 0 +**/vendor/** é.rb 0 +**/vendor/** dir/é.rb 0 +**/vendor/** éa.rb 0 +**/vendor/** x.rake 0 +**/vendor/** b/x.rb 0 +**/vendor/** a/c.rb 0 +**/vendor/** a/b/x/c.rb 0 +**/vendor/** {a}.rb 0 +**/vendor/** b.rb 0 +**/vendor/** a 0 +**/vendor/** b 0 +**/vendor/** lib/a/b.rb 0 +**/vendor/** test/lib/x.rb 0 +**/vendor/** .a/.b/c.rb 0 +**/*_test.rb a.rb 0 +**/*_test.rb app/a.rb 0 +**/*_test.rb app/jobs/a_job.rb 0 +**/*_test.rb app/jobs/deep/x.rb 0 +**/*_test.rb lib/a.rb 0 +**/*_test.rb lib/b.rb 0 +**/*_test.rb lib/ab.rb 0 +**/*_test.rb lib/d.rb 0 +**/*_test.rb lib/-.rb 0 +**/*_test.rb lib/].rb 0 +**/*_test.rb lib/*.rb 0 +**/*_test.rb lib/x.rb 0 +**/*_test.rb lib/[.rb 0 +**/*_test.rb vendor/x.rb 0 +**/*_test.rb vendor/gem/lib/x.rb 0 +**/*_test.rb vendored/x.rb 0 +**/*_test.rb vendor 0 +**/*_test.rb test/a_test.rb 1 +**/*_test.rb test/unit/b_test.rb 1 +**/*_test.rb a_test.rb 1 +**/*_test.rb .github/workflows/ci.yml 0 +**/*_test.rb .hidden 0 +**/*_test.rb .lintus.yml 0 +**/*_test.rb config/a.yaml 0 +**/*_test.rb config/deep/b.yml 0 +**/*_test.rb Gemfile 0 +**/*_test.rb Gemfile.lock 0 +**/*_test.rb sub/Gemfile 0 +**/*_test.rb db/schema.rb 0 +**/*_test.rb db/migrate/1.rb 0 +**/*_test.rb exe/lintus 0 +**/*_test.rb a/b.rb 0 +**/*_test.rb a/x/b.rb 0 +**/*_test.rb a/x/y/b.rb 0 +**/*_test.rb a/b/c.rb 0 +**/*_test.rb a/x/b/y/c.rb 0 +**/*_test.rb abc.rb 0 +**/*_test.rb aXbYc.rb 0 +**/*_test.rb x.RB 0 +**/*_test.rb x.rb 0 +**/*_test.rb é.rb 0 +**/*_test.rb dir/é.rb 0 +**/*_test.rb éa.rb 0 +**/*_test.rb x.rake 0 +**/*_test.rb b/x.rb 0 +**/*_test.rb a/c.rb 0 +**/*_test.rb a/b/x/c.rb 0 +**/*_test.rb {a}.rb 0 +**/*_test.rb b.rb 0 +**/*_test.rb a 0 +**/*_test.rb b 0 +**/*_test.rb lib/a/b.rb 0 +**/*_test.rb test/lib/x.rb 0 +**/*_test.rb .a/.b/c.rb 0 +test/**/*_test.rb a.rb 0 +test/**/*_test.rb app/a.rb 0 +test/**/*_test.rb app/jobs/a_job.rb 0 +test/**/*_test.rb app/jobs/deep/x.rb 0 +test/**/*_test.rb lib/a.rb 0 +test/**/*_test.rb lib/b.rb 0 +test/**/*_test.rb lib/ab.rb 0 +test/**/*_test.rb lib/d.rb 0 +test/**/*_test.rb lib/-.rb 0 +test/**/*_test.rb lib/].rb 0 +test/**/*_test.rb lib/*.rb 0 +test/**/*_test.rb lib/x.rb 0 +test/**/*_test.rb lib/[.rb 0 +test/**/*_test.rb vendor/x.rb 0 +test/**/*_test.rb vendor/gem/lib/x.rb 0 +test/**/*_test.rb vendored/x.rb 0 +test/**/*_test.rb vendor 0 +test/**/*_test.rb test/a_test.rb 1 +test/**/*_test.rb test/unit/b_test.rb 1 +test/**/*_test.rb a_test.rb 0 +test/**/*_test.rb .github/workflows/ci.yml 0 +test/**/*_test.rb .hidden 0 +test/**/*_test.rb .lintus.yml 0 +test/**/*_test.rb config/a.yaml 0 +test/**/*_test.rb config/deep/b.yml 0 +test/**/*_test.rb Gemfile 0 +test/**/*_test.rb Gemfile.lock 0 +test/**/*_test.rb sub/Gemfile 0 +test/**/*_test.rb db/schema.rb 0 +test/**/*_test.rb db/migrate/1.rb 0 +test/**/*_test.rb exe/lintus 0 +test/**/*_test.rb a/b.rb 0 +test/**/*_test.rb a/x/b.rb 0 +test/**/*_test.rb a/x/y/b.rb 0 +test/**/*_test.rb a/b/c.rb 0 +test/**/*_test.rb a/x/b/y/c.rb 0 +test/**/*_test.rb abc.rb 0 +test/**/*_test.rb aXbYc.rb 0 +test/**/*_test.rb x.RB 0 +test/**/*_test.rb x.rb 0 +test/**/*_test.rb é.rb 0 +test/**/*_test.rb dir/é.rb 0 +test/**/*_test.rb éa.rb 0 +test/**/*_test.rb x.rake 0 +test/**/*_test.rb b/x.rb 0 +test/**/*_test.rb a/c.rb 0 +test/**/*_test.rb a/b/x/c.rb 0 +test/**/*_test.rb {a}.rb 0 +test/**/*_test.rb b.rb 0 +test/**/*_test.rb a 0 +test/**/*_test.rb b 0 +test/**/*_test.rb lib/a/b.rb 0 +test/**/*_test.rb test/lib/x.rb 0 +test/**/*_test.rb .a/.b/c.rb 0 +{app,lib}/**/*.rb a.rb 0 +{app,lib}/**/*.rb app/a.rb 1 +{app,lib}/**/*.rb app/jobs/a_job.rb 1 +{app,lib}/**/*.rb app/jobs/deep/x.rb 1 +{app,lib}/**/*.rb lib/a.rb 1 +{app,lib}/**/*.rb lib/b.rb 1 +{app,lib}/**/*.rb lib/ab.rb 1 +{app,lib}/**/*.rb lib/d.rb 1 +{app,lib}/**/*.rb lib/-.rb 1 +{app,lib}/**/*.rb lib/].rb 1 +{app,lib}/**/*.rb lib/*.rb 1 +{app,lib}/**/*.rb lib/x.rb 1 +{app,lib}/**/*.rb lib/[.rb 1 +{app,lib}/**/*.rb vendor/x.rb 0 +{app,lib}/**/*.rb vendor/gem/lib/x.rb 0 +{app,lib}/**/*.rb vendored/x.rb 0 +{app,lib}/**/*.rb vendor 0 +{app,lib}/**/*.rb test/a_test.rb 0 +{app,lib}/**/*.rb test/unit/b_test.rb 0 +{app,lib}/**/*.rb a_test.rb 0 +{app,lib}/**/*.rb .github/workflows/ci.yml 0 +{app,lib}/**/*.rb .hidden 0 +{app,lib}/**/*.rb .lintus.yml 0 +{app,lib}/**/*.rb config/a.yaml 0 +{app,lib}/**/*.rb config/deep/b.yml 0 +{app,lib}/**/*.rb Gemfile 0 +{app,lib}/**/*.rb Gemfile.lock 0 +{app,lib}/**/*.rb sub/Gemfile 0 +{app,lib}/**/*.rb db/schema.rb 0 +{app,lib}/**/*.rb db/migrate/1.rb 0 +{app,lib}/**/*.rb exe/lintus 0 +{app,lib}/**/*.rb a/b.rb 0 +{app,lib}/**/*.rb a/x/b.rb 0 +{app,lib}/**/*.rb a/x/y/b.rb 0 +{app,lib}/**/*.rb a/b/c.rb 0 +{app,lib}/**/*.rb a/x/b/y/c.rb 0 +{app,lib}/**/*.rb abc.rb 0 +{app,lib}/**/*.rb aXbYc.rb 0 +{app,lib}/**/*.rb x.RB 0 +{app,lib}/**/*.rb x.rb 0 +{app,lib}/**/*.rb é.rb 0 +{app,lib}/**/*.rb dir/é.rb 0 +{app,lib}/**/*.rb éa.rb 0 +{app,lib}/**/*.rb x.rake 0 +{app,lib}/**/*.rb b/x.rb 0 +{app,lib}/**/*.rb a/c.rb 0 +{app,lib}/**/*.rb a/b/x/c.rb 0 +{app,lib}/**/*.rb {a}.rb 0 +{app,lib}/**/*.rb b.rb 0 +{app,lib}/**/*.rb a 0 +{app,lib}/**/*.rb b 0 +{app,lib}/**/*.rb lib/a/b.rb 1 +{app,lib}/**/*.rb test/lib/x.rb 0 +{app,lib}/**/*.rb .a/.b/c.rb 0 +**/*.{yml,yaml} a.rb 0 +**/*.{yml,yaml} app/a.rb 0 +**/*.{yml,yaml} app/jobs/a_job.rb 0 +**/*.{yml,yaml} app/jobs/deep/x.rb 0 +**/*.{yml,yaml} lib/a.rb 0 +**/*.{yml,yaml} lib/b.rb 0 +**/*.{yml,yaml} lib/ab.rb 0 +**/*.{yml,yaml} lib/d.rb 0 +**/*.{yml,yaml} lib/-.rb 0 +**/*.{yml,yaml} lib/].rb 0 +**/*.{yml,yaml} lib/*.rb 0 +**/*.{yml,yaml} lib/x.rb 0 +**/*.{yml,yaml} lib/[.rb 0 +**/*.{yml,yaml} vendor/x.rb 0 +**/*.{yml,yaml} vendor/gem/lib/x.rb 0 +**/*.{yml,yaml} vendored/x.rb 0 +**/*.{yml,yaml} vendor 0 +**/*.{yml,yaml} test/a_test.rb 0 +**/*.{yml,yaml} test/unit/b_test.rb 0 +**/*.{yml,yaml} a_test.rb 0 +**/*.{yml,yaml} .github/workflows/ci.yml 1 +**/*.{yml,yaml} .hidden 0 +**/*.{yml,yaml} .lintus.yml 1 +**/*.{yml,yaml} config/a.yaml 1 +**/*.{yml,yaml} config/deep/b.yml 1 +**/*.{yml,yaml} Gemfile 0 +**/*.{yml,yaml} Gemfile.lock 0 +**/*.{yml,yaml} sub/Gemfile 0 +**/*.{yml,yaml} db/schema.rb 0 +**/*.{yml,yaml} db/migrate/1.rb 0 +**/*.{yml,yaml} exe/lintus 0 +**/*.{yml,yaml} a/b.rb 0 +**/*.{yml,yaml} a/x/b.rb 0 +**/*.{yml,yaml} a/x/y/b.rb 0 +**/*.{yml,yaml} a/b/c.rb 0 +**/*.{yml,yaml} a/x/b/y/c.rb 0 +**/*.{yml,yaml} abc.rb 0 +**/*.{yml,yaml} aXbYc.rb 0 +**/*.{yml,yaml} x.RB 0 +**/*.{yml,yaml} x.rb 0 +**/*.{yml,yaml} é.rb 0 +**/*.{yml,yaml} dir/é.rb 0 +**/*.{yml,yaml} éa.rb 0 +**/*.{yml,yaml} x.rake 0 +**/*.{yml,yaml} b/x.rb 0 +**/*.{yml,yaml} a/c.rb 0 +**/*.{yml,yaml} a/b/x/c.rb 0 +**/*.{yml,yaml} {a}.rb 0 +**/*.{yml,yaml} b.rb 0 +**/*.{yml,yaml} a 0 +**/*.{yml,yaml} b 0 +**/*.{yml,yaml} lib/a/b.rb 0 +**/*.{yml,yaml} test/lib/x.rb 0 +**/*.{yml,yaml} .a/.b/c.rb 0 +*.{rb,rake} a.rb 1 +*.{rb,rake} app/a.rb 0 +*.{rb,rake} app/jobs/a_job.rb 0 +*.{rb,rake} app/jobs/deep/x.rb 0 +*.{rb,rake} lib/a.rb 0 +*.{rb,rake} lib/b.rb 0 +*.{rb,rake} lib/ab.rb 0 +*.{rb,rake} lib/d.rb 0 +*.{rb,rake} lib/-.rb 0 +*.{rb,rake} lib/].rb 0 +*.{rb,rake} lib/*.rb 0 +*.{rb,rake} lib/x.rb 0 +*.{rb,rake} lib/[.rb 0 +*.{rb,rake} vendor/x.rb 0 +*.{rb,rake} vendor/gem/lib/x.rb 0 +*.{rb,rake} vendored/x.rb 0 +*.{rb,rake} vendor 0 +*.{rb,rake} test/a_test.rb 0 +*.{rb,rake} test/unit/b_test.rb 0 +*.{rb,rake} a_test.rb 1 +*.{rb,rake} .github/workflows/ci.yml 0 +*.{rb,rake} .hidden 0 +*.{rb,rake} .lintus.yml 0 +*.{rb,rake} config/a.yaml 0 +*.{rb,rake} config/deep/b.yml 0 +*.{rb,rake} Gemfile 0 +*.{rb,rake} Gemfile.lock 0 +*.{rb,rake} sub/Gemfile 0 +*.{rb,rake} db/schema.rb 0 +*.{rb,rake} db/migrate/1.rb 0 +*.{rb,rake} exe/lintus 0 +*.{rb,rake} a/b.rb 0 +*.{rb,rake} a/x/b.rb 0 +*.{rb,rake} a/x/y/b.rb 0 +*.{rb,rake} a/b/c.rb 0 +*.{rb,rake} a/x/b/y/c.rb 0 +*.{rb,rake} abc.rb 1 +*.{rb,rake} aXbYc.rb 1 +*.{rb,rake} x.RB 0 +*.{rb,rake} x.rb 1 +*.{rb,rake} é.rb 1 +*.{rb,rake} dir/é.rb 0 +*.{rb,rake} éa.rb 1 +*.{rb,rake} x.rake 1 +*.{rb,rake} b/x.rb 0 +*.{rb,rake} a/c.rb 0 +*.{rb,rake} a/b/x/c.rb 0 +*.{rb,rake} {a}.rb 1 +*.{rb,rake} b.rb 1 +*.{rb,rake} a 0 +*.{rb,rake} b 0 +*.{rb,rake} lib/a/b.rb 0 +*.{rb,rake} test/lib/x.rb 0 +*.{rb,rake} .a/.b/c.rb 0 +{a,b a.rb 0 +{a,b app/a.rb 0 +{a,b app/jobs/a_job.rb 0 +{a,b app/jobs/deep/x.rb 0 +{a,b lib/a.rb 0 +{a,b lib/b.rb 0 +{a,b lib/ab.rb 0 +{a,b lib/d.rb 0 +{a,b lib/-.rb 0 +{a,b lib/].rb 0 +{a,b lib/*.rb 0 +{a,b lib/x.rb 0 +{a,b lib/[.rb 0 +{a,b vendor/x.rb 0 +{a,b vendor/gem/lib/x.rb 0 +{a,b vendored/x.rb 0 +{a,b vendor 0 +{a,b test/a_test.rb 0 +{a,b test/unit/b_test.rb 0 +{a,b a_test.rb 0 +{a,b .github/workflows/ci.yml 0 +{a,b .hidden 0 +{a,b .lintus.yml 0 +{a,b config/a.yaml 0 +{a,b config/deep/b.yml 0 +{a,b Gemfile 0 +{a,b Gemfile.lock 0 +{a,b sub/Gemfile 0 +{a,b db/schema.rb 0 +{a,b db/migrate/1.rb 0 +{a,b exe/lintus 0 +{a,b a/b.rb 0 +{a,b a/x/b.rb 0 +{a,b a/x/y/b.rb 0 +{a,b a/b/c.rb 0 +{a,b a/x/b/y/c.rb 0 +{a,b abc.rb 0 +{a,b aXbYc.rb 0 +{a,b x.RB 0 +{a,b x.rb 0 +{a,b é.rb 0 +{a,b dir/é.rb 0 +{a,b éa.rb 0 +{a,b x.rake 0 +{a,b b/x.rb 0 +{a,b a/c.rb 0 +{a,b a/b/x/c.rb 0 +{a,b {a}.rb 0 +{a,b b.rb 0 +{a,b a 0 +{a,b b 0 +{a,b lib/a/b.rb 0 +{a,b test/lib/x.rb 0 +{a,b .a/.b/c.rb 0 +a} a.rb 0 +a} app/a.rb 0 +a} app/jobs/a_job.rb 0 +a} app/jobs/deep/x.rb 0 +a} lib/a.rb 0 +a} lib/b.rb 0 +a} lib/ab.rb 0 +a} lib/d.rb 0 +a} lib/-.rb 0 +a} lib/].rb 0 +a} lib/*.rb 0 +a} lib/x.rb 0 +a} lib/[.rb 0 +a} vendor/x.rb 0 +a} vendor/gem/lib/x.rb 0 +a} vendored/x.rb 0 +a} vendor 0 +a} test/a_test.rb 0 +a} test/unit/b_test.rb 0 +a} a_test.rb 0 +a} .github/workflows/ci.yml 0 +a} .hidden 0 +a} .lintus.yml 0 +a} config/a.yaml 0 +a} config/deep/b.yml 0 +a} Gemfile 0 +a} Gemfile.lock 0 +a} sub/Gemfile 0 +a} db/schema.rb 0 +a} db/migrate/1.rb 0 +a} exe/lintus 0 +a} a/b.rb 0 +a} a/x/b.rb 0 +a} a/x/y/b.rb 0 +a} a/b/c.rb 0 +a} a/x/b/y/c.rb 0 +a} abc.rb 0 +a} aXbYc.rb 0 +a} x.RB 0 +a} x.rb 0 +a} é.rb 0 +a} dir/é.rb 0 +a} éa.rb 0 +a} x.rake 0 +a} b/x.rb 0 +a} a/c.rb 0 +a} a/b/x/c.rb 0 +a} {a}.rb 0 +a} b.rb 0 +a} a 0 +a} b 0 +a} lib/a/b.rb 0 +a} test/lib/x.rb 0 +a} .a/.b/c.rb 0 +{a,{b,c}}/*.rb a.rb 0 +{a,{b,c}}/*.rb app/a.rb 0 +{a,{b,c}}/*.rb app/jobs/a_job.rb 0 +{a,{b,c}}/*.rb app/jobs/deep/x.rb 0 +{a,{b,c}}/*.rb lib/a.rb 0 +{a,{b,c}}/*.rb lib/b.rb 0 +{a,{b,c}}/*.rb lib/ab.rb 0 +{a,{b,c}}/*.rb lib/d.rb 0 +{a,{b,c}}/*.rb lib/-.rb 0 +{a,{b,c}}/*.rb lib/].rb 0 +{a,{b,c}}/*.rb lib/*.rb 0 +{a,{b,c}}/*.rb lib/x.rb 0 +{a,{b,c}}/*.rb lib/[.rb 0 +{a,{b,c}}/*.rb vendor/x.rb 0 +{a,{b,c}}/*.rb vendor/gem/lib/x.rb 0 +{a,{b,c}}/*.rb vendored/x.rb 0 +{a,{b,c}}/*.rb vendor 0 +{a,{b,c}}/*.rb test/a_test.rb 0 +{a,{b,c}}/*.rb test/unit/b_test.rb 0 +{a,{b,c}}/*.rb a_test.rb 0 +{a,{b,c}}/*.rb .github/workflows/ci.yml 0 +{a,{b,c}}/*.rb .hidden 0 +{a,{b,c}}/*.rb .lintus.yml 0 +{a,{b,c}}/*.rb config/a.yaml 0 +{a,{b,c}}/*.rb config/deep/b.yml 0 +{a,{b,c}}/*.rb Gemfile 0 +{a,{b,c}}/*.rb Gemfile.lock 0 +{a,{b,c}}/*.rb sub/Gemfile 0 +{a,{b,c}}/*.rb db/schema.rb 0 +{a,{b,c}}/*.rb db/migrate/1.rb 0 +{a,{b,c}}/*.rb exe/lintus 0 +{a,{b,c}}/*.rb a/b.rb 1 +{a,{b,c}}/*.rb a/x/b.rb 0 +{a,{b,c}}/*.rb a/x/y/b.rb 0 +{a,{b,c}}/*.rb a/b/c.rb 0 +{a,{b,c}}/*.rb a/x/b/y/c.rb 0 +{a,{b,c}}/*.rb abc.rb 0 +{a,{b,c}}/*.rb aXbYc.rb 0 +{a,{b,c}}/*.rb x.RB 0 +{a,{b,c}}/*.rb x.rb 0 +{a,{b,c}}/*.rb é.rb 0 +{a,{b,c}}/*.rb dir/é.rb 0 +{a,{b,c}}/*.rb éa.rb 0 +{a,{b,c}}/*.rb x.rake 0 +{a,{b,c}}/*.rb b/x.rb 1 +{a,{b,c}}/*.rb a/c.rb 1 +{a,{b,c}}/*.rb a/b/x/c.rb 0 +{a,{b,c}}/*.rb {a}.rb 0 +{a,{b,c}}/*.rb b.rb 0 +{a,{b,c}}/*.rb a 0 +{a,{b,c}}/*.rb b 0 +{a,{b,c}}/*.rb lib/a/b.rb 0 +{a,{b,c}}/*.rb test/lib/x.rb 0 +{a,{b,c}}/*.rb .a/.b/c.rb 0 +{,lib/}x.rb a.rb 0 +{,lib/}x.rb app/a.rb 0 +{,lib/}x.rb app/jobs/a_job.rb 0 +{,lib/}x.rb app/jobs/deep/x.rb 0 +{,lib/}x.rb lib/a.rb 0 +{,lib/}x.rb lib/b.rb 0 +{,lib/}x.rb lib/ab.rb 0 +{,lib/}x.rb lib/d.rb 0 +{,lib/}x.rb lib/-.rb 0 +{,lib/}x.rb lib/].rb 0 +{,lib/}x.rb lib/*.rb 0 +{,lib/}x.rb lib/x.rb 1 +{,lib/}x.rb lib/[.rb 0 +{,lib/}x.rb vendor/x.rb 0 +{,lib/}x.rb vendor/gem/lib/x.rb 0 +{,lib/}x.rb vendored/x.rb 0 +{,lib/}x.rb vendor 0 +{,lib/}x.rb test/a_test.rb 0 +{,lib/}x.rb test/unit/b_test.rb 0 +{,lib/}x.rb a_test.rb 0 +{,lib/}x.rb .github/workflows/ci.yml 0 +{,lib/}x.rb .hidden 0 +{,lib/}x.rb .lintus.yml 0 +{,lib/}x.rb config/a.yaml 0 +{,lib/}x.rb config/deep/b.yml 0 +{,lib/}x.rb Gemfile 0 +{,lib/}x.rb Gemfile.lock 0 +{,lib/}x.rb sub/Gemfile 0 +{,lib/}x.rb db/schema.rb 0 +{,lib/}x.rb db/migrate/1.rb 0 +{,lib/}x.rb exe/lintus 0 +{,lib/}x.rb a/b.rb 0 +{,lib/}x.rb a/x/b.rb 0 +{,lib/}x.rb a/x/y/b.rb 0 +{,lib/}x.rb a/b/c.rb 0 +{,lib/}x.rb a/x/b/y/c.rb 0 +{,lib/}x.rb abc.rb 0 +{,lib/}x.rb aXbYc.rb 0 +{,lib/}x.rb x.RB 0 +{,lib/}x.rb x.rb 1 +{,lib/}x.rb é.rb 0 +{,lib/}x.rb dir/é.rb 0 +{,lib/}x.rb éa.rb 0 +{,lib/}x.rb x.rake 0 +{,lib/}x.rb b/x.rb 0 +{,lib/}x.rb a/c.rb 0 +{,lib/}x.rb a/b/x/c.rb 0 +{,lib/}x.rb {a}.rb 0 +{,lib/}x.rb b.rb 0 +{,lib/}x.rb a 0 +{,lib/}x.rb b 0 +{,lib/}x.rb lib/a/b.rb 0 +{,lib/}x.rb test/lib/x.rb 0 +{,lib/}x.rb .a/.b/c.rb 0 +lib/[a-c]*.rb a.rb 0 +lib/[a-c]*.rb app/a.rb 0 +lib/[a-c]*.rb app/jobs/a_job.rb 0 +lib/[a-c]*.rb app/jobs/deep/x.rb 0 +lib/[a-c]*.rb lib/a.rb 1 +lib/[a-c]*.rb lib/b.rb 1 +lib/[a-c]*.rb lib/ab.rb 1 +lib/[a-c]*.rb lib/d.rb 0 +lib/[a-c]*.rb lib/-.rb 0 +lib/[a-c]*.rb lib/].rb 0 +lib/[a-c]*.rb lib/*.rb 0 +lib/[a-c]*.rb lib/x.rb 0 +lib/[a-c]*.rb lib/[.rb 0 +lib/[a-c]*.rb vendor/x.rb 0 +lib/[a-c]*.rb vendor/gem/lib/x.rb 0 +lib/[a-c]*.rb vendored/x.rb 0 +lib/[a-c]*.rb vendor 0 +lib/[a-c]*.rb test/a_test.rb 0 +lib/[a-c]*.rb test/unit/b_test.rb 0 +lib/[a-c]*.rb a_test.rb 0 +lib/[a-c]*.rb .github/workflows/ci.yml 0 +lib/[a-c]*.rb .hidden 0 +lib/[a-c]*.rb .lintus.yml 0 +lib/[a-c]*.rb config/a.yaml 0 +lib/[a-c]*.rb config/deep/b.yml 0 +lib/[a-c]*.rb Gemfile 0 +lib/[a-c]*.rb Gemfile.lock 0 +lib/[a-c]*.rb sub/Gemfile 0 +lib/[a-c]*.rb db/schema.rb 0 +lib/[a-c]*.rb db/migrate/1.rb 0 +lib/[a-c]*.rb exe/lintus 0 +lib/[a-c]*.rb a/b.rb 0 +lib/[a-c]*.rb a/x/b.rb 0 +lib/[a-c]*.rb a/x/y/b.rb 0 +lib/[a-c]*.rb a/b/c.rb 0 +lib/[a-c]*.rb a/x/b/y/c.rb 0 +lib/[a-c]*.rb abc.rb 0 +lib/[a-c]*.rb aXbYc.rb 0 +lib/[a-c]*.rb x.RB 0 +lib/[a-c]*.rb x.rb 0 +lib/[a-c]*.rb é.rb 0 +lib/[a-c]*.rb dir/é.rb 0 +lib/[a-c]*.rb éa.rb 0 +lib/[a-c]*.rb x.rake 0 +lib/[a-c]*.rb b/x.rb 0 +lib/[a-c]*.rb a/c.rb 0 +lib/[a-c]*.rb a/b/x/c.rb 0 +lib/[a-c]*.rb {a}.rb 0 +lib/[a-c]*.rb b.rb 0 +lib/[a-c]*.rb a 0 +lib/[a-c]*.rb b 0 +lib/[a-c]*.rb lib/a/b.rb 0 +lib/[a-c]*.rb test/lib/x.rb 0 +lib/[a-c]*.rb .a/.b/c.rb 0 +lib/[!a-c]*.rb a.rb 0 +lib/[!a-c]*.rb app/a.rb 0 +lib/[!a-c]*.rb app/jobs/a_job.rb 0 +lib/[!a-c]*.rb app/jobs/deep/x.rb 0 +lib/[!a-c]*.rb lib/a.rb 0 +lib/[!a-c]*.rb lib/b.rb 0 +lib/[!a-c]*.rb lib/ab.rb 0 +lib/[!a-c]*.rb lib/d.rb 1 +lib/[!a-c]*.rb lib/-.rb 1 +lib/[!a-c]*.rb lib/].rb 1 +lib/[!a-c]*.rb lib/*.rb 1 +lib/[!a-c]*.rb lib/x.rb 1 +lib/[!a-c]*.rb lib/[.rb 1 +lib/[!a-c]*.rb vendor/x.rb 0 +lib/[!a-c]*.rb vendor/gem/lib/x.rb 0 +lib/[!a-c]*.rb vendored/x.rb 0 +lib/[!a-c]*.rb vendor 0 +lib/[!a-c]*.rb test/a_test.rb 0 +lib/[!a-c]*.rb test/unit/b_test.rb 0 +lib/[!a-c]*.rb a_test.rb 0 +lib/[!a-c]*.rb .github/workflows/ci.yml 0 +lib/[!a-c]*.rb .hidden 0 +lib/[!a-c]*.rb .lintus.yml 0 +lib/[!a-c]*.rb config/a.yaml 0 +lib/[!a-c]*.rb config/deep/b.yml 0 +lib/[!a-c]*.rb Gemfile 0 +lib/[!a-c]*.rb Gemfile.lock 0 +lib/[!a-c]*.rb sub/Gemfile 0 +lib/[!a-c]*.rb db/schema.rb 0 +lib/[!a-c]*.rb db/migrate/1.rb 0 +lib/[!a-c]*.rb exe/lintus 0 +lib/[!a-c]*.rb a/b.rb 0 +lib/[!a-c]*.rb a/x/b.rb 0 +lib/[!a-c]*.rb a/x/y/b.rb 0 +lib/[!a-c]*.rb a/b/c.rb 0 +lib/[!a-c]*.rb a/x/b/y/c.rb 0 +lib/[!a-c]*.rb abc.rb 0 +lib/[!a-c]*.rb aXbYc.rb 0 +lib/[!a-c]*.rb x.RB 0 +lib/[!a-c]*.rb x.rb 0 +lib/[!a-c]*.rb é.rb 0 +lib/[!a-c]*.rb dir/é.rb 0 +lib/[!a-c]*.rb éa.rb 0 +lib/[!a-c]*.rb x.rake 0 +lib/[!a-c]*.rb b/x.rb 0 +lib/[!a-c]*.rb a/c.rb 0 +lib/[!a-c]*.rb a/b/x/c.rb 0 +lib/[!a-c]*.rb {a}.rb 0 +lib/[!a-c]*.rb b.rb 0 +lib/[!a-c]*.rb a 0 +lib/[!a-c]*.rb b 0 +lib/[!a-c]*.rb lib/a/b.rb 0 +lib/[!a-c]*.rb test/lib/x.rb 0 +lib/[!a-c]*.rb .a/.b/c.rb 0 +lib/[^a]*.rb a.rb 0 +lib/[^a]*.rb app/a.rb 0 +lib/[^a]*.rb app/jobs/a_job.rb 0 +lib/[^a]*.rb app/jobs/deep/x.rb 0 +lib/[^a]*.rb lib/a.rb 0 +lib/[^a]*.rb lib/b.rb 1 +lib/[^a]*.rb lib/ab.rb 0 +lib/[^a]*.rb lib/d.rb 1 +lib/[^a]*.rb lib/-.rb 1 +lib/[^a]*.rb lib/].rb 1 +lib/[^a]*.rb lib/*.rb 1 +lib/[^a]*.rb lib/x.rb 1 +lib/[^a]*.rb lib/[.rb 1 +lib/[^a]*.rb vendor/x.rb 0 +lib/[^a]*.rb vendor/gem/lib/x.rb 0 +lib/[^a]*.rb vendored/x.rb 0 +lib/[^a]*.rb vendor 0 +lib/[^a]*.rb test/a_test.rb 0 +lib/[^a]*.rb test/unit/b_test.rb 0 +lib/[^a]*.rb a_test.rb 0 +lib/[^a]*.rb .github/workflows/ci.yml 0 +lib/[^a]*.rb .hidden 0 +lib/[^a]*.rb .lintus.yml 0 +lib/[^a]*.rb config/a.yaml 0 +lib/[^a]*.rb config/deep/b.yml 0 +lib/[^a]*.rb Gemfile 0 +lib/[^a]*.rb Gemfile.lock 0 +lib/[^a]*.rb sub/Gemfile 0 +lib/[^a]*.rb db/schema.rb 0 +lib/[^a]*.rb db/migrate/1.rb 0 +lib/[^a]*.rb exe/lintus 0 +lib/[^a]*.rb a/b.rb 0 +lib/[^a]*.rb a/x/b.rb 0 +lib/[^a]*.rb a/x/y/b.rb 0 +lib/[^a]*.rb a/b/c.rb 0 +lib/[^a]*.rb a/x/b/y/c.rb 0 +lib/[^a]*.rb abc.rb 0 +lib/[^a]*.rb aXbYc.rb 0 +lib/[^a]*.rb x.RB 0 +lib/[^a]*.rb x.rb 0 +lib/[^a]*.rb é.rb 0 +lib/[^a]*.rb dir/é.rb 0 +lib/[^a]*.rb éa.rb 0 +lib/[^a]*.rb x.rake 0 +lib/[^a]*.rb b/x.rb 0 +lib/[^a]*.rb a/c.rb 0 +lib/[^a]*.rb a/b/x/c.rb 0 +lib/[^a]*.rb {a}.rb 0 +lib/[^a]*.rb b.rb 0 +lib/[^a]*.rb a 0 +lib/[^a]*.rb b 0 +lib/[^a]*.rb lib/a/b.rb 0 +lib/[^a]*.rb test/lib/x.rb 0 +lib/[^a]*.rb .a/.b/c.rb 0 +lib/[a-]*.rb a.rb 0 +lib/[a-]*.rb app/a.rb 0 +lib/[a-]*.rb app/jobs/a_job.rb 0 +lib/[a-]*.rb app/jobs/deep/x.rb 0 +lib/[a-]*.rb lib/a.rb 1 +lib/[a-]*.rb lib/b.rb 0 +lib/[a-]*.rb lib/ab.rb 1 +lib/[a-]*.rb lib/d.rb 0 +lib/[a-]*.rb lib/-.rb 1 +lib/[a-]*.rb lib/].rb 0 +lib/[a-]*.rb lib/*.rb 0 +lib/[a-]*.rb lib/x.rb 0 +lib/[a-]*.rb lib/[.rb 0 +lib/[a-]*.rb vendor/x.rb 0 +lib/[a-]*.rb vendor/gem/lib/x.rb 0 +lib/[a-]*.rb vendored/x.rb 0 +lib/[a-]*.rb vendor 0 +lib/[a-]*.rb test/a_test.rb 0 +lib/[a-]*.rb test/unit/b_test.rb 0 +lib/[a-]*.rb a_test.rb 0 +lib/[a-]*.rb .github/workflows/ci.yml 0 +lib/[a-]*.rb .hidden 0 +lib/[a-]*.rb .lintus.yml 0 +lib/[a-]*.rb config/a.yaml 0 +lib/[a-]*.rb config/deep/b.yml 0 +lib/[a-]*.rb Gemfile 0 +lib/[a-]*.rb Gemfile.lock 0 +lib/[a-]*.rb sub/Gemfile 0 +lib/[a-]*.rb db/schema.rb 0 +lib/[a-]*.rb db/migrate/1.rb 0 +lib/[a-]*.rb exe/lintus 0 +lib/[a-]*.rb a/b.rb 0 +lib/[a-]*.rb a/x/b.rb 0 +lib/[a-]*.rb a/x/y/b.rb 0 +lib/[a-]*.rb a/b/c.rb 0 +lib/[a-]*.rb a/x/b/y/c.rb 0 +lib/[a-]*.rb abc.rb 0 +lib/[a-]*.rb aXbYc.rb 0 +lib/[a-]*.rb x.RB 0 +lib/[a-]*.rb x.rb 0 +lib/[a-]*.rb é.rb 0 +lib/[a-]*.rb dir/é.rb 0 +lib/[a-]*.rb éa.rb 0 +lib/[a-]*.rb x.rake 0 +lib/[a-]*.rb b/x.rb 0 +lib/[a-]*.rb a/c.rb 0 +lib/[a-]*.rb a/b/x/c.rb 0 +lib/[a-]*.rb {a}.rb 0 +lib/[a-]*.rb b.rb 0 +lib/[a-]*.rb a 0 +lib/[a-]*.rb b 0 +lib/[a-]*.rb lib/a/b.rb 0 +lib/[a-]*.rb test/lib/x.rb 0 +lib/[a-]*.rb .a/.b/c.rb 0 +lib/[]]*.rb a.rb 0 +lib/[]]*.rb app/a.rb 0 +lib/[]]*.rb app/jobs/a_job.rb 0 +lib/[]]*.rb app/jobs/deep/x.rb 0 +lib/[]]*.rb lib/a.rb 0 +lib/[]]*.rb lib/b.rb 0 +lib/[]]*.rb lib/ab.rb 0 +lib/[]]*.rb lib/d.rb 0 +lib/[]]*.rb lib/-.rb 0 +lib/[]]*.rb lib/].rb 0 +lib/[]]*.rb lib/*.rb 0 +lib/[]]*.rb lib/x.rb 0 +lib/[]]*.rb lib/[.rb 0 +lib/[]]*.rb vendor/x.rb 0 +lib/[]]*.rb vendor/gem/lib/x.rb 0 +lib/[]]*.rb vendored/x.rb 0 +lib/[]]*.rb vendor 0 +lib/[]]*.rb test/a_test.rb 0 +lib/[]]*.rb test/unit/b_test.rb 0 +lib/[]]*.rb a_test.rb 0 +lib/[]]*.rb .github/workflows/ci.yml 0 +lib/[]]*.rb .hidden 0 +lib/[]]*.rb .lintus.yml 0 +lib/[]]*.rb config/a.yaml 0 +lib/[]]*.rb config/deep/b.yml 0 +lib/[]]*.rb Gemfile 0 +lib/[]]*.rb Gemfile.lock 0 +lib/[]]*.rb sub/Gemfile 0 +lib/[]]*.rb db/schema.rb 0 +lib/[]]*.rb db/migrate/1.rb 0 +lib/[]]*.rb exe/lintus 0 +lib/[]]*.rb a/b.rb 0 +lib/[]]*.rb a/x/b.rb 0 +lib/[]]*.rb a/x/y/b.rb 0 +lib/[]]*.rb a/b/c.rb 0 +lib/[]]*.rb a/x/b/y/c.rb 0 +lib/[]]*.rb abc.rb 0 +lib/[]]*.rb aXbYc.rb 0 +lib/[]]*.rb x.RB 0 +lib/[]]*.rb x.rb 0 +lib/[]]*.rb é.rb 0 +lib/[]]*.rb dir/é.rb 0 +lib/[]]*.rb éa.rb 0 +lib/[]]*.rb x.rake 0 +lib/[]]*.rb b/x.rb 0 +lib/[]]*.rb a/c.rb 0 +lib/[]]*.rb a/b/x/c.rb 0 +lib/[]]*.rb {a}.rb 0 +lib/[]]*.rb b.rb 0 +lib/[]]*.rb a 0 +lib/[]]*.rb b 0 +lib/[]]*.rb lib/a/b.rb 0 +lib/[]]*.rb test/lib/x.rb 0 +lib/[]]*.rb .a/.b/c.rb 0 +lib/[ a.rb 0 +lib/[ app/a.rb 0 +lib/[ app/jobs/a_job.rb 0 +lib/[ app/jobs/deep/x.rb 0 +lib/[ lib/a.rb 0 +lib/[ lib/b.rb 0 +lib/[ lib/ab.rb 0 +lib/[ lib/d.rb 0 +lib/[ lib/-.rb 0 +lib/[ lib/].rb 0 +lib/[ lib/*.rb 0 +lib/[ lib/x.rb 0 +lib/[ lib/[.rb 0 +lib/[ vendor/x.rb 0 +lib/[ vendor/gem/lib/x.rb 0 +lib/[ vendored/x.rb 0 +lib/[ vendor 0 +lib/[ test/a_test.rb 0 +lib/[ test/unit/b_test.rb 0 +lib/[ a_test.rb 0 +lib/[ .github/workflows/ci.yml 0 +lib/[ .hidden 0 +lib/[ .lintus.yml 0 +lib/[ config/a.yaml 0 +lib/[ config/deep/b.yml 0 +lib/[ Gemfile 0 +lib/[ Gemfile.lock 0 +lib/[ sub/Gemfile 0 +lib/[ db/schema.rb 0 +lib/[ db/migrate/1.rb 0 +lib/[ exe/lintus 0 +lib/[ a/b.rb 0 +lib/[ a/x/b.rb 0 +lib/[ a/x/y/b.rb 0 +lib/[ a/b/c.rb 0 +lib/[ a/x/b/y/c.rb 0 +lib/[ abc.rb 0 +lib/[ aXbYc.rb 0 +lib/[ x.RB 0 +lib/[ x.rb 0 +lib/[ é.rb 0 +lib/[ dir/é.rb 0 +lib/[ éa.rb 0 +lib/[ x.rake 0 +lib/[ b/x.rb 0 +lib/[ a/c.rb 0 +lib/[ a/b/x/c.rb 0 +lib/[ {a}.rb 0 +lib/[ b.rb 0 +lib/[ a 0 +lib/[ b 0 +lib/[ lib/a/b.rb 0 +lib/[ test/lib/x.rb 0 +lib/[ .a/.b/c.rb 0 +lib/[a a.rb 0 +lib/[a app/a.rb 0 +lib/[a app/jobs/a_job.rb 0 +lib/[a app/jobs/deep/x.rb 0 +lib/[a lib/a.rb 0 +lib/[a lib/b.rb 0 +lib/[a lib/ab.rb 0 +lib/[a lib/d.rb 0 +lib/[a lib/-.rb 0 +lib/[a lib/].rb 0 +lib/[a lib/*.rb 0 +lib/[a lib/x.rb 0 +lib/[a lib/[.rb 0 +lib/[a vendor/x.rb 0 +lib/[a vendor/gem/lib/x.rb 0 +lib/[a vendored/x.rb 0 +lib/[a vendor 0 +lib/[a test/a_test.rb 0 +lib/[a test/unit/b_test.rb 0 +lib/[a a_test.rb 0 +lib/[a .github/workflows/ci.yml 0 +lib/[a .hidden 0 +lib/[a .lintus.yml 0 +lib/[a config/a.yaml 0 +lib/[a config/deep/b.yml 0 +lib/[a Gemfile 0 +lib/[a Gemfile.lock 0 +lib/[a sub/Gemfile 0 +lib/[a db/schema.rb 0 +lib/[a db/migrate/1.rb 0 +lib/[a exe/lintus 0 +lib/[a a/b.rb 0 +lib/[a a/x/b.rb 0 +lib/[a a/x/y/b.rb 0 +lib/[a a/b/c.rb 0 +lib/[a a/x/b/y/c.rb 0 +lib/[a abc.rb 0 +lib/[a aXbYc.rb 0 +lib/[a x.RB 0 +lib/[a x.rb 0 +lib/[a é.rb 0 +lib/[a dir/é.rb 0 +lib/[a éa.rb 0 +lib/[a x.rake 0 +lib/[a b/x.rb 0 +lib/[a a/c.rb 0 +lib/[a a/b/x/c.rb 0 +lib/[a {a}.rb 0 +lib/[a b.rb 0 +lib/[a a 0 +lib/[a b 0 +lib/[a lib/a/b.rb 0 +lib/[a test/lib/x.rb 0 +lib/[a .a/.b/c.rb 0 +lib/?.rb a.rb 0 +lib/?.rb app/a.rb 0 +lib/?.rb app/jobs/a_job.rb 0 +lib/?.rb app/jobs/deep/x.rb 0 +lib/?.rb lib/a.rb 1 +lib/?.rb lib/b.rb 1 +lib/?.rb lib/ab.rb 0 +lib/?.rb lib/d.rb 1 +lib/?.rb lib/-.rb 1 +lib/?.rb lib/].rb 1 +lib/?.rb lib/*.rb 1 +lib/?.rb lib/x.rb 1 +lib/?.rb lib/[.rb 1 +lib/?.rb vendor/x.rb 0 +lib/?.rb vendor/gem/lib/x.rb 0 +lib/?.rb vendored/x.rb 0 +lib/?.rb vendor 0 +lib/?.rb test/a_test.rb 0 +lib/?.rb test/unit/b_test.rb 0 +lib/?.rb a_test.rb 0 +lib/?.rb .github/workflows/ci.yml 0 +lib/?.rb .hidden 0 +lib/?.rb .lintus.yml 0 +lib/?.rb config/a.yaml 0 +lib/?.rb config/deep/b.yml 0 +lib/?.rb Gemfile 0 +lib/?.rb Gemfile.lock 0 +lib/?.rb sub/Gemfile 0 +lib/?.rb db/schema.rb 0 +lib/?.rb db/migrate/1.rb 0 +lib/?.rb exe/lintus 0 +lib/?.rb a/b.rb 0 +lib/?.rb a/x/b.rb 0 +lib/?.rb a/x/y/b.rb 0 +lib/?.rb a/b/c.rb 0 +lib/?.rb a/x/b/y/c.rb 0 +lib/?.rb abc.rb 0 +lib/?.rb aXbYc.rb 0 +lib/?.rb x.RB 0 +lib/?.rb x.rb 0 +lib/?.rb é.rb 0 +lib/?.rb dir/é.rb 0 +lib/?.rb éa.rb 0 +lib/?.rb x.rake 0 +lib/?.rb b/x.rb 0 +lib/?.rb a/c.rb 0 +lib/?.rb a/b/x/c.rb 0 +lib/?.rb {a}.rb 0 +lib/?.rb b.rb 0 +lib/?.rb a 0 +lib/?.rb b 0 +lib/?.rb lib/a/b.rb 0 +lib/?.rb test/lib/x.rb 0 +lib/?.rb .a/.b/c.rb 0 +lib/??.rb a.rb 0 +lib/??.rb app/a.rb 0 +lib/??.rb app/jobs/a_job.rb 0 +lib/??.rb app/jobs/deep/x.rb 0 +lib/??.rb lib/a.rb 0 +lib/??.rb lib/b.rb 0 +lib/??.rb lib/ab.rb 1 +lib/??.rb lib/d.rb 0 +lib/??.rb lib/-.rb 0 +lib/??.rb lib/].rb 0 +lib/??.rb lib/*.rb 0 +lib/??.rb lib/x.rb 0 +lib/??.rb lib/[.rb 0 +lib/??.rb vendor/x.rb 0 +lib/??.rb vendor/gem/lib/x.rb 0 +lib/??.rb vendored/x.rb 0 +lib/??.rb vendor 0 +lib/??.rb test/a_test.rb 0 +lib/??.rb test/unit/b_test.rb 0 +lib/??.rb a_test.rb 0 +lib/??.rb .github/workflows/ci.yml 0 +lib/??.rb .hidden 0 +lib/??.rb .lintus.yml 0 +lib/??.rb config/a.yaml 0 +lib/??.rb config/deep/b.yml 0 +lib/??.rb Gemfile 0 +lib/??.rb Gemfile.lock 0 +lib/??.rb sub/Gemfile 0 +lib/??.rb db/schema.rb 0 +lib/??.rb db/migrate/1.rb 0 +lib/??.rb exe/lintus 0 +lib/??.rb a/b.rb 0 +lib/??.rb a/x/b.rb 0 +lib/??.rb a/x/y/b.rb 0 +lib/??.rb a/b/c.rb 0 +lib/??.rb a/x/b/y/c.rb 0 +lib/??.rb abc.rb 0 +lib/??.rb aXbYc.rb 0 +lib/??.rb x.RB 0 +lib/??.rb x.rb 0 +lib/??.rb é.rb 0 +lib/??.rb dir/é.rb 0 +lib/??.rb éa.rb 0 +lib/??.rb x.rake 0 +lib/??.rb b/x.rb 0 +lib/??.rb a/c.rb 0 +lib/??.rb a/b/x/c.rb 0 +lib/??.rb {a}.rb 0 +lib/??.rb b.rb 0 +lib/??.rb a 0 +lib/??.rb b 0 +lib/??.rb lib/a/b.rb 0 +lib/??.rb test/lib/x.rb 0 +lib/??.rb .a/.b/c.rb 0 +? a.rb 0 +? app/a.rb 0 +? app/jobs/a_job.rb 0 +? app/jobs/deep/x.rb 0 +? lib/a.rb 0 +? lib/b.rb 0 +? lib/ab.rb 0 +? lib/d.rb 0 +? lib/-.rb 0 +? lib/].rb 0 +? lib/*.rb 0 +? lib/x.rb 0 +? lib/[.rb 0 +? vendor/x.rb 0 +? vendor/gem/lib/x.rb 0 +? vendored/x.rb 0 +? vendor 0 +? test/a_test.rb 0 +? test/unit/b_test.rb 0 +? a_test.rb 0 +? .github/workflows/ci.yml 0 +? .hidden 0 +? .lintus.yml 0 +? config/a.yaml 0 +? config/deep/b.yml 0 +? Gemfile 0 +? Gemfile.lock 0 +? sub/Gemfile 0 +? db/schema.rb 0 +? db/migrate/1.rb 0 +? exe/lintus 0 +? a/b.rb 0 +? a/x/b.rb 0 +? a/x/y/b.rb 0 +? a/b/c.rb 0 +? a/x/b/y/c.rb 0 +? abc.rb 0 +? aXbYc.rb 0 +? x.RB 0 +? x.rb 0 +? é.rb 0 +? dir/é.rb 0 +? éa.rb 0 +? x.rake 0 +? b/x.rb 0 +? a/c.rb 0 +? a/b/x/c.rb 0 +? {a}.rb 0 +? b.rb 0 +? a 1 +? b 1 +? lib/a/b.rb 0 +? test/lib/x.rb 0 +? .a/.b/c.rb 0 +.* a.rb 0 +.* app/a.rb 0 +.* app/jobs/a_job.rb 0 +.* app/jobs/deep/x.rb 0 +.* lib/a.rb 0 +.* lib/b.rb 0 +.* lib/ab.rb 0 +.* lib/d.rb 0 +.* lib/-.rb 0 +.* lib/].rb 0 +.* lib/*.rb 0 +.* lib/x.rb 0 +.* lib/[.rb 0 +.* vendor/x.rb 0 +.* vendor/gem/lib/x.rb 0 +.* vendored/x.rb 0 +.* vendor 0 +.* test/a_test.rb 0 +.* test/unit/b_test.rb 0 +.* a_test.rb 0 +.* .github/workflows/ci.yml 0 +.* .hidden 1 +.* .lintus.yml 1 +.* config/a.yaml 0 +.* config/deep/b.yml 0 +.* Gemfile 0 +.* Gemfile.lock 0 +.* sub/Gemfile 0 +.* db/schema.rb 0 +.* db/migrate/1.rb 0 +.* exe/lintus 0 +.* a/b.rb 0 +.* a/x/b.rb 0 +.* a/x/y/b.rb 0 +.* a/b/c.rb 0 +.* a/x/b/y/c.rb 0 +.* abc.rb 0 +.* aXbYc.rb 0 +.* x.RB 0 +.* x.rb 0 +.* é.rb 0 +.* dir/é.rb 0 +.* éa.rb 0 +.* x.rake 0 +.* b/x.rb 0 +.* a/c.rb 0 +.* a/b/x/c.rb 0 +.* {a}.rb 0 +.* b.rb 0 +.* a 0 +.* b 0 +.* lib/a/b.rb 0 +.* test/lib/x.rb 0 +.* .a/.b/c.rb 0 +.*/** a.rb 0 +.*/** app/a.rb 0 +.*/** app/jobs/a_job.rb 0 +.*/** app/jobs/deep/x.rb 0 +.*/** lib/a.rb 0 +.*/** lib/b.rb 0 +.*/** lib/ab.rb 0 +.*/** lib/d.rb 0 +.*/** lib/-.rb 0 +.*/** lib/].rb 0 +.*/** lib/*.rb 0 +.*/** lib/x.rb 0 +.*/** lib/[.rb 0 +.*/** vendor/x.rb 0 +.*/** vendor/gem/lib/x.rb 0 +.*/** vendored/x.rb 0 +.*/** vendor 0 +.*/** test/a_test.rb 0 +.*/** test/unit/b_test.rb 0 +.*/** a_test.rb 0 +.*/** .github/workflows/ci.yml 1 +.*/** .hidden 0 +.*/** .lintus.yml 0 +.*/** config/a.yaml 0 +.*/** config/deep/b.yml 0 +.*/** Gemfile 0 +.*/** Gemfile.lock 0 +.*/** sub/Gemfile 0 +.*/** db/schema.rb 0 +.*/** db/migrate/1.rb 0 +.*/** exe/lintus 0 +.*/** a/b.rb 0 +.*/** a/x/b.rb 0 +.*/** a/x/y/b.rb 0 +.*/** a/b/c.rb 0 +.*/** a/x/b/y/c.rb 0 +.*/** abc.rb 0 +.*/** aXbYc.rb 0 +.*/** x.RB 0 +.*/** x.rb 0 +.*/** é.rb 0 +.*/** dir/é.rb 0 +.*/** éa.rb 0 +.*/** x.rake 0 +.*/** b/x.rb 0 +.*/** a/c.rb 0 +.*/** a/b/x/c.rb 0 +.*/** {a}.rb 0 +.*/** b.rb 0 +.*/** a 0 +.*/** b 0 +.*/** lib/a/b.rb 0 +.*/** test/lib/x.rb 0 +.*/** .a/.b/c.rb 1 +**/.* a.rb 0 +**/.* app/a.rb 0 +**/.* app/jobs/a_job.rb 0 +**/.* app/jobs/deep/x.rb 0 +**/.* lib/a.rb 0 +**/.* lib/b.rb 0 +**/.* lib/ab.rb 0 +**/.* lib/d.rb 0 +**/.* lib/-.rb 0 +**/.* lib/].rb 0 +**/.* lib/*.rb 0 +**/.* lib/x.rb 0 +**/.* lib/[.rb 0 +**/.* vendor/x.rb 0 +**/.* vendor/gem/lib/x.rb 0 +**/.* vendored/x.rb 0 +**/.* vendor 0 +**/.* test/a_test.rb 0 +**/.* test/unit/b_test.rb 0 +**/.* a_test.rb 0 +**/.* .github/workflows/ci.yml 0 +**/.* .hidden 1 +**/.* .lintus.yml 1 +**/.* config/a.yaml 0 +**/.* config/deep/b.yml 0 +**/.* Gemfile 0 +**/.* Gemfile.lock 0 +**/.* sub/Gemfile 0 +**/.* db/schema.rb 0 +**/.* db/migrate/1.rb 0 +**/.* exe/lintus 0 +**/.* a/b.rb 0 +**/.* a/x/b.rb 0 +**/.* a/x/y/b.rb 0 +**/.* a/b/c.rb 0 +**/.* a/x/b/y/c.rb 0 +**/.* abc.rb 0 +**/.* aXbYc.rb 0 +**/.* x.RB 0 +**/.* x.rb 0 +**/.* é.rb 0 +**/.* dir/é.rb 0 +**/.* éa.rb 0 +**/.* x.rake 0 +**/.* b/x.rb 0 +**/.* a/c.rb 0 +**/.* a/b/x/c.rb 0 +**/.* {a}.rb 0 +**/.* b.rb 0 +**/.* a 0 +**/.* b 0 +**/.* lib/a/b.rb 0 +**/.* test/lib/x.rb 0 +**/.* .a/.b/c.rb 0 +.github/**/*.yml a.rb 0 +.github/**/*.yml app/a.rb 0 +.github/**/*.yml app/jobs/a_job.rb 0 +.github/**/*.yml app/jobs/deep/x.rb 0 +.github/**/*.yml lib/a.rb 0 +.github/**/*.yml lib/b.rb 0 +.github/**/*.yml lib/ab.rb 0 +.github/**/*.yml lib/d.rb 0 +.github/**/*.yml lib/-.rb 0 +.github/**/*.yml lib/].rb 0 +.github/**/*.yml lib/*.rb 0 +.github/**/*.yml lib/x.rb 0 +.github/**/*.yml lib/[.rb 0 +.github/**/*.yml vendor/x.rb 0 +.github/**/*.yml vendor/gem/lib/x.rb 0 +.github/**/*.yml vendored/x.rb 0 +.github/**/*.yml vendor 0 +.github/**/*.yml test/a_test.rb 0 +.github/**/*.yml test/unit/b_test.rb 0 +.github/**/*.yml a_test.rb 0 +.github/**/*.yml .github/workflows/ci.yml 1 +.github/**/*.yml .hidden 0 +.github/**/*.yml .lintus.yml 0 +.github/**/*.yml config/a.yaml 0 +.github/**/*.yml config/deep/b.yml 0 +.github/**/*.yml Gemfile 0 +.github/**/*.yml Gemfile.lock 0 +.github/**/*.yml sub/Gemfile 0 +.github/**/*.yml db/schema.rb 0 +.github/**/*.yml db/migrate/1.rb 0 +.github/**/*.yml exe/lintus 0 +.github/**/*.yml a/b.rb 0 +.github/**/*.yml a/x/b.rb 0 +.github/**/*.yml a/x/y/b.rb 0 +.github/**/*.yml a/b/c.rb 0 +.github/**/*.yml a/x/b/y/c.rb 0 +.github/**/*.yml abc.rb 0 +.github/**/*.yml aXbYc.rb 0 +.github/**/*.yml x.RB 0 +.github/**/*.yml x.rb 0 +.github/**/*.yml é.rb 0 +.github/**/*.yml dir/é.rb 0 +.github/**/*.yml éa.rb 0 +.github/**/*.yml x.rake 0 +.github/**/*.yml b/x.rb 0 +.github/**/*.yml a/c.rb 0 +.github/**/*.yml a/b/x/c.rb 0 +.github/**/*.yml {a}.rb 0 +.github/**/*.yml b.rb 0 +.github/**/*.yml a 0 +.github/**/*.yml b 0 +.github/**/*.yml lib/a/b.rb 0 +.github/**/*.yml test/lib/x.rb 0 +.github/**/*.yml .a/.b/c.rb 0 +**/**/*.rb a.rb 1 +**/**/*.rb app/a.rb 1 +**/**/*.rb app/jobs/a_job.rb 1 +**/**/*.rb app/jobs/deep/x.rb 1 +**/**/*.rb lib/a.rb 1 +**/**/*.rb lib/b.rb 1 +**/**/*.rb lib/ab.rb 1 +**/**/*.rb lib/d.rb 1 +**/**/*.rb lib/-.rb 1 +**/**/*.rb lib/].rb 1 +**/**/*.rb lib/*.rb 1 +**/**/*.rb lib/x.rb 1 +**/**/*.rb lib/[.rb 1 +**/**/*.rb vendor/x.rb 1 +**/**/*.rb vendor/gem/lib/x.rb 1 +**/**/*.rb vendored/x.rb 1 +**/**/*.rb vendor 0 +**/**/*.rb test/a_test.rb 1 +**/**/*.rb test/unit/b_test.rb 1 +**/**/*.rb a_test.rb 1 +**/**/*.rb .github/workflows/ci.yml 0 +**/**/*.rb .hidden 0 +**/**/*.rb .lintus.yml 0 +**/**/*.rb config/a.yaml 0 +**/**/*.rb config/deep/b.yml 0 +**/**/*.rb Gemfile 0 +**/**/*.rb Gemfile.lock 0 +**/**/*.rb sub/Gemfile 0 +**/**/*.rb db/schema.rb 1 +**/**/*.rb db/migrate/1.rb 1 +**/**/*.rb exe/lintus 0 +**/**/*.rb a/b.rb 1 +**/**/*.rb a/x/b.rb 1 +**/**/*.rb a/x/y/b.rb 1 +**/**/*.rb a/b/c.rb 1 +**/**/*.rb a/x/b/y/c.rb 1 +**/**/*.rb abc.rb 1 +**/**/*.rb aXbYc.rb 1 +**/**/*.rb x.RB 0 +**/**/*.rb x.rb 1 +**/**/*.rb é.rb 1 +**/**/*.rb dir/é.rb 1 +**/**/*.rb éa.rb 1 +**/**/*.rb x.rake 0 +**/**/*.rb b/x.rb 1 +**/**/*.rb a/c.rb 1 +**/**/*.rb a/b/x/c.rb 1 +**/**/*.rb {a}.rb 1 +**/**/*.rb b.rb 1 +**/**/*.rb a 0 +**/**/*.rb b 0 +**/**/*.rb lib/a/b.rb 1 +**/**/*.rb test/lib/x.rb 1 +**/**/*.rb .a/.b/c.rb 1 +a/**/**/b.rb a.rb 0 +a/**/**/b.rb app/a.rb 0 +a/**/**/b.rb app/jobs/a_job.rb 0 +a/**/**/b.rb app/jobs/deep/x.rb 0 +a/**/**/b.rb lib/a.rb 0 +a/**/**/b.rb lib/b.rb 0 +a/**/**/b.rb lib/ab.rb 0 +a/**/**/b.rb lib/d.rb 0 +a/**/**/b.rb lib/-.rb 0 +a/**/**/b.rb lib/].rb 0 +a/**/**/b.rb lib/*.rb 0 +a/**/**/b.rb lib/x.rb 0 +a/**/**/b.rb lib/[.rb 0 +a/**/**/b.rb vendor/x.rb 0 +a/**/**/b.rb vendor/gem/lib/x.rb 0 +a/**/**/b.rb vendored/x.rb 0 +a/**/**/b.rb vendor 0 +a/**/**/b.rb test/a_test.rb 0 +a/**/**/b.rb test/unit/b_test.rb 0 +a/**/**/b.rb a_test.rb 0 +a/**/**/b.rb .github/workflows/ci.yml 0 +a/**/**/b.rb .hidden 0 +a/**/**/b.rb .lintus.yml 0 +a/**/**/b.rb config/a.yaml 0 +a/**/**/b.rb config/deep/b.yml 0 +a/**/**/b.rb Gemfile 0 +a/**/**/b.rb Gemfile.lock 0 +a/**/**/b.rb sub/Gemfile 0 +a/**/**/b.rb db/schema.rb 0 +a/**/**/b.rb db/migrate/1.rb 0 +a/**/**/b.rb exe/lintus 0 +a/**/**/b.rb a/b.rb 1 +a/**/**/b.rb a/x/b.rb 1 +a/**/**/b.rb a/x/y/b.rb 1 +a/**/**/b.rb a/b/c.rb 0 +a/**/**/b.rb a/x/b/y/c.rb 0 +a/**/**/b.rb abc.rb 0 +a/**/**/b.rb aXbYc.rb 0 +a/**/**/b.rb x.RB 0 +a/**/**/b.rb x.rb 0 +a/**/**/b.rb é.rb 0 +a/**/**/b.rb dir/é.rb 0 +a/**/**/b.rb éa.rb 0 +a/**/**/b.rb x.rake 0 +a/**/**/b.rb b/x.rb 0 +a/**/**/b.rb a/c.rb 0 +a/**/**/b.rb a/b/x/c.rb 0 +a/**/**/b.rb {a}.rb 0 +a/**/**/b.rb b.rb 0 +a/**/**/b.rb a 0 +a/**/**/b.rb b 0 +a/**/**/b.rb lib/a/b.rb 0 +a/**/**/b.rb test/lib/x.rb 0 +a/**/**/b.rb .a/.b/c.rb 0 +a/**/b/**/c.rb a.rb 0 +a/**/b/**/c.rb app/a.rb 0 +a/**/b/**/c.rb app/jobs/a_job.rb 0 +a/**/b/**/c.rb app/jobs/deep/x.rb 0 +a/**/b/**/c.rb lib/a.rb 0 +a/**/b/**/c.rb lib/b.rb 0 +a/**/b/**/c.rb lib/ab.rb 0 +a/**/b/**/c.rb lib/d.rb 0 +a/**/b/**/c.rb lib/-.rb 0 +a/**/b/**/c.rb lib/].rb 0 +a/**/b/**/c.rb lib/*.rb 0 +a/**/b/**/c.rb lib/x.rb 0 +a/**/b/**/c.rb lib/[.rb 0 +a/**/b/**/c.rb vendor/x.rb 0 +a/**/b/**/c.rb vendor/gem/lib/x.rb 0 +a/**/b/**/c.rb vendored/x.rb 0 +a/**/b/**/c.rb vendor 0 +a/**/b/**/c.rb test/a_test.rb 0 +a/**/b/**/c.rb test/unit/b_test.rb 0 +a/**/b/**/c.rb a_test.rb 0 +a/**/b/**/c.rb .github/workflows/ci.yml 0 +a/**/b/**/c.rb .hidden 0 +a/**/b/**/c.rb .lintus.yml 0 +a/**/b/**/c.rb config/a.yaml 0 +a/**/b/**/c.rb config/deep/b.yml 0 +a/**/b/**/c.rb Gemfile 0 +a/**/b/**/c.rb Gemfile.lock 0 +a/**/b/**/c.rb sub/Gemfile 0 +a/**/b/**/c.rb db/schema.rb 0 +a/**/b/**/c.rb db/migrate/1.rb 0 +a/**/b/**/c.rb exe/lintus 0 +a/**/b/**/c.rb a/b.rb 0 +a/**/b/**/c.rb a/x/b.rb 0 +a/**/b/**/c.rb a/x/y/b.rb 0 +a/**/b/**/c.rb a/b/c.rb 1 +a/**/b/**/c.rb a/x/b/y/c.rb 1 +a/**/b/**/c.rb abc.rb 0 +a/**/b/**/c.rb aXbYc.rb 0 +a/**/b/**/c.rb x.RB 0 +a/**/b/**/c.rb x.rb 0 +a/**/b/**/c.rb é.rb 0 +a/**/b/**/c.rb dir/é.rb 0 +a/**/b/**/c.rb éa.rb 0 +a/**/b/**/c.rb x.rake 0 +a/**/b/**/c.rb b/x.rb 0 +a/**/b/**/c.rb a/c.rb 0 +a/**/b/**/c.rb a/b/x/c.rb 1 +a/**/b/**/c.rb {a}.rb 0 +a/**/b/**/c.rb b.rb 0 +a/**/b/**/c.rb a 0 +a/**/b/**/c.rb b 0 +a/**/b/**/c.rb lib/a/b.rb 0 +a/**/b/**/c.rb test/lib/x.rb 0 +a/**/b/**/c.rb .a/.b/c.rb 0 +**/b a.rb 0 +**/b app/a.rb 0 +**/b app/jobs/a_job.rb 0 +**/b app/jobs/deep/x.rb 0 +**/b lib/a.rb 0 +**/b lib/b.rb 0 +**/b lib/ab.rb 0 +**/b lib/d.rb 0 +**/b lib/-.rb 0 +**/b lib/].rb 0 +**/b lib/*.rb 0 +**/b lib/x.rb 0 +**/b lib/[.rb 0 +**/b vendor/x.rb 0 +**/b vendor/gem/lib/x.rb 0 +**/b vendored/x.rb 0 +**/b vendor 0 +**/b test/a_test.rb 0 +**/b test/unit/b_test.rb 0 +**/b a_test.rb 0 +**/b .github/workflows/ci.yml 0 +**/b .hidden 0 +**/b .lintus.yml 0 +**/b config/a.yaml 0 +**/b config/deep/b.yml 0 +**/b Gemfile 0 +**/b Gemfile.lock 0 +**/b sub/Gemfile 0 +**/b db/schema.rb 0 +**/b db/migrate/1.rb 0 +**/b exe/lintus 0 +**/b a/b.rb 0 +**/b a/x/b.rb 0 +**/b a/x/y/b.rb 0 +**/b a/b/c.rb 0 +**/b a/x/b/y/c.rb 0 +**/b abc.rb 0 +**/b aXbYc.rb 0 +**/b x.RB 0 +**/b x.rb 0 +**/b é.rb 0 +**/b dir/é.rb 0 +**/b éa.rb 0 +**/b x.rake 0 +**/b b/x.rb 0 +**/b a/c.rb 0 +**/b a/b/x/c.rb 0 +**/b {a}.rb 0 +**/b b.rb 0 +**/b a 0 +**/b b 1 +**/b lib/a/b.rb 0 +**/b test/lib/x.rb 0 +**/b .a/.b/c.rb 0 +a*b*c.rb a.rb 0 +a*b*c.rb app/a.rb 0 +a*b*c.rb app/jobs/a_job.rb 0 +a*b*c.rb app/jobs/deep/x.rb 0 +a*b*c.rb lib/a.rb 0 +a*b*c.rb lib/b.rb 0 +a*b*c.rb lib/ab.rb 0 +a*b*c.rb lib/d.rb 0 +a*b*c.rb lib/-.rb 0 +a*b*c.rb lib/].rb 0 +a*b*c.rb lib/*.rb 0 +a*b*c.rb lib/x.rb 0 +a*b*c.rb lib/[.rb 0 +a*b*c.rb vendor/x.rb 0 +a*b*c.rb vendor/gem/lib/x.rb 0 +a*b*c.rb vendored/x.rb 0 +a*b*c.rb vendor 0 +a*b*c.rb test/a_test.rb 0 +a*b*c.rb test/unit/b_test.rb 0 +a*b*c.rb a_test.rb 0 +a*b*c.rb .github/workflows/ci.yml 0 +a*b*c.rb .hidden 0 +a*b*c.rb .lintus.yml 0 +a*b*c.rb config/a.yaml 0 +a*b*c.rb config/deep/b.yml 0 +a*b*c.rb Gemfile 0 +a*b*c.rb Gemfile.lock 0 +a*b*c.rb sub/Gemfile 0 +a*b*c.rb db/schema.rb 0 +a*b*c.rb db/migrate/1.rb 0 +a*b*c.rb exe/lintus 0 +a*b*c.rb a/b.rb 0 +a*b*c.rb a/x/b.rb 0 +a*b*c.rb a/x/y/b.rb 0 +a*b*c.rb a/b/c.rb 0 +a*b*c.rb a/x/b/y/c.rb 0 +a*b*c.rb abc.rb 1 +a*b*c.rb aXbYc.rb 1 +a*b*c.rb x.RB 0 +a*b*c.rb x.rb 0 +a*b*c.rb é.rb 0 +a*b*c.rb dir/é.rb 0 +a*b*c.rb éa.rb 0 +a*b*c.rb x.rake 0 +a*b*c.rb b/x.rb 0 +a*b*c.rb a/c.rb 0 +a*b*c.rb a/b/x/c.rb 0 +a*b*c.rb {a}.rb 0 +a*b*c.rb b.rb 0 +a*b*c.rb a 0 +a*b*c.rb b 0 +a*b*c.rb lib/a/b.rb 0 +a*b*c.rb test/lib/x.rb 0 +a*b*c.rb .a/.b/c.rb 0 +*a* a.rb 1 +*a* app/a.rb 0 +*a* app/jobs/a_job.rb 0 +*a* app/jobs/deep/x.rb 0 +*a* lib/a.rb 0 +*a* lib/b.rb 0 +*a* lib/ab.rb 0 +*a* lib/d.rb 0 +*a* lib/-.rb 0 +*a* lib/].rb 0 +*a* lib/*.rb 0 +*a* lib/x.rb 0 +*a* lib/[.rb 0 +*a* vendor/x.rb 0 +*a* vendor/gem/lib/x.rb 0 +*a* vendored/x.rb 0 +*a* vendor 0 +*a* test/a_test.rb 0 +*a* test/unit/b_test.rb 0 +*a* a_test.rb 1 +*a* .github/workflows/ci.yml 0 +*a* .hidden 0 +*a* .lintus.yml 0 +*a* config/a.yaml 0 +*a* config/deep/b.yml 0 +*a* Gemfile 0 +*a* Gemfile.lock 0 +*a* sub/Gemfile 0 +*a* db/schema.rb 0 +*a* db/migrate/1.rb 0 +*a* exe/lintus 0 +*a* a/b.rb 0 +*a* a/x/b.rb 0 +*a* a/x/y/b.rb 0 +*a* a/b/c.rb 0 +*a* a/x/b/y/c.rb 0 +*a* abc.rb 1 +*a* aXbYc.rb 1 +*a* x.RB 0 +*a* x.rb 0 +*a* é.rb 0 +*a* dir/é.rb 0 +*a* éa.rb 1 +*a* x.rake 1 +*a* b/x.rb 0 +*a* a/c.rb 0 +*a* a/b/x/c.rb 0 +*a* {a}.rb 1 +*a* b.rb 0 +*a* a 1 +*a* b 0 +*a* lib/a/b.rb 0 +*a* test/lib/x.rb 0 +*a* .a/.b/c.rb 0 +lib/\*.rb a.rb 0 +lib/\*.rb app/a.rb 0 +lib/\*.rb app/jobs/a_job.rb 0 +lib/\*.rb app/jobs/deep/x.rb 0 +lib/\*.rb lib/a.rb 0 +lib/\*.rb lib/b.rb 0 +lib/\*.rb lib/ab.rb 0 +lib/\*.rb lib/d.rb 0 +lib/\*.rb lib/-.rb 0 +lib/\*.rb lib/].rb 0 +lib/\*.rb lib/*.rb 1 +lib/\*.rb lib/x.rb 0 +lib/\*.rb lib/[.rb 0 +lib/\*.rb vendor/x.rb 0 +lib/\*.rb vendor/gem/lib/x.rb 0 +lib/\*.rb vendored/x.rb 0 +lib/\*.rb vendor 0 +lib/\*.rb test/a_test.rb 0 +lib/\*.rb test/unit/b_test.rb 0 +lib/\*.rb a_test.rb 0 +lib/\*.rb .github/workflows/ci.yml 0 +lib/\*.rb .hidden 0 +lib/\*.rb .lintus.yml 0 +lib/\*.rb config/a.yaml 0 +lib/\*.rb config/deep/b.yml 0 +lib/\*.rb Gemfile 0 +lib/\*.rb Gemfile.lock 0 +lib/\*.rb sub/Gemfile 0 +lib/\*.rb db/schema.rb 0 +lib/\*.rb db/migrate/1.rb 0 +lib/\*.rb exe/lintus 0 +lib/\*.rb a/b.rb 0 +lib/\*.rb a/x/b.rb 0 +lib/\*.rb a/x/y/b.rb 0 +lib/\*.rb a/b/c.rb 0 +lib/\*.rb a/x/b/y/c.rb 0 +lib/\*.rb abc.rb 0 +lib/\*.rb aXbYc.rb 0 +lib/\*.rb x.RB 0 +lib/\*.rb x.rb 0 +lib/\*.rb é.rb 0 +lib/\*.rb dir/é.rb 0 +lib/\*.rb éa.rb 0 +lib/\*.rb x.rake 0 +lib/\*.rb b/x.rb 0 +lib/\*.rb a/c.rb 0 +lib/\*.rb a/b/x/c.rb 0 +lib/\*.rb {a}.rb 0 +lib/\*.rb b.rb 0 +lib/\*.rb a 0 +lib/\*.rb b 0 +lib/\*.rb lib/a/b.rb 0 +lib/\*.rb test/lib/x.rb 0 +lib/\*.rb .a/.b/c.rb 0 +lib\/x.rb a.rb 0 +lib\/x.rb app/a.rb 0 +lib\/x.rb app/jobs/a_job.rb 0 +lib\/x.rb app/jobs/deep/x.rb 0 +lib\/x.rb lib/a.rb 0 +lib\/x.rb lib/b.rb 0 +lib\/x.rb lib/ab.rb 0 +lib\/x.rb lib/d.rb 0 +lib\/x.rb lib/-.rb 0 +lib\/x.rb lib/].rb 0 +lib\/x.rb lib/*.rb 0 +lib\/x.rb lib/x.rb 1 +lib\/x.rb lib/[.rb 0 +lib\/x.rb vendor/x.rb 0 +lib\/x.rb vendor/gem/lib/x.rb 0 +lib\/x.rb vendored/x.rb 0 +lib\/x.rb vendor 0 +lib\/x.rb test/a_test.rb 0 +lib\/x.rb test/unit/b_test.rb 0 +lib\/x.rb a_test.rb 0 +lib\/x.rb .github/workflows/ci.yml 0 +lib\/x.rb .hidden 0 +lib\/x.rb .lintus.yml 0 +lib\/x.rb config/a.yaml 0 +lib\/x.rb config/deep/b.yml 0 +lib\/x.rb Gemfile 0 +lib\/x.rb Gemfile.lock 0 +lib\/x.rb sub/Gemfile 0 +lib\/x.rb db/schema.rb 0 +lib\/x.rb db/migrate/1.rb 0 +lib\/x.rb exe/lintus 0 +lib\/x.rb a/b.rb 0 +lib\/x.rb a/x/b.rb 0 +lib\/x.rb a/x/y/b.rb 0 +lib\/x.rb a/b/c.rb 0 +lib\/x.rb a/x/b/y/c.rb 0 +lib\/x.rb abc.rb 0 +lib\/x.rb aXbYc.rb 0 +lib\/x.rb x.RB 0 +lib\/x.rb x.rb 0 +lib\/x.rb é.rb 0 +lib\/x.rb dir/é.rb 0 +lib\/x.rb éa.rb 0 +lib\/x.rb x.rake 0 +lib\/x.rb b/x.rb 0 +lib\/x.rb a/c.rb 0 +lib\/x.rb a/b/x/c.rb 0 +lib\/x.rb {a}.rb 0 +lib\/x.rb b.rb 0 +lib\/x.rb a 0 +lib\/x.rb b 0 +lib\/x.rb lib/a/b.rb 0 +lib\/x.rb test/lib/x.rb 0 +lib\/x.rb .a/.b/c.rb 0 +lib/\[.rb a.rb 0 +lib/\[.rb app/a.rb 0 +lib/\[.rb app/jobs/a_job.rb 0 +lib/\[.rb app/jobs/deep/x.rb 0 +lib/\[.rb lib/a.rb 0 +lib/\[.rb lib/b.rb 0 +lib/\[.rb lib/ab.rb 0 +lib/\[.rb lib/d.rb 0 +lib/\[.rb lib/-.rb 0 +lib/\[.rb lib/].rb 0 +lib/\[.rb lib/*.rb 0 +lib/\[.rb lib/x.rb 0 +lib/\[.rb lib/[.rb 1 +lib/\[.rb vendor/x.rb 0 +lib/\[.rb vendor/gem/lib/x.rb 0 +lib/\[.rb vendored/x.rb 0 +lib/\[.rb vendor 0 +lib/\[.rb test/a_test.rb 0 +lib/\[.rb test/unit/b_test.rb 0 +lib/\[.rb a_test.rb 0 +lib/\[.rb .github/workflows/ci.yml 0 +lib/\[.rb .hidden 0 +lib/\[.rb .lintus.yml 0 +lib/\[.rb config/a.yaml 0 +lib/\[.rb config/deep/b.yml 0 +lib/\[.rb Gemfile 0 +lib/\[.rb Gemfile.lock 0 +lib/\[.rb sub/Gemfile 0 +lib/\[.rb db/schema.rb 0 +lib/\[.rb db/migrate/1.rb 0 +lib/\[.rb exe/lintus 0 +lib/\[.rb a/b.rb 0 +lib/\[.rb a/x/b.rb 0 +lib/\[.rb a/x/y/b.rb 0 +lib/\[.rb a/b/c.rb 0 +lib/\[.rb a/x/b/y/c.rb 0 +lib/\[.rb abc.rb 0 +lib/\[.rb aXbYc.rb 0 +lib/\[.rb x.RB 0 +lib/\[.rb x.rb 0 +lib/\[.rb é.rb 0 +lib/\[.rb dir/é.rb 0 +lib/\[.rb éa.rb 0 +lib/\[.rb x.rake 0 +lib/\[.rb b/x.rb 0 +lib/\[.rb a/c.rb 0 +lib/\[.rb a/b/x/c.rb 0 +lib/\[.rb {a}.rb 0 +lib/\[.rb b.rb 0 +lib/\[.rb a 0 +lib/\[.rb b 0 +lib/\[.rb lib/a/b.rb 0 +lib/\[.rb test/lib/x.rb 0 +lib/\[.rb .a/.b/c.rb 0 +Gemfile a.rb 0 +Gemfile app/a.rb 0 +Gemfile app/jobs/a_job.rb 0 +Gemfile app/jobs/deep/x.rb 0 +Gemfile lib/a.rb 0 +Gemfile lib/b.rb 0 +Gemfile lib/ab.rb 0 +Gemfile lib/d.rb 0 +Gemfile lib/-.rb 0 +Gemfile lib/].rb 0 +Gemfile lib/*.rb 0 +Gemfile lib/x.rb 0 +Gemfile lib/[.rb 0 +Gemfile vendor/x.rb 0 +Gemfile vendor/gem/lib/x.rb 0 +Gemfile vendored/x.rb 0 +Gemfile vendor 0 +Gemfile test/a_test.rb 0 +Gemfile test/unit/b_test.rb 0 +Gemfile a_test.rb 0 +Gemfile .github/workflows/ci.yml 0 +Gemfile .hidden 0 +Gemfile .lintus.yml 0 +Gemfile config/a.yaml 0 +Gemfile config/deep/b.yml 0 +Gemfile Gemfile 1 +Gemfile Gemfile.lock 0 +Gemfile sub/Gemfile 0 +Gemfile db/schema.rb 0 +Gemfile db/migrate/1.rb 0 +Gemfile exe/lintus 0 +Gemfile a/b.rb 0 +Gemfile a/x/b.rb 0 +Gemfile a/x/y/b.rb 0 +Gemfile a/b/c.rb 0 +Gemfile a/x/b/y/c.rb 0 +Gemfile abc.rb 0 +Gemfile aXbYc.rb 0 +Gemfile x.RB 0 +Gemfile x.rb 0 +Gemfile é.rb 0 +Gemfile dir/é.rb 0 +Gemfile éa.rb 0 +Gemfile x.rake 0 +Gemfile b/x.rb 0 +Gemfile a/c.rb 0 +Gemfile a/b/x/c.rb 0 +Gemfile {a}.rb 0 +Gemfile b.rb 0 +Gemfile a 0 +Gemfile b 0 +Gemfile lib/a/b.rb 0 +Gemfile test/lib/x.rb 0 +Gemfile .a/.b/c.rb 0 +Gemfile* a.rb 0 +Gemfile* app/a.rb 0 +Gemfile* app/jobs/a_job.rb 0 +Gemfile* app/jobs/deep/x.rb 0 +Gemfile* lib/a.rb 0 +Gemfile* lib/b.rb 0 +Gemfile* lib/ab.rb 0 +Gemfile* lib/d.rb 0 +Gemfile* lib/-.rb 0 +Gemfile* lib/].rb 0 +Gemfile* lib/*.rb 0 +Gemfile* lib/x.rb 0 +Gemfile* lib/[.rb 0 +Gemfile* vendor/x.rb 0 +Gemfile* vendor/gem/lib/x.rb 0 +Gemfile* vendored/x.rb 0 +Gemfile* vendor 0 +Gemfile* test/a_test.rb 0 +Gemfile* test/unit/b_test.rb 0 +Gemfile* a_test.rb 0 +Gemfile* .github/workflows/ci.yml 0 +Gemfile* .hidden 0 +Gemfile* .lintus.yml 0 +Gemfile* config/a.yaml 0 +Gemfile* config/deep/b.yml 0 +Gemfile* Gemfile 1 +Gemfile* Gemfile.lock 1 +Gemfile* sub/Gemfile 0 +Gemfile* db/schema.rb 0 +Gemfile* db/migrate/1.rb 0 +Gemfile* exe/lintus 0 +Gemfile* a/b.rb 0 +Gemfile* a/x/b.rb 0 +Gemfile* a/x/y/b.rb 0 +Gemfile* a/b/c.rb 0 +Gemfile* a/x/b/y/c.rb 0 +Gemfile* abc.rb 0 +Gemfile* aXbYc.rb 0 +Gemfile* x.RB 0 +Gemfile* x.rb 0 +Gemfile* é.rb 0 +Gemfile* dir/é.rb 0 +Gemfile* éa.rb 0 +Gemfile* x.rake 0 +Gemfile* b/x.rb 0 +Gemfile* a/c.rb 0 +Gemfile* a/b/x/c.rb 0 +Gemfile* {a}.rb 0 +Gemfile* b.rb 0 +Gemfile* a 0 +Gemfile* b 0 +Gemfile* lib/a/b.rb 0 +Gemfile* test/lib/x.rb 0 +Gemfile* .a/.b/c.rb 0 +db/schema.rb a.rb 0 +db/schema.rb app/a.rb 0 +db/schema.rb app/jobs/a_job.rb 0 +db/schema.rb app/jobs/deep/x.rb 0 +db/schema.rb lib/a.rb 0 +db/schema.rb lib/b.rb 0 +db/schema.rb lib/ab.rb 0 +db/schema.rb lib/d.rb 0 +db/schema.rb lib/-.rb 0 +db/schema.rb lib/].rb 0 +db/schema.rb lib/*.rb 0 +db/schema.rb lib/x.rb 0 +db/schema.rb lib/[.rb 0 +db/schema.rb vendor/x.rb 0 +db/schema.rb vendor/gem/lib/x.rb 0 +db/schema.rb vendored/x.rb 0 +db/schema.rb vendor 0 +db/schema.rb test/a_test.rb 0 +db/schema.rb test/unit/b_test.rb 0 +db/schema.rb a_test.rb 0 +db/schema.rb .github/workflows/ci.yml 0 +db/schema.rb .hidden 0 +db/schema.rb .lintus.yml 0 +db/schema.rb config/a.yaml 0 +db/schema.rb config/deep/b.yml 0 +db/schema.rb Gemfile 0 +db/schema.rb Gemfile.lock 0 +db/schema.rb sub/Gemfile 0 +db/schema.rb db/schema.rb 1 +db/schema.rb db/migrate/1.rb 0 +db/schema.rb exe/lintus 0 +db/schema.rb a/b.rb 0 +db/schema.rb a/x/b.rb 0 +db/schema.rb a/x/y/b.rb 0 +db/schema.rb a/b/c.rb 0 +db/schema.rb a/x/b/y/c.rb 0 +db/schema.rb abc.rb 0 +db/schema.rb aXbYc.rb 0 +db/schema.rb x.RB 0 +db/schema.rb x.rb 0 +db/schema.rb é.rb 0 +db/schema.rb dir/é.rb 0 +db/schema.rb éa.rb 0 +db/schema.rb x.rake 0 +db/schema.rb b/x.rb 0 +db/schema.rb a/c.rb 0 +db/schema.rb a/b/x/c.rb 0 +db/schema.rb {a}.rb 0 +db/schema.rb b.rb 0 +db/schema.rb a 0 +db/schema.rb b 0 +db/schema.rb lib/a/b.rb 0 +db/schema.rb test/lib/x.rb 0 +db/schema.rb .a/.b/c.rb 0 +db a.rb 0 +db app/a.rb 0 +db app/jobs/a_job.rb 0 +db app/jobs/deep/x.rb 0 +db lib/a.rb 0 +db lib/b.rb 0 +db lib/ab.rb 0 +db lib/d.rb 0 +db lib/-.rb 0 +db lib/].rb 0 +db lib/*.rb 0 +db lib/x.rb 0 +db lib/[.rb 0 +db vendor/x.rb 0 +db vendor/gem/lib/x.rb 0 +db vendored/x.rb 0 +db vendor 0 +db test/a_test.rb 0 +db test/unit/b_test.rb 0 +db a_test.rb 0 +db .github/workflows/ci.yml 0 +db .hidden 0 +db .lintus.yml 0 +db config/a.yaml 0 +db config/deep/b.yml 0 +db Gemfile 0 +db Gemfile.lock 0 +db sub/Gemfile 0 +db db/schema.rb 1 +db db/migrate/1.rb 1 +db exe/lintus 0 +db a/b.rb 0 +db a/x/b.rb 0 +db a/x/y/b.rb 0 +db a/b/c.rb 0 +db a/x/b/y/c.rb 0 +db abc.rb 0 +db aXbYc.rb 0 +db x.RB 0 +db x.rb 0 +db é.rb 0 +db dir/é.rb 0 +db éa.rb 0 +db x.rake 0 +db b/x.rb 0 +db a/c.rb 0 +db a/b/x/c.rb 0 +db {a}.rb 0 +db b.rb 0 +db a 0 +db b 0 +db lib/a/b.rb 0 +db test/lib/x.rb 0 +db .a/.b/c.rb 0 +db/ a.rb 0 +db/ app/a.rb 0 +db/ app/jobs/a_job.rb 0 +db/ app/jobs/deep/x.rb 0 +db/ lib/a.rb 0 +db/ lib/b.rb 0 +db/ lib/ab.rb 0 +db/ lib/d.rb 0 +db/ lib/-.rb 0 +db/ lib/].rb 0 +db/ lib/*.rb 0 +db/ lib/x.rb 0 +db/ lib/[.rb 0 +db/ vendor/x.rb 0 +db/ vendor/gem/lib/x.rb 0 +db/ vendored/x.rb 0 +db/ vendor 0 +db/ test/a_test.rb 0 +db/ test/unit/b_test.rb 0 +db/ a_test.rb 0 +db/ .github/workflows/ci.yml 0 +db/ .hidden 0 +db/ .lintus.yml 0 +db/ config/a.yaml 0 +db/ config/deep/b.yml 0 +db/ Gemfile 0 +db/ Gemfile.lock 0 +db/ sub/Gemfile 0 +db/ db/schema.rb 1 +db/ db/migrate/1.rb 1 +db/ exe/lintus 0 +db/ a/b.rb 0 +db/ a/x/b.rb 0 +db/ a/x/y/b.rb 0 +db/ a/b/c.rb 0 +db/ a/x/b/y/c.rb 0 +db/ abc.rb 0 +db/ aXbYc.rb 0 +db/ x.RB 0 +db/ x.rb 0 +db/ é.rb 0 +db/ dir/é.rb 0 +db/ éa.rb 0 +db/ x.rake 0 +db/ b/x.rb 0 +db/ a/c.rb 0 +db/ a/b/x/c.rb 0 +db/ {a}.rb 0 +db/ b.rb 0 +db/ a 0 +db/ b 0 +db/ lib/a/b.rb 0 +db/ test/lib/x.rb 0 +db/ .a/.b/c.rb 0 +exe/* a.rb 0 +exe/* app/a.rb 0 +exe/* app/jobs/a_job.rb 0 +exe/* app/jobs/deep/x.rb 0 +exe/* lib/a.rb 0 +exe/* lib/b.rb 0 +exe/* lib/ab.rb 0 +exe/* lib/d.rb 0 +exe/* lib/-.rb 0 +exe/* lib/].rb 0 +exe/* lib/*.rb 0 +exe/* lib/x.rb 0 +exe/* lib/[.rb 0 +exe/* vendor/x.rb 0 +exe/* vendor/gem/lib/x.rb 0 +exe/* vendored/x.rb 0 +exe/* vendor 0 +exe/* test/a_test.rb 0 +exe/* test/unit/b_test.rb 0 +exe/* a_test.rb 0 +exe/* .github/workflows/ci.yml 0 +exe/* .hidden 0 +exe/* .lintus.yml 0 +exe/* config/a.yaml 0 +exe/* config/deep/b.yml 0 +exe/* Gemfile 0 +exe/* Gemfile.lock 0 +exe/* sub/Gemfile 0 +exe/* db/schema.rb 0 +exe/* db/migrate/1.rb 0 +exe/* exe/lintus 1 +exe/* a/b.rb 0 +exe/* a/x/b.rb 0 +exe/* a/x/y/b.rb 0 +exe/* a/b/c.rb 0 +exe/* a/x/b/y/c.rb 0 +exe/* abc.rb 0 +exe/* aXbYc.rb 0 +exe/* x.RB 0 +exe/* x.rb 0 +exe/* é.rb 0 +exe/* dir/é.rb 0 +exe/* éa.rb 0 +exe/* x.rake 0 +exe/* b/x.rb 0 +exe/* a/c.rb 0 +exe/* a/b/x/c.rb 0 +exe/* {a}.rb 0 +exe/* b.rb 0 +exe/* a 0 +exe/* b 0 +exe/* lib/a/b.rb 0 +exe/* test/lib/x.rb 0 +exe/* .a/.b/c.rb 0 +*/ a.rb 1 +*/ app/a.rb 0 +*/ app/jobs/a_job.rb 0 +*/ app/jobs/deep/x.rb 0 +*/ lib/a.rb 0 +*/ lib/b.rb 0 +*/ lib/ab.rb 0 +*/ lib/d.rb 0 +*/ lib/-.rb 0 +*/ lib/].rb 0 +*/ lib/*.rb 0 +*/ lib/x.rb 0 +*/ lib/[.rb 0 +*/ vendor/x.rb 0 +*/ vendor/gem/lib/x.rb 0 +*/ vendored/x.rb 0 +*/ vendor 1 +*/ test/a_test.rb 0 +*/ test/unit/b_test.rb 0 +*/ a_test.rb 1 +*/ .github/workflows/ci.yml 0 +*/ .hidden 1 +*/ .lintus.yml 1 +*/ config/a.yaml 0 +*/ config/deep/b.yml 0 +*/ Gemfile 1 +*/ Gemfile.lock 1 +*/ sub/Gemfile 0 +*/ db/schema.rb 0 +*/ db/migrate/1.rb 0 +*/ exe/lintus 0 +*/ a/b.rb 0 +*/ a/x/b.rb 0 +*/ a/x/y/b.rb 0 +*/ a/b/c.rb 0 +*/ a/x/b/y/c.rb 0 +*/ abc.rb 1 +*/ aXbYc.rb 1 +*/ x.RB 1 +*/ x.rb 1 +*/ é.rb 1 +*/ dir/é.rb 0 +*/ éa.rb 1 +*/ x.rake 1 +*/ b/x.rb 0 +*/ a/c.rb 0 +*/ a/b/x/c.rb 0 +*/ {a}.rb 1 +*/ b.rb 1 +*/ a 1 +*/ b 1 +*/ lib/a/b.rb 0 +*/ test/lib/x.rb 0 +*/ .a/.b/c.rb 0 +a/**/*/c.rb a.rb 0 +a/**/*/c.rb app/a.rb 0 +a/**/*/c.rb app/jobs/a_job.rb 0 +a/**/*/c.rb app/jobs/deep/x.rb 0 +a/**/*/c.rb lib/a.rb 0 +a/**/*/c.rb lib/b.rb 0 +a/**/*/c.rb lib/ab.rb 0 +a/**/*/c.rb lib/d.rb 0 +a/**/*/c.rb lib/-.rb 0 +a/**/*/c.rb lib/].rb 0 +a/**/*/c.rb lib/*.rb 0 +a/**/*/c.rb lib/x.rb 0 +a/**/*/c.rb lib/[.rb 0 +a/**/*/c.rb vendor/x.rb 0 +a/**/*/c.rb vendor/gem/lib/x.rb 0 +a/**/*/c.rb vendored/x.rb 0 +a/**/*/c.rb vendor 0 +a/**/*/c.rb test/a_test.rb 0 +a/**/*/c.rb test/unit/b_test.rb 0 +a/**/*/c.rb a_test.rb 0 +a/**/*/c.rb .github/workflows/ci.yml 0 +a/**/*/c.rb .hidden 0 +a/**/*/c.rb .lintus.yml 0 +a/**/*/c.rb config/a.yaml 0 +a/**/*/c.rb config/deep/b.yml 0 +a/**/*/c.rb Gemfile 0 +a/**/*/c.rb Gemfile.lock 0 +a/**/*/c.rb sub/Gemfile 0 +a/**/*/c.rb db/schema.rb 0 +a/**/*/c.rb db/migrate/1.rb 0 +a/**/*/c.rb exe/lintus 0 +a/**/*/c.rb a/b.rb 0 +a/**/*/c.rb a/x/b.rb 0 +a/**/*/c.rb a/x/y/b.rb 0 +a/**/*/c.rb a/b/c.rb 1 +a/**/*/c.rb a/x/b/y/c.rb 1 +a/**/*/c.rb abc.rb 0 +a/**/*/c.rb aXbYc.rb 0 +a/**/*/c.rb x.RB 0 +a/**/*/c.rb x.rb 0 +a/**/*/c.rb é.rb 0 +a/**/*/c.rb dir/é.rb 0 +a/**/*/c.rb éa.rb 0 +a/**/*/c.rb x.rake 0 +a/**/*/c.rb b/x.rb 0 +a/**/*/c.rb a/c.rb 0 +a/**/*/c.rb a/b/x/c.rb 1 +a/**/*/c.rb {a}.rb 0 +a/**/*/c.rb b.rb 0 +a/**/*/c.rb a 0 +a/**/*/c.rb b 0 +a/**/*/c.rb lib/a/b.rb 0 +a/**/*/c.rb test/lib/x.rb 0 +a/**/*/c.rb .a/.b/c.rb 0 +**/*/ a.rb 1 +**/*/ app/a.rb 1 +**/*/ app/jobs/a_job.rb 1 +**/*/ app/jobs/deep/x.rb 1 +**/*/ lib/a.rb 1 +**/*/ lib/b.rb 1 +**/*/ lib/ab.rb 1 +**/*/ lib/d.rb 1 +**/*/ lib/-.rb 1 +**/*/ lib/].rb 1 +**/*/ lib/*.rb 1 +**/*/ lib/x.rb 1 +**/*/ lib/[.rb 1 +**/*/ vendor/x.rb 1 +**/*/ vendor/gem/lib/x.rb 1 +**/*/ vendored/x.rb 1 +**/*/ vendor 1 +**/*/ test/a_test.rb 1 +**/*/ test/unit/b_test.rb 1 +**/*/ a_test.rb 1 +**/*/ .github/workflows/ci.yml 1 +**/*/ .hidden 1 +**/*/ .lintus.yml 1 +**/*/ config/a.yaml 1 +**/*/ config/deep/b.yml 1 +**/*/ Gemfile 1 +**/*/ Gemfile.lock 1 +**/*/ sub/Gemfile 1 +**/*/ db/schema.rb 1 +**/*/ db/migrate/1.rb 1 +**/*/ exe/lintus 1 +**/*/ a/b.rb 1 +**/*/ a/x/b.rb 1 +**/*/ a/x/y/b.rb 1 +**/*/ a/b/c.rb 1 +**/*/ a/x/b/y/c.rb 1 +**/*/ abc.rb 1 +**/*/ aXbYc.rb 1 +**/*/ x.RB 1 +**/*/ x.rb 1 +**/*/ é.rb 1 +**/*/ dir/é.rb 1 +**/*/ éa.rb 1 +**/*/ x.rake 1 +**/*/ b/x.rb 1 +**/*/ a/c.rb 1 +**/*/ a/b/x/c.rb 1 +**/*/ {a}.rb 1 +**/*/ b.rb 1 +**/*/ a 1 +**/*/ b 1 +**/*/ lib/a/b.rb 1 +**/*/ test/lib/x.rb 1 +**/*/ .a/.b/c.rb 1 +lib/x.rb/ a.rb 0 +lib/x.rb/ app/a.rb 0 +lib/x.rb/ app/jobs/a_job.rb 0 +lib/x.rb/ app/jobs/deep/x.rb 0 +lib/x.rb/ lib/a.rb 0 +lib/x.rb/ lib/b.rb 0 +lib/x.rb/ lib/ab.rb 0 +lib/x.rb/ lib/d.rb 0 +lib/x.rb/ lib/-.rb 0 +lib/x.rb/ lib/].rb 0 +lib/x.rb/ lib/*.rb 0 +lib/x.rb/ lib/x.rb 1 +lib/x.rb/ lib/[.rb 0 +lib/x.rb/ vendor/x.rb 0 +lib/x.rb/ vendor/gem/lib/x.rb 0 +lib/x.rb/ vendored/x.rb 0 +lib/x.rb/ vendor 0 +lib/x.rb/ test/a_test.rb 0 +lib/x.rb/ test/unit/b_test.rb 0 +lib/x.rb/ a_test.rb 0 +lib/x.rb/ .github/workflows/ci.yml 0 +lib/x.rb/ .hidden 0 +lib/x.rb/ .lintus.yml 0 +lib/x.rb/ config/a.yaml 0 +lib/x.rb/ config/deep/b.yml 0 +lib/x.rb/ Gemfile 0 +lib/x.rb/ Gemfile.lock 0 +lib/x.rb/ sub/Gemfile 0 +lib/x.rb/ db/schema.rb 0 +lib/x.rb/ db/migrate/1.rb 0 +lib/x.rb/ exe/lintus 0 +lib/x.rb/ a/b.rb 0 +lib/x.rb/ a/x/b.rb 0 +lib/x.rb/ a/x/y/b.rb 0 +lib/x.rb/ a/b/c.rb 0 +lib/x.rb/ a/x/b/y/c.rb 0 +lib/x.rb/ abc.rb 0 +lib/x.rb/ aXbYc.rb 0 +lib/x.rb/ x.RB 0 +lib/x.rb/ x.rb 0 +lib/x.rb/ é.rb 0 +lib/x.rb/ dir/é.rb 0 +lib/x.rb/ éa.rb 0 +lib/x.rb/ x.rake 0 +lib/x.rb/ b/x.rb 0 +lib/x.rb/ a/c.rb 0 +lib/x.rb/ a/b/x/c.rb 0 +lib/x.rb/ {a}.rb 0 +lib/x.rb/ b.rb 0 +lib/x.rb/ a 0 +lib/x.rb/ b 0 +lib/x.rb/ lib/a/b.rb 0 +lib/x.rb/ test/lib/x.rb 0 +lib/x.rb/ .a/.b/c.rb 0 +*.RB a.rb 0 +*.RB app/a.rb 0 +*.RB app/jobs/a_job.rb 0 +*.RB app/jobs/deep/x.rb 0 +*.RB lib/a.rb 0 +*.RB lib/b.rb 0 +*.RB lib/ab.rb 0 +*.RB lib/d.rb 0 +*.RB lib/-.rb 0 +*.RB lib/].rb 0 +*.RB lib/*.rb 0 +*.RB lib/x.rb 0 +*.RB lib/[.rb 0 +*.RB vendor/x.rb 0 +*.RB vendor/gem/lib/x.rb 0 +*.RB vendored/x.rb 0 +*.RB vendor 0 +*.RB test/a_test.rb 0 +*.RB test/unit/b_test.rb 0 +*.RB a_test.rb 0 +*.RB .github/workflows/ci.yml 0 +*.RB .hidden 0 +*.RB .lintus.yml 0 +*.RB config/a.yaml 0 +*.RB config/deep/b.yml 0 +*.RB Gemfile 0 +*.RB Gemfile.lock 0 +*.RB sub/Gemfile 0 +*.RB db/schema.rb 0 +*.RB db/migrate/1.rb 0 +*.RB exe/lintus 0 +*.RB a/b.rb 0 +*.RB a/x/b.rb 0 +*.RB a/x/y/b.rb 0 +*.RB a/b/c.rb 0 +*.RB a/x/b/y/c.rb 0 +*.RB abc.rb 0 +*.RB aXbYc.rb 0 +*.RB x.RB 1 +*.RB x.rb 0 +*.RB é.rb 0 +*.RB dir/é.rb 0 +*.RB éa.rb 0 +*.RB x.rake 0 +*.RB b/x.rb 0 +*.RB a/c.rb 0 +*.RB a/b/x/c.rb 0 +*.RB {a}.rb 0 +*.RB b.rb 0 +*.RB a 0 +*.RB b 0 +*.RB lib/a/b.rb 0 +*.RB test/lib/x.rb 0 +*.RB .a/.b/c.rb 0 +é*.rb a.rb 0 +é*.rb app/a.rb 0 +é*.rb app/jobs/a_job.rb 0 +é*.rb app/jobs/deep/x.rb 0 +é*.rb lib/a.rb 0 +é*.rb lib/b.rb 0 +é*.rb lib/ab.rb 0 +é*.rb lib/d.rb 0 +é*.rb lib/-.rb 0 +é*.rb lib/].rb 0 +é*.rb lib/*.rb 0 +é*.rb lib/x.rb 0 +é*.rb lib/[.rb 0 +é*.rb vendor/x.rb 0 +é*.rb vendor/gem/lib/x.rb 0 +é*.rb vendored/x.rb 0 +é*.rb vendor 0 +é*.rb test/a_test.rb 0 +é*.rb test/unit/b_test.rb 0 +é*.rb a_test.rb 0 +é*.rb .github/workflows/ci.yml 0 +é*.rb .hidden 0 +é*.rb .lintus.yml 0 +é*.rb config/a.yaml 0 +é*.rb config/deep/b.yml 0 +é*.rb Gemfile 0 +é*.rb Gemfile.lock 0 +é*.rb sub/Gemfile 0 +é*.rb db/schema.rb 0 +é*.rb db/migrate/1.rb 0 +é*.rb exe/lintus 0 +é*.rb a/b.rb 0 +é*.rb a/x/b.rb 0 +é*.rb a/x/y/b.rb 0 +é*.rb a/b/c.rb 0 +é*.rb a/x/b/y/c.rb 0 +é*.rb abc.rb 0 +é*.rb aXbYc.rb 0 +é*.rb x.RB 0 +é*.rb x.rb 0 +é*.rb é.rb 1 +é*.rb dir/é.rb 0 +é*.rb éa.rb 1 +é*.rb x.rake 0 +é*.rb b/x.rb 0 +é*.rb a/c.rb 0 +é*.rb a/b/x/c.rb 0 +é*.rb {a}.rb 0 +é*.rb b.rb 0 +é*.rb a 0 +é*.rb b 0 +é*.rb lib/a/b.rb 0 +é*.rb test/lib/x.rb 0 +é*.rb .a/.b/c.rb 0 +**/é.rb a.rb 0 +**/é.rb app/a.rb 0 +**/é.rb app/jobs/a_job.rb 0 +**/é.rb app/jobs/deep/x.rb 0 +**/é.rb lib/a.rb 0 +**/é.rb lib/b.rb 0 +**/é.rb lib/ab.rb 0 +**/é.rb lib/d.rb 0 +**/é.rb lib/-.rb 0 +**/é.rb lib/].rb 0 +**/é.rb lib/*.rb 0 +**/é.rb lib/x.rb 0 +**/é.rb lib/[.rb 0 +**/é.rb vendor/x.rb 0 +**/é.rb vendor/gem/lib/x.rb 0 +**/é.rb vendored/x.rb 0 +**/é.rb vendor 0 +**/é.rb test/a_test.rb 0 +**/é.rb test/unit/b_test.rb 0 +**/é.rb a_test.rb 0 +**/é.rb .github/workflows/ci.yml 0 +**/é.rb .hidden 0 +**/é.rb .lintus.yml 0 +**/é.rb config/a.yaml 0 +**/é.rb config/deep/b.yml 0 +**/é.rb Gemfile 0 +**/é.rb Gemfile.lock 0 +**/é.rb sub/Gemfile 0 +**/é.rb db/schema.rb 0 +**/é.rb db/migrate/1.rb 0 +**/é.rb exe/lintus 0 +**/é.rb a/b.rb 0 +**/é.rb a/x/b.rb 0 +**/é.rb a/x/y/b.rb 0 +**/é.rb a/b/c.rb 0 +**/é.rb a/x/b/y/c.rb 0 +**/é.rb abc.rb 0 +**/é.rb aXbYc.rb 0 +**/é.rb x.RB 0 +**/é.rb x.rb 0 +**/é.rb é.rb 1 +**/é.rb dir/é.rb 1 +**/é.rb éa.rb 0 +**/é.rb x.rake 0 +**/é.rb b/x.rb 0 +**/é.rb a/c.rb 0 +**/é.rb a/b/x/c.rb 0 +**/é.rb {a}.rb 0 +**/é.rb b.rb 0 +**/é.rb a 0 +**/é.rb b 0 +**/é.rb lib/a/b.rb 0 +**/é.rb test/lib/x.rb 0 +**/é.rb .a/.b/c.rb 0 +[é]*.rb a.rb 0 +[é]*.rb app/a.rb 0 +[é]*.rb app/jobs/a_job.rb 0 +[é]*.rb app/jobs/deep/x.rb 0 +[é]*.rb lib/a.rb 0 +[é]*.rb lib/b.rb 0 +[é]*.rb lib/ab.rb 0 +[é]*.rb lib/d.rb 0 +[é]*.rb lib/-.rb 0 +[é]*.rb lib/].rb 0 +[é]*.rb lib/*.rb 0 +[é]*.rb lib/x.rb 0 +[é]*.rb lib/[.rb 0 +[é]*.rb vendor/x.rb 0 +[é]*.rb vendor/gem/lib/x.rb 0 +[é]*.rb vendored/x.rb 0 +[é]*.rb vendor 0 +[é]*.rb test/a_test.rb 0 +[é]*.rb test/unit/b_test.rb 0 +[é]*.rb a_test.rb 0 +[é]*.rb .github/workflows/ci.yml 0 +[é]*.rb .hidden 0 +[é]*.rb .lintus.yml 0 +[é]*.rb config/a.yaml 0 +[é]*.rb config/deep/b.yml 0 +[é]*.rb Gemfile 0 +[é]*.rb Gemfile.lock 0 +[é]*.rb sub/Gemfile 0 +[é]*.rb db/schema.rb 0 +[é]*.rb db/migrate/1.rb 0 +[é]*.rb exe/lintus 0 +[é]*.rb a/b.rb 0 +[é]*.rb a/x/b.rb 0 +[é]*.rb a/x/y/b.rb 0 +[é]*.rb a/b/c.rb 0 +[é]*.rb a/x/b/y/c.rb 0 +[é]*.rb abc.rb 0 +[é]*.rb aXbYc.rb 0 +[é]*.rb x.RB 0 +[é]*.rb x.rb 0 +[é]*.rb é.rb 1 +[é]*.rb dir/é.rb 0 +[é]*.rb éa.rb 1 +[é]*.rb x.rake 0 +[é]*.rb b/x.rb 0 +[é]*.rb a/c.rb 0 +[é]*.rb a/b/x/c.rb 0 +[é]*.rb {a}.rb 0 +[é]*.rb b.rb 0 +[é]*.rb a 0 +[é]*.rb b 0 +[é]*.rb lib/a/b.rb 0 +[é]*.rb test/lib/x.rb 0 +[é]*.rb .a/.b/c.rb 0 +a/*/c.rb a.rb 0 +a/*/c.rb app/a.rb 0 +a/*/c.rb app/jobs/a_job.rb 0 +a/*/c.rb app/jobs/deep/x.rb 0 +a/*/c.rb lib/a.rb 0 +a/*/c.rb lib/b.rb 0 +a/*/c.rb lib/ab.rb 0 +a/*/c.rb lib/d.rb 0 +a/*/c.rb lib/-.rb 0 +a/*/c.rb lib/].rb 0 +a/*/c.rb lib/*.rb 0 +a/*/c.rb lib/x.rb 0 +a/*/c.rb lib/[.rb 0 +a/*/c.rb vendor/x.rb 0 +a/*/c.rb vendor/gem/lib/x.rb 0 +a/*/c.rb vendored/x.rb 0 +a/*/c.rb vendor 0 +a/*/c.rb test/a_test.rb 0 +a/*/c.rb test/unit/b_test.rb 0 +a/*/c.rb a_test.rb 0 +a/*/c.rb .github/workflows/ci.yml 0 +a/*/c.rb .hidden 0 +a/*/c.rb .lintus.yml 0 +a/*/c.rb config/a.yaml 0 +a/*/c.rb config/deep/b.yml 0 +a/*/c.rb Gemfile 0 +a/*/c.rb Gemfile.lock 0 +a/*/c.rb sub/Gemfile 0 +a/*/c.rb db/schema.rb 0 +a/*/c.rb db/migrate/1.rb 0 +a/*/c.rb exe/lintus 0 +a/*/c.rb a/b.rb 0 +a/*/c.rb a/x/b.rb 0 +a/*/c.rb a/x/y/b.rb 0 +a/*/c.rb a/b/c.rb 1 +a/*/c.rb a/x/b/y/c.rb 0 +a/*/c.rb abc.rb 0 +a/*/c.rb aXbYc.rb 0 +a/*/c.rb x.RB 0 +a/*/c.rb x.rb 0 +a/*/c.rb é.rb 0 +a/*/c.rb dir/é.rb 0 +a/*/c.rb éa.rb 0 +a/*/c.rb x.rake 0 +a/*/c.rb b/x.rb 0 +a/*/c.rb a/c.rb 0 +a/*/c.rb a/b/x/c.rb 0 +a/*/c.rb {a}.rb 0 +a/*/c.rb b.rb 0 +a/*/c.rb a 0 +a/*/c.rb b 0 +a/*/c.rb lib/a/b.rb 0 +a/*/c.rb test/lib/x.rb 0 +a/*/c.rb .a/.b/c.rb 0 +** a.rb 1 +** app/a.rb 1 +** app/jobs/a_job.rb 1 +** app/jobs/deep/x.rb 1 +** lib/a.rb 1 +** lib/b.rb 1 +** lib/ab.rb 1 +** lib/d.rb 1 +** lib/-.rb 1 +** lib/].rb 1 +** lib/*.rb 1 +** lib/x.rb 1 +** lib/[.rb 1 +** vendor/x.rb 1 +** vendor/gem/lib/x.rb 1 +** vendored/x.rb 1 +** vendor 1 +** test/a_test.rb 1 +** test/unit/b_test.rb 1 +** a_test.rb 1 +** .github/workflows/ci.yml 1 +** .hidden 1 +** .lintus.yml 1 +** config/a.yaml 1 +** config/deep/b.yml 1 +** Gemfile 1 +** Gemfile.lock 1 +** sub/Gemfile 1 +** db/schema.rb 1 +** db/migrate/1.rb 1 +** exe/lintus 1 +** a/b.rb 1 +** a/x/b.rb 1 +** a/x/y/b.rb 1 +** a/b/c.rb 1 +** a/x/b/y/c.rb 1 +** abc.rb 1 +** aXbYc.rb 1 +** x.RB 1 +** x.rb 1 +** é.rb 1 +** dir/é.rb 1 +** éa.rb 1 +** x.rake 1 +** b/x.rb 1 +** a/c.rb 1 +** a/b/x/c.rb 1 +** {a}.rb 1 +** b.rb 1 +** a 1 +** b 1 +** lib/a/b.rb 1 +** test/lib/x.rb 1 +** .a/.b/c.rb 1 +**/ a.rb 1 +**/ app/a.rb 1 +**/ app/jobs/a_job.rb 1 +**/ app/jobs/deep/x.rb 1 +**/ lib/a.rb 1 +**/ lib/b.rb 1 +**/ lib/ab.rb 1 +**/ lib/d.rb 1 +**/ lib/-.rb 1 +**/ lib/].rb 1 +**/ lib/*.rb 1 +**/ lib/x.rb 1 +**/ lib/[.rb 1 +**/ vendor/x.rb 1 +**/ vendor/gem/lib/x.rb 1 +**/ vendored/x.rb 1 +**/ vendor 1 +**/ test/a_test.rb 1 +**/ test/unit/b_test.rb 1 +**/ a_test.rb 1 +**/ .github/workflows/ci.yml 1 +**/ .hidden 1 +**/ .lintus.yml 1 +**/ config/a.yaml 1 +**/ config/deep/b.yml 1 +**/ Gemfile 1 +**/ Gemfile.lock 1 +**/ sub/Gemfile 1 +**/ db/schema.rb 1 +**/ db/migrate/1.rb 1 +**/ exe/lintus 1 +**/ a/b.rb 1 +**/ a/x/b.rb 1 +**/ a/x/y/b.rb 1 +**/ a/b/c.rb 1 +**/ a/x/b/y/c.rb 1 +**/ abc.rb 1 +**/ aXbYc.rb 1 +**/ x.RB 1 +**/ x.rb 1 +**/ é.rb 1 +**/ dir/é.rb 1 +**/ éa.rb 1 +**/ x.rake 1 +**/ b/x.rb 1 +**/ a/c.rb 1 +**/ a/b/x/c.rb 1 +**/ {a}.rb 1 +**/ b.rb 1 +**/ a 1 +**/ b 1 +**/ lib/a/b.rb 1 +**/ test/lib/x.rb 1 +**/ .a/.b/c.rb 1 +a/** a.rb 0 +a/** app/a.rb 0 +a/** app/jobs/a_job.rb 0 +a/** app/jobs/deep/x.rb 0 +a/** lib/a.rb 0 +a/** lib/b.rb 0 +a/** lib/ab.rb 0 +a/** lib/d.rb 0 +a/** lib/-.rb 0 +a/** lib/].rb 0 +a/** lib/*.rb 0 +a/** lib/x.rb 0 +a/** lib/[.rb 0 +a/** vendor/x.rb 0 +a/** vendor/gem/lib/x.rb 0 +a/** vendored/x.rb 0 +a/** vendor 0 +a/** test/a_test.rb 0 +a/** test/unit/b_test.rb 0 +a/** a_test.rb 0 +a/** .github/workflows/ci.yml 0 +a/** .hidden 0 +a/** .lintus.yml 0 +a/** config/a.yaml 0 +a/** config/deep/b.yml 0 +a/** Gemfile 0 +a/** Gemfile.lock 0 +a/** sub/Gemfile 0 +a/** db/schema.rb 0 +a/** db/migrate/1.rb 0 +a/** exe/lintus 0 +a/** a/b.rb 1 +a/** a/x/b.rb 1 +a/** a/x/y/b.rb 1 +a/** a/b/c.rb 1 +a/** a/x/b/y/c.rb 1 +a/** abc.rb 0 +a/** aXbYc.rb 0 +a/** x.RB 0 +a/** x.rb 0 +a/** é.rb 0 +a/** dir/é.rb 0 +a/** éa.rb 0 +a/** x.rake 0 +a/** b/x.rb 0 +a/** a/c.rb 1 +a/** a/b/x/c.rb 1 +a/** {a}.rb 0 +a/** b.rb 0 +a/** a 0 +a/** b 0 +a/** lib/a/b.rb 0 +a/** test/lib/x.rb 0 +a/** .a/.b/c.rb 0 +*/*.rb a.rb 0 +*/*.rb app/a.rb 1 +*/*.rb app/jobs/a_job.rb 0 +*/*.rb app/jobs/deep/x.rb 0 +*/*.rb lib/a.rb 1 +*/*.rb lib/b.rb 1 +*/*.rb lib/ab.rb 1 +*/*.rb lib/d.rb 1 +*/*.rb lib/-.rb 1 +*/*.rb lib/].rb 1 +*/*.rb lib/*.rb 1 +*/*.rb lib/x.rb 1 +*/*.rb lib/[.rb 1 +*/*.rb vendor/x.rb 1 +*/*.rb vendor/gem/lib/x.rb 0 +*/*.rb vendored/x.rb 1 +*/*.rb vendor 0 +*/*.rb test/a_test.rb 1 +*/*.rb test/unit/b_test.rb 0 +*/*.rb a_test.rb 0 +*/*.rb .github/workflows/ci.yml 0 +*/*.rb .hidden 0 +*/*.rb .lintus.yml 0 +*/*.rb config/a.yaml 0 +*/*.rb config/deep/b.yml 0 +*/*.rb Gemfile 0 +*/*.rb Gemfile.lock 0 +*/*.rb sub/Gemfile 0 +*/*.rb db/schema.rb 1 +*/*.rb db/migrate/1.rb 0 +*/*.rb exe/lintus 0 +*/*.rb a/b.rb 1 +*/*.rb a/x/b.rb 0 +*/*.rb a/x/y/b.rb 0 +*/*.rb a/b/c.rb 0 +*/*.rb a/x/b/y/c.rb 0 +*/*.rb abc.rb 0 +*/*.rb aXbYc.rb 0 +*/*.rb x.RB 0 +*/*.rb x.rb 0 +*/*.rb é.rb 0 +*/*.rb dir/é.rb 1 +*/*.rb éa.rb 0 +*/*.rb x.rake 0 +*/*.rb b/x.rb 1 +*/*.rb a/c.rb 1 +*/*.rb a/b/x/c.rb 0 +*/*.rb {a}.rb 0 +*/*.rb b.rb 0 +*/*.rb a 0 +*/*.rb b 0 +*/*.rb lib/a/b.rb 0 +*/*.rb test/lib/x.rb 0 +*/*.rb .a/.b/c.rb 0 +**/x.rb a.rb 0 +**/x.rb app/a.rb 0 +**/x.rb app/jobs/a_job.rb 0 +**/x.rb app/jobs/deep/x.rb 1 +**/x.rb lib/a.rb 0 +**/x.rb lib/b.rb 0 +**/x.rb lib/ab.rb 0 +**/x.rb lib/d.rb 0 +**/x.rb lib/-.rb 0 +**/x.rb lib/].rb 0 +**/x.rb lib/*.rb 0 +**/x.rb lib/x.rb 1 +**/x.rb lib/[.rb 0 +**/x.rb vendor/x.rb 1 +**/x.rb vendor/gem/lib/x.rb 1 +**/x.rb vendored/x.rb 1 +**/x.rb vendor 0 +**/x.rb test/a_test.rb 0 +**/x.rb test/unit/b_test.rb 0 +**/x.rb a_test.rb 0 +**/x.rb .github/workflows/ci.yml 0 +**/x.rb .hidden 0 +**/x.rb .lintus.yml 0 +**/x.rb config/a.yaml 0 +**/x.rb config/deep/b.yml 0 +**/x.rb Gemfile 0 +**/x.rb Gemfile.lock 0 +**/x.rb sub/Gemfile 0 +**/x.rb db/schema.rb 0 +**/x.rb db/migrate/1.rb 0 +**/x.rb exe/lintus 0 +**/x.rb a/b.rb 0 +**/x.rb a/x/b.rb 0 +**/x.rb a/x/y/b.rb 0 +**/x.rb a/b/c.rb 0 +**/x.rb a/x/b/y/c.rb 0 +**/x.rb abc.rb 0 +**/x.rb aXbYc.rb 0 +**/x.rb x.RB 0 +**/x.rb x.rb 1 +**/x.rb é.rb 0 +**/x.rb dir/é.rb 0 +**/x.rb éa.rb 0 +**/x.rb x.rake 0 +**/x.rb b/x.rb 1 +**/x.rb a/c.rb 0 +**/x.rb a/b/x/c.rb 0 +**/x.rb {a}.rb 0 +**/x.rb b.rb 0 +**/x.rb a 0 +**/x.rb b 0 +**/x.rb lib/a/b.rb 0 +**/x.rb test/lib/x.rb 1 +**/x.rb .a/.b/c.rb 0 +{}x.rb a.rb 0 +{}x.rb app/a.rb 0 +{}x.rb app/jobs/a_job.rb 0 +{}x.rb app/jobs/deep/x.rb 0 +{}x.rb lib/a.rb 0 +{}x.rb lib/b.rb 0 +{}x.rb lib/ab.rb 0 +{}x.rb lib/d.rb 0 +{}x.rb lib/-.rb 0 +{}x.rb lib/].rb 0 +{}x.rb lib/*.rb 0 +{}x.rb lib/x.rb 0 +{}x.rb lib/[.rb 0 +{}x.rb vendor/x.rb 0 +{}x.rb vendor/gem/lib/x.rb 0 +{}x.rb vendored/x.rb 0 +{}x.rb vendor 0 +{}x.rb test/a_test.rb 0 +{}x.rb test/unit/b_test.rb 0 +{}x.rb a_test.rb 0 +{}x.rb .github/workflows/ci.yml 0 +{}x.rb .hidden 0 +{}x.rb .lintus.yml 0 +{}x.rb config/a.yaml 0 +{}x.rb config/deep/b.yml 0 +{}x.rb Gemfile 0 +{}x.rb Gemfile.lock 0 +{}x.rb sub/Gemfile 0 +{}x.rb db/schema.rb 0 +{}x.rb db/migrate/1.rb 0 +{}x.rb exe/lintus 0 +{}x.rb a/b.rb 0 +{}x.rb a/x/b.rb 0 +{}x.rb a/x/y/b.rb 0 +{}x.rb a/b/c.rb 0 +{}x.rb a/x/b/y/c.rb 0 +{}x.rb abc.rb 0 +{}x.rb aXbYc.rb 0 +{}x.rb x.RB 0 +{}x.rb x.rb 1 +{}x.rb é.rb 0 +{}x.rb dir/é.rb 0 +{}x.rb éa.rb 0 +{}x.rb x.rake 0 +{}x.rb b/x.rb 0 +{}x.rb a/c.rb 0 +{}x.rb a/b/x/c.rb 0 +{}x.rb {a}.rb 0 +{}x.rb b.rb 0 +{}x.rb a 0 +{}x.rb b 0 +{}x.rb lib/a/b.rb 0 +{}x.rb test/lib/x.rb 0 +{}x.rb .a/.b/c.rb 0 +lib/{a,b}.rb a.rb 0 +lib/{a,b}.rb app/a.rb 0 +lib/{a,b}.rb app/jobs/a_job.rb 0 +lib/{a,b}.rb app/jobs/deep/x.rb 0 +lib/{a,b}.rb lib/a.rb 1 +lib/{a,b}.rb lib/b.rb 1 +lib/{a,b}.rb lib/ab.rb 0 +lib/{a,b}.rb lib/d.rb 0 +lib/{a,b}.rb lib/-.rb 0 +lib/{a,b}.rb lib/].rb 0 +lib/{a,b}.rb lib/*.rb 0 +lib/{a,b}.rb lib/x.rb 0 +lib/{a,b}.rb lib/[.rb 0 +lib/{a,b}.rb vendor/x.rb 0 +lib/{a,b}.rb vendor/gem/lib/x.rb 0 +lib/{a,b}.rb vendored/x.rb 0 +lib/{a,b}.rb vendor 0 +lib/{a,b}.rb test/a_test.rb 0 +lib/{a,b}.rb test/unit/b_test.rb 0 +lib/{a,b}.rb a_test.rb 0 +lib/{a,b}.rb .github/workflows/ci.yml 0 +lib/{a,b}.rb .hidden 0 +lib/{a,b}.rb .lintus.yml 0 +lib/{a,b}.rb config/a.yaml 0 +lib/{a,b}.rb config/deep/b.yml 0 +lib/{a,b}.rb Gemfile 0 +lib/{a,b}.rb Gemfile.lock 0 +lib/{a,b}.rb sub/Gemfile 0 +lib/{a,b}.rb db/schema.rb 0 +lib/{a,b}.rb db/migrate/1.rb 0 +lib/{a,b}.rb exe/lintus 0 +lib/{a,b}.rb a/b.rb 0 +lib/{a,b}.rb a/x/b.rb 0 +lib/{a,b}.rb a/x/y/b.rb 0 +lib/{a,b}.rb a/b/c.rb 0 +lib/{a,b}.rb a/x/b/y/c.rb 0 +lib/{a,b}.rb abc.rb 0 +lib/{a,b}.rb aXbYc.rb 0 +lib/{a,b}.rb x.RB 0 +lib/{a,b}.rb x.rb 0 +lib/{a,b}.rb é.rb 0 +lib/{a,b}.rb dir/é.rb 0 +lib/{a,b}.rb éa.rb 0 +lib/{a,b}.rb x.rake 0 +lib/{a,b}.rb b/x.rb 0 +lib/{a,b}.rb a/c.rb 0 +lib/{a,b}.rb a/b/x/c.rb 0 +lib/{a,b}.rb {a}.rb 0 +lib/{a,b}.rb b.rb 0 +lib/{a,b}.rb a 0 +lib/{a,b}.rb b 0 +lib/{a,b}.rb lib/a/b.rb 0 +lib/{a,b}.rb test/lib/x.rb 0 +lib/{a,b}.rb .a/.b/c.rb 0 +lib/{a,}b.rb a.rb 0 +lib/{a,}b.rb app/a.rb 0 +lib/{a,}b.rb app/jobs/a_job.rb 0 +lib/{a,}b.rb app/jobs/deep/x.rb 0 +lib/{a,}b.rb lib/a.rb 0 +lib/{a,}b.rb lib/b.rb 1 +lib/{a,}b.rb lib/ab.rb 1 +lib/{a,}b.rb lib/d.rb 0 +lib/{a,}b.rb lib/-.rb 0 +lib/{a,}b.rb lib/].rb 0 +lib/{a,}b.rb lib/*.rb 0 +lib/{a,}b.rb lib/x.rb 0 +lib/{a,}b.rb lib/[.rb 0 +lib/{a,}b.rb vendor/x.rb 0 +lib/{a,}b.rb vendor/gem/lib/x.rb 0 +lib/{a,}b.rb vendored/x.rb 0 +lib/{a,}b.rb vendor 0 +lib/{a,}b.rb test/a_test.rb 0 +lib/{a,}b.rb test/unit/b_test.rb 0 +lib/{a,}b.rb a_test.rb 0 +lib/{a,}b.rb .github/workflows/ci.yml 0 +lib/{a,}b.rb .hidden 0 +lib/{a,}b.rb .lintus.yml 0 +lib/{a,}b.rb config/a.yaml 0 +lib/{a,}b.rb config/deep/b.yml 0 +lib/{a,}b.rb Gemfile 0 +lib/{a,}b.rb Gemfile.lock 0 +lib/{a,}b.rb sub/Gemfile 0 +lib/{a,}b.rb db/schema.rb 0 +lib/{a,}b.rb db/migrate/1.rb 0 +lib/{a,}b.rb exe/lintus 0 +lib/{a,}b.rb a/b.rb 0 +lib/{a,}b.rb a/x/b.rb 0 +lib/{a,}b.rb a/x/y/b.rb 0 +lib/{a,}b.rb a/b/c.rb 0 +lib/{a,}b.rb a/x/b/y/c.rb 0 +lib/{a,}b.rb abc.rb 0 +lib/{a,}b.rb aXbYc.rb 0 +lib/{a,}b.rb x.RB 0 +lib/{a,}b.rb x.rb 0 +lib/{a,}b.rb é.rb 0 +lib/{a,}b.rb dir/é.rb 0 +lib/{a,}b.rb éa.rb 0 +lib/{a,}b.rb x.rake 0 +lib/{a,}b.rb b/x.rb 0 +lib/{a,}b.rb a/c.rb 0 +lib/{a,}b.rb a/b/x/c.rb 0 +lib/{a,}b.rb {a}.rb 0 +lib/{a,}b.rb b.rb 0 +lib/{a,}b.rb a 0 +lib/{a,}b.rb b 0 +lib/{a,}b.rb lib/a/b.rb 0 +lib/{a,}b.rb test/lib/x.rb 0 +lib/{a,}b.rb .a/.b/c.rb 0 +{app,{lib,test}} a.rb 0 +{app,{lib,test}} app/a.rb 0 +{app,{lib,test}} app/jobs/a_job.rb 0 +{app,{lib,test}} app/jobs/deep/x.rb 0 +{app,{lib,test}} lib/a.rb 0 +{app,{lib,test}} lib/b.rb 0 +{app,{lib,test}} lib/ab.rb 0 +{app,{lib,test}} lib/d.rb 0 +{app,{lib,test}} lib/-.rb 0 +{app,{lib,test}} lib/].rb 0 +{app,{lib,test}} lib/*.rb 0 +{app,{lib,test}} lib/x.rb 0 +{app,{lib,test}} lib/[.rb 0 +{app,{lib,test}} vendor/x.rb 0 +{app,{lib,test}} vendor/gem/lib/x.rb 0 +{app,{lib,test}} vendored/x.rb 0 +{app,{lib,test}} vendor 0 +{app,{lib,test}} test/a_test.rb 0 +{app,{lib,test}} test/unit/b_test.rb 0 +{app,{lib,test}} a_test.rb 0 +{app,{lib,test}} .github/workflows/ci.yml 0 +{app,{lib,test}} .hidden 0 +{app,{lib,test}} .lintus.yml 0 +{app,{lib,test}} config/a.yaml 0 +{app,{lib,test}} config/deep/b.yml 0 +{app,{lib,test}} Gemfile 0 +{app,{lib,test}} Gemfile.lock 0 +{app,{lib,test}} sub/Gemfile 0 +{app,{lib,test}} db/schema.rb 0 +{app,{lib,test}} db/migrate/1.rb 0 +{app,{lib,test}} exe/lintus 0 +{app,{lib,test}} a/b.rb 0 +{app,{lib,test}} a/x/b.rb 0 +{app,{lib,test}} a/x/y/b.rb 0 +{app,{lib,test}} a/b/c.rb 0 +{app,{lib,test}} a/x/b/y/c.rb 0 +{app,{lib,test}} abc.rb 0 +{app,{lib,test}} aXbYc.rb 0 +{app,{lib,test}} x.RB 0 +{app,{lib,test}} x.rb 0 +{app,{lib,test}} é.rb 0 +{app,{lib,test}} dir/é.rb 0 +{app,{lib,test}} éa.rb 0 +{app,{lib,test}} x.rake 0 +{app,{lib,test}} b/x.rb 0 +{app,{lib,test}} a/c.rb 0 +{app,{lib,test}} a/b/x/c.rb 0 +{app,{lib,test}} {a}.rb 0 +{app,{lib,test}} b.rb 0 +{app,{lib,test}} a 0 +{app,{lib,test}} b 0 +{app,{lib,test}} lib/a/b.rb 0 +{app,{lib,test}} test/lib/x.rb 0 +{app,{lib,test}} .a/.b/c.rb 0 +\{a}.rb a.rb 0 +\{a}.rb app/a.rb 0 +\{a}.rb app/jobs/a_job.rb 0 +\{a}.rb app/jobs/deep/x.rb 0 +\{a}.rb lib/a.rb 0 +\{a}.rb lib/b.rb 0 +\{a}.rb lib/ab.rb 0 +\{a}.rb lib/d.rb 0 +\{a}.rb lib/-.rb 0 +\{a}.rb lib/].rb 0 +\{a}.rb lib/*.rb 0 +\{a}.rb lib/x.rb 0 +\{a}.rb lib/[.rb 0 +\{a}.rb vendor/x.rb 0 +\{a}.rb vendor/gem/lib/x.rb 0 +\{a}.rb vendored/x.rb 0 +\{a}.rb vendor 0 +\{a}.rb test/a_test.rb 0 +\{a}.rb test/unit/b_test.rb 0 +\{a}.rb a_test.rb 0 +\{a}.rb .github/workflows/ci.yml 0 +\{a}.rb .hidden 0 +\{a}.rb .lintus.yml 0 +\{a}.rb config/a.yaml 0 +\{a}.rb config/deep/b.yml 0 +\{a}.rb Gemfile 0 +\{a}.rb Gemfile.lock 0 +\{a}.rb sub/Gemfile 0 +\{a}.rb db/schema.rb 0 +\{a}.rb db/migrate/1.rb 0 +\{a}.rb exe/lintus 0 +\{a}.rb a/b.rb 0 +\{a}.rb a/x/b.rb 0 +\{a}.rb a/x/y/b.rb 0 +\{a}.rb a/b/c.rb 0 +\{a}.rb a/x/b/y/c.rb 0 +\{a}.rb abc.rb 0 +\{a}.rb aXbYc.rb 0 +\{a}.rb x.RB 0 +\{a}.rb x.rb 0 +\{a}.rb é.rb 0 +\{a}.rb dir/é.rb 0 +\{a}.rb éa.rb 0 +\{a}.rb x.rake 0 +\{a}.rb b/x.rb 0 +\{a}.rb a/c.rb 0 +\{a}.rb a/b/x/c.rb 0 +\{a}.rb {a}.rb 1 +\{a}.rb b.rb 0 +\{a}.rb a 0 +\{a}.rb b 0 +\{a}.rb lib/a/b.rb 0 +\{a}.rb test/lib/x.rb 0 +\{a}.rb .a/.b/c.rb 0 +**/{a,b}/**/*.rb a.rb 0 +**/{a,b}/**/*.rb app/a.rb 0 +**/{a,b}/**/*.rb app/jobs/a_job.rb 0 +**/{a,b}/**/*.rb app/jobs/deep/x.rb 0 +**/{a,b}/**/*.rb lib/a.rb 0 +**/{a,b}/**/*.rb lib/b.rb 0 +**/{a,b}/**/*.rb lib/ab.rb 0 +**/{a,b}/**/*.rb lib/d.rb 0 +**/{a,b}/**/*.rb lib/-.rb 0 +**/{a,b}/**/*.rb lib/].rb 0 +**/{a,b}/**/*.rb lib/*.rb 0 +**/{a,b}/**/*.rb lib/x.rb 0 +**/{a,b}/**/*.rb lib/[.rb 0 +**/{a,b}/**/*.rb vendor/x.rb 0 +**/{a,b}/**/*.rb vendor/gem/lib/x.rb 0 +**/{a,b}/**/*.rb vendored/x.rb 0 +**/{a,b}/**/*.rb vendor 0 +**/{a,b}/**/*.rb test/a_test.rb 0 +**/{a,b}/**/*.rb test/unit/b_test.rb 0 +**/{a,b}/**/*.rb a_test.rb 0 +**/{a,b}/**/*.rb .github/workflows/ci.yml 0 +**/{a,b}/**/*.rb .hidden 0 +**/{a,b}/**/*.rb .lintus.yml 0 +**/{a,b}/**/*.rb config/a.yaml 0 +**/{a,b}/**/*.rb config/deep/b.yml 0 +**/{a,b}/**/*.rb Gemfile 0 +**/{a,b}/**/*.rb Gemfile.lock 0 +**/{a,b}/**/*.rb sub/Gemfile 0 +**/{a,b}/**/*.rb db/schema.rb 0 +**/{a,b}/**/*.rb db/migrate/1.rb 0 +**/{a,b}/**/*.rb exe/lintus 0 +**/{a,b}/**/*.rb a/b.rb 1 +**/{a,b}/**/*.rb a/x/b.rb 1 +**/{a,b}/**/*.rb a/x/y/b.rb 1 +**/{a,b}/**/*.rb a/b/c.rb 1 +**/{a,b}/**/*.rb a/x/b/y/c.rb 1 +**/{a,b}/**/*.rb abc.rb 0 +**/{a,b}/**/*.rb aXbYc.rb 0 +**/{a,b}/**/*.rb x.RB 0 +**/{a,b}/**/*.rb x.rb 0 +**/{a,b}/**/*.rb é.rb 0 +**/{a,b}/**/*.rb dir/é.rb 0 +**/{a,b}/**/*.rb éa.rb 0 +**/{a,b}/**/*.rb x.rake 0 +**/{a,b}/**/*.rb b/x.rb 1 +**/{a,b}/**/*.rb a/c.rb 1 +**/{a,b}/**/*.rb a/b/x/c.rb 1 +**/{a,b}/**/*.rb {a}.rb 0 +**/{a,b}/**/*.rb b.rb 0 +**/{a,b}/**/*.rb a 0 +**/{a,b}/**/*.rb b 0 +**/{a,b}/**/*.rb lib/a/b.rb 1 +**/{a,b}/**/*.rb test/lib/x.rb 0 +**/{a,b}/**/*.rb .a/.b/c.rb 0 diff --git a/crates/lintus/tests/fixtures/round_cases.tsv b/crates/lintus/tests/fixtures/round_cases.tsv new file mode 100644 index 0000000..3725835 --- /dev/null +++ b/crates/lintus/tests/fixtures/round_cases.tsv @@ -0,0 +1,9908 @@ +0000000000000000 0.0 0.0 +3ff0000000000000 1.0 1.0 +3fe0000000000000 0.5 0.5 +3fc0000000000000 0.13 0.125 +3fefd70a3d70a3d7 1.0 0.995 +3feffbe76c8b4396 1.0 1.0 +3f741205bc01a36e 0.0 0.005 +3f747ae147ae147b 0.01 0.005 +3f40624dd2f1a9fc 0.0 0.001 +3ee4f8b588e368f1 0.0 0.0 +3bc79ca10c924223 0.0 0.0 +4000000000000000 2.0 2.0 +4028b0a3d70a3d71 12.35 12.345 +3f50624dd2f1a9fc 0.0 0.001 +3f589374bc6a7efa 0.0 0.002 +3f8eb851eb851eb8 0.02 0.015 +3f50624dd2f1a9fd 0.0 0.001 +3f50624dd2f1a9fb 0.0 0.001 +3f60624dd2f1a9fc 0.0 0.002 +3f647ae147ae147b 0.0 0.003 +3f9999999999999a 0.03 0.025 +3f60624dd2f1a9fd 0.0 0.002 +3f60624dd2f1a9fb 0.0 0.002 +3f689374bc6a7efa 0.0 0.003 +3f6cac083126e979 0.0 0.004 +3fa1eb851eb851ec 0.04 0.035 +3f689374bc6a7efb 0.0 0.003 +3f689374bc6a7ef9 0.0 0.003 +3f70624dd2f1a9fc 0.0 0.004 +3f726e978d4fdf3b 0.0 0.005 +3fa70a3d70a3d70a 0.05 0.045 +3f70624dd2f1a9fd 0.0 0.004 +3f70624dd2f1a9fb 0.0 0.004 +3f76872b020c49ba 0.01 0.006 +3fac28f5c28f5c29 0.06 0.055 +3f747ae147ae147c 0.01 0.005 +3f747ae147ae147a 0.0 0.005 +3f789374bc6a7efa 0.01 0.006 +3f7a9fbe76c8b439 0.01 0.007 +3fb0a3d70a3d70a4 0.07 0.065 +3f789374bc6a7efb 0.01 0.006 +3f789374bc6a7ef9 0.01 0.006 +3f7cac083126e979 0.01 0.007 +3f7eb851eb851eb8 0.01 0.008 +3fb3333333333333 0.08 0.075 +3f7cac083126e97a 0.01 0.007 +3f7cac083126e978 0.01 0.007 +3f80624dd2f1a9fc 0.01 0.008 +3f816872b020c49c 0.01 0.009 +3fb5c28f5c28f5c3 0.09 0.085 +3f80624dd2f1a9fd 0.01 0.008 +3f80624dd2f1a9fb 0.01 0.008 +3f826e978d4fdf3b 0.01 0.009 +3f8374bc6a7ef9db 0.01 0.01 +3fb851eb851eb852 0.1 0.095 +3f826e978d4fdf3c 0.01 0.009 +3f826e978d4fdf3a 0.01 0.009 +3f847ae147ae147b 0.01 0.01 +3f85810624dd2f1b 0.01 0.011 +3fbae147ae147ae1 0.11 0.105 +3f847ae147ae147c 0.01 0.01 +3f847ae147ae147a 0.01 0.01 +3f86872b020c49ba 0.01 0.011 +3f878d4fdf3b645a 0.01 0.012 +3fbd70a3d70a3d71 0.12 0.115 +3f86872b020c49bb 0.01 0.011 +3f86872b020c49b9 0.01 0.011 +3f889374bc6a7efa 0.01 0.012 +3f8999999999999a 0.01 0.013 +3f889374bc6a7efb 0.01 0.012 +3f889374bc6a7ef9 0.01 0.012 +3f8a9fbe76c8b439 0.01 0.013 +3f8ba5e353f7ced9 0.01 0.014 +3fc147ae147ae148 0.14 0.135 +3f8a9fbe76c8b43a 0.01 0.013 +3f8a9fbe76c8b438 0.01 0.013 +3f8cac083126e979 0.01 0.014 +3f8db22d0e560419 0.01 0.015 +3fc28f5c28f5c28f 0.15 0.145 +3f8cac083126e97a 0.01 0.014 +3f8cac083126e978 0.01 0.014 +3f8fbe76c8b43958 0.02 0.016 +3fc3d70a3d70a3d7 0.16 0.155 +3f8eb851eb851eb9 0.02 0.015 +3f8eb851eb851eb7 0.01 0.015 +3f90624dd2f1a9fc 0.02 0.016 +3f90e5604189374c 0.02 0.017 +3fc51eb851eb851f 0.17 0.165 +3f90624dd2f1a9fd 0.02 0.016 +3f90624dd2f1a9fb 0.02 0.016 +3f916872b020c49c 0.02 0.017 +3f91eb851eb851ec 0.02 0.018 +3fc6666666666666 0.18 0.175 +3f916872b020c49d 0.02 0.017 +3f916872b020c49b 0.02 0.017 +3f926e978d4fdf3b 0.02 0.018 +3f92f1a9fbe76c8b 0.02 0.019 +3fc7ae147ae147ae 0.19 0.185 +3f926e978d4fdf3c 0.02 0.018 +3f926e978d4fdf3a 0.02 0.018 +3f9374bc6a7ef9db 0.02 0.019 +3f93f7ced916872b 0.02 0.02 +3fc8f5c28f5c28f6 0.2 0.195 +3f9374bc6a7ef9dc 0.02 0.019 +3f9374bc6a7ef9da 0.02 0.019 +3f947ae147ae147b 0.02 0.02 +3f94fdf3b645a1cb 0.02 0.021 +3fca3d70a3d70a3d 0.21 0.205 +3f947ae147ae147c 0.02 0.02 +3f947ae147ae147a 0.02 0.02 +3f95810624dd2f1b 0.02 0.021 +3f9604189374bc6a 0.02 0.022 +3fcb851eb851eb85 0.22 0.215 +3f95810624dd2f1c 0.02 0.021 +3f95810624dd2f1a 0.02 0.021 +3f96872b020c49ba 0.02 0.022 +3f970a3d70a3d70a 0.02 0.023 +3fcccccccccccccd 0.23 0.225 +3f96872b020c49bb 0.02 0.022 +3f96872b020c49b9 0.02 0.022 +3f978d4fdf3b645a 0.02 0.023 +3f9810624dd2f1aa 0.02 0.024 +3fce147ae147ae14 0.24 0.235 +3f978d4fdf3b645b 0.02 0.023 +3f978d4fdf3b6459 0.02 0.023 +3f989374bc6a7efa 0.02 0.024 +3f9916872b020c4a 0.02 0.025 +3fcf5c28f5c28f5c 0.25 0.245 +3f989374bc6a7efb 0.02 0.024 +3f989374bc6a7ef9 0.02 0.024 +3f9a1cac083126e9 0.03 0.026 +3fd051eb851eb852 0.26 0.255 +3f9999999999999b 0.03 0.025 +3f99999999999999 0.03 0.025 +3f9a9fbe76c8b439 0.03 0.026 +3f9b22d0e5604189 0.03 0.027 +3fd0f5c28f5c28f6 0.27 0.265 +3f9a9fbe76c8b43a 0.03 0.026 +3f9a9fbe76c8b438 0.03 0.026 +3f9ba5e353f7ced9 0.03 0.027 +3f9c28f5c28f5c29 0.03 0.028 +3fd199999999999a 0.28 0.275 +3f9ba5e353f7ceda 0.03 0.027 +3f9ba5e353f7ced8 0.03 0.027 +3f9cac083126e979 0.03 0.028 +3f9d2f1a9fbe76c9 0.03 0.029 +3fd23d70a3d70a3d 0.29 0.285 +3f9cac083126e97a 0.03 0.028 +3f9cac083126e978 0.03 0.028 +3f9db22d0e560419 0.03 0.029 +3f9e353f7ced9168 0.03 0.03 +3fd2e147ae147ae1 0.3 0.295 +3f9db22d0e56041a 0.03 0.029 +3f9db22d0e560418 0.03 0.029 +3f9eb851eb851eb8 0.03 0.03 +3f9f3b645a1cac08 0.03 0.031 +3fd3851eb851eb85 0.31 0.305 +3f9eb851eb851eb9 0.03 0.03 +3f9eb851eb851eb7 0.03 0.03 +3f9fbe76c8b43958 0.03 0.031 +3fa020c49ba5e354 0.03 0.032 +3fd428f5c28f5c29 0.32 0.315 +3f9fbe76c8b43959 0.03 0.031 +3f9fbe76c8b43957 0.03 0.031 +3fa0624dd2f1a9fc 0.03 0.032 +3fa0a3d70a3d70a4 0.03 0.033 +3fd4cccccccccccd 0.33 0.325 +3fa0624dd2f1a9fd 0.03 0.032 +3fa0624dd2f1a9fb 0.03 0.032 +3fa0e5604189374c 0.03 0.033 +3fa126e978d4fdf4 0.03 0.034 +3fd570a3d70a3d71 0.34 0.335 +3fa0e5604189374d 0.03 0.033 +3fa0e5604189374b 0.03 0.033 +3fa16872b020c49c 0.03 0.034 +3fa1a9fbe76c8b44 0.03 0.035 +3fd6147ae147ae14 0.35 0.345 +3fa16872b020c49d 0.03 0.034 +3fa16872b020c49b 0.03 0.034 +3fa22d0e56041893 0.04 0.036 +3fd6b851eb851eb8 0.36 0.355 +3fa1eb851eb851ed 0.04 0.035 +3fa1eb851eb851eb 0.03 0.035 +3fa26e978d4fdf3b 0.04 0.036 +3fa2b020c49ba5e3 0.04 0.037 +3fd75c28f5c28f5c 0.37 0.365 +3fa26e978d4fdf3c 0.04 0.036 +3fa26e978d4fdf3a 0.04 0.036 +3fa2f1a9fbe76c8b 0.04 0.037 +3fa3333333333333 0.04 0.038 +3fd8000000000000 0.38 0.375 +3fa2f1a9fbe76c8c 0.04 0.037 +3fa2f1a9fbe76c8a 0.04 0.037 +3fa374bc6a7ef9db 0.04 0.038 +3fa3b645a1cac083 0.04 0.039 +3fd8a3d70a3d70a4 0.39 0.385 +3fa374bc6a7ef9dc 0.04 0.038 +3fa374bc6a7ef9da 0.04 0.038 +3fa3f7ced916872b 0.04 0.039 +3fa4395810624dd3 0.04 0.04 +3fd947ae147ae148 0.4 0.395 +3fa3f7ced916872c 0.04 0.039 +3fa3f7ced916872a 0.04 0.039 +3fa47ae147ae147b 0.04 0.04 +3fa4bc6a7ef9db23 0.04 0.041 +3fd9eb851eb851ec 0.41 0.405 +3fa47ae147ae147c 0.04 0.04 +3fa47ae147ae147a 0.04 0.04 +3fa4fdf3b645a1cb 0.04 0.041 +3fa53f7ced916873 0.04 0.042 +3fda8f5c28f5c28f 0.42 0.415 +3fa4fdf3b645a1cc 0.04 0.041 +3fa4fdf3b645a1ca 0.04 0.041 +3fa5810624dd2f1b 0.04 0.042 +3fa5c28f5c28f5c3 0.04 0.043 +3fdb333333333333 0.43 0.425 +3fa5810624dd2f1c 0.04 0.042 +3fa5810624dd2f1a 0.04 0.042 +3fa604189374bc6a 0.04 0.043 +3fa645a1cac08312 0.04 0.044 +3fdbd70a3d70a3d7 0.44 0.435 +3fa604189374bc6b 0.04 0.043 +3fa604189374bc69 0.04 0.043 +3fa6872b020c49ba 0.04 0.044 +3fa6c8b439581062 0.04 0.045 +3fdc7ae147ae147b 0.45 0.445 +3fa6872b020c49bb 0.04 0.044 +3fa6872b020c49b9 0.04 0.044 +3fa74bc6a7ef9db2 0.05 0.046 +3fdd1eb851eb851f 0.46 0.455 +3fa70a3d70a3d70b 0.05 0.045 +3fa70a3d70a3d709 0.04 0.045 +3fa78d4fdf3b645a 0.05 0.046 +3fa7ced916872b02 0.05 0.047 +3fddc28f5c28f5c3 0.47 0.465 +3fa78d4fdf3b645b 0.05 0.046 +3fa78d4fdf3b6459 0.05 0.046 +3fa810624dd2f1aa 0.05 0.047 +3fa851eb851eb852 0.05 0.048 +3fde666666666666 0.48 0.475 +3fa810624dd2f1ab 0.05 0.047 +3fa810624dd2f1a9 0.05 0.047 +3fa89374bc6a7efa 0.05 0.048 +3fa8d4fdf3b645a2 0.05 0.049 +3fdf0a3d70a3d70a 0.49 0.485 +3fa89374bc6a7efb 0.05 0.048 +3fa89374bc6a7ef9 0.05 0.048 +3fa916872b020c4a 0.05 0.049 +3fa95810624dd2f2 0.05 0.05 +3fdfae147ae147ae 0.5 0.495 +3fa916872b020c4b 0.05 0.049 +3fa916872b020c49 0.05 0.049 +3fa999999999999a 0.05 0.05 +3fa9db22d0e56042 0.05 0.051 +3fe028f5c28f5c29 0.51 0.505 +3fa999999999999b 0.05 0.05 +3fa9999999999999 0.05 0.05 +3faa1cac083126e9 0.05 0.051 +3faa5e353f7ced91 0.05 0.052 +3fe07ae147ae147b 0.52 0.515 +3faa1cac083126ea 0.05 0.051 +3faa1cac083126e8 0.05 0.051 +3faa9fbe76c8b439 0.05 0.052 +3faae147ae147ae1 0.05 0.053 +3fe0cccccccccccd 0.53 0.525 +3faa9fbe76c8b43a 0.05 0.052 +3faa9fbe76c8b438 0.05 0.052 +3fab22d0e5604189 0.05 0.053 +3fab645a1cac0831 0.05 0.054 +3fe11eb851eb851f 0.54 0.535 +3fab22d0e560418a 0.05 0.053 +3fab22d0e5604188 0.05 0.053 +3faba5e353f7ced9 0.05 0.054 +3fabe76c8b439581 0.05 0.055 +3fe170a3d70a3d71 0.55 0.545 +3faba5e353f7ceda 0.05 0.054 +3faba5e353f7ced8 0.05 0.054 +3fac6a7ef9db22d1 0.06 0.056 +3fe1c28f5c28f5c3 0.56 0.555 +3fac28f5c28f5c2a 0.06 0.055 +3fac28f5c28f5c28 0.05 0.055 +3facac083126e979 0.06 0.056 +3faced916872b021 0.06 0.057 +3fe2147ae147ae14 0.57 0.565 +3facac083126e97a 0.06 0.056 +3facac083126e978 0.06 0.056 +3fad2f1a9fbe76c9 0.06 0.057 +3fad70a3d70a3d71 0.06 0.058 +3fe2666666666666 0.58 0.575 +3fad2f1a9fbe76ca 0.06 0.057 +3fad2f1a9fbe76c8 0.06 0.057 +3fadb22d0e560419 0.06 0.058 +3fadf3b645a1cac1 0.06 0.059 +3fe2b851eb851eb8 0.59 0.585 +3fadb22d0e56041a 0.06 0.058 +3fadb22d0e560418 0.06 0.058 +3fae353f7ced9168 0.06 0.059 +3fae76c8b4395810 0.06 0.06 +3fe30a3d70a3d70a 0.6 0.595 +3fae353f7ced9169 0.06 0.059 +3fae353f7ced9167 0.06 0.059 +3faeb851eb851eb8 0.06 0.06 +3faef9db22d0e560 0.06 0.061 +3fe35c28f5c28f5c 0.61 0.605 +3faeb851eb851eb9 0.06 0.06 +3faeb851eb851eb7 0.06 0.06 +3faf3b645a1cac08 0.06 0.061 +3faf7ced916872b0 0.06 0.062 +3fe3ae147ae147ae 0.62 0.615 +3faf3b645a1cac09 0.06 0.061 +3faf3b645a1cac07 0.06 0.061 +3fafbe76c8b43958 0.06 0.062 +3fb0000000000000 0.06 0.063 +3fe4000000000000 0.63 0.625 +3fafbe76c8b43959 0.06 0.062 +3fafbe76c8b43957 0.06 0.062 +3fb020c49ba5e354 0.06 0.063 +3fb04189374bc6a8 0.06 0.064 +3fe451eb851eb852 0.64 0.635 +3fb020c49ba5e355 0.06 0.063 +3fb020c49ba5e353 0.06 0.063 +3fb0624dd2f1a9fc 0.06 0.064 +3fb083126e978d50 0.06 0.065 +3fe4a3d70a3d70a4 0.65 0.645 +3fb0624dd2f1a9fd 0.06 0.064 +3fb0624dd2f1a9fb 0.06 0.064 +3fb0c49ba5e353f8 0.07 0.066 +3fe4f5c28f5c28f6 0.66 0.655 +3fb0a3d70a3d70a5 0.07 0.065 +3fb0a3d70a3d70a3 0.06 0.065 +3fb0e5604189374c 0.07 0.066 +3fb10624dd2f1aa0 0.07 0.067 +3fe547ae147ae148 0.67 0.665 +3fb0e5604189374d 0.07 0.066 +3fb0e5604189374b 0.07 0.066 +3fb126e978d4fdf4 0.07 0.067 +3fb147ae147ae148 0.07 0.068 +3fe599999999999a 0.68 0.675 +3fb126e978d4fdf5 0.07 0.067 +3fb126e978d4fdf3 0.07 0.067 +3fb16872b020c49c 0.07 0.068 +3fb189374bc6a7f0 0.07 0.069 +3fe5eb851eb851ec 0.69 0.685 +3fb16872b020c49d 0.07 0.068 +3fb16872b020c49b 0.07 0.068 +3fb1a9fbe76c8b44 0.07 0.069 +3fb1cac083126e98 0.07 0.07 +3fe63d70a3d70a3d 0.7 0.695 +3fb1a9fbe76c8b45 0.07 0.069 +3fb1a9fbe76c8b43 0.07 0.069 +3fb1eb851eb851ec 0.07 0.07 +3fb20c49ba5e353f 0.07 0.071 +3fe68f5c28f5c28f 0.71 0.705 +3fb1eb851eb851ed 0.07 0.07 +3fb1eb851eb851eb 0.07 0.07 +3fb22d0e56041893 0.07 0.071 +3fb24dd2f1a9fbe7 0.07 0.072 +3fe6e147ae147ae1 0.72 0.715 +3fb22d0e56041894 0.07 0.071 +3fb22d0e56041892 0.07 0.071 +3fb26e978d4fdf3b 0.07 0.072 +3fb28f5c28f5c28f 0.07 0.073 +3fe7333333333333 0.73 0.725 +3fb26e978d4fdf3c 0.07 0.072 +3fb26e978d4fdf3a 0.07 0.072 +3fb2b020c49ba5e3 0.07 0.073 +3fb2d0e560418937 0.07 0.074 +3fe7851eb851eb85 0.74 0.735 +3fb2b020c49ba5e4 0.07 0.073 +3fb2b020c49ba5e2 0.07 0.073 +3fb2f1a9fbe76c8b 0.07 0.074 +3fb3126e978d4fdf 0.07 0.075 +3fe7d70a3d70a3d7 0.75 0.745 +3fb2f1a9fbe76c8c 0.07 0.074 +3fb2f1a9fbe76c8a 0.07 0.074 +3fb353f7ced91687 0.08 0.076 +3fe828f5c28f5c29 0.76 0.755 +3fb3333333333334 0.08 0.075 +3fb3333333333332 0.07 0.075 +3fb374bc6a7ef9db 0.08 0.076 +3fb395810624dd2f 0.08 0.077 +3fe87ae147ae147b 0.77 0.765 +3fb374bc6a7ef9dc 0.08 0.076 +3fb374bc6a7ef9da 0.08 0.076 +3fb3b645a1cac083 0.08 0.077 +3fb3d70a3d70a3d7 0.08 0.078 +3fe8cccccccccccd 0.78 0.775 +3fb3b645a1cac084 0.08 0.077 +3fb3b645a1cac082 0.08 0.077 +3fb3f7ced916872b 0.08 0.078 +3fb4189374bc6a7f 0.08 0.079 +3fe91eb851eb851f 0.79 0.785 +3fb3f7ced916872c 0.08 0.078 +3fb3f7ced916872a 0.08 0.078 +3fb4395810624dd3 0.08 0.079 +3fb45a1cac083127 0.08 0.08 +3fe970a3d70a3d71 0.8 0.795 +3fb4395810624dd4 0.08 0.079 +3fb4395810624dd2 0.08 0.079 +3fb47ae147ae147b 0.08 0.08 +3fb49ba5e353f7cf 0.08 0.081 +3fe9c28f5c28f5c3 0.81 0.805 +3fb47ae147ae147c 0.08 0.08 +3fb47ae147ae147a 0.08 0.08 +3fb4bc6a7ef9db23 0.08 0.081 +3fb4dd2f1a9fbe77 0.08 0.082 +3fea147ae147ae14 0.82 0.815 +3fb4bc6a7ef9db24 0.08 0.081 +3fb4bc6a7ef9db22 0.08 0.081 +3fb4fdf3b645a1cb 0.08 0.082 +3fb51eb851eb851f 0.08 0.083 +3fea666666666666 0.83 0.825 +3fb4fdf3b645a1cc 0.08 0.082 +3fb4fdf3b645a1ca 0.08 0.082 +3fb53f7ced916873 0.08 0.083 +3fb5604189374bc7 0.08 0.084 +3feab851eb851eb8 0.84 0.835 +3fb53f7ced916874 0.08 0.083 +3fb53f7ced916872 0.08 0.083 +3fb5810624dd2f1b 0.08 0.084 +3fb5a1cac083126f 0.08 0.085 +3feb0a3d70a3d70a 0.85 0.845 +3fb5810624dd2f1c 0.08 0.084 +3fb5810624dd2f1a 0.08 0.084 +3fb5e353f7ced917 0.09 0.086 +3feb5c28f5c28f5c 0.86 0.855 +3fb5c28f5c28f5c4 0.09 0.085 +3fb5c28f5c28f5c2 0.09 0.085 +3fb604189374bc6a 0.09 0.086 +3fb624dd2f1a9fbe 0.09 0.087 +3febae147ae147ae 0.87 0.865 +3fb604189374bc6b 0.09 0.086 +3fb604189374bc69 0.09 0.086 +3fb645a1cac08312 0.09 0.087 +3fb6666666666666 0.09 0.088 +3fec000000000000 0.88 0.875 +3fb645a1cac08313 0.09 0.087 +3fb645a1cac08311 0.09 0.087 +3fb6872b020c49ba 0.09 0.088 +3fb6a7ef9db22d0e 0.09 0.089 +3fec51eb851eb852 0.89 0.885 +3fb6872b020c49bb 0.09 0.088 +3fb6872b020c49b9 0.09 0.088 +3fb6c8b439581062 0.09 0.089 +3fb6e978d4fdf3b6 0.09 0.09 +3feca3d70a3d70a4 0.9 0.895 +3fb6c8b439581063 0.09 0.089 +3fb6c8b439581061 0.09 0.089 +3fb70a3d70a3d70a 0.09 0.09 +3fb72b020c49ba5e 0.09 0.091 +3fecf5c28f5c28f6 0.91 0.905 +3fb70a3d70a3d70b 0.09 0.09 +3fb70a3d70a3d709 0.09 0.09 +3fb74bc6a7ef9db2 0.09 0.091 +3fb76c8b43958106 0.09 0.092 +3fed47ae147ae148 0.92 0.915 +3fb74bc6a7ef9db3 0.09 0.091 +3fb74bc6a7ef9db1 0.09 0.091 +3fb78d4fdf3b645a 0.09 0.092 +3fb7ae147ae147ae 0.09 0.093 +3fed99999999999a 0.93 0.925 +3fb78d4fdf3b645b 0.09 0.092 +3fb78d4fdf3b6459 0.09 0.092 +3fb7ced916872b02 0.09 0.093 +3fb7ef9db22d0e56 0.09 0.094 +3fedeb851eb851ec 0.94 0.935 +3fb7ced916872b03 0.09 0.093 +3fb7ced916872b01 0.09 0.093 +3fb810624dd2f1aa 0.09 0.094 +3fb83126e978d4fe 0.09 0.095 +3fee3d70a3d70a3d 0.95 0.945 +3fb810624dd2f1ab 0.09 0.094 +3fb810624dd2f1a9 0.09 0.094 +3fb872b020c49ba6 0.1 0.096 +3fee8f5c28f5c28f 0.96 0.955 +3fb851eb851eb853 0.1 0.095 +3fb851eb851eb851 0.09 0.095 +3fb89374bc6a7efa 0.1 0.096 +3fb8b4395810624e 0.1 0.097 +3feee147ae147ae1 0.97 0.965 +3fb89374bc6a7efb 0.1 0.096 +3fb89374bc6a7ef9 0.1 0.096 +3fb8d4fdf3b645a2 0.1 0.097 +3fb8f5c28f5c28f6 0.1 0.098 +3fef333333333333 0.98 0.975 +3fb8d4fdf3b645a3 0.1 0.097 +3fb8d4fdf3b645a1 0.1 0.097 +3fb916872b020c4a 0.1 0.098 +3fb9374bc6a7ef9e 0.1 0.099 +3fef851eb851eb85 0.99 0.985 +3fb916872b020c4b 0.1 0.098 +3fb916872b020c49 0.1 0.098 +3fb95810624dd2f2 0.1 0.099 +3fb978d4fdf3b646 0.1 0.1 +3fb95810624dd2f3 0.1 0.099 +3fb95810624dd2f1 0.1 0.099 +3fb999999999999a 0.1 0.1 +3fb9ba5e353f7cee 0.1 0.101 +3ff0147ae147ae14 1.01 1.005 +3fb999999999999b 0.1 0.1 +3fb9999999999999 0.1 0.1 +3fb9db22d0e56042 0.1 0.101 +3fb9fbe76c8b4396 0.1 0.102 +3ff03d70a3d70a3d 1.02 1.015 +3fb9db22d0e56043 0.1 0.101 +3fb9db22d0e56041 0.1 0.101 +3fba1cac083126e9 0.1 0.102 +3fba3d70a3d70a3d 0.1 0.103 +3ff0666666666666 1.03 1.025 +3fba1cac083126ea 0.1 0.102 +3fba1cac083126e8 0.1 0.102 +3fba5e353f7ced91 0.1 0.103 +3fba7ef9db22d0e5 0.1 0.104 +3ff08f5c28f5c28f 1.04 1.035 +3fba5e353f7ced92 0.1 0.103 +3fba5e353f7ced90 0.1 0.103 +3fba9fbe76c8b439 0.1 0.104 +3fbac083126e978d 0.1 0.105 +3ff0b851eb851eb8 1.05 1.045 +3fba9fbe76c8b43a 0.1 0.104 +3fba9fbe76c8b438 0.1 0.104 +3fbb020c49ba5e35 0.11 0.106 +3ff0e147ae147ae1 1.06 1.055 +3fbae147ae147ae2 0.11 0.105 +3fbae147ae147ae0 0.1 0.105 +3fbb22d0e5604189 0.11 0.106 +3fbb4395810624dd 0.11 0.107 +3ff10a3d70a3d70a 1.07 1.065 +3fbb22d0e560418a 0.11 0.106 +3fbb22d0e5604188 0.11 0.106 +3fbb645a1cac0831 0.11 0.107 +3fbb851eb851eb85 0.11 0.108 +3ff1333333333333 1.08 1.075 +3fbb645a1cac0832 0.11 0.107 +3fbb645a1cac0830 0.11 0.107 +3fbba5e353f7ced9 0.11 0.108 +3fbbc6a7ef9db22d 0.11 0.109 +3ff15c28f5c28f5c 1.09 1.085 +3fbba5e353f7ceda 0.11 0.108 +3fbba5e353f7ced8 0.11 0.108 +3fbbe76c8b439581 0.11 0.109 +3fbc083126e978d5 0.11 0.11 +3ff1851eb851eb85 1.1 1.095 +3fbbe76c8b439582 0.11 0.109 +3fbbe76c8b439580 0.11 0.109 +3fbc28f5c28f5c29 0.11 0.11 +3fbc49ba5e353f7d 0.11 0.111 +3ff1ae147ae147ae 1.11 1.105 +3fbc28f5c28f5c2a 0.11 0.11 +3fbc28f5c28f5c28 0.11 0.11 +3fbc6a7ef9db22d1 0.11 0.111 +3fbc8b4395810625 0.11 0.112 +3ff1d70a3d70a3d7 1.12 1.115 +3fbc6a7ef9db22d2 0.11 0.111 +3fbc6a7ef9db22d0 0.11 0.111 +3fbcac083126e979 0.11 0.112 +3fbccccccccccccd 0.11 0.113 +3ff2000000000000 1.13 1.125 +3fbcac083126e97a 0.11 0.112 +3fbcac083126e978 0.11 0.112 +3fbced916872b021 0.11 0.113 +3fbd0e5604189375 0.11 0.114 +3ff228f5c28f5c29 1.14 1.135 +3fbced916872b022 0.11 0.113 +3fbced916872b020 0.11 0.113 +3fbd2f1a9fbe76c9 0.11 0.114 +3fbd4fdf3b645a1d 0.11 0.115 +3ff251eb851eb852 1.15 1.145 +3fbd2f1a9fbe76ca 0.11 0.114 +3fbd2f1a9fbe76c8 0.11 0.114 +3fbd916872b020c5 0.12 0.116 +3ff27ae147ae147b 1.16 1.155 +3fbd70a3d70a3d72 0.12 0.115 +3fbd70a3d70a3d70 0.12 0.115 +3fbdb22d0e560419 0.12 0.116 +3fbdd2f1a9fbe76d 0.12 0.117 +3ff2a3d70a3d70a4 1.17 1.165 +3fbdb22d0e56041a 0.12 0.116 +3fbdb22d0e560418 0.12 0.116 +3fbdf3b645a1cac1 0.12 0.117 +3fbe147ae147ae14 0.12 0.118 +3ff2cccccccccccd 1.18 1.175 +3fbdf3b645a1cac2 0.12 0.117 +3fbdf3b645a1cac0 0.12 0.117 +3fbe353f7ced9168 0.12 0.118 +3fbe5604189374bc 0.12 0.119 +3ff2f5c28f5c28f6 1.19 1.185 +3fbe353f7ced9169 0.12 0.118 +3fbe353f7ced9167 0.12 0.118 +3fbe76c8b4395810 0.12 0.119 +3fbe978d4fdf3b64 0.12 0.12 +3ff31eb851eb851f 1.2 1.195 +3fbe76c8b4395811 0.12 0.119 +3fbe76c8b439580f 0.12 0.119 +3fbeb851eb851eb8 0.12 0.12 +3fbed916872b020c 0.12 0.121 +3ff347ae147ae148 1.21 1.205 +3fbeb851eb851eb9 0.12 0.12 +3fbeb851eb851eb7 0.12 0.12 +3fbef9db22d0e560 0.12 0.121 +3fbf1a9fbe76c8b4 0.12 0.122 +3ff370a3d70a3d71 1.22 1.215 +3fbef9db22d0e561 0.12 0.121 +3fbef9db22d0e55f 0.12 0.121 +3fbf3b645a1cac08 0.12 0.122 +3fbf5c28f5c28f5c 0.12 0.123 +3ff399999999999a 1.23 1.225 +3fbf3b645a1cac09 0.12 0.122 +3fbf3b645a1cac07 0.12 0.122 +3fbf7ced916872b0 0.12 0.123 +3fbf9db22d0e5604 0.12 0.124 +3ff3c28f5c28f5c3 1.24 1.235 +3fbf7ced916872b1 0.12 0.123 +3fbf7ced916872af 0.12 0.123 +3fbfbe76c8b43958 0.12 0.124 +3fbfdf3b645a1cac 0.12 0.125 +3ff3eb851eb851ec 1.25 1.245 +3fbfbe76c8b43959 0.12 0.124 +3fbfbe76c8b43957 0.12 0.124 +3fc010624dd2f1aa 0.13 0.126 +3ff4147ae147ae14 1.26 1.255 +3fc0000000000001 0.13 0.125 +3fbfffffffffffff 0.12 0.125 +3fc020c49ba5e354 0.13 0.126 +3fc03126e978d4fe 0.13 0.127 +3ff43d70a3d70a3d 1.27 1.265 +3fc020c49ba5e355 0.13 0.126 +3fc020c49ba5e353 0.13 0.126 +3fc04189374bc6a8 0.13 0.127 +3fc051eb851eb852 0.13 0.128 +3ff4666666666666 1.28 1.275 +3fc04189374bc6a9 0.13 0.127 +3fc04189374bc6a7 0.13 0.127 +3fc0624dd2f1a9fc 0.13 0.128 +3fc072b020c49ba6 0.13 0.129 +3ff48f5c28f5c28f 1.29 1.285 +3fc0624dd2f1a9fd 0.13 0.128 +3fc0624dd2f1a9fb 0.13 0.128 +3fc083126e978d50 0.13 0.129 +3fc09374bc6a7efa 0.13 0.13 +3ff4b851eb851eb8 1.3 1.295 +3fc083126e978d51 0.13 0.129 +3fc083126e978d4f 0.13 0.129 +3fc0a3d70a3d70a4 0.13 0.13 +3fc0b4395810624e 0.13 0.131 +3ff4e147ae147ae1 1.31 1.305 +3fc0a3d70a3d70a5 0.13 0.13 +3fc0a3d70a3d70a3 0.13 0.13 +3fc0c49ba5e353f8 0.13 0.131 +3fc0d4fdf3b645a2 0.13 0.132 +3ff50a3d70a3d70a 1.32 1.315 +3fc0c49ba5e353f9 0.13 0.131 +3fc0c49ba5e353f7 0.13 0.131 +3fc0e5604189374c 0.13 0.132 +3fc0f5c28f5c28f6 0.13 0.133 +3ff5333333333333 1.33 1.325 +3fc0e5604189374d 0.13 0.132 +3fc0e5604189374b 0.13 0.132 +3fc10624dd2f1aa0 0.13 0.133 +3fc116872b020c4a 0.13 0.134 +3ff55c28f5c28f5c 1.34 1.335 +3fc10624dd2f1aa1 0.13 0.133 +3fc10624dd2f1a9f 0.13 0.133 +3fc126e978d4fdf4 0.13 0.134 +3fc1374bc6a7ef9e 0.13 0.135 +3ff5851eb851eb85 1.35 1.345 +3fc126e978d4fdf5 0.13 0.134 +3fc126e978d4fdf3 0.13 0.134 +3fc15810624dd2f2 0.14 0.136 +3ff5ae147ae147ae 1.36 1.355 +3fc147ae147ae149 0.14 0.135 +3fc147ae147ae147 0.13 0.135 +3fc16872b020c49c 0.14 0.136 +3fc178d4fdf3b646 0.14 0.137 +3ff5d70a3d70a3d7 1.37 1.365 +3fc16872b020c49d 0.14 0.136 +3fc16872b020c49b 0.14 0.136 +3fc189374bc6a7f0 0.14 0.137 +3fc199999999999a 0.14 0.138 +3ff6000000000000 1.38 1.375 +3fc189374bc6a7f1 0.14 0.137 +3fc189374bc6a7ef 0.14 0.137 +3fc1a9fbe76c8b44 0.14 0.138 +3fc1ba5e353f7cee 0.14 0.139 +3ff628f5c28f5c29 1.39 1.385 +3fc1a9fbe76c8b45 0.14 0.138 +3fc1a9fbe76c8b43 0.14 0.138 +3fc1cac083126e98 0.14 0.139 +3fc1db22d0e56042 0.14 0.14 +3ff651eb851eb852 1.4 1.395 +3fc1cac083126e99 0.14 0.139 +3fc1cac083126e97 0.14 0.139 +3fc1eb851eb851ec 0.14 0.14 +3fc1fbe76c8b4396 0.14 0.141 +3ff67ae147ae147b 1.41 1.405 +3fc1eb851eb851ed 0.14 0.14 +3fc1eb851eb851eb 0.14 0.14 +3fc20c49ba5e353f 0.14 0.141 +3fc21cac083126e9 0.14 0.142 +3ff6a3d70a3d70a4 1.42 1.415 +3fc20c49ba5e3540 0.14 0.141 +3fc20c49ba5e353e 0.14 0.141 +3fc22d0e56041893 0.14 0.142 +3fc23d70a3d70a3d 0.14 0.143 +3ff6cccccccccccd 1.43 1.425 +3fc22d0e56041894 0.14 0.142 +3fc22d0e56041892 0.14 0.142 +3fc24dd2f1a9fbe7 0.14 0.143 +3fc25e353f7ced91 0.14 0.144 +3ff6f5c28f5c28f6 1.44 1.435 +3fc24dd2f1a9fbe8 0.14 0.143 +3fc24dd2f1a9fbe6 0.14 0.143 +3fc26e978d4fdf3b 0.14 0.144 +3fc27ef9db22d0e5 0.14 0.145 +3ff71eb851eb851f 1.45 1.445 +3fc26e978d4fdf3c 0.14 0.144 +3fc26e978d4fdf3a 0.14 0.144 +3fc29fbe76c8b439 0.15 0.146 +3ff747ae147ae148 1.46 1.455 +3fc28f5c28f5c290 0.15 0.145 +3fc28f5c28f5c28e 0.14 0.145 +3fc2b020c49ba5e3 0.15 0.146 +3fc2c083126e978d 0.15 0.147 +3ff770a3d70a3d71 1.47 1.465 +3fc2b020c49ba5e4 0.15 0.146 +3fc2b020c49ba5e2 0.15 0.146 +3fc2d0e560418937 0.15 0.147 +3fc2e147ae147ae1 0.15 0.148 +3ff799999999999a 1.48 1.475 +3fc2d0e560418938 0.15 0.147 +3fc2d0e560418936 0.15 0.147 +3fc2f1a9fbe76c8b 0.15 0.148 +3fc3020c49ba5e35 0.15 0.149 +3ff7c28f5c28f5c3 1.49 1.485 +3fc2f1a9fbe76c8c 0.15 0.148 +3fc2f1a9fbe76c8a 0.15 0.148 +3fc3126e978d4fdf 0.15 0.149 +3fc322d0e5604189 0.15 0.15 +3ff7eb851eb851ec 1.5 1.495 +3fc3126e978d4fe0 0.15 0.149 +3fc3126e978d4fde 0.15 0.149 +3fc3333333333333 0.15 0.15 +3fc34395810624dd 0.15 0.151 +3ff8147ae147ae14 1.51 1.505 +3fc3333333333334 0.15 0.15 +3fc3333333333332 0.15 0.15 +3fc353f7ced91687 0.15 0.151 +3fc3645a1cac0831 0.15 0.152 +3ff83d70a3d70a3d 1.52 1.515 +3fc353f7ced91688 0.15 0.151 +3fc353f7ced91686 0.15 0.151 +3fc374bc6a7ef9db 0.15 0.152 +3fc3851eb851eb85 0.15 0.153 +3ff8666666666666 1.53 1.525 +3fc374bc6a7ef9dc 0.15 0.152 +3fc374bc6a7ef9da 0.15 0.152 +3fc395810624dd2f 0.15 0.153 +3fc3a5e353f7ced9 0.15 0.154 +3ff88f5c28f5c28f 1.54 1.535 +3fc395810624dd30 0.15 0.153 +3fc395810624dd2e 0.15 0.153 +3fc3b645a1cac083 0.15 0.154 +3fc3c6a7ef9db22d 0.15 0.155 +3ff8b851eb851eb8 1.55 1.545 +3fc3b645a1cac084 0.15 0.154 +3fc3b645a1cac082 0.15 0.154 +3fc3e76c8b439581 0.16 0.156 +3ff8e147ae147ae1 1.56 1.555 +3fc3d70a3d70a3d8 0.16 0.155 +3fc3d70a3d70a3d6 0.15 0.155 +3fc3f7ced916872b 0.16 0.156 +3fc4083126e978d5 0.16 0.157 +3ff90a3d70a3d70a 1.57 1.565 +3fc3f7ced916872c 0.16 0.156 +3fc3f7ced916872a 0.16 0.156 +3fc4189374bc6a7f 0.16 0.157 +3fc428f5c28f5c29 0.16 0.158 +3ff9333333333333 1.58 1.575 +3fc4189374bc6a80 0.16 0.157 +3fc4189374bc6a7e 0.16 0.157 +3fc4395810624dd3 0.16 0.158 +3fc449ba5e353f7d 0.16 0.159 +3ff95c28f5c28f5c 1.59 1.585 +3fc4395810624dd4 0.16 0.158 +3fc4395810624dd2 0.16 0.158 +3fc45a1cac083127 0.16 0.159 +3fc46a7ef9db22d1 0.16 0.16 +3ff9851eb851eb85 1.6 1.595 +3fc45a1cac083128 0.16 0.159 +3fc45a1cac083126 0.16 0.159 +3fc47ae147ae147b 0.16 0.16 +3fc48b4395810625 0.16 0.161 +3ff9ae147ae147ae 1.61 1.605 +3fc47ae147ae147c 0.16 0.16 +3fc47ae147ae147a 0.16 0.16 +3fc49ba5e353f7cf 0.16 0.161 +3fc4ac083126e979 0.16 0.162 +3ff9d70a3d70a3d7 1.62 1.615 +3fc49ba5e353f7d0 0.16 0.161 +3fc49ba5e353f7ce 0.16 0.161 +3fc4bc6a7ef9db23 0.16 0.162 +3fc4cccccccccccd 0.16 0.163 +3ffa000000000000 1.63 1.625 +3fc4bc6a7ef9db24 0.16 0.162 +3fc4bc6a7ef9db22 0.16 0.162 +3fc4dd2f1a9fbe77 0.16 0.163 +3fc4ed916872b021 0.16 0.164 +3ffa28f5c28f5c29 1.64 1.635 +3fc4dd2f1a9fbe78 0.16 0.163 +3fc4dd2f1a9fbe76 0.16 0.163 +3fc4fdf3b645a1cb 0.16 0.164 +3fc50e5604189375 0.16 0.165 +3ffa51eb851eb852 1.65 1.645 +3fc4fdf3b645a1cc 0.16 0.164 +3fc4fdf3b645a1ca 0.16 0.164 +3fc52f1a9fbe76c9 0.17 0.166 +3ffa7ae147ae147b 1.66 1.655 +3fc51eb851eb8520 0.17 0.165 +3fc51eb851eb851e 0.16 0.165 +3fc53f7ced916873 0.17 0.166 +3fc54fdf3b645a1d 0.17 0.167 +3ffaa3d70a3d70a4 1.67 1.665 +3fc53f7ced916874 0.17 0.166 +3fc53f7ced916872 0.17 0.166 +3fc5604189374bc7 0.17 0.167 +3fc570a3d70a3d71 0.17 0.168 +3ffacccccccccccd 1.68 1.675 +3fc5604189374bc8 0.17 0.167 +3fc5604189374bc6 0.17 0.167 +3fc5810624dd2f1b 0.17 0.168 +3fc5916872b020c5 0.17 0.169 +3ffaf5c28f5c28f6 1.69 1.685 +3fc5810624dd2f1c 0.17 0.168 +3fc5810624dd2f1a 0.17 0.168 +3fc5a1cac083126f 0.17 0.169 +3fc5b22d0e560419 0.17 0.17 +3ffb1eb851eb851f 1.7 1.695 +3fc5a1cac0831270 0.17 0.169 +3fc5a1cac083126e 0.17 0.169 +3fc5c28f5c28f5c3 0.17 0.17 +3fc5d2f1a9fbe76d 0.17 0.171 +3ffb47ae147ae148 1.71 1.705 +3fc5c28f5c28f5c4 0.17 0.17 +3fc5c28f5c28f5c2 0.17 0.17 +3fc5e353f7ced917 0.17 0.171 +3fc5f3b645a1cac1 0.17 0.172 +3ffb70a3d70a3d71 1.72 1.715 +3fc5e353f7ced918 0.17 0.171 +3fc5e353f7ced916 0.17 0.171 +3fc604189374bc6a 0.17 0.172 +3fc6147ae147ae14 0.17 0.173 +3ffb99999999999a 1.73 1.725 +3fc604189374bc6b 0.17 0.172 +3fc604189374bc69 0.17 0.172 +3fc624dd2f1a9fbe 0.17 0.173 +3fc6353f7ced9168 0.17 0.174 +3ffbc28f5c28f5c3 1.74 1.735 +3fc624dd2f1a9fbf 0.17 0.173 +3fc624dd2f1a9fbd 0.17 0.173 +3fc645a1cac08312 0.17 0.174 +3fc65604189374bc 0.17 0.175 +3ffbeb851eb851ec 1.75 1.745 +3fc645a1cac08313 0.17 0.174 +3fc645a1cac08311 0.17 0.174 +3fc676c8b4395810 0.18 0.176 +3ffc147ae147ae14 1.76 1.755 +3fc6666666666667 0.18 0.175 +3fc6666666666665 0.17 0.175 +3fc6872b020c49ba 0.18 0.176 +3fc6978d4fdf3b64 0.18 0.177 +3ffc3d70a3d70a3d 1.77 1.765 +3fc6872b020c49bb 0.18 0.176 +3fc6872b020c49b9 0.18 0.176 +3fc6a7ef9db22d0e 0.18 0.177 +3fc6b851eb851eb8 0.18 0.178 +3ffc666666666666 1.78 1.775 +3fc6a7ef9db22d0f 0.18 0.177 +3fc6a7ef9db22d0d 0.18 0.177 +3fc6c8b439581062 0.18 0.178 +3fc6d916872b020c 0.18 0.179 +3ffc8f5c28f5c28f 1.79 1.785 +3fc6c8b439581063 0.18 0.178 +3fc6c8b439581061 0.18 0.178 +3fc6e978d4fdf3b6 0.18 0.179 +3fc6f9db22d0e560 0.18 0.18 +3ffcb851eb851eb8 1.8 1.795 +3fc6e978d4fdf3b7 0.18 0.179 +3fc6e978d4fdf3b5 0.18 0.179 +3fc70a3d70a3d70a 0.18 0.18 +3fc71a9fbe76c8b4 0.18 0.181 +3ffce147ae147ae1 1.81 1.805 +3fc70a3d70a3d70b 0.18 0.18 +3fc70a3d70a3d709 0.18 0.18 +3fc72b020c49ba5e 0.18 0.181 +3fc73b645a1cac08 0.18 0.182 +3ffd0a3d70a3d70a 1.82 1.815 +3fc72b020c49ba5f 0.18 0.181 +3fc72b020c49ba5d 0.18 0.181 +3fc74bc6a7ef9db2 0.18 0.182 +3fc75c28f5c28f5c 0.18 0.183 +3ffd333333333333 1.83 1.825 +3fc74bc6a7ef9db3 0.18 0.182 +3fc74bc6a7ef9db1 0.18 0.182 +3fc76c8b43958106 0.18 0.183 +3fc77ced916872b0 0.18 0.184 +3ffd5c28f5c28f5c 1.84 1.835 +3fc76c8b43958107 0.18 0.183 +3fc76c8b43958105 0.18 0.183 +3fc78d4fdf3b645a 0.18 0.184 +3fc79db22d0e5604 0.18 0.185 +3ffd851eb851eb85 1.85 1.845 +3fc78d4fdf3b645b 0.18 0.184 +3fc78d4fdf3b6459 0.18 0.184 +3fc7be76c8b43958 0.19 0.186 +3ffdae147ae147ae 1.86 1.855 +3fc7ae147ae147af 0.19 0.185 +3fc7ae147ae147ad 0.18 0.185 +3fc7ced916872b02 0.19 0.186 +3fc7df3b645a1cac 0.19 0.187 +3ffdd70a3d70a3d7 1.87 1.865 +3fc7ced916872b03 0.19 0.186 +3fc7ced916872b01 0.19 0.186 +3fc7ef9db22d0e56 0.19 0.187 +3fc8000000000000 0.19 0.188 +3ffe000000000000 1.88 1.875 +3fc7ef9db22d0e57 0.19 0.187 +3fc7ef9db22d0e55 0.19 0.187 +3fc810624dd2f1aa 0.19 0.188 +3fc820c49ba5e354 0.19 0.189 +3ffe28f5c28f5c29 1.89 1.885 +3fc810624dd2f1ab 0.19 0.188 +3fc810624dd2f1a9 0.19 0.188 +3fc83126e978d4fe 0.19 0.189 +3fc84189374bc6a8 0.19 0.19 +3ffe51eb851eb852 1.9 1.895 +3fc83126e978d4ff 0.19 0.189 +3fc83126e978d4fd 0.19 0.189 +3fc851eb851eb852 0.19 0.19 +3fc8624dd2f1a9fc 0.19 0.191 +3ffe7ae147ae147b 1.91 1.905 +3fc851eb851eb853 0.19 0.19 +3fc851eb851eb851 0.19 0.19 +3fc872b020c49ba6 0.19 0.191 +3fc883126e978d50 0.19 0.192 +3ffea3d70a3d70a4 1.92 1.915 +3fc872b020c49ba7 0.19 0.191 +3fc872b020c49ba5 0.19 0.191 +3fc89374bc6a7efa 0.19 0.192 +3fc8a3d70a3d70a4 0.19 0.193 +3ffecccccccccccd 1.93 1.925 +3fc89374bc6a7efb 0.19 0.192 +3fc89374bc6a7ef9 0.19 0.192 +3fc8b4395810624e 0.19 0.193 +3fc8c49ba5e353f8 0.19 0.194 +3ffef5c28f5c28f6 1.94 1.935 +3fc8b4395810624f 0.19 0.193 +3fc8b4395810624d 0.19 0.193 +3fc8d4fdf3b645a2 0.19 0.194 +3fc8e5604189374c 0.19 0.195 +3fff1eb851eb851f 1.95 1.945 +3fc8d4fdf3b645a3 0.19 0.194 +3fc8d4fdf3b645a1 0.19 0.194 +3fc90624dd2f1aa0 0.2 0.196 +3fff47ae147ae148 1.96 1.955 +3fc8f5c28f5c28f7 0.2 0.195 +3fc8f5c28f5c28f5 0.19 0.195 +3fc916872b020c4a 0.2 0.196 +3fc926e978d4fdf4 0.2 0.197 +3fff70a3d70a3d71 1.97 1.965 +3fc916872b020c4b 0.2 0.196 +3fc916872b020c49 0.2 0.196 +3fc9374bc6a7ef9e 0.2 0.197 +3fc947ae147ae148 0.2 0.198 +3fff99999999999a 1.98 1.975 +3fc9374bc6a7ef9f 0.2 0.197 +3fc9374bc6a7ef9d 0.2 0.197 +3fc95810624dd2f2 0.2 0.198 +3fc96872b020c49c 0.2 0.199 +3fffc28f5c28f5c3 1.99 1.985 +3fc95810624dd2f3 0.2 0.198 +3fc95810624dd2f1 0.2 0.198 +3fc978d4fdf3b646 0.2 0.199 +3fc989374bc6a7f0 0.2 0.2 +3fffeb851eb851ec 2.0 1.995 +3fc978d4fdf3b647 0.2 0.199 +3fc978d4fdf3b645 0.2 0.199 +3fc999999999999a 0.2 0.2 +3fc9a9fbe76c8b44 0.2 0.201 +40000a3d70a3d70a 2.01 2.005 +3fc999999999999b 0.2 0.2 +3fc9999999999999 0.2 0.2 +3fc9ba5e353f7cee 0.2 0.201 +3fc9cac083126e98 0.2 0.202 +40001eb851eb851f 2.02 2.015 +3fc9ba5e353f7cef 0.2 0.201 +3fc9ba5e353f7ced 0.2 0.201 +3fc9db22d0e56042 0.2 0.202 +3fc9eb851eb851ec 0.2 0.203 +4000333333333333 2.03 2.025 +3fc9db22d0e56043 0.2 0.202 +3fc9db22d0e56041 0.2 0.202 +3fc9fbe76c8b4396 0.2 0.203 +3fca0c49ba5e353f 0.2 0.204 +400047ae147ae148 2.04 2.035 +3fc9fbe76c8b4397 0.2 0.203 +3fc9fbe76c8b4395 0.2 0.203 +3fca1cac083126e9 0.2 0.204 +3fca2d0e56041893 0.2 0.205 +40005c28f5c28f5c 2.05 2.045 +3fca1cac083126ea 0.2 0.204 +3fca1cac083126e8 0.2 0.204 +3fca4dd2f1a9fbe7 0.21 0.206 +400070a3d70a3d71 2.06 2.055 +3fca3d70a3d70a3e 0.21 0.205 +3fca3d70a3d70a3c 0.2 0.205 +3fca5e353f7ced91 0.21 0.206 +3fca6e978d4fdf3b 0.21 0.207 +4000851eb851eb85 2.07 2.065 +3fca5e353f7ced92 0.21 0.206 +3fca5e353f7ced90 0.21 0.206 +3fca7ef9db22d0e5 0.21 0.207 +3fca8f5c28f5c28f 0.21 0.208 +400099999999999a 2.08 2.075 +3fca7ef9db22d0e6 0.21 0.207 +3fca7ef9db22d0e4 0.21 0.207 +3fca9fbe76c8b439 0.21 0.208 +3fcab020c49ba5e3 0.21 0.209 +4000ae147ae147ae 2.09 2.085 +3fca9fbe76c8b43a 0.21 0.208 +3fca9fbe76c8b438 0.21 0.208 +3fcac083126e978d 0.21 0.209 +3fcad0e560418937 0.21 0.21 +4000c28f5c28f5c3 2.1 2.095 +3fcac083126e978e 0.21 0.209 +3fcac083126e978c 0.21 0.209 +3fcae147ae147ae1 0.21 0.21 +3fcaf1a9fbe76c8b 0.21 0.211 +4000d70a3d70a3d7 2.11 2.105 +3fcae147ae147ae2 0.21 0.21 +3fcae147ae147ae0 0.21 0.21 +3fcb020c49ba5e35 0.21 0.211 +3fcb126e978d4fdf 0.21 0.212 +4000eb851eb851ec 2.12 2.115 +3fcb020c49ba5e36 0.21 0.211 +3fcb020c49ba5e34 0.21 0.211 +3fcb22d0e5604189 0.21 0.212 +3fcb333333333333 0.21 0.213 +4001000000000000 2.13 2.125 +3fcb22d0e560418a 0.21 0.212 +3fcb22d0e5604188 0.21 0.212 +3fcb4395810624dd 0.21 0.213 +3fcb53f7ced91687 0.21 0.214 +4001147ae147ae14 2.14 2.135 +3fcb4395810624de 0.21 0.213 +3fcb4395810624dc 0.21 0.213 +3fcb645a1cac0831 0.21 0.214 +3fcb74bc6a7ef9db 0.21 0.215 +400128f5c28f5c29 2.15 2.145 +3fcb645a1cac0832 0.21 0.214 +3fcb645a1cac0830 0.21 0.214 +3fcb95810624dd2f 0.22 0.216 +40013d70a3d70a3d 2.16 2.155 +3fcb851eb851eb86 0.22 0.215 +3fcb851eb851eb84 0.21 0.215 +3fcba5e353f7ced9 0.22 0.216 +3fcbb645a1cac083 0.22 0.217 +400151eb851eb852 2.17 2.165 +3fcba5e353f7ceda 0.22 0.216 +3fcba5e353f7ced8 0.22 0.216 +3fcbc6a7ef9db22d 0.22 0.217 +3fcbd70a3d70a3d7 0.22 0.218 +4001666666666666 2.18 2.175 +3fcbc6a7ef9db22e 0.22 0.217 +3fcbc6a7ef9db22c 0.22 0.217 +3fcbe76c8b439581 0.22 0.218 +3fcbf7ced916872b 0.22 0.219 +40017ae147ae147b 2.19 2.185 +3fcbe76c8b439582 0.22 0.218 +3fcbe76c8b439580 0.22 0.218 +3fcc083126e978d5 0.22 0.219 +3fcc189374bc6a7f 0.22 0.22 +40018f5c28f5c28f 2.2 2.195 +3fcc083126e978d6 0.22 0.219 +3fcc083126e978d4 0.22 0.219 +3fcc28f5c28f5c29 0.22 0.22 +3fcc395810624dd3 0.22 0.221 +4001a3d70a3d70a4 2.21 2.205 +3fcc28f5c28f5c2a 0.22 0.22 +3fcc28f5c28f5c28 0.22 0.22 +3fcc49ba5e353f7d 0.22 0.221 +3fcc5a1cac083127 0.22 0.222 +4001b851eb851eb8 2.22 2.215 +3fcc49ba5e353f7e 0.22 0.221 +3fcc49ba5e353f7c 0.22 0.221 +3fcc6a7ef9db22d1 0.22 0.222 +3fcc7ae147ae147b 0.22 0.223 +4001cccccccccccd 2.23 2.225 +3fcc6a7ef9db22d2 0.22 0.222 +3fcc6a7ef9db22d0 0.22 0.222 +3fcc8b4395810625 0.22 0.223 +3fcc9ba5e353f7cf 0.22 0.224 +4001e147ae147ae1 2.24 2.235 +3fcc8b4395810626 0.22 0.223 +3fcc8b4395810624 0.22 0.223 +3fccac083126e979 0.22 0.224 +3fccbc6a7ef9db23 0.22 0.225 +4001f5c28f5c28f6 2.25 2.245 +3fccac083126e97a 0.22 0.224 +3fccac083126e978 0.22 0.224 +3fccdd2f1a9fbe77 0.23 0.226 +40020a3d70a3d70a 2.26 2.255 +3fccccccccccccce 0.23 0.225 +3fcccccccccccccc 0.22 0.225 +3fcced916872b021 0.23 0.226 +3fccfdf3b645a1cb 0.23 0.227 +40021eb851eb851f 2.27 2.265 +3fcced916872b022 0.23 0.226 +3fcced916872b020 0.23 0.226 +3fcd0e5604189375 0.23 0.227 +3fcd1eb851eb851f 0.23 0.228 +4002333333333333 2.28 2.275 +3fcd0e5604189376 0.23 0.227 +3fcd0e5604189374 0.23 0.227 +3fcd2f1a9fbe76c9 0.23 0.228 +3fcd3f7ced916873 0.23 0.229 +400247ae147ae148 2.29 2.285 +3fcd2f1a9fbe76ca 0.23 0.228 +3fcd2f1a9fbe76c8 0.23 0.228 +3fcd4fdf3b645a1d 0.23 0.229 +3fcd604189374bc7 0.23 0.23 +40025c28f5c28f5c 2.3 2.295 +3fcd4fdf3b645a1e 0.23 0.229 +3fcd4fdf3b645a1c 0.23 0.229 +3fcd70a3d70a3d71 0.23 0.23 +3fcd810624dd2f1b 0.23 0.231 +400270a3d70a3d71 2.31 2.305 +3fcd70a3d70a3d72 0.23 0.23 +3fcd70a3d70a3d70 0.23 0.23 +3fcd916872b020c5 0.23 0.231 +3fcda1cac083126f 0.23 0.232 +4002851eb851eb85 2.32 2.315 +3fcd916872b020c6 0.23 0.231 +3fcd916872b020c4 0.23 0.231 +3fcdb22d0e560419 0.23 0.232 +3fcdc28f5c28f5c3 0.23 0.233 +400299999999999a 2.33 2.325 +3fcdb22d0e56041a 0.23 0.232 +3fcdb22d0e560418 0.23 0.232 +3fcdd2f1a9fbe76d 0.23 0.233 +3fcde353f7ced917 0.23 0.234 +4002ae147ae147ae 2.34 2.335 +3fcdd2f1a9fbe76e 0.23 0.233 +3fcdd2f1a9fbe76c 0.23 0.233 +3fcdf3b645a1cac1 0.23 0.234 +3fce04189374bc6a 0.23 0.235 +4002c28f5c28f5c3 2.35 2.345 +3fcdf3b645a1cac2 0.23 0.234 +3fcdf3b645a1cac0 0.23 0.234 +3fce24dd2f1a9fbe 0.24 0.236 +4002d70a3d70a3d7 2.36 2.355 +3fce147ae147ae15 0.24 0.235 +3fce147ae147ae13 0.23 0.235 +3fce353f7ced9168 0.24 0.236 +3fce45a1cac08312 0.24 0.237 +4002eb851eb851ec 2.37 2.365 +3fce353f7ced9169 0.24 0.236 +3fce353f7ced9167 0.24 0.236 +3fce5604189374bc 0.24 0.237 +3fce666666666666 0.24 0.238 +4003000000000000 2.38 2.375 +3fce5604189374bd 0.24 0.237 +3fce5604189374bb 0.24 0.237 +3fce76c8b4395810 0.24 0.238 +3fce872b020c49ba 0.24 0.239 +4003147ae147ae14 2.39 2.385 +3fce76c8b4395811 0.24 0.238 +3fce76c8b439580f 0.24 0.238 +3fce978d4fdf3b64 0.24 0.239 +3fcea7ef9db22d0e 0.24 0.24 +400328f5c28f5c29 2.4 2.395 +3fce978d4fdf3b65 0.24 0.239 +3fce978d4fdf3b63 0.24 0.239 +3fceb851eb851eb8 0.24 0.24 +3fcec8b439581062 0.24 0.241 +40033d70a3d70a3d 2.41 2.405 +3fceb851eb851eb9 0.24 0.24 +3fceb851eb851eb7 0.24 0.24 +3fced916872b020c 0.24 0.241 +3fcee978d4fdf3b6 0.24 0.242 +400351eb851eb852 2.42 2.415 +3fced916872b020d 0.24 0.241 +3fced916872b020b 0.24 0.241 +3fcef9db22d0e560 0.24 0.242 +3fcf0a3d70a3d70a 0.24 0.243 +4003666666666666 2.43 2.425 +3fcef9db22d0e561 0.24 0.242 +3fcef9db22d0e55f 0.24 0.242 +3fcf1a9fbe76c8b4 0.24 0.243 +3fcf2b020c49ba5e 0.24 0.244 +40037ae147ae147b 2.44 2.435 +3fcf1a9fbe76c8b5 0.24 0.243 +3fcf1a9fbe76c8b3 0.24 0.243 +3fcf3b645a1cac08 0.24 0.244 +3fcf4bc6a7ef9db2 0.24 0.245 +40038f5c28f5c28f 2.45 2.445 +3fcf3b645a1cac09 0.24 0.244 +3fcf3b645a1cac07 0.24 0.244 +3fcf6c8b43958106 0.25 0.246 +4003a3d70a3d70a4 2.46 2.455 +3fcf5c28f5c28f5d 0.25 0.245 +3fcf5c28f5c28f5b 0.24 0.245 +3fcf7ced916872b0 0.25 0.246 +3fcf8d4fdf3b645a 0.25 0.247 +4003b851eb851eb8 2.47 2.465 +3fcf7ced916872b1 0.25 0.246 +3fcf7ced916872af 0.25 0.246 +3fcf9db22d0e5604 0.25 0.247 +3fcfae147ae147ae 0.25 0.248 +4003cccccccccccd 2.48 2.475 +3fcf9db22d0e5605 0.25 0.247 +3fcf9db22d0e5603 0.25 0.247 +3fcfbe76c8b43958 0.25 0.248 +3fcfced916872b02 0.25 0.249 +4003e147ae147ae1 2.49 2.485 +3fcfbe76c8b43959 0.25 0.248 +3fcfbe76c8b43957 0.25 0.248 +3fcfdf3b645a1cac 0.25 0.249 +3fcfef9db22d0e56 0.25 0.25 +4003f5c28f5c28f6 2.5 2.495 +3fcfdf3b645a1cad 0.25 0.249 +3fcfdf3b645a1cab 0.25 0.249 +3fd0000000000000 0.25 0.25 +3fd0083126e978d5 0.25 0.251 +40040a3d70a3d70a 2.51 2.505 +3fd0000000000001 0.25 0.25 +3fcfffffffffffff 0.25 0.25 +3fd010624dd2f1aa 0.25 0.251 +3fd0189374bc6a7f 0.25 0.252 +40041eb851eb851f 2.52 2.515 +3fd010624dd2f1ab 0.25 0.251 +3fd010624dd2f1a9 0.25 0.251 +3fd020c49ba5e354 0.25 0.252 +3fd028f5c28f5c29 0.25 0.253 +4004333333333333 2.53 2.525 +3fd020c49ba5e355 0.25 0.252 +3fd020c49ba5e353 0.25 0.252 +3fd03126e978d4fe 0.25 0.253 +3fd0395810624dd3 0.25 0.254 +400447ae147ae148 2.54 2.535 +3fd03126e978d4ff 0.25 0.253 +3fd03126e978d4fd 0.25 0.253 +3fd04189374bc6a8 0.25 0.254 +3fd049ba5e353f7d 0.25 0.255 +40045c28f5c28f5c 2.55 2.545 +3fd04189374bc6a9 0.25 0.254 +3fd04189374bc6a7 0.25 0.254 +3fd05a1cac083127 0.26 0.256 +400470a3d70a3d71 2.56 2.555 +3fd051eb851eb853 0.26 0.255 +3fd051eb851eb851 0.25 0.255 +3fd0624dd2f1a9fc 0.26 0.256 +3fd06a7ef9db22d1 0.26 0.257 +4004851eb851eb85 2.57 2.565 +3fd0624dd2f1a9fd 0.26 0.256 +3fd0624dd2f1a9fb 0.26 0.256 +3fd072b020c49ba6 0.26 0.257 +3fd07ae147ae147b 0.26 0.258 +400499999999999a 2.58 2.575 +3fd072b020c49ba7 0.26 0.257 +3fd072b020c49ba5 0.26 0.257 +3fd083126e978d50 0.26 0.258 +3fd08b4395810625 0.26 0.259 +4004ae147ae147ae 2.59 2.585 +3fd083126e978d51 0.26 0.258 +3fd083126e978d4f 0.26 0.258 +3fd09374bc6a7efa 0.26 0.259 +3fd09ba5e353f7cf 0.26 0.26 +4004c28f5c28f5c3 2.6 2.595 +3fd09374bc6a7efb 0.26 0.259 +3fd09374bc6a7ef9 0.26 0.259 +3fd0a3d70a3d70a4 0.26 0.26 +3fd0ac083126e979 0.26 0.261 +4004d70a3d70a3d7 2.61 2.605 +3fd0a3d70a3d70a5 0.26 0.26 +3fd0a3d70a3d70a3 0.26 0.26 +3fd0b4395810624e 0.26 0.261 +3fd0bc6a7ef9db23 0.26 0.262 +4004eb851eb851ec 2.62 2.615 +3fd0b4395810624f 0.26 0.261 +3fd0b4395810624d 0.26 0.261 +3fd0c49ba5e353f8 0.26 0.262 +3fd0cccccccccccd 0.26 0.263 +4005000000000000 2.63 2.625 +3fd0c49ba5e353f9 0.26 0.262 +3fd0c49ba5e353f7 0.26 0.262 +3fd0d4fdf3b645a2 0.26 0.263 +3fd0dd2f1a9fbe77 0.26 0.264 +4005147ae147ae14 2.64 2.635 +3fd0d4fdf3b645a3 0.26 0.263 +3fd0d4fdf3b645a1 0.26 0.263 +3fd0e5604189374c 0.26 0.264 +3fd0ed916872b021 0.26 0.265 +400528f5c28f5c29 2.65 2.645 +3fd0e5604189374d 0.26 0.264 +3fd0e5604189374b 0.26 0.264 +3fd0fdf3b645a1cb 0.27 0.266 +40053d70a3d70a3d 2.66 2.655 +3fd0f5c28f5c28f7 0.27 0.265 +3fd0f5c28f5c28f5 0.26 0.265 +3fd10624dd2f1aa0 0.27 0.266 +3fd10e5604189375 0.27 0.267 +400551eb851eb852 2.67 2.665 +3fd10624dd2f1aa1 0.27 0.266 +3fd10624dd2f1a9f 0.27 0.266 +3fd116872b020c4a 0.27 0.267 +3fd11eb851eb851f 0.27 0.268 +4005666666666666 2.68 2.675 +3fd116872b020c4b 0.27 0.267 +3fd116872b020c49 0.27 0.267 +3fd126e978d4fdf4 0.27 0.268 +3fd12f1a9fbe76c9 0.27 0.269 +40057ae147ae147b 2.69 2.685 +3fd126e978d4fdf5 0.27 0.268 +3fd126e978d4fdf3 0.27 0.268 +3fd1374bc6a7ef9e 0.27 0.269 +3fd13f7ced916873 0.27 0.27 +40058f5c28f5c28f 2.7 2.695 +3fd1374bc6a7ef9f 0.27 0.269 +3fd1374bc6a7ef9d 0.27 0.269 +3fd147ae147ae148 0.27 0.27 +3fd14fdf3b645a1d 0.27 0.271 +4005a3d70a3d70a4 2.71 2.705 +3fd147ae147ae149 0.27 0.27 +3fd147ae147ae147 0.27 0.27 +3fd15810624dd2f2 0.27 0.271 +3fd1604189374bc7 0.27 0.272 +4005b851eb851eb8 2.72 2.715 +3fd15810624dd2f3 0.27 0.271 +3fd15810624dd2f1 0.27 0.271 +3fd16872b020c49c 0.27 0.272 +3fd170a3d70a3d71 0.27 0.273 +4005cccccccccccd 2.73 2.725 +3fd16872b020c49d 0.27 0.272 +3fd16872b020c49b 0.27 0.272 +3fd178d4fdf3b646 0.27 0.273 +3fd1810624dd2f1b 0.27 0.274 +4005e147ae147ae1 2.74 2.735 +3fd178d4fdf3b647 0.27 0.273 +3fd178d4fdf3b645 0.27 0.273 +3fd189374bc6a7f0 0.27 0.274 +3fd1916872b020c5 0.27 0.275 +4005f5c28f5c28f6 2.75 2.745 +3fd189374bc6a7f1 0.27 0.274 +3fd189374bc6a7ef 0.27 0.274 +3fd1a1cac083126f 0.28 0.276 +40060a3d70a3d70a 2.76 2.755 +3fd199999999999b 0.28 0.275 +3fd1999999999999 0.27 0.275 +3fd1a9fbe76c8b44 0.28 0.276 +3fd1b22d0e560419 0.28 0.277 +40061eb851eb851f 2.77 2.765 +3fd1a9fbe76c8b45 0.28 0.276 +3fd1a9fbe76c8b43 0.28 0.276 +3fd1ba5e353f7cee 0.28 0.277 +3fd1c28f5c28f5c3 0.28 0.278 +4006333333333333 2.78 2.775 +3fd1ba5e353f7cef 0.28 0.277 +3fd1ba5e353f7ced 0.28 0.277 +3fd1cac083126e98 0.28 0.278 +3fd1d2f1a9fbe76d 0.28 0.279 +400647ae147ae148 2.79 2.785 +3fd1cac083126e99 0.28 0.278 +3fd1cac083126e97 0.28 0.278 +3fd1db22d0e56042 0.28 0.279 +3fd1e353f7ced917 0.28 0.28 +40065c28f5c28f5c 2.8 2.795 +3fd1db22d0e56043 0.28 0.279 +3fd1db22d0e56041 0.28 0.279 +3fd1eb851eb851ec 0.28 0.28 +3fd1f3b645a1cac1 0.28 0.281 +400670a3d70a3d71 2.81 2.805 +3fd1eb851eb851ed 0.28 0.28 +3fd1eb851eb851eb 0.28 0.28 +3fd1fbe76c8b4396 0.28 0.281 +3fd204189374bc6a 0.28 0.282 +4006851eb851eb85 2.82 2.815 +3fd1fbe76c8b4397 0.28 0.281 +3fd1fbe76c8b4395 0.28 0.281 +3fd20c49ba5e353f 0.28 0.282 +3fd2147ae147ae14 0.28 0.283 +400699999999999a 2.83 2.825 +3fd20c49ba5e3540 0.28 0.282 +3fd20c49ba5e353e 0.28 0.282 +3fd21cac083126e9 0.28 0.283 +3fd224dd2f1a9fbe 0.28 0.284 +4006ae147ae147ae 2.84 2.835 +3fd21cac083126ea 0.28 0.283 +3fd21cac083126e8 0.28 0.283 +3fd22d0e56041893 0.28 0.284 +3fd2353f7ced9168 0.28 0.285 +4006c28f5c28f5c3 2.85 2.845 +3fd22d0e56041894 0.28 0.284 +3fd22d0e56041892 0.28 0.284 +3fd245a1cac08312 0.29 0.286 +4006d70a3d70a3d7 2.86 2.855 +3fd23d70a3d70a3e 0.29 0.285 +3fd23d70a3d70a3c 0.28 0.285 +3fd24dd2f1a9fbe7 0.29 0.286 +3fd25604189374bc 0.29 0.287 +4006eb851eb851ec 2.87 2.865 +3fd24dd2f1a9fbe8 0.29 0.286 +3fd24dd2f1a9fbe6 0.29 0.286 +3fd25e353f7ced91 0.29 0.287 +3fd2666666666666 0.29 0.288 +4007000000000000 2.88 2.875 +3fd25e353f7ced92 0.29 0.287 +3fd25e353f7ced90 0.29 0.287 +3fd26e978d4fdf3b 0.29 0.288 +3fd276c8b4395810 0.29 0.289 +4007147ae147ae14 2.89 2.885 +3fd26e978d4fdf3c 0.29 0.288 +3fd26e978d4fdf3a 0.29 0.288 +3fd27ef9db22d0e5 0.29 0.289 +3fd2872b020c49ba 0.29 0.29 +400728f5c28f5c29 2.9 2.895 +3fd27ef9db22d0e6 0.29 0.289 +3fd27ef9db22d0e4 0.29 0.289 +3fd28f5c28f5c28f 0.29 0.29 +3fd2978d4fdf3b64 0.29 0.291 +40073d70a3d70a3d 2.91 2.905 +3fd28f5c28f5c290 0.29 0.29 +3fd28f5c28f5c28e 0.29 0.29 +3fd29fbe76c8b439 0.29 0.291 +3fd2a7ef9db22d0e 0.29 0.292 +400751eb851eb852 2.92 2.915 +3fd29fbe76c8b43a 0.29 0.291 +3fd29fbe76c8b438 0.29 0.291 +3fd2b020c49ba5e3 0.29 0.292 +3fd2b851eb851eb8 0.29 0.293 +4007666666666666 2.93 2.925 +3fd2b020c49ba5e4 0.29 0.292 +3fd2b020c49ba5e2 0.29 0.292 +3fd2c083126e978d 0.29 0.293 +3fd2c8b439581062 0.29 0.294 +40077ae147ae147b 2.94 2.935 +3fd2c083126e978e 0.29 0.293 +3fd2c083126e978c 0.29 0.293 +3fd2d0e560418937 0.29 0.294 +3fd2d916872b020c 0.29 0.295 +40078f5c28f5c28f 2.95 2.945 +3fd2d0e560418938 0.29 0.294 +3fd2d0e560418936 0.29 0.294 +3fd2e978d4fdf3b6 0.3 0.296 +4007a3d70a3d70a4 2.96 2.955 +3fd2e147ae147ae2 0.3 0.295 +3fd2e147ae147ae0 0.29 0.295 +3fd2f1a9fbe76c8b 0.3 0.296 +3fd2f9db22d0e560 0.3 0.297 +4007b851eb851eb8 2.97 2.965 +3fd2f1a9fbe76c8c 0.3 0.296 +3fd2f1a9fbe76c8a 0.3 0.296 +3fd3020c49ba5e35 0.3 0.297 +3fd30a3d70a3d70a 0.3 0.298 +4007cccccccccccd 2.98 2.975 +3fd3020c49ba5e36 0.3 0.297 +3fd3020c49ba5e34 0.3 0.297 +3fd3126e978d4fdf 0.3 0.298 +3fd31a9fbe76c8b4 0.3 0.299 +4007e147ae147ae1 2.99 2.985 +3fd3126e978d4fe0 0.3 0.298 +3fd3126e978d4fde 0.3 0.298 +3fd322d0e5604189 0.3 0.299 +3fd32b020c49ba5e 0.3 0.3 +4007f5c28f5c28f6 3.0 2.995 +3fd322d0e560418a 0.3 0.299 +3fd322d0e5604188 0.3 0.299 +3fd3333333333333 0.3 0.3 +3fd33b645a1cac08 0.3 0.301 +40080a3d70a3d70a 3.01 3.005 +3fd3333333333334 0.3 0.3 +3fd3333333333332 0.3 0.3 +3fd34395810624dd 0.3 0.301 +3fd34bc6a7ef9db2 0.3 0.302 +40081eb851eb851f 3.02 3.015 +3fd34395810624de 0.3 0.301 +3fd34395810624dc 0.3 0.301 +3fd353f7ced91687 0.3 0.302 +3fd35c28f5c28f5c 0.3 0.303 +4008333333333333 3.03 3.025 +3fd353f7ced91688 0.3 0.302 +3fd353f7ced91686 0.3 0.302 +3fd3645a1cac0831 0.3 0.303 +3fd36c8b43958106 0.3 0.304 +400847ae147ae148 3.04 3.035 +3fd3645a1cac0832 0.3 0.303 +3fd3645a1cac0830 0.3 0.303 +3fd374bc6a7ef9db 0.3 0.304 +3fd37ced916872b0 0.3 0.305 +40085c28f5c28f5c 3.05 3.045 +3fd374bc6a7ef9dc 0.3 0.304 +3fd374bc6a7ef9da 0.3 0.304 +3fd38d4fdf3b645a 0.31 0.306 +400870a3d70a3d71 3.06 3.055 +3fd3851eb851eb86 0.31 0.305 +3fd3851eb851eb84 0.3 0.305 +3fd395810624dd2f 0.31 0.306 +3fd39db22d0e5604 0.31 0.307 +4008851eb851eb85 3.07 3.065 +3fd395810624dd30 0.31 0.306 +3fd395810624dd2e 0.31 0.306 +3fd3a5e353f7ced9 0.31 0.307 +3fd3ae147ae147ae 0.31 0.308 +400899999999999a 3.08 3.075 +3fd3a5e353f7ceda 0.31 0.307 +3fd3a5e353f7ced8 0.31 0.307 +3fd3b645a1cac083 0.31 0.308 +3fd3be76c8b43958 0.31 0.309 +4008ae147ae147ae 3.09 3.085 +3fd3b645a1cac084 0.31 0.308 +3fd3b645a1cac082 0.31 0.308 +3fd3c6a7ef9db22d 0.31 0.309 +3fd3ced916872b02 0.31 0.31 +4008c28f5c28f5c3 3.1 3.095 +3fd3c6a7ef9db22e 0.31 0.309 +3fd3c6a7ef9db22c 0.31 0.309 +3fd3d70a3d70a3d7 0.31 0.31 +3fd3df3b645a1cac 0.31 0.311 +4008d70a3d70a3d7 3.11 3.105 +3fd3d70a3d70a3d8 0.31 0.31 +3fd3d70a3d70a3d6 0.31 0.31 +3fd3e76c8b439581 0.31 0.311 +3fd3ef9db22d0e56 0.31 0.312 +4008eb851eb851ec 3.12 3.115 +3fd3e76c8b439582 0.31 0.311 +3fd3e76c8b439580 0.31 0.311 +3fd3f7ced916872b 0.31 0.312 +3fd4000000000000 0.31 0.313 +4009000000000000 3.13 3.125 +3fd3f7ced916872c 0.31 0.312 +3fd3f7ced916872a 0.31 0.312 +3fd4083126e978d5 0.31 0.313 +3fd410624dd2f1aa 0.31 0.314 +4009147ae147ae14 3.14 3.135 +3fd4083126e978d6 0.31 0.313 +3fd4083126e978d4 0.31 0.313 +3fd4189374bc6a7f 0.31 0.314 +3fd420c49ba5e354 0.31 0.315 +400928f5c28f5c29 3.15 3.145 +3fd4189374bc6a80 0.31 0.314 +3fd4189374bc6a7e 0.31 0.314 +3fd43126e978d4fe 0.32 0.316 +40093d70a3d70a3d 3.16 3.155 +3fd428f5c28f5c2a 0.32 0.315 +3fd428f5c28f5c28 0.31 0.315 +3fd4395810624dd3 0.32 0.316 +3fd44189374bc6a8 0.32 0.317 +400951eb851eb852 3.17 3.165 +3fd4395810624dd4 0.32 0.316 +3fd4395810624dd2 0.32 0.316 +3fd449ba5e353f7d 0.32 0.317 +3fd451eb851eb852 0.32 0.318 +4009666666666666 3.18 3.175 +3fd449ba5e353f7e 0.32 0.317 +3fd449ba5e353f7c 0.32 0.317 +3fd45a1cac083127 0.32 0.318 +3fd4624dd2f1a9fc 0.32 0.319 +40097ae147ae147b 3.19 3.185 +3fd45a1cac083128 0.32 0.318 +3fd45a1cac083126 0.32 0.318 +3fd46a7ef9db22d1 0.32 0.319 +3fd472b020c49ba6 0.32 0.32 +40098f5c28f5c28f 3.2 3.195 +3fd46a7ef9db22d2 0.32 0.319 +3fd46a7ef9db22d0 0.32 0.319 +3fd47ae147ae147b 0.32 0.32 +3fd483126e978d50 0.32 0.321 +4009a3d70a3d70a4 3.21 3.205 +3fd47ae147ae147c 0.32 0.32 +3fd47ae147ae147a 0.32 0.32 +3fd48b4395810625 0.32 0.321 +3fd49374bc6a7efa 0.32 0.322 +4009b851eb851eb8 3.22 3.215 +3fd48b4395810626 0.32 0.321 +3fd48b4395810624 0.32 0.321 +3fd49ba5e353f7cf 0.32 0.322 +3fd4a3d70a3d70a4 0.32 0.323 +4009cccccccccccd 3.23 3.225 +3fd49ba5e353f7d0 0.32 0.322 +3fd49ba5e353f7ce 0.32 0.322 +3fd4ac083126e979 0.32 0.323 +3fd4b4395810624e 0.32 0.324 +4009e147ae147ae1 3.24 3.235 +3fd4ac083126e97a 0.32 0.323 +3fd4ac083126e978 0.32 0.323 +3fd4bc6a7ef9db23 0.32 0.324 +3fd4c49ba5e353f8 0.32 0.325 +4009f5c28f5c28f6 3.25 3.245 +3fd4bc6a7ef9db24 0.32 0.324 +3fd4bc6a7ef9db22 0.32 0.324 +3fd4d4fdf3b645a2 0.33 0.326 +400a0a3d70a3d70a 3.26 3.255 +3fd4ccccccccccce 0.33 0.325 +3fd4cccccccccccc 0.32 0.325 +3fd4dd2f1a9fbe77 0.33 0.326 +3fd4e5604189374c 0.33 0.327 +400a1eb851eb851f 3.27 3.265 +3fd4dd2f1a9fbe78 0.33 0.326 +3fd4dd2f1a9fbe76 0.33 0.326 +3fd4ed916872b021 0.33 0.327 +3fd4f5c28f5c28f6 0.33 0.328 +400a333333333333 3.28 3.275 +3fd4ed916872b022 0.33 0.327 +3fd4ed916872b020 0.33 0.327 +3fd4fdf3b645a1cb 0.33 0.328 +3fd50624dd2f1aa0 0.33 0.329 +400a47ae147ae148 3.29 3.285 +3fd4fdf3b645a1cc 0.33 0.328 +3fd4fdf3b645a1ca 0.33 0.328 +3fd50e5604189375 0.33 0.329 +3fd516872b020c4a 0.33 0.33 +400a5c28f5c28f5c 3.3 3.295 +3fd50e5604189376 0.33 0.329 +3fd50e5604189374 0.33 0.329 +3fd51eb851eb851f 0.33 0.33 +3fd526e978d4fdf4 0.33 0.331 +400a70a3d70a3d71 3.31 3.305 +3fd51eb851eb8520 0.33 0.33 +3fd51eb851eb851e 0.33 0.33 +3fd52f1a9fbe76c9 0.33 0.331 +3fd5374bc6a7ef9e 0.33 0.332 +400a851eb851eb85 3.32 3.315 +3fd52f1a9fbe76ca 0.33 0.331 +3fd52f1a9fbe76c8 0.33 0.331 +3fd53f7ced916873 0.33 0.332 +3fd547ae147ae148 0.33 0.333 +400a99999999999a 3.33 3.325 +3fd53f7ced916874 0.33 0.332 +3fd53f7ced916872 0.33 0.332 +3fd54fdf3b645a1d 0.33 0.333 +3fd55810624dd2f2 0.33 0.334 +400aae147ae147ae 3.34 3.335 +3fd54fdf3b645a1e 0.33 0.333 +3fd54fdf3b645a1c 0.33 0.333 +3fd5604189374bc7 0.33 0.334 +3fd56872b020c49c 0.33 0.335 +400ac28f5c28f5c3 3.35 3.345 +3fd5604189374bc8 0.33 0.334 +3fd5604189374bc6 0.33 0.334 +3fd578d4fdf3b646 0.34 0.336 +400ad70a3d70a3d7 3.36 3.355 +3fd570a3d70a3d72 0.34 0.335 +3fd570a3d70a3d70 0.34 0.335 +3fd5810624dd2f1b 0.34 0.336 +3fd589374bc6a7f0 0.34 0.337 +400aeb851eb851ec 3.37 3.365 +3fd5810624dd2f1c 0.34 0.336 +3fd5810624dd2f1a 0.34 0.336 +3fd5916872b020c5 0.34 0.337 +3fd599999999999a 0.34 0.338 +400b000000000000 3.38 3.375 +3fd5916872b020c6 0.34 0.337 +3fd5916872b020c4 0.34 0.337 +3fd5a1cac083126f 0.34 0.338 +3fd5a9fbe76c8b44 0.34 0.339 +400b147ae147ae14 3.39 3.385 +3fd5a1cac0831270 0.34 0.338 +3fd5a1cac083126e 0.34 0.338 +3fd5b22d0e560419 0.34 0.339 +3fd5ba5e353f7cee 0.34 0.34 +400b28f5c28f5c29 3.4 3.395 +3fd5b22d0e56041a 0.34 0.339 +3fd5b22d0e560418 0.34 0.339 +3fd5c28f5c28f5c3 0.34 0.34 +3fd5cac083126e98 0.34 0.341 +400b3d70a3d70a3d 3.41 3.405 +3fd5c28f5c28f5c4 0.34 0.34 +3fd5c28f5c28f5c2 0.34 0.34 +3fd5d2f1a9fbe76d 0.34 0.341 +3fd5db22d0e56042 0.34 0.342 +400b51eb851eb852 3.42 3.415 +3fd5d2f1a9fbe76e 0.34 0.341 +3fd5d2f1a9fbe76c 0.34 0.341 +3fd5e353f7ced917 0.34 0.342 +3fd5eb851eb851ec 0.34 0.343 +400b666666666666 3.43 3.425 +3fd5e353f7ced918 0.34 0.342 +3fd5e353f7ced916 0.34 0.342 +3fd5f3b645a1cac1 0.34 0.343 +3fd5fbe76c8b4396 0.34 0.344 +400b7ae147ae147b 3.44 3.435 +3fd5f3b645a1cac2 0.34 0.343 +3fd5f3b645a1cac0 0.34 0.343 +3fd604189374bc6a 0.34 0.344 +3fd60c49ba5e353f 0.34 0.345 +400b8f5c28f5c28f 3.45 3.445 +3fd604189374bc6b 0.34 0.344 +3fd604189374bc69 0.34 0.344 +3fd61cac083126e9 0.35 0.346 +400ba3d70a3d70a4 3.46 3.455 +3fd6147ae147ae15 0.35 0.345 +3fd6147ae147ae13 0.34 0.345 +3fd624dd2f1a9fbe 0.35 0.346 +3fd62d0e56041893 0.35 0.347 +400bb851eb851eb8 3.47 3.465 +3fd624dd2f1a9fbf 0.35 0.346 +3fd624dd2f1a9fbd 0.35 0.346 +3fd6353f7ced9168 0.35 0.347 +3fd63d70a3d70a3d 0.35 0.348 +400bcccccccccccd 3.48 3.475 +3fd6353f7ced9169 0.35 0.347 +3fd6353f7ced9167 0.35 0.347 +3fd645a1cac08312 0.35 0.348 +3fd64dd2f1a9fbe7 0.35 0.349 +400be147ae147ae1 3.49 3.485 +3fd645a1cac08313 0.35 0.348 +3fd645a1cac08311 0.35 0.348 +3fd65604189374bc 0.35 0.349 +3fd65e353f7ced91 0.35 0.35 +400bf5c28f5c28f6 3.5 3.495 +3fd65604189374bd 0.35 0.349 +3fd65604189374bb 0.35 0.349 +3fd6666666666666 0.35 0.35 +3fd66e978d4fdf3b 0.35 0.351 +400c0a3d70a3d70a 3.51 3.505 +3fd6666666666667 0.35 0.35 +3fd6666666666665 0.35 0.35 +3fd676c8b4395810 0.35 0.351 +3fd67ef9db22d0e5 0.35 0.352 +400c1eb851eb851f 3.52 3.515 +3fd676c8b4395811 0.35 0.351 +3fd676c8b439580f 0.35 0.351 +3fd6872b020c49ba 0.35 0.352 +3fd68f5c28f5c28f 0.35 0.353 +400c333333333333 3.53 3.525 +3fd6872b020c49bb 0.35 0.352 +3fd6872b020c49b9 0.35 0.352 +3fd6978d4fdf3b64 0.35 0.353 +3fd69fbe76c8b439 0.35 0.354 +400c47ae147ae148 3.54 3.535 +3fd6978d4fdf3b65 0.35 0.353 +3fd6978d4fdf3b63 0.35 0.353 +3fd6a7ef9db22d0e 0.35 0.354 +3fd6b020c49ba5e3 0.35 0.355 +400c5c28f5c28f5c 3.55 3.545 +3fd6a7ef9db22d0f 0.35 0.354 +3fd6a7ef9db22d0d 0.35 0.354 +3fd6c083126e978d 0.36 0.356 +400c70a3d70a3d71 3.56 3.555 +3fd6b851eb851eb9 0.36 0.355 +3fd6b851eb851eb7 0.35 0.355 +3fd6c8b439581062 0.36 0.356 +3fd6d0e560418937 0.36 0.357 +400c851eb851eb85 3.57 3.565 +3fd6c8b439581063 0.36 0.356 +3fd6c8b439581061 0.36 0.356 +3fd6d916872b020c 0.36 0.357 +3fd6e147ae147ae1 0.36 0.358 +400c99999999999a 3.58 3.575 +3fd6d916872b020d 0.36 0.357 +3fd6d916872b020b 0.36 0.357 +3fd6e978d4fdf3b6 0.36 0.358 +3fd6f1a9fbe76c8b 0.36 0.359 +400cae147ae147ae 3.59 3.585 +3fd6e978d4fdf3b7 0.36 0.358 +3fd6e978d4fdf3b5 0.36 0.358 +3fd6f9db22d0e560 0.36 0.359 +3fd7020c49ba5e35 0.36 0.36 +400cc28f5c28f5c3 3.6 3.595 +3fd6f9db22d0e561 0.36 0.359 +3fd6f9db22d0e55f 0.36 0.359 +3fd70a3d70a3d70a 0.36 0.36 +3fd7126e978d4fdf 0.36 0.361 +400cd70a3d70a3d7 3.61 3.605 +3fd70a3d70a3d70b 0.36 0.36 +3fd70a3d70a3d709 0.36 0.36 +3fd71a9fbe76c8b4 0.36 0.361 +3fd722d0e5604189 0.36 0.362 +400ceb851eb851ec 3.62 3.615 +3fd71a9fbe76c8b5 0.36 0.361 +3fd71a9fbe76c8b3 0.36 0.361 +3fd72b020c49ba5e 0.36 0.362 +3fd7333333333333 0.36 0.363 +400d000000000000 3.63 3.625 +3fd72b020c49ba5f 0.36 0.362 +3fd72b020c49ba5d 0.36 0.362 +3fd73b645a1cac08 0.36 0.363 +3fd74395810624dd 0.36 0.364 +400d147ae147ae14 3.64 3.635 +3fd73b645a1cac09 0.36 0.363 +3fd73b645a1cac07 0.36 0.363 +3fd74bc6a7ef9db2 0.36 0.364 +3fd753f7ced91687 0.36 0.365 +400d28f5c28f5c29 3.65 3.645 +3fd74bc6a7ef9db3 0.36 0.364 +3fd74bc6a7ef9db1 0.36 0.364 +3fd7645a1cac0831 0.37 0.366 +400d3d70a3d70a3d 3.66 3.655 +3fd75c28f5c28f5d 0.37 0.365 +3fd75c28f5c28f5b 0.36 0.365 +3fd76c8b43958106 0.37 0.366 +3fd774bc6a7ef9db 0.37 0.367 +400d51eb851eb852 3.67 3.665 +3fd76c8b43958107 0.37 0.366 +3fd76c8b43958105 0.37 0.366 +3fd77ced916872b0 0.37 0.367 +3fd7851eb851eb85 0.37 0.368 +400d666666666666 3.68 3.675 +3fd77ced916872b1 0.37 0.367 +3fd77ced916872af 0.37 0.367 +3fd78d4fdf3b645a 0.37 0.368 +3fd795810624dd2f 0.37 0.369 +400d7ae147ae147b 3.69 3.685 +3fd78d4fdf3b645b 0.37 0.368 +3fd78d4fdf3b6459 0.37 0.368 +3fd79db22d0e5604 0.37 0.369 +3fd7a5e353f7ced9 0.37 0.37 +400d8f5c28f5c28f 3.7 3.695 +3fd79db22d0e5605 0.37 0.369 +3fd79db22d0e5603 0.37 0.369 +3fd7ae147ae147ae 0.37 0.37 +3fd7b645a1cac083 0.37 0.371 +400da3d70a3d70a4 3.71 3.705 +3fd7ae147ae147af 0.37 0.37 +3fd7ae147ae147ad 0.37 0.37 +3fd7be76c8b43958 0.37 0.371 +3fd7c6a7ef9db22d 0.37 0.372 +400db851eb851eb8 3.72 3.715 +3fd7be76c8b43959 0.37 0.371 +3fd7be76c8b43957 0.37 0.371 +3fd7ced916872b02 0.37 0.372 +3fd7d70a3d70a3d7 0.37 0.373 +400dcccccccccccd 3.73 3.725 +3fd7ced916872b03 0.37 0.372 +3fd7ced916872b01 0.37 0.372 +3fd7df3b645a1cac 0.37 0.373 +3fd7e76c8b439581 0.37 0.374 +400de147ae147ae1 3.74 3.735 +3fd7df3b645a1cad 0.37 0.373 +3fd7df3b645a1cab 0.37 0.373 +3fd7ef9db22d0e56 0.37 0.374 +3fd7f7ced916872b 0.37 0.375 +400df5c28f5c28f6 3.75 3.745 +3fd7ef9db22d0e57 0.37 0.374 +3fd7ef9db22d0e55 0.37 0.374 +3fd8083126e978d5 0.38 0.376 +400e0a3d70a3d70a 3.76 3.755 +3fd8000000000001 0.38 0.375 +3fd7ffffffffffff 0.37 0.375 +3fd810624dd2f1aa 0.38 0.376 +3fd8189374bc6a7f 0.38 0.377 +400e1eb851eb851f 3.77 3.765 +3fd810624dd2f1ab 0.38 0.376 +3fd810624dd2f1a9 0.38 0.376 +3fd820c49ba5e354 0.38 0.377 +3fd828f5c28f5c29 0.38 0.378 +400e333333333333 3.78 3.775 +3fd820c49ba5e355 0.38 0.377 +3fd820c49ba5e353 0.38 0.377 +3fd83126e978d4fe 0.38 0.378 +3fd8395810624dd3 0.38 0.379 +400e47ae147ae148 3.79 3.785 +3fd83126e978d4ff 0.38 0.378 +3fd83126e978d4fd 0.38 0.378 +3fd84189374bc6a8 0.38 0.379 +3fd849ba5e353f7d 0.38 0.38 +400e5c28f5c28f5c 3.8 3.795 +3fd84189374bc6a9 0.38 0.379 +3fd84189374bc6a7 0.38 0.379 +3fd851eb851eb852 0.38 0.38 +3fd85a1cac083127 0.38 0.381 +400e70a3d70a3d71 3.81 3.805 +3fd851eb851eb853 0.38 0.38 +3fd851eb851eb851 0.38 0.38 +3fd8624dd2f1a9fc 0.38 0.381 +3fd86a7ef9db22d1 0.38 0.382 +400e851eb851eb85 3.82 3.815 +3fd8624dd2f1a9fd 0.38 0.381 +3fd8624dd2f1a9fb 0.38 0.381 +3fd872b020c49ba6 0.38 0.382 +3fd87ae147ae147b 0.38 0.383 +400e99999999999a 3.83 3.825 +3fd872b020c49ba7 0.38 0.382 +3fd872b020c49ba5 0.38 0.382 +3fd883126e978d50 0.38 0.383 +3fd88b4395810625 0.38 0.384 +400eae147ae147ae 3.84 3.835 +3fd883126e978d51 0.38 0.383 +3fd883126e978d4f 0.38 0.383 +3fd89374bc6a7efa 0.38 0.384 +3fd89ba5e353f7cf 0.38 0.385 +400ec28f5c28f5c3 3.85 3.845 +3fd89374bc6a7efb 0.38 0.384 +3fd89374bc6a7ef9 0.38 0.384 +3fd8ac083126e979 0.39 0.386 +400ed70a3d70a3d7 3.86 3.855 +3fd8a3d70a3d70a5 0.39 0.385 +3fd8a3d70a3d70a3 0.38 0.385 +3fd8b4395810624e 0.39 0.386 +3fd8bc6a7ef9db23 0.39 0.387 +400eeb851eb851ec 3.87 3.865 +3fd8b4395810624f 0.39 0.386 +3fd8b4395810624d 0.39 0.386 +3fd8c49ba5e353f8 0.39 0.387 +3fd8cccccccccccd 0.39 0.388 +400f000000000000 3.88 3.875 +3fd8c49ba5e353f9 0.39 0.387 +3fd8c49ba5e353f7 0.39 0.387 +3fd8d4fdf3b645a2 0.39 0.388 +3fd8dd2f1a9fbe77 0.39 0.389 +400f147ae147ae14 3.89 3.885 +3fd8d4fdf3b645a3 0.39 0.388 +3fd8d4fdf3b645a1 0.39 0.388 +3fd8e5604189374c 0.39 0.389 +3fd8ed916872b021 0.39 0.39 +400f28f5c28f5c29 3.9 3.895 +3fd8e5604189374d 0.39 0.389 +3fd8e5604189374b 0.39 0.389 +3fd8f5c28f5c28f6 0.39 0.39 +3fd8fdf3b645a1cb 0.39 0.391 +400f3d70a3d70a3d 3.91 3.905 +3fd8f5c28f5c28f7 0.39 0.39 +3fd8f5c28f5c28f5 0.39 0.39 +3fd90624dd2f1aa0 0.39 0.391 +3fd90e5604189375 0.39 0.392 +400f51eb851eb852 3.92 3.915 +3fd90624dd2f1aa1 0.39 0.391 +3fd90624dd2f1a9f 0.39 0.391 +3fd916872b020c4a 0.39 0.392 +3fd91eb851eb851f 0.39 0.393 +400f666666666666 3.93 3.925 +3fd916872b020c4b 0.39 0.392 +3fd916872b020c49 0.39 0.392 +3fd926e978d4fdf4 0.39 0.393 +3fd92f1a9fbe76c9 0.39 0.394 +400f7ae147ae147b 3.94 3.935 +3fd926e978d4fdf5 0.39 0.393 +3fd926e978d4fdf3 0.39 0.393 +3fd9374bc6a7ef9e 0.39 0.394 +3fd93f7ced916873 0.39 0.395 +400f8f5c28f5c28f 3.95 3.945 +3fd9374bc6a7ef9f 0.39 0.394 +3fd9374bc6a7ef9d 0.39 0.394 +3fd94fdf3b645a1d 0.4 0.396 +400fa3d70a3d70a4 3.96 3.955 +3fd947ae147ae149 0.4 0.395 +3fd947ae147ae147 0.39 0.395 +3fd95810624dd2f2 0.4 0.396 +3fd9604189374bc7 0.4 0.397 +400fb851eb851eb8 3.97 3.965 +3fd95810624dd2f3 0.4 0.396 +3fd95810624dd2f1 0.4 0.396 +3fd96872b020c49c 0.4 0.397 +3fd970a3d70a3d71 0.4 0.398 +400fcccccccccccd 3.98 3.975 +3fd96872b020c49d 0.4 0.397 +3fd96872b020c49b 0.4 0.397 +3fd978d4fdf3b646 0.4 0.398 +3fd9810624dd2f1b 0.4 0.399 +400fe147ae147ae1 3.99 3.985 +3fd978d4fdf3b647 0.4 0.398 +3fd978d4fdf3b645 0.4 0.398 +3fd989374bc6a7f0 0.4 0.399 +3fd9916872b020c5 0.4 0.4 +400ff5c28f5c28f6 4.0 3.995 +3fd989374bc6a7f1 0.4 0.399 +3fd989374bc6a7ef 0.4 0.399 +3fd999999999999a 0.4 0.4 +3fd9a1cac083126f 0.4 0.401 +4010051eb851eb85 4.01 4.005 +3fd999999999999b 0.4 0.4 +3fd9999999999999 0.4 0.4 +3fd9a9fbe76c8b44 0.4 0.401 +3fd9b22d0e560419 0.4 0.402 +40100f5c28f5c28f 4.02 4.015 +3fd9a9fbe76c8b45 0.4 0.401 +3fd9a9fbe76c8b43 0.4 0.401 +3fd9ba5e353f7cee 0.4 0.402 +3fd9c28f5c28f5c3 0.4 0.403 +401019999999999a 4.03 4.025 +3fd9ba5e353f7cef 0.4 0.402 +3fd9ba5e353f7ced 0.4 0.402 +3fd9cac083126e98 0.4 0.403 +3fd9d2f1a9fbe76d 0.4 0.404 +401023d70a3d70a4 4.04 4.035 +3fd9cac083126e99 0.4 0.403 +3fd9cac083126e97 0.4 0.403 +3fd9db22d0e56042 0.4 0.404 +3fd9e353f7ced917 0.4 0.405 +40102e147ae147ae 4.05 4.045 +3fd9db22d0e56043 0.4 0.404 +3fd9db22d0e56041 0.4 0.404 +3fd9f3b645a1cac1 0.41 0.406 +40103851eb851eb8 4.06 4.055 +3fd9eb851eb851ed 0.41 0.405 +3fd9eb851eb851eb 0.41 0.405 +3fd9fbe76c8b4396 0.41 0.406 +3fda04189374bc6a 0.41 0.407 +4010428f5c28f5c3 4.07 4.065 +3fd9fbe76c8b4397 0.41 0.406 +3fd9fbe76c8b4395 0.41 0.406 +3fda0c49ba5e353f 0.41 0.407 +3fda147ae147ae14 0.41 0.408 +40104ccccccccccd 4.08 4.075 +3fda0c49ba5e3540 0.41 0.407 +3fda0c49ba5e353e 0.41 0.407 +3fda1cac083126e9 0.41 0.408 +3fda24dd2f1a9fbe 0.41 0.409 +4010570a3d70a3d7 4.09 4.085 +3fda1cac083126ea 0.41 0.408 +3fda1cac083126e8 0.41 0.408 +3fda2d0e56041893 0.41 0.409 +3fda353f7ced9168 0.41 0.41 +40106147ae147ae1 4.1 4.095 +3fda2d0e56041894 0.41 0.409 +3fda2d0e56041892 0.41 0.409 +3fda3d70a3d70a3d 0.41 0.41 +3fda45a1cac08312 0.41 0.411 +40106b851eb851ec 4.11 4.105 +3fda3d70a3d70a3e 0.41 0.41 +3fda3d70a3d70a3c 0.41 0.41 +3fda4dd2f1a9fbe7 0.41 0.411 +3fda5604189374bc 0.41 0.412 +401075c28f5c28f6 4.12 4.115 +3fda4dd2f1a9fbe8 0.41 0.411 +3fda4dd2f1a9fbe6 0.41 0.411 +3fda5e353f7ced91 0.41 0.412 +3fda666666666666 0.41 0.413 +4010800000000000 4.13 4.125 +3fda5e353f7ced92 0.41 0.412 +3fda5e353f7ced90 0.41 0.412 +3fda6e978d4fdf3b 0.41 0.413 +3fda76c8b4395810 0.41 0.414 +40108a3d70a3d70a 4.14 4.135 +3fda6e978d4fdf3c 0.41 0.413 +3fda6e978d4fdf3a 0.41 0.413 +3fda7ef9db22d0e5 0.41 0.414 +3fda872b020c49ba 0.41 0.415 +4010947ae147ae14 4.15 4.145 +3fda7ef9db22d0e6 0.41 0.414 +3fda7ef9db22d0e4 0.41 0.414 +3fda978d4fdf3b64 0.42 0.416 +40109eb851eb851f 4.16 4.155 +3fda8f5c28f5c290 0.42 0.415 +3fda8f5c28f5c28e 0.41 0.415 +3fda9fbe76c8b439 0.42 0.416 +3fdaa7ef9db22d0e 0.42 0.417 +4010a8f5c28f5c29 4.17 4.165 +3fda9fbe76c8b43a 0.42 0.416 +3fda9fbe76c8b438 0.42 0.416 +3fdab020c49ba5e3 0.42 0.417 +3fdab851eb851eb8 0.42 0.418 +4010b33333333333 4.18 4.175 +3fdab020c49ba5e4 0.42 0.417 +3fdab020c49ba5e2 0.42 0.417 +3fdac083126e978d 0.42 0.418 +3fdac8b439581062 0.42 0.419 +4010bd70a3d70a3d 4.19 4.185 +3fdac083126e978e 0.42 0.418 +3fdac083126e978c 0.42 0.418 +3fdad0e560418937 0.42 0.419 +3fdad916872b020c 0.42 0.42 +4010c7ae147ae148 4.2 4.195 +3fdad0e560418938 0.42 0.419 +3fdad0e560418936 0.42 0.419 +3fdae147ae147ae1 0.42 0.42 +3fdae978d4fdf3b6 0.42 0.421 +4010d1eb851eb852 4.21 4.205 +3fdae147ae147ae2 0.42 0.42 +3fdae147ae147ae0 0.42 0.42 +3fdaf1a9fbe76c8b 0.42 0.421 +3fdaf9db22d0e560 0.42 0.422 +4010dc28f5c28f5c 4.22 4.215 +3fdaf1a9fbe76c8c 0.42 0.421 +3fdaf1a9fbe76c8a 0.42 0.421 +3fdb020c49ba5e35 0.42 0.422 +3fdb0a3d70a3d70a 0.42 0.423 +4010e66666666666 4.23 4.225 +3fdb020c49ba5e36 0.42 0.422 +3fdb020c49ba5e34 0.42 0.422 +3fdb126e978d4fdf 0.42 0.423 +3fdb1a9fbe76c8b4 0.42 0.424 +4010f0a3d70a3d71 4.24 4.235 +3fdb126e978d4fe0 0.42 0.423 +3fdb126e978d4fde 0.42 0.423 +3fdb22d0e5604189 0.42 0.424 +3fdb2b020c49ba5e 0.42 0.425 +4010fae147ae147b 4.25 4.245 +3fdb22d0e560418a 0.42 0.424 +3fdb22d0e5604188 0.42 0.424 +3fdb3b645a1cac08 0.43 0.426 +4011051eb851eb85 4.26 4.255 +3fdb333333333334 0.43 0.425 +3fdb333333333332 0.42 0.425 +3fdb4395810624dd 0.43 0.426 +3fdb4bc6a7ef9db2 0.43 0.427 +40110f5c28f5c28f 4.27 4.265 +3fdb4395810624de 0.43 0.426 +3fdb4395810624dc 0.43 0.426 +3fdb53f7ced91687 0.43 0.427 +3fdb5c28f5c28f5c 0.43 0.428 +401119999999999a 4.28 4.275 +3fdb53f7ced91688 0.43 0.427 +3fdb53f7ced91686 0.43 0.427 +3fdb645a1cac0831 0.43 0.428 +3fdb6c8b43958106 0.43 0.429 +401123d70a3d70a4 4.29 4.285 +3fdb645a1cac0832 0.43 0.428 +3fdb645a1cac0830 0.43 0.428 +3fdb74bc6a7ef9db 0.43 0.429 +3fdb7ced916872b0 0.43 0.43 +40112e147ae147ae 4.3 4.295 +3fdb74bc6a7ef9dc 0.43 0.429 +3fdb74bc6a7ef9da 0.43 0.429 +3fdb851eb851eb85 0.43 0.43 +3fdb8d4fdf3b645a 0.43 0.431 +40113851eb851eb8 4.31 4.305 +3fdb851eb851eb86 0.43 0.43 +3fdb851eb851eb84 0.43 0.43 +3fdb95810624dd2f 0.43 0.431 +3fdb9db22d0e5604 0.43 0.432 +4011428f5c28f5c3 4.32 4.315 +3fdb95810624dd30 0.43 0.431 +3fdb95810624dd2e 0.43 0.431 +3fdba5e353f7ced9 0.43 0.432 +3fdbae147ae147ae 0.43 0.433 +40114ccccccccccd 4.33 4.325 +3fdba5e353f7ceda 0.43 0.432 +3fdba5e353f7ced8 0.43 0.432 +3fdbb645a1cac083 0.43 0.433 +3fdbbe76c8b43958 0.43 0.434 +4011570a3d70a3d7 4.34 4.335 +3fdbb645a1cac084 0.43 0.433 +3fdbb645a1cac082 0.43 0.433 +3fdbc6a7ef9db22d 0.43 0.434 +3fdbced916872b02 0.43 0.435 +40116147ae147ae1 4.35 4.345 +3fdbc6a7ef9db22e 0.43 0.434 +3fdbc6a7ef9db22c 0.43 0.434 +3fdbdf3b645a1cac 0.44 0.436 +40116b851eb851ec 4.36 4.355 +3fdbd70a3d70a3d8 0.44 0.435 +3fdbd70a3d70a3d6 0.43 0.435 +3fdbe76c8b439581 0.44 0.436 +3fdbef9db22d0e56 0.44 0.437 +401175c28f5c28f6 4.37 4.365 +3fdbe76c8b439582 0.44 0.436 +3fdbe76c8b439580 0.44 0.436 +3fdbf7ced916872b 0.44 0.437 +3fdc000000000000 0.44 0.438 +4011800000000000 4.38 4.375 +3fdbf7ced916872c 0.44 0.437 +3fdbf7ced916872a 0.44 0.437 +3fdc083126e978d5 0.44 0.438 +3fdc10624dd2f1aa 0.44 0.439 +40118a3d70a3d70a 4.39 4.385 +3fdc083126e978d6 0.44 0.438 +3fdc083126e978d4 0.44 0.438 +3fdc189374bc6a7f 0.44 0.439 +3fdc20c49ba5e354 0.44 0.44 +4011947ae147ae14 4.4 4.395 +3fdc189374bc6a80 0.44 0.439 +3fdc189374bc6a7e 0.44 0.439 +3fdc28f5c28f5c29 0.44 0.44 +3fdc3126e978d4fe 0.44 0.441 +40119eb851eb851f 4.41 4.405 +3fdc28f5c28f5c2a 0.44 0.44 +3fdc28f5c28f5c28 0.44 0.44 +3fdc395810624dd3 0.44 0.441 +3fdc4189374bc6a8 0.44 0.442 +4011a8f5c28f5c29 4.42 4.415 +3fdc395810624dd4 0.44 0.441 +3fdc395810624dd2 0.44 0.441 +3fdc49ba5e353f7d 0.44 0.442 +3fdc51eb851eb852 0.44 0.443 +4011b33333333333 4.43 4.425 +3fdc49ba5e353f7e 0.44 0.442 +3fdc49ba5e353f7c 0.44 0.442 +3fdc5a1cac083127 0.44 0.443 +3fdc624dd2f1a9fc 0.44 0.444 +4011bd70a3d70a3d 4.44 4.435 +3fdc5a1cac083128 0.44 0.443 +3fdc5a1cac083126 0.44 0.443 +3fdc6a7ef9db22d1 0.44 0.444 +3fdc72b020c49ba6 0.44 0.445 +4011c7ae147ae148 4.45 4.445 +3fdc6a7ef9db22d2 0.44 0.444 +3fdc6a7ef9db22d0 0.44 0.444 +3fdc83126e978d50 0.45 0.446 +4011d1eb851eb852 4.46 4.455 +3fdc7ae147ae147c 0.45 0.445 +3fdc7ae147ae147a 0.44 0.445 +3fdc8b4395810625 0.45 0.446 +3fdc9374bc6a7efa 0.45 0.447 +4011dc28f5c28f5c 4.47 4.465 +3fdc8b4395810626 0.45 0.446 +3fdc8b4395810624 0.45 0.446 +3fdc9ba5e353f7cf 0.45 0.447 +3fdca3d70a3d70a4 0.45 0.448 +4011e66666666666 4.48 4.475 +3fdc9ba5e353f7d0 0.45 0.447 +3fdc9ba5e353f7ce 0.45 0.447 +3fdcac083126e979 0.45 0.448 +3fdcb4395810624e 0.45 0.449 +4011f0a3d70a3d71 4.49 4.485 +3fdcac083126e97a 0.45 0.448 +3fdcac083126e978 0.45 0.448 +3fdcbc6a7ef9db23 0.45 0.449 +3fdcc49ba5e353f8 0.45 0.45 +4011fae147ae147b 4.5 4.495 +3fdcbc6a7ef9db24 0.45 0.449 +3fdcbc6a7ef9db22 0.45 0.449 +3fdccccccccccccd 0.45 0.45 +3fdcd4fdf3b645a2 0.45 0.451 +4012051eb851eb85 4.51 4.505 +3fdcccccccccccce 0.45 0.45 +3fdccccccccccccc 0.45 0.45 +3fdcdd2f1a9fbe77 0.45 0.451 +3fdce5604189374c 0.45 0.452 +40120f5c28f5c28f 4.52 4.515 +3fdcdd2f1a9fbe78 0.45 0.451 +3fdcdd2f1a9fbe76 0.45 0.451 +3fdced916872b021 0.45 0.452 +3fdcf5c28f5c28f6 0.45 0.453 +401219999999999a 4.53 4.525 +3fdced916872b022 0.45 0.452 +3fdced916872b020 0.45 0.452 +3fdcfdf3b645a1cb 0.45 0.453 +3fdd0624dd2f1aa0 0.45 0.454 +401223d70a3d70a4 4.54 4.535 +3fdcfdf3b645a1cc 0.45 0.453 +3fdcfdf3b645a1ca 0.45 0.453 +3fdd0e5604189375 0.45 0.454 +3fdd16872b020c4a 0.45 0.455 +40122e147ae147ae 4.55 4.545 +3fdd0e5604189376 0.45 0.454 +3fdd0e5604189374 0.45 0.454 +3fdd26e978d4fdf4 0.46 0.456 +40123851eb851eb8 4.56 4.555 +3fdd1eb851eb8520 0.46 0.455 +3fdd1eb851eb851e 0.45 0.455 +3fdd2f1a9fbe76c9 0.46 0.456 +3fdd374bc6a7ef9e 0.46 0.457 +4012428f5c28f5c3 4.57 4.565 +3fdd2f1a9fbe76ca 0.46 0.456 +3fdd2f1a9fbe76c8 0.46 0.456 +3fdd3f7ced916873 0.46 0.457 +3fdd47ae147ae148 0.46 0.458 +40124ccccccccccd 4.58 4.575 +3fdd3f7ced916874 0.46 0.457 +3fdd3f7ced916872 0.46 0.457 +3fdd4fdf3b645a1d 0.46 0.458 +3fdd5810624dd2f2 0.46 0.459 +4012570a3d70a3d7 4.59 4.585 +3fdd4fdf3b645a1e 0.46 0.458 +3fdd4fdf3b645a1c 0.46 0.458 +3fdd604189374bc7 0.46 0.459 +3fdd6872b020c49c 0.46 0.46 +40126147ae147ae1 4.6 4.595 +3fdd604189374bc8 0.46 0.459 +3fdd604189374bc6 0.46 0.459 +3fdd70a3d70a3d71 0.46 0.46 +3fdd78d4fdf3b646 0.46 0.461 +40126b851eb851ec 4.61 4.605 +3fdd70a3d70a3d72 0.46 0.46 +3fdd70a3d70a3d70 0.46 0.46 +3fdd810624dd2f1b 0.46 0.461 +3fdd89374bc6a7f0 0.46 0.462 +401275c28f5c28f6 4.62 4.615 +3fdd810624dd2f1c 0.46 0.461 +3fdd810624dd2f1a 0.46 0.461 +3fdd916872b020c5 0.46 0.462 +3fdd99999999999a 0.46 0.463 +4012800000000000 4.63 4.625 +3fdd916872b020c6 0.46 0.462 +3fdd916872b020c4 0.46 0.462 +3fdda1cac083126f 0.46 0.463 +3fdda9fbe76c8b44 0.46 0.464 +40128a3d70a3d70a 4.64 4.635 +3fdda1cac0831270 0.46 0.463 +3fdda1cac083126e 0.46 0.463 +3fddb22d0e560419 0.46 0.464 +3fddba5e353f7cee 0.46 0.465 +4012947ae147ae14 4.65 4.645 +3fddb22d0e56041a 0.46 0.464 +3fddb22d0e560418 0.46 0.464 +3fddcac083126e98 0.47 0.466 +40129eb851eb851f 4.66 4.655 +3fddc28f5c28f5c4 0.47 0.465 +3fddc28f5c28f5c2 0.47 0.465 +3fddd2f1a9fbe76d 0.47 0.466 +3fdddb22d0e56042 0.47 0.467 +4012a8f5c28f5c29 4.67 4.665 +3fddd2f1a9fbe76e 0.47 0.466 +3fddd2f1a9fbe76c 0.47 0.466 +3fdde353f7ced917 0.47 0.467 +3fddeb851eb851ec 0.47 0.468 +4012b33333333333 4.68 4.675 +3fdde353f7ced918 0.47 0.467 +3fdde353f7ced916 0.47 0.467 +3fddf3b645a1cac1 0.47 0.468 +3fddfbe76c8b4396 0.47 0.469 +4012bd70a3d70a3d 4.69 4.685 +3fddf3b645a1cac2 0.47 0.468 +3fddf3b645a1cac0 0.47 0.468 +3fde04189374bc6a 0.47 0.469 +3fde0c49ba5e353f 0.47 0.47 +4012c7ae147ae148 4.7 4.695 +3fde04189374bc6b 0.47 0.469 +3fde04189374bc69 0.47 0.469 +3fde147ae147ae14 0.47 0.47 +3fde1cac083126e9 0.47 0.471 +4012d1eb851eb852 4.71 4.705 +3fde147ae147ae15 0.47 0.47 +3fde147ae147ae13 0.47 0.47 +3fde24dd2f1a9fbe 0.47 0.471 +3fde2d0e56041893 0.47 0.472 +4012dc28f5c28f5c 4.72 4.715 +3fde24dd2f1a9fbf 0.47 0.471 +3fde24dd2f1a9fbd 0.47 0.471 +3fde353f7ced9168 0.47 0.472 +3fde3d70a3d70a3d 0.47 0.473 +4012e66666666666 4.73 4.725 +3fde353f7ced9169 0.47 0.472 +3fde353f7ced9167 0.47 0.472 +3fde45a1cac08312 0.47 0.473 +3fde4dd2f1a9fbe7 0.47 0.474 +4012f0a3d70a3d71 4.74 4.735 +3fde45a1cac08313 0.47 0.473 +3fde45a1cac08311 0.47 0.473 +3fde5604189374bc 0.47 0.474 +3fde5e353f7ced91 0.47 0.475 +4012fae147ae147b 4.75 4.745 +3fde5604189374bd 0.47 0.474 +3fde5604189374bb 0.47 0.474 +3fde6e978d4fdf3b 0.48 0.476 +4013051eb851eb85 4.76 4.755 +3fde666666666667 0.48 0.475 +3fde666666666665 0.47 0.475 +3fde76c8b4395810 0.48 0.476 +3fde7ef9db22d0e5 0.48 0.477 +40130f5c28f5c28f 4.77 4.765 +3fde76c8b4395811 0.48 0.476 +3fde76c8b439580f 0.48 0.476 +3fde872b020c49ba 0.48 0.477 +3fde8f5c28f5c28f 0.48 0.478 +401319999999999a 4.78 4.775 +3fde872b020c49bb 0.48 0.477 +3fde872b020c49b9 0.48 0.477 +3fde978d4fdf3b64 0.48 0.478 +3fde9fbe76c8b439 0.48 0.479 +401323d70a3d70a4 4.79 4.785 +3fde978d4fdf3b65 0.48 0.478 +3fde978d4fdf3b63 0.48 0.478 +3fdea7ef9db22d0e 0.48 0.479 +3fdeb020c49ba5e3 0.48 0.48 +40132e147ae147ae 4.8 4.795 +3fdea7ef9db22d0f 0.48 0.479 +3fdea7ef9db22d0d 0.48 0.479 +3fdeb851eb851eb8 0.48 0.48 +3fdec083126e978d 0.48 0.481 +40133851eb851eb8 4.81 4.805 +3fdeb851eb851eb9 0.48 0.48 +3fdeb851eb851eb7 0.48 0.48 +3fdec8b439581062 0.48 0.481 +3fded0e560418937 0.48 0.482 +4013428f5c28f5c3 4.82 4.815 +3fdec8b439581063 0.48 0.481 +3fdec8b439581061 0.48 0.481 +3fded916872b020c 0.48 0.482 +3fdee147ae147ae1 0.48 0.483 +40134ccccccccccd 4.83 4.825 +3fded916872b020d 0.48 0.482 +3fded916872b020b 0.48 0.482 +3fdee978d4fdf3b6 0.48 0.483 +3fdef1a9fbe76c8b 0.48 0.484 +4013570a3d70a3d7 4.84 4.835 +3fdee978d4fdf3b7 0.48 0.483 +3fdee978d4fdf3b5 0.48 0.483 +3fdef9db22d0e560 0.48 0.484 +3fdf020c49ba5e35 0.48 0.485 +40136147ae147ae1 4.85 4.845 +3fdef9db22d0e561 0.48 0.484 +3fdef9db22d0e55f 0.48 0.484 +3fdf126e978d4fdf 0.49 0.486 +40136b851eb851ec 4.86 4.855 +3fdf0a3d70a3d70b 0.49 0.485 +3fdf0a3d70a3d709 0.48 0.485 +3fdf1a9fbe76c8b4 0.49 0.486 +3fdf22d0e5604189 0.49 0.487 +401375c28f5c28f6 4.87 4.865 +3fdf1a9fbe76c8b5 0.49 0.486 +3fdf1a9fbe76c8b3 0.49 0.486 +3fdf2b020c49ba5e 0.49 0.487 +3fdf333333333333 0.49 0.488 +4013800000000000 4.88 4.875 +3fdf2b020c49ba5f 0.49 0.487 +3fdf2b020c49ba5d 0.49 0.487 +3fdf3b645a1cac08 0.49 0.488 +3fdf4395810624dd 0.49 0.489 +40138a3d70a3d70a 4.89 4.885 +3fdf3b645a1cac09 0.49 0.488 +3fdf3b645a1cac07 0.49 0.488 +3fdf4bc6a7ef9db2 0.49 0.489 +3fdf53f7ced91687 0.49 0.49 +4013947ae147ae14 4.9 4.895 +3fdf4bc6a7ef9db3 0.49 0.489 +3fdf4bc6a7ef9db1 0.49 0.489 +3fdf5c28f5c28f5c 0.49 0.49 +3fdf645a1cac0831 0.49 0.491 +40139eb851eb851f 4.91 4.905 +3fdf5c28f5c28f5d 0.49 0.49 +3fdf5c28f5c28f5b 0.49 0.49 +3fdf6c8b43958106 0.49 0.491 +3fdf74bc6a7ef9db 0.49 0.492 +4013a8f5c28f5c29 4.92 4.915 +3fdf6c8b43958107 0.49 0.491 +3fdf6c8b43958105 0.49 0.491 +3fdf7ced916872b0 0.49 0.492 +3fdf851eb851eb85 0.49 0.493 +4013b33333333333 4.93 4.925 +3fdf7ced916872b1 0.49 0.492 +3fdf7ced916872af 0.49 0.492 +3fdf8d4fdf3b645a 0.49 0.493 +3fdf95810624dd2f 0.49 0.494 +4013bd70a3d70a3d 4.94 4.935 +3fdf8d4fdf3b645b 0.49 0.493 +3fdf8d4fdf3b6459 0.49 0.493 +3fdf9db22d0e5604 0.49 0.494 +3fdfa5e353f7ced9 0.49 0.495 +4013c7ae147ae148 4.95 4.945 +3fdf9db22d0e5605 0.49 0.494 +3fdf9db22d0e5603 0.49 0.494 +3fdfb645a1cac083 0.5 0.496 +4013d1eb851eb852 4.96 4.955 +3fdfae147ae147af 0.5 0.495 +3fdfae147ae147ad 0.49 0.495 +3fdfbe76c8b43958 0.5 0.496 +3fdfc6a7ef9db22d 0.5 0.497 +4013dc28f5c28f5c 4.97 4.965 +3fdfbe76c8b43959 0.5 0.496 +3fdfbe76c8b43957 0.5 0.496 +3fdfced916872b02 0.5 0.497 +3fdfd70a3d70a3d7 0.5 0.498 +4013e66666666666 4.98 4.975 +3fdfced916872b03 0.5 0.497 +3fdfced916872b01 0.5 0.497 +3fdfdf3b645a1cac 0.5 0.498 +3fdfe76c8b439581 0.5 0.499 +4013f0a3d70a3d71 4.99 4.985 +3fdfdf3b645a1cad 0.5 0.498 +3fdfdf3b645a1cab 0.5 0.498 +3fdfef9db22d0e56 0.5 0.499 +3fdff7ced916872b 0.5 0.5 +4013fae147ae147b 5.0 4.995 +3fdfef9db22d0e57 0.5 0.499 +3fdfef9db22d0e55 0.5 0.499 +3fe004189374bc6a 0.5 0.501 +4014051eb851eb85 5.01 5.005 +3fe0000000000001 0.5 0.5 +3fdfffffffffffff 0.5 0.5 +3fe0083126e978d5 0.5 0.501 +3fe00c49ba5e353f 0.5 0.502 +40140f5c28f5c28f 5.02 5.015 +3fe0083126e978d6 0.5 0.501 +3fe0083126e978d4 0.5 0.501 +3fe010624dd2f1aa 0.5 0.502 +3fe0147ae147ae14 0.5 0.503 +401419999999999a 5.03 5.025 +3fe010624dd2f1ab 0.5 0.502 +3fe010624dd2f1a9 0.5 0.502 +3fe0189374bc6a7f 0.5 0.503 +3fe01cac083126e9 0.5 0.504 +401423d70a3d70a4 5.04 5.035 +3fe0189374bc6a80 0.5 0.503 +3fe0189374bc6a7e 0.5 0.503 +3fe020c49ba5e354 0.5 0.504 +3fe024dd2f1a9fbe 0.5 0.505 +40142e147ae147ae 5.05 5.045 +3fe020c49ba5e355 0.5 0.504 +3fe020c49ba5e353 0.5 0.504 +3fe02d0e56041893 0.51 0.506 +40143851eb851eb8 5.06 5.055 +3fe028f5c28f5c2a 0.51 0.505 +3fe028f5c28f5c28 0.5 0.505 +3fe03126e978d4fe 0.51 0.506 +3fe0353f7ced9168 0.51 0.507 +4014428f5c28f5c3 5.07 5.065 +3fe03126e978d4ff 0.51 0.506 +3fe03126e978d4fd 0.51 0.506 +3fe0395810624dd3 0.51 0.507 +3fe03d70a3d70a3d 0.51 0.508 +40144ccccccccccd 5.08 5.075 +3fe0395810624dd4 0.51 0.507 +3fe0395810624dd2 0.51 0.507 +3fe04189374bc6a8 0.51 0.508 +3fe045a1cac08312 0.51 0.509 +4014570a3d70a3d7 5.09 5.085 +3fe04189374bc6a9 0.51 0.508 +3fe04189374bc6a7 0.51 0.508 +3fe049ba5e353f7d 0.51 0.509 +3fe04dd2f1a9fbe7 0.51 0.51 +40146147ae147ae1 5.1 5.095 +3fe049ba5e353f7e 0.51 0.509 +3fe049ba5e353f7c 0.51 0.509 +3fe051eb851eb852 0.51 0.51 +3fe05604189374bc 0.51 0.511 +40146b851eb851ec 5.11 5.105 +3fe051eb851eb853 0.51 0.51 +3fe051eb851eb851 0.51 0.51 +3fe05a1cac083127 0.51 0.511 +3fe05e353f7ced91 0.51 0.512 +401475c28f5c28f6 5.12 5.115 +3fe05a1cac083128 0.51 0.511 +3fe05a1cac083126 0.51 0.511 +3fe0624dd2f1a9fc 0.51 0.512 +3fe0666666666666 0.51 0.513 +4014800000000000 5.13 5.125 +3fe0624dd2f1a9fd 0.51 0.512 +3fe0624dd2f1a9fb 0.51 0.512 +3fe06a7ef9db22d1 0.51 0.513 +3fe06e978d4fdf3b 0.51 0.514 +40148a3d70a3d70a 5.14 5.135 +3fe06a7ef9db22d2 0.51 0.513 +3fe06a7ef9db22d0 0.51 0.513 +3fe072b020c49ba6 0.51 0.514 +3fe076c8b4395810 0.51 0.515 +4014947ae147ae14 5.15 5.145 +3fe072b020c49ba7 0.51 0.514 +3fe072b020c49ba5 0.51 0.514 +3fe07ef9db22d0e5 0.52 0.516 +40149eb851eb851f 5.16 5.155 +3fe07ae147ae147c 0.52 0.515 +3fe07ae147ae147a 0.51 0.515 +3fe083126e978d50 0.52 0.516 +3fe0872b020c49ba 0.52 0.517 +4014a8f5c28f5c29 5.17 5.165 +3fe083126e978d51 0.52 0.516 +3fe083126e978d4f 0.52 0.516 +3fe08b4395810625 0.52 0.517 +3fe08f5c28f5c28f 0.52 0.518 +4014b33333333333 5.18 5.175 +3fe08b4395810626 0.52 0.517 +3fe08b4395810624 0.52 0.517 +3fe09374bc6a7efa 0.52 0.518 +3fe0978d4fdf3b64 0.52 0.519 +4014bd70a3d70a3d 5.19 5.185 +3fe09374bc6a7efb 0.52 0.518 +3fe09374bc6a7ef9 0.52 0.518 +3fe09ba5e353f7cf 0.52 0.519 +3fe09fbe76c8b439 0.52 0.52 +4014c7ae147ae148 5.2 5.195 +3fe09ba5e353f7d0 0.52 0.519 +3fe09ba5e353f7ce 0.52 0.519 +3fe0a3d70a3d70a4 0.52 0.52 +3fe0a7ef9db22d0e 0.52 0.521 +4014d1eb851eb852 5.21 5.205 +3fe0a3d70a3d70a5 0.52 0.52 +3fe0a3d70a3d70a3 0.52 0.52 +3fe0ac083126e979 0.52 0.521 +3fe0b020c49ba5e3 0.52 0.522 +4014dc28f5c28f5c 5.22 5.215 +3fe0ac083126e97a 0.52 0.521 +3fe0ac083126e978 0.52 0.521 +3fe0b4395810624e 0.52 0.522 +3fe0b851eb851eb8 0.52 0.523 +4014e66666666666 5.23 5.225 +3fe0b4395810624f 0.52 0.522 +3fe0b4395810624d 0.52 0.522 +3fe0bc6a7ef9db23 0.52 0.523 +3fe0c083126e978d 0.52 0.524 +4014f0a3d70a3d71 5.24 5.235 +3fe0bc6a7ef9db24 0.52 0.523 +3fe0bc6a7ef9db22 0.52 0.523 +3fe0c49ba5e353f8 0.52 0.524 +3fe0c8b439581062 0.52 0.525 +4014fae147ae147b 5.25 5.245 +3fe0c49ba5e353f9 0.52 0.524 +3fe0c49ba5e353f7 0.52 0.524 +3fe0d0e560418937 0.53 0.526 +4015051eb851eb85 5.26 5.255 +3fe0ccccccccccce 0.53 0.525 +3fe0cccccccccccc 0.52 0.525 +3fe0d4fdf3b645a2 0.53 0.526 +3fe0d916872b020c 0.53 0.527 +40150f5c28f5c28f 5.27 5.265 +3fe0d4fdf3b645a3 0.53 0.526 +3fe0d4fdf3b645a1 0.53 0.526 +3fe0dd2f1a9fbe77 0.53 0.527 +3fe0e147ae147ae1 0.53 0.528 +401519999999999a 5.28 5.275 +3fe0dd2f1a9fbe78 0.53 0.527 +3fe0dd2f1a9fbe76 0.53 0.527 +3fe0e5604189374c 0.53 0.528 +3fe0e978d4fdf3b6 0.53 0.529 +401523d70a3d70a4 5.29 5.285 +3fe0e5604189374d 0.53 0.528 +3fe0e5604189374b 0.53 0.528 +3fe0ed916872b021 0.53 0.529 +3fe0f1a9fbe76c8b 0.53 0.53 +40152e147ae147ae 5.3 5.295 +3fe0ed916872b022 0.53 0.529 +3fe0ed916872b020 0.53 0.529 +3fe0f5c28f5c28f6 0.53 0.53 +3fe0f9db22d0e560 0.53 0.531 +40153851eb851eb8 5.31 5.305 +3fe0f5c28f5c28f7 0.53 0.53 +3fe0f5c28f5c28f5 0.53 0.53 +3fe0fdf3b645a1cb 0.53 0.531 +3fe1020c49ba5e35 0.53 0.532 +4015428f5c28f5c3 5.32 5.315 +3fe0fdf3b645a1cc 0.53 0.531 +3fe0fdf3b645a1ca 0.53 0.531 +3fe10624dd2f1aa0 0.53 0.532 +3fe10a3d70a3d70a 0.53 0.533 +40154ccccccccccd 5.33 5.325 +3fe10624dd2f1aa1 0.53 0.532 +3fe10624dd2f1a9f 0.53 0.532 +3fe10e5604189375 0.53 0.533 +3fe1126e978d4fdf 0.53 0.534 +4015570a3d70a3d7 5.34 5.335 +3fe10e5604189376 0.53 0.533 +3fe10e5604189374 0.53 0.533 +3fe116872b020c4a 0.53 0.534 +3fe11a9fbe76c8b4 0.53 0.535 +40156147ae147ae1 5.35 5.345 +3fe116872b020c4b 0.53 0.534 +3fe116872b020c49 0.53 0.534 +3fe122d0e5604189 0.54 0.536 +40156b851eb851ec 5.36 5.355 +3fe11eb851eb8520 0.54 0.535 +3fe11eb851eb851e 0.53 0.535 +3fe126e978d4fdf4 0.54 0.536 +3fe12b020c49ba5e 0.54 0.537 +401575c28f5c28f6 5.37 5.365 +3fe126e978d4fdf5 0.54 0.536 +3fe126e978d4fdf3 0.54 0.536 +3fe12f1a9fbe76c9 0.54 0.537 +3fe1333333333333 0.54 0.538 +4015800000000000 5.38 5.375 +3fe12f1a9fbe76ca 0.54 0.537 +3fe12f1a9fbe76c8 0.54 0.537 +3fe1374bc6a7ef9e 0.54 0.538 +3fe13b645a1cac08 0.54 0.539 +40158a3d70a3d70a 5.39 5.385 +3fe1374bc6a7ef9f 0.54 0.538 +3fe1374bc6a7ef9d 0.54 0.538 +3fe13f7ced916873 0.54 0.539 +3fe14395810624dd 0.54 0.54 +4015947ae147ae14 5.4 5.395 +3fe13f7ced916874 0.54 0.539 +3fe13f7ced916872 0.54 0.539 +3fe147ae147ae148 0.54 0.54 +3fe14bc6a7ef9db2 0.54 0.541 +40159eb851eb851f 5.41 5.405 +3fe147ae147ae149 0.54 0.54 +3fe147ae147ae147 0.54 0.54 +3fe14fdf3b645a1d 0.54 0.541 +3fe153f7ced91687 0.54 0.542 +4015a8f5c28f5c29 5.42 5.415 +3fe14fdf3b645a1e 0.54 0.541 +3fe14fdf3b645a1c 0.54 0.541 +3fe15810624dd2f2 0.54 0.542 +3fe15c28f5c28f5c 0.54 0.543 +4015b33333333333 5.43 5.425 +3fe15810624dd2f3 0.54 0.542 +3fe15810624dd2f1 0.54 0.542 +3fe1604189374bc7 0.54 0.543 +3fe1645a1cac0831 0.54 0.544 +4015bd70a3d70a3d 5.44 5.435 +3fe1604189374bc8 0.54 0.543 +3fe1604189374bc6 0.54 0.543 +3fe16872b020c49c 0.54 0.544 +3fe16c8b43958106 0.54 0.545 +4015c7ae147ae148 5.45 5.445 +3fe16872b020c49d 0.54 0.544 +3fe16872b020c49b 0.54 0.544 +3fe174bc6a7ef9db 0.55 0.546 +4015d1eb851eb852 5.46 5.455 +3fe170a3d70a3d72 0.55 0.545 +3fe170a3d70a3d70 0.54 0.545 +3fe178d4fdf3b646 0.55 0.546 +3fe17ced916872b0 0.55 0.547 +4015dc28f5c28f5c 5.47 5.465 +3fe178d4fdf3b647 0.55 0.546 +3fe178d4fdf3b645 0.55 0.546 +3fe1810624dd2f1b 0.55 0.547 +3fe1851eb851eb85 0.55 0.548 +4015e66666666666 5.48 5.475 +3fe1810624dd2f1c 0.55 0.547 +3fe1810624dd2f1a 0.55 0.547 +3fe189374bc6a7f0 0.55 0.548 +3fe18d4fdf3b645a 0.55 0.549 +4015f0a3d70a3d71 5.49 5.485 +3fe189374bc6a7f1 0.55 0.548 +3fe189374bc6a7ef 0.55 0.548 +3fe1916872b020c5 0.55 0.549 +3fe195810624dd2f 0.55 0.55 +4015fae147ae147b 5.5 5.495 +3fe1916872b020c6 0.55 0.549 +3fe1916872b020c4 0.55 0.549 +3fe199999999999a 0.55 0.55 +3fe19db22d0e5604 0.55 0.551 +4016051eb851eb85 5.51 5.505 +3fe199999999999b 0.55 0.55 +3fe1999999999999 0.55 0.55 +3fe1a1cac083126f 0.55 0.551 +3fe1a5e353f7ced9 0.55 0.552 +40160f5c28f5c28f 5.52 5.515 +3fe1a1cac0831270 0.55 0.551 +3fe1a1cac083126e 0.55 0.551 +3fe1a9fbe76c8b44 0.55 0.552 +3fe1ae147ae147ae 0.55 0.553 +401619999999999a 5.53 5.525 +3fe1a9fbe76c8b45 0.55 0.552 +3fe1a9fbe76c8b43 0.55 0.552 +3fe1b22d0e560419 0.55 0.553 +3fe1b645a1cac083 0.55 0.554 +401623d70a3d70a4 5.54 5.535 +3fe1b22d0e56041a 0.55 0.553 +3fe1b22d0e560418 0.55 0.553 +3fe1ba5e353f7cee 0.55 0.554 +3fe1be76c8b43958 0.55 0.555 +40162e147ae147ae 5.55 5.545 +3fe1ba5e353f7cef 0.55 0.554 +3fe1ba5e353f7ced 0.55 0.554 +3fe1c6a7ef9db22d 0.56 0.556 +40163851eb851eb8 5.56 5.555 +3fe1c28f5c28f5c4 0.56 0.555 +3fe1c28f5c28f5c2 0.55 0.555 +3fe1cac083126e98 0.56 0.556 +3fe1ced916872b02 0.56 0.557 +4016428f5c28f5c3 5.57 5.565 +3fe1cac083126e99 0.56 0.556 +3fe1cac083126e97 0.56 0.556 +3fe1d2f1a9fbe76d 0.56 0.557 +3fe1d70a3d70a3d7 0.56 0.558 +40164ccccccccccd 5.58 5.575 +3fe1d2f1a9fbe76e 0.56 0.557 +3fe1d2f1a9fbe76c 0.56 0.557 +3fe1db22d0e56042 0.56 0.558 +3fe1df3b645a1cac 0.56 0.559 +4016570a3d70a3d7 5.59 5.585 +3fe1db22d0e56043 0.56 0.558 +3fe1db22d0e56041 0.56 0.558 +3fe1e353f7ced917 0.56 0.559 +3fe1e76c8b439581 0.56 0.56 +40166147ae147ae1 5.6 5.595 +3fe1e353f7ced918 0.56 0.559 +3fe1e353f7ced916 0.56 0.559 +3fe1eb851eb851ec 0.56 0.56 +3fe1ef9db22d0e56 0.56 0.561 +40166b851eb851ec 5.61 5.605 +3fe1eb851eb851ed 0.56 0.56 +3fe1eb851eb851eb 0.56 0.56 +3fe1f3b645a1cac1 0.56 0.561 +3fe1f7ced916872b 0.56 0.562 +401675c28f5c28f6 5.62 5.615 +3fe1f3b645a1cac2 0.56 0.561 +3fe1f3b645a1cac0 0.56 0.561 +3fe1fbe76c8b4396 0.56 0.562 +3fe2000000000000 0.56 0.563 +4016800000000000 5.63 5.625 +3fe1fbe76c8b4397 0.56 0.562 +3fe1fbe76c8b4395 0.56 0.562 +3fe204189374bc6a 0.56 0.563 +3fe2083126e978d5 0.56 0.564 +40168a3d70a3d70a 5.64 5.635 +3fe204189374bc6b 0.56 0.563 +3fe204189374bc69 0.56 0.563 +3fe20c49ba5e353f 0.56 0.564 +3fe210624dd2f1aa 0.56 0.565 +4016947ae147ae14 5.65 5.645 +3fe20c49ba5e3540 0.56 0.564 +3fe20c49ba5e353e 0.56 0.564 +3fe2189374bc6a7f 0.57 0.566 +40169eb851eb851f 5.66 5.655 +3fe2147ae147ae15 0.57 0.565 +3fe2147ae147ae13 0.56 0.565 +3fe21cac083126e9 0.57 0.566 +3fe220c49ba5e354 0.57 0.567 +4016a8f5c28f5c29 5.67 5.665 +3fe21cac083126ea 0.57 0.566 +3fe21cac083126e8 0.57 0.566 +3fe224dd2f1a9fbe 0.57 0.567 +3fe228f5c28f5c29 0.57 0.568 +4016b33333333333 5.68 5.675 +3fe224dd2f1a9fbf 0.57 0.567 +3fe224dd2f1a9fbd 0.57 0.567 +3fe22d0e56041893 0.57 0.568 +3fe23126e978d4fe 0.57 0.569 +4016bd70a3d70a3d 5.69 5.685 +3fe22d0e56041894 0.57 0.568 +3fe22d0e56041892 0.57 0.568 +3fe2353f7ced9168 0.57 0.569 +3fe2395810624dd3 0.57 0.57 +4016c7ae147ae148 5.7 5.695 +3fe2353f7ced9169 0.57 0.569 +3fe2353f7ced9167 0.57 0.569 +3fe23d70a3d70a3d 0.57 0.57 +3fe24189374bc6a8 0.57 0.571 +4016d1eb851eb852 5.71 5.705 +3fe23d70a3d70a3e 0.57 0.57 +3fe23d70a3d70a3c 0.57 0.57 +3fe245a1cac08312 0.57 0.571 +3fe249ba5e353f7d 0.57 0.572 +4016dc28f5c28f5c 5.72 5.715 +3fe245a1cac08313 0.57 0.571 +3fe245a1cac08311 0.57 0.571 +3fe24dd2f1a9fbe7 0.57 0.572 +3fe251eb851eb852 0.57 0.573 +4016e66666666666 5.73 5.725 +3fe24dd2f1a9fbe8 0.57 0.572 +3fe24dd2f1a9fbe6 0.57 0.572 +3fe25604189374bc 0.57 0.573 +3fe25a1cac083127 0.57 0.574 +4016f0a3d70a3d71 5.74 5.735 +3fe25604189374bd 0.57 0.573 +3fe25604189374bb 0.57 0.573 +3fe25e353f7ced91 0.57 0.574 +3fe2624dd2f1a9fc 0.57 0.575 +4016fae147ae147b 5.75 5.745 +3fe25e353f7ced92 0.57 0.574 +3fe25e353f7ced90 0.57 0.574 +3fe26a7ef9db22d1 0.58 0.576 +4017051eb851eb85 5.76 5.755 +3fe2666666666667 0.58 0.575 +3fe2666666666665 0.57 0.575 +3fe26e978d4fdf3b 0.58 0.576 +3fe272b020c49ba6 0.58 0.577 +40170f5c28f5c28f 5.77 5.765 +3fe26e978d4fdf3c 0.58 0.576 +3fe26e978d4fdf3a 0.58 0.576 +3fe276c8b4395810 0.58 0.577 +3fe27ae147ae147b 0.58 0.578 +401719999999999a 5.78 5.775 +3fe276c8b4395811 0.58 0.577 +3fe276c8b439580f 0.58 0.577 +3fe27ef9db22d0e5 0.58 0.578 +3fe283126e978d50 0.58 0.579 +401723d70a3d70a4 5.79 5.785 +3fe27ef9db22d0e6 0.58 0.578 +3fe27ef9db22d0e4 0.58 0.578 +3fe2872b020c49ba 0.58 0.579 +3fe28b4395810625 0.58 0.58 +40172e147ae147ae 5.8 5.795 +3fe2872b020c49bb 0.58 0.579 +3fe2872b020c49b9 0.58 0.579 +3fe28f5c28f5c28f 0.58 0.58 +3fe29374bc6a7efa 0.58 0.581 +40173851eb851eb8 5.81 5.805 +3fe28f5c28f5c290 0.58 0.58 +3fe28f5c28f5c28e 0.58 0.58 +3fe2978d4fdf3b64 0.58 0.581 +3fe29ba5e353f7cf 0.58 0.582 +4017428f5c28f5c3 5.82 5.815 +3fe2978d4fdf3b65 0.58 0.581 +3fe2978d4fdf3b63 0.58 0.581 +3fe29fbe76c8b439 0.58 0.582 +3fe2a3d70a3d70a4 0.58 0.583 +40174ccccccccccd 5.83 5.825 +3fe29fbe76c8b43a 0.58 0.582 +3fe29fbe76c8b438 0.58 0.582 +3fe2a7ef9db22d0e 0.58 0.583 +3fe2ac083126e979 0.58 0.584 +4017570a3d70a3d7 5.84 5.835 +3fe2a7ef9db22d0f 0.58 0.583 +3fe2a7ef9db22d0d 0.58 0.583 +3fe2b020c49ba5e3 0.58 0.584 +3fe2b4395810624e 0.58 0.585 +40176147ae147ae1 5.85 5.845 +3fe2b020c49ba5e4 0.58 0.584 +3fe2b020c49ba5e2 0.58 0.584 +3fe2bc6a7ef9db23 0.59 0.586 +40176b851eb851ec 5.86 5.855 +3fe2b851eb851eb9 0.59 0.585 +3fe2b851eb851eb7 0.58 0.585 +3fe2c083126e978d 0.59 0.586 +3fe2c49ba5e353f8 0.59 0.587 +401775c28f5c28f6 5.87 5.865 +3fe2c083126e978e 0.59 0.586 +3fe2c083126e978c 0.59 0.586 +3fe2c8b439581062 0.59 0.587 +3fe2cccccccccccd 0.59 0.588 +4017800000000000 5.88 5.875 +3fe2c8b439581063 0.59 0.587 +3fe2c8b439581061 0.59 0.587 +3fe2d0e560418937 0.59 0.588 +3fe2d4fdf3b645a2 0.59 0.589 +40178a3d70a3d70a 5.89 5.885 +3fe2d0e560418938 0.59 0.588 +3fe2d0e560418936 0.59 0.588 +3fe2d916872b020c 0.59 0.589 +3fe2dd2f1a9fbe77 0.59 0.59 +4017947ae147ae14 5.9 5.895 +3fe2d916872b020d 0.59 0.589 +3fe2d916872b020b 0.59 0.589 +3fe2e147ae147ae1 0.59 0.59 +3fe2e5604189374c 0.59 0.591 +40179eb851eb851f 5.91 5.905 +3fe2e147ae147ae2 0.59 0.59 +3fe2e147ae147ae0 0.59 0.59 +3fe2e978d4fdf3b6 0.59 0.591 +3fe2ed916872b021 0.59 0.592 +4017a8f5c28f5c29 5.92 5.915 +3fe2e978d4fdf3b7 0.59 0.591 +3fe2e978d4fdf3b5 0.59 0.591 +3fe2f1a9fbe76c8b 0.59 0.592 +3fe2f5c28f5c28f6 0.59 0.593 +4017b33333333333 5.93 5.925 +3fe2f1a9fbe76c8c 0.59 0.592 +3fe2f1a9fbe76c8a 0.59 0.592 +3fe2f9db22d0e560 0.59 0.593 +3fe2fdf3b645a1cb 0.59 0.594 +4017bd70a3d70a3d 5.94 5.935 +3fe2f9db22d0e561 0.59 0.593 +3fe2f9db22d0e55f 0.59 0.593 +3fe3020c49ba5e35 0.59 0.594 +3fe30624dd2f1aa0 0.59 0.595 +4017c7ae147ae148 5.95 5.945 +3fe3020c49ba5e36 0.59 0.594 +3fe3020c49ba5e34 0.59 0.594 +3fe30e5604189375 0.6 0.596 +4017d1eb851eb852 5.96 5.955 +3fe30a3d70a3d70b 0.6 0.595 +3fe30a3d70a3d709 0.59 0.595 +3fe3126e978d4fdf 0.6 0.596 +3fe316872b020c4a 0.6 0.597 +4017dc28f5c28f5c 5.97 5.965 +3fe3126e978d4fe0 0.6 0.596 +3fe3126e978d4fde 0.6 0.596 +3fe31a9fbe76c8b4 0.6 0.597 +3fe31eb851eb851f 0.6 0.598 +4017e66666666666 5.98 5.975 +3fe31a9fbe76c8b5 0.6 0.597 +3fe31a9fbe76c8b3 0.6 0.597 +3fe322d0e5604189 0.6 0.598 +3fe326e978d4fdf4 0.6 0.599 +4017f0a3d70a3d71 5.99 5.985 +3fe322d0e560418a 0.6 0.598 +3fe322d0e5604188 0.6 0.598 +3fe32b020c49ba5e 0.6 0.599 +3fe32f1a9fbe76c9 0.6 0.6 +4017fae147ae147b 6.0 5.995 +3fe32b020c49ba5f 0.6 0.599 +3fe32b020c49ba5d 0.6 0.599 +3fe3333333333333 0.6 0.6 +3fe3374bc6a7ef9e 0.6 0.601 +4018051eb851eb85 6.01 6.005 +3fe3333333333334 0.6 0.6 +3fe3333333333332 0.6 0.6 +3fe33b645a1cac08 0.6 0.601 +3fe33f7ced916873 0.6 0.602 +40180f5c28f5c28f 6.02 6.015 +3fe33b645a1cac09 0.6 0.601 +3fe33b645a1cac07 0.6 0.601 +3fe34395810624dd 0.6 0.602 +3fe347ae147ae148 0.6 0.603 +401819999999999a 6.03 6.025 +3fe34395810624de 0.6 0.602 +3fe34395810624dc 0.6 0.602 +3fe34bc6a7ef9db2 0.6 0.603 +3fe34fdf3b645a1d 0.6 0.604 +401823d70a3d70a4 6.04 6.035 +3fe34bc6a7ef9db3 0.6 0.603 +3fe34bc6a7ef9db1 0.6 0.603 +3fe353f7ced91687 0.6 0.604 +3fe35810624dd2f2 0.6 0.605 +40182e147ae147ae 6.05 6.045 +3fe353f7ced91688 0.6 0.604 +3fe353f7ced91686 0.6 0.604 +3fe3604189374bc7 0.61 0.606 +40183851eb851eb8 6.06 6.055 +3fe35c28f5c28f5d 0.61 0.605 +3fe35c28f5c28f5b 0.6 0.605 +3fe3645a1cac0831 0.61 0.606 +3fe36872b020c49c 0.61 0.607 +4018428f5c28f5c3 6.07 6.065 +3fe3645a1cac0832 0.61 0.606 +3fe3645a1cac0830 0.61 0.606 +3fe36c8b43958106 0.61 0.607 +3fe370a3d70a3d71 0.61 0.608 +40184ccccccccccd 6.08 6.075 +3fe36c8b43958107 0.61 0.607 +3fe36c8b43958105 0.61 0.607 +3fe374bc6a7ef9db 0.61 0.608 +3fe378d4fdf3b646 0.61 0.609 +4018570a3d70a3d7 6.09 6.085 +3fe374bc6a7ef9dc 0.61 0.608 +3fe374bc6a7ef9da 0.61 0.608 +3fe37ced916872b0 0.61 0.609 +3fe3810624dd2f1b 0.61 0.61 +40186147ae147ae1 6.1 6.095 +3fe37ced916872b1 0.61 0.609 +3fe37ced916872af 0.61 0.609 +3fe3851eb851eb85 0.61 0.61 +3fe389374bc6a7f0 0.61 0.611 +40186b851eb851ec 6.11 6.105 +3fe3851eb851eb86 0.61 0.61 +3fe3851eb851eb84 0.61 0.61 +3fe38d4fdf3b645a 0.61 0.611 +3fe3916872b020c5 0.61 0.612 +401875c28f5c28f6 6.12 6.115 +3fe38d4fdf3b645b 0.61 0.611 +3fe38d4fdf3b6459 0.61 0.611 +3fe395810624dd2f 0.61 0.612 +3fe399999999999a 0.61 0.613 +4018800000000000 6.13 6.125 +3fe395810624dd30 0.61 0.612 +3fe395810624dd2e 0.61 0.612 +3fe39db22d0e5604 0.61 0.613 +3fe3a1cac083126f 0.61 0.614 +40188a3d70a3d70a 6.14 6.135 +3fe39db22d0e5605 0.61 0.613 +3fe39db22d0e5603 0.61 0.613 +3fe3a5e353f7ced9 0.61 0.614 +3fe3a9fbe76c8b44 0.61 0.615 +4018947ae147ae14 6.15 6.145 +3fe3a5e353f7ceda 0.61 0.614 +3fe3a5e353f7ced8 0.61 0.614 +3fe3b22d0e560419 0.62 0.616 +40189eb851eb851f 6.16 6.155 +3fe3ae147ae147af 0.62 0.615 +3fe3ae147ae147ad 0.61 0.615 +3fe3b645a1cac083 0.62 0.616 +3fe3ba5e353f7cee 0.62 0.617 +4018a8f5c28f5c29 6.17 6.165 +3fe3b645a1cac084 0.62 0.616 +3fe3b645a1cac082 0.62 0.616 +3fe3be76c8b43958 0.62 0.617 +3fe3c28f5c28f5c3 0.62 0.618 +4018b33333333333 6.18 6.175 +3fe3be76c8b43959 0.62 0.617 +3fe3be76c8b43957 0.62 0.617 +3fe3c6a7ef9db22d 0.62 0.618 +3fe3cac083126e98 0.62 0.619 +4018bd70a3d70a3d 6.19 6.185 +3fe3c6a7ef9db22e 0.62 0.618 +3fe3c6a7ef9db22c 0.62 0.618 +3fe3ced916872b02 0.62 0.619 +3fe3d2f1a9fbe76d 0.62 0.62 +4018c7ae147ae148 6.2 6.195 +3fe3ced916872b03 0.62 0.619 +3fe3ced916872b01 0.62 0.619 +3fe3d70a3d70a3d7 0.62 0.62 +3fe3db22d0e56042 0.62 0.621 +4018d1eb851eb852 6.21 6.205 +3fe3d70a3d70a3d8 0.62 0.62 +3fe3d70a3d70a3d6 0.62 0.62 +3fe3df3b645a1cac 0.62 0.621 +3fe3e353f7ced917 0.62 0.622 +4018dc28f5c28f5c 6.22 6.215 +3fe3df3b645a1cad 0.62 0.621 +3fe3df3b645a1cab 0.62 0.621 +3fe3e76c8b439581 0.62 0.622 +3fe3eb851eb851ec 0.62 0.623 +4018e66666666666 6.23 6.225 +3fe3e76c8b439582 0.62 0.622 +3fe3e76c8b439580 0.62 0.622 +3fe3ef9db22d0e56 0.62 0.623 +3fe3f3b645a1cac1 0.62 0.624 +4018f0a3d70a3d71 6.24 6.235 +3fe3ef9db22d0e57 0.62 0.623 +3fe3ef9db22d0e55 0.62 0.623 +3fe3f7ced916872b 0.62 0.624 +3fe3fbe76c8b4396 0.62 0.625 +4018fae147ae147b 6.25 6.245 +3fe3f7ced916872c 0.62 0.624 +3fe3f7ced916872a 0.62 0.624 +3fe404189374bc6a 0.63 0.626 +4019051eb851eb85 6.26 6.255 +3fe4000000000001 0.63 0.625 +3fe3ffffffffffff 0.62 0.625 +3fe4083126e978d5 0.63 0.626 +3fe40c49ba5e353f 0.63 0.627 +40190f5c28f5c28f 6.27 6.265 +3fe4083126e978d6 0.63 0.626 +3fe4083126e978d4 0.63 0.626 +3fe410624dd2f1aa 0.63 0.627 +3fe4147ae147ae14 0.63 0.628 +401919999999999a 6.28 6.275 +3fe410624dd2f1ab 0.63 0.627 +3fe410624dd2f1a9 0.63 0.627 +3fe4189374bc6a7f 0.63 0.628 +3fe41cac083126e9 0.63 0.629 +401923d70a3d70a4 6.29 6.285 +3fe4189374bc6a80 0.63 0.628 +3fe4189374bc6a7e 0.63 0.628 +3fe420c49ba5e354 0.63 0.629 +3fe424dd2f1a9fbe 0.63 0.63 +40192e147ae147ae 6.3 6.295 +3fe420c49ba5e355 0.63 0.629 +3fe420c49ba5e353 0.63 0.629 +3fe428f5c28f5c29 0.63 0.63 +3fe42d0e56041893 0.63 0.631 +40193851eb851eb8 6.31 6.305 +3fe428f5c28f5c2a 0.63 0.63 +3fe428f5c28f5c28 0.63 0.63 +3fe43126e978d4fe 0.63 0.631 +3fe4353f7ced9168 0.63 0.632 +4019428f5c28f5c3 6.32 6.315 +3fe43126e978d4ff 0.63 0.631 +3fe43126e978d4fd 0.63 0.631 +3fe4395810624dd3 0.63 0.632 +3fe43d70a3d70a3d 0.63 0.633 +40194ccccccccccd 6.33 6.325 +3fe4395810624dd4 0.63 0.632 +3fe4395810624dd2 0.63 0.632 +3fe44189374bc6a8 0.63 0.633 +3fe445a1cac08312 0.63 0.634 +4019570a3d70a3d7 6.34 6.335 +3fe44189374bc6a9 0.63 0.633 +3fe44189374bc6a7 0.63 0.633 +3fe449ba5e353f7d 0.63 0.634 +3fe44dd2f1a9fbe7 0.63 0.635 +40196147ae147ae1 6.35 6.345 +3fe449ba5e353f7e 0.63 0.634 +3fe449ba5e353f7c 0.63 0.634 +3fe45604189374bc 0.64 0.636 +40196b851eb851ec 6.36 6.355 +3fe451eb851eb853 0.64 0.635 +3fe451eb851eb851 0.63 0.635 +3fe45a1cac083127 0.64 0.636 +3fe45e353f7ced91 0.64 0.637 +401975c28f5c28f6 6.37 6.365 +3fe45a1cac083128 0.64 0.636 +3fe45a1cac083126 0.64 0.636 +3fe4624dd2f1a9fc 0.64 0.637 +3fe4666666666666 0.64 0.638 +4019800000000000 6.38 6.375 +3fe4624dd2f1a9fd 0.64 0.637 +3fe4624dd2f1a9fb 0.64 0.637 +3fe46a7ef9db22d1 0.64 0.638 +3fe46e978d4fdf3b 0.64 0.639 +40198a3d70a3d70a 6.39 6.385 +3fe46a7ef9db22d2 0.64 0.638 +3fe46a7ef9db22d0 0.64 0.638 +3fe472b020c49ba6 0.64 0.639 +3fe476c8b4395810 0.64 0.64 +4019947ae147ae14 6.4 6.395 +3fe472b020c49ba7 0.64 0.639 +3fe472b020c49ba5 0.64 0.639 +3fe47ae147ae147b 0.64 0.64 +3fe47ef9db22d0e5 0.64 0.641 +40199eb851eb851f 6.41 6.405 +3fe47ae147ae147c 0.64 0.64 +3fe47ae147ae147a 0.64 0.64 +3fe483126e978d50 0.64 0.641 +3fe4872b020c49ba 0.64 0.642 +4019a8f5c28f5c29 6.42 6.415 +3fe483126e978d51 0.64 0.641 +3fe483126e978d4f 0.64 0.641 +3fe48b4395810625 0.64 0.642 +3fe48f5c28f5c28f 0.64 0.643 +4019b33333333333 6.43 6.425 +3fe48b4395810626 0.64 0.642 +3fe48b4395810624 0.64 0.642 +3fe49374bc6a7efa 0.64 0.643 +3fe4978d4fdf3b64 0.64 0.644 +4019bd70a3d70a3d 6.44 6.435 +3fe49374bc6a7efb 0.64 0.643 +3fe49374bc6a7ef9 0.64 0.643 +3fe49ba5e353f7cf 0.64 0.644 +3fe49fbe76c8b439 0.64 0.645 +4019c7ae147ae148 6.45 6.445 +3fe49ba5e353f7d0 0.64 0.644 +3fe49ba5e353f7ce 0.64 0.644 +3fe4a7ef9db22d0e 0.65 0.646 +4019d1eb851eb852 6.46 6.455 +3fe4a3d70a3d70a5 0.65 0.645 +3fe4a3d70a3d70a3 0.64 0.645 +3fe4ac083126e979 0.65 0.646 +3fe4b020c49ba5e3 0.65 0.647 +4019dc28f5c28f5c 6.47 6.465 +3fe4ac083126e97a 0.65 0.646 +3fe4ac083126e978 0.65 0.646 +3fe4b4395810624e 0.65 0.647 +3fe4b851eb851eb8 0.65 0.648 +4019e66666666666 6.48 6.475 +3fe4b4395810624f 0.65 0.647 +3fe4b4395810624d 0.65 0.647 +3fe4bc6a7ef9db23 0.65 0.648 +3fe4c083126e978d 0.65 0.649 +4019f0a3d70a3d71 6.49 6.485 +3fe4bc6a7ef9db24 0.65 0.648 +3fe4bc6a7ef9db22 0.65 0.648 +3fe4c49ba5e353f8 0.65 0.649 +3fe4c8b439581062 0.65 0.65 +4019fae147ae147b 6.5 6.495 +3fe4c49ba5e353f9 0.65 0.649 +3fe4c49ba5e353f7 0.65 0.649 +3fe4cccccccccccd 0.65 0.65 +3fe4d0e560418937 0.65 0.651 +401a051eb851eb85 6.51 6.505 +3fe4ccccccccccce 0.65 0.65 +3fe4cccccccccccc 0.65 0.65 +3fe4d4fdf3b645a2 0.65 0.651 +3fe4d916872b020c 0.65 0.652 +401a0f5c28f5c28f 6.52 6.515 +3fe4d4fdf3b645a3 0.65 0.651 +3fe4d4fdf3b645a1 0.65 0.651 +3fe4dd2f1a9fbe77 0.65 0.652 +3fe4e147ae147ae1 0.65 0.653 +401a19999999999a 6.53 6.525 +3fe4dd2f1a9fbe78 0.65 0.652 +3fe4dd2f1a9fbe76 0.65 0.652 +3fe4e5604189374c 0.65 0.653 +3fe4e978d4fdf3b6 0.65 0.654 +401a23d70a3d70a4 6.54 6.535 +3fe4e5604189374d 0.65 0.653 +3fe4e5604189374b 0.65 0.653 +3fe4ed916872b021 0.65 0.654 +3fe4f1a9fbe76c8b 0.65 0.655 +401a2e147ae147ae 6.55 6.545 +3fe4ed916872b022 0.65 0.654 +3fe4ed916872b020 0.65 0.654 +3fe4f9db22d0e560 0.66 0.656 +401a3851eb851eb8 6.56 6.555 +3fe4f5c28f5c28f7 0.66 0.655 +3fe4f5c28f5c28f5 0.65 0.655 +3fe4fdf3b645a1cb 0.66 0.656 +3fe5020c49ba5e35 0.66 0.657 +401a428f5c28f5c3 6.57 6.565 +3fe4fdf3b645a1cc 0.66 0.656 +3fe4fdf3b645a1ca 0.66 0.656 +3fe50624dd2f1aa0 0.66 0.657 +3fe50a3d70a3d70a 0.66 0.658 +401a4ccccccccccd 6.58 6.575 +3fe50624dd2f1aa1 0.66 0.657 +3fe50624dd2f1a9f 0.66 0.657 +3fe50e5604189375 0.66 0.658 +3fe5126e978d4fdf 0.66 0.659 +401a570a3d70a3d7 6.59 6.585 +3fe50e5604189376 0.66 0.658 +3fe50e5604189374 0.66 0.658 +3fe516872b020c4a 0.66 0.659 +3fe51a9fbe76c8b4 0.66 0.66 +401a6147ae147ae1 6.6 6.595 +3fe516872b020c4b 0.66 0.659 +3fe516872b020c49 0.66 0.659 +3fe51eb851eb851f 0.66 0.66 +3fe522d0e5604189 0.66 0.661 +401a6b851eb851ec 6.61 6.605 +3fe51eb851eb8520 0.66 0.66 +3fe51eb851eb851e 0.66 0.66 +3fe526e978d4fdf4 0.66 0.661 +3fe52b020c49ba5e 0.66 0.662 +401a75c28f5c28f6 6.62 6.615 +3fe526e978d4fdf5 0.66 0.661 +3fe526e978d4fdf3 0.66 0.661 +3fe52f1a9fbe76c9 0.66 0.662 +3fe5333333333333 0.66 0.663 +401a800000000000 6.63 6.625 +3fe52f1a9fbe76ca 0.66 0.662 +3fe52f1a9fbe76c8 0.66 0.662 +3fe5374bc6a7ef9e 0.66 0.663 +3fe53b645a1cac08 0.66 0.664 +401a8a3d70a3d70a 6.64 6.635 +3fe5374bc6a7ef9f 0.66 0.663 +3fe5374bc6a7ef9d 0.66 0.663 +3fe53f7ced916873 0.66 0.664 +3fe54395810624dd 0.66 0.665 +401a947ae147ae14 6.65 6.645 +3fe53f7ced916874 0.66 0.664 +3fe53f7ced916872 0.66 0.664 +3fe54bc6a7ef9db2 0.67 0.666 +401a9eb851eb851f 6.66 6.655 +3fe547ae147ae149 0.67 0.665 +3fe547ae147ae147 0.66 0.665 +3fe54fdf3b645a1d 0.67 0.666 +3fe553f7ced91687 0.67 0.667 +401aa8f5c28f5c29 6.67 6.665 +3fe54fdf3b645a1e 0.67 0.666 +3fe54fdf3b645a1c 0.67 0.666 +3fe55810624dd2f2 0.67 0.667 +3fe55c28f5c28f5c 0.67 0.668 +401ab33333333333 6.68 6.675 +3fe55810624dd2f3 0.67 0.667 +3fe55810624dd2f1 0.67 0.667 +3fe5604189374bc7 0.67 0.668 +3fe5645a1cac0831 0.67 0.669 +401abd70a3d70a3d 6.69 6.685 +3fe5604189374bc8 0.67 0.668 +3fe5604189374bc6 0.67 0.668 +3fe56872b020c49c 0.67 0.669 +3fe56c8b43958106 0.67 0.67 +401ac7ae147ae148 6.7 6.695 +3fe56872b020c49d 0.67 0.669 +3fe56872b020c49b 0.67 0.669 +3fe570a3d70a3d71 0.67 0.67 +3fe574bc6a7ef9db 0.67 0.671 +401ad1eb851eb852 6.71 6.705 +3fe570a3d70a3d72 0.67 0.67 +3fe570a3d70a3d70 0.67 0.67 +3fe578d4fdf3b646 0.67 0.671 +3fe57ced916872b0 0.67 0.672 +401adc28f5c28f5c 6.72 6.715 +3fe578d4fdf3b647 0.67 0.671 +3fe578d4fdf3b645 0.67 0.671 +3fe5810624dd2f1b 0.67 0.672 +3fe5851eb851eb85 0.67 0.673 +401ae66666666666 6.73 6.725 +3fe5810624dd2f1c 0.67 0.672 +3fe5810624dd2f1a 0.67 0.672 +3fe589374bc6a7f0 0.67 0.673 +3fe58d4fdf3b645a 0.67 0.674 +401af0a3d70a3d71 6.74 6.735 +3fe589374bc6a7f1 0.67 0.673 +3fe589374bc6a7ef 0.67 0.673 +3fe5916872b020c5 0.67 0.674 +3fe595810624dd2f 0.67 0.675 +401afae147ae147b 6.75 6.745 +3fe5916872b020c6 0.67 0.674 +3fe5916872b020c4 0.67 0.674 +3fe59db22d0e5604 0.68 0.676 +401b051eb851eb85 6.76 6.755 +3fe599999999999b 0.68 0.675 +3fe5999999999999 0.68 0.675 +3fe5a1cac083126f 0.68 0.676 +3fe5a5e353f7ced9 0.68 0.677 +401b0f5c28f5c28f 6.77 6.765 +3fe5a1cac0831270 0.68 0.676 +3fe5a1cac083126e 0.68 0.676 +3fe5a9fbe76c8b44 0.68 0.677 +3fe5ae147ae147ae 0.68 0.678 +401b19999999999a 6.78 6.775 +3fe5a9fbe76c8b45 0.68 0.677 +3fe5a9fbe76c8b43 0.68 0.677 +3fe5b22d0e560419 0.68 0.678 +3fe5b645a1cac083 0.68 0.679 +401b23d70a3d70a4 6.79 6.785 +3fe5b22d0e56041a 0.68 0.678 +3fe5b22d0e560418 0.68 0.678 +3fe5ba5e353f7cee 0.68 0.679 +3fe5be76c8b43958 0.68 0.68 +401b2e147ae147ae 6.8 6.795 +3fe5ba5e353f7cef 0.68 0.679 +3fe5ba5e353f7ced 0.68 0.679 +3fe5c28f5c28f5c3 0.68 0.68 +3fe5c6a7ef9db22d 0.68 0.681 +401b3851eb851eb8 6.81 6.805 +3fe5c28f5c28f5c4 0.68 0.68 +3fe5c28f5c28f5c2 0.68 0.68 +3fe5cac083126e98 0.68 0.681 +3fe5ced916872b02 0.68 0.682 +401b428f5c28f5c3 6.82 6.815 +3fe5cac083126e99 0.68 0.681 +3fe5cac083126e97 0.68 0.681 +3fe5d2f1a9fbe76d 0.68 0.682 +3fe5d70a3d70a3d7 0.68 0.683 +401b4ccccccccccd 6.83 6.825 +3fe5d2f1a9fbe76e 0.68 0.682 +3fe5d2f1a9fbe76c 0.68 0.682 +3fe5db22d0e56042 0.68 0.683 +3fe5df3b645a1cac 0.68 0.684 +401b570a3d70a3d7 6.84 6.835 +3fe5db22d0e56043 0.68 0.683 +3fe5db22d0e56041 0.68 0.683 +3fe5e353f7ced917 0.68 0.684 +3fe5e76c8b439581 0.68 0.685 +401b6147ae147ae1 6.85 6.845 +3fe5e353f7ced918 0.68 0.684 +3fe5e353f7ced916 0.68 0.684 +3fe5ef9db22d0e56 0.69 0.686 +401b6b851eb851ec 6.86 6.855 +3fe5eb851eb851ed 0.69 0.685 +3fe5eb851eb851eb 0.69 0.685 +3fe5f3b645a1cac1 0.69 0.686 +3fe5f7ced916872b 0.69 0.687 +401b75c28f5c28f6 6.87 6.865 +3fe5f3b645a1cac2 0.69 0.686 +3fe5f3b645a1cac0 0.69 0.686 +3fe5fbe76c8b4396 0.69 0.687 +3fe6000000000000 0.69 0.688 +401b800000000000 6.88 6.875 +3fe5fbe76c8b4397 0.69 0.687 +3fe5fbe76c8b4395 0.69 0.687 +3fe604189374bc6a 0.69 0.688 +3fe6083126e978d5 0.69 0.689 +401b8a3d70a3d70a 6.89 6.885 +3fe604189374bc6b 0.69 0.688 +3fe604189374bc69 0.69 0.688 +3fe60c49ba5e353f 0.69 0.689 +3fe610624dd2f1aa 0.69 0.69 +401b947ae147ae14 6.9 6.895 +3fe60c49ba5e3540 0.69 0.689 +3fe60c49ba5e353e 0.69 0.689 +3fe6147ae147ae14 0.69 0.69 +3fe6189374bc6a7f 0.69 0.691 +401b9eb851eb851f 6.91 6.905 +3fe6147ae147ae15 0.69 0.69 +3fe6147ae147ae13 0.69 0.69 +3fe61cac083126e9 0.69 0.691 +3fe620c49ba5e354 0.69 0.692 +401ba8f5c28f5c29 6.92 6.915 +3fe61cac083126ea 0.69 0.691 +3fe61cac083126e8 0.69 0.691 +3fe624dd2f1a9fbe 0.69 0.692 +3fe628f5c28f5c29 0.69 0.693 +401bb33333333333 6.93 6.925 +3fe624dd2f1a9fbf 0.69 0.692 +3fe624dd2f1a9fbd 0.69 0.692 +3fe62d0e56041893 0.69 0.693 +3fe63126e978d4fe 0.69 0.694 +401bbd70a3d70a3d 6.94 6.935 +3fe62d0e56041894 0.69 0.693 +3fe62d0e56041892 0.69 0.693 +3fe6353f7ced9168 0.69 0.694 +3fe6395810624dd3 0.69 0.695 +401bc7ae147ae148 6.95 6.945 +3fe6353f7ced9169 0.69 0.694 +3fe6353f7ced9167 0.69 0.694 +3fe64189374bc6a8 0.7 0.696 +401bd1eb851eb852 6.96 6.955 +3fe63d70a3d70a3e 0.7 0.695 +3fe63d70a3d70a3c 0.69 0.695 +3fe645a1cac08312 0.7 0.696 +3fe649ba5e353f7d 0.7 0.697 +401bdc28f5c28f5c 6.97 6.965 +3fe645a1cac08313 0.7 0.696 +3fe645a1cac08311 0.7 0.696 +3fe64dd2f1a9fbe7 0.7 0.697 +3fe651eb851eb852 0.7 0.698 +401be66666666666 6.98 6.975 +3fe64dd2f1a9fbe8 0.7 0.697 +3fe64dd2f1a9fbe6 0.7 0.697 +3fe65604189374bc 0.7 0.698 +3fe65a1cac083127 0.7 0.699 +401bf0a3d70a3d71 6.99 6.985 +3fe65604189374bd 0.7 0.698 +3fe65604189374bb 0.7 0.698 +3fe65e353f7ced91 0.7 0.699 +3fe6624dd2f1a9fc 0.7 0.7 +401bfae147ae147b 7.0 6.995 +3fe65e353f7ced92 0.7 0.699 +3fe65e353f7ced90 0.7 0.699 +3fe6666666666666 0.7 0.7 +3fe66a7ef9db22d1 0.7 0.701 +401c051eb851eb85 7.01 7.005 +3fe6666666666667 0.7 0.7 +3fe6666666666665 0.7 0.7 +3fe66e978d4fdf3b 0.7 0.701 +3fe672b020c49ba6 0.7 0.702 +401c0f5c28f5c28f 7.02 7.015 +3fe66e978d4fdf3c 0.7 0.701 +3fe66e978d4fdf3a 0.7 0.701 +3fe676c8b4395810 0.7 0.702 +3fe67ae147ae147b 0.7 0.703 +401c19999999999a 7.03 7.025 +3fe676c8b4395811 0.7 0.702 +3fe676c8b439580f 0.7 0.702 +3fe67ef9db22d0e5 0.7 0.703 +3fe683126e978d50 0.7 0.704 +401c23d70a3d70a4 7.04 7.035 +3fe67ef9db22d0e6 0.7 0.703 +3fe67ef9db22d0e4 0.7 0.703 +3fe6872b020c49ba 0.7 0.704 +3fe68b4395810625 0.7 0.705 +401c2e147ae147ae 7.05 7.045 +3fe6872b020c49bb 0.7 0.704 +3fe6872b020c49b9 0.7 0.704 +3fe69374bc6a7efa 0.71 0.706 +401c3851eb851eb8 7.06 7.055 +3fe68f5c28f5c290 0.71 0.705 +3fe68f5c28f5c28e 0.7 0.705 +3fe6978d4fdf3b64 0.71 0.706 +3fe69ba5e353f7cf 0.71 0.707 +401c428f5c28f5c3 7.07 7.065 +3fe6978d4fdf3b65 0.71 0.706 +3fe6978d4fdf3b63 0.71 0.706 +3fe69fbe76c8b439 0.71 0.707 +3fe6a3d70a3d70a4 0.71 0.708 +401c4ccccccccccd 7.08 7.075 +3fe69fbe76c8b43a 0.71 0.707 +3fe69fbe76c8b438 0.71 0.707 +3fe6a7ef9db22d0e 0.71 0.708 +3fe6ac083126e979 0.71 0.709 +401c570a3d70a3d7 7.09 7.085 +3fe6a7ef9db22d0f 0.71 0.708 +3fe6a7ef9db22d0d 0.71 0.708 +3fe6b020c49ba5e3 0.71 0.709 +3fe6b4395810624e 0.71 0.71 +401c6147ae147ae1 7.1 7.095 +3fe6b020c49ba5e4 0.71 0.709 +3fe6b020c49ba5e2 0.71 0.709 +3fe6b851eb851eb8 0.71 0.71 +3fe6bc6a7ef9db23 0.71 0.711 +401c6b851eb851ec 7.11 7.105 +3fe6b851eb851eb9 0.71 0.71 +3fe6b851eb851eb7 0.71 0.71 +3fe6c083126e978d 0.71 0.711 +3fe6c49ba5e353f8 0.71 0.712 +401c75c28f5c28f6 7.12 7.115 +3fe6c083126e978e 0.71 0.711 +3fe6c083126e978c 0.71 0.711 +3fe6c8b439581062 0.71 0.712 +3fe6cccccccccccd 0.71 0.713 +401c800000000000 7.13 7.125 +3fe6c8b439581063 0.71 0.712 +3fe6c8b439581061 0.71 0.712 +3fe6d0e560418937 0.71 0.713 +3fe6d4fdf3b645a2 0.71 0.714 +401c8a3d70a3d70a 7.14 7.135 +3fe6d0e560418938 0.71 0.713 +3fe6d0e560418936 0.71 0.713 +3fe6d916872b020c 0.71 0.714 +3fe6dd2f1a9fbe77 0.71 0.715 +401c947ae147ae14 7.15 7.145 +3fe6d916872b020d 0.71 0.714 +3fe6d916872b020b 0.71 0.714 +3fe6e5604189374c 0.72 0.716 +401c9eb851eb851f 7.16 7.155 +3fe6e147ae147ae2 0.72 0.715 +3fe6e147ae147ae0 0.71 0.715 +3fe6e978d4fdf3b6 0.72 0.716 +3fe6ed916872b021 0.72 0.717 +401ca8f5c28f5c29 7.17 7.165 +3fe6e978d4fdf3b7 0.72 0.716 +3fe6e978d4fdf3b5 0.72 0.716 +3fe6f1a9fbe76c8b 0.72 0.717 +3fe6f5c28f5c28f6 0.72 0.718 +401cb33333333333 7.18 7.175 +3fe6f1a9fbe76c8c 0.72 0.717 +3fe6f1a9fbe76c8a 0.72 0.717 +3fe6f9db22d0e560 0.72 0.718 +3fe6fdf3b645a1cb 0.72 0.719 +401cbd70a3d70a3d 7.19 7.185 +3fe6f9db22d0e561 0.72 0.718 +3fe6f9db22d0e55f 0.72 0.718 +3fe7020c49ba5e35 0.72 0.719 +3fe70624dd2f1aa0 0.72 0.72 +401cc7ae147ae148 7.2 7.195 +3fe7020c49ba5e36 0.72 0.719 +3fe7020c49ba5e34 0.72 0.719 +3fe70a3d70a3d70a 0.72 0.72 +3fe70e5604189375 0.72 0.721 +401cd1eb851eb852 7.21 7.205 +3fe70a3d70a3d70b 0.72 0.72 +3fe70a3d70a3d709 0.72 0.72 +3fe7126e978d4fdf 0.72 0.721 +3fe716872b020c4a 0.72 0.722 +401cdc28f5c28f5c 7.22 7.215 +3fe7126e978d4fe0 0.72 0.721 +3fe7126e978d4fde 0.72 0.721 +3fe71a9fbe76c8b4 0.72 0.722 +3fe71eb851eb851f 0.72 0.723 +401ce66666666666 7.23 7.225 +3fe71a9fbe76c8b5 0.72 0.722 +3fe71a9fbe76c8b3 0.72 0.722 +3fe722d0e5604189 0.72 0.723 +3fe726e978d4fdf4 0.72 0.724 +401cf0a3d70a3d71 7.24 7.235 +3fe722d0e560418a 0.72 0.723 +3fe722d0e5604188 0.72 0.723 +3fe72b020c49ba5e 0.72 0.724 +3fe72f1a9fbe76c9 0.72 0.725 +401cfae147ae147b 7.25 7.245 +3fe72b020c49ba5f 0.72 0.724 +3fe72b020c49ba5d 0.72 0.724 +3fe7374bc6a7ef9e 0.73 0.726 +401d051eb851eb85 7.26 7.255 +3fe7333333333334 0.73 0.725 +3fe7333333333332 0.72 0.725 +3fe73b645a1cac08 0.73 0.726 +3fe73f7ced916873 0.73 0.727 +401d0f5c28f5c28f 7.27 7.265 +3fe73b645a1cac09 0.73 0.726 +3fe73b645a1cac07 0.73 0.726 +3fe74395810624dd 0.73 0.727 +3fe747ae147ae148 0.73 0.728 +401d19999999999a 7.28 7.275 +3fe74395810624de 0.73 0.727 +3fe74395810624dc 0.73 0.727 +3fe74bc6a7ef9db2 0.73 0.728 +3fe74fdf3b645a1d 0.73 0.729 +401d23d70a3d70a4 7.29 7.285 +3fe74bc6a7ef9db3 0.73 0.728 +3fe74bc6a7ef9db1 0.73 0.728 +3fe753f7ced91687 0.73 0.729 +3fe75810624dd2f2 0.73 0.73 +401d2e147ae147ae 7.3 7.295 +3fe753f7ced91688 0.73 0.729 +3fe753f7ced91686 0.73 0.729 +3fe75c28f5c28f5c 0.73 0.73 +3fe7604189374bc7 0.73 0.731 +401d3851eb851eb8 7.31 7.305 +3fe75c28f5c28f5d 0.73 0.73 +3fe75c28f5c28f5b 0.73 0.73 +3fe7645a1cac0831 0.73 0.731 +3fe76872b020c49c 0.73 0.732 +401d428f5c28f5c3 7.32 7.315 +3fe7645a1cac0832 0.73 0.731 +3fe7645a1cac0830 0.73 0.731 +3fe76c8b43958106 0.73 0.732 +3fe770a3d70a3d71 0.73 0.733 +401d4ccccccccccd 7.33 7.325 +3fe76c8b43958107 0.73 0.732 +3fe76c8b43958105 0.73 0.732 +3fe774bc6a7ef9db 0.73 0.733 +3fe778d4fdf3b646 0.73 0.734 +401d570a3d70a3d7 7.34 7.335 +3fe774bc6a7ef9dc 0.73 0.733 +3fe774bc6a7ef9da 0.73 0.733 +3fe77ced916872b0 0.73 0.734 +3fe7810624dd2f1b 0.73 0.735 +401d6147ae147ae1 7.35 7.345 +3fe77ced916872b1 0.73 0.734 +3fe77ced916872af 0.73 0.734 +3fe789374bc6a7f0 0.74 0.736 +401d6b851eb851ec 7.36 7.355 +3fe7851eb851eb86 0.74 0.735 +3fe7851eb851eb84 0.73 0.735 +3fe78d4fdf3b645a 0.74 0.736 +3fe7916872b020c5 0.74 0.737 +401d75c28f5c28f6 7.37 7.365 +3fe78d4fdf3b645b 0.74 0.736 +3fe78d4fdf3b6459 0.74 0.736 +3fe795810624dd2f 0.74 0.737 +3fe799999999999a 0.74 0.738 +401d800000000000 7.38 7.375 +3fe795810624dd30 0.74 0.737 +3fe795810624dd2e 0.74 0.737 +3fe79db22d0e5604 0.74 0.738 +3fe7a1cac083126f 0.74 0.739 +401d8a3d70a3d70a 7.39 7.385 +3fe79db22d0e5605 0.74 0.738 +3fe79db22d0e5603 0.74 0.738 +3fe7a5e353f7ced9 0.74 0.739 +3fe7a9fbe76c8b44 0.74 0.74 +401d947ae147ae14 7.4 7.395 +3fe7a5e353f7ceda 0.74 0.739 +3fe7a5e353f7ced8 0.74 0.739 +3fe7ae147ae147ae 0.74 0.74 +3fe7b22d0e560419 0.74 0.741 +401d9eb851eb851f 7.41 7.405 +3fe7ae147ae147af 0.74 0.74 +3fe7ae147ae147ad 0.74 0.74 +3fe7b645a1cac083 0.74 0.741 +3fe7ba5e353f7cee 0.74 0.742 +401da8f5c28f5c29 7.42 7.415 +3fe7b645a1cac084 0.74 0.741 +3fe7b645a1cac082 0.74 0.741 +3fe7be76c8b43958 0.74 0.742 +3fe7c28f5c28f5c3 0.74 0.743 +401db33333333333 7.43 7.425 +3fe7be76c8b43959 0.74 0.742 +3fe7be76c8b43957 0.74 0.742 +3fe7c6a7ef9db22d 0.74 0.743 +3fe7cac083126e98 0.74 0.744 +401dbd70a3d70a3d 7.44 7.435 +3fe7c6a7ef9db22e 0.74 0.743 +3fe7c6a7ef9db22c 0.74 0.743 +3fe7ced916872b02 0.74 0.744 +3fe7d2f1a9fbe76d 0.74 0.745 +401dc7ae147ae148 7.45 7.445 +3fe7ced916872b03 0.74 0.744 +3fe7ced916872b01 0.74 0.744 +3fe7db22d0e56042 0.75 0.746 +401dd1eb851eb852 7.46 7.455 +3fe7d70a3d70a3d8 0.75 0.745 +3fe7d70a3d70a3d6 0.74 0.745 +3fe7df3b645a1cac 0.75 0.746 +3fe7e353f7ced917 0.75 0.747 +401ddc28f5c28f5c 7.47 7.465 +3fe7df3b645a1cad 0.75 0.746 +3fe7df3b645a1cab 0.75 0.746 +3fe7e76c8b439581 0.75 0.747 +3fe7eb851eb851ec 0.75 0.748 +401de66666666666 7.48 7.475 +3fe7e76c8b439582 0.75 0.747 +3fe7e76c8b439580 0.75 0.747 +3fe7ef9db22d0e56 0.75 0.748 +3fe7f3b645a1cac1 0.75 0.749 +401df0a3d70a3d71 7.49 7.485 +3fe7ef9db22d0e57 0.75 0.748 +3fe7ef9db22d0e55 0.75 0.748 +3fe7f7ced916872b 0.75 0.749 +3fe7fbe76c8b4396 0.75 0.75 +401dfae147ae147b 7.5 7.495 +3fe7f7ced916872c 0.75 0.749 +3fe7f7ced916872a 0.75 0.749 +3fe8000000000000 0.75 0.75 +3fe804189374bc6a 0.75 0.751 +401e051eb851eb85 7.51 7.505 +3fe8000000000001 0.75 0.75 +3fe7ffffffffffff 0.75 0.75 +3fe8083126e978d5 0.75 0.751 +3fe80c49ba5e353f 0.75 0.752 +401e0f5c28f5c28f 7.52 7.515 +3fe8083126e978d6 0.75 0.751 +3fe8083126e978d4 0.75 0.751 +3fe810624dd2f1aa 0.75 0.752 +3fe8147ae147ae14 0.75 0.753 +401e19999999999a 7.53 7.525 +3fe810624dd2f1ab 0.75 0.752 +3fe810624dd2f1a9 0.75 0.752 +3fe8189374bc6a7f 0.75 0.753 +3fe81cac083126e9 0.75 0.754 +401e23d70a3d70a4 7.54 7.535 +3fe8189374bc6a80 0.75 0.753 +3fe8189374bc6a7e 0.75 0.753 +3fe820c49ba5e354 0.75 0.754 +3fe824dd2f1a9fbe 0.75 0.755 +401e2e147ae147ae 7.55 7.545 +3fe820c49ba5e355 0.75 0.754 +3fe820c49ba5e353 0.75 0.754 +3fe82d0e56041893 0.76 0.756 +401e3851eb851eb8 7.56 7.555 +3fe828f5c28f5c2a 0.76 0.755 +3fe828f5c28f5c28 0.75 0.755 +3fe83126e978d4fe 0.76 0.756 +3fe8353f7ced9168 0.76 0.757 +401e428f5c28f5c3 7.57 7.565 +3fe83126e978d4ff 0.76 0.756 +3fe83126e978d4fd 0.76 0.756 +3fe8395810624dd3 0.76 0.757 +3fe83d70a3d70a3d 0.76 0.758 +401e4ccccccccccd 7.58 7.575 +3fe8395810624dd4 0.76 0.757 +3fe8395810624dd2 0.76 0.757 +3fe84189374bc6a8 0.76 0.758 +3fe845a1cac08312 0.76 0.759 +401e570a3d70a3d7 7.59 7.585 +3fe84189374bc6a9 0.76 0.758 +3fe84189374bc6a7 0.76 0.758 +3fe849ba5e353f7d 0.76 0.759 +3fe84dd2f1a9fbe7 0.76 0.76 +401e6147ae147ae1 7.6 7.595 +3fe849ba5e353f7e 0.76 0.759 +3fe849ba5e353f7c 0.76 0.759 +3fe851eb851eb852 0.76 0.76 +3fe85604189374bc 0.76 0.761 +401e6b851eb851ec 7.61 7.605 +3fe851eb851eb853 0.76 0.76 +3fe851eb851eb851 0.76 0.76 +3fe85a1cac083127 0.76 0.761 +3fe85e353f7ced91 0.76 0.762 +401e75c28f5c28f6 7.62 7.615 +3fe85a1cac083128 0.76 0.761 +3fe85a1cac083126 0.76 0.761 +3fe8624dd2f1a9fc 0.76 0.762 +3fe8666666666666 0.76 0.763 +401e800000000000 7.63 7.625 +3fe8624dd2f1a9fd 0.76 0.762 +3fe8624dd2f1a9fb 0.76 0.762 +3fe86a7ef9db22d1 0.76 0.763 +3fe86e978d4fdf3b 0.76 0.764 +401e8a3d70a3d70a 7.64 7.635 +3fe86a7ef9db22d2 0.76 0.763 +3fe86a7ef9db22d0 0.76 0.763 +3fe872b020c49ba6 0.76 0.764 +3fe876c8b4395810 0.76 0.765 +401e947ae147ae14 7.65 7.645 +3fe872b020c49ba7 0.76 0.764 +3fe872b020c49ba5 0.76 0.764 +3fe87ef9db22d0e5 0.77 0.766 +401e9eb851eb851f 7.66 7.655 +3fe87ae147ae147c 0.77 0.765 +3fe87ae147ae147a 0.76 0.765 +3fe883126e978d50 0.77 0.766 +3fe8872b020c49ba 0.77 0.767 +401ea8f5c28f5c29 7.67 7.665 +3fe883126e978d51 0.77 0.766 +3fe883126e978d4f 0.77 0.766 +3fe88b4395810625 0.77 0.767 +3fe88f5c28f5c28f 0.77 0.768 +401eb33333333333 7.68 7.675 +3fe88b4395810626 0.77 0.767 +3fe88b4395810624 0.77 0.767 +3fe89374bc6a7efa 0.77 0.768 +3fe8978d4fdf3b64 0.77 0.769 +401ebd70a3d70a3d 7.69 7.685 +3fe89374bc6a7efb 0.77 0.768 +3fe89374bc6a7ef9 0.77 0.768 +3fe89ba5e353f7cf 0.77 0.769 +3fe89fbe76c8b439 0.77 0.77 +401ec7ae147ae148 7.7 7.695 +3fe89ba5e353f7d0 0.77 0.769 +3fe89ba5e353f7ce 0.77 0.769 +3fe8a3d70a3d70a4 0.77 0.77 +3fe8a7ef9db22d0e 0.77 0.771 +401ed1eb851eb852 7.71 7.705 +3fe8a3d70a3d70a5 0.77 0.77 +3fe8a3d70a3d70a3 0.77 0.77 +3fe8ac083126e979 0.77 0.771 +3fe8b020c49ba5e3 0.77 0.772 +401edc28f5c28f5c 7.72 7.715 +3fe8ac083126e97a 0.77 0.771 +3fe8ac083126e978 0.77 0.771 +3fe8b4395810624e 0.77 0.772 +3fe8b851eb851eb8 0.77 0.773 +401ee66666666666 7.73 7.725 +3fe8b4395810624f 0.77 0.772 +3fe8b4395810624d 0.77 0.772 +3fe8bc6a7ef9db23 0.77 0.773 +3fe8c083126e978d 0.77 0.774 +401ef0a3d70a3d71 7.74 7.735 +3fe8bc6a7ef9db24 0.77 0.773 +3fe8bc6a7ef9db22 0.77 0.773 +3fe8c49ba5e353f8 0.77 0.774 +3fe8c8b439581062 0.77 0.775 +401efae147ae147b 7.75 7.745 +3fe8c49ba5e353f9 0.77 0.774 +3fe8c49ba5e353f7 0.77 0.774 +3fe8d0e560418937 0.78 0.776 +401f051eb851eb85 7.76 7.755 +3fe8ccccccccccce 0.78 0.775 +3fe8cccccccccccc 0.77 0.775 +3fe8d4fdf3b645a2 0.78 0.776 +3fe8d916872b020c 0.78 0.777 +401f0f5c28f5c28f 7.77 7.765 +3fe8d4fdf3b645a3 0.78 0.776 +3fe8d4fdf3b645a1 0.78 0.776 +3fe8dd2f1a9fbe77 0.78 0.777 +3fe8e147ae147ae1 0.78 0.778 +401f19999999999a 7.78 7.775 +3fe8dd2f1a9fbe78 0.78 0.777 +3fe8dd2f1a9fbe76 0.78 0.777 +3fe8e5604189374c 0.78 0.778 +3fe8e978d4fdf3b6 0.78 0.779 +401f23d70a3d70a4 7.79 7.785 +3fe8e5604189374d 0.78 0.778 +3fe8e5604189374b 0.78 0.778 +3fe8ed916872b021 0.78 0.779 +3fe8f1a9fbe76c8b 0.78 0.78 +401f2e147ae147ae 7.8 7.795 +3fe8ed916872b022 0.78 0.779 +3fe8ed916872b020 0.78 0.779 +3fe8f5c28f5c28f6 0.78 0.78 +3fe8f9db22d0e560 0.78 0.781 +401f3851eb851eb8 7.81 7.805 +3fe8f5c28f5c28f7 0.78 0.78 +3fe8f5c28f5c28f5 0.78 0.78 +3fe8fdf3b645a1cb 0.78 0.781 +3fe9020c49ba5e35 0.78 0.782 +401f428f5c28f5c3 7.82 7.815 +3fe8fdf3b645a1cc 0.78 0.781 +3fe8fdf3b645a1ca 0.78 0.781 +3fe90624dd2f1aa0 0.78 0.782 +3fe90a3d70a3d70a 0.78 0.783 +401f4ccccccccccd 7.83 7.825 +3fe90624dd2f1aa1 0.78 0.782 +3fe90624dd2f1a9f 0.78 0.782 +3fe90e5604189375 0.78 0.783 +3fe9126e978d4fdf 0.78 0.784 +401f570a3d70a3d7 7.84 7.835 +3fe90e5604189376 0.78 0.783 +3fe90e5604189374 0.78 0.783 +3fe916872b020c4a 0.78 0.784 +3fe91a9fbe76c8b4 0.78 0.785 +401f6147ae147ae1 7.85 7.845 +3fe916872b020c4b 0.78 0.784 +3fe916872b020c49 0.78 0.784 +3fe922d0e5604189 0.79 0.786 +401f6b851eb851ec 7.86 7.855 +3fe91eb851eb8520 0.79 0.785 +3fe91eb851eb851e 0.78 0.785 +3fe926e978d4fdf4 0.79 0.786 +3fe92b020c49ba5e 0.79 0.787 +401f75c28f5c28f6 7.87 7.865 +3fe926e978d4fdf5 0.79 0.786 +3fe926e978d4fdf3 0.79 0.786 +3fe92f1a9fbe76c9 0.79 0.787 +3fe9333333333333 0.79 0.788 +401f800000000000 7.88 7.875 +3fe92f1a9fbe76ca 0.79 0.787 +3fe92f1a9fbe76c8 0.79 0.787 +3fe9374bc6a7ef9e 0.79 0.788 +3fe93b645a1cac08 0.79 0.789 +401f8a3d70a3d70a 7.89 7.885 +3fe9374bc6a7ef9f 0.79 0.788 +3fe9374bc6a7ef9d 0.79 0.788 +3fe93f7ced916873 0.79 0.789 +3fe94395810624dd 0.79 0.79 +401f947ae147ae14 7.9 7.895 +3fe93f7ced916874 0.79 0.789 +3fe93f7ced916872 0.79 0.789 +3fe947ae147ae148 0.79 0.79 +3fe94bc6a7ef9db2 0.79 0.791 +401f9eb851eb851f 7.91 7.905 +3fe947ae147ae149 0.79 0.79 +3fe947ae147ae147 0.79 0.79 +3fe94fdf3b645a1d 0.79 0.791 +3fe953f7ced91687 0.79 0.792 +401fa8f5c28f5c29 7.92 7.915 +3fe94fdf3b645a1e 0.79 0.791 +3fe94fdf3b645a1c 0.79 0.791 +3fe95810624dd2f2 0.79 0.792 +3fe95c28f5c28f5c 0.79 0.793 +401fb33333333333 7.93 7.925 +3fe95810624dd2f3 0.79 0.792 +3fe95810624dd2f1 0.79 0.792 +3fe9604189374bc7 0.79 0.793 +3fe9645a1cac0831 0.79 0.794 +401fbd70a3d70a3d 7.94 7.935 +3fe9604189374bc8 0.79 0.793 +3fe9604189374bc6 0.79 0.793 +3fe96872b020c49c 0.79 0.794 +3fe96c8b43958106 0.79 0.795 +401fc7ae147ae148 7.95 7.945 +3fe96872b020c49d 0.79 0.794 +3fe96872b020c49b 0.79 0.794 +3fe974bc6a7ef9db 0.8 0.796 +401fd1eb851eb852 7.96 7.955 +3fe970a3d70a3d72 0.8 0.795 +3fe970a3d70a3d70 0.8 0.795 +3fe978d4fdf3b646 0.8 0.796 +3fe97ced916872b0 0.8 0.797 +401fdc28f5c28f5c 7.97 7.965 +3fe978d4fdf3b647 0.8 0.796 +3fe978d4fdf3b645 0.8 0.796 +3fe9810624dd2f1b 0.8 0.797 +3fe9851eb851eb85 0.8 0.798 +401fe66666666666 7.98 7.975 +3fe9810624dd2f1c 0.8 0.797 +3fe9810624dd2f1a 0.8 0.797 +3fe989374bc6a7f0 0.8 0.798 +3fe98d4fdf3b645a 0.8 0.799 +401ff0a3d70a3d71 7.99 7.985 +3fe989374bc6a7f1 0.8 0.798 +3fe989374bc6a7ef 0.8 0.798 +3fe9916872b020c5 0.8 0.799 +3fe995810624dd2f 0.8 0.8 +401ffae147ae147b 8.0 7.995 +3fe9916872b020c6 0.8 0.799 +3fe9916872b020c4 0.8 0.799 +3fe999999999999a 0.8 0.8 +3fe99db22d0e5604 0.8 0.801 +4020028f5c28f5c3 8.01 8.005 +3fe999999999999b 0.8 0.8 +3fe9999999999999 0.8 0.8 +3fe9a1cac083126f 0.8 0.801 +3fe9a5e353f7ced9 0.8 0.802 +402007ae147ae148 8.02 8.015 +3fe9a1cac0831270 0.8 0.801 +3fe9a1cac083126e 0.8 0.801 +3fe9a9fbe76c8b44 0.8 0.802 +3fe9ae147ae147ae 0.8 0.803 +40200ccccccccccd 8.03 8.025 +3fe9a9fbe76c8b45 0.8 0.802 +3fe9a9fbe76c8b43 0.8 0.802 +3fe9b22d0e560419 0.8 0.803 +3fe9b645a1cac083 0.8 0.804 +402011eb851eb852 8.04 8.035 +3fe9b22d0e56041a 0.8 0.803 +3fe9b22d0e560418 0.8 0.803 +3fe9ba5e353f7cee 0.8 0.804 +3fe9be76c8b43958 0.8 0.805 +4020170a3d70a3d7 8.05 8.045 +3fe9ba5e353f7cef 0.8 0.804 +3fe9ba5e353f7ced 0.8 0.804 +3fe9c6a7ef9db22d 0.81 0.806 +40201c28f5c28f5c 8.06 8.055 +3fe9c28f5c28f5c4 0.81 0.805 +3fe9c28f5c28f5c2 0.81 0.805 +3fe9cac083126e98 0.81 0.806 +3fe9ced916872b02 0.81 0.807 +40202147ae147ae1 8.07 8.065 +3fe9cac083126e99 0.81 0.806 +3fe9cac083126e97 0.81 0.806 +3fe9d2f1a9fbe76d 0.81 0.807 +3fe9d70a3d70a3d7 0.81 0.808 +4020266666666666 8.08 8.075 +3fe9d2f1a9fbe76e 0.81 0.807 +3fe9d2f1a9fbe76c 0.81 0.807 +3fe9db22d0e56042 0.81 0.808 +3fe9df3b645a1cac 0.81 0.809 +40202b851eb851ec 8.09 8.085 +3fe9db22d0e56043 0.81 0.808 +3fe9db22d0e56041 0.81 0.808 +3fe9e353f7ced917 0.81 0.809 +3fe9e76c8b439581 0.81 0.81 +402030a3d70a3d71 8.1 8.095 +3fe9e353f7ced918 0.81 0.809 +3fe9e353f7ced916 0.81 0.809 +3fe9eb851eb851ec 0.81 0.81 +3fe9ef9db22d0e56 0.81 0.811 +402035c28f5c28f6 8.11 8.105 +3fe9eb851eb851ed 0.81 0.81 +3fe9eb851eb851eb 0.81 0.81 +3fe9f3b645a1cac1 0.81 0.811 +3fe9f7ced916872b 0.81 0.812 +40203ae147ae147b 8.12 8.115 +3fe9f3b645a1cac2 0.81 0.811 +3fe9f3b645a1cac0 0.81 0.811 +3fe9fbe76c8b4396 0.81 0.812 +3fea000000000000 0.81 0.813 +4020400000000000 8.13 8.125 +3fe9fbe76c8b4397 0.81 0.812 +3fe9fbe76c8b4395 0.81 0.812 +3fea04189374bc6a 0.81 0.813 +3fea083126e978d5 0.81 0.814 +4020451eb851eb85 8.14 8.135 +3fea04189374bc6b 0.81 0.813 +3fea04189374bc69 0.81 0.813 +3fea0c49ba5e353f 0.81 0.814 +3fea10624dd2f1aa 0.81 0.815 +40204a3d70a3d70a 8.15 8.145 +3fea0c49ba5e3540 0.81 0.814 +3fea0c49ba5e353e 0.81 0.814 +3fea189374bc6a7f 0.82 0.816 +40204f5c28f5c28f 8.16 8.155 +3fea147ae147ae15 0.82 0.815 +3fea147ae147ae13 0.81 0.815 +3fea1cac083126e9 0.82 0.816 +3fea20c49ba5e354 0.82 0.817 +4020547ae147ae14 8.17 8.165 +3fea1cac083126ea 0.82 0.816 +3fea1cac083126e8 0.82 0.816 +3fea24dd2f1a9fbe 0.82 0.817 +3fea28f5c28f5c29 0.82 0.818 +402059999999999a 8.18 8.175 +3fea24dd2f1a9fbf 0.82 0.817 +3fea24dd2f1a9fbd 0.82 0.817 +3fea2d0e56041893 0.82 0.818 +3fea3126e978d4fe 0.82 0.819 +40205eb851eb851f 8.19 8.185 +3fea2d0e56041894 0.82 0.818 +3fea2d0e56041892 0.82 0.818 +3fea353f7ced9168 0.82 0.819 +3fea395810624dd3 0.82 0.82 +402063d70a3d70a4 8.2 8.195 +3fea353f7ced9169 0.82 0.819 +3fea353f7ced9167 0.82 0.819 +3fea3d70a3d70a3d 0.82 0.82 +3fea4189374bc6a8 0.82 0.821 +402068f5c28f5c29 8.21 8.205 +3fea3d70a3d70a3e 0.82 0.82 +3fea3d70a3d70a3c 0.82 0.82 +3fea45a1cac08312 0.82 0.821 +3fea49ba5e353f7d 0.82 0.822 +40206e147ae147ae 8.22 8.215 +3fea45a1cac08313 0.82 0.821 +3fea45a1cac08311 0.82 0.821 +3fea4dd2f1a9fbe7 0.82 0.822 +3fea51eb851eb852 0.82 0.823 +4020733333333333 8.23 8.225 +3fea4dd2f1a9fbe8 0.82 0.822 +3fea4dd2f1a9fbe6 0.82 0.822 +3fea5604189374bc 0.82 0.823 +3fea5a1cac083127 0.82 0.824 +40207851eb851eb8 8.24 8.235 +3fea5604189374bd 0.82 0.823 +3fea5604189374bb 0.82 0.823 +3fea5e353f7ced91 0.82 0.824 +3fea624dd2f1a9fc 0.82 0.825 +40207d70a3d70a3d 8.25 8.245 +3fea5e353f7ced92 0.82 0.824 +3fea5e353f7ced90 0.82 0.824 +3fea6a7ef9db22d1 0.83 0.826 +4020828f5c28f5c3 8.26 8.255 +3fea666666666667 0.83 0.825 +3fea666666666665 0.82 0.825 +3fea6e978d4fdf3b 0.83 0.826 +3fea72b020c49ba6 0.83 0.827 +402087ae147ae148 8.27 8.265 +3fea6e978d4fdf3c 0.83 0.826 +3fea6e978d4fdf3a 0.83 0.826 +3fea76c8b4395810 0.83 0.827 +3fea7ae147ae147b 0.83 0.828 +40208ccccccccccd 8.28 8.275 +3fea76c8b4395811 0.83 0.827 +3fea76c8b439580f 0.83 0.827 +3fea7ef9db22d0e5 0.83 0.828 +3fea83126e978d50 0.83 0.829 +402091eb851eb852 8.29 8.285 +3fea7ef9db22d0e6 0.83 0.828 +3fea7ef9db22d0e4 0.83 0.828 +3fea872b020c49ba 0.83 0.829 +3fea8b4395810625 0.83 0.83 +4020970a3d70a3d7 8.3 8.295 +3fea872b020c49bb 0.83 0.829 +3fea872b020c49b9 0.83 0.829 +3fea8f5c28f5c28f 0.83 0.83 +3fea9374bc6a7efa 0.83 0.831 +40209c28f5c28f5c 8.31 8.305 +3fea8f5c28f5c290 0.83 0.83 +3fea8f5c28f5c28e 0.83 0.83 +3fea978d4fdf3b64 0.83 0.831 +3fea9ba5e353f7cf 0.83 0.832 +4020a147ae147ae1 8.32 8.315 +3fea978d4fdf3b65 0.83 0.831 +3fea978d4fdf3b63 0.83 0.831 +3fea9fbe76c8b439 0.83 0.832 +3feaa3d70a3d70a4 0.83 0.833 +4020a66666666666 8.33 8.325 +3fea9fbe76c8b43a 0.83 0.832 +3fea9fbe76c8b438 0.83 0.832 +3feaa7ef9db22d0e 0.83 0.833 +3feaac083126e979 0.83 0.834 +4020ab851eb851ec 8.34 8.335 +3feaa7ef9db22d0f 0.83 0.833 +3feaa7ef9db22d0d 0.83 0.833 +3feab020c49ba5e3 0.83 0.834 +3feab4395810624e 0.83 0.835 +4020b0a3d70a3d71 8.35 8.345 +3feab020c49ba5e4 0.83 0.834 +3feab020c49ba5e2 0.83 0.834 +3feabc6a7ef9db23 0.84 0.836 +4020b5c28f5c28f6 8.36 8.355 +3feab851eb851eb9 0.84 0.835 +3feab851eb851eb7 0.83 0.835 +3feac083126e978d 0.84 0.836 +3feac49ba5e353f8 0.84 0.837 +4020bae147ae147b 8.37 8.365 +3feac083126e978e 0.84 0.836 +3feac083126e978c 0.84 0.836 +3feac8b439581062 0.84 0.837 +3feacccccccccccd 0.84 0.838 +4020c00000000000 8.38 8.375 +3feac8b439581063 0.84 0.837 +3feac8b439581061 0.84 0.837 +3fead0e560418937 0.84 0.838 +3fead4fdf3b645a2 0.84 0.839 +4020c51eb851eb85 8.39 8.385 +3fead0e560418938 0.84 0.838 +3fead0e560418936 0.84 0.838 +3fead916872b020c 0.84 0.839 +3feadd2f1a9fbe77 0.84 0.84 +4020ca3d70a3d70a 8.4 8.395 +3fead916872b020d 0.84 0.839 +3fead916872b020b 0.84 0.839 +3feae147ae147ae1 0.84 0.84 +3feae5604189374c 0.84 0.841 +4020cf5c28f5c28f 8.41 8.405 +3feae147ae147ae2 0.84 0.84 +3feae147ae147ae0 0.84 0.84 +3feae978d4fdf3b6 0.84 0.841 +3feaed916872b021 0.84 0.842 +4020d47ae147ae14 8.42 8.415 +3feae978d4fdf3b7 0.84 0.841 +3feae978d4fdf3b5 0.84 0.841 +3feaf1a9fbe76c8b 0.84 0.842 +3feaf5c28f5c28f6 0.84 0.843 +4020d9999999999a 8.43 8.425 +3feaf1a9fbe76c8c 0.84 0.842 +3feaf1a9fbe76c8a 0.84 0.842 +3feaf9db22d0e560 0.84 0.843 +3feafdf3b645a1cb 0.84 0.844 +4020deb851eb851f 8.44 8.435 +3feaf9db22d0e561 0.84 0.843 +3feaf9db22d0e55f 0.84 0.843 +3feb020c49ba5e35 0.84 0.844 +3feb0624dd2f1aa0 0.84 0.845 +4020e3d70a3d70a4 8.45 8.445 +3feb020c49ba5e36 0.84 0.844 +3feb020c49ba5e34 0.84 0.844 +3feb0e5604189375 0.85 0.846 +4020e8f5c28f5c29 8.46 8.455 +3feb0a3d70a3d70b 0.85 0.845 +3feb0a3d70a3d709 0.84 0.845 +3feb126e978d4fdf 0.85 0.846 +3feb16872b020c4a 0.85 0.847 +4020ee147ae147ae 8.47 8.465 +3feb126e978d4fe0 0.85 0.846 +3feb126e978d4fde 0.85 0.846 +3feb1a9fbe76c8b4 0.85 0.847 +3feb1eb851eb851f 0.85 0.848 +4020f33333333333 8.48 8.475 +3feb1a9fbe76c8b5 0.85 0.847 +3feb1a9fbe76c8b3 0.85 0.847 +3feb22d0e5604189 0.85 0.848 +3feb26e978d4fdf4 0.85 0.849 +4020f851eb851eb8 8.49 8.485 +3feb22d0e560418a 0.85 0.848 +3feb22d0e5604188 0.85 0.848 +3feb2b020c49ba5e 0.85 0.849 +3feb2f1a9fbe76c9 0.85 0.85 +4020fd70a3d70a3d 8.5 8.495 +3feb2b020c49ba5f 0.85 0.849 +3feb2b020c49ba5d 0.85 0.849 +3feb333333333333 0.85 0.85 +3feb374bc6a7ef9e 0.85 0.851 +4021028f5c28f5c3 8.51 8.505 +3feb333333333334 0.85 0.85 +3feb333333333332 0.85 0.85 +3feb3b645a1cac08 0.85 0.851 +3feb3f7ced916873 0.85 0.852 +402107ae147ae148 8.52 8.515 +3feb3b645a1cac09 0.85 0.851 +3feb3b645a1cac07 0.85 0.851 +3feb4395810624dd 0.85 0.852 +3feb47ae147ae148 0.85 0.853 +40210ccccccccccd 8.53 8.525 +3feb4395810624de 0.85 0.852 +3feb4395810624dc 0.85 0.852 +3feb4bc6a7ef9db2 0.85 0.853 +3feb4fdf3b645a1d 0.85 0.854 +402111eb851eb852 8.54 8.535 +3feb4bc6a7ef9db3 0.85 0.853 +3feb4bc6a7ef9db1 0.85 0.853 +3feb53f7ced91687 0.85 0.854 +3feb5810624dd2f2 0.85 0.855 +4021170a3d70a3d7 8.55 8.545 +3feb53f7ced91688 0.85 0.854 +3feb53f7ced91686 0.85 0.854 +3feb604189374bc7 0.86 0.856 +40211c28f5c28f5c 8.56 8.555 +3feb5c28f5c28f5d 0.86 0.855 +3feb5c28f5c28f5b 0.85 0.855 +3feb645a1cac0831 0.86 0.856 +3feb6872b020c49c 0.86 0.857 +40212147ae147ae1 8.57 8.565 +3feb645a1cac0832 0.86 0.856 +3feb645a1cac0830 0.86 0.856 +3feb6c8b43958106 0.86 0.857 +3feb70a3d70a3d71 0.86 0.858 +4021266666666666 8.58 8.575 +3feb6c8b43958107 0.86 0.857 +3feb6c8b43958105 0.86 0.857 +3feb74bc6a7ef9db 0.86 0.858 +3feb78d4fdf3b646 0.86 0.859 +40212b851eb851ec 8.59 8.585 +3feb74bc6a7ef9dc 0.86 0.858 +3feb74bc6a7ef9da 0.86 0.858 +3feb7ced916872b0 0.86 0.859 +3feb810624dd2f1b 0.86 0.86 +402130a3d70a3d71 8.6 8.595 +3feb7ced916872b1 0.86 0.859 +3feb7ced916872af 0.86 0.859 +3feb851eb851eb85 0.86 0.86 +3feb89374bc6a7f0 0.86 0.861 +402135c28f5c28f6 8.61 8.605 +3feb851eb851eb86 0.86 0.86 +3feb851eb851eb84 0.86 0.86 +3feb8d4fdf3b645a 0.86 0.861 +3feb916872b020c5 0.86 0.862 +40213ae147ae147b 8.62 8.615 +3feb8d4fdf3b645b 0.86 0.861 +3feb8d4fdf3b6459 0.86 0.861 +3feb95810624dd2f 0.86 0.862 +3feb99999999999a 0.86 0.863 +4021400000000000 8.63 8.625 +3feb95810624dd30 0.86 0.862 +3feb95810624dd2e 0.86 0.862 +3feb9db22d0e5604 0.86 0.863 +3feba1cac083126f 0.86 0.864 +4021451eb851eb85 8.64 8.635 +3feb9db22d0e5605 0.86 0.863 +3feb9db22d0e5603 0.86 0.863 +3feba5e353f7ced9 0.86 0.864 +3feba9fbe76c8b44 0.86 0.865 +40214a3d70a3d70a 8.65 8.645 +3feba5e353f7ceda 0.86 0.864 +3feba5e353f7ced8 0.86 0.864 +3febb22d0e560419 0.87 0.866 +40214f5c28f5c28f 8.66 8.655 +3febae147ae147af 0.87 0.865 +3febae147ae147ad 0.86 0.865 +3febb645a1cac083 0.87 0.866 +3febba5e353f7cee 0.87 0.867 +4021547ae147ae14 8.67 8.665 +3febb645a1cac084 0.87 0.866 +3febb645a1cac082 0.87 0.866 +3febbe76c8b43958 0.87 0.867 +3febc28f5c28f5c3 0.87 0.868 +402159999999999a 8.68 8.675 +3febbe76c8b43959 0.87 0.867 +3febbe76c8b43957 0.87 0.867 +3febc6a7ef9db22d 0.87 0.868 +3febcac083126e98 0.87 0.869 +40215eb851eb851f 8.69 8.685 +3febc6a7ef9db22e 0.87 0.868 +3febc6a7ef9db22c 0.87 0.868 +3febced916872b02 0.87 0.869 +3febd2f1a9fbe76d 0.87 0.87 +402163d70a3d70a4 8.7 8.695 +3febced916872b03 0.87 0.869 +3febced916872b01 0.87 0.869 +3febd70a3d70a3d7 0.87 0.87 +3febdb22d0e56042 0.87 0.871 +402168f5c28f5c29 8.71 8.705 +3febd70a3d70a3d8 0.87 0.87 +3febd70a3d70a3d6 0.87 0.87 +3febdf3b645a1cac 0.87 0.871 +3febe353f7ced917 0.87 0.872 +40216e147ae147ae 8.72 8.715 +3febdf3b645a1cad 0.87 0.871 +3febdf3b645a1cab 0.87 0.871 +3febe76c8b439581 0.87 0.872 +3febeb851eb851ec 0.87 0.873 +4021733333333333 8.73 8.725 +3febe76c8b439582 0.87 0.872 +3febe76c8b439580 0.87 0.872 +3febef9db22d0e56 0.87 0.873 +3febf3b645a1cac1 0.87 0.874 +40217851eb851eb8 8.74 8.735 +3febef9db22d0e57 0.87 0.873 +3febef9db22d0e55 0.87 0.873 +3febf7ced916872b 0.87 0.874 +3febfbe76c8b4396 0.87 0.875 +40217d70a3d70a3d 8.75 8.745 +3febf7ced916872c 0.87 0.874 +3febf7ced916872a 0.87 0.874 +3fec04189374bc6a 0.88 0.876 +4021828f5c28f5c3 8.76 8.755 +3fec000000000001 0.88 0.875 +3febffffffffffff 0.87 0.875 +3fec083126e978d5 0.88 0.876 +3fec0c49ba5e353f 0.88 0.877 +402187ae147ae148 8.77 8.765 +3fec083126e978d6 0.88 0.876 +3fec083126e978d4 0.88 0.876 +3fec10624dd2f1aa 0.88 0.877 +3fec147ae147ae14 0.88 0.878 +40218ccccccccccd 8.78 8.775 +3fec10624dd2f1ab 0.88 0.877 +3fec10624dd2f1a9 0.88 0.877 +3fec189374bc6a7f 0.88 0.878 +3fec1cac083126e9 0.88 0.879 +402191eb851eb852 8.79 8.785 +3fec189374bc6a80 0.88 0.878 +3fec189374bc6a7e 0.88 0.878 +3fec20c49ba5e354 0.88 0.879 +3fec24dd2f1a9fbe 0.88 0.88 +4021970a3d70a3d7 8.8 8.795 +3fec20c49ba5e355 0.88 0.879 +3fec20c49ba5e353 0.88 0.879 +3fec28f5c28f5c29 0.88 0.88 +3fec2d0e56041893 0.88 0.881 +40219c28f5c28f5c 8.81 8.805 +3fec28f5c28f5c2a 0.88 0.88 +3fec28f5c28f5c28 0.88 0.88 +3fec3126e978d4fe 0.88 0.881 +3fec353f7ced9168 0.88 0.882 +4021a147ae147ae1 8.82 8.815 +3fec3126e978d4ff 0.88 0.881 +3fec3126e978d4fd 0.88 0.881 +3fec395810624dd3 0.88 0.882 +3fec3d70a3d70a3d 0.88 0.883 +4021a66666666666 8.83 8.825 +3fec395810624dd4 0.88 0.882 +3fec395810624dd2 0.88 0.882 +3fec4189374bc6a8 0.88 0.883 +3fec45a1cac08312 0.88 0.884 +4021ab851eb851ec 8.84 8.835 +3fec4189374bc6a9 0.88 0.883 +3fec4189374bc6a7 0.88 0.883 +3fec49ba5e353f7d 0.88 0.884 +3fec4dd2f1a9fbe7 0.88 0.885 +4021b0a3d70a3d71 8.85 8.845 +3fec49ba5e353f7e 0.88 0.884 +3fec49ba5e353f7c 0.88 0.884 +3fec5604189374bc 0.89 0.886 +4021b5c28f5c28f6 8.86 8.855 +3fec51eb851eb853 0.89 0.885 +3fec51eb851eb851 0.88 0.885 +3fec5a1cac083127 0.89 0.886 +3fec5e353f7ced91 0.89 0.887 +4021bae147ae147b 8.87 8.865 +3fec5a1cac083128 0.89 0.886 +3fec5a1cac083126 0.89 0.886 +3fec624dd2f1a9fc 0.89 0.887 +3fec666666666666 0.89 0.888 +4021c00000000000 8.88 8.875 +3fec624dd2f1a9fd 0.89 0.887 +3fec624dd2f1a9fb 0.89 0.887 +3fec6a7ef9db22d1 0.89 0.888 +3fec6e978d4fdf3b 0.89 0.889 +4021c51eb851eb85 8.89 8.885 +3fec6a7ef9db22d2 0.89 0.888 +3fec6a7ef9db22d0 0.89 0.888 +3fec72b020c49ba6 0.89 0.889 +3fec76c8b4395810 0.89 0.89 +4021ca3d70a3d70a 8.9 8.895 +3fec72b020c49ba7 0.89 0.889 +3fec72b020c49ba5 0.89 0.889 +3fec7ae147ae147b 0.89 0.89 +3fec7ef9db22d0e5 0.89 0.891 +4021cf5c28f5c28f 8.91 8.905 +3fec7ae147ae147c 0.89 0.89 +3fec7ae147ae147a 0.89 0.89 +3fec83126e978d50 0.89 0.891 +3fec872b020c49ba 0.89 0.892 +4021d47ae147ae14 8.92 8.915 +3fec83126e978d51 0.89 0.891 +3fec83126e978d4f 0.89 0.891 +3fec8b4395810625 0.89 0.892 +3fec8f5c28f5c28f 0.89 0.893 +4021d9999999999a 8.93 8.925 +3fec8b4395810626 0.89 0.892 +3fec8b4395810624 0.89 0.892 +3fec9374bc6a7efa 0.89 0.893 +3fec978d4fdf3b64 0.89 0.894 +4021deb851eb851f 8.94 8.935 +3fec9374bc6a7efb 0.89 0.893 +3fec9374bc6a7ef9 0.89 0.893 +3fec9ba5e353f7cf 0.89 0.894 +3fec9fbe76c8b439 0.89 0.895 +4021e3d70a3d70a4 8.95 8.945 +3fec9ba5e353f7d0 0.89 0.894 +3fec9ba5e353f7ce 0.89 0.894 +3feca7ef9db22d0e 0.9 0.896 +4021e8f5c28f5c29 8.96 8.955 +3feca3d70a3d70a5 0.9 0.895 +3feca3d70a3d70a3 0.89 0.895 +3fecac083126e979 0.9 0.896 +3fecb020c49ba5e3 0.9 0.897 +4021ee147ae147ae 8.97 8.965 +3fecac083126e97a 0.9 0.896 +3fecac083126e978 0.9 0.896 +3fecb4395810624e 0.9 0.897 +3fecb851eb851eb8 0.9 0.898 +4021f33333333333 8.98 8.975 +3fecb4395810624f 0.9 0.897 +3fecb4395810624d 0.9 0.897 +3fecbc6a7ef9db23 0.9 0.898 +3fecc083126e978d 0.9 0.899 +4021f851eb851eb8 8.99 8.985 +3fecbc6a7ef9db24 0.9 0.898 +3fecbc6a7ef9db22 0.9 0.898 +3fecc49ba5e353f8 0.9 0.899 +3fecc8b439581062 0.9 0.9 +4021fd70a3d70a3d 9.0 8.995 +3fecc49ba5e353f9 0.9 0.899 +3fecc49ba5e353f7 0.9 0.899 +3feccccccccccccd 0.9 0.9 +3fecd0e560418937 0.9 0.901 +4022028f5c28f5c3 9.01 9.005 +3fecccccccccccce 0.9 0.9 +3feccccccccccccc 0.9 0.9 +3fecd4fdf3b645a2 0.9 0.901 +3fecd916872b020c 0.9 0.902 +402207ae147ae148 9.02 9.015 +3fecd4fdf3b645a3 0.9 0.901 +3fecd4fdf3b645a1 0.9 0.901 +3fecdd2f1a9fbe77 0.9 0.902 +3fece147ae147ae1 0.9 0.903 +40220ccccccccccd 9.03 9.025 +3fecdd2f1a9fbe78 0.9 0.902 +3fecdd2f1a9fbe76 0.9 0.902 +3fece5604189374c 0.9 0.903 +3fece978d4fdf3b6 0.9 0.904 +402211eb851eb852 9.04 9.035 +3fece5604189374d 0.9 0.903 +3fece5604189374b 0.9 0.903 +3feced916872b021 0.9 0.904 +3fecf1a9fbe76c8b 0.9 0.905 +4022170a3d70a3d7 9.05 9.045 +3feced916872b022 0.9 0.904 +3feced916872b020 0.9 0.904 +3fecf9db22d0e560 0.91 0.906 +40221c28f5c28f5c 9.06 9.055 +3fecf5c28f5c28f7 0.91 0.905 +3fecf5c28f5c28f5 0.9 0.905 +3fecfdf3b645a1cb 0.91 0.906 +3fed020c49ba5e35 0.91 0.907 +40222147ae147ae1 9.07 9.065 +3fecfdf3b645a1cc 0.91 0.906 +3fecfdf3b645a1ca 0.91 0.906 +3fed0624dd2f1aa0 0.91 0.907 +3fed0a3d70a3d70a 0.91 0.908 +4022266666666666 9.08 9.075 +3fed0624dd2f1aa1 0.91 0.907 +3fed0624dd2f1a9f 0.91 0.907 +3fed0e5604189375 0.91 0.908 +3fed126e978d4fdf 0.91 0.909 +40222b851eb851ec 9.09 9.085 +3fed0e5604189376 0.91 0.908 +3fed0e5604189374 0.91 0.908 +3fed16872b020c4a 0.91 0.909 +3fed1a9fbe76c8b4 0.91 0.91 +402230a3d70a3d71 9.1 9.095 +3fed16872b020c4b 0.91 0.909 +3fed16872b020c49 0.91 0.909 +3fed1eb851eb851f 0.91 0.91 +3fed22d0e5604189 0.91 0.911 +402235c28f5c28f6 9.11 9.105 +3fed1eb851eb8520 0.91 0.91 +3fed1eb851eb851e 0.91 0.91 +3fed26e978d4fdf4 0.91 0.911 +3fed2b020c49ba5e 0.91 0.912 +40223ae147ae147b 9.12 9.115 +3fed26e978d4fdf5 0.91 0.911 +3fed26e978d4fdf3 0.91 0.911 +3fed2f1a9fbe76c9 0.91 0.912 +3fed333333333333 0.91 0.913 +4022400000000000 9.13 9.125 +3fed2f1a9fbe76ca 0.91 0.912 +3fed2f1a9fbe76c8 0.91 0.912 +3fed374bc6a7ef9e 0.91 0.913 +3fed3b645a1cac08 0.91 0.914 +4022451eb851eb85 9.14 9.135 +3fed374bc6a7ef9f 0.91 0.913 +3fed374bc6a7ef9d 0.91 0.913 +3fed3f7ced916873 0.91 0.914 +3fed4395810624dd 0.91 0.915 +40224a3d70a3d70a 9.15 9.145 +3fed3f7ced916874 0.91 0.914 +3fed3f7ced916872 0.91 0.914 +3fed4bc6a7ef9db2 0.92 0.916 +40224f5c28f5c28f 9.16 9.155 +3fed47ae147ae149 0.92 0.915 +3fed47ae147ae147 0.91 0.915 +3fed4fdf3b645a1d 0.92 0.916 +3fed53f7ced91687 0.92 0.917 +4022547ae147ae14 9.17 9.165 +3fed4fdf3b645a1e 0.92 0.916 +3fed4fdf3b645a1c 0.92 0.916 +3fed5810624dd2f2 0.92 0.917 +3fed5c28f5c28f5c 0.92 0.918 +402259999999999a 9.18 9.175 +3fed5810624dd2f3 0.92 0.917 +3fed5810624dd2f1 0.92 0.917 +3fed604189374bc7 0.92 0.918 +3fed645a1cac0831 0.92 0.919 +40225eb851eb851f 9.19 9.185 +3fed604189374bc8 0.92 0.918 +3fed604189374bc6 0.92 0.918 +3fed6872b020c49c 0.92 0.919 +3fed6c8b43958106 0.92 0.92 +402263d70a3d70a4 9.2 9.195 +3fed6872b020c49d 0.92 0.919 +3fed6872b020c49b 0.92 0.919 +3fed70a3d70a3d71 0.92 0.92 +3fed74bc6a7ef9db 0.92 0.921 +402268f5c28f5c29 9.21 9.205 +3fed70a3d70a3d72 0.92 0.92 +3fed70a3d70a3d70 0.92 0.92 +3fed78d4fdf3b646 0.92 0.921 +3fed7ced916872b0 0.92 0.922 +40226e147ae147ae 9.22 9.215 +3fed78d4fdf3b647 0.92 0.921 +3fed78d4fdf3b645 0.92 0.921 +3fed810624dd2f1b 0.92 0.922 +3fed851eb851eb85 0.92 0.923 +4022733333333333 9.23 9.225 +3fed810624dd2f1c 0.92 0.922 +3fed810624dd2f1a 0.92 0.922 +3fed89374bc6a7f0 0.92 0.923 +3fed8d4fdf3b645a 0.92 0.924 +40227851eb851eb8 9.24 9.235 +3fed89374bc6a7f1 0.92 0.923 +3fed89374bc6a7ef 0.92 0.923 +3fed916872b020c5 0.92 0.924 +3fed95810624dd2f 0.92 0.925 +40227d70a3d70a3d 9.25 9.245 +3fed916872b020c6 0.92 0.924 +3fed916872b020c4 0.92 0.924 +3fed9db22d0e5604 0.93 0.926 +4022828f5c28f5c3 9.26 9.255 +3fed99999999999b 0.93 0.925 +3fed999999999999 0.93 0.925 +3feda1cac083126f 0.93 0.926 +3feda5e353f7ced9 0.93 0.927 +402287ae147ae148 9.27 9.265 +3feda1cac0831270 0.93 0.926 +3feda1cac083126e 0.93 0.926 +3feda9fbe76c8b44 0.93 0.927 +3fedae147ae147ae 0.93 0.928 +40228ccccccccccd 9.28 9.275 +3feda9fbe76c8b45 0.93 0.927 +3feda9fbe76c8b43 0.93 0.927 +3fedb22d0e560419 0.93 0.928 +3fedb645a1cac083 0.93 0.929 +402291eb851eb852 9.29 9.285 +3fedb22d0e56041a 0.93 0.928 +3fedb22d0e560418 0.93 0.928 +3fedba5e353f7cee 0.93 0.929 +3fedbe76c8b43958 0.93 0.93 +4022970a3d70a3d7 9.3 9.295 +3fedba5e353f7cef 0.93 0.929 +3fedba5e353f7ced 0.93 0.929 +3fedc28f5c28f5c3 0.93 0.93 +3fedc6a7ef9db22d 0.93 0.931 +40229c28f5c28f5c 9.31 9.305 +3fedc28f5c28f5c4 0.93 0.93 +3fedc28f5c28f5c2 0.93 0.93 +3fedcac083126e98 0.93 0.931 +3fedced916872b02 0.93 0.932 +4022a147ae147ae1 9.32 9.315 +3fedcac083126e99 0.93 0.931 +3fedcac083126e97 0.93 0.931 +3fedd2f1a9fbe76d 0.93 0.932 +3fedd70a3d70a3d7 0.93 0.933 +4022a66666666666 9.33 9.325 +3fedd2f1a9fbe76e 0.93 0.932 +3fedd2f1a9fbe76c 0.93 0.932 +3feddb22d0e56042 0.93 0.933 +3feddf3b645a1cac 0.93 0.934 +4022ab851eb851ec 9.34 9.335 +3feddb22d0e56043 0.93 0.933 +3feddb22d0e56041 0.93 0.933 +3fede353f7ced917 0.93 0.934 +3fede76c8b439581 0.93 0.935 +4022b0a3d70a3d71 9.35 9.345 +3fede353f7ced918 0.93 0.934 +3fede353f7ced916 0.93 0.934 +3fedef9db22d0e56 0.94 0.936 +4022b5c28f5c28f6 9.36 9.355 +3fedeb851eb851ed 0.94 0.935 +3fedeb851eb851eb 0.94 0.935 +3fedf3b645a1cac1 0.94 0.936 +3fedf7ced916872b 0.94 0.937 +4022bae147ae147b 9.37 9.365 +3fedf3b645a1cac2 0.94 0.936 +3fedf3b645a1cac0 0.94 0.936 +3fedfbe76c8b4396 0.94 0.937 +3fee000000000000 0.94 0.938 +4022c00000000000 9.38 9.375 +3fedfbe76c8b4397 0.94 0.937 +3fedfbe76c8b4395 0.94 0.937 +3fee04189374bc6a 0.94 0.938 +3fee083126e978d5 0.94 0.939 +4022c51eb851eb85 9.39 9.385 +3fee04189374bc6b 0.94 0.938 +3fee04189374bc69 0.94 0.938 +3fee0c49ba5e353f 0.94 0.939 +3fee10624dd2f1aa 0.94 0.94 +4022ca3d70a3d70a 9.4 9.395 +3fee0c49ba5e3540 0.94 0.939 +3fee0c49ba5e353e 0.94 0.939 +3fee147ae147ae14 0.94 0.94 +3fee189374bc6a7f 0.94 0.941 +4022cf5c28f5c28f 9.41 9.405 +3fee147ae147ae15 0.94 0.94 +3fee147ae147ae13 0.94 0.94 +3fee1cac083126e9 0.94 0.941 +3fee20c49ba5e354 0.94 0.942 +4022d47ae147ae14 9.42 9.415 +3fee1cac083126ea 0.94 0.941 +3fee1cac083126e8 0.94 0.941 +3fee24dd2f1a9fbe 0.94 0.942 +3fee28f5c28f5c29 0.94 0.943 +4022d9999999999a 9.43 9.425 +3fee24dd2f1a9fbf 0.94 0.942 +3fee24dd2f1a9fbd 0.94 0.942 +3fee2d0e56041893 0.94 0.943 +3fee3126e978d4fe 0.94 0.944 +4022deb851eb851f 9.44 9.435 +3fee2d0e56041894 0.94 0.943 +3fee2d0e56041892 0.94 0.943 +3fee353f7ced9168 0.94 0.944 +3fee395810624dd3 0.94 0.945 +4022e3d70a3d70a4 9.45 9.445 +3fee353f7ced9169 0.94 0.944 +3fee353f7ced9167 0.94 0.944 +3fee4189374bc6a8 0.95 0.946 +4022e8f5c28f5c29 9.46 9.455 +3fee3d70a3d70a3e 0.95 0.945 +3fee3d70a3d70a3c 0.94 0.945 +3fee45a1cac08312 0.95 0.946 +3fee49ba5e353f7d 0.95 0.947 +4022ee147ae147ae 9.47 9.465 +3fee45a1cac08313 0.95 0.946 +3fee45a1cac08311 0.95 0.946 +3fee4dd2f1a9fbe7 0.95 0.947 +3fee51eb851eb852 0.95 0.948 +4022f33333333333 9.48 9.475 +3fee4dd2f1a9fbe8 0.95 0.947 +3fee4dd2f1a9fbe6 0.95 0.947 +3fee5604189374bc 0.95 0.948 +3fee5a1cac083127 0.95 0.949 +4022f851eb851eb8 9.49 9.485 +3fee5604189374bd 0.95 0.948 +3fee5604189374bb 0.95 0.948 +3fee5e353f7ced91 0.95 0.949 +3fee624dd2f1a9fc 0.95 0.95 +4022fd70a3d70a3d 9.5 9.495 +3fee5e353f7ced92 0.95 0.949 +3fee5e353f7ced90 0.95 0.949 +3fee666666666666 0.95 0.95 +3fee6a7ef9db22d1 0.95 0.951 +4023028f5c28f5c3 9.51 9.505 +3fee666666666667 0.95 0.95 +3fee666666666665 0.95 0.95 +3fee6e978d4fdf3b 0.95 0.951 +3fee72b020c49ba6 0.95 0.952 +402307ae147ae148 9.52 9.515 +3fee6e978d4fdf3c 0.95 0.951 +3fee6e978d4fdf3a 0.95 0.951 +3fee76c8b4395810 0.95 0.952 +3fee7ae147ae147b 0.95 0.953 +40230ccccccccccd 9.53 9.525 +3fee76c8b4395811 0.95 0.952 +3fee76c8b439580f 0.95 0.952 +3fee7ef9db22d0e5 0.95 0.953 +3fee83126e978d50 0.95 0.954 +402311eb851eb852 9.54 9.535 +3fee7ef9db22d0e6 0.95 0.953 +3fee7ef9db22d0e4 0.95 0.953 +3fee872b020c49ba 0.95 0.954 +3fee8b4395810625 0.95 0.955 +4023170a3d70a3d7 9.55 9.545 +3fee872b020c49bb 0.95 0.954 +3fee872b020c49b9 0.95 0.954 +3fee9374bc6a7efa 0.96 0.956 +40231c28f5c28f5c 9.56 9.555 +3fee8f5c28f5c290 0.96 0.955 +3fee8f5c28f5c28e 0.95 0.955 +3fee978d4fdf3b64 0.96 0.956 +3fee9ba5e353f7cf 0.96 0.957 +40232147ae147ae1 9.57 9.565 +3fee978d4fdf3b65 0.96 0.956 +3fee978d4fdf3b63 0.96 0.956 +3fee9fbe76c8b439 0.96 0.957 +3feea3d70a3d70a4 0.96 0.958 +4023266666666666 9.58 9.575 +3fee9fbe76c8b43a 0.96 0.957 +3fee9fbe76c8b438 0.96 0.957 +3feea7ef9db22d0e 0.96 0.958 +3feeac083126e979 0.96 0.959 +40232b851eb851ec 9.59 9.585 +3feea7ef9db22d0f 0.96 0.958 +3feea7ef9db22d0d 0.96 0.958 +3feeb020c49ba5e3 0.96 0.959 +3feeb4395810624e 0.96 0.96 +402330a3d70a3d71 9.6 9.595 +3feeb020c49ba5e4 0.96 0.959 +3feeb020c49ba5e2 0.96 0.959 +3feeb851eb851eb8 0.96 0.96 +3feebc6a7ef9db23 0.96 0.961 +402335c28f5c28f6 9.61 9.605 +3feeb851eb851eb9 0.96 0.96 +3feeb851eb851eb7 0.96 0.96 +3feec083126e978d 0.96 0.961 +3feec49ba5e353f8 0.96 0.962 +40233ae147ae147b 9.62 9.615 +3feec083126e978e 0.96 0.961 +3feec083126e978c 0.96 0.961 +3feec8b439581062 0.96 0.962 +3feecccccccccccd 0.96 0.963 +4023400000000000 9.63 9.625 +3feec8b439581063 0.96 0.962 +3feec8b439581061 0.96 0.962 +3feed0e560418937 0.96 0.963 +3feed4fdf3b645a2 0.96 0.964 +4023451eb851eb85 9.64 9.635 +3feed0e560418938 0.96 0.963 +3feed0e560418936 0.96 0.963 +3feed916872b020c 0.96 0.964 +3feedd2f1a9fbe77 0.96 0.965 +40234a3d70a3d70a 9.65 9.645 +3feed916872b020d 0.96 0.964 +3feed916872b020b 0.96 0.964 +3feee5604189374c 0.97 0.966 +40234f5c28f5c28f 9.66 9.655 +3feee147ae147ae2 0.97 0.965 +3feee147ae147ae0 0.96 0.965 +3feee978d4fdf3b6 0.97 0.966 +3feeed916872b021 0.97 0.967 +4023547ae147ae14 9.67 9.665 +3feee978d4fdf3b7 0.97 0.966 +3feee978d4fdf3b5 0.97 0.966 +3feef1a9fbe76c8b 0.97 0.967 +3feef5c28f5c28f6 0.97 0.968 +402359999999999a 9.68 9.675 +3feef1a9fbe76c8c 0.97 0.967 +3feef1a9fbe76c8a 0.97 0.967 +3feef9db22d0e560 0.97 0.968 +3feefdf3b645a1cb 0.97 0.969 +40235eb851eb851f 9.69 9.685 +3feef9db22d0e561 0.97 0.968 +3feef9db22d0e55f 0.97 0.968 +3fef020c49ba5e35 0.97 0.969 +3fef0624dd2f1aa0 0.97 0.97 +402363d70a3d70a4 9.7 9.695 +3fef020c49ba5e36 0.97 0.969 +3fef020c49ba5e34 0.97 0.969 +3fef0a3d70a3d70a 0.97 0.97 +3fef0e5604189375 0.97 0.971 +402368f5c28f5c29 9.71 9.705 +3fef0a3d70a3d70b 0.97 0.97 +3fef0a3d70a3d709 0.97 0.97 +3fef126e978d4fdf 0.97 0.971 +3fef16872b020c4a 0.97 0.972 +40236e147ae147ae 9.72 9.715 +3fef126e978d4fe0 0.97 0.971 +3fef126e978d4fde 0.97 0.971 +3fef1a9fbe76c8b4 0.97 0.972 +3fef1eb851eb851f 0.97 0.973 +4023733333333333 9.73 9.725 +3fef1a9fbe76c8b5 0.97 0.972 +3fef1a9fbe76c8b3 0.97 0.972 +3fef22d0e5604189 0.97 0.973 +3fef26e978d4fdf4 0.97 0.974 +40237851eb851eb8 9.74 9.735 +3fef22d0e560418a 0.97 0.973 +3fef22d0e5604188 0.97 0.973 +3fef2b020c49ba5e 0.97 0.974 +3fef2f1a9fbe76c9 0.97 0.975 +40237d70a3d70a3d 9.75 9.745 +3fef2b020c49ba5f 0.97 0.974 +3fef2b020c49ba5d 0.97 0.974 +3fef374bc6a7ef9e 0.98 0.976 +4023828f5c28f5c3 9.76 9.755 +3fef333333333334 0.98 0.975 +3fef333333333332 0.97 0.975 +3fef3b645a1cac08 0.98 0.976 +3fef3f7ced916873 0.98 0.977 +402387ae147ae148 9.77 9.765 +3fef3b645a1cac09 0.98 0.976 +3fef3b645a1cac07 0.98 0.976 +3fef4395810624dd 0.98 0.977 +3fef47ae147ae148 0.98 0.978 +40238ccccccccccd 9.78 9.775 +3fef4395810624de 0.98 0.977 +3fef4395810624dc 0.98 0.977 +3fef4bc6a7ef9db2 0.98 0.978 +3fef4fdf3b645a1d 0.98 0.979 +402391eb851eb852 9.79 9.785 +3fef4bc6a7ef9db3 0.98 0.978 +3fef4bc6a7ef9db1 0.98 0.978 +3fef53f7ced91687 0.98 0.979 +3fef5810624dd2f2 0.98 0.98 +4023970a3d70a3d7 9.8 9.795 +3fef53f7ced91688 0.98 0.979 +3fef53f7ced91686 0.98 0.979 +3fef5c28f5c28f5c 0.98 0.98 +3fef604189374bc7 0.98 0.981 +40239c28f5c28f5c 9.81 9.805 +3fef5c28f5c28f5d 0.98 0.98 +3fef5c28f5c28f5b 0.98 0.98 +3fef645a1cac0831 0.98 0.981 +3fef6872b020c49c 0.98 0.982 +4023a147ae147ae1 9.82 9.815 +3fef645a1cac0832 0.98 0.981 +3fef645a1cac0830 0.98 0.981 +3fef6c8b43958106 0.98 0.982 +3fef70a3d70a3d71 0.98 0.983 +4023a66666666666 9.83 9.825 +3fef6c8b43958107 0.98 0.982 +3fef6c8b43958105 0.98 0.982 +3fef74bc6a7ef9db 0.98 0.983 +3fef78d4fdf3b646 0.98 0.984 +4023ab851eb851ec 9.84 9.835 +3fef74bc6a7ef9dc 0.98 0.983 +3fef74bc6a7ef9da 0.98 0.983 +3fef7ced916872b0 0.98 0.984 +3fef810624dd2f1b 0.98 0.985 +4023b0a3d70a3d71 9.85 9.845 +3fef7ced916872b1 0.98 0.984 +3fef7ced916872af 0.98 0.984 +3fef89374bc6a7f0 0.99 0.986 +4023b5c28f5c28f6 9.86 9.855 +3fef851eb851eb86 0.99 0.985 +3fef851eb851eb84 0.98 0.985 +3fef8d4fdf3b645a 0.99 0.986 +3fef916872b020c5 0.99 0.987 +4023bae147ae147b 9.87 9.865 +3fef8d4fdf3b645b 0.99 0.986 +3fef8d4fdf3b6459 0.99 0.986 +3fef95810624dd2f 0.99 0.987 +3fef99999999999a 0.99 0.988 +4023c00000000000 9.88 9.875 +3fef95810624dd30 0.99 0.987 +3fef95810624dd2e 0.99 0.987 +3fef9db22d0e5604 0.99 0.988 +3fefa1cac083126f 0.99 0.989 +4023c51eb851eb85 9.89 9.885 +3fef9db22d0e5605 0.99 0.988 +3fef9db22d0e5603 0.99 0.988 +3fefa5e353f7ced9 0.99 0.989 +3fefa9fbe76c8b44 0.99 0.99 +4023ca3d70a3d70a 9.9 9.895 +3fefa5e353f7ceda 0.99 0.989 +3fefa5e353f7ced8 0.99 0.989 +3fefae147ae147ae 0.99 0.99 +3fefb22d0e560419 0.99 0.991 +4023cf5c28f5c28f 9.91 9.905 +3fefae147ae147af 0.99 0.99 +3fefae147ae147ad 0.99 0.99 +3fefb645a1cac083 0.99 0.991 +3fefba5e353f7cee 0.99 0.992 +4023d47ae147ae14 9.92 9.915 +3fefb645a1cac084 0.99 0.991 +3fefb645a1cac082 0.99 0.991 +3fefbe76c8b43958 0.99 0.992 +3fefc28f5c28f5c3 0.99 0.993 +4023d9999999999a 9.93 9.925 +3fefbe76c8b43959 0.99 0.992 +3fefbe76c8b43957 0.99 0.992 +3fefc6a7ef9db22d 0.99 0.993 +3fefcac083126e98 0.99 0.994 +4023deb851eb851f 9.94 9.935 +3fefc6a7ef9db22e 0.99 0.993 +3fefc6a7ef9db22c 0.99 0.993 +3fefced916872b02 0.99 0.994 +3fefd2f1a9fbe76d 0.99 0.995 +4023e3d70a3d70a4 9.95 9.945 +3fefced916872b03 0.99 0.994 +3fefced916872b01 0.99 0.994 +3fefdb22d0e56042 1.0 0.996 +4023e8f5c28f5c29 9.96 9.955 +3fefd70a3d70a3d8 1.0 0.995 +3fefd70a3d70a3d6 0.99 0.995 +3fefdf3b645a1cac 1.0 0.996 +3fefe353f7ced917 1.0 0.997 +4023ee147ae147ae 9.97 9.965 +3fefdf3b645a1cad 1.0 0.996 +3fefdf3b645a1cab 1.0 0.996 +3fefe76c8b439581 1.0 0.997 +3fefeb851eb851ec 1.0 0.998 +4023f33333333333 9.98 9.975 +3fefe76c8b439582 1.0 0.997 +3fefe76c8b439580 1.0 0.997 +3fefef9db22d0e56 1.0 0.998 +3feff3b645a1cac1 1.0 0.999 +4023f851eb851eb8 9.99 9.985 +3fefef9db22d0e57 1.0 0.998 +3fefef9db22d0e55 1.0 0.998 +3feff7ced916872b 1.0 0.999 +4023fd70a3d70a3d 10.0 9.995 +3feff7ced916872c 1.0 0.999 +3feff7ced916872a 1.0 0.999 +3ff0020c49ba5e35 1.0 1.001 +4024028f5c28f5c3 10.01 10.005 +3ff0000000000001 1.0 1.0 +3fefffffffffffff 1.0 1.0 +3fe489a421f44d7f 0.64 0.642 +3fe1033d48ed7e28 0.53 0.532 +3fe6730a33ac9cf3 0.7 0.702 +3fe87cab6240d3cf 0.77 0.765 +3fe02a0e67a0b3d1 0.51 0.505 +3fe6c01ac5d33e3a 0.71 0.711 +3fd3163a92313e20 0.3 0.298 +3fe2260046444177 0.57 0.567 +3f89928513fc3b40 0.01 0.012 +3fb2d0d89636d548 0.07 0.073 +3fbbe771d9f75250 0.11 0.109 +3fd85ec5fde1247e 0.38 0.381 +3fec0d35195c78e9 0.88 0.877 +3fda4a4597b124c0 0.41 0.411 +3fdeac2f82f9dfae 0.48 0.479 +3fd5dbf7f4de7d5a 0.34 0.342 +3fe874abfc47925b 0.76 0.764 +3fe223e7f5f858f1 0.57 0.567 +3fd6814f10f6d666 0.35 0.352 +3feca7a809fc391e 0.9 0.895 +3fb738b917ce95e8 0.09 0.091 +3fdc0166ca312ab0 0.44 0.438 +3fc26818937de3d8 0.14 0.144 +3fea31412a668a2e 0.82 0.819 +3fe5ac185bd2a29b 0.68 0.677 +3feef95cce9cb000 0.97 0.968 +3fd6c182295d6384 0.36 0.356 +3fe64d9f8efea12f 0.7 0.697 +3feb7f5735eff974 0.86 0.859 +3fc2c413266454d0 0.15 0.147 +3fe39fbf6c14a15c 0.61 0.613 +3feb07901b6943f2 0.84 0.845 +3fefb635369a0243 0.99 0.991 +3fee5d661f52f5bc 0.95 0.949 +3fe7f5b9089c82ad 0.75 0.749 +3fd58cf231b70192 0.34 0.337 +3fd66ed5a550cdd0 0.35 0.351 +3fe9576af8674480 0.79 0.792 +3f99e6f1c9389100 0.03 0.025 +3fcd36ec38b3a8ac 0.23 0.228 +3fd94efa500ef518 0.4 0.395 +3fee31eb8e90315a 0.94 0.944 +3feb84e63f42134c 0.86 0.86 +3fed5d34d7a24b87 0.92 0.918 +3fe3f6ea8fd19c0f 0.62 0.624 +3fe884afb34d6976 0.77 0.766 +3fca5f75f76806e8 0.21 0.206 +3fe070db62146bd0 0.51 0.514 +3fddf673c58a0f20 0.47 0.468 +3fe2feee9f01d539 0.59 0.594 +3fec338feac99b60 0.88 0.881 +3fe249eeb287495f 0.57 0.572 +3fc3143a01a90134 0.15 0.149 +3fec46aebbe786af 0.88 0.884 +3fc39117ce131588 0.15 0.153 +3fee37d003efa6e4 0.94 0.944 +3fef7ec8074848f3 0.98 0.984 +3f661a68db053700 0.0 0.003 +3fee2dd301400472 0.94 0.943 +3fd3245e1022ae0a 0.3 0.299 +3fda0e52cd1b1a68 0.41 0.407 +3fd0c178712bf5c2 0.26 0.262 +3f9d6c6e4cc3f420 0.03 0.029 +3fe193dfbe558d78 0.55 0.549 +3fe53fd0eadc30ec 0.66 0.664 +3fd4dab62c83f066 0.33 0.326 +3fee73bbbf2f7513 0.95 0.952 +3fd31621c741c1f0 0.3 0.298 +3fde3bc47a18b438 0.47 0.472 +3fe6178cc76fec8d 0.69 0.69 +3fd4b9be089b94b4 0.32 0.324 +3fe6251bdf58cca9 0.69 0.692 +3fe59038203fc299 0.67 0.674 +3fdd9c2b31f33378 0.46 0.463 +3f86a5291445d2c0 0.01 0.011 +3fe855541f42391a 0.76 0.76 +3fee8840b793846e 0.95 0.954 +3fbc80943cf32f88 0.11 0.111 +3fed441858a249f9 0.91 0.915 +3fb7f321845dee78 0.09 0.094 +3fe72002d83abb80 0.72 0.723 +3faf813637d14980 0.06 0.062 +3fdb4276b2b7ee82 0.43 0.426 +3fe5179f4ad0f87a 0.66 0.659 +3feb135124c684ab 0.85 0.846 +3fc5b84936096884 0.17 0.17 +3fde3fc38b96bd18 0.47 0.473 +3fe20c585d9b4837 0.56 0.564 +3fd4689164652ce4 0.32 0.319 +3fe5b880bd6ec003 0.68 0.679 +3fdf9436759ec9fa 0.49 0.493 +3fd6082d226aad90 0.34 0.344 +3feac6a9f59345f0 0.84 0.837 +3fead9078d30c065 0.84 0.839 +3f839bbe4a487d40 0.01 0.01 +3fe75cd670faae64 0.73 0.73 +3fec4e651b7aeefd 0.88 0.885 +3fd77d81ca4ff772 0.37 0.367 +3fd7353249e88c02 0.36 0.363 +3fde9aab3c3ac09c 0.48 0.478 +3fe49960bba2a329 0.64 0.644 +3fd3b7005309dfb8 0.31 0.308 +3fcff1f2848052ac 0.25 0.25 +3fcf2edb31ad7c70 0.24 0.244 +3fb428f1fbafc380 0.08 0.079 +3fbb85a5ddeee178 0.11 0.108 +3fd8e5546ed3de84 0.39 0.389 +3fe2cf24e366b4e9 0.59 0.588 +3fdf547394409818 0.49 0.49 +3fd904571178b9fa 0.39 0.391 +3fb8860605c3a9b0 0.1 0.096 +3fe5f6005886eded 0.69 0.686 +3fe46e318072ad00 0.64 0.638 +3fe0293492a5b339 0.51 0.505 +3fdbeea4c579a580 0.44 0.436 +3fe02539d214260d 0.5 0.505 +3fcd654eeee8ae20 0.23 0.23 +3fccac67970f4c6c 0.22 0.224 +3feb6b0eb0a0317c 0.86 0.857 +3fe32ed903e77bd3 0.6 0.599 +3fe5f9a0d8fac7c0 0.69 0.687 +3fcf840c13f25a58 0.25 0.246 +3fd8475b55713a3a 0.38 0.379 +3fd85f8587b6723e 0.38 0.381 +3fc7d12b28c70f58 0.19 0.186 +3fcb186aae73e93c 0.21 0.212 +3fdbf8f84088ab02 0.44 0.437 +3fc579d10aff0f9c 0.17 0.168 +3fec236388d9c414 0.88 0.879 +3fe2dfddc22c9aad 0.59 0.59 +3fd2b0eaf0968084 0.29 0.292 +3fac8e89952b5cf0 0.06 0.056 +3fc7fa7dbe7aac30 0.19 0.187 +3febdbfddc872f46 0.87 0.871 +3fe28353e757e629 0.58 0.579 +3fa858f80c987590 0.05 0.048 +3fe3ae912ba791b6 0.62 0.615 +3fefb360df4a3278 0.99 0.991 +3fed97eed8428c7a 0.92 0.925 +3fe4f84f0bf6f130 0.66 0.655 +3fd18dd3af37e4c0 0.27 0.274 +3fd8bb74288915a0 0.39 0.386 +3feb54d48f0e1b5c 0.85 0.854 +3fc70f129fbed714 0.18 0.18 +3fd885cf51a180a2 0.38 0.383 +3fd547b18c8607a6 0.33 0.333 +3fc8f0111c97ff88 0.19 0.195 +3fe6bf9a322749e3 0.71 0.711 +3fdd333281aa462e 0.46 0.456 +3fe8a224061a19ba 0.77 0.77 +3fe759319ef1c467 0.73 0.73 +3fb8273e0e486430 0.09 0.094 +3fca5c7d95d1a4b0 0.21 0.206 +3fedef769cb2ac5a 0.94 0.935 +3fe2afa4fa8e3a9e 0.58 0.584 +3fcf695d95d64108 0.25 0.245 +3fecca731cb84344 0.9 0.9 +3faa3f780b116fb0 0.05 0.051 +3fe082a7611fdb82 0.52 0.516 +3fea1f7c012568c9 0.82 0.816 +3fed58c74ac76061 0.92 0.917 +3fed4df9faba0b24 0.92 0.916 +3fe530247db24b9d 0.66 0.662 +3fcece07abba9860 0.24 0.241 +3fec904640f50383 0.89 0.893 +3fbf31c650194290 0.12 0.122 +3fedecbd32055c84 0.94 0.935 +3fdcef0022b22e40 0.45 0.452 +3fc1a0d9d8ccac30 0.14 0.138 +3fc3754bdc4c2c84 0.15 0.152 +3feb4442d9d0a3e6 0.85 0.852 +3fa953f560f0cde0 0.05 0.049 +3fe8ab60327fb19e 0.77 0.771 +3fc0f716c188b8f8 0.13 0.133 +3fc6e8142915ed08 0.18 0.179 +3fe535a6d77f06cc 0.66 0.663 +3fd1f9083d073f30 0.28 0.281 +3fd647b84a738336 0.35 0.348 +3fe81e9b782ef291 0.75 0.754 +3fd88262d5b00698 0.38 0.383 +3fe6a6dd8ed93ca1 0.71 0.708 +3fd47bdf3083ae0c 0.32 0.32 +3fddd71543c48c9e 0.47 0.466 +3fef8136daa99676 0.98 0.985 +3fd5eaff8cee6288 0.34 0.342 +3fea9638e6af4927 0.83 0.831 +3fc997ec56d6ea70 0.2 0.2 +3fe16d10acfe3a9a 0.54 0.545 +3fe1ca07987541c6 0.56 0.556 +3fef3ac2c9345755 0.98 0.976 +3fe980bd852563d4 0.8 0.797 +3fe4aa01a27b1605 0.65 0.646 +3fe920f434d54a39 0.79 0.785 +3fe3f2145ad9a12a 0.62 0.623 +3fe8bf091a39b00b 0.77 0.773 +3fb540ab77d1c540 0.08 0.083 +3fd17e51484e38d0 0.27 0.273 +3fe7793ab57e6cf2 0.73 0.734 +3fbccf4964bd0528 0.11 0.113 +3fc1f0477e3a60d0 0.14 0.14 +3fef2db83dff6909 0.97 0.974 +3fee2ce0212af689 0.94 0.943 +3fd22312fd110fca 0.28 0.283 +3fc695f0d02f51f8 0.18 0.176 +3faf8ab81ec0b300 0.06 0.062 +3fe7078d9fea77dc 0.72 0.72 +3fdfd3eb252241b8 0.5 0.497 +3f9fa79bf67e12c0 0.03 0.031 +3fe861abc69551da 0.76 0.762 +3fe64c8abbbd6511 0.7 0.697 +3fefcd54aa406a61 0.99 0.994 +3febe17d811dbd88 0.87 0.871 +3fd76b55411c7cc6 0.37 0.366 +3fe1d4d8cfdf2888 0.56 0.557 +3fd8bd474490e43a 0.39 0.387 +3fcca0c14caf5920 0.22 0.224 +3fd8656538c073ae 0.38 0.381 +3fd7b1ffbb448314 0.37 0.37 +3fe047a91e45f8f6 0.51 0.509 +3fe8972b91a3e0a7 0.77 0.768 +3fed4cc8dddc5e64 0.92 0.916 +3fef22758ebef83a 0.97 0.973 +3fe1a41e4713b727 0.55 0.551 +3fde5c2b40d5b18e 0.47 0.474 +3fe94f47fde7cdf2 0.79 0.791 +3fd300cdea83806a 0.3 0.297 +3fde0d9f6befae5a 0.47 0.47 +3fbee30609376f68 0.12 0.121 +3fda2a7b467c6134 0.41 0.409 +3fee6fc0c8ade590 0.95 0.951 +3fd63317ea89488e 0.35 0.347 +3fe7e295a8238dba 0.75 0.746 +3fe7218eca0d1404 0.72 0.723 +3fec604abdfbbaba 0.89 0.887 +3feccf14e8094963 0.9 0.9 +3fdbe3b8edca3672 0.44 0.436 +3fef651a6ac85beb 0.98 0.981 +3fe36797c4e6614f 0.61 0.606 +3fc8a5ede82f8698 0.19 0.193 +3fe3f879d4e9e737 0.62 0.624 +3f8b5e4d7b523c80 0.01 0.013 +3febcd8d2287a716 0.87 0.869 +3fe2866392d978f4 0.58 0.579 +3f6368b98eb8d100 0.0 0.002 +3fe663ac4b5b25bf 0.7 0.7 +3fe6bd2ad35da28a 0.71 0.711 +3fd7d5f103710990 0.37 0.372 +3fe3549cb4074b44 0.6 0.604 +3fd1559ec8308d70 0.27 0.271 +3fdf6fbfdb4fb14e 0.49 0.491 +3fe77b6cd369f677 0.73 0.734 +3fc9c5324e9ddd40 0.2 0.201 +3fada8622c20daf0 0.06 0.058 +3fd60ac926f0768c 0.34 0.344 +3feb8a8f59f1186c 0.86 0.861 +3fdf4688ec8e397c 0.49 0.489 +3fe576a8ccf5a0fc 0.67 0.671 +3feed93fa1accb12 0.96 0.964 +3fece01d306abf86 0.9 0.902 +3fc35e0f706bd104 0.15 0.151 +3fe5fcacd234a32c 0.69 0.687 +3fe27f42da371fa1 0.58 0.578 +3fea614f347c821c 0.82 0.824 +3fcb402a7df522dc 0.21 0.213 +3fef065dc5032251 0.97 0.97 +3fe2423ea28904e4 0.57 0.571 +3fd51efd90124572 0.33 0.33 +3fd84268e8adf7c0 0.38 0.379 +3fe374648c4c94f7 0.61 0.608 +3fdb04b2027b4eea 0.42 0.422 +3fc77bc4028d3e24 0.18 0.183 +3fd30a51eb0ba0e6 0.3 0.298 +3f977400e7421360 0.02 0.023 +3fd4015de575ac10 0.31 0.313 +3feb18e776cf4493 0.85 0.847 +3fbc39156dc3db10 0.11 0.11 +3fdd9eea8042e0f8 0.46 0.463 +3fed5eb42d73bc2e 0.92 0.918 +3fe36a6631a5d171 0.61 0.607 +3fd80c92bc7d02c6 0.38 0.376 +3fc441dd1f4a04d4 0.16 0.158 +3fdbd6ce26e7f13a 0.43 0.435 +3febfa2ca451fd68 0.87 0.874 +3fc50baa759ec214 0.16 0.164 +3fe86d179a60a81a 0.76 0.763 +3f785c019c866c00 0.01 0.006 +3fefcc3cdef1a328 0.99 0.994 +3fefd926f6563707 1.0 0.995 +3fd8c5fc222e6da4 0.39 0.387 +3fef13c85010f281 0.97 0.971 +3fed69b922f18835 0.92 0.919 +3fecd0b316430214 0.9 0.9 +3fe053720e7a9dbf 0.51 0.51 +3fd7e9f91efaa36e 0.37 0.374 +3fdaf17e0957859e 0.42 0.421 +3fd7269089117b44 0.36 0.362 +3fe88346ec5c1261 0.77 0.766 +3fd2bcaa0458ec46 0.29 0.293 +3fe6a29a493dae7f 0.71 0.707 +3fe0332679cea24c 0.51 0.506 +3fbeca36528a1e88 0.12 0.12 +3fec3425b1edaefb 0.88 0.881 +3fef7236ec322abe 0.98 0.983 +3fa29563abc3c270 0.04 0.036 +3fe8f9cab4dcab8f 0.78 0.78 +3fc7b200757abb3c 0.19 0.185 +3fd71462ca0777a0 0.36 0.361 +3fca00b69bf85d28 0.2 0.203 +3fddad1a16969bb4 0.46 0.464 +3fd74c6c4c54b0f8 0.36 0.364 +3faf3bb288bf91e0 0.06 0.061 +3fe3088a698db49a 0.59 0.595 +3fbe988620a09470 0.12 0.12 +3fddf777bc8d3cc8 0.47 0.468 +3fdc808ce4f92030 0.45 0.445 +3fe22fabf4909004 0.57 0.568 +3fe90a172779a720 0.78 0.782 +3fd600e87466d28a 0.34 0.344 +3fe048f48a28be69 0.51 0.509 +3fb93e921468f550 0.1 0.099 +3fd066573d99c2e6 0.26 0.256 +3fe10130778cde11 0.53 0.531 +3fec84ad4a88ce7b 0.89 0.891 +3fec29e17ae722e3 0.88 0.88 +3fcff29e6510799c 0.25 0.25 +3f98c591e1bfed40 0.02 0.024 +3fedc435739a8ce5 0.93 0.93 +3fd9185f3c1f3018 0.39 0.392 +3fbdb7cc612e2fc8 0.12 0.116 +3fe84adb3580bb03 0.76 0.759 +3fcc4aa2768d1df8 0.22 0.221 +3fd3745cc8ddf02e 0.3 0.304 +3feff3f4f890746c 1.0 0.999 +3fe28a21899c35e2 0.58 0.579 +3fe835ec2be1037b 0.76 0.757 +3feafe274e7920cb 0.84 0.844 +3fe034fc2fd93a8b 0.51 0.506 +3fe07391eab88840 0.51 0.514 +3fe01c366031e1c9 0.5 0.503 +3fd43e0cf89f540e 0.32 0.316 +3fef7d775681f55b 0.98 0.984 +3fc658f426ca0178 0.17 0.175 +3fdb23b25b8774cc 0.42 0.424 +3fe82de7f71e695d 0.76 0.756 +3fd62530b0f538bc 0.35 0.346 +3fe90f854dc03428 0.78 0.783 +3fa932fed2dcc460 0.05 0.049 +3fdf1e85480ac910 0.49 0.486 +3fdf645eae164e92 0.49 0.491 +3fe3cf3228866659 0.62 0.619 +3fd1e765772c4416 0.28 0.28 +3fdc2fcf78f1394c 0.44 0.44 +3fe6ddc99bea458b 0.71 0.715 +3fd15420365a9c8c 0.27 0.271 +3fe234d833fdcabf 0.57 0.569 +3fe7e47bdfdc73e5 0.75 0.747 +3fee282afa939fb8 0.94 0.942 +3fed938fc0ba4c9e 0.92 0.924 +3fda97f177517584 0.42 0.416 +3fea3b6cb2fbccb6 0.82 0.82 +3fdfbcf0a660ce62 0.5 0.496 +3fc0fdbf8c5c0300 0.13 0.133 +3fcf3287f965c08c 0.24 0.244 +3fcc95db342a0038 0.22 0.223 +3fe50bbb6c09613a 0.66 0.658 +3fa36f7df8c2ff70 0.04 0.038 +3fe8c5fa4e8d1df4 0.77 0.774 +3fee04132cd565ac 0.94 0.938 +3fd6264abe0199e6 0.35 0.346 +3fd1fbe2be0fd1f0 0.28 0.281 +3f946a5732e708c0 0.02 0.02 +3fec8e26d36f2bca 0.89 0.892 +3f898ec187887a00 0.01 0.012 +3fed164265c20f37 0.91 0.909 +3fe209216087d252 0.56 0.564 +3fd689b22bcad244 0.35 0.352 +3fe358f69c5b388d 0.6 0.605 +3fe298c742191ec0 0.58 0.581 +3fe25cb84280124b 0.57 0.574 +3fc7e9f6206e635c 0.19 0.187 +3fc042207a95091c 0.13 0.127 +3fbbf6144d21cd48 0.11 0.109 +3fd0ca152a008230 0.26 0.262 +3fe3b8c2479b719b 0.62 0.616 +3f98ff5c03163c60 0.02 0.024 +3fd6887fb17f59e6 0.35 0.352 +3fe7fd759eead442 0.75 0.75 +3fe9c0f5d05a1bb2 0.8 0.805 +3fee0a6ebd366fe0 0.94 0.939 +3fcd5517ce9cb318 0.23 0.229 +3fa182633ca74300 0.03 0.034 +3fead9a6276a9464 0.84 0.839 +3fcd38c0c2f990c8 0.23 0.228 +3fc8d83bd7e130b0 0.19 0.194 +3fd745411d19c3f4 0.36 0.364 +3f9f3ed06fb72d20 0.03 0.031 +3fe2307c8feeb492 0.57 0.568 +3fc1a53525449c5c 0.14 0.138 +3fee210f5c93c9da 0.94 0.942 +3fbd78c03098efa8 0.12 0.115 +3fe8cc2c5e6e3b33 0.77 0.775 +3fef4b3ba1484eae 0.98 0.978 +3fdf4414984c7976 0.49 0.489 +3fea9c66268e6aef 0.83 0.832 +3fd6b306a72c364a 0.35 0.355 +3fe8490a9f84ad09 0.76 0.759 +3fee27fdf23ef099 0.94 0.942 +3fd531622a114274 0.33 0.331 +3fb93f435aae7500 0.1 0.099 +3fd2988327503ab2 0.29 0.291 +3fb735c6b407de48 0.09 0.091 +3fd72b66151cdaca 0.36 0.362 +3fea1085225513da 0.81 0.815 +3fa2b3edfb1b8490 0.04 0.037 +3feccc089772329d 0.9 0.9 +3fde83b095acc61e 0.48 0.477 +3fc49a093edec15c 0.16 0.161 +3fe08c17fc3d1acf 0.52 0.517 +3fea1fe13d99a6cb 0.82 0.816 +3fe74ce4dcf87fbf 0.73 0.728 +3fdb0888840efa32 0.42 0.422 +3fddab9eba0fa4e4 0.46 0.464 +3fe50167f368d462 0.66 0.656 +3fc14fe015b352e0 0.14 0.135 +3fd83c53096bb3c2 0.38 0.379 +3f91d240278c8d40 0.02 0.017 +3fed3994bc3323d9 0.91 0.913 +3fd7c19fa37a8f88 0.37 0.371 +3fc8b4683583a848 0.19 0.193 +3fccc341fcfbde88 0.22 0.225 +3fe037899b493f87 0.51 0.507 +3fd8fac0cca14c7a 0.39 0.39 +3f9eeb6da18a1180 0.03 0.03 +3fcff5b67cf0381c 0.25 0.25 +3fe341453d98f6b8 0.6 0.602 +3fd7a2e3eabab002 0.37 0.369 +3fd22045cd928232 0.28 0.283 +3fe8fc3ac289242c 0.78 0.781 +3fef837f939525c6 0.98 0.985 +3fc5b2acb106d078 0.17 0.17 +3fdf2e66f8dc6274 0.49 0.487 +3fa849fb7d7de0f0 0.05 0.047 +3fe2080f7fa3f007 0.56 0.563 +3fdd30e5fe5f0ea2 0.46 0.456 +3fc3a03edcc01eac 0.15 0.153 +3fa484984eca38e0 0.04 0.04 +3fe6907500b005e4 0.71 0.705 +3fece4f5b14cdc4b 0.9 0.903 +3fe98a2e32a924b1 0.8 0.798 +3fe5ab28dd9e67de 0.68 0.677 +3fe26d5c698753d8 0.58 0.576 +3fc0d2ade2bbaae8 0.13 0.131 +3fed13917771e801 0.91 0.909 +3fd8d40b3e5ba758 0.39 0.388 +3fbfe0ae333841d8 0.12 0.125 +3fd91a3d857149fa 0.39 0.392 +3fe7e46462b36f91 0.75 0.747 +3fed4e1f895d8f0c 0.92 0.916 +3fd79341c01ea3d0 0.37 0.368 +3fecc2c043bd66c1 0.9 0.899 +3fcda1cacca39198 0.23 0.232 +3feeb30a1bc226f0 0.96 0.959 +3fd7e257bb2b971e 0.37 0.373 +3feb1e11c1e8f850 0.85 0.847 +3fb4c6a95e1492c8 0.08 0.081 +3fd1818971e92e56 0.27 0.274 +3fefad753eb99cd3 0.99 0.99 +3fe642c1355cc977 0.7 0.696 +3fe2b660379e424c 0.58 0.585 +3fe88d6b5edd57bc 0.77 0.767 +3fd8ed778a22649a 0.39 0.389 +3fb6eb1c527938c8 0.09 0.09 +3fd5d61562282656 0.34 0.341 +3fbfed814419f8a8 0.12 0.125 +3fed16d0f3f1e117 0.91 0.909 +3fa485d61bffddf0 0.04 0.04 +3fe6dd664db8a3c8 0.71 0.715 +3fca7bee2968a080 0.21 0.207 +3fd02b16d21dcb54 0.25 0.253 +3fe89fb5c5708e15 0.77 0.769 +3fc5c701a72f7f6c 0.17 0.17 +3fc8e5a39b501108 0.19 0.195 +3fe6e7cb43baf0e5 0.72 0.716 +3fe88807a09428c2 0.77 0.767 +3fc3bc8c7ecfcaec 0.15 0.154 +3feceb0b99b236fa 0.9 0.904 +3fec8dba069c4eec 0.89 0.892 +3fb63f2aa665b5e0 0.09 0.087 +3feeef08721c7365 0.97 0.967 +3fe01539b42eaae0 0.5 0.503 +3fd2051afd0a0afe 0.28 0.282 +3fb700c6b069a3f8 0.09 0.09 +3fb39748dc2212f8 0.08 0.077 +3feffa10aaeb5a9b 1.0 0.999 +3fb48cf4746c6ff0 0.08 0.08 +3fef51610ec2e060 0.98 0.979 +3fe6a79116409061 0.71 0.708 +3fe446fac5ff7f3b 0.63 0.634 +3fec6e8144cf377f 0.89 0.888 +3fc6a8761c4cde98 0.18 0.177 +3fe4cf022aadaebb 0.65 0.65 +3fec8686618dcf5b 0.89 0.891 +3fae35cc80347cb0 0.06 0.059 +3fc27a72153c7b3c 0.14 0.144 +3fd72301923d7776 0.36 0.362 +3fdba419bcf1e168 0.43 0.432 +3fe3bf688b88d861 0.62 0.617 +3fc5b9d0e31e4800 0.17 0.17 +3fd174cacc04fe62 0.27 0.273 +3fd59f241f75c5c0 0.34 0.338 +3fe198ffa378271d 0.55 0.55 +3feac7d47e28bc8a 0.84 0.837 +3fd51abc65561692 0.33 0.33 +3fee7295b32814f4 0.95 0.951 +3fd8b293a5607822 0.39 0.386 +3fd908da481645d0 0.39 0.391 +3f9042821d256340 0.02 0.016 +3fe04a384a4f7a10 0.51 0.509 +3fc07679cd616320 0.13 0.129 +3fea87c934b4629b 0.83 0.829 +3feb35f6a941a840 0.85 0.85 +3fd36cafb56669b4 0.3 0.304 +3fe42144f93c8652 0.63 0.629 +3fe0734fbc97cdcc 0.51 0.514 +3fd3e7bd0de95430 0.31 0.311 +3fe61a21e6f810a3 0.69 0.691 +3fd5730cc4d7b800 0.34 0.335 +3f8846eefa7b5440 0.01 0.012 +3fdd9cfb39aebba8 0.46 0.463 +3fdcdf44cc25522a 0.45 0.451 +3fee0e57e5d242a4 0.94 0.939 +3fe4b0a0b68c4cda 0.65 0.647 +3fd0dbdb25c60892 0.26 0.263 +3fe6f0ce74bc23c9 0.72 0.717 +3fd30e180ffb1ba6 0.3 0.298 +3fea1c20fe24fe61 0.82 0.816 +3fe974ed877cfd91 0.8 0.796 +3faa0ece5d86cc60 0.05 0.051 +3fdb58d7ef57c236 0.43 0.427 +3fe5f881c961bb3a 0.69 0.687 +3fe8e97270a47de8 0.78 0.778 +3feaab10b25fddb3 0.83 0.833 +3fdf936f86567074 0.49 0.493 +3fb33b64069e7388 0.08 0.075 +3fcacc9e3d66e848 0.21 0.209 +3fb1e393f711b550 0.07 0.07 +3fa35e1b57a28f30 0.04 0.038 +3fc0450937213f00 0.13 0.127 +3fb90937e1a0ddc0 0.1 0.098 +3fe51f8ebc7c89f4 0.66 0.66 +3fc5f8780d334838 0.17 0.172 +3fdaf4d72690c27c 0.42 0.421 +3feef6559dbebe4f 0.97 0.968 +3fd30110ce68d25a 0.3 0.297 +3fe7db16c39340f0 0.75 0.745 +3fe3db75d9c58cc7 0.62 0.621 +3fee08a9ed7805f3 0.94 0.939 +3febed3dd6ccab03 0.87 0.873 +3fd0cacf7bab2bbe 0.26 0.262 +3feaa5380ba0e58c 0.83 0.833 +3fe24add9be23cbf 0.57 0.572 +3fc95fc565f2bec4 0.2 0.198 +3fe60295df66729e 0.69 0.688 +3fe5f97ad774c702 0.69 0.687 +3fe2cd07bbbb5816 0.59 0.588 +3fec58e3ea142c4d 0.89 0.886 +3fd2d3beb451318a 0.29 0.294 +3fdb0d47d422e8d8 0.42 0.423 +3fe244c4992d34d0 0.57 0.571 +3fe732b31f525913 0.72 0.725 +3fecf6f68640bae5 0.91 0.905 +3fe2da7bea93a30a 0.59 0.589 +3fed5d3ea03fd3f0 0.92 0.918 +3fae8852bb813fa0 0.06 0.06 +3fb8ed719de2bfe0 0.1 0.097 +3fd23ae7a647377c 0.28 0.285 +3fe3cd547d209bd8 0.62 0.619 +3fd30aff1b068624 0.3 0.298 +3fd28340ba1ee42e 0.29 0.289 +3fe8223e526e5315 0.75 0.754 +3fe93cda1fd2a8d2 0.79 0.789 +3fe36a6e8a906292 0.61 0.607 +3fdd13d5fd2a770c 0.45 0.454 +3fefea85cb682804 1.0 0.997 +3fee19232c4b67b3 0.94 0.941 +3f991e8deeca5a40 0.02 0.025 +3fdc086f83199ffe 0.44 0.438 +3fe37f666302730d 0.61 0.609 +3fddb4b7776d85fa 0.46 0.464 +3fdf79007436fe04 0.49 0.492 +3fd737fe1668b69c 0.36 0.363 +3fd4e5a60f2c5af4 0.33 0.327 +3fd48eed941462d6 0.32 0.321 +3fed9993237c1a9e 0.92 0.925 +3fe3236445e81f1c 0.6 0.598 +3fd7d96a7e15202a 0.37 0.373 +3fef4104a88de435 0.98 0.977 +3fb81428360e7130 0.09 0.094 +3fd67b2751afc0c6 0.35 0.351 +3fb1d0a2d7d05ec8 0.07 0.07 +3fecfc009d834885 0.91 0.906 +3fdbb55110608000 0.43 0.433 +3fe93f1cc37a292c 0.79 0.789 +3fefff76987266d9 1.0 1.0 +3fe61a18e9b73656 0.69 0.691 +3fb57be36eaf12a0 0.08 0.084 +3fe7902eb5803324 0.74 0.736 +3fdd4409281658d2 0.46 0.457 +3fe37fea6e50d109 0.61 0.609 +3fdf023694aeecf6 0.48 0.485 +3fd89ff99372a68c 0.38 0.385 +3fdf877f55a37c5c 0.49 0.493 +3fed41c01e8b9067 0.91 0.914 +3fe321df45b7ba0f 0.6 0.598 +3fe1a744a1340482 0.55 0.552 +3f8f951eab7dcd00 0.02 0.015 +3fe7640ff9e71d3f 0.73 0.731 +3fc859b891941248 0.19 0.19 +3fea56cd8c4d08c4 0.82 0.823 +3fee9317bb1a44c0 0.96 0.955 +3fee2a2d39f51229 0.94 0.943 +3fd3d53835e4a900 0.31 0.31 +3fc8bb41c03993a4 0.19 0.193 +3fe9e087e9d30f0c 0.81 0.809 +3fe6692cb6cdcf29 0.7 0.7 +3fd6b2535df1fa42 0.35 0.355 +3fc6ff2784e5562c 0.18 0.18 +3fd9a3b2db86023c 0.4 0.401 +3fef6db45404903f 0.98 0.982 +3fe00cb26e3f9dd2 0.5 0.502 +3fea489c38f68508 0.82 0.821 +3feb10ade2182971 0.85 0.846 +3fc60e60514b9100 0.17 0.172 +3fe82b2d6656957f 0.76 0.755 +3fd037b68d711c5e 0.25 0.253 +3fc81abb77a6a10c 0.19 0.188 +3fdaacb2c0f18a08 0.42 0.417 +3fde4f1e6ddf700c 0.47 0.474 +3fd5678a605078fe 0.33 0.334 +3fec10b4c5afd2e3 0.88 0.877 +3fed134be436678b 0.91 0.909 +3fd0a88aab22bbcc 0.26 0.26 +3fa6e01bebc49450 0.04 0.045 +3fe5b72144c21269 0.68 0.679 +3fc1ed1cadca3db4 0.14 0.14 +3fec701966aab909 0.89 0.889 +3fc7b5476bb10b38 0.19 0.185 +3fe3af3532fd7fe1 0.62 0.615 +3fdefa939959c01e 0.48 0.484 +3fc26ae7abbefd1c 0.14 0.144 +3fe8b93b52ff2761 0.77 0.773 +3fc94cfbf8b26944 0.2 0.198 +3fc4da68c88a8aa0 0.16 0.163 +3fdec178469198d6 0.48 0.481 +3fc95a29526ca93c 0.2 0.198 +3fef70e93b83447c 0.98 0.983 +3fcc6b21d56c195c 0.22 0.222 +3fd26f026514fcba 0.29 0.288 +3fe690332a1a792e 0.71 0.705 +3fd42eb23b66801a 0.32 0.315 +3fe5f5d92cb2abf8 0.69 0.686 +3fb86efce75dfea0 0.1 0.095 +3fde868fa8ce2b6c 0.48 0.477 +3fd9db0875642340 0.4 0.404 +3fc81b925282f600 0.19 0.188 +3fd0c032c6f7ac40 0.26 0.262 +3f8f3700ee6cda80 0.02 0.015 +3fca428b1c3c0acc 0.21 0.205 +3fe5d0aa2d7a07c1 0.68 0.682 +3fb37f62aa0b4518 0.08 0.076 +3fe26d7bed35b069 0.58 0.576 +3fdf2397fd3332b0 0.49 0.487 +3fe359267e85606a 0.6 0.605 +3fe1c8fecdaefa69 0.56 0.556 +3fd02a9194e09528 0.25 0.253 +3fc158218865a230 0.14 0.136 +3fd679e94b9e78d2 0.35 0.351 +3fe369ae404a8ac3 0.61 0.607 +3fd1767e6c2ba7e8 0.27 0.273 +3fbbafbfa17776b0 0.11 0.108 +3fd3c391dff30dba 0.31 0.309 +3f7e9d4df7cb0000 0.01 0.007 +3fc8069df2bcf7b4 0.19 0.188 +3fd2077141596a9a 0.28 0.282 +3fd1438957a6ba34 0.27 0.27 +3fdd4f5294de11de 0.46 0.458 +3fdbe0d3c063d4de 0.44 0.436 +3fca4115371b8668 0.21 0.205 +3feb28251dc80b5e 0.85 0.849 +3fdac719819efd32 0.42 0.418 +3fe167f550760609 0.54 0.544 +3fee06f8dc558415 0.94 0.938 +3fe07eaa29db742c 0.52 0.515 +3fe18081b43fb4b9 0.55 0.547 +3fdf5f325aef3ed4 0.49 0.49 +3fe09cc5623539da 0.52 0.519 +3fb612811065fe90 0.09 0.086 +3fd3aa4ad1007bec 0.31 0.307 +3fde55ed79d3ef9c 0.47 0.474 +3fe5c7b781cacd0c 0.68 0.681 +3fe64a084ca48d08 0.7 0.697 +3fd98770f4dc18b2 0.4 0.399 +3fe62339bc53cae5 0.69 0.692 +3fd46c81e67a1570 0.32 0.319 +3fc9ef9d411ee354 0.2 0.203 +3fe90c7bce91caef 0.78 0.783 +3fe6f1d2143bfc1f 0.72 0.717 +3fe1e858910cbbc0 0.56 0.56 +3fe13069c6ba9dc5 0.54 0.537 +3fee589ab202c457 0.95 0.948 +3fe1f66b1bf31039 0.56 0.561 +3fefd1c400a46e94 0.99 0.994 +3fe32b52634ff00e 0.6 0.599 +3fd03fe2acbd0d3c 0.25 0.254 +3fef9cd733beeccd 0.99 0.988 +3fe0b6db4a2c4832 0.52 0.522 +3fe9ced9419041b7 0.81 0.807 +3fe650c1156156d3 0.7 0.697 +3fe4be02928666c3 0.65 0.648 +3fe80d7742f1218e 0.75 0.752 +3fd33668081711be 0.3 0.3 +3fcb37ce010d33c0 0.21 0.213 +3fd883f5adf362a8 0.38 0.383 +3fb66bbf2dcdd040 0.09 0.088 +3fe4efa16193e0a0 0.65 0.654 +3faa2023edd81960 0.05 0.051 +3fcd02c979108574 0.23 0.227 +3fe7451d2888d77e 0.73 0.727 +3fe67b2184dd3c33 0.7 0.703 +3fe97d0a28daf24e 0.8 0.797 +3fd0c68f5ba877c2 0.26 0.262 +3fed0f20b920c4c8 0.91 0.908 +3fba21a40832a070 0.1 0.102 +3feabd537ab8e900 0.84 0.836 +3fe3a02115914f7b 0.61 0.613 +3fef77cd90e707f9 0.98 0.983 +3fdf68dd4fe850f8 0.49 0.491 +3fcc2814e65e95b0 0.22 0.22 +3fd50cfd55d03e20 0.33 0.329 +3febb1171337fb94 0.87 0.865 +3fee7100873a5b18 0.95 0.951 +3fed4e9f8a217956 0.92 0.916 +3fd1f8a3450fc5b0 0.28 0.281 +3f8e059fae9d4780 0.01 0.015 +3fea11391db6e648 0.81 0.815 +3fc795571f109780 0.18 0.184 +3fe44d8af6b2aa60 0.63 0.634 +3fe356667efd434f 0.6 0.604 +3fc0b8c2ce2c0cc8 0.13 0.131 +3fe82ed9fa23a69a 0.76 0.756 +3fe6b5dadd80953c 0.71 0.71 +3fdad9c851943f3e 0.42 0.42 +3fb98e02e28b6718 0.1 0.1 +3fd851e77d005e5c 0.38 0.38 +3fecc14c04cc1399 0.9 0.899 +3fd80956f6dea9f4 0.38 0.376 +3fecaa1af4f1b817 0.9 0.896 +3fd9ef1d590fea3e 0.41 0.405 +3fdb4e2fccfdd078 0.43 0.427 +3fde8cc253176746 0.48 0.477 +3fbdcfd8c98c32e8 0.12 0.116 +3fc19b72886d376c 0.14 0.138 +3fe3b5083b40d84b 0.62 0.616 +3feacb2e252a2b93 0.84 0.837 +3fe4c4162d71b76a 0.65 0.649 +3fe5002ba41d3f7e 0.66 0.656 +3fee258bba8c2f82 0.94 0.942 +3febc1f17bdf4f44 0.87 0.867 +3fe4d2deaf5ffc44 0.65 0.651 +3fe2bd3b05ac0c3a 0.59 0.586 +3fdf7690edd615e8 0.49 0.492 +3fe9f75f10b8074d 0.81 0.811 +3fe91b358d0d564f 0.78 0.785 +3fe54761ffdc8468 0.66 0.665 +3fd0b1274543ec2a 0.26 0.261 +3fe2e97e468b691e 0.59 0.591 +3fd619fa001f719e 0.35 0.345 +3fd5d0be3652bfe6 0.34 0.341 +3fe8f3500e38355b 0.78 0.78 +3fc2d7d981edb61c 0.15 0.147 +3fc91c3e699c4244 0.2 0.196 +3fe84def19ae318a 0.76 0.76 +3febea0b98ca82f9 0.87 0.872 +3fe0bed88ecb1b19 0.52 0.523 +3fe6657bb12ea927 0.7 0.7 +3fc23a1ec6d8fcdc 0.14 0.142 +3fdde67fedaf8640 0.47 0.467 +3fe56abc0d32e9bd 0.67 0.669 +3fdfe19c437f21ac 0.5 0.498 +3fcb52bcfbc78300 0.21 0.213 +3fcdcf3c08d18f90 0.23 0.233 +3fddc49c632564e8 0.47 0.465 +3f89f60528994740 0.01 0.013 +3fd670ad99ab419e 0.35 0.351 +3fdb365d90606120 0.43 0.425 +3fe3db7f93a99c64 0.62 0.621 +3fe165723546e646 0.54 0.544 +3fd8d9b7bb144300 0.39 0.388 +3fdff5ed1f61f33e 0.5 0.499 +3fdd8421675715a6 0.46 0.461 +3fe8165e048d7d21 0.75 0.753 +3fd96ec0931775be 0.4 0.397 +3fd9837f19117de6 0.4 0.399 +3fd3cdda9d77cec4 0.31 0.309 +3fea6271c40f56bc 0.82 0.825 +3fea9dad900912b0 0.83 0.832 +3fee4d40bc618d4e 0.95 0.947 +3fe4a6fd5d579f33 0.65 0.645 +3fb2e6e62b4679e8 0.07 0.074 +3fc6c517a6799bd0 0.18 0.178 +3f9caeb380576300 0.03 0.028 +3fc678b552c4ca98 0.18 0.176 +3fe0f6d94171d291 0.53 0.53 +3fce99a771e055b4 0.24 0.239 +3fef89d7619b4801 0.99 0.986 +3fc9893f9f4178c8 0.2 0.2 +3fe11870a3b4ee5f 0.53 0.534 +3feaac711c5e9fbd 0.83 0.834 +3fe6aa83ab8cf116 0.71 0.708 +3feb85205e1ab041 0.86 0.86 +3fe362fc3e2da3ed 0.61 0.606 +3fe73f06e0294e23 0.73 0.726 +3fef6e8f314cceb9 0.98 0.982 +3fe004d3ba1603f0 0.5 0.501 +3fe05eb1aaa48483 0.51 0.512 +3fceb5a3a6af7ac0 0.24 0.24 +3fc3166a414b9da0 0.15 0.149 +3fec8002781f9e5b 0.89 0.891 +3fe9f4d4e94f68a8 0.81 0.811 +3fcfd15a35e9d2e8 0.25 0.249 +3fce5918b117e338 0.24 0.237 +3fd3111e869203aa 0.3 0.298 +3febc7102d8e9d64 0.87 0.868 +3fe6516a764db404 0.7 0.697 +3fe633aea71d83b8 0.69 0.694 +3fde0ede80eeb8fe 0.47 0.47 +3fcc66d23099dbd0 0.22 0.222 +3fc3731dca148d04 0.15 0.152 +3fc2b94547ff2070 0.15 0.146 +3fd98b0350e0f75c 0.4 0.399 +3fee5f4350a2e45d 0.95 0.949 +3fe9e766ed074ba5 0.81 0.809 +3fef3cb3bcca78f5 0.98 0.976 +3fe2c4bf9b7ed511 0.59 0.587 +3fd0f8e1bf7ba8f6 0.27 0.265 +3fcafb1c18224f28 0.21 0.211 +3fbfbafdab3dea90 0.12 0.124 +3fd8d4e0a67d9b96 0.39 0.388 +3fda9f0d34917966 0.42 0.416 +3fd3d6d42549895a 0.31 0.31 +3faa4a1940329760 0.05 0.051 +3fd6134e5adf471a 0.34 0.345 +3fefea2f99ccd7e9 1.0 0.997 +3fe3e23d2661c56e 0.62 0.621 +3fcbb9f32ddd3a18 0.22 0.217 +3fed7bdf85c1b963 0.92 0.921 +3fe66ef14a5d9b22 0.7 0.701 +3fd9e6efd94f0822 0.4 0.405 +3fe4c4f991d2e200 0.65 0.649 +3fcd843261181aa4 0.23 0.231 +3feb397166c3c90f 0.85 0.851 +3fee6d79b5864bc6 0.95 0.951 +3fea170760ed7daa 0.82 0.815 +3fd9c94e0880a034 0.4 0.403 +3feaa1b17771f0b6 0.83 0.832 +3fd2b3780d2d414e 0.29 0.292 +3fd40b9383785796 0.31 0.313 +3fee2380ac179db2 0.94 0.942 +3fe5c8c18960a041 0.68 0.681 +3fd8082da2b4cf88 0.38 0.375 +3fc232276ca96108 0.14 0.142 +3fbc2974e51d3b58 0.11 0.11 +3fc2ed869d24e084 0.15 0.148 +3febc614718d3db9 0.87 0.868 +3fe37d57f1ba9ab5 0.61 0.609 +3fe6928489c0624a 0.71 0.705 +3fe0231c84f00141 0.5 0.504 +3fe8b06a4335d77c 0.77 0.772 +3fe32a28ee445b11 0.6 0.599 +3feb854eba2dc69f 0.86 0.86 +3fde15a1292a7cf0 0.47 0.47 +3fe81569afab4956 0.75 0.753 +3fd2a57d41a6ef06 0.29 0.291 +3fcf71e937ff8e80 0.25 0.246 +3fcb74a014236cac 0.21 0.214 +3fe5c65b18e30d1c 0.68 0.68 +3fd0926569a8589c 0.26 0.259 +3fec5886bcbe1826 0.89 0.886 +3fe2fbde11e4dedf 0.59 0.593 +3fe834260e22bee2 0.76 0.756 +3fe2c6a62f057765 0.59 0.587 +3fdbe27a01e56f5a 0.44 0.436 +3fdec93cf2136698 0.48 0.481 +3fcf4227555acd90 0.24 0.244 +3fedef569b9dfba1 0.94 0.935 +3fefe5a206b39d4d 1.0 0.997 +3fe02e11571114f5 0.51 0.506 +3fd81178bdd6cff2 0.38 0.376 +3fe7a1a67815f30a 0.74 0.738 +3fe75f1b100942ea 0.73 0.73 +3fd0912edcbe476e 0.26 0.259 +3fb9f316e663f920 0.1 0.101 +3fadf491802c41e0 0.06 0.059 +3fea51e604fb3533 0.82 0.822 +3fbb54d9f439f988 0.11 0.107 +3fcf0493dc47e6ec 0.24 0.242 +3fd3268aa77a9a6e 0.3 0.299 +3fc12d3e12aea1f0 0.13 0.134 +3fe8296186772b08 0.76 0.755 +3fe3b15650a5c106 0.62 0.615 +3fb0ba1ea4d27638 0.07 0.065 +3fedd015ca2bb898 0.93 0.932 +3fed7b04f0f52183 0.92 0.921 +3fe68d83d9a9ba23 0.7 0.705 +3fe0fe8f74a7a263 0.53 0.531 +3fe93062916d6f6a 0.79 0.787 +3fc080494cb27950 0.13 0.129 +3fe9c4f28525f083 0.81 0.805 +3fdfcb590b3ee6ac 0.5 0.497 +3fad0e7c100c7de0 0.06 0.057 +3feb3dfe8a6a308a 0.85 0.851 +3fd2326bae4e6e5e 0.28 0.284 +3fb1e4451d83c1c0 0.07 0.07 +3fee8e6de621d56f 0.95 0.955 +3fd9726ca218b6bc 0.4 0.398 +3fd9ae818ba16086 0.4 0.401 +3fa928f2af2d68b0 0.05 0.049 +3fe8af8279462bf1 0.77 0.771 +3fe5f3bfddb91d0a 0.69 0.686 +3fefb964604fc538 0.99 0.991 +3fe8457efec4af37 0.76 0.758 +3fe018e143fa7713 0.5 0.503 +3fdb399103634e3c 0.43 0.425 +3fe7df3f90a4af7f 0.75 0.746 +3fd5249d726a694a 0.33 0.33 +3fe940f559371ab2 0.79 0.789 +3fe5f997d555c154 0.69 0.687 +3fef8dc00b720a02 0.99 0.986 +3fda42f60a07812c 0.41 0.41 +3fecbc1dfd07c06f 0.9 0.898 +3fd94628268255fc 0.39 0.395 +3fd4c4e943893b18 0.32 0.325 +3fa5862607ca2200 0.04 0.042 +3fcd19b834ffe20c 0.23 0.227 +3fe8dd860e13dee2 0.78 0.777 +3fe58fc065009ccd 0.67 0.674 +3fdcd7a56d4dbea0 0.45 0.451 +3fe63263e26b6aad 0.69 0.694 +3fdce2e66b26a0b8 0.45 0.451 +3fe1a5910ef9dd58 0.55 0.551 +3fe35853f4437f63 0.6 0.605 +3fa11d0e8a78ffe0 0.03 0.033 +3fe2edf18422bcac 0.59 0.592 +3fe1e2f5e1e32e1a 0.56 0.559 +3fe652e531f68b7b 0.7 0.698 +3fc71faa507f1d30 0.18 0.181 +3fe1ed9bade8ec6b 0.56 0.56 +3fd300595edb2390 0.3 0.297 +3fec81ba4ad9132f 0.89 0.891 +3fed614d42d78b69 0.92 0.918 +3fea9dfce71e4e03 0.83 0.832 +3fe0a72549e72a74 0.52 0.52 +3fdce3a2bf018f94 0.45 0.451 +3fe624ee6e34db23 0.69 0.692 +3fde9bb5f3c01a50 0.48 0.478 +3fc8112ba3344450 0.19 0.188 +3fe04e2c469e0794 0.51 0.51 +3fe20194eb8c8513 0.56 0.563 +3fe2e897d8c0cef8 0.59 0.591 +3fc63a0ec16d1958 0.17 0.174 +3fe994f9e41003f5 0.8 0.799 +3fe74018b30e8951 0.73 0.727 +3fec32cccc6cabf3 0.88 0.881 +3fdd48009abe4a2c 0.46 0.458 +3faacf093675b680 0.05 0.052 +3fd1312750a0ed92 0.27 0.269 +3fe1a085ddaaa88e 0.55 0.551 +3fb2249110a75a68 0.07 0.071 +3fd00b85b3ac8830 0.25 0.251 +3fe22d4c5a003bdf 0.57 0.568 +3fd8d69699e15dea 0.39 0.388 +3fe58c293f708697 0.67 0.673 +3fa938838477a6f0 0.05 0.049 +3fefe3951e4d5671 1.0 0.997 +3fec91194a6430cd 0.89 0.893 +3fdc0f1d32f2b102 0.44 0.438 +3fd7abd8b6b468ce 0.37 0.37 +3febaab2b1e48f7b 0.86 0.865 +3fea1f24327a5102 0.82 0.816 +3fdfe935b3bd19c0 0.5 0.499 +3fee1003a94d84b9 0.94 0.939 +3fb0c69f6f8dcb80 0.07 0.066 +3fe37cfc6a364e99 0.61 0.609 +3fe6302ccc45d561 0.69 0.693 +3feeb0707f5b439b 0.96 0.959 +3feb0cf39e866f53 0.85 0.845 +3fe9995bfad1b46d 0.8 0.8 +3fde8617b47ee6c6 0.48 0.477 +3fdc839bf543cb56 0.45 0.446 +3fcedbdd868ce1d0 0.24 0.241 +3fe06c784d58845c 0.51 0.513 +3f894978e635ec00 0.01 0.012 +3fea93e2d512e891 0.83 0.831 +3fe2e8e3f5bfbf7f 0.59 0.591 +3fe0d0327ac6f793 0.53 0.525 +3fea4701fa1eca3e 0.82 0.821 +3fb5417cc2fed718 0.08 0.083 +3fc675ca936f7c48 0.18 0.175 +3fdf854e2b9c0b3e 0.49 0.493 +3fd48564fc88a0b4 0.32 0.321 +3fe199edb9e6766e 0.55 0.55 +3fe3b5b8adc468ab 0.62 0.616 +3fdc7a2dbf40ff06 0.44 0.445 +3feaae835e205f13 0.83 0.834 +3fe94bf702dd1261 0.79 0.791 +3fcd3cec3c3c9dc4 0.23 0.228 +3fe69b4bcb3718a5 0.71 0.706 +3fe40502d473a81d 0.63 0.626 +3fcc942c167c7c78 0.22 0.223 +3fd98f0af932ba12 0.4 0.399 +3fdd28ffd7aa13ae 0.46 0.456 +3feceb824df457cf 0.9 0.904 +3fed8648d048cfc8 0.92 0.923 +3fe3a859cba3982f 0.61 0.614 +3fe83fc35d3df52c 0.76 0.758 +3fddb60f2747adc4 0.46 0.464 +3feb45de8f287056 0.85 0.852 +3feab7ef1d6c51c6 0.83 0.835 +3fc12ba285a4c264 0.13 0.134 +3fd3a8f38178c930 0.31 0.307 +3fad788b2eef5250 0.06 0.058 +3fde2a7ec33baefe 0.47 0.471 +3fe55feeb12f99cc 0.67 0.668 +3fe54295b41e13e0 0.66 0.664 +3fb884332e761038 0.1 0.096 +3fd70f79ee091686 0.36 0.36 +3fd8ccd522f285ac 0.39 0.388 +3fd9c66a2c43d474 0.4 0.403 +3fe9c92f9facc78f 0.81 0.806 +3fe20170e9a21cd6 0.56 0.563 +3fb3cf6c4b558d38 0.08 0.077 +3fe15d751cceaa9c 0.54 0.543 +3fedceaba7b253a5 0.93 0.931 +3f8119566cbdf940 0.01 0.008 +3fc6461b4df32b2c 0.17 0.174 +3fe04551c1596681 0.51 0.508 +3fec19ae4c6aef86 0.88 0.878 +3fe79cc4fe103cc0 0.74 0.738 +3fe668825b2b50e2 0.7 0.7 +3fdc0d8fa76d35fc 0.44 0.438 +3fdf1ddd8e99de2a 0.49 0.486 +3fe923e4c407d3bd 0.79 0.786 +3fdd6a38d65cf59c 0.46 0.46 +3fef264c75309be5 0.97 0.973 +3fd8868e04902ac0 0.38 0.383 +3fdecbfcd4c8186c 0.48 0.481 +3fe0d862f29fa2a4 0.53 0.526 +3fe29c0a82083a89 0.58 0.582 +3fdf83a4efa101d4 0.49 0.492 +3feda7d2e436a464 0.93 0.927 +3fe5062f5745f330 0.66 0.657 +3fe37fe81e137ae6 0.61 0.609 +3fe4525830b2ca22 0.64 0.635 +3fe503044802b2e8 0.66 0.657 +3fe70ba72b81eb88 0.72 0.72 +3fb53f488700e2d0 0.08 0.083 +3fe3c4ea78abda19 0.62 0.618 +3fee5cb8944cb5bb 0.95 0.949 +3fe58fffe65239a7 0.67 0.674 +3fe3f7fd54e742eb 0.62 0.624 +3fe4aaa00822f6ab 0.65 0.646 +3fda5f849b0ddd28 0.41 0.412 +3fe67179ad9a66fe 0.7 0.701 +3fd8227134887642 0.38 0.377 +3f8f7a7b43fe6000 0.02 0.015 +3fcad80c1044712c 0.21 0.21 +3f9a6e666c1f4dc0 0.03 0.026 +3fe75419604c3926 0.73 0.729 +3fdddb274ec69c3c 0.47 0.467 +3fd9ff13e4e5a9f6 0.41 0.406 +3fe1b0690d0c2336 0.55 0.553 +3fe3c4b8953f710d 0.62 0.618 +3fe13876113717b2 0.54 0.538 +3faf349c4de94a90 0.06 0.061 +3fda876c8854a918 0.41 0.415 +3fd2c7ec3ed3a4b0 0.29 0.293 +3fe87e96bdee299c 0.77 0.765 +3fd43f709eb36de2 0.32 0.316 +3feb8eebc4e56106 0.86 0.861 +3f50ef774c3ede00 0.0 0.001 +3fec4e63d6f71eae 0.88 0.885 +3fe1f24ae328b861 0.56 0.561 +3fda8aca01fc37e6 0.41 0.415 +3fd7010c00eb1e0a 0.36 0.359 +3fdc1fb89fdd85f6 0.44 0.439 +3fea3a187082e789 0.82 0.82 +3f8e2eb0dd916f00 0.01 0.015 +3fbbc5a0dede7b58 0.11 0.108 +3fd87501e596254e 0.38 0.382 +3fe269dd1b7d32f7 0.58 0.575 +3fee46226dba4399 0.95 0.946 +3fe2a2b449360887 0.58 0.582 +3fd15ccaa94921a0 0.27 0.271 +3fe7a3778340b675 0.74 0.739 +3fef6dfb478b4f5c 0.98 0.982 +3fb037ada9f3a798 0.06 0.063 +3fec1a1c6492745c 0.88 0.878 +3fe6872fa85bfcec 0.7 0.704 +3fdf71ab8695b924 0.49 0.491 +3fe427c7278c86c6 0.63 0.63 +3fc90dea9baa8f28 0.2 0.196 +3fbac55a370d3928 0.1 0.105 +3fc63de759376814 0.17 0.174 +3fe5e3656b48c5e0 0.68 0.684 +3fe7ab83ebdb7ccc 0.74 0.74 +3febc58fc8af2726 0.87 0.868 +3fc32959a7388330 0.15 0.15 +3fe308082629eb43 0.59 0.595 +3fe6fcdbe076f586 0.72 0.718 +3febb2c605f0c31f 0.87 0.866 +3fe2ed81a639b558 0.59 0.591 +3feaaf46fcb6e58e 0.83 0.834 +3fe424e63be531a3 0.63 0.63 +3fdd686bf4bac492 0.46 0.459 +3fe432d0ac601bf2 0.63 0.631 +3fca1b54e62b924c 0.2 0.204 +3fe65215c8b92102 0.7 0.698 +3fd3f61a439e1940 0.31 0.312 +3fba0244e769c950 0.1 0.102 +3fde05f6b5b16310 0.47 0.469 +3fcbc8d057a50e60 0.22 0.217 +3fe5d30debe374dd 0.68 0.682 +3fc45100b45c2778 0.16 0.159 +3fdf642142dcba9a 0.49 0.49 +3feff16f505a4015 1.0 0.998 +3fefe6a6a2de00b9 1.0 0.997 +3fda05ef781b0a4e 0.41 0.407 +3fea091f659a0cf1 0.81 0.814 +3fee41fdf6ac976c 0.95 0.946 +3fbeb626dd6717f0 0.12 0.12 +3fe33a9f68dee5e0 0.6 0.601 +3fec104a67095e8f 0.88 0.877 +3fab1b59d076ee70 0.05 0.053 +3fe78a2b52319f4e 0.74 0.736 +3fd8b7e3acc930e0 0.39 0.386 +3fa7ee447c7f52c0 0.05 0.047 +3fe511703e27645f 0.66 0.658 +3fe9f0a8d2523742 0.81 0.811 +3fea2881d5b140bb 0.82 0.817 +3fbcf99633034b70 0.11 0.113 +3fc583dc64e6bbc4 0.17 0.168 +3fee47265851aa16 0.95 0.946 +3fdb6f710cfdaffc 0.43 0.429 +3fec1a05e4f6ac88 0.88 0.878 +3fe6a0c3e4045789 0.71 0.707 +3fcb84af0593c9b8 0.21 0.215 +3feb761d4f531e02 0.86 0.858 +3fde74a427bd9264 0.48 0.476 +3fd46bfc2ead1db6 0.32 0.319 +3fd18710322fe2c8 0.27 0.274 +3feb2fec95de1b05 0.85 0.85 +3fe9eba35accbc72 0.81 0.81 +3fd1712014aec1c6 0.27 0.273 +3fd0f65df4e8cbd8 0.27 0.265 +3fec3fa7c8c868ca 0.88 0.883 +3fa2e3cdba822000 0.04 0.037 +3fd45b25d7669604 0.32 0.318 +3fdbbf7bcc4db82c 0.43 0.434 +3fe709aeb5b6b741 0.72 0.72 +3fe427cf20b0a23c 0.63 0.63 +3fefb0f20cb6a26e 0.99 0.99 +3fef0ef650771fc7 0.97 0.971 +3fe37469cbd4bc57 0.61 0.608 +3fec2c9b5c6fe736 0.88 0.88 +3fd2c7d917367330 0.29 0.293 +3fd99edd47240e12 0.4 0.4 +3fefcda816aac438 0.99 0.994 +3fde74951e146978 0.48 0.476 +3fd5778aa42730ec 0.34 0.335 +3fa015f1044db8f0 0.03 0.031 +3fdc0b5f9723421a 0.44 0.438 +3fe666972801860f 0.7 0.7 +3f933ab765a8f740 0.02 0.019 +3fe9647566b5a2c2 0.79 0.794 +3fe31ecf8bea0624 0.6 0.598 +3fe6c65ff0fd6c3f 0.71 0.712 +3fe069d838a54d69 0.51 0.513 +3fce2e406d91f074 0.24 0.236 +3fbd73dd7b2c6750 0.12 0.115 +3fec71783494ed26 0.89 0.889 +3fe1a89fed8b1cf2 0.55 0.552 +3fbf001449519ad8 0.12 0.121 +3fa49ff38c9ae520 0.04 0.04 +3fd1009cf3e0f1d0 0.27 0.266 +3fe5068b2aee1dcb 0.66 0.657 +3fef1c729c6c2a1e 0.97 0.972 +3fd3ae936f69b2d0 0.31 0.308 +3fe633037be900f5 0.69 0.694 +3fd369a06dd7a158 0.3 0.303 +3f73eb0fcff55f00 0.0 0.005 +3fe84121b3269a54 0.76 0.758 +3fe7ccf6d1db385e 0.74 0.744 +3fdc713bc0be90e0 0.44 0.444 +3fd68bf867063866 0.35 0.352 +3fed9716f08b1063 0.92 0.925 +3f88e406ec700a80 0.01 0.012 +3fe8cec7fd479360 0.78 0.775 +3fe66117da340d96 0.7 0.699 +3fd0e603be7936ac 0.26 0.264 +3fc24a1d40a7c7f0 0.14 0.143 +3fec0d6fa3d448e4 0.88 0.877 +3fe26f4cf37ddb56 0.58 0.576 +3fcf0b71620859c4 0.24 0.243 +3fd4b57606d6d306 0.32 0.324 +3fec44df4cb2d82a 0.88 0.883 +3feff51b0240a327 1.0 0.999 +3feecc5a11562f39 0.96 0.962 +3fc8b7979a595bc4 0.19 0.193 +3fe3fe6a4781d1dd 0.62 0.625 +3fef828fc95ef925 0.98 0.985 +3fe97b24e3eaea23 0.8 0.796 +3fe7470d028996c0 0.73 0.727 +3feff947f7a0e8f5 1.0 0.999 +3fc1f553ec7724bc 0.14 0.14 +3fd8ee13df0e5060 0.39 0.39 +3fd43ae1c4e0c97c 0.32 0.316 +3f9e7f0073de2360 0.03 0.03 +3fe9dc2f903406ee 0.81 0.808 +3fc1e444ebf7c0c0 0.14 0.14 +3f825fb5832caf40 0.01 0.009 +3fe61b701c92d673 0.69 0.691 +3fe73732c48ccf0d 0.73 0.725 +3fed0e9579c3e315 0.91 0.908 +3fbe069b59a39668 0.12 0.117 +3fb7e72fe288ae58 0.09 0.093 +3fec06c928311988 0.88 0.876 +3fc219b04b042f54 0.14 0.141 +3fb63cc4798a0de8 0.09 0.087 +3fdf11d69ec5cd14 0.49 0.485 +3fd527df2b4d6e1c 0.33 0.331 +3fe7d790b7bffb5a 0.75 0.745 +3fd911275a1d7740 0.39 0.392 +3fe1897afd61e6eb 0.55 0.548 +3f9328cded5b84e0 0.02 0.019 +3fe3af47b8520312 0.62 0.615 +3fd2346c37c48f6e 0.28 0.284 +3fe785fbaa49810c 0.74 0.735 +3fc2be5dbd05d4b8 0.15 0.146 +3fe97c7b79302e9d 0.8 0.796 +3fbd8eead7f490a0 0.12 0.115 +3fe6747ce52cd98a 0.7 0.702 +3fb9939eb92a67a0 0.1 0.1 +3fd6900695e1e3be 0.35 0.353 +3fe39c73a6fc2458 0.61 0.613 +3fe0e6cc5ff0e349 0.53 0.528 +3fd75c7f1be2018a 0.37 0.365 +3fe3f51f58df9191 0.62 0.624 +3fd3bd0aa106f434 0.31 0.308 +3fd6c4bb907be898 0.36 0.356 +3fe2a2e836f28c4f 0.58 0.582 +3fd40cf7acd57cd0 0.31 0.313 +3fe1195af06d3344 0.53 0.534 +3fe7dc9dbdd17a58 0.75 0.746 +3fd039049fef8f82 0.25 0.253 +3fd7596f75494e9a 0.36 0.365 +3fc664250557b058 0.17 0.175 +3f8587e70545d180 0.01 0.011 +3fe4e2fb29a68d78 0.65 0.653 +3fee31088d046386 0.94 0.943 +3fe5bdf6ac916e97 0.68 0.679 +3fdc9085d5fb4360 0.45 0.446 +3fe07c694d1bef34 0.52 0.515 +3fe37046a97c614d 0.61 0.607 +3f7a070fc0324180 0.01 0.006 +3fef2afbc59507ca 0.97 0.974 +3fec7f3d1b490240 0.89 0.891 +3f9f291d4dc27440 0.03 0.03 +3fdf624def956592 0.49 0.49 +3fe2ea6695b06faa 0.59 0.591 +3fe0f3c0234d8939 0.53 0.53 +3fd16872dbddb604 0.27 0.272 +3fd428f5cff3d276 0.32 0.315 +3fef58f66be35e2c 0.98 0.98 +3fef78ef9b732a4d 0.98 0.984 +3fe7ba5deb8ff34a 0.74 0.741 +3fd1ea23839c6e14 0.28 0.28 +3fecd691f09ea905 0.9 0.901 +3fcaed2054020fd4 0.21 0.21 +3fe0d87e4b6d9157 0.53 0.526 +3fccd4441c49a5d8 0.23 0.225 +3fde8ab19f36738e 0.48 0.477 +3fd4767275aed0d6 0.32 0.32 +3fe432850bd246ff 0.63 0.631 +3fd7aa374bf8da20 0.37 0.37 +3fe168abf245511b 0.54 0.544 +3fe871ac7513ea73 0.76 0.764 +3fe32a08b7cd4e97 0.6 0.599 +3fe436dabdf7ed5c 0.63 0.632 +3fea92240f660014 0.83 0.83 +3fe7b3877ac2077b 0.74 0.741 +3fd9f2f2bfa565f0 0.41 0.405 +3feaad787818ce4c 0.83 0.834 +3fe0cf482c550b86 0.53 0.525 +3fccc0812b7767a8 0.22 0.225 +3fe7e42f8aa51526 0.75 0.747 +3fef15d48b5a5f02 0.97 0.971 +3fe194aada40f5ff 0.55 0.549 +3fd9947ab4ce79f8 0.4 0.4 +3fdd787aa4fdf5e4 0.46 0.46 +3faeba8c7f7abf50 0.06 0.06 +3feb9c07d4bf8bfd 0.86 0.863 +3fef366a485a5d38 0.98 0.975 +3fdec198b186a2fe 0.48 0.481 +3fddbc481543099c 0.46 0.465 +3fd30cbddb08782e 0.3 0.298 +3fef43e15b21abd3 0.98 0.977 +3fca641602eaa604 0.21 0.206 +3fecf5ad6f6db909 0.9 0.905 +3fed03781df40683 0.91 0.907 +3fe29fb69440e751 0.58 0.582 +3fc6d4f7eb26e218 0.18 0.178 +3fe2f5acc3682248 0.59 0.592 +3fe6acf49636e441 0.71 0.709 +3fd4a7e3a2b6e020 0.32 0.323 +3fe9535dd6a4b70c 0.79 0.791 +3fe44c7abc72ccbd 0.63 0.634 +3fdb3c524d491620 0.43 0.426 +3fc092d48a7085b4 0.13 0.129 +3fc2ac7c673c2c8c 0.15 0.146 +3f840ecd4a603880 0.01 0.01 +3fdb76cf91cfd6e8 0.43 0.429 +3fe18908e12f41fa 0.55 0.548 +3fc852cfe1e12eb4 0.19 0.19 +3fc019c7f172b4f4 0.13 0.126 +3fef0ecfdf96a2e9 0.97 0.971 +3fe164a5715a6c13 0.54 0.544 +3fe2ff7262657004 0.59 0.594 +3fa1b6c1ffff3830 0.03 0.035 +3fc6c4b48a25988c 0.18 0.178 +3fe079e9eb940868 0.51 0.515 +3fd338933f95a802 0.3 0.3 +3fb67da30e38d6d8 0.09 0.088 +3fddc44d26215bcc 0.47 0.465 +3fc87da6abff5950 0.19 0.191 +3fe6147e952e4a80 0.69 0.69 +3fe8977fddcef09e 0.77 0.768 +3fe5abfb2bf0155b 0.68 0.677 +3fecc21993e6c21a 0.9 0.899 +3fd89604cd9efec4 0.38 0.384 +3fd17f48976a0546 0.27 0.273 +3fddbd1105c4cb72 0.46 0.465 +3fe0943151a16b8f 0.52 0.518 +3fefade285cb0940 0.99 0.99 +3fef84a6d25ab853 0.98 0.985 +3fefa04be59f2cf3 0.99 0.988 +3fe360f85491de2e 0.61 0.606 +3feae192e1652159 0.84 0.84 +3feda02bdc7b1566 0.93 0.926 +3fd778a414da4484 0.37 0.367 +3fbc1a186f79de40 0.11 0.11 +3fd6f1a239fa02ce 0.36 0.358 +3fef80c356873723 0.98 0.984 +3fee913999d2159a 0.96 0.955 +3fdb2a9cbd76b230 0.42 0.424 +3fe86b0b23cc9c1f 0.76 0.763 +3fe84cb0f5dba415 0.76 0.759 +3fe72b1d8accd7c6 0.72 0.724 +3fefde2245943466 1.0 0.996 +3fe4d336271239ff 0.65 0.651 +3fe451cacd6e6d46 0.63 0.635 +3fccee92bb9d8780 0.23 0.226 +3fedaba1a230ddfa 0.93 0.927 +3fe30e547aa6bca1 0.6 0.595 +3fe0f0b412015c0d 0.53 0.529 +3fe7cff3775f53d0 0.74 0.744 +3feaa5b35d7b48b8 0.83 0.833 +3f9673ab134aecc0 0.02 0.022 +3feea02af5129c40 0.96 0.957 +3fd3159001181a26 0.3 0.298 +3fef0c29f0bbb714 0.97 0.97 +3fe0dd3dd05d898e 0.53 0.527 +3fe05a3dd061b6bc 0.51 0.511 +3fb2141186428d40 0.07 0.071 +3fd33db86bd48a8e 0.3 0.301 +3fdef0dcebc76d12 0.48 0.483 +3fdbc61960806804 0.43 0.434 +3fc5a3a31b674038 0.17 0.169 +3fd323bf88291fa2 0.3 0.299 +3fe1606048a9f856 0.54 0.543 +3fd7a0c453bb0930 0.37 0.369 +3fe437546c19ecf8 0.63 0.632 +3fe0f88a7e434c44 0.53 0.53 +3fe9c7c4bb2ddd68 0.81 0.806 +3feba57712d97bcb 0.86 0.864 +3fd4316731f64cfa 0.32 0.316 +3fd7f26f20b4dba4 0.37 0.374 +3fccbdcba92a0bf0 0.22 0.225 +3fdf6832fb68cfc4 0.49 0.491 +3fe713f1937df20d 0.72 0.721 +3feeb6ae73726a22 0.96 0.96 +3fcedde6aa8f61c4 0.24 0.241 +3fbf32538a9411a0 0.12 0.122 +3fe9cbf54ce272bf 0.81 0.806 +3fe17e32f1a956af 0.55 0.547 +3fcfbd41c13da8bc 0.25 0.248 +3fde30b8f50cbdda 0.47 0.472 +3fa24b9850d27620 0.04 0.036 +3fe70496225f9e2d 0.72 0.719 +3fd756450ab0d5c0 0.36 0.365 +3fba620928528418 0.1 0.103 +3fd4d02fad4f35a2 0.33 0.325 +3fd05f8700c7b7c8 0.26 0.256 +3fe6099db041ea5e 0.69 0.689 +3fe19d393f614b42 0.55 0.55 +3feb356de36e36a1 0.85 0.85 +3fa6a6604572d6f0 0.04 0.044 +3fe3172ea8ce0d34 0.6 0.597 +3f8b4a8fea374000 0.01 0.013 +3f8499e1012dd700 0.01 0.01 +3fed11b5b6f0c63e 0.91 0.908 +3fdfb8f056e234a6 0.5 0.496 +3fef75cd5ec286aa 0.98 0.983 +3fb3c9d0da691b50 0.08 0.077 +3fef2f31520b7d4f 0.97 0.975 +3fca81194fa92d68 0.21 0.207 +3f9c9cf0631fc060 0.03 0.028 +3fcbef165f78d544 0.22 0.218 +3fe2fd14028549c2 0.59 0.593 +3fb53b879326c918 0.08 0.083 +3fee86a84504f610 0.95 0.954 +3fe4419c18829c22 0.63 0.633 +3fc638936a4c2da4 0.17 0.174 +3fce6ccf7817f6d8 0.24 0.238 +3fe7a110dad51b46 0.74 0.738 +3febdca79a33dd07 0.87 0.871 +3fe0e74957cb41dc 0.53 0.528 +3fe1e729958318c2 0.56 0.559 +3fe4d372eb2f7ecb 0.65 0.651 +3fd787e7d69da76c 0.37 0.368 +3fc0d3c7efb6954c 0.13 0.131 +3fdf137ae961a542 0.49 0.486 +3fe49fdc9e72bdd5 0.64 0.645 +3fe237119872d792 0.57 0.569 +3fe678e57cb66eaa 0.7 0.702 +3fee27832e09c305 0.94 0.942 +3fcd1438b30f5604 0.23 0.227 +3fe966d9571b6c0e 0.79 0.794 +3fe8e9385d0df8aa 0.78 0.778 +3fcc444cd417f320 0.22 0.221 +3fda2c4e1504367e 0.41 0.409 +3fd5c71aa6d63f4a 0.34 0.34 +3feb38c0fcf26ae9 0.85 0.851 +3fc31d5b2f38ff78 0.15 0.149 +3fdea7224f81f6a0 0.48 0.479 +3f957154bb9a3000 0.02 0.021 +3fdacc1b6f586042 0.42 0.419 +3fe945eb4931c80a 0.79 0.79 +3fec21c98c40c1f7 0.88 0.879 +3fd6f268f9453696 0.36 0.359 +3fe5640a71127b9e 0.67 0.668 +3fed4b86068a4a07 0.92 0.915 +3fe45ba6c21bf018 0.64 0.636 +3fdc0b1ad3a5bcee 0.44 0.438 +3fc1f2f987c41d8c 0.14 0.14 +3fdadb60b9caf9d0 0.42 0.42 +3fc4cdf4b9fa7fc0 0.16 0.163 +3fd42c9447a0f1c0 0.32 0.315 +3fd188878caca07e 0.27 0.274 +3fe446b74b5ba9eb 0.63 0.634 +3fd4ec02be060c06 0.33 0.327 +3fe04abdb33a537e 0.51 0.509 +3fe9145427baf4b5 0.78 0.784 +3fd8007a0207fed2 0.38 0.375 +3fe4738832257674 0.64 0.639 +3fed689b5e21a0b1 0.92 0.919 +3fdd42dac2ab7a7e 0.46 0.457 +3fd846d767a29430 0.38 0.379 +3fdf10bdf0d6da5c 0.49 0.485 +3fd116b8ee848d4a 0.27 0.267 +3fdfd60d0a8a49b6 0.5 0.497 +3fdd21100941c55c 0.46 0.455 +3fe1de7339358f34 0.56 0.558 +3feed29afc9bf103 0.96 0.963 +3fc81b325b9a39c0 0.19 0.188 +3fec2fc56b1fbd7b 0.88 0.881 +3fe51619722b0ba5 0.66 0.659 +3fe5c7e977a53c22 0.68 0.681 +3fe047314357760b 0.51 0.509 +3fe3748ea5b86a56 0.61 0.608 +3fd3f9b1425cedd2 0.31 0.312 +3fede9be9d1d234b 0.93 0.935 +3fc152190e45dce0 0.14 0.135 +3fea650a1a56dd6b 0.82 0.825 +3fd1225ab00a2c06 0.27 0.268 +3fe0c66808635de7 0.52 0.524 +3fecd2d01043e084 0.9 0.901 +3fee63b90af75117 0.95 0.95 +3feef43bff4e2533 0.97 0.967 +3fc6609d05b757e8 0.17 0.175 +3fe51881537f29a8 0.66 0.659 +3fe205f29223a10b 0.56 0.563 +3fec30102c264d13 0.88 0.881 +3fce012bbe127730 0.23 0.234 +3fe63a13011198d8 0.69 0.695 +3fd37a39313bcaf6 0.3 0.304 +3f90559cc9d95780 0.02 0.016 +3fcc5a0c464d853c 0.22 0.221 +3fb372c20b9aaca0 0.08 0.076 +3fe710da5ee4b75a 0.72 0.721 +3fe7013adaf3887e 0.72 0.719 +3fd549466ee98a3e 0.33 0.333 +3fef92cbe4cf1154 0.99 0.987 +3fd8e317b315597e 0.39 0.389 +3fe66e724f77c816 0.7 0.701 +3fe1bf28a6d06cdb 0.55 0.555 +3fc39b9b8559b12c 0.15 0.153 +3fe6f5571fc350e1 0.72 0.717 +3fd3a7caa7e02ac6 0.31 0.307 +3fe933066d888ea7 0.79 0.787 +3fdb28cf86b9e10a 0.42 0.424 +3fdccbec814e098e 0.45 0.45 +3fe26ca11d36a359 0.58 0.576 +3fc1a9dff80948fc 0.14 0.138 +3fb74980677594f8 0.09 0.091 +3fe161f2fdf91bf5 0.54 0.543 +3fe0daa62c322af8 0.53 0.527 +3fca626dce8a30f4 0.21 0.206 +3feea9ba812981f7 0.96 0.958 +3fefedbb3580c71a 1.0 0.998 +3fd7b88e8ef13adc 0.37 0.371 +3feaccbfeae2c3f8 0.84 0.837 +3fefb8b6d5301f77 0.99 0.991 +3f986d919ad25e60 0.02 0.024 +3fc6a4b7a5edef98 0.18 0.177 +3fdf9607b0a5aa50 0.49 0.494 +3fd6b1ea21ff3a06 0.35 0.355 +3fc91c62cde77d30 0.2 0.196 +3fe335bebd795643 0.6 0.6 +3fe6c0d25dff3cf6 0.71 0.711 +3fb0ac5015781a78 0.07 0.065 +3fe929c7f7649185 0.79 0.786 +3fe23bca6ddc5aa6 0.57 0.57 +3fdc183908b0a31a 0.44 0.439 +3fdc1a8041eb3ff2 0.44 0.439 +3fe6b4021df6a535 0.71 0.709 +3fe00d926854bbd6 0.5 0.502 +3fe7861497ff2bfd 0.74 0.735 +3fc293aefdbd11c4 0.15 0.145 +3fcdb02e4dfb3910 0.23 0.232 +3fe42c349b8961f6 0.63 0.63 +3fc2888d6663e730 0.14 0.145 +3fdb623faa79977e 0.43 0.428 +3f96d7e865dce580 0.02 0.022 +3fd71e4d30b9763c 0.36 0.361 +3fd080dd36540138 0.26 0.258 +3fd4dbaa50ef1d32 0.33 0.326 +3fd20bca6b41e02a 0.28 0.282 +3fe0c1988ff35bda 0.52 0.524 +3fe1dd4c31cb984a 0.56 0.558 +3fb96a6497f773f0 0.1 0.099 +3fe33604589c0784 0.6 0.6 +3feeef12cf2668c6 0.97 0.967 +3fa3ede03fbab860 0.04 0.039 +3fe884fe2826427c 0.77 0.766 +3fa165807cf68970 0.03 0.034 +3fe55c51f44dc748 0.67 0.668 +3fe06ec219e168c2 0.51 0.514 +3fd8e682f5df3bce 0.39 0.389 +3fda4ff8a5a84070 0.41 0.411 +3f4344069652f400 0.0 0.001 +3fe7474f7db9e300 0.73 0.727 +3fd8d44dd1b0bb82 0.39 0.388 +3fde1b8d6c8e6a7e 0.47 0.47 +3fabec256f82dc50 0.05 0.055 +3fe29a8e0f99f79b 0.58 0.581 +3fc3de38ed1d9938 0.16 0.155 +3fb805161a589040 0.09 0.094 +3fe4f8d4c8025499 0.66 0.655 +3fe47f183cd20058 0.64 0.641 +3fe14c16edc0f117 0.54 0.541 +3fa683adafac06a0 0.04 0.044 +3feffe257dcedf6a 1.0 1.0 +3fe99f3b133fca67 0.8 0.801 +3fe6846ede13c964 0.7 0.704 +3fd7baca0c105458 0.37 0.371 +3fd060acf53c070c 0.26 0.256 +3fcde7c89298253c 0.23 0.234 +3feafc619c070c6e 0.84 0.843 +3fd950ec7139997a 0.4 0.396 +3fcb33966eba17ec 0.21 0.213 +3fc6c38036d29ff8 0.18 0.178 +3fe22374bb358ff4 0.57 0.567 +3fd3c88856c3fa46 0.31 0.309 +3fe9f62a8272ac40 0.81 0.811 +3fdaa8c05660d780 0.42 0.417 +3fa2689cea742350 0.04 0.036 +3fe2260105a2e117 0.57 0.567 +3fc0c64a6e1ca8cc 0.13 0.131 +3fdbd4e043a363d8 0.43 0.435 +3fe39ebde3b570ea 0.61 0.613 +3fc778b64284f038 0.18 0.183 +3fe70651fca1c830 0.72 0.72 +3fea3c516ca34e2f 0.82 0.82 +3f9518b821041140 0.02 0.021 +3fd7b0bf48759bee 0.37 0.37 +3fe9ff9ca42c5df5 0.81 0.812 +3fcd594ab27907e0 0.23 0.229 +3fdd5e1ef9f7b110 0.46 0.459 +3fc5ab04c18cea0c 0.17 0.169 +3fec3bf53dd874cc 0.88 0.882 +3fd365f255e85656 0.3 0.303 +3fcb8b34bbdc1920 0.22 0.215 +3fef5914d881d81b 0.98 0.98 +3fe9aaf4751beea3 0.8 0.802 +3fe548fe820e6718 0.67 0.665 +3fe7bd87d112ae87 0.74 0.742 +3fd1ea279ded90da 0.28 0.28 +3fc5293156cc70b8 0.17 0.165 +3f1609366f02a000 0.0 0.0 +3fb85da94959eed8 0.1 0.095 +3fefef56aa55ff21 1.0 0.998 +3fe3a9dd163169bd 0.61 0.614 +3fd3279040dadb0a 0.3 0.299 +3fb342f03e250558 0.08 0.075 +3f93d3750f23a800 0.02 0.019 +3fef83ab7f49b064 0.98 0.985 +3fc1696ecf033fd0 0.14 0.136 +3fe460e93dae1015 0.64 0.637 +3fd1f951b74a9aec 0.28 0.281 +3fc0448c6c22065c 0.13 0.127 +3fa9089009ad6730 0.05 0.049 +3fc4964c46ade118 0.16 0.161 +3fdd5645cc522ff0 0.46 0.458 +3fd94d54bc32f09a 0.4 0.395 +3fd0457e9402d0ce 0.25 0.254 +3fd07eee2549d138 0.26 0.258 +3fd63bf226da472e 0.35 0.347 +3feee56687fcdc0c 0.97 0.966 +3fde4e6fdfb37d6e 0.47 0.474 +3fe26fd40d788365 0.58 0.576 +3fb3ee77d0c5c8a0 0.08 0.078 +3fa1a302a3b5b2e0 0.03 0.034 +3fe5554328ac1712 0.67 0.667 +3fd055a8dfa1db12 0.26 0.255 +3fe6c6575c40644f 0.71 0.712 +3fde6faa73240b2c 0.48 0.476 +3fec6bb1b8e009bd 0.89 0.888 +3fc1eae56f27dde0 0.14 0.14 +3fe2d1bea8310cf7 0.59 0.588 +3fe4ed481fca8564 0.65 0.654 +3fecf418939ed441 0.9 0.905 +3fe61a42de581c57 0.69 0.691 +3fea75d6385f1a98 0.83 0.827 +3fe39d10565e5735 0.61 0.613 +3f9964a9f1b74940 0.02 0.025 +3fe2a41513abcc39 0.58 0.583 +3fe39adf3a09e97f 0.61 0.613 +3fc0554af2ba7ee0 0.13 0.128 +3fd92362f848ec50 0.39 0.393 +3fe73cb77a5ee45b 0.73 0.726 +3fec7107e5002d17 0.89 0.889 +3fd289071a5d7e88 0.29 0.29 +3fe33de610928325 0.6 0.601 +3fc0d7b4a52ef3b0 0.13 0.132 +3fe11dacd078abda 0.53 0.535 +3fd64bd49ca93030 0.35 0.348 +3fe01825c61c0ba2 0.5 0.503 +3fd0700fbc307fa6 0.26 0.257 +3fe33cd861c466bb 0.6 0.601 +3fdad47b1cb91980 0.42 0.419 +3fa0450c37575470 0.03 0.032 +3fe235d1d82d97ed 0.57 0.569 +3fb388fd04e8f8b0 0.08 0.076 +3fca8c307392f5bc 0.21 0.207 +3fee4c01177000b5 0.95 0.947 +3fe4a1cb356c8606 0.64 0.645 +3fdb7b8d6b1514f0 0.43 0.429 +3fd11079569bbdb0 0.27 0.267 +3fe2a55444f3c5f4 0.58 0.583 +3fd0556babfd71bc 0.26 0.255 +3fdb1c779b91b00a 0.42 0.424 +3fdaeead9846b95a 0.42 0.421 +3fb86ece1cf20350 0.1 0.095 +3fa7a1fa7637d540 0.05 0.046 +3fe85db255dce16f 0.76 0.761 +3f6cb2b20c9c3600 0.0 0.004 +3f893bdda1f47240 0.01 0.012 +3fefd9863ffc61dd 1.0 0.995 +3fd731763ef2fe1a 0.36 0.362 +3fd4c19fe31b4da4 0.32 0.324 +3fda41748b3c9a58 0.41 0.41 +3fefc7c168f9c6f2 0.99 0.993 +3fdf2c03964b9240 0.49 0.487 +3fe8453f9d6205e0 0.76 0.758 +3fda63c6520832c8 0.41 0.412 +3fe9275ac9b00398 0.79 0.786 +3fe4f5681a4785d1 0.65 0.655 +3fc32907c6c316b0 0.15 0.15 +3fe71f8dc783b1f1 0.72 0.723 +3fec64f6c475a3f2 0.89 0.887 +3fd29f834b3215d8 0.29 0.291 +3fbd50d0053d6e50 0.11 0.115 +3fe9bd18da191fa4 0.8 0.804 +3fd9b6d79602e124 0.4 0.402 +3fd6c81781f69e62 0.36 0.356 +3fd0dc074168572a 0.26 0.263 +3fc9c2bb31077ee4 0.2 0.201 +3fd42d4887d9f2cc 0.32 0.315 +3fe1dbe60acfe03b 0.56 0.558 +3fce048212b790b8 0.23 0.235 +3fdc263368b9881c 0.44 0.44 +3fcba845820777a4 0.22 0.216 +3fcbc5a64c8bdc18 0.22 0.217 +3fe0cec892734c86 0.53 0.525 +3fcf67fd17431ef8 0.25 0.245 +3fd5f9d18887af68 0.34 0.343 +3fb6998ec15e21f8 0.09 0.088 +3fee44f07500ec91 0.95 0.946 +3fec0437c828483c 0.88 0.876 +3fdcfb5355781a66 0.45 0.453 +3fca8124adcef1a0 0.21 0.207 +3fe3be804c4a453d 0.62 0.617 +3fe4aa06a57fae45 0.65 0.646 +3fedcafa779fc38a 0.93 0.931 +3fe2bd5c9773fd0e 0.59 0.586 +3fd2da7d7a82ad40 0.29 0.295 +3fec25a49c292cd8 0.88 0.88 +3fd6e80121142694 0.36 0.358 +3fe6b506726077ca 0.71 0.71 +3fef63624c924325 0.98 0.981 +3fcdc93aa6c51fd0 0.23 0.233 +3fc5cbe571a6d82c 0.17 0.17 +3fc0161f1cb9c438 0.13 0.126 +3f967abca71cd720 0.02 0.022 +3fcd524491f2cd6c 0.23 0.229 +3fdafc26b2fb5ef4 0.42 0.422 +3fe79622bd73b3e0 0.74 0.737 +3fee3374cda62e9b 0.94 0.944 +3fcb903fd6076b30 0.22 0.215 +3fe457199121038c 0.64 0.636 +3fe1b100a57e6554 0.55 0.553 +3fe02fdc2f2724c4 0.51 0.506 +3fed9cba2fbd4474 0.93 0.925 +3fdde8507e33d99a 0.47 0.467 +3fe39f28af32ce50 0.61 0.613 +3fd70b62aecd588c 0.36 0.36 +3febbb6ce1bf83b4 0.87 0.867 +3fe900ad8e2533c8 0.78 0.781 +3fe8031d9d1da56f 0.75 0.75 +3fec94e593b0cc41 0.89 0.893 +3fa9f1989b3eda10 0.05 0.051 +3fee6349846159d3 0.95 0.95 +3fce4931bfd9f894 0.24 0.237 +3fa5dcf8d00906d0 0.04 0.043 +3fbaef2aac146f30 0.11 0.105 +3fdbf6dcf807d4ee 0.44 0.437 +3fd64f347fc36e7e 0.35 0.349 +3fd4a7465830169c 0.32 0.323 +3fe2c5ef5527c716 0.59 0.587 +3fc41b45d4955a84 0.16 0.157 +3fa4c9863ad2a110 0.04 0.041 +3fe70482e3db1d58 0.72 0.719 +3fec0e083e72cb1f 0.88 0.877 +3fe14088a1e394f0 0.54 0.539 +3fbf13423ffb0140 0.12 0.121 +3fa2e12da6136d30 0.04 0.037 +3fdcfd007ceb470a 0.45 0.453 +3fe7675bc8798970 0.73 0.731 +3fbb653477c6f208 0.11 0.107 +3fef20f75a34091d 0.97 0.973 +3fec4c1c4301f396 0.88 0.884 +3fdfee9e2c864dda 0.5 0.499 +3fd098120ebb1ebe 0.26 0.259 +3fd1de2b0b6c5398 0.28 0.279 +3fec93970f4029af 0.89 0.893 +3fdc922e4bdbde60 0.45 0.446 +3fb25596399bdcf0 0.07 0.072 +3fd21e1537573720 0.28 0.283 +3f8d0dfbb742f280 0.01 0.014 +3fd1b6c2ae5cd722 0.28 0.277 +3fefedce02a43cba 1.0 0.998 +3febd6be4508c69b 0.87 0.87 +3fe9c71ad9b38796 0.81 0.806 +3fe0d36b7f8c6d17 0.53 0.526 +3fe963eb8c2eb040 0.79 0.793 +3fe73dab8d560517 0.73 0.726 +3fd0ec8c850610d8 0.26 0.264 +3fcdf685f539227c 0.23 0.234 +3fbd8300a6277d90 0.12 0.115 +3fe9ca16c84839fa 0.81 0.806 +3fec720100840110 0.89 0.889 +3fe9ad3e199f9091 0.8 0.802 +3fd47b5fb89633fe 0.32 0.32 +3fb2add3c80392a0 0.07 0.073 +3fa636d46f52e7c0 0.04 0.043 +3fe2ddd8a96912c9 0.59 0.59 +3fb6772166bb1950 0.09 0.088 +3fe4ed0f7d318c20 0.65 0.654 +3fe06d7861909231 0.51 0.513 +3fde94dc0cfa5e88 0.48 0.478 +3fda3176d835dac4 0.41 0.409 +3fe708ec7bcf4ab6 0.72 0.72 +3fe4cb84317dbd23 0.65 0.65 +3fe83d3f7d6237a1 0.76 0.757 +3fefc52864067c13 0.99 0.993 +3fef19cd0d70578b 0.97 0.972 +3fa640cc26a536a0 0.04 0.043 +3fda3b03f916b828 0.41 0.41 +3fd0369a1b6ef052 0.25 0.253 +3fec219532c04515 0.88 0.879 +3fde517999da21a0 0.47 0.474 +3fcc3784c90a84d8 0.22 0.22 +3fe67e8c8f147dcf 0.7 0.703 +3fd924ec7865a04a 0.39 0.393 +3fe5a36a914a1692 0.68 0.676 +3fdc5207d166f0ee 0.44 0.443 +3fe946392b080cf3 0.79 0.79 +3fd3a732f94cada0 0.31 0.307 +3fd4737a8f5c1ca6 0.32 0.32 +3fd3ca1b097241da 0.31 0.309 +3fd379ce5665d642 0.3 0.304 +3fd54d6cba2fca46 0.33 0.333 +3fed92cab38153dc 0.92 0.924 +3fe9dd28dc411bfa 0.81 0.808 +3fe7a5c3467e861a 0.74 0.739 +3fdc7d6db44788a0 0.45 0.445 +3fe231bbe1104f7f 0.57 0.569 +3fe83f5f3df5c447 0.76 0.758 +3fb406c87c3ead58 0.08 0.078 +3fc8827fa2e9334c 0.19 0.191 +3fe389b5f9a3edea 0.61 0.611 +3fe7b88312cb9ef0 0.74 0.741 +3feef3bd0d6a4cb6 0.97 0.967 +3fe7fc4ceb604f07 0.75 0.75 +3fb535ff86483048 0.08 0.083 +3fecef285e38fab3 0.9 0.904 +3fd3708928de7d30 0.3 0.304 +3fa005d578c0d2d0 0.03 0.031 +3fe6bd8d7a46c189 0.71 0.711 +3fe576811a90d42b 0.67 0.671 +3fe8d2e7de75ed8e 0.78 0.776 +3fb014497891bbf8 0.06 0.063 +3fed56eed0bbfada 0.92 0.917 +3fe9aa05bd6caa0b 0.8 0.802 +3fe170db282be285 0.55 0.545 +3fe8e64655914579 0.78 0.778 +3fe129ecb9d05997 0.54 0.536 +3fc988af82a157cc 0.2 0.199 +3fb154f6875e9af0 0.07 0.068 +3fd24f591fd41e94 0.29 0.286 +3fe10b13bfaa520c 0.53 0.533 +3fed5269460b9bf7 0.92 0.916 +3fcf5ce15fa1571c 0.25 0.245 +3fcb115f9860487c 0.21 0.211 +3fed4595cac81d5e 0.91 0.915 +3fe823aaca3c2ac9 0.75 0.754 +3fe7e3eb030f9912 0.75 0.747 +3fee97f4c8cbf918 0.96 0.956 +3fd6444325ac4f3e 0.35 0.348 +3f9fad2cbad6d060 0.03 0.031 +3fd57e084329ab88 0.34 0.336 +3fd8badec335e84e 0.39 0.386 +3f36a7d6a6437800 0.0 0.0 +3feaa12829071ab9 0.83 0.832 +3fc4967a6bc5aba8 0.16 0.161 +3faefa083ca341b0 0.06 0.061 +3fd41f0465a2adca 0.31 0.314 +3fddd82ade285740 0.47 0.466 +3fd18b3db47c4326 0.27 0.274 +3fee91ae086e2722 0.96 0.955 +3fed82661b2320f8 0.92 0.922 +3fd8ed7e91928d6c 0.39 0.389 +3feb2e65bae332da 0.85 0.849 +3fedb7451acaf571 0.93 0.929 +3fc5a597eba4c160 0.17 0.169 +3fe3668f54344fae 0.61 0.606 +3fe5aaeee7693a4b 0.68 0.677 +3fc70af933ffe86c 0.18 0.18 +3fe6523f5c92cca0 0.7 0.698 +3fbfb8a2efcd47f8 0.12 0.124 +3fe36dcb28f25e7f 0.61 0.607 +3fda81d396193422 0.41 0.414 +3fded76204c87c2a 0.48 0.482 +3fddf9ecefbfa5de 0.47 0.468 +3feedd03a34bc4ac 0.96 0.964 +3fe71d3b627a2357 0.72 0.722 +3fe16c6f69d9bd16 0.54 0.544 +3fe37783be454366 0.61 0.608 +3fa1e7bf6f29dc40 0.03 0.035 +3fe0bff087ba7e66 0.52 0.523 +3fe101cd6d9d51a7 0.53 0.531 +3fe752d40cfabe39 0.73 0.729 +3fe7fb8ad343f1c3 0.75 0.749 +3fe2cfdfa36628d0 0.59 0.588 +3fedf2d354521395 0.94 0.936 +3feeca08fd229ca1 0.96 0.962 +3fdb6c09232a8454 0.43 0.428 +3fed1683da12d496 0.91 0.909 +3fe7396f8e4171e8 0.73 0.726 +3fe0c08a03ef7732 0.52 0.524 +3fe5be504c582b93 0.68 0.679 +3fd3641ebb6effe2 0.3 0.303 +3fdecf3949aae840 0.48 0.481 +3fef0917d76a847d 0.97 0.97 +3fd940c181ba3682 0.39 0.395 +3f91f7aecc07dc20 0.02 0.018 +3fde7c2f6e03af4c 0.48 0.476 +3fe59193aee97e72 0.67 0.674 +3fe39ec72351cec8 0.61 0.613 +3fbcff2d3d8a38f8 0.11 0.113 +3fe7c501f25ea9af 0.74 0.743 +3fdb4fe00e36cfc8 0.43 0.427 +3fc71753b56cf768 0.18 0.18 +3feb75ddffff5f82 0.86 0.858 +3fe70959fa3c98a4 0.72 0.72 +3fbdde8735acb4d8 0.12 0.117 +3fd23d72f03ee304 0.29 0.285 +3fe185df567ab989 0.55 0.548 +3fe6a4c87864593f 0.71 0.708 +3fb70b5b1c2ac518 0.09 0.09 +3fdbba1f79ecd59a 0.43 0.433 +3fefc58f1d34d16d 0.99 0.993 +3feb30428c37d560 0.85 0.85 +3fd8d441c6e07cf8 0.39 0.388 +3fd8a44e7166984e 0.39 0.385 +3fe7fe26d8c136ae 0.75 0.75 +3fde4312917ed93e 0.47 0.473 +3fe7ac3ca71d4e77 0.74 0.74 +3fe05bb17245d3b9 0.51 0.511 +3fb90cb9e71b9a28 0.1 0.098 +3fb12de6869a2a68 0.07 0.067 +3fe106c355bada91 0.53 0.532 +3fa19e262fc61aa0 0.03 0.034 +3fbf35f1976e8428 0.12 0.122 +3fe79e4044286789 0.74 0.738 +3fe569ae097063ec 0.67 0.669 +3febbb47d0d67f4a 0.87 0.867 +3fe223f1b9d8877a 0.57 0.567 +3fd4110452c51204 0.31 0.314 +3fa098bc844c8e50 0.03 0.032 +3fef33384d2e5d29 0.98 0.975 +3fe37fd5f99d0a75 0.61 0.609 +3fe4a823bd1c713f 0.65 0.646 +3fdbdc994bdf1050 0.44 0.435 +3fd1515afaaff8f2 0.27 0.271 +3fdcce6b207be014 0.45 0.45 +3fdec659131afb66 0.48 0.481 +3fbad37a153ba650 0.1 0.105 +3fcec046f26538b4 0.24 0.24 +3fd6b672cef39cee 0.35 0.355 +3fce062d94220974 0.23 0.235 +3fe883a33d5f680c 0.77 0.766 +3fecb3978cb8ad97 0.9 0.897 +3fe5f40e1595ad5a 0.69 0.686 +3fd436a1aba46484 0.32 0.316 +3fe883410486329f 0.77 0.766 +3fc30ac95c80b3b0 0.15 0.149 +3fbd87f8a08ba8b8 0.12 0.115 +3fe4e5bcc75ad7e8 0.65 0.653 +3fe0e5776a316254 0.53 0.528 +3feeb29973d6d78c 0.96 0.959 +3fcd18c3617eef14 0.23 0.227 +3fa537b49b5e6550 0.04 0.041 +3fdd515f342174aa 0.46 0.458 +3fce903550fff418 0.24 0.239 +3fef94b8e79ce735 0.99 0.987 +3fec31d9c702a2d1 0.88 0.881 +3fdcf34631eee76c 0.45 0.452 +3fededfa5aa769ca 0.94 0.935 +3fecf8576a4d5638 0.91 0.905 +3feeda893721502a 0.96 0.964 +3fc4d6b2355f9580 0.16 0.163 +3fd0fdf65e38258e 0.27 0.266 +3fdf6985f34bcb4e 0.49 0.491 +3fb5dbe926246c48 0.09 0.085 +3fec6c1ee7be1b8b 0.89 0.888 +3fe873e7de5f003d 0.76 0.764 +3fe26ae56cf01ddc 0.58 0.576 +3f73888a45fc3380 0.0 0.005 +3fe96e77701f97f8 0.79 0.795 +3fe50a72eb943ea4 0.66 0.658 +3fee07c65de95d2d 0.94 0.938 +3f78020143023380 0.01 0.006 +3fe4c52a6ee85b40 0.65 0.649 +3fc529804d11b880 0.17 0.165 +3fddff299414efee 0.47 0.469 +3fd8075313fe4946 0.38 0.375 +3fe998e51e704c00 0.8 0.8 +3fecab56ee02cd0c 0.9 0.896 +3fc73e0b4cc6e70c 0.18 0.182 +3fe1af435926c0a6 0.55 0.553 +3fe0f751b54c0ea1 0.53 0.53 +3fc3adabdb2fbfbc 0.15 0.154 +3fee4025095ebc2b 0.95 0.945 +3fb4d42d5cc756e8 0.08 0.081 +3fdf83e39220d3bc 0.49 0.492 +3fe981a9e2a42f4b 0.8 0.797 +3fb2b968d07638a0 0.07 0.073 +3fcce966a24061a0 0.23 0.226 +3fc1937f6a2d4330 0.14 0.137 +3fdc91def61faa74 0.45 0.446 +3fe9811f8e8848a3 0.8 0.797 +3f8cff736f00b400 0.01 0.014 +3fdc047cfef8f174 0.44 0.438 +3fe79f31c5f8aef8 0.74 0.738 +3fca91772901c61c 0.21 0.208 +3fc919f4116dc5a0 0.2 0.196 +3fe2fca79d0321b2 0.59 0.593 +3fec5ef4333c9a7d 0.89 0.887 +3fd7995b53a61134 0.37 0.369 +3fe364556f0ef3a9 0.61 0.606 +3fcf0913d7f1e0ac 0.24 0.242 +3fc4561671c0a994 0.16 0.159 +3fef1becbec80e49 0.97 0.972 +3f9afc03618ec100 0.03 0.026 +3fe138bc646e1fa1 0.54 0.538 +3fe5c76846c34be8 0.68 0.681 +3fdb43673c790a8a 0.43 0.426 +3fdf20888b233308 0.49 0.486 +3fc7e70555527474 0.19 0.187 +3fd0367c1750c488 0.25 0.253 +3fb4ff4bcf253b38 0.08 0.082 +3fed4d3bd0310d79 0.92 0.916 +3fe8a5b62c78c36a 0.77 0.77 +3fc113875f94a034 0.13 0.133 +3fc9e7c954f8c204 0.2 0.202 +3fd8b1e0a875a016 0.39 0.386 +3feb3aeaa79ffe51 0.85 0.851 +3fab2e4d421d8d20 0.05 0.053 +3fee8ed8d6203f61 0.95 0.955 +3fe9dcb8810fe026 0.81 0.808 +3feed6d05f16e627 0.96 0.964 +3fe1f456ac928092 0.56 0.561 +3fd08d3774ce4530 0.26 0.259 +3fb6be53789f31d8 0.09 0.089 +3fed061667abc835 0.91 0.907 +3f9f80c1d5465780 0.03 0.031 +3fe5e9cb8a01f69e 0.68 0.685 +3fd66157d3257f28 0.35 0.35 +3fa15c23f14828a0 0.03 0.034 +3fe3a1e33f9a66e0 0.61 0.614 +3fc9d612e4971190 0.2 0.202 +3fd5acbc1f1f8492 0.34 0.339 +3feaa33c392ceb88 0.83 0.832 +3fd7e035084a12e4 0.37 0.373 +3fce7c0f466990f0 0.24 0.238 +3fcc3a49a45767e4 0.22 0.221 +3fe7aef20ac242b1 0.74 0.74 +3fe6fbe1555cea88 0.72 0.718 +3fe1f01885bf430c 0.56 0.561 +3fdf92f4190dc152 0.49 0.493 +3fe72d1e87b40b34 0.72 0.724 +3fe7ead80bbc5daf 0.75 0.747 +3fa3159462e64ed0 0.04 0.037 +3f86087fccaac040 0.01 0.011 +3fd270c8ecbd9b20 0.29 0.288 +3fe1949de3dcdb95 0.55 0.549 +3fd9f6f28b336fb6 0.41 0.406 +3fe95910efdaefaa 0.79 0.792 +3fe076ee884868b5 0.51 0.515 +3fefafe348828918 0.99 0.99 +3fe053a580522ef6 0.51 0.51 +3fdde9d7f0c17058 0.47 0.467 +3fce1f285f7866e0 0.24 0.235 +3fe6c84e8ca87be7 0.71 0.712 +3fe3d2e92523bfd4 0.62 0.619 +3fd2d35c4578a9a0 0.29 0.294 +3fd37613acf8c4d2 0.3 0.304 +3fe7bf6574a684c0 0.74 0.742 +3fe15e4236853a00 0.54 0.543 +3fc53eb22c19a330 0.17 0.166 +3fef131449aa5496 0.97 0.971 +3fe55b7771d0abe9 0.67 0.667 +3fe9f02e0bdb6aad 0.81 0.811 +3fe8bfeec3a730d0 0.77 0.773 +3feed1af28b2bcb1 0.96 0.963 +3fa04625ec3a48f0 0.03 0.032 +3fef1a5076df641e 0.97 0.972 +3fdf3839cd572570 0.49 0.488 +3fe3fe57ca8515bb 0.62 0.625 +3fc716921e30d0f4 0.18 0.18 +3fd690ce9a704c98 0.35 0.353 +3fe88d73c6bb3b2c 0.77 0.767 +3fec6241910a5900 0.89 0.887 +3fd521815484c5ec 0.33 0.33 +3fca59a496353314 0.21 0.206 +3fe744202845431a 0.73 0.727 +3fe671eff0948bd7 0.7 0.701 +3fded0b1ab35918c 0.48 0.481 +3fe98c5102fd6668 0.8 0.798 +3fbd51ea6fefe508 0.11 0.115 +3feb6a45bbffccd0 0.86 0.857 +3fe59b152ae2efd7 0.68 0.675 +3fe432022616f538 0.63 0.631 +3fdd7ae3872dadf2 0.46 0.461 +3fe60a9461aa8282 0.69 0.689 +3fe83e9d3fcb5f7c 0.76 0.758 +3fc510d0d182a050 0.16 0.165 +3fd05b087b14a5ae 0.26 0.256 +3fe813968ebedd76 0.75 0.752 +3feb29c22f17cc71 0.85 0.849 +3fcba8e1142fe9a0 0.22 0.216 +3fdcece4643a5a60 0.45 0.452 +3feabf4494ebfe0e 0.84 0.836 +3fd8d253ac52964a 0.39 0.388 +3fe2cc33e467e881 0.59 0.587 +3fdf798813579a18 0.49 0.492 +3fe7120aca7c6e85 0.72 0.721 +3fe742a843bbceae 0.73 0.727 +3fe50c6e32e0f35f 0.66 0.658 +3fa17e3813ab4d70 0.03 0.034 +3fbd9c29cbc3d608 0.12 0.116 +3fd43670fa15f880 0.32 0.316 +3fec5923ecd199ad 0.89 0.886 +3fdd569f293b2c90 0.46 0.458 +3feba6fe3b25bafc 0.86 0.864 +3fce91971fb313fc 0.24 0.239 +3fc46126ac7d746c 0.16 0.159 +3fedc959aea982dd 0.93 0.931 +3fec53964b4a13f2 0.89 0.885 +3fd428889ecb799e 0.31 0.315 +3fc6475fe176669c 0.17 0.174 +3fb7e2a0ebec8f58 0.09 0.093 +3fc59f4ad6864478 0.17 0.169 +3fe34894126227ab 0.6 0.603 +3fa9a9a6a9900c40 0.05 0.05 +3fe7648c86d88c5c 0.73 0.731 +3fe55394f2fdad5f 0.67 0.666 +3fd4033a21bd4c86 0.31 0.313 +3fca7e7ee04e3838 0.21 0.207 +3fe1dc016c5cf4c6 0.56 0.558 +3fe4ded15a12ee06 0.65 0.652 +3fa03d058f546420 0.03 0.032 +3fe03493690ad86e 0.51 0.506 +3fe0186ab6b01168 0.5 0.503 +3fb4174aa5172b20 0.08 0.078 +3fe45f7ebe244e2f 0.64 0.637 +3fe8cb8a7c6e1e06 0.77 0.775 +3fdbc38304c35362 0.43 0.434 +3fe9ba8ee8ae91fa 0.8 0.804 +3fdbcaa5dfc14b02 0.43 0.434 +3fedb4f00622cb4b 0.93 0.928 +3febac2d2033a27f 0.86 0.865 +3fda0bfc65678c34 0.41 0.407 +3fef7c047b18fd49 0.98 0.984 +3faa4a2ade6d6c20 0.05 0.051 +3fe6d0fa436a6839 0.71 0.713 +3fe9154f04a1a70c 0.78 0.784 +3fa306b1c7ce48a0 0.04 0.037 +3fe6987c68dec708 0.71 0.706 +3feee9b348cb419a 0.97 0.966 +3fc2212501551be8 0.14 0.142 +3fa06920e0d92750 0.03 0.032 +3fbde6a3e06a8800 0.12 0.117 +3fcbe5336ece4e58 0.22 0.218 +3fe399d2caffeb7b 0.61 0.613 +3fdf8698312c1baa 0.49 0.493 +3fe20a40f4b63c2d 0.56 0.564 +3fddab92a64a66f2 0.46 0.464 +3fd9e1e81ad0e190 0.4 0.404 +3fe76e4c3af5fb59 0.73 0.732 +3fe2aecfce4fd004 0.58 0.584 +3fe365d528d1e14f 0.61 0.606 +3fcdcb0ec163d4c8 0.23 0.233 +3fe4b7ba331ded2a 0.65 0.647 +3fc7b4df8a8ce6b8 0.19 0.185 +3fe1335476274ab6 0.54 0.538 +3f88df27c47c25c0 0.01 0.012 +3fdb667abd0562e0 0.43 0.428 +3fc2dace1795b6a8 0.15 0.147 +3fd54cfc4e08dd1c 0.33 0.333 +3fd608ada045530c 0.34 0.344 +3fb5665b8124f150 0.08 0.084 +3fdb4069c255898c 0.43 0.426 +3fd9f7d217902478 0.41 0.406 +3fc5e576a50f4fcc 0.17 0.171 +3feb4a1c1ea9fdcd 0.85 0.853 +3fe6eba5cc0b2b3e 0.72 0.716 +3fe5e1845d5ee3f4 0.68 0.684 +3fc5a1801e6e088c 0.17 0.169 +3fd67218cbb8fb7e 0.35 0.351 +3fe62717463e0c8d 0.69 0.692 +3fe391e27cd8d30e 0.61 0.612 +3fe02a66bd9d8efe 0.51 0.505 +3fd67ed50fe7671e 0.35 0.351 +3fae78d2b139d4f0 0.06 0.06 +3fec45d97a6155dd 0.88 0.884 +3fe6bb8e5b9c30e0 0.71 0.71 +3fe2c24905163745 0.59 0.586 +3fcd986ac7095034 0.23 0.231 +3fe69fe0ff64ba65 0.71 0.707 +3febae86aa329690 0.87 0.865 +3fed2c4c6e800028 0.91 0.912 +3fe4dbb8858864f1 0.65 0.652 +3fefb52d0bd44b5a 0.99 0.991 +3fecb85b75dcbe2a 0.9 0.898 +3feb0b5cb2551e0b 0.85 0.845 +3fbdeb3fc3c8e178 0.12 0.117 +3fe506a2f44cc8e2 0.66 0.657 +3fd7bb056f9a624c 0.37 0.371 +3fec21d11d131796 0.88 0.879 +3fecc1ddb0f001f8 0.9 0.899 +3fd6954ad645d7fa 0.35 0.353 +3feb1942f96d89ab 0.85 0.847 +3fe67cffb6e5d6a5 0.7 0.703 +3fec07ba7f15f51c 0.88 0.876 +3fd681943f110eae 0.35 0.352 +3fbfb6357ec93930 0.12 0.124 +3fde0128794743dc 0.47 0.469 +3fe80a1f04987eb5 0.75 0.751 +3f7253867f305a80 0.0 0.004 +3feb8d4028a1c8aa 0.86 0.861 +3fe4f4cb2421cf5e 0.65 0.655 +3face62e4e153630 0.06 0.056 +3fb8cf29e6ddeb40 0.1 0.097 +3fd915e3bd7f4ac6 0.39 0.392 +3fed0f6e0ad060d5 0.91 0.908 +3fcfa338d0623860 0.25 0.247 +3fec7f10f2e710b9 0.89 0.891 +3feb441d8b6263ec 0.85 0.852 +3feec779cea8a309 0.96 0.962 +3fe0d557035a0ae0 0.53 0.526 +3fe925eb83dd0f5a 0.79 0.786 +3fee71ba9020dddd 0.95 0.951 +3feef127790bb4db 0.97 0.967 +3fe63138100bdc9a 0.69 0.694 +3febd2e16edb5dee 0.87 0.869 +3fdfc543a497a822 0.5 0.496 +3fce8016a3aaec8c 0.24 0.238 +3fe249c5dea05c64 0.57 0.572 +3fdab6abf94d06be 0.42 0.417 +3fd240ac6915e860 0.29 0.285 +3f97704d0e2c83e0 0.02 0.023 +3fd99674138d11d4 0.4 0.4 +3fa4801e3260eb00 0.04 0.04 +3fd3d61ea8275b68 0.31 0.31 +3fe0163e0a0211ed 0.5 0.503 +3fd6d6fe6f94dc88 0.36 0.357 +3fe54cb1286178ad 0.67 0.666 +3fd791f74a0b6618 0.37 0.368 +3fefbdec8d7422b6 0.99 0.992 +3feeff10fa980714 0.97 0.969 +3fe4b3607f535c06 0.65 0.647 +3fe7940e4b167021 0.74 0.737 +3fd2dae92a36bd46 0.29 0.295 +3fde1bbd22a919bc 0.47 0.47 +3fcf0638fc17927c 0.24 0.242 +3fca6aaaec685198 0.21 0.206 +3fc2e50f418c508c 0.15 0.148 +3fb91a0df6f01078 0.1 0.098 +3fecd7d6f33495aa 0.9 0.901 +3fe08164b3d4b801 0.52 0.516 +3fde40b4507235f4 0.47 0.473 +3feb5ba502d2e84f 0.85 0.855 +3fad40bf7c89d470 0.06 0.057 +3f90c4cc4db99ec0 0.02 0.016 +3fc6a957c159227c 0.18 0.177 +3feaca5db8293a0b 0.84 0.837 +3fe013e59663fa18 0.5 0.502 +3fc11a9f685856f4 0.13 0.134 +3fd19bb76f4ab176 0.28 0.275 +3fe5673ae9f84a48 0.67 0.669 +3fe02d03e2e9f6f2 0.51 0.505 +3fea35198964951d 0.82 0.819 +3fe990255bd85b5d 0.8 0.799 +3febe3ee46783ca0 0.87 0.872 +3fe6dfe96a41c7d1 0.71 0.715 +3fea48d6e40fdf2c 0.82 0.821 +3fb6bdc341a09998 0.09 0.089 +3fe36777051a0bd4 0.61 0.606 +3fed13590900436d 0.91 0.909 +3fc96b3d3f0f6f80 0.2 0.199 +3feae67e74ceb100 0.84 0.841 +3fd2ca996e6bfac4 0.29 0.294 +3fe39d230752e2e9 0.61 0.613 +3fedb0b50465205f 0.93 0.928 +3fde01f469230190 0.47 0.469 +3fd49cd7c8045c92 0.32 0.322 +3fc7059367f5df1c 0.18 0.18 +3fec8c86aab867ab 0.89 0.892 +3fd0f36ae46f2ad8 0.26 0.265 +3feecbe2a88df45c 0.96 0.962 +3fdcd8e25d594942 0.45 0.451 +3fef3f3610044900 0.98 0.976 +3fe4d79eeadd3e94 0.65 0.651 +3fd0dcab8cd1e4da 0.26 0.263 +3fe94ea49561b585 0.79 0.791 +3fe9da23a81f1fca 0.81 0.808 +3fe1602e3c0628a7 0.54 0.543 +3fdafea6d23efdd6 0.42 0.422 +3fde07f28eb42290 0.47 0.469 +3fe64de833331ed0 0.7 0.697 +3fe2e3bda53ab89a 0.59 0.59 +3fe334178e973e86 0.6 0.6 +3febf7d29b9d0cbd 0.87 0.874 +3fecb9b3638aa9f5 0.9 0.898 +3feba143c4a597eb 0.86 0.863 +3febe9c1dc319829 0.87 0.872 +3f9a4f5e7e3dbe00 0.03 0.026 +3fbea94b4774c8f0 0.12 0.12 +3fecfb3394adafab 0.91 0.906 +3fe02a90be1f0b95 0.51 0.505 +3fe2255187e54e1d 0.57 0.567 +3fe8f15b9cb3225b 0.78 0.779 +3fd4df75082cdb66 0.33 0.326 +3fe724f663d509a3 0.72 0.723 +3fdc126d78e4cc1c 0.44 0.439 +3fd23a005feb101c 0.28 0.285 +3fd475f4dc08f5e6 0.32 0.32 +3fb0e335fb9a8318 0.07 0.066 +3fd8333d8c9e433a 0.38 0.378 +3fb2c4bcce92dc68 0.07 0.073 +3fc84f49ec68e28c 0.19 0.19 +3fe281f71e4a02cb 0.58 0.578 +3fe84a5ddfec2580 0.76 0.759 +3fef8a06ed167da1 0.99 0.986 +3fd20890be310682 0.28 0.282 +3fd0a7f9e523381a 0.26 0.26 +3fd1093f24dc4020 0.27 0.266 +3fdfc9d4e0d4be26 0.5 0.497 +3fecf56b7b70e172 0.9 0.905 +3feb49ac53102e90 0.85 0.853 +3fd38e4a9948407c 0.31 0.306 +3f79b1f2ffa1ab80 0.01 0.006 +3fde18d3d4eff768 0.47 0.47 +3fe4fc3cc6cbddc3 0.66 0.656 +3fe77fa74870dddd 0.73 0.734 +3fe0fa7a9d67d845 0.53 0.531 +3fe5e46645747bfa 0.68 0.684 +3fc3a4e1e066fc08 0.15 0.153 +3fe6e65e134dc0ef 0.72 0.716 +3fe8d4dc671af11a 0.78 0.776 +3feefa612732b559 0.97 0.968 +3fcdc94e277a0c94 0.23 0.233 +3fe85be54f9841b0 0.76 0.761 +3fea4fc96972e3d9 0.82 0.822 +3fe994cfcada046d 0.8 0.799 +3fead2a2be4c13ad 0.84 0.838 +3fc47938b4420cc8 0.16 0.16 +3fd662ebf483f152 0.35 0.35 +3fe81bd5a8f6f4aa 0.75 0.753 +3fe73be6d86ee11e 0.73 0.726 +3fc2a8cd5762dc80 0.15 0.146 +3fe5e262861014ad 0.68 0.684 +3fe7e90e905ddad1 0.75 0.747 +3fc994b85873308c 0.2 0.2 +3fe4dd2b49c1efe4 0.65 0.652 +3fe40c6da6601a9d 0.63 0.627 +3fed11344f7657d5 0.91 0.908 +3feb21e0249fcb91 0.85 0.848 +3fed0fb76792a22f 0.91 0.908 +3fb580485812da00 0.08 0.084 +3fe4f30f86a23e61 0.65 0.655 +3febdbe70c22a23e 0.87 0.871 +3fecfe5bc36ca58d 0.91 0.906 +3fef1949c7882888 0.97 0.972 +3fe13005ad1fa4a5 0.54 0.537 +3fb97b4d421e0bb8 0.1 0.1 +3fe99eff9d82f676 0.8 0.801 +3fe7309421a3f1ca 0.72 0.725 +3fbb2c1a5aa80970 0.11 0.106 +3fe276902f7eddbc 0.58 0.577 +3fdeb6fc76668a40 0.48 0.48 +3fe2b73d0d033ea4 0.58 0.585 +3fea7e063579911d 0.83 0.828 +3fde058cd42149c0 0.47 0.469 +3fd6e8a2dcee2e84 0.36 0.358 +3feaf27f659c6fe8 0.84 0.842 +3fdcae702468cc68 0.45 0.448 +3fdc6c1546970a78 0.44 0.444 +3fd82d1a276ff5a4 0.38 0.378 +3fc85af2180f0dec 0.19 0.19 +3fecdceccb3a0968 0.9 0.902 +3fc56a2266f257ec 0.17 0.167 +3fcd0e40aad02650 0.23 0.227 +3fea95c6929fed88 0.83 0.831 +3fd8c254bb663c02 0.39 0.387 +3fc61a1eae3c4114 0.17 0.173 +3fd6fbc09fe017ae 0.36 0.359 +3fdaf70b853dd7ea 0.42 0.421 +3fe85669b0a7bbc6 0.76 0.761 +3fef2789a35ed144 0.97 0.974 +3fd1c2239cb611c0 0.28 0.277 +3feb92e4393d3901 0.86 0.862 +3fd2ae7f9ba35c3e 0.29 0.292 +3fde615544f201f0 0.47 0.475 +3fc167d3302da39c 0.14 0.136 +3f9c9d72428cc7e0 0.03 0.028 +3fbe24afdf6cd660 0.12 0.118 +3feaf5d52f2d0211 0.84 0.843 +3fe1d0b1fafd1595 0.56 0.557 +3fe73079ee1c8bd9 0.72 0.725 +3fe1271dca2b9a87 0.54 0.536 +3fec9359ad6878db 0.89 0.893 +3fe0094117186c5b 0.5 0.501 +3fe07062455f9657 0.51 0.514 +3fb6322656660000 0.09 0.087 +3febc2154edd5391 0.87 0.867 +3fc2576c8e06c76c 0.14 0.143 +3f9cbb8ddeb524a0 0.03 0.028 +3fe3043205ae0364 0.59 0.594 +3f92c6a0cb881ca0 0.02 0.018 +3fe5686084cb7712 0.67 0.669 +3fed49763532152b 0.92 0.915 +3fd915f9dbf9d758 0.39 0.392 +3fe892919ab67e9f 0.77 0.768 +3fecf34f7e258d39 0.9 0.905 +3fbaa9ff1e7208f8 0.1 0.104 +3fecfd9644768c71 0.91 0.906 +3fe6d79a1d4f1533 0.71 0.714 +3fce08eaa716877c 0.23 0.235 +3feed5ecfc53a2b1 0.96 0.964 +3fc280caf3b53fd0 0.14 0.145 +3fe43a1aa6c2ccda 0.63 0.632 +3fe45fdbc5057726 0.64 0.637 +3fd5a668e29a6ef2 0.34 0.338 +3fee05cb801ef0f7 0.94 0.938 +3fde7a6d1080249a 0.48 0.476 +3fefbb68eb3f8d7b 0.99 0.992 +3fe56157397e9f73 0.67 0.668 +3fe7a32844a59bc6 0.74 0.739 +3f81f9c86d9c8f00 0.01 0.009 +3fe319d8b6fa5744 0.6 0.597 +3f956cb4d9522ea0 0.02 0.021 +3fe3b72319e54ad5 0.62 0.616 +3fed4c98c608641e 0.92 0.916 +3fe09fab8d160e6a 0.52 0.519 +3fc9a93cc85ccc28 0.2 0.2 +3fec33946102f11a 0.88 0.881 +3fd3ed6e28920714 0.31 0.311 +3fe7aadb93451528 0.74 0.74 +3fe470167f58d88a 0.64 0.639 +3fea397f1801bbe9 0.82 0.82 +3fb8b3ff4d2c1750 0.1 0.096 +3fec0bda3591a1eb 0.88 0.876 +3f8a3fbf28a5a480 0.01 0.013 +3fea7182fcdc4ec9 0.83 0.826 +3fea0b0141aa495c 0.81 0.814 +3fd607182a8c8782 0.34 0.344 +3fba1f1373978d08 0.1 0.102 +3feb6bc2aa2b965d 0.86 0.857 +3fecb225655afff3 0.9 0.897 +3fed417508121b9b 0.91 0.914 +3fe3bfc65c40dde5 0.62 0.617 +3fc0eb4e6548907c 0.13 0.132 +3fde6fbd4878ff46 0.48 0.476 +3fd83d81156da418 0.38 0.379 +3feadd20b00f6950 0.84 0.839 +3fbd43eea1f2d038 0.11 0.114 +3fe0cbb4ec0f045a 0.52 0.525 +3feea5e7f7c5c84d 0.96 0.958 +3fd5b1fe4f99feca 0.34 0.339 +3fd99ed7bf8b06c0 0.4 0.4 +3fedaf94936aef85 0.93 0.928 +3fd3d2264b02748a 0.31 0.31 +3fc067e97eea6ebc 0.13 0.128 +3fa29985d542d660 0.04 0.036 +3fb0431526096600 0.06 0.064 +3fe209c98e0ac974 0.56 0.564 +3fe6eeb518960bdf 0.72 0.717 +3fb8ab2d0e3734e8 0.1 0.096 +3fe51b071216d7eb 0.66 0.66 +3fa94fe90b289e60 0.05 0.049 +3fc9494e1e40fe98 0.2 0.198 +3fa2a34f2ac83900 0.04 0.036 +3fd6043a9d416f96 0.34 0.344 +3fa75fae38d24590 0.05 0.046 +3f9cdd90cb593c00 0.03 0.028 +3fdbab4b770ce620 0.43 0.432 +3fe13149ab190960 0.54 0.537 +3fc5b820b962fd74 0.17 0.17 +3fe4f36df7255dac 0.65 0.655 +3fea7e11aca8bc41 0.83 0.828 +3fe0af64d591a08f 0.52 0.521 +3fe2086c1f7cf4cd 0.56 0.564 +3fd71a5ce6ac075a 0.36 0.361 +3fcb38b3178daccc 0.21 0.213 +3fd9407a6977c8d6 0.39 0.395 +3fe669cd43958fba 0.7 0.7 +3fb2a9dbe0ce4410 0.07 0.073 +3fed6001c042be93 0.92 0.918 +3fe6f3b6a8dc4b2f 0.72 0.717 +3fea0a21c8fb9b8d 0.81 0.814 +3fe870f8783ccee1 0.76 0.764 +3fd2df645a0030ba 0.29 0.295 +3faf50495698de00 0.06 0.061 +3fa908fe5e4dafb0 0.05 0.049 +3fdbf0f9bd06d808 0.44 0.437 +3fd063a8e29fd060 0.26 0.256 +3fe84daaae5f441e 0.76 0.759 +3fd1c532f793f4b2 0.28 0.278 +3fd751bba97c4524 0.36 0.364 +3fc16d4377d61798 0.14 0.136 +3fe1aab178837c38 0.55 0.552 +3fecb370098299e2 0.9 0.897 +3fe67e15da73df67 0.7 0.703 +3fe14cb717afb333 0.54 0.541 +3fd1148f9650bfb6 0.27 0.267 +3fda63eb3090a16e 0.41 0.412 +3fb6a64c0064be28 0.09 0.088 +3fe6053e0b9b45f6 0.69 0.688 +3fd5d05125f1df92 0.34 0.341 +3fe70872825219a3 0.72 0.72 +3fea973e217b9b7f 0.83 0.831 +3fec082cf4b1d2b6 0.88 0.876 +3fd990d269c9b68a 0.4 0.399 +3fc7b1bca87b2954 0.19 0.185 +3fe94c30386bac8f 0.79 0.791 +3fd80a636e04cc9a 0.38 0.376 +3f7491b774b5f600 0.01 0.005 +3fcc39860e237858 0.22 0.221 +3feaee08d537b25b 0.84 0.842 +3fe320f67a9e7695 0.6 0.598 +3fbd7b18d28cab20 0.12 0.115 +3fca39ea08f28e68 0.2 0.205 +3fd2ed7114e0c488 0.3 0.296 +3fd6069e1c2c8e98 0.34 0.344 +3fd42d0ce2966e86 0.32 0.315 +3fef60a839b8812b 0.98 0.981 +3fe31bee8e5436d6 0.6 0.597 +3fe3973dffebfaba 0.61 0.612 +3fc90bdacd2eefb0 0.2 0.196 +3fe450bb40df58b9 0.63 0.635 +3fee7955135fd4ca 0.95 0.952 +3fe8fa1a37dc8f03 0.78 0.781 +3fea7858de3e15e5 0.83 0.827 +3fd520480fdb1b82 0.33 0.33 +3fee90017b6061d0 0.96 0.955 +3fda7342f1a15b68 0.41 0.413 +3fd9f455787b9caa 0.41 0.406 +3fd31082d2cfb720 0.3 0.298 +3fd3d9e3dd00f788 0.31 0.31 +3fc09deec1880b44 0.13 0.13 +3fe02799a4cf3c5d 0.5 0.505 +3fe9575229445050 0.79 0.792 +3fd754db48c46cdc 0.36 0.365 +3fd37d90e3329070 0.3 0.305 +3fc721e29220c9dc 0.18 0.181 +3fbf32de98253c38 0.12 0.122 +3fe6bb8be1b6ca0a 0.71 0.71 +3fd11a7b30e1ccde 0.27 0.267 +3f9a071decc53b20 0.03 0.025 +3fe6854e3a7ae205 0.7 0.704 +3fdafc229d5f67e6 0.42 0.422 +3fed3b6089bbd8d2 0.91 0.913 +3fe8d8bab6340d67 0.78 0.776 +3fc70e874a38f4d0 0.18 0.18 +3fd7507f4827ef48 0.36 0.364 +3fc0d167372dbb50 0.13 0.131 +3fb0bde9dc5df778 0.07 0.065 +3fa9fdf56458c1d0 0.05 0.051 +3fe338f175f2d9c6 0.6 0.601 +3fea7028111c0a82 0.83 0.826 +3fde5f5fbb7d5be8 0.47 0.475 +3fb5bdf3df0091e0 0.08 0.085 +3fe3e5d6fb690d0a 0.62 0.622 +3fb24b6bc83bb098 0.07 0.071 +3feaf9872654651c 0.84 0.843 +3fe59065c7e3ad91 0.67 0.674 +3fbc80a5f4ad16e8 0.11 0.111 +3fc57e1ef1050adc 0.17 0.168 +3fdaa8b0414f5c4a 0.42 0.417 +3fe73530b7bdcb3c 0.73 0.725 +3fe239a9be133b52 0.57 0.57 +3fcabee06be8e410 0.21 0.209 +3f945cd89b10a240 0.02 0.02 +3fcd78920646d1ec 0.23 0.23 +3fedf086b265e33f 0.94 0.936 +3fd27b1ce1ecb2c8 0.29 0.289 +3fd2ec92fb0bfc50 0.3 0.296 +3fe8b980b90d462b 0.77 0.773 +3fecd1f1dedf28aa 0.9 0.901 +3fcbd62f84aa208c 0.22 0.217 +3fd4a48f7494dda0 0.32 0.323 +3fcff92c11a2f414 0.25 0.25 +3fe38f62115f545a 0.61 0.611 +3fb76f3e9694a8e0 0.09 0.092 +3fbfba38a748ba30 0.12 0.124 +3fed0f0583579b95 0.91 0.908 +3feae88a172fb064 0.84 0.841 +3f781299a0e14c00 0.01 0.006 +3f8d0ebf34d3ab40 0.01 0.014 +3fd00c412e02628a 0.25 0.251 +3fe75a20be8f6da1 0.73 0.73 +3f79ee94633c5300 0.01 0.006 +3fd684a8bb2c73fa 0.35 0.352 +3fe2387e2e7246e5 0.57 0.569 +3fe9bf2d8ed9005a 0.8 0.805 +3feee16d1ef1954c 0.97 0.965 +3fd70944281f7c64 0.36 0.36 +3fe639bf11cac82d 0.69 0.695 +3fe0b930958b3b7d 0.52 0.523 +3fe3c8349e4a9e3b 0.62 0.618 +3fe865a7fc32bd84 0.76 0.762 +3fe5babfc1e796b7 0.68 0.679 +3fedc0f834ce2a53 0.93 0.93 +3fb709cd94373328 0.09 0.09 +3fe68ed63d164714 0.7 0.705 +3fe12f9c4844b162 0.54 0.537 +3fd168838e04b2ec 0.27 0.272 +3f983361e29088c0 0.02 0.024 +3fef5350c2b8e1ed 0.98 0.979 +3fb68c6ad9296a00 0.09 0.088 +3fde725b93dabf68 0.48 0.476 +3fcf72fdaef675ec 0.25 0.246 +3fd298a531154cda 0.29 0.291 +3fee36402c5822d5 0.94 0.944 +3fe72fcbcfbe17bc 0.72 0.725 +3fd4b4943f0ecf56 0.32 0.324 +3fbdcc55042bd038 0.12 0.116 +3fd2b7f655df3b72 0.29 0.292 +3fc621cf4695b0e0 0.17 0.173 +3feed947a21b4a99 0.96 0.964 +3fd4836bb1da55d2 0.32 0.321 +3fe3e320dd30ad46 0.62 0.621 +3fdf64c82c65e206 0.49 0.491 +3fe71cb72796511e 0.72 0.722 +3fe5e58b1c0ea099 0.68 0.684 +3fd2aee3ca1f6e4c 0.29 0.292 +3fe02d1f9d99a3a1 0.51 0.506 +3fed85a33c87a14d 0.92 0.923 +3fdacb2342dba7aa 0.42 0.419 +3f79f1a73a02f080 0.01 0.006 +3fdb1f1d18e751b2 0.42 0.424 +3fb2213cc4749998 0.07 0.071 +3fc823150f2d1db8 0.19 0.189 +3fea8f65cb1e7b1f 0.83 0.83 +3fddaa9358ec9f7e 0.46 0.464 +3fee7d6416b722f9 0.95 0.953 +3fe46c5d4eaa2288 0.64 0.638 +3fc379836c923778 0.15 0.152 +3fef17be4941074c 0.97 0.972 +3fe2523ba1c6c317 0.57 0.573 +3fdb0afe1f92c728 0.42 0.423 +3fedb3a08d933aaf 0.93 0.928 +3feab7eddfc5f45e 0.83 0.835 +3fe143b103748052 0.54 0.54 +3fdfb7979cd89306 0.5 0.496 +3fe26d700f1512b0 0.58 0.576 +3fdad974a3c776da 0.42 0.42 +3fdcd896a52abfd8 0.45 0.451 +3fd19209be77d1c6 0.27 0.275 +3fdbf54e3fceaf76 0.44 0.437 +3fc55989c0dd840c 0.17 0.167 +3fee06da1fe7a459 0.94 0.938 +3fc00ca34f13d850 0.13 0.125 +3feeaee30294e96a 0.96 0.959 +3feddd4022559f71 0.93 0.933 +3fbc748c41bbc190 0.11 0.111 +3fe6e197d1f9708d 0.72 0.715 +3fd773fec83e9c70 0.37 0.366 +3fac76f3de4d4840 0.06 0.056 +3fdaf4c370a8f838 0.42 0.421 +3fd9ff4063dc8a92 0.41 0.406 +3fef171fab78f38a 0.97 0.972 +3fb19ff40fe3d9d0 0.07 0.069 +3fcc4b4a862220ac 0.22 0.221 +3fe85562dfdd20de 0.76 0.76 +3fefe4d0f62bebe1 1.0 0.997 +3feaa475c8a7941c 0.83 0.833 +3fecf6e8b4bf1348 0.91 0.905 +3fea4a02d170ba27 0.82 0.822 +3fb68be11a37acf8 0.09 0.088 +3feba23825c03a6c 0.86 0.864 +3fd0e33cb5146d3e 0.26 0.264 +3fe99e0e6aadce91 0.8 0.801 +3fee85b3fc3e55f8 0.95 0.954 +3fe1ca2cb12791c8 0.56 0.556 +3fc4ac6fb3549784 0.16 0.162 +3fb4cafc362c20a0 0.08 0.081 +3fef88bbb06feb05 0.99 0.985 +3fe7232f92d4bc18 0.72 0.723 +3fde6ee6eab4e28a 0.48 0.476 +3fdee7ba604f2378 0.48 0.483 +3fe36deee7b36a49 0.61 0.607 +3fee8c45c6a407cf 0.95 0.955 +3fd7993dc450814a 0.37 0.369 +3fe4f887915bf41d 0.66 0.655 +3fd0083c7f10a6c0 0.25 0.251 +3fe4a63899b4141e 0.65 0.645 +3fc1c970ed0d4968 0.14 0.139 +3fc95a7258b49868 0.2 0.198 +3fc0d5a490fca8e8 0.13 0.132 +3fd23486bc6a072e 0.28 0.284 +3fe1d7fd0ef2de9f 0.56 0.558 +3fe7299af456e181 0.72 0.724 +3fe0f4853a2da51c 0.53 0.53 +3fdff45df6e9c74a 0.5 0.499 +3fb4a7d6e1cca760 0.08 0.081 +3fee1373fd1eadc7 0.94 0.94 +3fe7b43ca45a8bb0 0.74 0.741 +3fc526dd1b5eaec0 0.17 0.165 +3fee91d9b48c27d6 0.96 0.955 +3fc125827af98d0c 0.13 0.134 +3fdebdf1ab8a52c2 0.48 0.48 +3febc364b19d9f2c 0.87 0.868 +3fdceee80234b090 0.45 0.452 +3fe97a07ff54fc7f 0.8 0.796 +3fccd9cbdddc5a94 0.23 0.225 +3fe87a4b269881a5 0.76 0.765 +3fddc9984fbde86c 0.47 0.465 +3fec47500ed1de49 0.88 0.884 +3fee571d040f4d0b 0.95 0.948 +3fc251d5b627b8a0 0.14 0.143 +3fd34e4f7cd5f0ca 0.3 0.302 +3feeb1c0e6a81511 0.96 0.959 +3fc6f44d12dc77c4 0.18 0.179 +3fd3e998088b4cac 0.31 0.311 +3fe6c8d62ce5530c 0.71 0.712 +3fbc7bba258d2ac8 0.11 0.111 +3fe62e4bff13fc83 0.69 0.693 +3fe58de3b5382010 0.67 0.674 +3fe68f57885c297b 0.7 0.705 +3febe58f82ae0620 0.87 0.872 +3fd9bec859e081f8 0.4 0.402 +3fc28ac061c9f7f4 0.14 0.145 +3fe1b9ba44555a98 0.55 0.554 +3fe0f838b872f35b 0.53 0.53 +3fe4211ffca8e474 0.63 0.629 +3fd7e8081a944644 0.37 0.374 +3fef896d45a31645 0.99 0.986 +3fd14e59448dadc6 0.27 0.27 +3fc0a955341df2bc 0.13 0.13 +3fd6d224d0500154 0.36 0.357 +3fb587cdbb13b120 0.08 0.084 +3febaf4ed91edee8 0.87 0.865 +3fe4941cdc23be7d 0.64 0.643 +3f92dd44238c7a60 0.02 0.018 +3fe07ccba79e08ba 0.52 0.515 +3fdaf69b8f90ef54 0.42 0.421 +3fe536d5756031a6 0.66 0.663 +3fec2accc29dc5f8 0.88 0.88 +3feba9c677e73478 0.86 0.864 +3febd153086e08d5 0.87 0.869 +3fe92568e7e4089a 0.79 0.786 +3fe3330a2dd5c3c6 0.6 0.6 +3fd65097140ae9c0 0.35 0.349 +3fe1ac2694c4136c 0.55 0.552 +3fe82637a730ad6d 0.75 0.755 +3fccf80e9f009e68 0.23 0.226 +3fd83f3043a81f4e 0.38 0.379 +3fbad7b9cc01df20 0.1 0.105 +3fe0c62342b54cd6 0.52 0.524 +3fbc993c0b101e68 0.11 0.112 +3fcb326efc00f790 0.21 0.212 +3feb85cdbf7997cc 0.86 0.86 +3fe94dea6efa9aac 0.79 0.791 +3fdf460e6f7292a0 0.49 0.489 +3fe033243b8741f5 0.51 0.506 +3fc04934996ec498 0.13 0.127 +3fd506ad1059e4a0 0.33 0.329 +3fed4164dd54f706 0.91 0.914 +3fbce5c91dedd360 0.11 0.113 +3fd62469eee7156a 0.35 0.346 +3fcc2383216c1ac8 0.22 0.22 +3fd3ea0bf7e71bde 0.31 0.311 +3fc93d5a527c3100 0.2 0.197 +3fdcfe73a6c43e62 0.45 0.453 +3fe5e8ad62b7fc96 0.68 0.685 +3fe5aeb0a938227b 0.68 0.678 +3fd84608c6f29012 0.38 0.379 +3fe662b3d7fc2cb4 0.7 0.7 +3fe6acd082e0daeb 0.71 0.709 +3fd4361adfeda7e2 0.32 0.316 +3fd0730a3ca47a06 0.26 0.257 +3fef7697d6e3fe50 0.98 0.983 +3feb13451fbd2378 0.85 0.846 +3fe26ee3a8b15e06 0.58 0.576 +3fe7eaec034ba342 0.75 0.747 +3f777bbbe64f1f80 0.01 0.006 +3fe992ae635f9d17 0.8 0.799 +3fb3ccac7405d300 0.08 0.077 +3fec7ad5eceff116 0.89 0.89 +3fe6ff4b19d6dc8c 0.72 0.719 +3fee643a029c7edf 0.95 0.95 +3fe5762b86d9cdad 0.67 0.671 +3fe9ac67616ed54b 0.8 0.802 +3fb3dcab2dd8c9c0 0.08 0.078 +3feb88e47f4c71ae 0.86 0.86 +3fcf851fcc17b118 0.25 0.246 +3fe5ce23033f536a 0.68 0.681 +3fdb6e7ec5b8832e 0.43 0.429 +3fe8a92b0e8c465b 0.77 0.771 +3f9a48939d7ff800 0.03 0.026 +3fe51a953c4876b2 0.66 0.659 +3fe04f20cfa0dc82 0.51 0.51 +3fd4280abbb24c48 0.31 0.315 +3fe1199e13e8924d 0.53 0.534 +3fe1c6db50e5d421 0.56 0.556 +3fcf55e9321a00a0 0.24 0.245 +3fe8ed342ba1d110 0.78 0.779 +3fe56229ab8b074b 0.67 0.668 +3fe0a89f9faba0d9 0.52 0.521 +3fbdf38d03768cd8 0.12 0.117 +3fec97c0934d3271 0.89 0.894 +3fe9399bfa8c0711 0.79 0.788 +3fcf4796e2b42450 0.24 0.244 +3fefd148f5a1e4ee 0.99 0.994 +3fd67a3084f6c4aa 0.35 0.351 +3fcf2959816a7088 0.24 0.243 +3fdb5ddac3743dce 0.43 0.428 +3fdec1a748f40360 0.48 0.481 +3fcb4c3c5d269424 0.21 0.213 +3fe13ad3932bb57b 0.54 0.538 +3fe0b71776798ad8 0.52 0.522 +3fd274a17c89948c 0.29 0.288 +3feca48011542205 0.9 0.895 +3fe9b7fa7c5483b7 0.8 0.804 +3fa7282b1d1cee40 0.05 0.045 +3fe31281c977f1e2 0.6 0.596 +3fdbb2dc59114242 0.43 0.433 +3fd10eddc5fc4b62 0.27 0.267 +3fe53da7d614fe57 0.66 0.664 +3fb4bbd73e94b398 0.08 0.081 +3fe5d257337c4d82 0.68 0.682 +3fd46ff5818f31de 0.32 0.319 +3febbe20c1ba0326 0.87 0.867 +3fac0d34d10a29b0 0.05 0.055 +3fef84946e7d1517 0.98 0.985 +3fed6469ebb7c0b2 0.92 0.919 +3fea2c86eff02621 0.82 0.818 +3fcce6ca33bef334 0.23 0.226 +3fc0fe397ed4d338 0.13 0.133 +3fe5186b25d3f844 0.66 0.659 +3f8117f13ef30780 0.01 0.008 +3feedd4c963bd06d 0.96 0.965 +3fe2cc3636eeba1a 0.59 0.587 +3fda2b78fd05df50 0.41 0.409 +3fdb6830bda15fdc 0.43 0.428 +3fe75210a0d20b58 0.73 0.729 +3fd8644f85ff6d18 0.38 0.381 +3fd34c84a6d6860a 0.3 0.302 +3fd16f998aaedb74 0.27 0.272 +3fe16d3bad8d56e1 0.54 0.545 +3fe33bfaaf9990fe 0.6 0.601 +3fdf2522782eff34 0.49 0.487 +3fe7accf430f3866 0.74 0.74 +3fe6cd4f1e5f2cce 0.71 0.713 +3fa221ccdc559690 0.04 0.035 +3fdf3be650478ec0 0.49 0.488 +3fe96cc372272885 0.79 0.795 +3fe0e6a475070e16 0.53 0.528 +3fd3365341bb6250 0.3 0.3 +3fed676222b0e781 0.92 0.919 +3fe5b57d0c43a952 0.68 0.678 +3feb8e153cdc53d6 0.86 0.861 +3fe388636824efe2 0.61 0.61 +3febc38b4c22fa4e 0.87 0.868 +3fe553cb918285c0 0.67 0.666 +3fcbec07c2a3558c 0.22 0.218 +3fe89a303aac2721 0.77 0.769 +3fe781f880585c99 0.73 0.735 +3fe474487cd6b3a4 0.64 0.639 +3fd2953a0d827772 0.29 0.29 +3fc48dd8ad0bd130 0.16 0.161 +3fe1a26f175b61b4 0.55 0.551 +3fd71a29ba50885a 0.36 0.361 +3fc734451ffa9dc8 0.18 0.181 +3fe17f6f0b20d9d7 0.55 0.547 +3fed5c7f512c3c44 0.92 0.918 +3fedc8a5f6551235 0.93 0.931 +3fecfbbde2f0e45e 0.91 0.906 +3fe25f92aecdf0f9 0.57 0.574 +3fe4cbab48b62822 0.65 0.65 +3fe6fb49c5d169b2 0.72 0.718 +3fe40a0bf88bb300 0.63 0.626 +3fd060d567532994 0.26 0.256 +3fe0519ba056d7bc 0.51 0.51 +3fdccd97aa9f7676 0.45 0.45 +3fec07fbaef8d280 0.88 0.876 +3fdbbad0b34dcc48 0.43 0.433 +3fe3ab92f02d88a1 0.61 0.615 +3fe073a44109853b 0.51 0.514 +3feab87cb8c44199 0.84 0.835 +3fce91bc3f803884 0.24 0.239 +3fce31fd12704f28 0.24 0.236 +3fed1a5716cb4a45 0.91 0.909 +3fc53478f6f1d4f4 0.17 0.166 +3fcba3b7b0429bb8 0.22 0.216 +3feed26874392542 0.96 0.963 +3fe252a0ca14b150 0.57 0.573 +3fe73ad4374b2122 0.73 0.726 +3fd8488c7f7ad7d8 0.38 0.379 +3fedf3a0d297c2a5 0.94 0.936 +3fef048e6d8b64a1 0.97 0.969 +3fde3899c1d86994 0.47 0.472 +3fd6c6a9ee81fdd2 0.36 0.356 +3fd7208b4668a090 0.36 0.361 +3fe3f8ba940fe877 0.62 0.624 +3faa4dcf2ac28310 0.05 0.051 +3fe6d445af70cf5e 0.71 0.713 +3fc6a98d7a9f9d6c 0.18 0.177 +3fdb964a03b12396 0.43 0.431 +3fe194dcb39b29e4 0.55 0.549 +3fdc5e5139f8e6fc 0.44 0.443 +3fd3b48bb3d3437e 0.31 0.308 +3fe18e5a72a9ed35 0.55 0.549 +3fc4b719a3c52f38 0.16 0.162 +3fe51c312cf074d9 0.66 0.66 +3fdc76e9f6c5ef52 0.44 0.445 +3fe44420554ee825 0.63 0.633 +3feb5fd70dcd7b0e 0.86 0.855 +3fcfc2a9a1b1e784 0.25 0.248 +3fe5a0e6492fef2e 0.68 0.676 +3fe4dbb749a3625f 0.65 0.652 +3fef206c0215066e 0.97 0.973 +3f862a01b41f7780 0.01 0.011 +3fd072c50e4ed950 0.26 0.257 +3fd45df4771dbb9e 0.32 0.318 +3fb8b118081b7050 0.1 0.096 +3fed9ecc55292b91 0.93 0.926 +3feb0a761bd7b3f7 0.85 0.845 +3fe2145ccc7e072c 0.56 0.565 +3fe17063f84decef 0.54 0.545 +3fe9fd430ac8972a 0.81 0.812 +3fea15db68f28ae3 0.82 0.815 +3fcb48288f556f1c 0.21 0.213 +3fd9ec7b0c8dbe26 0.41 0.405 +3fd158b136f56d86 0.27 0.271 +3feaedd1c6f11bd6 0.84 0.842 +3fee5cad1a29575d 0.95 0.949 +3fe4613918bead46 0.64 0.637 +3fd17bb261612444 0.27 0.273 +3fee991d36bb6672 0.96 0.956 +3fe113abb1b90a55 0.53 0.534 +3fda2f0af59d1088 0.41 0.409 +3fc1cf0ca1c803a4 0.14 0.139 +3feab0ff351e70b2 0.83 0.834 +3fc6dbabfb9319d8 0.18 0.179 +3fdbd48ff7a5ea9c 0.43 0.435 +3fe480086788b8cd 0.64 0.641 +3fdf66d558f0d89e 0.49 0.491 +3fea2d386c93c18a 0.82 0.818 +3fd0e03b9ab9adfc 0.26 0.264 +3fefa116d8afa3f9 0.99 0.988 +3fdee26cad4797c6 0.48 0.483 +3feaacb5e15bfe83 0.83 0.834 +3fe41f7748b07b64 0.63 0.629 +3f9a35ee7ec2d720 0.03 0.026 +3fe68f07812b53a0 0.7 0.705 +3fe47e2a6faa562c 0.64 0.64 +3fe3ed4f94f763e1 0.62 0.623 +3fd060715111346e 0.26 0.256 +3fd1026e301cf5be 0.27 0.266 +3fe3b7aaec366ab8 0.62 0.616 +3feafd7c74749e29 0.84 0.843 +3fd62bff6618883c 0.35 0.346 +3fd012e147d2464e 0.25 0.251 +3fe71b8d34e56f21 0.72 0.722 +3fe4c00338f91e92 0.65 0.648 +3febe0e034339a99 0.87 0.871 +3fe062858e02ac9d 0.51 0.512 +3fea5bea7e4460c1 0.82 0.824 +3fc7403808791fc4 0.18 0.182 +3fbcaf3946b77dc8 0.11 0.112 +3fd3224ec1a820cc 0.3 0.299 +3fea92fd7ef0720a 0.83 0.83 +3fd3c7a35506206a 0.31 0.309 +3feb283f6b709533 0.85 0.849 +3fd444156742f8be 0.32 0.317 +3fec4f721c4cf212 0.88 0.885 +3fd34d1e3ffa4afe 0.3 0.302 +3feec887c57e1581 0.96 0.962 +3feef7b10f16a58d 0.97 0.968 +3fd43127c379ac82 0.32 0.316 +3fe5df10679b19ac 0.68 0.683 +3fc4e5660d4632f0 0.16 0.163 +3fa3740224f9af80 0.04 0.038 +3feda581a85810b7 0.93 0.926 +3fe95fb1c36b0848 0.79 0.793 +3fe27ee8266a27e9 0.58 0.578 +3fb75d3c022c8480 0.09 0.091 +3feb40370e6b4b5b 0.85 0.852 +3fd012270374a78c 0.25 0.251 +3fc334d8ce3eaf80 0.15 0.15 +3fd757757e8c284c 0.36 0.365 +3fe94013fadbbeda 0.79 0.789 +3fdefd37e1087a98 0.48 0.484 +3fe5a6ad226ae88a 0.68 0.677 +3fe00afc2a3a3652 0.5 0.501 +3fd800ce9720d7cc 0.38 0.375 +3fdf50cf468aad36 0.49 0.489 +3fdab4b2bdae1b8c 0.42 0.417 +3fd08c5eb345b68e 0.26 0.259 +3fa91f45bbb81650 0.05 0.049 +3fb449b096b05fe8 0.08 0.079 +3fd16bd0a0cb3598 0.27 0.272 +3f7d2ee090b86180 0.01 0.007 +3fef5e409446561e 0.98 0.98 +3fe8a14e7fbb6a46 0.77 0.77 +3faf919c057cda70 0.06 0.062 +3fefec6534969a1c 1.0 0.998 +3fecab5e74cfac9a 0.9 0.896 +3fe4ab4ca2f367f1 0.65 0.646 +3fe8fd94e1ae6dd4 0.78 0.781 +3fe7bc7c2733fa39 0.74 0.742 +3fdbf4c9f3ef57b8 0.44 0.437 +3fe2c63a41f691fb 0.59 0.587 +3fe2201b79d34ca6 0.57 0.566 +3fef46b737196f25 0.98 0.977 +3fd9787de4b23cf0 0.4 0.398 +3fd3f59de0d9912e 0.31 0.312 +3fbceff245d27dc0 0.11 0.113 +3fe4bb1d4311f123 0.65 0.648 +3fef7a2a8783d2de 0.98 0.984 +3f915fa369eaf760 0.02 0.017 +3fa89ac5076b2f70 0.05 0.048 +3fa1e93d88af51d0 0.03 0.035 +3fe247899a93fe9d 0.57 0.571 +3fa0118764fca640 0.03 0.031 +3fb2b3ac47e62ac8 0.07 0.073 +3fbfb536c523cb78 0.12 0.124 +3fdec2178776b8aa 0.48 0.481 +3fe6450f6c61a10c 0.7 0.696 +3fdb465fca423aea 0.43 0.426 +3fcf3ee2dc32fd70 0.24 0.244 +3fbf7ceb855319c8 0.12 0.123 +3fd674d01e58ecee 0.35 0.351 +3fe5af1198178f1e 0.68 0.678 +3fc2f20525661060 0.15 0.148 +3fd021df0ba0a47a 0.25 0.252 +3fe986440d3aa5c9 0.8 0.798 +3fd47f96e623bcee 0.32 0.32 +3fb7bfdc4106ecf8 0.09 0.093 +3fab1e9188c84bd0 0.05 0.053 +3fe6cd25348dd11e 0.71 0.713 +3fd272738b62941a 0.29 0.288 +3fdd0d57d6af3334 0.45 0.454 +3fee3fa14697b3b1 0.95 0.945 +3fd9889a961a025a 0.4 0.399 +3fd48bc58582643a 0.32 0.321 +3fed3bb3167718c4 0.91 0.914 +3fdb4abf0ec34eb6 0.43 0.426 +3fe328bd6708cfa0 0.6 0.599 +3fe02aa1b5a2d367 0.51 0.505 +3fe4b629ee0a9cb0 0.65 0.647 +3fd475f546a8b284 0.32 0.32 +3fcd4c8aa75c3710 0.23 0.229 +3fb29714e8360e80 0.07 0.073 +3fc70984e969b5e0 0.18 0.18 +3fd1e11ca9ac565a 0.28 0.279 +3fe5dc7f8a1391b4 0.68 0.683 +3fde977e8cb971da 0.48 0.478 +3febe5b151fa72fb 0.87 0.872 +3fca2badcdccaa6c 0.2 0.204 +3fd43d297c010188 0.32 0.316 +3fdf932d5192ec44 0.49 0.493 +3fdf35b4b965cabc 0.49 0.488 +3feee02b19b22ad6 0.96 0.965 +3fe141b8c96f3cc9 0.54 0.539 +3fd85a481802f770 0.38 0.381 +3fb358131a380d78 0.08 0.076 +3fd3745d724f8c22 0.3 0.304 +3fefdc21b49c45fc 1.0 0.996 +3fe9c31c240ffd7f 0.81 0.805 +3feb85c3d3a1552f 0.86 0.86 +3fe6146db696f257 0.69 0.69 +3fd14aa60b952a8a 0.27 0.27 +3fd1023df9c23e9a 0.27 0.266 +3fe2b71b7c5f374b 0.58 0.585 +3fd46f59576496f4 0.32 0.319 +3fe81540ad3afe80 0.75 0.753 +3fe4b1173f84513d 0.65 0.647 +3fe3b0af58540f62 0.62 0.615 +3fcce2e3aee9a5b0 0.23 0.226 +3fe607af7e49df5b 0.69 0.688 +3fe44bf4a0010927 0.63 0.634 +3fc204a3b64be874 0.14 0.141 +3fd2bd0a4fdaae3c 0.29 0.293 +3fe1d4bf51964e8f 0.56 0.557 +3fd88109e82e3a4a 0.38 0.383 +3fb13dea49f356d0 0.07 0.067 +3fdb9cbf81abcbe0 0.43 0.431 +3fe1ae53f02d9aa8 0.55 0.553 +3feab4ddc3fb333e 0.83 0.835 +3fa8d97e06e04610 0.05 0.049 +3fd2ae1dc63a76aa 0.29 0.292 +3fd1cc269cffa330 0.28 0.278 +3fe55b144fc8bef0 0.67 0.667 +3fee2454cf267c1c 0.94 0.942 +3fe936b045ae1a64 0.79 0.788 +3fef4a793987d5dd 0.98 0.978 +3fdd5eea564086dc 0.46 0.459 +3fd15f5973d21520 0.27 0.271 +3fb2a35963f573b8 0.07 0.073 +3fea1dc952935d94 0.82 0.816 +3fdcc87ddc872834 0.45 0.45 +3fed4028f89a7740 0.91 0.914 +3fb86d6156b79da8 0.1 0.095 +3fdb63273d74f984 0.43 0.428 +3fb8a43774df9cb8 0.1 0.096 +3fc125d946abb550 0.13 0.134 +3fe30c60f770d7a9 0.6 0.595 +3fcee0a0416772bc 0.24 0.241 +3fce509ce198f638 0.24 0.237 +3fe2be2928bd886b 0.59 0.586 +3fe6ff510c0cf707 0.72 0.719 +3fe6bbb66fd386de 0.71 0.71 +3fe543cb57eab0ed 0.66 0.665 +3fe849079c0352fe 0.76 0.759 +3fede85b3fd40527 0.93 0.935 +3fd5720a8cd89f88 0.34 0.335 +3feaefef33f6ca39 0.84 0.842 +3fe3d82d2517ec2c 0.62 0.62 +3fd599e7e68ff854 0.34 0.338 +3fe6cd12fd44d165 0.71 0.713 +3fed19b68de3b9b1 0.91 0.909 +3fd9d586732747ba 0.4 0.404 +3f960440f0710f40 0.02 0.022 +3fe526926b292d61 0.66 0.661 +3fcbedc0a385c530 0.22 0.218 +3fdae99b0721927e 0.42 0.421 +3fe04f3a9007f5bd 0.51 0.51 +3febb64d6ff9f969 0.87 0.866 +3fb1d0cd9ec79df8 0.07 0.07 +3fde56a5d0114c44 0.47 0.474 +3fd0cfb461150708 0.26 0.263 +3fdf5a1a4c1f9eb6 0.49 0.49 +3fee3a601eb460ca 0.94 0.945 +3fd443a6e6d20788 0.32 0.317 +3fefa48fd50a9280 0.99 0.989 +3fde5e0fc9975606 0.47 0.474 +3fceefda98906a1c 0.24 0.242 +3fe5686f67dfff7d 0.67 0.669 +3fbbec7dd0c39fa0 0.11 0.109 +3fe480be3141af97 0.64 0.641 +3fe7b1fe711c1efe 0.74 0.74 +3fe56442a738c91a 0.67 0.668 +3feab7bc6213b77b 0.83 0.835 +3fe5d707dda94616 0.68 0.682 +3fb7f1a9cf6f2868 0.09 0.094 +3fe5ab41543c64f0 0.68 0.677 +3fe371ed64a58313 0.61 0.608 +3fecc71f15d206f2 0.9 0.899 +3fd2e5042078c9ce 0.3 0.295 +3fc00a33a64aef00 0.13 0.125 +3fdcd2f26f9efbe6 0.45 0.45 +3facc4de8a1055d0 0.06 0.056 +3f921ac98d7f3fe0 0.02 0.018 +3fd7c4902916e964 0.37 0.371 +3fe6134b59beb257 0.69 0.69 +3f933ebee13a4620 0.02 0.019 +3fa127c0717678a0 0.03 0.034 +3fe3458a591481e3 0.6 0.602 +3fc6aba499077354 0.18 0.177 +3fd81c3c87af7d9e 0.38 0.377 +3fefa97e9af53ae2 0.99 0.989 +3fe91b53ae37b291 0.78 0.785 +3fdfb4663176165e 0.5 0.495 +3fedeaaf27ceb5dc 0.93 0.935 +3fb0f29f236b5c48 0.07 0.066 +3faa1eec30a9d950 0.05 0.051 +3fe1417b769e06ca 0.54 0.539 +3fd67bf68105be4e 0.35 0.351 +3fc4ef3795703d24 0.16 0.164 +3feaa2820079e50c 0.83 0.832 +3fab663a0e47f900 0.05 0.054 +3fe1374769bfc31d 0.54 0.538 +3feb530df46a43e4 0.85 0.854 +3fe16f19c92971fb 0.54 0.545 +3fb84312d8958630 0.09 0.095 +3fe477f82a4cd694 0.64 0.64 +3fe93b132ab7957f 0.79 0.788 +3fd81b3f43a58406 0.38 0.377 +3fc939bc914af924 0.2 0.197 +3fef90188c2ca4b0 0.99 0.986 +3fee4430a7b104b7 0.95 0.946 +3fc16453b0571f68 0.14 0.136 +3fe318313f5a0717 0.6 0.597 +3fc17ce829c6a378 0.14 0.137 +3fc5a9602b6ba6d0 0.17 0.169 +3fe5f977c799edf2 0.69 0.687 +3fe2688b95f57cce 0.58 0.575 +3fed8495be116623 0.92 0.922 +3fab3467686a7470 0.05 0.053 +3fe3030791cc28e0 0.59 0.594 +3fecb203f30d1b5c 0.9 0.897 +3fed752ff134eb53 0.92 0.921 +3fe1dc78d37cc5d9 0.56 0.558 +3fe1f8e38d0c6687 0.56 0.562 +3fe568417479a05f 0.67 0.669 +3fe917f052a27040 0.78 0.784 +3fd749a0ae28dc00 0.36 0.364 +3fb04e572805a870 0.06 0.064 +3fdb287a24beb7dc 0.42 0.424 +3fac297f62cd8c50 0.06 0.055 +3fdc549782330674 0.44 0.443 +3fd583504cd48348 0.34 0.336 +3f918fcce1f02ca0 0.02 0.017 +3fb4b54199b6d950 0.08 0.081 +3fe4f059126dc708 0.65 0.654 +3fe7bab54ecedca0 0.74 0.742 +3fe89d1b53ef351d 0.77 0.769 +3fe51c242ff85408 0.66 0.66 +3fcd3496dad8b3ec 0.23 0.228 +3fd72efb1983371e 0.36 0.362 +3fe8f1f134437c16 0.78 0.78 +3febcb3bce7a2dff 0.87 0.869 +3fe70069cd3fdea2 0.72 0.719 +3fd9c4063aef7380 0.4 0.403 +3fba5892352846b0 0.1 0.103 +3fd5cdca9cfeaa88 0.34 0.341 +3fed7f5eed6661d0 0.92 0.922 +3fd9dc7f3fc9b016 0.4 0.404 +3fe285cbc7651c4b 0.58 0.579 +3fe12f6d12cb96a4 0.54 0.537 +3fc286ac0d1e4c98 0.14 0.145 +3fdce321f6ccd546 0.45 0.451 +3fd0e552fa1a16a2 0.26 0.264 +3fedb38a38ade7fc 0.93 0.928 +3fe7ec01718ae84c 0.75 0.748 +3fdec4ef8a6ef174 0.48 0.481 +3fd930de8ce3f46e 0.39 0.394 +3fe588eae2d78926 0.67 0.673 +3fa5cd23e8267ec0 0.04 0.043 +3fc5e9ce46b41e7c 0.17 0.171 +3feb42ec12191011 0.85 0.852 +3fdff7cf2e2309fe 0.5 0.5 +3fd2d7ab51bc9786 0.29 0.294 +3fe21b20a5dbec50 0.57 0.566 +3fcf5dde1eb149d8 0.25 0.245 +3feff2f81e0d3164 1.0 0.998 +3fe591176ad90e4d 0.67 0.674 +3feb8f7d2180df82 0.86 0.861 +3fdf76a423e1d354 0.49 0.492 +3fb001a1714c51f0 0.06 0.063 +3fe8f2d48f141d88 0.78 0.78 +3fdb9a22178be832 0.43 0.431 +3fe438ed9754679f 0.63 0.632 +3fd1d8da30b03e70 0.28 0.279 +3fbb548cc113f028 0.11 0.107 +3fc8e437a00e8af4 0.19 0.194 +3fd92c5cc0c7bbe2 0.39 0.393 +3fb21a5b40c1a618 0.07 0.071 +3fd9ffa81b66dc2c 0.41 0.406 +3fbeba7402564d00 0.12 0.12 +3fd25bcf934ba054 0.29 0.287 +3fec62683405072d 0.89 0.887 +3fedc07088140428 0.93 0.93 +3fdb0d5f0cb1841c 0.42 0.423 +3fea6a9d54104c32 0.83 0.826 +3fd16102e929a19a 0.27 0.272 +3fdc60dbf02fdcaa 0.44 0.443 +3fd486f905571f0a 0.32 0.321 +3fe20ec031ed4e47 0.56 0.564 +3fba7225a59ad2f0 0.1 0.103 +3fc24848e2349da8 0.14 0.143 +3fef6699560b3c39 0.98 0.981 +3feaf35435a75205 0.84 0.842 +3fc02a46de90b7e4 0.13 0.126 +3fe58ebd65913874 0.67 0.674 +3fe5b7ffe1a83557 0.68 0.679 +3fb4bde5f7c68188 0.08 0.081 +3fe5db7d9c889b61 0.68 0.683 +3fc6483146d1be18 0.17 0.174 +3fb0915b4472f448 0.06 0.065 +3fe38405c10add2c 0.61 0.61 +3fc704e27adcaf44 0.18 0.18 +3fdb4f0e0dbd4344 0.43 0.427 +3fc5f3455e98a470 0.17 0.171 +3fec806ca09c5168 0.89 0.891 +3fda99024378af50 0.42 0.416 +3fe4d9c7d4eba19c 0.65 0.652 +3fd6df62e4e0cbde 0.36 0.357 +3fdb544031b92166 0.43 0.427 +3fc11669142874f4 0.13 0.133 +3fe7a4ff84dbe5f4 0.74 0.739 +3fec0abc6dda454e 0.88 0.876 +3fd32405def65460 0.3 0.299 +3fdf128c35fd3b18 0.49 0.486 +3fdd1acd8596658c 0.45 0.455 +3fe8dfda57f02eb8 0.78 0.777 +3fe4540e65f03d6f 0.64 0.635 +3fe82ec70a114570 0.76 0.756 +3fefb2869a7bbe82 0.99 0.991 +3fecfe23215676db 0.91 0.906 +3fb2eb29ff4ad160 0.07 0.074 +3fb3739872be3fc0 0.08 0.076 +3fea3bdbd64a420f 0.82 0.82 +3fee9377ecc4f57b 0.96 0.956 +3fd9074bc245e35e 0.39 0.391 +3fd7dbc9f73efa8a 0.37 0.373 +3fe520bf9dc6dbe6 0.66 0.66 +3fdf83f6bf2c1fe8 0.49 0.492 +3fef6e67c38d2878 0.98 0.982 +3fd215c3bf36eb46 0.28 0.283 +3fdbe825db7a0dae 0.44 0.436 +3fbea14099d5a410 0.12 0.12 +3fd24d5bfe6e79a0 0.29 0.286 +3fba42f212f30840 0.1 0.103 +3fdc06427209360e 0.44 0.438 +3fe332f07eed8d2f 0.6 0.6 +3fedb4a27974f1e4 0.93 0.928 +3fe2e510945229f4 0.59 0.59 +3fe41f8e790c8f44 0.63 0.629 +3fe1d95f2f519f71 0.56 0.558 +3fe4c8d296c4db04 0.65 0.65 +3fe1c203b1790782 0.55 0.555 +3fc5ea7bacd4ee14 0.17 0.171 +3fd4aaf7d3697d80 0.32 0.323 +3fd24b2a3f93e22c 0.29 0.286 +3fd2673c57c6c7b8 0.29 0.288 +3fb5e6d7512a4420 0.09 0.086 +3fd425a4cc7d85d4 0.31 0.315 +3fb30b600c1c7540 0.07 0.074 +3fd1afa24cb6424e 0.28 0.276 +3fea8daede0090a1 0.83 0.83 +3fb403c7aa097410 0.08 0.078 +3fedc877e31dd212 0.93 0.931 +3fba0265ec21b7f8 0.1 0.102 +3fe48973e8ea9265 0.64 0.642 +3fc5b8dd8dda8b98 0.17 0.17 +3fd5a530f401f97e 0.34 0.338 +3fe0e69dd0da3c6f 0.53 0.528 +3fc72cab328fb2c4 0.18 0.181 +3fe9ba252719a562 0.8 0.804 +3fdcdbe3076a5456 0.45 0.451 +3fea75fc9f4fb9ae 0.83 0.827 +3fcb2654e78c9f24 0.21 0.212 +3fa538abff475b90 0.04 0.041 +3fe41fffc85a42d8 0.63 0.629 +3feedd94178b45eb 0.96 0.965 +3fe84ed89cc6210b 0.76 0.76 +3fc1e4b440c9c17c 0.14 0.14 +3fe53f2ad9cac4ef 0.66 0.664 +3fd758645800d664 0.36 0.365 +3fdac32f8da875ce 0.42 0.418 +3fd249cb601157aa 0.29 0.286 +3fde312d843df9ce 0.47 0.472 +3fbb33b8a49eed08 0.11 0.106 +3fecb6667d97ee9d 0.9 0.897 +3fe94c1b922c73e8 0.79 0.791 +3fefe66d5c8f2a21 1.0 0.997 +3fd246039881c1e4 0.29 0.286 +3fee4cf4ac473dea 0.95 0.947 +3fbd6c6526ac7498 0.11 0.115 +3fbdf2631d3a3740 0.12 0.117 +3fb8d3858df973f0 0.1 0.097 +3fe506fff02e079a 0.66 0.657 +3fc3d986810b6ce4 0.16 0.155 +3fe94b5344192c2f 0.79 0.79 +3fd50b6a8acbaeb4 0.33 0.329 +3fefaa3d99247de8 0.99 0.99 +3fe757e5fb87c9a3 0.73 0.729 +3fe9cd10275d43a1 0.81 0.806 +3fd72e6327f79b5c 0.36 0.362 +3fde4fe96d12a182 0.47 0.474 +3fedc4cb8375df9a 0.93 0.93 +3fecd538861c294c 0.9 0.901 +3fe5b81c1a37a64b 0.68 0.679 +3fecefde75154422 0.9 0.904 +3fd7993c7df6e7a6 0.37 0.369 +3fe6d6f788f83c2b 0.71 0.714 +3fdb6599f66da61e 0.43 0.428 +3fe30ec18e2da65d 0.6 0.596 +3fe85b9fc7f50718 0.76 0.761 +3f51e95cdbe7dc00 0.0 0.001 +3fca383fb929db18 0.2 0.205 +3feee80ab46a1c5c 0.97 0.966 +3fe7750787633ea7 0.73 0.733 +3fca8a2827d2b080 0.21 0.207 +3fd6579d92351f26 0.35 0.349 +3feca19a1cf83815 0.89 0.895 +3fe63201cab4fa04 0.69 0.694 +3fc4028e20a936b0 0.16 0.156 +3feff69b51218989 1.0 0.999 +3fd69775b62f8bbe 0.35 0.353 +3fec05a42255c6b1 0.88 0.876 +3fd6c41bff23c570 0.36 0.356 +3fcad3bcc7878788 0.21 0.21 +3fdd5abd32ba8386 0.46 0.459 +3f9805619cdf6c80 0.02 0.023 +3fc71a3ce6d77d50 0.18 0.18 +3fe6a2b8217933ca 0.71 0.707 +3fe59941fae4382c 0.67 0.675 +3facb4c04796ee60 0.06 0.056 +3fe71ad1c2a46d10 0.72 0.722 +3fe2d8d79476e67f 0.59 0.589 +3feeaeb20fb4a9fa 0.96 0.959 +3f9f83657a9d5b00 0.03 0.031 +3fb1a229e59c7400 0.07 0.069 +3febb15192ad1eb3 0.87 0.865 +3fd7afc30f65d638 0.37 0.37 +3fde22963bcef914 0.47 0.471 +3fe6f65011f1291c 0.72 0.718 +3fe8960285c2654b 0.77 0.768 +3fc86772d9c6f740 0.19 0.191 +3fb8eaddf1af5448 0.1 0.097 +3fecb13cb233f36c 0.9 0.897 +3fd912bbd5b25d02 0.39 0.392 +3fb3c4746503bec8 0.08 0.077 +3fedce1bddc894fd 0.93 0.931 +3fec2aaf52b05c9e 0.88 0.88 +3fe53bb1eb0157e7 0.66 0.664 +3fce3c4f4593a4d8 0.24 0.236 +3fc6fbe7eb775d48 0.18 0.18 +3fe77304fdcc3068 0.73 0.733 +3fe606c95e48b930 0.69 0.688 +3fd81261844087ac 0.38 0.376 +3fc48f95f7c5da1c 0.16 0.161 +3fbf2539e4351728 0.12 0.122 +3fe7ee1b2598bcb0 0.75 0.748 +3fe50835795704e3 0.66 0.657 +3fe0e55089a96c18 0.53 0.528 +3fc4e8dc4f944654 0.16 0.163 +3fd9e7bfd72cc4ba 0.4 0.405 +3fe1380af6561cfe 0.54 0.538 +3fe67c46b75e5167 0.7 0.703 +3fc84030378c4fb4 0.19 0.189 +3fb092dd7d1336f0 0.06 0.065 +3fcb00d9eb63e0d8 0.21 0.211 +3fd379819d4093da 0.3 0.304 +3fd09c23226264b2 0.26 0.26 +3fe63f1900b12018 0.7 0.695 +3fd631e3c7722c74 0.35 0.347 +3fe8bccd9b4fd34b 0.77 0.773 +3fd7311ab3323970 0.36 0.362 +3fdbab8e0c0c6b18 0.43 0.432 +3febe311119a0695 0.87 0.871 +3febe9b9a41499ac 0.87 0.872 +3fd088b78f4d3608 0.26 0.258 +3fd866035a6f754a 0.38 0.381 +3fde9231528f3508 0.48 0.478 +3fed0d6964bd698b 0.91 0.908 +3fed11555bbf85d7 0.91 0.908 +3fda7e4ab5293e10 0.41 0.414 +3fe4eb0bd4dbf4cb 0.65 0.654 +3fc8d07fbb13f408 0.19 0.194 +3fe52cfc8ad0b0d6 0.66 0.662 +3fc85b21286212a4 0.19 0.19 +3f6e3909f8f43500 0.0 0.004 +3feea1932d838b03 0.96 0.957 +3fe72badd6f7ff95 0.72 0.724 +3fea833d7141f3ef 0.83 0.829 +3fe342ad176e95ee 0.6 0.602 +3fe0fdd64b614519 0.53 0.531 +3fc8e6020ca026f0 0.19 0.195 +3feac4b5b20dc9fa 0.84 0.837 +3fd4e37475d8365c 0.33 0.326 +3fe3bb723a7799d2 0.62 0.617 +3fe7bdbc2b22f28b 0.74 0.742 +3fe004aaee567689 0.5 0.501 +3fcbd9052d7e017c 0.22 0.218 +3fea4017761f7ff2 0.82 0.82 +3fc38c6ca0f48c14 0.15 0.153 +3fdd65e92b4bdd32 0.46 0.459 +3fe503036eac71d4 0.66 0.657 +3fc1036eb9ba5d84 0.13 0.133 +3fe5f4bb0eccb660 0.69 0.686 +3fe61270771b5ea3 0.69 0.69 +3f4ef6f700519800 0.0 0.001 +3fd96f4bdc606108 0.4 0.397 +3fcf32c64ed1bed0 0.24 0.244 +3fc9c5425aa28820 0.2 0.201 +3fbdeb98794cd440 0.12 0.117 +3fead955949cf91e 0.84 0.839 +3fe7d797e51fd190 0.75 0.745 +3fec68aafe0c78d9 0.89 0.888 +3fe355268821ea68 0.6 0.604 +3fa262e3203b3440 0.04 0.036 +3fcc6825880e1db4 0.22 0.222 +3fe93047ed86c063 0.79 0.787 +3fc676ed96518cdc 0.18 0.176 +3fefb131af7345a6 0.99 0.99 +3fdeb8acc405366a 0.48 0.48 +3fe0cac97b1b739a 0.52 0.525 +3fdcd4efe6d4b7c4 0.45 0.45 +3fd4a85bd683cd6c 0.32 0.323 +3fd0021b10faaac8 0.25 0.25 +3fe42da87660073e 0.63 0.631 +3f96e4fb49aee8c0 0.02 0.022 +3fe6fdb34ecb57ea 0.72 0.718 +3fd48dfe620f84da 0.32 0.321 +3fe252da4c5062d4 0.57 0.573 +3fe6b7341e1c214e 0.71 0.71 +3fd16834d226a956 0.27 0.272 +3fc09db4d38b3204 0.13 0.13 +3fe8a358884f214f 0.77 0.77 +3fdc30b1d9b2a8d2 0.44 0.44 +3fe04b85e726c606 0.51 0.509 +3fe77090a9b51fc9 0.73 0.732 +3fd76e2ca1056f00 0.37 0.366 +3fdf26294fba801e 0.49 0.487 +3fb1425d38a20188 0.07 0.067 +3fdbc47d13689084 0.43 0.434 +3fe373ae040b7a3f 0.61 0.608 +3fe2358f7952a094 0.57 0.569 +3fe15d67b2dab7dd 0.54 0.543 +3fb4a7d39052f710 0.08 0.081 +3fe36af74b48282d 0.61 0.607 +3fe32f0a25050689 0.6 0.599 +3fea43a791512757 0.82 0.821 +3fefa868ed0fc3b7 0.99 0.989 +3fea350fdaedeb81 0.82 0.819 +3fdae3ce3a7d78ce 0.42 0.42 +3fd4eaa2bc1fd47e 0.33 0.327 +3fc4f5c4a8e79e28 0.16 0.164 +3fd3a9199c34d4e4 0.31 0.307 +3f7ca87daa088480 0.01 0.007 +3fc09efefbd53138 0.13 0.13 +3fd1fb4fbe8c422c 0.28 0.281 +3fd3dae5a41fa684 0.31 0.31 +3fe2c182e8d55eb1 0.59 0.586 +3fe9377a97c5a603 0.79 0.788 +3fe2087a9de7376c 0.56 0.564 +3fa72e5f8fefd010 0.05 0.045 +3fa8e7986cd8c160 0.05 0.049 +3fedff9bb3612139 0.94 0.937 +3feefdb0ddcb21d6 0.97 0.968 +3fc17db20888a720 0.14 0.137 +3fec6b17d26ce7f6 0.89 0.888 +3febea2396a5c34c 0.87 0.872 +3fe1ba2a78fcff62 0.55 0.554 +3fe3c29d60652c7f 0.62 0.618 +3fbc78931c5c0a90 0.11 0.111 +3fe7703a057b631e 0.73 0.732 +3fdd34a0b92d3f4e 0.46 0.456 +3fdbbdd65bee0d08 0.43 0.433 +3fe43d2b0d0a9499 0.63 0.632 +3feb987f4f68263e 0.86 0.862 +3fd68b2d8e4031b0 0.35 0.352 +3fdd897d97f34b80 0.46 0.462 +3fd13b594dfed1f8 0.27 0.269 +3fe20ac3e0ae9583 0.56 0.564 +3fd10360036eea02 0.27 0.266 +3feb1133b415e550 0.85 0.846 +3fe276ffa6eb90f0 0.58 0.577 +3fd2041644d4f720 0.28 0.281 +3fee52a27e224679 0.95 0.948 +3fe80db601bc9a30 0.75 0.752 +3fef3c5c73766f96 0.98 0.976 +3fe3cbd48cef20b4 0.62 0.619 +3fa94a90992aa200 0.05 0.049 +3fe6d7c5e89e119f 0.71 0.714 +3fcaa49ac769d178 0.21 0.208 +3fee2e6bc3a49bfc 0.94 0.943 +3fcee09c5000fff0 0.24 0.241 +3fd4b01a8a127bf6 0.32 0.323 +3fe967af1f3c5f28 0.79 0.794 +3fe74b340aac4f3f 0.73 0.728 +3fe494c05c1937ea 0.64 0.643 +3febfedbcdc79e5d 0.87 0.875 +3fc320b84831732c 0.15 0.149 +3fdccfc6094d5afa 0.45 0.45 +3fe692924da48446 0.71 0.705 +3fd1b940392b2a84 0.28 0.277 +3feedbd881cec6d9 0.96 0.964 +3fdb0304e11b2eaa 0.42 0.422 +3fe315b4670eb59c 0.6 0.596 +3fd1130e7e4eab2e 0.27 0.267 +3fe3e34ef197a639 0.62 0.621 +3fcee6ff90893440 0.24 0.241 +3fe99f27848c9a18 0.8 0.801 +3fd099ddc688ffd6 0.26 0.259 +3fe3f72bb960eda1 0.62 0.624 +3fe0b61deeb7faae 0.52 0.522 +3fd9c95036eb1988 0.4 0.403 +3fe53709a1422101 0.66 0.663 +3fb1c11a84df64d8 0.07 0.069 +3fed97408034e5b2 0.92 0.925 +3fe282517aa2f894 0.58 0.578 +3fcc7aaf9c039ffc 0.22 0.222 +3fe54a0991fbc51c 0.67 0.665 +3fe6eab2edced796 0.72 0.716 +3fd64e95def1e4b6 0.35 0.349 +3fb3ca737041dfc8 0.08 0.077 +3fc04f7597c0fb50 0.13 0.127 +3fbfbaab86dc3ec8 0.12 0.124 +3fe9936c4c119fdb 0.8 0.799 +3fb7296caa352788 0.09 0.09 +3fe336fa844a17ab 0.6 0.6 +3fdf67acaa3f4be6 0.49 0.491 +3fac163759fe3c90 0.05 0.055 +3fdafe2dd6c318e2 0.42 0.422 +3fbf29742b8bf0a0 0.12 0.122 +3fe3ab882502b7f4 0.61 0.615 +3fea11d6875978cd 0.81 0.815 +3fd51d2435005e24 0.33 0.33 +3fd0ff9a7cd877cc 0.27 0.266 +3fe2ca91aa695e4d 0.59 0.587 +3fd79f30426b515c 0.37 0.369 +3fe0e0a46517fae4 0.53 0.527 +3fc98cae4ebfe444 0.2 0.2 +3fefa4f4efca0d39 0.99 0.989 +3fe38c928ae95db0 0.61 0.611 +3fbeef2f2bf8f2e0 0.12 0.121 +3fefaf63618c791b 0.99 0.99 +3fe0dae126cf165b 0.53 0.527 +3fb1fb4c6e2a1d38 0.07 0.07 +3feccb5749652c5e 0.9 0.9 +3fea9ac2b825d759 0.83 0.831 +3fee7c27fcc02ee7 0.95 0.953 +3fe6a58e6ed5ce6a 0.71 0.708 +3fec7586f2574661 0.89 0.889 +3fddc03490c210e6 0.46 0.465 +3fe1fb269cd85fcf 0.56 0.562 +3f8208cffec6f5c0 0.01 0.009 +3fe436a6872f5af8 0.63 0.632 +3fde468ea4dabb5c 0.47 0.473 +3f9f053a536daaa0 0.03 0.03 +3fe1d3dd3b9e3624 0.56 0.557 +3fd6c42b8d4a4eb6 0.36 0.356 +3fefe26f49a46445 1.0 0.996 +3fd81ffc6fa4c7b0 0.38 0.377 +3fe8aa6f05048f83 0.77 0.771 +3f7cbd97a2967a80 0.01 0.007 +3fe15e0dc5387896 0.54 0.543 +3fe16f97b8180cc0 0.54 0.545 +3fc1cd04119f4a38 0.14 0.139 +3fcc3ae7e7cd95a0 0.22 0.221 +3feadf291c34ffbf 0.84 0.84 +3fdb1c7ddfc81e48 0.42 0.424 +3fde2bba6e284066 0.47 0.471 +3fe4c8a62bc0d289 0.65 0.649 +3fdbbd47c1f053ca 0.43 0.433 +3fb1d83c5c76cba0 0.07 0.07 +3fd9c66f2615e254 0.4 0.403 +3fefa3f4be1d5423 0.99 0.989 +3fd310c78a281e82 0.3 0.298 +3fc04422f3ecb23c 0.13 0.127 +3fc3d405e16208fc 0.15 0.155 +3fd420bed799e340 0.31 0.314 +3fea13e973556fc4 0.81 0.815 +3fcc0214e126ff44 0.22 0.219 +3fd9a64c09172188 0.4 0.401 +3fdfefdf66ea2a0a 0.5 0.499 +3fe00a34a16dd6d9 0.5 0.501 +3feae4ba0e5352e1 0.84 0.84 +3fce4eada02a717c 0.24 0.237 +3fd81d93882a3066 0.38 0.377 +3fe3b7252fb4bbdc 0.62 0.616 +3fe15b112eecfa36 0.54 0.542 +3fe124feb55b5b5f 0.54 0.536 +3feb9d5cc915c018 0.86 0.863 +3fd5e9068d164a58 0.34 0.342 +3fbcc9ae36b4a368 0.11 0.112 +3fd861f38d66ffd2 0.38 0.381 +3fe5c561da702034 0.68 0.68 +3fd7453cb7a1746a 0.36 0.364 +3fec36096630f559 0.88 0.882 +3fce565e7c73f998 0.24 0.237 +3fb0a7f6ea1fd3c0 0.07 0.065 +3febe09480b09bf6 0.87 0.871 +3fd6194a066967cc 0.35 0.345 +3fe97d518f152d97 0.8 0.797 +3fb228e7836a1bb0 0.07 0.071 +3fe0a79ddb1e3b39 0.52 0.52 +3fdb0e0723c98166 0.42 0.423 +3fd960c5e3206134 0.4 0.397 +3f77b37416153e00 0.01 0.006 +3fedaf9aec7be38a 0.93 0.928 +3fe5ef1d896947c4 0.69 0.685 +3fe2d788a8edf4df 0.59 0.589 +3febdd20485f2549 0.87 0.871 +3fab8f021a980a80 0.05 0.054 +3fc61ab8ab2efd4c 0.17 0.173 +3fd6bb0ae363edf0 0.36 0.355 +3fea43ddef107b7a 0.82 0.821 +3fe1fd78908a067f 0.56 0.562 +3fd3744bda903c36 0.3 0.304 +3fdc6327f25d919a 0.44 0.444 +3fdcc0e06eeb71de 0.45 0.449 +3fd299a61d48fa42 0.29 0.291 +3f74392e8e8c4800 0.0 0.005 +3fc4d2df27f66094 0.16 0.163 +3fec028171b21bb6 0.88 0.875 +3fe6083c332f531f 0.69 0.689 +3feec8f4e246a012 0.96 0.962 +3fcefda191bc1c80 0.24 0.242 +3fe72bdb179066e0 0.72 0.724 +3fe3c8433c5b0a3f 0.62 0.618 +3fb129c30850d6a0 0.07 0.067 +3fd3da04c456e062 0.31 0.31 +3fd9c7ca4874bd84 0.4 0.403 +3fb5c0cebab75108 0.08 0.085 +3fb410a3a1f38148 0.08 0.078 +3fc4483f27ff68e4 0.16 0.158 +3fda73c6b7f9a574 0.41 0.413 +3fedf42388130a79 0.94 0.936 +3fef538d9dd456ae 0.98 0.979 +3fe0f12a01e87f91 0.53 0.529 +3fe23f381e36d51e 0.57 0.57 +3fd0b0ee61f835ba 0.26 0.261 +3fee78e4ce77d3f6 0.95 0.952 +3fe2c1f8bec1c90c 0.59 0.586 +3f96ade87188ff80 0.02 0.022 +3feb1ed3c0a3dc39 0.85 0.848 +3fd3f939a12b8f9e 0.31 0.312 +3fbb141f0c9b8e48 0.11 0.106 +3fe4fd1b36557ce9 0.66 0.656 +3fe9c668ec1e7f84 0.81 0.805 +3fe974e251c2445f 0.8 0.796 +3fee1a0f79d71593 0.94 0.941 +3fec107360e06f15 0.88 0.877 +3fe3ebd618d7e4f6 0.62 0.623 +3f81ae97f2b8b280 0.01 0.009 +3fea218d6fd0cbc8 0.82 0.817 +3fef45c452fe62a4 0.98 0.977 +3fea75b747b110aa 0.83 0.827 +3fdba3ed8c05e698 0.43 0.432 +3fb698df168349c8 0.09 0.088 +3fd08b8c2231c206 0.26 0.259 +3fe6cf83cb3f012b 0.71 0.713 +3fb13cc9a0b2e828 0.07 0.067 +3fe600f4fe96c196 0.69 0.688 +3fea57981a2d0152 0.82 0.823 +3fd3f2837d3cff62 0.31 0.312 +3fea72da1dc0551b 0.83 0.827 +3fe84c788e59d876 0.76 0.759 +3fe1a4270c760092 0.55 0.551 +3fc00a0ca0992c68 0.13 0.125 +3faee170619450e0 0.06 0.06 +3fe3a431523cd0a9 0.61 0.614 +3fe2746ee22482b3 0.58 0.577 +3feca0cbd7660da0 0.89 0.895 +3fc30447cece79bc 0.15 0.149 +3fe9cdd5139b896e 0.81 0.806 +3fd2c8aa8e588602 0.29 0.293 +3fe2434c3b9ec6f5 0.57 0.571 +3fecf1e55403f939 0.9 0.905 +3fd597481818cbb8 0.34 0.337 +3fe7ba68851d883b 0.74 0.742 +3fe6b58ca5527138 0.71 0.71 +3fb501315b44be28 0.08 0.082 +3fe5b560a52ca907 0.68 0.678 +3fc257a469cada48 0.14 0.143 +3fe65a0a6bc8011b 0.7 0.698 +3fe29c5c84dfa1a9 0.58 0.582 +3feb63e8f8788da8 0.86 0.856 +3fe3c4176a48816c 0.62 0.618 +3fe3658847e892d0 0.61 0.606 +3fe2a9a105bb6563 0.58 0.583 +3fe046dc1ad920bb 0.51 0.509 +3fe87a48a38f781f 0.76 0.765 +3fd208ec8853f1e4 0.28 0.282 +3fc9446eefc17150 0.2 0.197 +3fd8ee1ea8266494 0.39 0.39 +3fdc55da12a38bde 0.44 0.443 +3fdd1486ece8b3e8 0.45 0.454 +3fcfdd8705411768 0.25 0.249 +3feae216155bc7a9 0.84 0.84 +3fea66b5b8cb5e93 0.83 0.825 +3fe4da35eab15b4c 0.65 0.652 +3fb622890307d228 0.09 0.086 +3fd2f870e0eaa49e 0.3 0.296 +3fc2a9468932f7c4 0.15 0.146 +3fe8496536041c83 0.76 0.759 +3fb092638d31dfa0 0.06 0.065 +3fd67931c6597c18 0.35 0.351 +3fef996673fcd11a 0.99 0.987 +3fe8fd38e2436018 0.78 0.781 +3fe58a4b3ae45b41 0.67 0.673 +3fa4f3c7bb8d5790 0.04 0.041 +3fc34dfbfec57334 0.15 0.151 +3fdc27e166ec877a 0.44 0.44 +3fdf38888790f992 0.49 0.488 +3fdeb1eb39779b66 0.48 0.48 +3fec67e5070d80eb 0.89 0.888 +3feffd6615a3db38 1.0 1.0 +3fe469fb2a1dc68b 0.64 0.638 +3fee3adfa83ebedc 0.94 0.945 +3feb66cda9127a40 0.86 0.856 +3fe5645a7914a921 0.67 0.669 +3fe610e93c05e926 0.69 0.69 +3feed885571715c8 0.96 0.964 +3fdbe71c66bb6974 0.44 0.436 +3fd66591ee395064 0.35 0.35 +3fec82d02de05885 0.89 0.891 +3fd1cf25e462529a 0.28 0.278 +3fd74287226c8df2 0.36 0.363 +3fdeb9fca59f90d0 0.48 0.48 +3fd470c4838c5d8e 0.32 0.319 +3fe7c81d8e3f3df2 0.74 0.743 +3fddade3d33cd294 0.46 0.464 +3fcf31a0c81e6650 0.24 0.244 +3fd91c2d8b20f3e2 0.39 0.392 +3fe4a497a507da8f 0.65 0.645 +3fdea3b526c9fe94 0.48 0.479 +3fe7c1b1c95f805e 0.74 0.742 +3fe38e7595ea04a7 0.61 0.611 +3fc1e7d9e53ca62c 0.14 0.14 +3fc8d3dab7fda51c 0.19 0.194 +3fe970116c2bc538 0.79 0.795 +3fefca6ebfd5f183 0.99 0.993 +3f9172294f5945a0 0.02 0.017 +3f9ebbe151d74760 0.03 0.03 +3fd6000450a79ac6 0.34 0.344 +3fe9db48028f843e 0.81 0.808 +3fcbdf7104e658f0 0.22 0.218 +3fd84d17a9f8523c 0.38 0.38 +3fe684765d3f1d63 0.7 0.704 +3fd408fc5a53a866 0.31 0.313 +3feccabdc9910107 0.9 0.9 +3fe0f5d7dc38e3ea 0.53 0.53 +3fec87453a39151a 0.89 0.892 +3f58c5a0c249c600 0.0 0.002 +3fdd1d347a4ff232 0.45 0.455 +3fd6fdc9c819aa9e 0.36 0.359 +3fe22d4afdcd5684 0.57 0.568 +3fdc8040aebbabf2 0.45 0.445 +3fc5a7713bf0be00 0.17 0.169 +3fdfe1e0c98f0abe 0.5 0.498 +3fd8709f9267a238 0.38 0.382 +3fd9dfb4edc00f7e 0.4 0.404 +3fe6fbc1569c4411 0.72 0.718 +3fe8c71428d950ab 0.77 0.774 +3fed4063ecd888d5 0.91 0.914 +3fdf22cd9c16c77e 0.49 0.486 +3fea5c30141d7ee4 0.82 0.824 +3fe4b98d1033ca1b 0.65 0.648 +3feb45eee716a67b 0.85 0.852 +3fe492e4ba3865e9 0.64 0.643 +3fe43e0a3f313363 0.63 0.633 +3feffdaeb5cdc4eb 1.0 1.0 +3fd49071e8fd2982 0.32 0.321 +3fd3835b757ce6ce 0.3 0.305 +3fd86300ee173f9c 0.38 0.381 +3fe06c8a61b82f2b 0.51 0.513 +3fee49a80f6381e4 0.95 0.946 +3febff1235ce45a9 0.87 0.875 +3fe65d93108b4e8a 0.7 0.699 +3feba0d72340d50c 0.86 0.863 +3fe369efc2a40037 0.61 0.607 +3feaf03977eb56fa 0.84 0.842 +3fe20ef47cebd81c 0.56 0.564 +3fe2bbae5d10874a 0.59 0.585 +3fc80f9d0b7a745c 0.19 0.188 +3fec8d3f58d6b1d2 0.89 0.892 +3fd1622de476f18e 0.27 0.272 +3fdf7be00b60ef6a 0.49 0.492 +3fe5abc7daccdb5c 0.68 0.677 +3fd2589621080088 0.29 0.287 +3fe806d65709414e 0.75 0.751 +3fcbbd2e13d62a14 0.22 0.217 +3fdbe1ab3c69b880 0.44 0.436 +3fe1b6d5a2de2bb4 0.55 0.554 +3fcdd5247c15ab48 0.23 0.233 +3fcb4c8c432714f0 0.21 0.213 +3fc4d5c0db1e429c 0.16 0.163 +3fb677ea1280c920 0.09 0.088 +3fe66ff7bd7dbf1e 0.7 0.701 +3fcfdf3f2de34708 0.25 0.249 +3fe8368a8cf77aaf 0.76 0.757 +3fe536a6b77ff9fb 0.66 0.663 +3fea22c9795aec83 0.82 0.817 +3fe33a50789dfec0 0.6 0.601 +3fd3542c761941ae 0.3 0.302 +3fd51fb86fbffb90 0.33 0.33 +3fd2c650ec1cbe58 0.29 0.293 +3fe355f4d1be9c17 0.6 0.604 +3fe8f0056190f6fa 0.78 0.779 +3fe09041bbb1d6b3 0.52 0.518 +3fddc2be8aa0c686 0.47 0.465 +3fe38018335696e6 0.61 0.609 +3fd4ae9abc466ea4 0.32 0.323 +3fb3964a348dceb0 0.08 0.077 +3fd82d4519a47c66 0.38 0.378 +3fc9fb37dcb5dd68 0.2 0.203 +3fcc6fb730126a0c 0.22 0.222 +3fe1882537b8e294 0.55 0.548 +3feecce651cb59de 0.96 0.963 +3fcd2691989b4ce8 0.23 0.228 +3fdd1a7dc51a2f64 0.45 0.455 +3fea8d6369b04779 0.83 0.83 +3fd987ee1b9c2dca 0.4 0.399 +3fb3563004a51108 0.08 0.076 +3fed64858937a96d 0.92 0.919 +3fca9e3cbf6036cc 0.21 0.208 +3fc67834ea2a08a0 0.18 0.176 +3fd183ca7a3c762c 0.27 0.274 +3fee31cc9409ce5a 0.94 0.944 +3fec7bfe8590e5c2 0.89 0.89 +3fe4568ccb92ffc1 0.64 0.636 +3fe6b4990349c04b 0.71 0.71 +3febcd7a6bc8451c 0.87 0.869 +3feeb418803bb58a 0.96 0.959 +3fe1e38e4f120c3a 0.56 0.559 +3feec73bf097e77d 0.96 0.962 +3fa96c9339afce40 0.05 0.05 +3fe761d321001af3 0.73 0.731 +3fd2c79652f6031c 0.29 0.293 +3fbedc80938ae908 0.12 0.121 +3fed5c8f205c4cb1 0.92 0.918 +3fde7c6e782fb5e8 0.48 0.476 +3fdf16063653817e 0.49 0.486 +3fe1660048a4ab72 0.54 0.544 +3fa7a499dfc520d0 0.05 0.046 +3fce73e9c6947e5c 0.24 0.238 +3fe46f8b79805e01 0.64 0.639 +3fef7b1188a4238c 0.98 0.984 +3fc9e954cc8644c8 0.2 0.202 +3fd343a9581bb77e 0.3 0.301 +3fe14e3b1be97b64 0.54 0.541 +3fe62b5560855839 0.69 0.693 +3fe8c130963a39fb 0.77 0.774 +3fde7b41759c15e4 0.48 0.476 +3fe737a2c0fe1f42 0.73 0.726 +3fd540af6341c99c 0.33 0.332 +3fe80e8d45de8613 0.75 0.752 +3fe33c340da28133 0.6 0.601 +3fea2a6f6e508d8f 0.82 0.818 +3feb06e20161a352 0.84 0.845 +3fe97eed8348699c 0.8 0.797 +3fed89018ee2dade 0.92 0.923 +3fd31902f6751564 0.3 0.298 +3fce5b436035025c 0.24 0.237 +3fe341327b3742de 0.6 0.602 +3f9240f9f1cb84c0 0.02 0.018 +3fe00bd8137b8e7f 0.5 0.501 +3fa5cf3fe6153b70 0.04 0.043 +3fe208aed1d0075d 0.56 0.564 +3fe0b4da1d3e38f0 0.52 0.522 +3fed03b678c8621f 0.91 0.907 +3febd850602ec3f5 0.87 0.87 +3fb21adbbecd43a8 0.07 0.071 +3fce65aeed4335f0 0.24 0.237 +3fbc3e5297fc7730 0.11 0.11 +3fbbacf39c3a07b0 0.11 0.108 +3fcb08679c82a594 0.21 0.211 +3fd3bf7b10f74e3a 0.31 0.309 +3fec80daf073e6f8 0.89 0.891 +3fece679296e0925 0.9 0.903 +3fe168e8294a0aff 0.54 0.544 +3f922c9fbb95cb20 0.02 0.018 +3fe492802e045106 0.64 0.643 +3fcf1a96951fbc60 0.24 0.243 +3feadf1e8ea93dc1 0.84 0.84 +3fe4aa4cad16a511 0.65 0.646 +3febaffd37945299 0.87 0.865 +3fe26e20ff3ba04d 0.58 0.576 +3fe2f2e9f8ed18bd 0.59 0.592 +3febe55382926177 0.87 0.872 +3fe3f16380fa514c 0.62 0.623 +3fe77713e9c101f9 0.73 0.733 +3fdfd725ee7f585c 0.5 0.498 +3fca02a7bd378994 0.2 0.203 +3fb11c7bc808b6c8 0.07 0.067 +3fe326471366938b 0.6 0.598 +3fd71779fc8abe4a 0.36 0.361 +3fe62dbfaf2a3d08 0.69 0.693 +3fd65f297caed72e 0.35 0.35 +3fedc55f3bc63335 0.93 0.93 +3fecfdee3f913d5d 0.91 0.906 +3fe2c5fbeff75da5 0.59 0.587 +3fed4e6c87ddc64d 0.92 0.916 +3fde6a5f7342c8d2 0.48 0.475 +3fd4f797a233e6a2 0.33 0.328 +3fc7de9debc7e920 0.19 0.186 +3fa8fcb960f92660 0.05 0.049 +3fee7318aee8d178 0.95 0.952 +3fd009c91f9aa22a 0.25 0.251 +3fe7f72e4f0ff2ce 0.75 0.749 +3feab0bb076fc0d3 0.83 0.834 +3fdaf4ccfd2dc522 0.42 0.421 +3fd0282ffd826190 0.25 0.252 +3fe9ad5b324c33a9 0.8 0.802 +3fc808ed3dd941c0 0.19 0.188 +3f8754004a449e40 0.01 0.011 +3fd6eece7ae5778c 0.36 0.358 +3fe7e08ed566a7ee 0.75 0.746 +3fdca6a49d52b106 0.45 0.448 +3fdd9aa72e62ff66 0.46 0.463 +3fbb4c02f76b75b0 0.11 0.107 +3fefb0d777d887eb 0.99 0.99 +3fe7c9f4043a2f20 0.74 0.743 +3fc43b51c6c5c008 0.16 0.158 +3fd5e7c552f2ebce 0.34 0.342 +3fd2895df1da4690 0.29 0.29 +3fefe11046a26200 1.0 0.996 +3fb728b7078defb0 0.09 0.09 +3fa54e30d2167ab0 0.04 0.042 +3fdaedcb8e56f576 0.42 0.421 +3fbf13f35b4ef1f8 0.12 0.121 +3fe75d6951627d16 0.73 0.73 +3fdef3307fe1c7ba 0.48 0.484 +3fdd5859fb74b134 0.46 0.459 +3fe478a6dd9e0a7e 0.64 0.64 +3fea10f0f931a047 0.81 0.815 +3fe8b18ff441e427 0.77 0.772 +3fef9237721f05c5 0.99 0.987 +3fbb50b16f6a6520 0.11 0.107 +3fc23368b09dc928 0.14 0.142 +3fdc48ba9c7b2ae4 0.44 0.442 +3fe498e2f96919ea 0.64 0.644 +3fe82e5598e5fede 0.76 0.756 +3fe9daf8d1bf0656 0.81 0.808 +3fec5a19e587929a 0.89 0.886 +3fe498ac409fc4fe 0.64 0.644 +3fd5ac6ddc25b38c 0.34 0.339 +3fec73b6381e9629 0.89 0.889 +3fe72101deba9004 0.72 0.723 +3fe90e65e6a12a00 0.78 0.783 +3fc2de98cd479a18 0.15 0.147 +3fe7cdda29b57cf1 0.74 0.744 +3fec4a583b9370ca 0.88 0.884 +3fe15ee70e3645e1 0.54 0.543 +3fd75cac7fb9055c 0.37 0.365 +3fec9cb8fccc2a98 0.89 0.894 +3fef42adb2c993eb 0.98 0.977 +3fd8310fdc37273a 0.38 0.378 +3fed64b9cf52d557 0.92 0.919 +3feaec609b0b6392 0.84 0.841 +3feb79f77295078d 0.86 0.859 +3fe50666e22560b1 0.66 0.657 +3fe6074a42b997d0 0.69 0.688 +3fe4fbaae23b841c 0.66 0.656 +3fe41b4ce9b4c1b6 0.63 0.628 +3fec5ff48d7f0786 0.89 0.887 +3fe73f710ce6af28 0.73 0.726 +3fcb87e78ca79fd8 0.22 0.215 +3fc4cedae2310e34 0.16 0.163 +3fe57b987b34ecb4 0.67 0.671 +3fe4de45b4c5ee7d 0.65 0.652 +3fe1045f0ea14027 0.53 0.532 +3fe4aaddc1e28c9b 0.65 0.646 +3fd34f9d32af6032 0.3 0.302 +3fee057108a386ab 0.94 0.938 +3fe07209c7f13cb1 0.51 0.514 +3fe0665c1a233767 0.51 0.512 +3fd3bf97f2abf4b6 0.31 0.309 +3fd79a2192c4a1c4 0.37 0.369 +3fe3eeebe30b79e2 0.62 0.623 +3fb0ffdbfdeb34e0 0.07 0.066 +3fea6eadebbe3b38 0.83 0.826 +3fe72cee8c9e2831 0.72 0.724 +3febc88ff2e1bb33 0.87 0.868 +3fcf4cf1834290f0 0.24 0.245 +3fce0b0ea868af60 0.23 0.235 +3fe993e1d52e3083 0.8 0.799 +3f9d87896ab6f360 0.03 0.029 +3fbd365e374c9ee8 0.11 0.114 +3fc73c1a7c5c1e48 0.18 0.182 +3fb159793e794440 0.07 0.068 +3fe93356b49a5fdb 0.79 0.788 +3fe87798a179c636 0.76 0.765 +3fc3a50aad844f64 0.15 0.153 +3fdf124a57db6704 0.49 0.485 +3fee1e5861221d71 0.94 0.941 +3fe21237b257901c 0.56 0.565 +3fe1b48a7e02ac77 0.55 0.553 +3fe9b7365fcc5028 0.8 0.804 +3fd714795370f0d6 0.36 0.361 +3fe6d2645fbb634c 0.71 0.713 +3fb780f8ce7ffee8 0.09 0.092 +3fd9ac6e1a711fd0 0.4 0.401 +3fb27bdbe60f5de8 0.07 0.072 +3fd678ab13321364 0.35 0.351 +3fd17530d0feed58 0.27 0.273 +3fe5db27d20dbfe5 0.68 0.683 +3fc69f06b540fb1c 0.18 0.177 +3fdae1d61bc056ee 0.42 0.42 +3fe31200e084fc4a 0.6 0.596 +3fa4e08b50d3e740 0.04 0.041 +3fdfa356afe47c36 0.49 0.494 +3fe981d04320a99c 0.8 0.797 +3fb332e752be0100 0.07 0.075 +3fb673f5a3477160 0.09 0.088 +3feefd5df1107dea 0.97 0.968 +3fd9978ecae8e05e 0.4 0.4 +3fe23dbfc3b62e53 0.57 0.57 +3fe53d924548dab8 0.66 0.664 +3fe14424d9e30a09 0.54 0.54 +3fdb44d89056d816 0.43 0.426 +3fe7aaa0028cee71 0.74 0.74 +3fd8dde2be0cea98 0.39 0.389 +3fdb844212e0a2d0 0.43 0.43 +3fd8c4d5ea8f14da 0.39 0.387 +3fe82901f45fc6cd 0.76 0.755 +3fe87f1c16844558 0.77 0.766 +3feb86a0b1229789 0.86 0.86 +3fc4033149442c84 0.16 0.156 +3fe8605d2bf5eba2 0.76 0.762 +3fdc0fb1439bc06e 0.44 0.438 +3fd9e04044c545a8 0.4 0.404 +3fe89850d1c05e2e 0.77 0.769 +3feee3b362abd6d3 0.97 0.965 +3fa07d5b28f05410 0.03 0.032 +3feba25a150be84c 0.86 0.864 +3fcb23a8bd9cbff8 0.21 0.212 +3fe129d5e7e1b1a0 0.54 0.536 +3fe3b7083f6c6899 0.62 0.616 +3fcb7fb11cfc4750 0.21 0.215 +3fee1b970f624b38 0.94 0.941 +3fdf8f35fcb22eb0 0.49 0.493 +3fbaaff0d924bac0 0.1 0.104 +3fd17434521daa32 0.27 0.273 +3fddd07efa51dccc 0.47 0.466 +3feb89a17188ce40 0.86 0.861 +3fe602db068c0fa8 0.69 0.688 +3f84aedcb30843c0 0.01 0.01 +3fd5daeafbf3812a 0.34 0.341 +3fed346147482c7d 0.91 0.913 +3fa7afdbd3be0af0 0.05 0.046 +3fe0a7347cf105d7 0.52 0.52 +3fe503165b425143 0.66 0.657 +3fcfef865b3e13ac 0.25 0.249 +3fd4bb81445cf066 0.32 0.324 +3fd19d0ac839b652 0.28 0.275 +3fdccda8e092dd0e 0.45 0.45 +3feeb555388c0c12 0.96 0.96 +3fef03766d7c9d9d 0.97 0.969 +3fd69f3120678734 0.35 0.353 +3fe05173210fcf1b 0.51 0.51 +3f995fb62650aca0 0.02 0.025 +3fe8bfc5eda048b3 0.77 0.773 +3fb07cbace10f938 0.06 0.064 +3fd0b52afc134c08 0.26 0.261 +3fc02e569ffb1490 0.13 0.126 +3fd0fa8627e9f1ea 0.27 0.265 +3fe0728deeb48f62 0.51 0.514 +3fea695a937feda9 0.83 0.825 +3fb105b60ec42670 0.07 0.066 +3f41a48c595e8400 0.0 0.001 +3fea7d9ce349037d 0.83 0.828 +3fd3926341693a36 0.31 0.306 +3fe6110805c2f3da 0.69 0.69 +3fe9b760e75089d4 0.8 0.804 +3fc397b4df1968dc 0.15 0.153 +3feb818789e01a00 0.86 0.86 +3fc1590f4685d5a8 0.14 0.136 +3fd434d69db7b8e8 0.32 0.316 +3fe64c3c33d051cc 0.7 0.697 +3fe4adab5c14c225 0.65 0.646 +3feb5a1a0a1d5b8f 0.85 0.855 +3fee25bf6e70f5f8 0.94 0.942 +3fb21c5cba07a888 0.07 0.071 +3fe74953f11286c4 0.73 0.728 +3feb6d3231b38e80 0.86 0.857 +3fde0f2ae8337ef2 0.47 0.47 +3fd6546d78c9d088 0.35 0.349 +3fec71ff76e4609d 0.89 0.889 +3fd23cafffdfd3c4 0.28 0.285 +3fe7999e29aa2c94 0.74 0.738 +3fdf3ce4826389d6 0.49 0.488 +3fddce5974e89b26 0.47 0.466 +3fedf8b243376e89 0.94 0.937 +3fc625957eabeafc 0.17 0.173 +3fb641a47ddd9fd0 0.09 0.087 +3fe4dad7ab5dc3b0 0.65 0.652 +3fd1e6efbc48ac2a 0.28 0.28 +3fc46f1cbb846a6c 0.16 0.16 +3fd74fb6f60a749c 0.36 0.364 +3feeda00f9f4bf31 0.96 0.964 +3fed367235b28039 0.91 0.913 +3fcaa246af4e2a78 0.21 0.208 +3fe83a57552cfdc4 0.76 0.757 +3fedea43b7fcd4cf 0.93 0.935 +3fdd61a5af99f0fc 0.46 0.459 +3fe919e56a793fe0 0.78 0.784 +3fca8c003e8c04d0 0.21 0.207 +3f6cd683ea621e00 0.0 0.004 +3fec3032a8016733 0.88 0.881 +3fa0f81a01c33d10 0.03 0.033 +3fd999ae93bbfd52 0.4 0.4 +3fe35db3c5c6f053 0.61 0.605 +3febde146e24c20b 0.87 0.871 +3fd8114eae6bd02c 0.38 0.376 +3fd1639eb78e50ea 0.27 0.272 +3fd37dee35264d08 0.3 0.305 +3fe1fe3877ebd210 0.56 0.562 +3fd010fa1d9f261a 0.25 0.251 +3fe5fa851fa683ac 0.69 0.687 +3feef6d149053f20 0.97 0.968 +3fe63df87a2562ef 0.7 0.695 +3fde06f895381c56 0.47 0.469 +3fd5aebd264b4c74 0.34 0.339 +3feb042d1714aa5e 0.84 0.844 +3fefa1d590cd283e 0.99 0.989 +3fda3874e1626d6a 0.41 0.41 +3fc93df743653884 0.2 0.197 +3fef75426c28aeb1 0.98 0.983 +3fd664faa37557d8 0.35 0.35 +3fa14ad739bb4e50 0.03 0.034 +3fd5e74227392400 0.34 0.342 +3fe6cef69b346110 0.71 0.713 +3facfba0c5958120 0.06 0.057 +3fee5b4bf8d50831 0.95 0.949 +3fb08ab52700ecf0 0.06 0.065 +3fe307321fbf7c6a 0.59 0.595 +3febb911e9903d6e 0.87 0.866 +3fecc7827192520c 0.9 0.899 +3fe28002b0ce7703 0.58 0.578 +3fe94beb47d037f0 0.79 0.791 +3fd565cf7fa1da7a 0.33 0.334 +3fec4f1690aa83e4 0.88 0.885 +3fd105fa0da04e32 0.27 0.266 +3febf381f1a1ec18 0.87 0.873 +3fdca696feff98ae 0.45 0.448 +3feac57509aeeef4 0.84 0.837 +3fe03450892e0ad6 0.51 0.506 +3f9cdfe1b8aef840 0.03 0.028 +3fe1dd97682a93bc 0.56 0.558 +3f892133cd9661c0 0.01 0.012 +3fb790b9c42d9a60 0.09 0.092 +3fb0ae1d44e2da90 0.07 0.065 +3fc3a97e49c4b218 0.15 0.154 +3fef44cc82600829 0.98 0.977 +3fd3926680fcaeb8 0.31 0.306 +3fe327eb8c38c496 0.6 0.599 +3fdd41811047d5c6 0.46 0.457 +3fd300655091ddca 0.3 0.297 +3fd164644d264016 0.27 0.272 +3fd9b1443357d490 0.4 0.401 +3fc1c1d02112856c 0.14 0.139 +3fef93e2d3d9f33a 0.99 0.987 +3fe2068513f5d2df 0.56 0.563 +3fddcc1f90c50224 0.47 0.466 +3fd538b3dfb6a008 0.33 0.332 +3faae608f06315f0 0.05 0.053 +3fefd1fabed60c71 0.99 0.994 +3fda2f9816ffe774 0.41 0.409 +3fe8928624e4157a 0.77 0.768 +3fb4b213e2e680e8 0.08 0.081 +3fc27eda4f999f58 0.14 0.144 +3fb19a156c596b08 0.07 0.069 +3febf6e051be6afd 0.87 0.874 +3fefd346cd7044cb 0.99 0.995 +3fd366f6fe92e2a0 0.3 0.303 +3f96bd01bbb1e4a0 0.02 0.022 +3fd713bc98c99766 0.36 0.361 +3fcf5db18220ec20 0.25 0.245 +3fcb855fc98e9a60 0.22 0.215 +3fe3039f2b4d52cb 0.59 0.594 +3fef8af5418f46e5 0.99 0.986 +3fe2c9571fded3f5 0.59 0.587 +3fe40e9c9aff3186 0.63 0.627 +3fe092450ca39ef0 0.52 0.518 +3fc049bb01c64030 0.13 0.127 +3fb5e94eb94ccf80 0.09 0.086 +3fe5cd16ff4689a0 0.68 0.681 +3fe76edb50c01f3d 0.73 0.732 +3fc1f73695b06bbc 0.14 0.14 +3fe9eba7896acfe2 0.81 0.81 +3fd80fc79d7eed8a 0.38 0.376 +3fe8d8da8c97050b 0.78 0.776 +3fd17501cd818bfe 0.27 0.273 +3fba441a8bfd4d00 0.1 0.103 +3fbec1ec7bd2c2b8 0.12 0.12 +3fb0a9e117749538 0.07 0.065 +3fe706c829218944 0.72 0.72 +3fe5136abd591efc 0.66 0.659 +3fafc53a54fef200 0.06 0.062 +3fe64731c61c6e88 0.7 0.696 +3fe9a1c0d28cebfb 0.8 0.801 +3fe44604ee47a1b6 0.63 0.634 +3f8cfa52194c8000 0.01 0.014 +3fc39b2177eed2f8 0.15 0.153 +3fe6609c4756645a 0.7 0.699 +3fda036db3e73c44 0.41 0.406 +3fe4831b6455b793 0.64 0.641 +3fd2ab8c1a88ee2e 0.29 0.292 +3fef7ab7eb77e989 0.98 0.984 +3fa8333d75af46a0 0.05 0.047 +3fe62da9f990e5f9 0.69 0.693 +3fe136e025adb79e 0.54 0.538 +3f9a9b40a8abc680 0.03 0.026 +3fe1b285cbe96da3 0.55 0.553 +3fe7e3ef7ab3e99e 0.75 0.747 +3fe5fa95727534f0 0.69 0.687 +3fd5ca242b38a54c 0.34 0.34 +3fd36b44dcf47220 0.3 0.303 +3fc2ae095179b6b0 0.15 0.146 +3fec4b0dd133ab53 0.88 0.884 +3fca8721c1d43300 0.21 0.207 +3fd1cf214b475b14 0.28 0.278 +3fe616f8c18ee714 0.69 0.69 +3fd24a78708a663c 0.29 0.286 +3fcf7ffdbe61e838 0.25 0.246 +3fe0f4f7a47563f9 0.53 0.53 +3fe4ca9b76980bf3 0.65 0.65 +3fe2c7f30e8d185c 0.59 0.587 +3fe7a3b03742b873 0.74 0.739 +3fda4b62b423e74c 0.41 0.411 +3fecc026876ac384 0.9 0.898 +3fc41b54e4f9a47c 0.16 0.157 +3fea80d47dda7746 0.83 0.828 +3fd972111a70d760 0.4 0.398 +3fe4bb4129e7ca78 0.65 0.648 +3fc87a78d7e32ec0 0.19 0.191 +3fd3f6b50695da46 0.31 0.312 +3fe492c0429e4fc8 0.64 0.643 +3fcb6e4f4b4055cc 0.21 0.214 +3fe4361277bf1a9d 0.63 0.632 +3faba7ba1f6ff330 0.05 0.054 +3fb4ba276984ee80 0.08 0.081 +3fe40ba52541e548 0.63 0.626 +3fcf2e530f71a908 0.24 0.244 +3fdf3e770a6ae7a4 0.49 0.488 +3fa1ffa4ea9f2210 0.04 0.035 +3fe01a3d2c61e9da 0.5 0.503 +3fe2a31ecafa3940 0.58 0.582 +3fb64c1d7fb83758 0.09 0.087 +3fd2010a0bce3932 0.28 0.281 +3fb737cba71b5450 0.09 0.091 +3fe75675c0fc2927 0.73 0.729 +3fdaeac775530040 0.42 0.421 +3fca7b5e1669f524 0.21 0.207 +3fe4466fa9b54634 0.63 0.634 +3feb0290c0d1bc66 0.84 0.844 +3fe6552c87923707 0.7 0.698 +3fd5d6f6112b1f3e 0.34 0.341 +3fd5fc5aa14a03d0 0.34 0.344 +3fe36b298a4c7509 0.61 0.607 +3fe0a9ba720b49f1 0.52 0.521 +3fd6cc0471dc786c 0.36 0.356 +3fcf0cf0f2e31f58 0.24 0.243 +3fe3f2470b74005f 0.62 0.623 +3fb2ea16a9a9b038 0.07 0.074 +3fed0da8bde7f7c6 0.91 0.908 +3fd4975d919b85da 0.32 0.322 +3fe94b8922cac4db 0.79 0.79 +3fde5a5876dbda52 0.47 0.474 +3fbf9d75631da2e8 0.12 0.123 +3fed7ff87b933b47 0.92 0.922 +3fdf9becf476cde4 0.49 0.494 +3fdc088d150af9da 0.44 0.438 +3fe15be111914c4a 0.54 0.542 +3fc2d70e97fa7bc4 0.15 0.147 +3fe0b3be1eb703e3 0.52 0.522 +3fed47d02ec0d2f5 0.92 0.915 +3fe421cc2af5fbd4 0.63 0.629 +3fd2955f2d2d1cec 0.29 0.29 +3fe729ebb7fe4062 0.72 0.724 +3fc381bd451d8b94 0.15 0.152 +3fcbf8bea5b2b478 0.22 0.219 +3fe9aa807c49efc4 0.8 0.802 +3fcfef541efd7f0c 0.25 0.249 +3fec4ff3505a84aa 0.88 0.885 +3fc9d5d63191831c 0.2 0.202 +3fea5473babecd08 0.82 0.823 +3fdb9b66f2dda88e 0.43 0.431 +3fd92e68303c13c8 0.39 0.393 +3fe042786f0539e0 0.51 0.508 +3fe0c802433c5f85 0.52 0.524 +3feb5425604106a9 0.85 0.854 +3fe11ef026efca84 0.54 0.535 +3fe68e1c01a7a57d 0.7 0.705 +3fda1afdf23a4aba 0.41 0.408 +3fc6f4a1b72b3cdc 0.18 0.179 +3fe343094887a02b 0.6 0.602 +3fe84cb36082904b 0.76 0.759 +3fe072f4166ccb25 0.51 0.514 +3fe509eb23c6604c 0.66 0.657 +3fee392178462058 0.94 0.944 +3fdf60cca5c552ca 0.49 0.49 +3f426db7150c0c00 0.0 0.001 +3febacfc1f33fb3f 0.86 0.865 +3fd2b4b556abcc56 0.29 0.292 +3fda60f49aa2f9a2 0.41 0.412 +3fdd4dc91ee68bd2 0.46 0.458 +3fe8602ce841bf4d 0.76 0.762 +3fe5463cd7e2daa8 0.66 0.665 +3fc642bb11633a68 0.17 0.174 +3fed6d5b8a6516ad 0.92 0.92 +3fd1fc8fb81633e0 0.28 0.281 +3fda8421fa4d1e06 0.41 0.414 +3fe6a9d8dbba22e3 0.71 0.708 +3fec2d0240549d6c 0.88 0.88 +3fe9e984b89d4418 0.81 0.81 +3fea7cae21ea211f 0.83 0.828 +3fd3a120eef6e836 0.31 0.307 +3fc42ff53dc7f3bc 0.16 0.158 +3fc79bd0339df51c 0.18 0.184 +3fe3618fd5b379ca 0.61 0.606 +3febc78f93db0d78 0.87 0.868 +3feae5d9a3e5e13f 0.84 0.841 +3fe0868c165c81cd 0.52 0.516 +3fd8d1310a415f20 0.39 0.388 +3fe56c90ac35d5de 0.67 0.67 +3fc03adcd8e81e7c 0.13 0.127 +3f873b85bcd38e00 0.01 0.011 +3fd83938b68c3060 0.38 0.378 +3fe2a1d77a8ecf3a 0.58 0.582 +3fcf088aa85e6fd0 0.24 0.242 +3fe04f7f6d3ed738 0.51 0.51 +3fe358d25dee4160 0.6 0.605 +3fba382cd0550878 0.1 0.102 +3fccc24530f3a4dc 0.22 0.225 +3fc97e3b80df75a4 0.2 0.199 +3fe48300c59ee2de 0.64 0.641 +3fed69114a3f113c 0.92 0.919 +3fda6fee7d2c4aa6 0.41 0.413 +3faa0248c6750260 0.05 0.051 +3fd89eb4e1384246 0.38 0.385 +3fd49b2fe29f7872 0.32 0.322 +3fe0e29e836c5d90 0.53 0.528 +3fee873151cc108d 0.95 0.954 +3fdac8faf57bc272 0.42 0.419 +3fde33c2c70caba8 0.47 0.472 +3fc80c50b5e9a0d0 0.19 0.188 +3fbb2444442b98f8 0.11 0.106 +3fe0119577234830 0.5 0.502 +3fe8e358eb4f77d0 0.78 0.778 +3fdace1f1ff21cbe 0.42 0.419 +3fd45f27fb431922 0.32 0.318 +3fd1d9fc521cadc4 0.28 0.279 +3fd87ba7b9689cea 0.38 0.383 +3fd7445ad11bb7aa 0.36 0.364 +3fd3a33753d49b88 0.31 0.307 +3fe1c72f0d0484fd 0.56 0.556 +3fb68ca4e9528a28 0.09 0.088 +3fc5cb5b3442be7c 0.17 0.17 +3fb426a2665009b8 0.08 0.079 +3fd0b161743c7586 0.26 0.261 +3fee4776d6f14c3f 0.95 0.946 +3fe982bce3ee7636 0.8 0.797 +3fed0ffa142a0b36 0.91 0.908 +3fcb390563771e88 0.21 0.213 +3fd22627b1f3a740 0.28 0.284 +3fe9afa307b74e86 0.8 0.803 +3fd682fda9850f72 0.35 0.352 +3fdd13cc0c67196a 0.45 0.454 +3fb240ac24a73f90 0.07 0.071 +3fe40fc761590a61 0.63 0.627 +3fd0cccd051f32ac 0.26 0.263 +3fe2ea782da095fa 0.59 0.591 +3fe1e7d6cac2e981 0.56 0.56 +3fe2a13866d6cb5d 0.58 0.582 +3fe565adaab7242d 0.67 0.669 +3fb2dd5d943c25f8 0.07 0.074 +3fd9c7cc11b252c6 0.4 0.403 +3fe0b15cdd0d84b9 0.52 0.522 +3febcf18ce91d1cf 0.87 0.869 +3fdfbf3adc080354 0.5 0.496 +3fe33b691a4d76e8 0.6 0.601 +3fe71051d5e2ee8e 0.72 0.721 +3fe24a61c141ec2f 0.57 0.572 +3fc708a39db7f150 0.18 0.18 +3fee2bb1ca22a95d 0.94 0.943 +3fec2aa74c2e0972 0.88 0.88 +3fbdee67b95f3a38 0.12 0.117 +3fc2b3eb09ae0b40 0.15 0.146 +3f86449628f3e2c0 0.01 0.011 +3fecffebced56914 0.91 0.906 +3f984d089a1e9420 0.02 0.024 +3feb2c476edbb3c3 0.85 0.849 +3fe8d01bc6e03d64 0.78 0.775 +3fe5fa657f41a5e5 0.69 0.687 +3feecbd73dbc6de4 0.96 0.962 +3fc015cc97ecdafc 0.13 0.126 +3fd41d9b24c22e6e 0.31 0.314 +3fd21fa783eea072 0.28 0.283 +3fd9f2918e90c4de 0.41 0.405 +3fd7e09a7007f016 0.37 0.373 +3fe220db07bc9c24 0.57 0.567 +3fef02efe9790e72 0.97 0.969 +3fb2b951e52dc838 0.07 0.073 +3fd9e026c8a5f002 0.4 0.404 +3fe9765042780367 0.8 0.796 +3fed3c97fc543538 0.91 0.914 +3fe7696bed737ef2 0.73 0.732 +3fd54b0d7f86a76a 0.33 0.333 +3fd6265a9992613c 0.35 0.346 +3fb818d0f2308f78 0.09 0.094 +3fdb72a867d8bf56 0.43 0.429 +3fe80c718193403d 0.75 0.752 +3fe61417fe628d16 0.69 0.69 +3fed6406816ffc57 0.92 0.918 +3f9dbf75bddf00e0 0.03 0.029 +3fe5c057630b2cc8 0.68 0.68 +3fe6a8cf673b09ba 0.71 0.708 +3fcaed88c4d82598 0.21 0.21 +3fe71df9e4aa3233 0.72 0.722 +3f9e8ed81a9e8ea0 0.03 0.03 +3fdeac7cfff6b6b6 0.48 0.479 +3fe5bb8022b6c62b 0.68 0.679 +3fe86289c42c2e7c 0.76 0.762 +3fde058caa0913b4 0.47 0.469 +3fbf01a8ab3fc4f8 0.12 0.121 +3fddc753821fcc92 0.47 0.465 +3fedf04e0374dca1 0.94 0.936 +3feccbe75caf5fe3 0.9 0.9 +3f92c4c5646e73c0 0.02 0.018 +3fe9d4f4fd327a44 0.81 0.807 +3fdd33351c41dbe6 0.46 0.456 +3fefb2a6771a6555 0.99 0.991 +3fe03ffcebcdbea0 0.51 0.508 +3fa2c349f3604280 0.04 0.037 +3fde9470c57d71fc 0.48 0.478 +3feb20a7d3f36098 0.85 0.848 +3fea4e719fde70d7 0.82 0.822 +3fd184691d6a579c 0.27 0.274 +3fd7ffe25e539a06 0.37 0.375 +3fd361d7a6b0c362 0.3 0.303 +3f926471afdb77e0 0.02 0.018 +3fed783b7a255743 0.92 0.921 +3fcab5c61056a8fc 0.21 0.209 +3fdf942b84d4b5a4 0.49 0.493 +3feb884ed0069562 0.86 0.86 +3fac69391f6bc1c0 0.06 0.055 +3f936a1ed8e086a0 0.02 0.019 +3f94086d66fa3cc0 0.02 0.02 +3fe7f143d6ccf07f 0.75 0.748 +3fed222bfea95f58 0.91 0.91 +3fe8365e958007e9 0.76 0.757 +3fe1493f745f5651 0.54 0.54 +3fd6c4f76beed20c 0.36 0.356 +3fa6c595311f92b0 0.04 0.044 +3fdc17232404043e 0.44 0.439 +3fe237076c8bd805 0.57 0.569 +3fddc5291d18bc32 0.47 0.465 +3fe54209b44a5a17 0.66 0.664 +3fe904938feca30c 0.78 0.782 +3fe6d8e59af9460e 0.71 0.714 +3fdf78011fdfe198 0.49 0.492 +3fe17b1aab796103 0.55 0.546 +3fe86fe3fa8bced1 0.76 0.764 +3faabbe29aa16730 0.05 0.052 +3fe437a7b8f31a78 0.63 0.632 +3fcce25394a30f0c 0.23 0.226 +3fd6207ac3ec5ff4 0.35 0.346 +3fc3c65a2e6a0204 0.15 0.154 +3fdf24cad8d8b3c4 0.49 0.487 +3fe1b597096ab5ab 0.55 0.553 +3fe03c9b568f01b4 0.51 0.507 +3fe601805b4a6dbe 0.69 0.688 +3fe30a10447f92d7 0.59 0.595 +3fe83f82bc9a5fd8 0.76 0.758 +3fcbb902d6fd2b6c 0.22 0.217 +3fe95a327d655c58 0.79 0.792 +3fe887102389502c 0.77 0.766 +3fcfffe7c92c75d0 0.25 0.25 +3fe02fc0f8a1e893 0.51 0.506 +3fcf341627574124 0.24 0.244 +3fe273a7772dfe37 0.58 0.577 +3fd8b99cae041920 0.39 0.386 +3fdd5fa03d5b67d6 0.46 0.459 +3fe7c1fe091a3104 0.74 0.742 +3f9d1118798e8e80 0.03 0.028 +3fe45cb41e1eca95 0.64 0.636 +3fec6108fc891456 0.89 0.887 +3feef592f00040a5 0.97 0.967 +3f920e47077e0bc0 0.02 0.018 +3fd05b8381758666 0.26 0.256 +3fe1f2228eda5ed3 0.56 0.561 +3fb998813a468448 0.1 0.1 +3fd94c35e986cc5e 0.4 0.395 +3fed06d6eed534ca 0.91 0.907 +3fe3b2c858c830fb 0.62 0.616 +3fe38ae98b687c66 0.61 0.611 +3fe3a79c677e3371 0.61 0.614 +3fd5eaf75ab620ee 0.34 0.342 +3fdb8576d7ce2c58 0.43 0.43 +3fdc5f2052c1158e 0.44 0.443 +3fce011986172ac0 0.23 0.234 +3fd313130b6d03d6 0.3 0.298 +3fc953fee24cba14 0.2 0.198 +3fe0a6d68570ed6a 0.52 0.52 +3fea3e10d36e6dbd 0.82 0.82 +3fbb2adf89e5d6c8 0.11 0.106 +3fe09e2b36ef2264 0.52 0.519 +3feaf2261af9ee52 0.84 0.842 +3fd32ca93884d9dc 0.3 0.3 +3fe66d93c5bab53e 0.7 0.701 +3feb105798865ce9 0.85 0.846 +3fef17a9112280ca 0.97 0.972 +3fec7b5ce06bbfd7 0.89 0.89 +3fe2858a032a65dc 0.58 0.579 +3fee50c870ae7857 0.95 0.947 +3fdf5a68519745d0 0.49 0.49 +3fee919b8bbef724 0.96 0.955 +3fd6e19dda24328a 0.36 0.358 +3fea534dc93a733b 0.82 0.823 +3fd82cb841ae9970 0.38 0.378 +3fbdd757be96f928 0.12 0.117 +3fe10b7c3b0931e5 0.53 0.533 +3fd8b124460081fe 0.39 0.386 +3fdfc0c835acc04a 0.5 0.496 +3fe9df6faed7cf39 0.81 0.809 +3fec42c0381af07a 0.88 0.883 +3fc6215855057f1c 0.17 0.173 +3fead2704183f500 0.84 0.838 +3feba88b98570f17 0.86 0.864 +3fe91d3648f7868d 0.78 0.785 +3fea86ee7886d80d 0.83 0.829 +3fddedee3039a5ba 0.47 0.468 +3fe82860451c7dfb 0.75 0.755 +3fea0d88fcc0c573 0.81 0.814 +3fe18f9af7959b73 0.55 0.549 +3fea481c9a8e7967 0.82 0.821 +3fd417d17f228202 0.31 0.314 +3fe2eb30249e3405 0.59 0.591 +3fe22f35c9f6228b 0.57 0.568 +3fd0a6d0d458a508 0.26 0.26 +3fca48438e461d08 0.21 0.205 +3fc06dee8a2261d4 0.13 0.128 +3feff199a91a8c8f 1.0 0.998 +3fba78bac3f62a20 0.1 0.103 +3fd16d1614535f38 0.27 0.272 +3feb637a7d68547c 0.86 0.856 +3feb250df6a7098e 0.85 0.848 +3fc92d5fd7a434f4 0.2 0.197 +3fb4ac852e4ae630 0.08 0.081 +3fcf9685e8d73464 0.25 0.247 +3fd0bc56a25832a4 0.26 0.261 +3fe1b854f354babb 0.55 0.554 +3fed8fc8d949dd14 0.92 0.924 +3fef44bda1c2f053 0.98 0.977 +3fdf108e1f9eaae4 0.49 0.485 +3fd94e4625fdc396 0.4 0.395 +3fe249996a480c82 0.57 0.571 +3fd380e672c1cc5c 0.3 0.305 +3fdc11478451854c 0.44 0.439 +3fd7b41407547c66 0.37 0.37 +3fd4f82a5b57a152 0.33 0.328 +3febe36565274068 0.87 0.872 +3f8d3f663804a080 0.01 0.014 +3fc92de6a98fa2b0 0.2 0.197 +3f436390c62d0800 0.0 0.001 +3fe0f9f76612e90d 0.53 0.531 +3fbddf0be20aa930 0.12 0.117 +3fe1368c3c5b931a 0.54 0.538 +3feec533ae717b6e 0.96 0.962 +3fab29e4d7817b80 0.05 0.053 +3fb866e600e72b50 0.1 0.095 +3fee38ee641be7a7 0.94 0.944 +3fca72682ffa5f54 0.21 0.207 +3fdd9a9bf3f28496 0.46 0.463 +3fe9c2055ba2cc43 0.8 0.805 +3fde28e367d7e018 0.47 0.471 +3fd4f775c054c402 0.33 0.328 +3fc175a0c036f9c4 0.14 0.136 +3fc6e2974f902680 0.18 0.179 +3f85c50f1d3246c0 0.01 0.011 +3fee04da96f6209f 0.94 0.938 +3fe0038a8ebb20b5 0.5 0.5 +3fea53e61fc9aa19 0.82 0.823 +3fe8628b2d7931d7 0.76 0.762 +3fd2f159a960b1d2 0.3 0.296 +3fc685983fd8237c 0.18 0.176 +3fef09b18ef3ff6d 0.97 0.97 +3fe2dd18e648edb2 0.59 0.589 +3fe86ba31b9bb48f 0.76 0.763 +3fe3207c21b0cdbb 0.6 0.598 +3fe79a8bf6768c2c 0.74 0.738 +3fcf45cc53c604c4 0.24 0.244 +3fe7c6de0a46ccd9 0.74 0.743 +3fe8f13594a298c8 0.78 0.779 +3fc352f504f2d8c0 0.15 0.151 +3fd6c38229897a2e 0.36 0.356 +3febf937a9388e54 0.87 0.874 +3fdd6de26cae2336 0.46 0.46 +3fe7d978396c832d 0.75 0.745 +3fe78b6bfd1a7308 0.74 0.736 +3fe94e83f580c8fe 0.79 0.791 +3feb3100e791aaf8 0.85 0.85 +3fea9e5c42f2b602 0.83 0.832 +3fec08a39bce58a3 0.88 0.876 +3feca54c698bd727 0.9 0.895 +3fd82e612a48e440 0.38 0.378 +3fc91e1fe9896590 0.2 0.196 +3fe4b9a26eb884ba 0.65 0.648 +3fdf0a072f45f38a 0.48 0.485 +3fe729a3e7687378 0.72 0.724 +3fe883779dfbc347 0.77 0.766 +3fe5f0f8f691ac08 0.69 0.686 +3fe5b1c132d21d51 0.68 0.678 +3fe6fb9d1573fd71 0.72 0.718 +3feafb5c45bcbab8 0.84 0.843 +3fea33864dac1f79 0.82 0.819 +3fcf9ca16967e458 0.25 0.247 +3fbd4cb94531f4d0 0.11 0.114 +3fcc84e495a14058 0.22 0.223 +3fae3e29abad2230 0.06 0.059 +3fd74f0f2bc76246 0.36 0.364 +3fe78e1d73b13cbd 0.74 0.736 +3fee989914bd7e3b 0.96 0.956 +3fc9b1a2602314bc 0.2 0.201 +3fef9fe217792d12 0.99 0.988 +3fd63f695ab14f24 0.35 0.348 +3fbbe7da281f7758 0.11 0.109 +3fc80af6e6a26dd0 0.19 0.188 +3fe9c54799b7bdb0 0.81 0.805 +3fdbbff2cb0275a8 0.43 0.434 +3fe0dc861ece32b4 0.53 0.527 +3faa346c13e18330 0.05 0.051 +3fa0a205322aa090 0.03 0.032 +3fd3626513ddc910 0.3 0.303 +3fe7c27e9c69b119 0.74 0.742 +3fe0c56c7d7cc215 0.52 0.524 +3fe501aa5014b62a 0.66 0.656 +3fd589cf80126e9c 0.34 0.337 +3fe245ef48f6c69a 0.57 0.571 +3fda7b0a09b2beca 0.41 0.414 +3fb373078d04fc98 0.08 0.076 +3fd41db0a51f5fb6 0.31 0.314 +3febe2cd9251df2a 0.87 0.871 +3fcbcfb3763bb8b4 0.22 0.217 +3fdadf63589ef096 0.42 0.42 +3fd5c8a948cc0dac 0.34 0.34 +3feb9e08124ae764 0.86 0.863 +3fdfc774387833dc 0.5 0.497 +3fe9c780056d6d4e 0.81 0.806 +3fd78f2f303283f6 0.37 0.368 +3fde96b2323e9cac 0.48 0.478 +3fcfef4d33aaf618 0.25 0.249 +3fdb15f6ed520212 0.42 0.423 +3fe5c562ab393b4a 0.68 0.68 +3fe3ea683744d6b1 0.62 0.622 +3fe6461773c5ccb0 0.7 0.696 +3faa5dc4a912afe0 0.05 0.051 +3feb39361f294978 0.85 0.851 +3fc4ebf43148c344 0.16 0.163 +3fecd665502adf41 0.9 0.901 +3fec556b80d0e1c6 0.89 0.885 +3fc601373e4d3450 0.17 0.172 +3fe29db0c0e750aa 0.58 0.582 +3fedd1567ad77c16 0.93 0.932 +3fb96f838f000080 0.1 0.099 +3fdfc3eebdddc7e2 0.5 0.496 +3f62b1e9ba67b700 0.0 0.002 +3fcdb9fa22aebd8c 0.23 0.232 +3fef95457db88596 0.99 0.987 +3fe386b22419f053 0.61 0.61 +3fe60b4a32b35b9a 0.69 0.689 +3fd0641c7bb32036 0.26 0.256 +3fb1a52322642b18 0.07 0.069 +3fe62482c4072ad7 0.69 0.692 +3fe4933d410cd22f 0.64 0.643 +3fa50ce9219ed0f0 0.04 0.041 +3fd6dca9c927382a 0.36 0.357 +3fe34341f72853e2 0.6 0.602 +3fe800607610a997 0.75 0.75 +3fd6503a82cf821e 0.35 0.349 +3fe4db39d156673c 0.65 0.652 +3fed30a3ebfb4fb3 0.91 0.912 +3fb1fd6ee4a6da60 0.07 0.07 +3feb9d3102a5d713 0.86 0.863 +3fe85cee93b1fa69 0.76 0.761 +3fcc00a113a88c0c 0.22 0.219 +3feeab29f09255bb 0.96 0.958 +3fca2fcb0f8e3270 0.2 0.205 +3fd7c799f33382ec 0.37 0.372 +3fc7d0ae689ede54 0.19 0.186 +3fcca92484895114 0.22 0.224 +3fe151955e14652a 0.54 0.541 +3fef345763fbecfb 0.98 0.975 +3fec27c6d6c79545 0.88 0.88 +3fe0b2214d41dfbe 0.52 0.522 +3fec58faf5c43391 0.89 0.886 +3f7e6d99c5c5f980 0.01 0.007 +3fe8c069164a4942 0.77 0.773 +3fdad0ead6881d8e 0.42 0.419 +3fc9d730c9ed2150 0.2 0.202 +3fe7c3a6d877ecca 0.74 0.743 +3fc8414a127e5c94 0.19 0.189 +3fe3df5fcbf3d909 0.62 0.621 +3fd74fd523b6fbda 0.36 0.364 +3fccbeec03fbe3c0 0.22 0.225 +3fe0590d2a8899e0 0.51 0.511 +3fba4140291edfe8 0.1 0.103 +3fde1fced6a92756 0.47 0.471 +3fe1925d88ba5d5f 0.55 0.549 +3fde744cc740e9f0 0.48 0.476 +3fe08a90380ed60b 0.52 0.517 +3fe3e0cb03839a50 0.62 0.621 +3fe0db9116f726b8 0.53 0.527 +3fd8f76d99d3cec4 0.39 0.39 +3fd0dc97dc6c7db4 0.26 0.263 +3fe83fbcaf74a541 0.76 0.758 +3fc0c572c4925e44 0.13 0.131 +3fe8aa5f06ef9552 0.77 0.771 +3fe3db527ab6216b 0.62 0.621 +3fe36b752b29ca3e 0.61 0.607 +3feffe629f786a3c 1.0 1.0 +3fcd25de3c94f74c 0.23 0.228 +3fe701cecf176b68 0.72 0.719 +3fd26a8d4b10146a 0.29 0.288 +3febea211214a7f9 0.87 0.872 +3fe7c99cbc15a4d4 0.74 0.743 +3fd5023cce4e9a58 0.33 0.328 +3fde09bc023fa682 0.47 0.469 +3fc64e6cfbf1d4b4 0.17 0.174 +3fd11970724f3e74 0.27 0.267 +3faa3a7a9bc709d0 0.05 0.051 +3fcbac94d08f9b38 0.22 0.216 +3fd1f048905fb8e6 0.28 0.28 +3fef1ddfbdf750cd 0.97 0.972 +3fd1bcbc2f121992 0.28 0.277 +3fe945402c86f68e 0.79 0.79 +3fed429968694908 0.91 0.914 +3fc3485301b1b818 0.15 0.151 +3fb403904280c960 0.08 0.078 +3fae5446772d9ab0 0.06 0.059 +3feab333286eace2 0.83 0.834 +3fd2f02550e1d9ca 0.3 0.296 +3fbb9216767137c0 0.11 0.108 +3f81f05c62fa8380 0.01 0.009 +3fe72d158ae0d050 0.72 0.724 +3fee7ae7c419aebe 0.95 0.953 +3fed8fcd0dedf4a4 0.92 0.924 +3fe6dbca9f1b20b9 0.71 0.714 +3fd97c5d136f4150 0.4 0.398 +3fdaa1034809e59a 0.42 0.416 +3fecdaa7528a14b1 0.9 0.902 +3fd14aa7cfcb84fa 0.27 0.27 +3fa589e62d8b21c0 0.04 0.042 +3fe5f685f8c0fed8 0.69 0.686 +3fe2db6cbdfb2413 0.59 0.589 +3fea304a69075c2d 0.82 0.818 +3febb06813a9f23f 0.87 0.865 +3fea5fc26f453cd4 0.82 0.824 +3fa940238b6f98f0 0.05 0.049 +3fbbc77c33af7d98 0.11 0.109 +3fe4cd5006869477 0.65 0.65 +3fef07a90f89f5e4 0.97 0.97 +3f9a855541d66d80 0.03 0.026 +3fd4994fa9498a0a 0.32 0.322 +3fe3486d0bc66361 0.6 0.603 +3fede5462e59b221 0.93 0.934 +3fc1474fec3ac604 0.13 0.135 +3fdcf4a938fc7a8e 0.45 0.452 +3fd6c81f6d2711a8 0.36 0.356 +3fb09509f5a05420 0.06 0.065 +3fe71a96ccf9fdd1 0.72 0.722 +3feed3ce70629d66 0.96 0.963 +3fce6b4e761d4e98 0.24 0.238 +3fd1a6e02dc4fce0 0.28 0.276 +3fe228ae5a652dbf 0.57 0.567 +3fc33d2f9f156584 0.15 0.15 +3fdb7339b8227c46 0.43 0.429 +3f9acd33c5009ac0 0.03 0.026 +3fba9a49ad432f38 0.1 0.104 +3fd39613703ed784 0.31 0.306 +3fe6539a35674f36 0.7 0.698 +3fda4f6f98a66c4e 0.41 0.411 +3fe09c81194c0616 0.52 0.519 +3fe4e6818d7a2383 0.65 0.653 +3fd1349657ae86d0 0.27 0.269 +3fe914b7d4a96315 0.78 0.784 +3fc154ddc1eba698 0.14 0.135 +3fe94779fddc99df 0.79 0.79 +3fe345d988946d5e 0.6 0.602 +3fe16c8911c1d778 0.54 0.544 +3fca18076205a444 0.2 0.204 +3fe01db5839a4fc5 0.5 0.504 +3fc1fe95960a9d14 0.14 0.141 +3fe7ce9fb308b886 0.74 0.744 +3fe048f7407512e4 0.51 0.509 +3febbd1837adf80a 0.87 0.867 +3fc76e8e693b44ac 0.18 0.183 +3fefa432dac3af3f 0.99 0.989 +3facbedafecbfbc0 0.06 0.056 +3feca3acd8aa62a1 0.89 0.895 +3fe69eccb6c2a566 0.71 0.707 +3fe21960c070e160 0.57 0.566 +3fea5819bc390108 0.82 0.823 +3f560a4bc25fb800 0.0 0.001 +3fe7b532ebaaade0 0.74 0.741 +3fe6bb13947f73f4 0.71 0.71 +3fe3a75b54cc345e 0.61 0.614 +3fd3fd9946a3d30c 0.31 0.312 +3fda5e016d88f798 0.41 0.412 +3fe8c51a2414cf74 0.77 0.774 +3fdb9f1c63b8acd2 0.43 0.432 +3fe1c16a83f376d7 0.55 0.555 +3feee8dbd7396327 0.97 0.966 +3f8bfa3ad0b28940 0.01 0.014 +3feb06197e7ee2b1 0.84 0.844 +3fe27bbbf52f456f 0.58 0.578 +3fdc362b0ab475f4 0.44 0.441 +3fd492e470c7ace6 0.32 0.321 +3fd191816c622526 0.27 0.275 +3f93c3365a2fcac0 0.02 0.019 +3fd130fccb2ca2c6 0.27 0.269 +3fe9fc16caf80270 0.81 0.812 +3fee321eb1c0e1dd 0.94 0.944 +3fb36419d96d5480 0.08 0.076 +3fe5e4e5b7520f05 0.68 0.684 +3fba55b58fba62a0 0.1 0.103 +3fdc9582a3e644fc 0.45 0.447 +3fb79b54a631d368 0.09 0.092 +3fe0d9558f121a52 0.53 0.527 +3fd83d92c5cf111e 0.38 0.379 +3fed90c4cb2a93a8 0.92 0.924 +3fe522699cd15f56 0.66 0.66 +3fe8e4745e9bcccb 0.78 0.778 +3fdb6a0b9caa879a 0.43 0.428 +3fd09934d4acf4e6 0.26 0.259 +3fb9c75e77004618 0.1 0.101 +3fe6b1ca5a9ee24d 0.71 0.709 +3fe88978c269f7e5 0.77 0.767 +3feb514c6fba019d 0.85 0.854 +3fd2f3dc81f09360 0.3 0.296 +3fedaf2f7918b80d 0.93 0.928 +3fe0a5475cdbf673 0.52 0.52 +3fe943a64c445d3c 0.79 0.79 +3fe576ca961a892b 0.67 0.671 +3fe91a6438521640 0.78 0.784 +3fd896f84affb3b8 0.38 0.384 +3fef20f8667b9ef4 0.97 0.973 +3fea984c0454d0d4 0.83 0.831 +3fc83c31bfb18134 0.19 0.189 +3fd95005fc62473a 0.4 0.396 +3feb190818aca541 0.85 0.847 +3fd92621bf9db18a 0.39 0.393 +3fbc64f530a48e58 0.11 0.111 +3fe011584e7a203c 0.5 0.502 +3fe856af4fbc740f 0.76 0.761 +3feb72d6c4280979 0.86 0.858 +3f9ce057f744bd00 0.03 0.028 +3fef18493e1d701c 0.97 0.972 +3fd5c8d7ff4607c8 0.34 0.34 +3fef2c373585042a 0.97 0.974 +3fdee77f895a4f84 0.48 0.483 +3fb2763c9f4a21c0 0.07 0.072 +3fdb8f3b1d2fc20c 0.43 0.431 +3fd83e9fd8ee9cde 0.38 0.379 +3f799315cd55dc80 0.01 0.006 +3fc2edb4d8674bac 0.15 0.148 +3feec93d4d761cdb 0.96 0.962 +3fbea1f3da85b448 0.12 0.12 +3fe150c8ecf4d260 0.54 0.541 +3fe332326431e890 0.6 0.6 +3fc94c3050dbb6a4 0.2 0.198 +3fc35263f8f5b2e8 0.15 0.151 +3fe3ff0d67afe3d9 0.62 0.625 +3fe4dbbe50e04de7 0.65 0.652 +3fd86e55a6f4c688 0.38 0.382 +3febb0460f3206f5 0.87 0.865 +3fcb2eff8e38f3ac 0.21 0.212 +3fefd68ba4400f27 0.99 0.995 +3faf180abc03a8e0 0.06 0.061 +3fe7c770d73749b2 0.74 0.743 +3fea0ba2c2daad70 0.81 0.814 +3fec89a16e80f79c 0.89 0.892 +3fc15f1ceb92f258 0.14 0.136 +3fe5812919d0691f 0.67 0.672 +3fe0bcf3e924a950 0.52 0.523 +3fe48452a6d31308 0.64 0.641 +3fe8af6d2bca2372 0.77 0.771 +3fe5b8f9781bbf29 0.68 0.679 +3fcd64f552b1740c 0.23 0.23 +3fdd59eedd1c865c 0.46 0.459 +3fe35aaca96802bb 0.6 0.605 +3fd6f8a67f42594e 0.36 0.359 +3fd87bd5d2c07b5e 0.38 0.383 +3fec4920e4b34650 0.88 0.884 +3fe31ce83b656ce8 0.6 0.597 +3fef74f6fe910a38 0.98 0.983 +3fe3c8805c9694b4 0.62 0.618 +3fc85b69529c0a74 0.19 0.19 +3fe299be108c5c86 0.58 0.581 +3fe9001086603404 0.78 0.781 +3fe6d15b41ec0d44 0.71 0.713 +3fe431d43e9388be 0.63 0.631 +3fda1539ac60519e 0.41 0.408 +3fef70cb951a9022 0.98 0.983 +3fb18c34310be288 0.07 0.069 +3fd1a4076d2bca3c 0.28 0.276 +3f928e0e73d71480 0.02 0.018 +3fdf57f1896d4ae6 0.49 0.49 +3fb61a85402e1ba0 0.09 0.086 +3fedae57cc2b143d 0.93 0.928 +3fecb414b4c3b095 0.9 0.897 +3fa2f679655b8500 0.04 0.037 +3fdca4e4febabeb6 0.45 0.448 +3fe233cb8be4adc4 0.57 0.569 +3fed04c13f6d790e 0.91 0.907 +3fe46f6fbb2960ce 0.64 0.639 +3fe5fb0141894f69 0.69 0.687 +3fea97559f0fcb13 0.83 0.831 +3fd74ac2ebc3c9ba 0.36 0.364 +3fe59923c8af353c 0.67 0.675 +3fe06352dc8c1269 0.51 0.512 +3fd32d3ad1875d48 0.3 0.3 +3fe8aa13a700005d 0.77 0.771 +3fc4c4875579c83c 0.16 0.162 +3fe39a8c8c1d3a56 0.61 0.613 +3fe87ff740848fc3 0.77 0.766 +3fc9c4845e9b17ec 0.2 0.201 +3fe3cc502fcf880d 0.62 0.619 +3fe3697637ee59bc 0.61 0.607 +3fddd3928cab2176 0.47 0.466 +3fe3501801d01549 0.6 0.604 +3fdb60a1431924ac 0.43 0.428 +3fe25a2836c8094b 0.57 0.574 +3febdc06aa1eb774 0.87 0.871 +3fe740a73e433c06 0.73 0.727 +3fe00369ec7a2bb1 0.5 0.5 +3fe67393e9ca8161 0.7 0.702 +3fd7918c7e3609e6 0.37 0.368 +3fdc0d097ef626a8 0.44 0.438 +3fea3adc35015a13 0.82 0.82 +3fedbecf32c496d3 0.93 0.93 +3fe0508b84109800 0.51 0.51 +3fb88a4b8f463ef0 0.1 0.096 +3fdeacdd3b7e9d1a 0.48 0.479 +3fe27348bdb2efca 0.58 0.577 +3fbefd75dbe8d8b0 0.12 0.121 +3fe18d5aa2d89282 0.55 0.549 +3fdaa0234b243590 0.42 0.416 +3fe5ebc4602cbabe 0.69 0.685 +3fbf0894d17f86f0 0.12 0.121 +3fd29cc175374f9a 0.29 0.291 +3fd73586a5571c70 0.36 0.363 +3fba5c90811fdb30 0.1 0.103 +3fc62e4757f7d078 0.17 0.173 +3fe1a08ee5888b4d 0.55 0.551 +3fcb560ca9eff800 0.21 0.214 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..c775577 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 120 +use_small_heuristics = "Max" From e6994efa0196c708175dd6880b297a1d5546f8e4 Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:25:25 +0200 Subject: [PATCH 4/9] Trust bundled root certificates when the system has none Slim container images such as debian:bookworm-slim ship without CA certificates, and every request failed there. The client still prefers the operating system's store, so private CAs keep working. The crate is published as jev-api, since jev is taken on crates.io; its library is still named jev. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- crates/jev/Cargo.toml | 9 ++++++++- crates/jev/src/client.rs | 14 +++++++++++++- crates/lintus/Cargo.toml | 2 +- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c6b3e4..6f88537 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,10 +481,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "jev" +name = "jev-api" version = "0.1.0" dependencies = [ "indexmap", + "rustls-native-certs", "serde", "serde_json", "tiny_http", @@ -552,7 +553,7 @@ version = "0.3.0" dependencies = [ "clap", "indexmap", - "jev", + "jev-api", "rpassword", "serde_json", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 063b663..68fc54f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ authors = ["Vincent Rolea"] repository = "https://github.com/virolea/lintus" [workspace.dependencies] -jev = { path = "crates/jev", version = "0.1.0" } +jev = { package = "jev-api", path = "crates/jev", version = "0.1.0" } indexmap = "2" serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["preserve_order"] } diff --git a/crates/jev/Cargo.toml b/crates/jev/Cargo.toml index d6aac1e..a4083ff 100644 --- a/crates/jev/Cargo.toml +++ b/crates/jev/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "jev" +name = "jev-api" version = "0.1.0" description = "A typed client for the Typesafe Jev model API." edition.workspace = true @@ -13,5 +13,12 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["preserve_order"] } ureq = { version = "3", default-features = false, features = ["rustls", "platform-verifier", "json"] } +# To tell whether the system has CA certificates, the way the platform verifier looks for them. +[target.'cfg(all(unix, not(target_os = "android"), not(target_vendor = "apple")))'.dependencies] +rustls-native-certs = "0.8" + +[lib] +name = "jev" + [dev-dependencies] tiny_http.workspace = true diff --git a/crates/jev/src/client.rs b/crates/jev/src/client.rs index 72fe427..430fbec 100644 --- a/crates/jev/src/client.rs +++ b/crates/jev/src/client.rs @@ -27,7 +27,7 @@ impl Client { return Err(Error::MissingApiKey); } - let tls = ureq::tls::TlsConfig::builder().root_certs(ureq::tls::RootCerts::PlatformVerifier).build(); + let tls = ureq::tls::TlsConfig::builder().root_certs(root_certs()).build(); let agent = ureq::Agent::config_builder() .http_status_as_error(false) .timeout_connect(Some(Duration::from_secs(60))) @@ -78,6 +78,18 @@ impl Client { } } +/// The operating system's certificate store, so that corporate proxies and +/// private CAs the machine trusts are trusted here too. A Linux system with no +/// CA certificates at all, such as a slim container image, gets the Mozilla +/// roots bundled in the binary instead. +fn root_certs() -> ureq::tls::RootCerts { + #[cfg(all(unix, not(target_os = "android"), not(target_vendor = "apple")))] + if rustls_native_certs::load_native_certs().certs.is_empty() { + return ureq::tls::RootCerts::WebPki; + } + ureq::tls::RootCerts::PlatformVerifier +} + impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Client").field("api_url", &self.api_url).field("api_key", &"[redacted]").finish() diff --git a/crates/lintus/Cargo.toml b/crates/lintus/Cargo.toml index bf149a0..1a5c4e4 100644 --- a/crates/lintus/Cargo.toml +++ b/crates/lintus/Cargo.toml @@ -11,7 +11,7 @@ repository.workspace = true [dependencies] clap = { version = "4.6.7", features = ["derive", "wrap_help"] } indexmap.workspace = true -jev = { version = "0.1.0", path = "../jev" } +jev.workspace = true rpassword = "7.5.4" serde_json.workspace = true yaml-rust2 = "0.13.0" From b1ebc388bd5fd1bb01654a050c6a59ea665a8162 Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:26:34 +0200 Subject: [PATCH 5/9] Move the lintus crate to the repository root pre-commit installs a language: rust hook with `cargo install --path .` at the repository root, which needs a package there rather than a bare workspace. The hook now builds lintus from source, with a Rust toolchain of its own if the machine has none; checked with `pre-commit try-repo`. The conformance harness no longer relies on Unix-only paths, so the suite can run on Windows. Co-Authored-By: Claude Opus 5.5 --- .pre-commit-hooks.yaml | 11 +++--- Cargo.toml | 36 ++++++++++++++++++- crates/lintus/Cargo.toml | 22 ------------ {crates/lintus/src => src}/cli.rs | 0 {crates/lintus/src => src}/config.rs | 0 {crates/lintus/src => src}/credentials.rs | 0 {crates/lintus/src => src}/error.rs | 0 {crates/lintus/src => src}/files.rs | 0 {crates/lintus/src => src}/format.rs | 0 {crates/lintus/src => src}/git.rs | 0 {crates/lintus/src => src}/glob.rs | 0 {crates/lintus/src => src}/main.rs | 0 {crates/lintus/src => src}/paths.rs | 0 {crates/lintus/src => src}/progress.rs | 0 {crates/lintus/src => src}/report.rs | 0 {crates/lintus/src => src}/runner.rs | 0 {crates/lintus/src => src}/template.rs | 0 {crates/lintus/src => src}/yaml.rs | 0 test/support/lintus-ruby | 2 +- {crates/lintus/tests => tests}/auth.rs | 0 .../lintus/tests => tests}/conformance/cli.rs | 0 .../tests => tests}/conformance/config.rs | 0 .../tests => tests}/conformance/files.rs | 0 .../tests => tests}/conformance/main.rs | 0 .../tests => tests}/conformance/output.rs | 11 ++++-- .../tests => tests}/conformance/runner.rs | 0 .../tests => tests}/conformance/support.rs | 19 +++++++--- .../fixtures/generate_glob_cases.rb | 2 +- .../fixtures/generate_round_cases.rb | 2 +- .../tests => tests}/fixtures/glob_cases.tsv | 0 .../tests => tests}/fixtures/round_cases.tsv | 0 31 files changed, 67 insertions(+), 38 deletions(-) delete mode 100644 crates/lintus/Cargo.toml rename {crates/lintus/src => src}/cli.rs (100%) rename {crates/lintus/src => src}/config.rs (100%) rename {crates/lintus/src => src}/credentials.rs (100%) rename {crates/lintus/src => src}/error.rs (100%) rename {crates/lintus/src => src}/files.rs (100%) rename {crates/lintus/src => src}/format.rs (100%) rename {crates/lintus/src => src}/git.rs (100%) rename {crates/lintus/src => src}/glob.rs (100%) rename {crates/lintus/src => src}/main.rs (100%) rename {crates/lintus/src => src}/paths.rs (100%) rename {crates/lintus/src => src}/progress.rs (100%) rename {crates/lintus/src => src}/report.rs (100%) rename {crates/lintus/src => src}/runner.rs (100%) rename {crates/lintus/src => src}/template.rs (100%) rename {crates/lintus/src => src}/yaml.rs (100%) rename {crates/lintus/tests => tests}/auth.rs (100%) rename {crates/lintus/tests => tests}/conformance/cli.rs (100%) rename {crates/lintus/tests => tests}/conformance/config.rs (100%) rename {crates/lintus/tests => tests}/conformance/files.rs (100%) rename {crates/lintus/tests => tests}/conformance/main.rs (100%) rename {crates/lintus/tests => tests}/conformance/output.rs (94%) rename {crates/lintus/tests => tests}/conformance/runner.rs (100%) rename {crates/lintus/tests => tests}/conformance/support.rs (94%) rename {crates/lintus/tests => tests}/fixtures/generate_glob_cases.rb (97%) rename {crates/lintus/tests => tests}/fixtures/generate_round_cases.rb (94%) rename {crates/lintus/tests => tests}/fixtures/glob_cases.tsv (100%) rename {crates/lintus/tests => tests}/fixtures/round_cases.tsv (100%) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 4177b89..183ed5b 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -2,17 +2,18 @@ # # repos: # - repo: https://github.com/virolea/lintus -# rev: v0.1.0 +# rev: v0.3.0 # hooks: # - id: lintus # -# pre-commit passes the staged file names as arguments, so only the files in -# the commit are sent to the model. JEV_API_KEY must be set in the environment. +# pre-commit builds lintus from source the first time (it installs a Rust +# toolchain for that if the machine has none), then reuses the build. It passes +# the staged file names as arguments, so only the files in the commit are sent +# to the model. The API key comes from JEV_API_KEY or `lintus auth login`. - id: lintus name: lintus description: Lint staged files against the plain-language rules in .lintus.yml, using the Jev model. entry: lintus - language: ruby + language: rust pass_filenames: true require_serial: true - additional_dependencies: [] diff --git a/Cargo.toml b/Cargo.toml index 68fc54f..2ca1c21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,32 @@ +[package] +name = "lintus" +version = "0.3.0" +description = "A linter whose rules are plain-language questions, answered by the Jev model." +readme = "README.md" +keywords = ["linter", "llm", "code-review", "ci"] +categories = ["command-line-utilities", "development-tools"] +include = ["src/**", "Cargo.toml", "README.md", "CHANGELOG.md", "LICENSE.txt"] +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true + +[dependencies] +clap = { version = "4.6.7", features = ["derive", "wrap_help"] } +indexmap.workspace = true +jev.workspace = true +rpassword = "7.5.4" +serde_json.workspace = true +yaml-rust2 = "0.13.0" + +[dev-dependencies] +serde_json.workspace = true +tempfile.workspace = true +tiny_http.workspace = true + [workspace] -members = ["crates/jev", "crates/lintus"] +members = ["crates/jev"] resolver = "3" [workspace.package] @@ -7,6 +34,7 @@ edition = "2024" license = "MIT" authors = ["Vincent Rolea"] repository = "https://github.com/virolea/lintus" +homepage = "https://github.com/virolea/lintus" [workspace.dependencies] jev = { package = "jev-api", path = "crates/jev", version = "0.1.0" } @@ -15,3 +43,9 @@ serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["preserve_order"] } tempfile = "3" tiny_http = "0.12" + +# A small, fast binary for the releases. +[profile.release] +lto = true +codegen-units = 1 +strip = true diff --git a/crates/lintus/Cargo.toml b/crates/lintus/Cargo.toml deleted file mode 100644 index 1a5c4e4..0000000 --- a/crates/lintus/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "lintus" -version = "0.3.0" -description = "A linter whose rules are plain-language questions, answered by the Jev model." -readme = "../../README.md" -edition.workspace = true -license.workspace = true -authors.workspace = true -repository.workspace = true - -[dependencies] -clap = { version = "4.6.7", features = ["derive", "wrap_help"] } -indexmap.workspace = true -jev.workspace = true -rpassword = "7.5.4" -serde_json.workspace = true -yaml-rust2 = "0.13.0" - -[dev-dependencies] -serde_json.workspace = true -tempfile.workspace = true -tiny_http.workspace = true diff --git a/crates/lintus/src/cli.rs b/src/cli.rs similarity index 100% rename from crates/lintus/src/cli.rs rename to src/cli.rs diff --git a/crates/lintus/src/config.rs b/src/config.rs similarity index 100% rename from crates/lintus/src/config.rs rename to src/config.rs diff --git a/crates/lintus/src/credentials.rs b/src/credentials.rs similarity index 100% rename from crates/lintus/src/credentials.rs rename to src/credentials.rs diff --git a/crates/lintus/src/error.rs b/src/error.rs similarity index 100% rename from crates/lintus/src/error.rs rename to src/error.rs diff --git a/crates/lintus/src/files.rs b/src/files.rs similarity index 100% rename from crates/lintus/src/files.rs rename to src/files.rs diff --git a/crates/lintus/src/format.rs b/src/format.rs similarity index 100% rename from crates/lintus/src/format.rs rename to src/format.rs diff --git a/crates/lintus/src/git.rs b/src/git.rs similarity index 100% rename from crates/lintus/src/git.rs rename to src/git.rs diff --git a/crates/lintus/src/glob.rs b/src/glob.rs similarity index 100% rename from crates/lintus/src/glob.rs rename to src/glob.rs diff --git a/crates/lintus/src/main.rs b/src/main.rs similarity index 100% rename from crates/lintus/src/main.rs rename to src/main.rs diff --git a/crates/lintus/src/paths.rs b/src/paths.rs similarity index 100% rename from crates/lintus/src/paths.rs rename to src/paths.rs diff --git a/crates/lintus/src/progress.rs b/src/progress.rs similarity index 100% rename from crates/lintus/src/progress.rs rename to src/progress.rs diff --git a/crates/lintus/src/report.rs b/src/report.rs similarity index 100% rename from crates/lintus/src/report.rs rename to src/report.rs diff --git a/crates/lintus/src/runner.rs b/src/runner.rs similarity index 100% rename from crates/lintus/src/runner.rs rename to src/runner.rs diff --git a/crates/lintus/src/template.rs b/src/template.rs similarity index 100% rename from crates/lintus/src/template.rs rename to src/template.rs diff --git a/crates/lintus/src/yaml.rs b/src/yaml.rs similarity index 100% rename from crates/lintus/src/yaml.rs rename to src/yaml.rs diff --git a/test/support/lintus-ruby b/test/support/lintus-ruby index 68daca1..1589945 100755 --- a/test/support/lintus-ruby +++ b/test/support/lintus-ruby @@ -2,6 +2,6 @@ # Runs the Ruby lintus from this checkout, honouring $JEV_API_URL. Used to check # the conformance suite against the Ruby implementation: # -# LINTUS_BIN=test/support/lintus-ruby cargo test -p lintus --test conformance +# LINTUS_BIN=test/support/lintus-ruby cargo test --test conformance root=$(cd "$(dirname "$0")/../.." && pwd) RUBYOPT="-I$root/lib -r$root/test/support/jev_api_url.rb $RUBYOPT" exec ruby "$root/exe/lintus" "$@" diff --git a/crates/lintus/tests/auth.rs b/tests/auth.rs similarity index 100% rename from crates/lintus/tests/auth.rs rename to tests/auth.rs diff --git a/crates/lintus/tests/conformance/cli.rs b/tests/conformance/cli.rs similarity index 100% rename from crates/lintus/tests/conformance/cli.rs rename to tests/conformance/cli.rs diff --git a/crates/lintus/tests/conformance/config.rs b/tests/conformance/config.rs similarity index 100% rename from crates/lintus/tests/conformance/config.rs rename to tests/conformance/config.rs diff --git a/crates/lintus/tests/conformance/files.rs b/tests/conformance/files.rs similarity index 100% rename from crates/lintus/tests/conformance/files.rs rename to tests/conformance/files.rs diff --git a/crates/lintus/tests/conformance/main.rs b/tests/conformance/main.rs similarity index 100% rename from crates/lintus/tests/conformance/main.rs rename to tests/conformance/main.rs diff --git a/crates/lintus/tests/conformance/output.rs b/tests/conformance/output.rs similarity index 94% rename from crates/lintus/tests/conformance/output.rs rename to tests/conformance/output.rs index 48ee3cb..68ef832 100644 --- a/crates/lintus/tests/conformance/output.rs +++ b/tests/conformance/output.rs @@ -148,7 +148,10 @@ fn github_escapes_messages_and_properties() { ".lintus.yml", "paths: [lib/**]\nrules:\n r:\n question: q\n description: \"100%\\nsure, really: yes\\r\"\n", ); - project.write("lib/a,b:c.rb", "x\n"); + // Windows does not allow ":" in file names. + let (name, escaped) = + if cfg!(windows) { ("lib/a,b.rb", "lib/a%2Cb.rb") } else { ("lib/a,b:c.rb", "lib/a%2Cb%3Ac.rb") }; + project.write(name, "x\n"); project.write("lib/fail.rb", "x\n"); let server = FakeJev::start(|request, _| match request.file() { "lib/fail.rb" => Reply::status(500, "line 1\nline 2: 50%"), @@ -159,11 +162,13 @@ fn github_escapes_messages_and_properties() { assert_eq!( out.stdout, - "\ -::error file=lib/a%2Cb%3Ac.rb,title=lintus%3A r::100%25%0Asure, really: yes%0D + format!( + "\ +::error file={escaped},title=lintus%3A r::100%25%0Asure, really: yes%0D ::error file=lib/fail.rb,title=lintus%3A request failed::Jev API error 500: line 1%0Aline 2: 50%25 1 file inspected, 1 offense detected, 1 file failed " + ) ); } diff --git a/crates/lintus/tests/conformance/runner.rs b/tests/conformance/runner.rs similarity index 100% rename from crates/lintus/tests/conformance/runner.rs rename to tests/conformance/runner.rs diff --git a/crates/lintus/tests/conformance/support.rs b/tests/conformance/support.rs similarity index 94% rename from crates/lintus/tests/conformance/support.rs rename to tests/conformance/support.rs index f6a713e..3ddc923 100644 --- a/crates/lintus/tests/conformance/support.rs +++ b/tests/conformance/support.rs @@ -2,7 +2,7 @@ //! and a way to run the lintus binary under test. //! //! The binary is the one cargo builds, unless `LINTUS_BIN` names another one -//! (a path relative to the workspace root works), so the same suite can check +//! (a path relative to the repository root works), so the same suite can check //! any implementation of the command line. #![allow(dead_code)] @@ -21,7 +21,7 @@ pub const API_KEY: &str = "test-key"; pub fn lintus_bin() -> PathBuf { match std::env::var_os("LINTUS_BIN") { - Some(bin) => Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").join(bin), + Some(bin) => Path::new(env!("CARGO_MANIFEST_DIR")).join(bin), None => PathBuf::from(env!("CARGO_BIN_EXE_lintus")), } } @@ -38,7 +38,7 @@ impl Project { pub fn new() -> Self { let dir = tempfile::Builder::new().prefix("lintus").tempdir().unwrap(); let home = tempfile::Builder::new().prefix("lintus-home").tempdir().unwrap(); - let root = dir.path().canonicalize().unwrap(); + let root = canonical(dir.path()); Project { dir, home, root } } @@ -110,8 +110,19 @@ impl Project { } } +/// The path with symlinks resolved (macOS's /var is /private/var), without +/// the `\\?\` prefix Windows adds, which git and humans do not expect. +fn canonical(path: &Path) -> PathBuf { + let path = path.canonicalize().unwrap(); + match path.to_str().and_then(|p| p.strip_prefix(r"\\?\")) { + Some(stripped) => PathBuf::from(stripped), + None => path, + } +} + fn isolate_git(mut command: process::Command) -> process::Command { - command.env("GIT_CONFIG_GLOBAL", "/dev/null").env("GIT_CONFIG_NOSYSTEM", "1"); + let null = if cfg!(windows) { "NUL" } else { "/dev/null" }; + command.env("GIT_CONFIG_GLOBAL", null).env("GIT_CONFIG_NOSYSTEM", "1"); command } diff --git a/crates/lintus/tests/fixtures/generate_glob_cases.rb b/tests/fixtures/generate_glob_cases.rb similarity index 97% rename from crates/lintus/tests/fixtures/generate_glob_cases.rb rename to tests/fixtures/generate_glob_cases.rb index d206287..9824d6e 100644 --- a/crates/lintus/tests/fixtures/generate_glob_cases.rb +++ b/tests/fixtures/generate_glob_cases.rb @@ -5,7 +5,7 @@ # tested against it (src/glob.rs). Run from the repository root while the Ruby # implementation is still there: # -# ruby -Ilib crates/lintus/tests/fixtures/generate_glob_cases.rb +# ruby -Ilib tests/fixtures/generate_glob_cases.rb require "lintus" PATTERNS = [ diff --git a/crates/lintus/tests/fixtures/generate_round_cases.rb b/tests/fixtures/generate_round_cases.rb similarity index 94% rename from crates/lintus/tests/fixtures/generate_round_cases.rb rename to tests/fixtures/generate_round_cases.rb index e6afaa0..6e8fd35 100644 --- a/crates/lintus/tests/fixtures/generate_round_cases.rb +++ b/tests/fixtures/generate_round_cases.rb @@ -5,7 +5,7 @@ # "". The Rust # formatter is tested against it (src/format.rs). Run with any Ruby: # -# ruby crates/lintus/tests/fixtures/generate_round_cases.rb +# ruby tests/fixtures/generate_round_cases.rb def bits(float) = [float].pack("E").unpack1("Q<") def next_float(float, direction) = [bits(float) + direction].pack("Q<").unpack1("E") diff --git a/crates/lintus/tests/fixtures/glob_cases.tsv b/tests/fixtures/glob_cases.tsv similarity index 100% rename from crates/lintus/tests/fixtures/glob_cases.tsv rename to tests/fixtures/glob_cases.tsv diff --git a/crates/lintus/tests/fixtures/round_cases.tsv b/tests/fixtures/round_cases.tsv similarity index 100% rename from crates/lintus/tests/fixtures/round_cases.tsv rename to tests/fixtures/round_cases.tsv From e2141f1d6a6ecdcf71f8e779072d5e6e5ae1b374 Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:31:17 +0200 Subject: [PATCH 6/9] Ship lintus as prebuilt binaries - Releases: a tag builds static Linux (x86_64, arm64), macOS (Intel, Apple silicon) and Windows binaries with checksums, attaches them to a GitHub release, and publishes jev-api and lintus to crates.io with trusted publishing. It replaces the gem release. - install.sh installs the right binary for the machine and checks its checksum. - The GitHub Action downloads the binary for the runner, the release matching its own tag by default, instead of setting up Ruby. - CI runs the Rust tests on Linux, macOS and Windows, and the conformance suite against the Ruby implementation. - Lintus lints its own Rust code with the Rust binary. - README and CHANGELOG describe installing the binary and `lintus auth`. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 34 +++++++- .github/workflows/lintus.yml | 16 ++-- .github/workflows/release.yml | 151 +++++++++++++++++++++++++++++----- .lintus.yml | 36 +++----- CHANGELOG.md | 12 +++ Cargo.toml | 2 +- README.md | 92 ++++++++++++++++----- action.yml | 58 ++++++++++--- crates/jev/Cargo.toml | 4 + crates/jev/README.md | 9 +- crates/jev/src/client.rs | 2 + crates/jev/src/error.rs | 2 + crates/jev/src/query.rs | 2 + crates/jev/src/question.rs | 2 + crates/jev/src/response.rs | 2 + install.sh | 68 +++++++++++++++ src/error.rs | 2 + 17 files changed, 406 insertions(+), 88 deletions(-) create mode 100755 install.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 765d1df..992b162 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,34 @@ permissions: contents: read jobs: - test: + rust: + runs-on: ${{ matrix.os }} + name: Rust on ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - name: Check formatting + if: runner.os == 'Linux' + run: cargo fmt --all --check + - name: Clippy + run: cargo clippy --workspace --all-targets --locked -- -D warnings + - name: Test + run: cargo test --workspace --locked + + ruby: runs-on: ubuntu-latest name: Ruby ${{ matrix.ruby }} strategy: @@ -32,3 +59,8 @@ jobs: bundler-cache: true - name: Run tests and RuboCop run: bundle exec rake + - uses: dtolnay/rust-toolchain@stable + - name: Run the conformance suite against the Ruby implementation + run: cargo test --locked --test conformance + env: + LINTUS_BIN: test/support/lintus-ruby diff --git a/.github/workflows/lintus.yml b/.github/workflows/lintus.yml index a30b8c1..06a62ee 100644 --- a/.github/workflows/lintus.yml +++ b/.github/workflows/lintus.yml @@ -1,5 +1,5 @@ -# Lintus linting itself. Uses the action defined in this repository, so it also -# serves as a smoke test of action.yml. Skipped on forks without the secret. +# Lintus linting itself, built from this checkout. It also checks, on every pull +# request, that a real call to the Jev API works. Skipped on forks without the secret. name: Lintus on: @@ -18,12 +18,12 @@ jobs: with: fetch-depth: 0 persist-credentials: false - - name: Set up Ruby + - uses: dtolnay/rust-toolchain@stable + if: env.JEV_API_KEY != '' + - uses: Swatinem/rust-cache@v2 if: env.JEV_API_KEY != '' - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.3" - bundler-cache: true - name: Run lintus from this checkout if: env.JEV_API_KEY != '' - run: bundle exec exe/lintus --format github --diff "origin/${{ github.base_ref }}" + env: + BASE_REF: ${{ github.base_ref }} + run: cargo run --release --locked -- --format github --diff "origin/$BASE_REF" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8620878..89a4492 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,16 @@ -# Publishes the gem to rubygems.org when a version tag is pushed. +# Builds lintus for every platform when a version tag is pushed, attaches the +# binaries to a GitHub release, and publishes the crates to crates.io. # -# Uses RubyGems trusted publishing (OIDC), so no API key is stored in the -# repository. One-time setup on rubygems.org, before the first release: -# Profile > Pending trusted publishers > Add: gem "lintus", -# repository virolea/lintus, workflow release.yml (no environment). -# After the first release the publisher moves to the gem's own settings. +# To release: +# 1. On main, set the version in Cargo.toml (and in crates/jev/Cargo.toml if +# the client changed), and move the CHANGELOG.md entries under a heading +# for the version: "## [0.3.0] - 2026-10-01". +# 2. git tag v0.3.0 && git push origin v0.3.0 # -# To release: bump lib/lintus/version.rb and CHANGELOG.md on main, then -# git tag v0.1.0 && git push origin v0.1.0 +# crates.io uses trusted publishing (OIDC), so no token is stored in the +# repository. It can only be set up for a crate that exists, so publish +# jev-api and lintus once by hand with `cargo publish`, then add this +# repository and workflow as a trusted publisher in each crate's settings. name: Release on: @@ -15,27 +18,131 @@ on: tags: - "v*" +permissions: + contents: read + jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Check the tag matches the version in Cargo.toml + run: | + version="v$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "lintus") | .version')" + if [ "$GITHUB_REF_NAME" != "$version" ]; then + echo "Tag $GITHUB_REF_NAME does not match the lintus version in Cargo.toml ($version)" >&2 + exit 1 + fi + + build: + needs: check + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # Linux binaries are static (musl), so they run on any distribution. + - target: x86_64-unknown-linux-musl + os: ubuntu-latest + - target: aarch64-unknown-linux-musl + os: ubuntu-24.04-arm + - target: x86_64-apple-darwin + os: macos-latest + - target: aarch64-apple-darwin + os: macos-latest + - target: x86_64-pc-windows-msvc + os: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - name: Install musl + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y musl-tools + - name: Test + if: matrix.target != 'x86_64-apple-darwin' + run: cargo test --workspace --locked --target ${{ matrix.target }} + - name: Build + run: cargo build --release --locked --target ${{ matrix.target }} + - name: Package + shell: bash + env: + TARGET: ${{ matrix.target }} + run: | + name="lintus-$TARGET" + mkdir "$name" + cp README.md LICENSE.txt CHANGELOG.md "$name/" + if [ "$RUNNER_OS" = "Windows" ]; then + cp "target/$TARGET/release/lintus.exe" "$name/" + (cd "$name" && 7z a -tzip "../$name.zip" . >/dev/null) + archive="$name.zip" + sha256sum "$archive" > "$archive.sha256" + else + cp "target/$TARGET/release/lintus" "$name/" + tar -czf "$name.tar.gz" -C "$name" . + archive="$name.tar.gz" + shasum -a 256 "$archive" > "$archive.sha256" + fi + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.target }} + path: | + lintus-${{ matrix.target }}.tar.gz* + lintus-${{ matrix.target }}.zip* + release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - name: Create the release + env: + GH_TOKEN: ${{ github.token }} + run: | + version="${GITHUB_REF_NAME#v}" + awk -v heading="## [$version]" 'index($0, heading) == 1 { found = 1; next } found && /^## \[/ { exit } found' CHANGELOG.md > notes.md + if [ ! -s notes.md ]; then echo "See CHANGELOG.md." > notes.md; fi + cp install.sh dist/ + gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file notes.md dist/* + + crates: + needs: release runs-on: ubuntu-latest permissions: - contents: write # rake release pushes the tag if it is not there yet id-token: write # trusted publishing steps: - uses: actions/checkout@v4 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.3" - bundler-cache: true - - name: Check the tag matches the gem version + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + - uses: rust-lang/crates-io-auth-action@v1 + id: auth + - name: Publish jev-api, unless this version is already out + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} run: | - version="v$(ruby -Ilib -e 'require "lintus/version"; print Lintus::VERSION')" - if [ "$GITHUB_REF_NAME" != "$version" ]; then - echo "Tag $GITHUB_REF_NAME does not match Lintus::VERSION ($version)" >&2 - exit 1 + version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "jev-api") | .version') + if curl -sf "https://crates.io/api/v1/crates/jev-api/$version" > /dev/null; then + echo "jev-api $version is already on crates.io" + else + cargo publish --locked -p jev-api fi - - name: Run tests - run: bundle exec rake test - - name: Build and push to rubygems.org - uses: rubygems/release-gem@v1 + - name: Publish lintus + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + run: cargo publish --locked -p lintus diff --git a/.lintus.yml b/.lintus.yml index 78402da..8845009 100644 --- a/.lintus.yml +++ b/.lintus.yml @@ -1,37 +1,27 @@ # Lintus lints itself with these rules. See README.md for the format. paths: - - "lib/**/*.rb" - - "exe/*" - -exclude: - - "lib/lintus/version.rb" + - "src/**/*.rs" + - "crates/*/src/**/*.rs" rules: no_debugging_leftovers: description: Debugging statements must not be committed. - question: Does this file contain leftover debugging code, such as binding.irb, debugger, or a puts/p/pp used to inspect a value? + question: Does this file contain leftover debugging code, such as dbg!, or a println!/eprintln! used to inspect a value? criteria: - "true": A debugger breakpoint or a throwaway print statement is present. + "true": A dbg! call, or a print statement that dumps an internal value rather than telling the user something, is present. "false": Any output is deliberate program behaviour, or there is none. errors_are_actionable: - description: Raised error messages must tell the user what was wrong and what to do about it. - question: Does this file raise an error whose message would leave a user unsure of what went wrong or how to fix it? + description: Error messages must tell the user what was wrong and what to do about it. + question: Does this file build an error message that would leave a user unsure of what went wrong or how to fix it? criteria: - "true": At least one raised message is vague, such as "invalid" or "failed", with no detail. - "false": Every raised message names the problem, or no errors are raised. + "true": At least one message is vague, such as "invalid" or "failed", with no detail. + "false": Every message names the problem, or the file builds no error messages. - classes_are_documented: - description: Each class and module should open with a comment describing its responsibility. - question: Is there a class or module defined in this file whose definition is not preceded by a comment describing what it is for? + modules_are_documented: + description: Each module should open with a comment describing its responsibility. + question: Does this file start without a module-level doc comment (//!) explaining what the module is for? criteria: - "true": >- - A class or module with a body of its own is introduced without a descriptive comment on the - lines right above its `class` or `module` line. - "false": >- - Every class or module that carries behaviour has such a comment, or the file defines none. - Ignore namespace wrappers whose body only nests other definitions, classes reopened only to - nest another definition, and one-line subclasses such as `class Error < StandardError; end`. - exclude: - - "exe/*" + "true": The file does not open with a //! comment, or that comment does not say what the module does. + "false": The file opens with a //! comment describing its purpose. diff --git a/CHANGELOG.md b/CHANGELOG.md index 137532f..65e3637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ ## [Unreleased] +- Lintus is now a single binary, rewritten in Rust: no Ruby needed. Install it with the + script or the archives on the GitHub releases, or `cargo install lintus`. Config files, + flags, output formats and exit statuses are unchanged, checked by a conformance suite that + both implementations pass. +- `lintus auth login`, `status` and `logout` save the Jev API key in the user's config + directory. A run takes the key from `--api-key`, then `JEV_API_KEY`, then the saved key. +- `JEV_API_URL` sends requests to another endpoint, such as a proxy. +- The GitHub Action downloads the binary instead of setting up Ruby; its `ruby-version` + input is ignored. The pre-commit hook builds lintus from source. +- A request that cannot reach the API fails its file instead of stopping the run. +- The Jev client ships as its own crate, `jev-api`. + ## [0.2.0] - 2026-09-21 - A progress counter on stderr while files are being checked. diff --git a/Cargo.toml b/Cargo.toml index 2ca1c21..983b73a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ description = "A linter whose rules are plain-language questions, answered by th readme = "README.md" keywords = ["linter", "llm", "code-review", "ci"] categories = ["command-line-utilities", "development-tools"] -include = ["src/**", "Cargo.toml", "README.md", "CHANGELOG.md", "LICENSE.txt"] +include = ["src/**", "tests/fixtures/*.tsv", "Cargo.toml", "README.md", "CHANGELOG.md", "LICENSE.txt"] edition.workspace = true license.workspace = true authors.workspace = true diff --git a/README.md b/README.md index 4d41a77..4492a59 100644 --- a/README.md +++ b/README.md @@ -29,17 +29,38 @@ commit, so it fits a CI job as well as a pre-commit hook. ## Installation +Lintus is a single binary with no runtime to install. On macOS and Linux: + ```bash -gem install lintus +curl -fsSL https://github.com/virolea/lintus/releases/latest/download/install.sh | sh ``` -Or add it to your `Gemfile`: +The script picks the binary for your machine, checks its checksum, and puts it in +`~/.local/bin` (set `LINTUS_INSTALL_DIR` to change that, and `LINTUS_VERSION` to pin a +release). You can also download the archive for your platform from the +[releases](https://github.com/virolea/lintus/releases) yourself: Linux (x86_64 and arm64, +static, so any distribution), macOS (Intel and Apple silicon) and Windows (x86_64). With a +Rust toolchain, `cargo install lintus` builds it from source. + +Upgrading from the Ruby gem? Run `gem uninstall lintus` so the old executable does not +shadow the new one. Config files carry over unchanged. + +### API key -```ruby -gem "lintus", group: :development +Lintus needs a Jev API key. Save it once: + +```bash +lintus auth login # paste the key at the prompt; it is not echoed +lintus auth status # shows which key lintus will use, and where it comes from +lintus auth logout # deletes the saved key ``` -Lintus needs a Jev API key. Export it as `JEV_API_KEY` or pass `--api-key`. +The key is saved in `~/.config/lintus/credentials.json` (under `$XDG_CONFIG_HOME` when it is +set, and `%APPDATA%` on Windows), readable by you only. `lintus auth login` also reads the +key from stdin when it is piped in. + +A run takes the key from `--api-key`, then the `JEV_API_KEY` environment variable, then the +saved key. In CI, set `JEV_API_KEY` from a secret. ## Getting started @@ -107,9 +128,13 @@ Rule ids are snake_case. They become the question identifiers in the Jev request ### Globs -Globs use Ruby's `File.fnmatch` with pathname semantics: `*` does not cross directories, -`**/` does, and `{a,b}` alternation works. A bare directory (`vendor`) and a trailing `**` -(`vendor/**`) both match everything beneath it. +`*` does not cross directories, `**/` does, and `{a,b}` alternation works. Wildcards match +dotfiles too. A bare directory (`vendor`) and a trailing `**` (`vendor/**`) both match +everything beneath it. These are the semantics of Ruby's `File.fnmatch` with pathname +matching, which Lintus used when it was a Ruby gem and keeps exactly. + +The file is read as YAML 1.1, as Ruby reads it: `yes`, `no`, `on` and `off` are booleans, +numbers may be written `100_000`, and `<<` merges a mapping into another. ### Writing good rules @@ -180,15 +205,16 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # needed to diff against the base branch - - uses: virolea/lintus@v0.2.0 + - uses: virolea/lintus@v0.3.0 with: jev-api-key: ${{ secrets.JEV_API_KEY }} ``` On pull requests the action lints only the files changed against the base branch, and annotates them. Set `base: none` to lint everything, or `base: origin/develop` to diff -against another ref. Extra flags go in `args`. If the workflow already sets up Ruby, the -action reuses it. +against another ref. Extra flags go in `args`. The action downloads the lintus binary for +the runner (the release matching the action's tag, or `version` if set), so the job needs +neither Ruby nor Rust. ## Pre-commit hook @@ -197,12 +223,26 @@ With [pre-commit](https://pre-commit.com): ```yaml repos: - repo: https://github.com/virolea/lintus - rev: v0.2.0 + rev: v0.3.0 hooks: - id: lintus ``` -pre-commit passes the staged file names, so only those are checked. +pre-commit passes the staged file names, so only those are checked. The first run builds +lintus from source, installing a Rust toolchain for it if there is none; later runs reuse the +build. If lintus is already installed, a local hook skips the build: + +```yaml +repos: + - repo: local + hooks: + - id: lintus + name: lintus + entry: lintus + language: system + pass_filenames: true + require_serial: true +``` With a plain git hook, use `--staged` so the linted content is what is actually being committed: @@ -213,26 +253,40 @@ committed: exec lintus --staged ``` -Either way `JEV_API_KEY` must be in the environment of the shell running the commit. +Either way the key comes from `lintus auth login`, or from `JEV_API_KEY` in the environment +of the shell running the commit. ## Other options ``` -c, --config PATH Config file to use instead of searching for one -j, --jobs N Concurrent requests to the Jev API (default 4) - --api-key KEY Jev API key (default: $JEV_API_KEY) + --api-key KEY Jev API key (default: $JEV_API_KEY, then the saved key) -l, --list Show what would be checked without calling the API ``` +`JEV_API_URL` sends requests to another endpoint than the public API, such as a proxy. + Rate-limited and overloaded responses are retried with exponential backoff, three times per file, before the file is reported as failed. ## Development -After checking out the repo, run `bin/setup` to install dependencies, then `bundle exec rake` -to run the tests and RuboCop. `bin/console` gives you an IRB session with the gem loaded. +Lintus is written in Rust. The repository holds two crates: `lintus` (the command line, at +the root) and `jev-api` (in `crates/jev`, the typed client for the Jev API it is built on). + +```bash +cargo test --workspace # unit tests, and the conformance suite in tests/conformance +cargo run -- --list # run the command from the checkout +cargo clippy --workspace --all-targets +``` + +The conformance suite runs the `lintus` binary against temporary projects and a fake Jev +API, and checks its output, exit status and requests byte for byte. `LINTUS_BIN` points it +at another build of the command. -The repository lints itself: see `.lintus.yml` and `.github/workflows/lintus.yml`. +The repository lints itself: see `.lintus.yml` and `.github/workflows/lintus.yml`. Releases +are built by `.github/workflows/release.yml` when a version tag is pushed. ## Contributing @@ -240,4 +294,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/virole ## License -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). +Lintus is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/action.yml b/action.yml index 378cdac..2dbaeea 100644 --- a/action.yml +++ b/action.yml @@ -21,32 +21,65 @@ inputs: required: false default: "" version: - description: Version of the lintus gem to install. Defaults to the latest release. + description: >- + Version of lintus to install, such as 0.3.0. Defaults to the version of the action + itself when it is used at a version tag (virolea/lintus@v0.3.0), and to the latest + release otherwise. required: false default: "" ruby-version: - description: Ruby version to set up when the workflow has not done so already. + description: No longer used; lintus is a single binary and needs no Ruby. required: false - default: "3.3" + default: "" runs: using: composite steps: - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ inputs.ruby-version }} - - name: Install lintus shell: bash env: LINTUS_VERSION: ${{ inputs.version }} + ACTION_REF: ${{ github.action_ref }} run: | - if [ -n "$LINTUS_VERSION" ]; then - gem install lintus --version "$LINTUS_VERSION" - else - gem install lintus + version="$LINTUS_VERSION" + if [ -z "$version" ]; then + case "$ACTION_REF" in + v[0-9]*) version="$ACTION_REF" ;; + *) version="latest" ;; + esac + fi + case "$version" in + latest) base="https://github.com/virolea/lintus/releases/latest/download" ;; + v*) base="https://github.com/virolea/lintus/releases/download/$version" ;; + *) base="https://github.com/virolea/lintus/releases/download/v$version" ;; + esac + + case "$RUNNER_OS-$RUNNER_ARCH" in + Linux-X64) archive="lintus-x86_64-unknown-linux-musl.tar.gz" ;; + Linux-ARM64) archive="lintus-aarch64-unknown-linux-musl.tar.gz" ;; + macOS-X64) archive="lintus-x86_64-apple-darwin.tar.gz" ;; + macOS-ARM64) archive="lintus-aarch64-apple-darwin.tar.gz" ;; + Windows-X64) archive="lintus-x86_64-pc-windows-msvc.zip" ;; + *) echo "::error title=lintus::No lintus binary for $RUNNER_OS $RUNNER_ARCH"; exit 1 ;; + esac + + dir="$RUNNER_TEMP/lintus" + mkdir -p "$dir" + cd "$dir" + if ! curl -fsSL "$base/$archive" -o "$archive" || ! curl -fsSL "$base/$archive.sha256" -o "$archive.sha256"; then + echo "::error title=lintus::Could not download $base/$archive. Releases before 0.3.0 have no binaries." + exit 1 + fi + if command -v sha256sum > /dev/null; then actual=$(sha256sum "$archive" | cut -d ' ' -f 1); else actual=$(shasum -a 256 "$archive" | cut -d ' ' -f 1); fi + if [ "$actual" != "$(cut -d ' ' -f 1 "$archive.sha256")" ]; then + echo "::error title=lintus::The checksum of $archive does not match" + exit 1 fi + case "$archive" in + *.zip) unzip -q "$archive" ;; + *) tar -xzf "$archive" ;; + esac + echo "$dir" >> "$GITHUB_PATH" - name: Run lintus shell: bash @@ -56,6 +89,7 @@ runs: LINTUS_ARGS: ${{ inputs.args }} PR_BASE_REF: ${{ github.base_ref }} run: | + lintus --version base="$LINTUS_BASE" if [ -z "$base" ] && [ -n "$PR_BASE_REF" ]; then base="origin/$PR_BASE_REF" diff --git a/crates/jev/Cargo.toml b/crates/jev/Cargo.toml index a4083ff..90c8842 100644 --- a/crates/jev/Cargo.toml +++ b/crates/jev/Cargo.toml @@ -2,6 +2,10 @@ name = "jev-api" version = "0.1.0" description = "A typed client for the Typesafe Jev model API." +readme = "README.md" +keywords = ["jev", "typesafe", "llm", "api-client"] +categories = ["api-bindings"] +homepage.workspace = true edition.workspace = true license.workspace = true authors.workspace = true diff --git a/crates/jev/README.md b/crates/jev/README.md index a77c75b..342783c 100644 --- a/crates/jev/README.md +++ b/crates/jev/README.md @@ -1,13 +1,18 @@ -# jev +# jev-api A typed Rust client for the [Typesafe](https://typesafe.ai) Jev model API. It is the Rust counterpart of the [`jev` gem](https://github.com/virolea/jev), and what [Lintus](https://github.com/virolea/lintus) talks to the API with. +```toml +[dependencies] +jev-api = "0.1" # imported as `jev` +``` + ```rust use jev::{Client, Noul, Query, Score}; -let client = Client::new(std::env::var("JEV_API_KEY")?)?; +let client = Client::new(std::env::var("JEV_API_KEY").unwrap_or_default())?; let mut query = Query::new("Help! My payouts have been failing for 3 days."); query.ask("is_urgent", Noul::new("Does this convey urgency?").with_threshold(0.8))?; diff --git a/crates/jev/src/client.rs b/crates/jev/src/client.rs index 430fbec..7620a2c 100644 --- a/crates/jev/src/client.rs +++ b/crates/jev/src/client.rs @@ -1,3 +1,5 @@ +//! The HTTP client that sends a query to the API and reads its response. + use std::fmt; use std::time::Duration; diff --git a/crates/jev/src/error.rs b/crates/jev/src/error.rs index e5b659c..b375a63 100644 --- a/crates/jev/src/error.rs +++ b/crates/jev/src/error.rs @@ -1,3 +1,5 @@ +//! Errors from building a query and from talking to the API. + use std::fmt; pub type Result = std::result::Result; diff --git a/crates/jev/src/query.rs b/crates/jev/src/query.rs index 127ab18..96c50fa 100644 --- a/crates/jev/src/query.rs +++ b/crates/jev/src/query.rs @@ -1,3 +1,5 @@ +//! A query: the state, and the questions to ask of it. + use indexmap::IndexMap; use crate::{Error, Question, Result}; diff --git a/crates/jev/src/question.rs b/crates/jev/src/question.rs index 45a1976..f9886fe 100644 --- a/crates/jev/src/question.rs +++ b/crates/jev/src/question.rs @@ -1,3 +1,5 @@ +//! The three question types and how each one is sent to the API. + use indexmap::IndexMap; use serde::Serialize; diff --git a/crates/jev/src/response.rs b/crates/jev/src/response.rs index 0243133..9f94bf7 100644 --- a/crates/jev/src/response.rs +++ b/crates/jev/src/response.rs @@ -1,3 +1,5 @@ +//! The API's response, with each answer typed after the question it answers. + use indexmap::IndexMap; use serde::Deserialize; use serde::de::DeserializeOwned; diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..1dbe86f --- /dev/null +++ b/install.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# Installs the lintus binary for this machine from its GitHub releases. +# +# curl -fsSL https://github.com/virolea/lintus/releases/latest/download/install.sh | sh +# +# LINTUS_VERSION picks a release (such as v0.3.0; default: the latest), and +# LINTUS_INSTALL_DIR where the binary goes (default: ~/.local/bin). +set -eu + +repo="virolea/lintus" +version="${LINTUS_VERSION:-latest}" +dir="${LINTUS_INSTALL_DIR:-$HOME/.local/bin}" + +fail() { + echo "lintus install: $*" >&2 + exit 1 +} + +case "$(uname -s)-$(uname -m)" in + Linux-x86_64 | Linux-amd64) target="x86_64-unknown-linux-musl" ;; + Linux-aarch64 | Linux-arm64) target="aarch64-unknown-linux-musl" ;; + Darwin-x86_64) target="x86_64-apple-darwin" ;; + Darwin-arm64) target="aarch64-apple-darwin" ;; + *) fail "no prebuilt binary for $(uname -s) $(uname -m). Build it with: cargo install lintus" ;; +esac + +case "$version" in + latest) base="https://github.com/$repo/releases/latest/download" ;; + v*) base="https://github.com/$repo/releases/download/$version" ;; + *) base="https://github.com/$repo/releases/download/v$version" ;; +esac + +download() { + if command -v curl > /dev/null; then + curl -fsSL "$1" -o "$2" || fail "could not download $1" + elif command -v wget > /dev/null; then + wget -qO "$2" "$1" || fail "could not download $1" + else + fail "curl or wget is needed to download lintus" + fi +} + +sha256() { + if command -v sha256sum > /dev/null; then + sha256sum "$1" | cut -d ' ' -f 1 + else + shasum -a 256 "$1" | cut -d ' ' -f 1 + fi +} + +archive="lintus-$target.tar.gz" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +download "$base/$archive" "$tmp/$archive" +download "$base/$archive.sha256" "$tmp/$archive.sha256" +[ "$(cut -d ' ' -f 1 "$tmp/$archive.sha256")" = "$(sha256 "$tmp/$archive")" ] || fail "the checksum of $archive does not match" + +tar -xzf "$tmp/$archive" -C "$tmp" +mkdir -p "$dir" +cp "$tmp/lintus" "$dir/lintus" +chmod 755 "$dir/lintus" + +echo "Installed $("$dir/lintus" --version) to $dir/lintus" +case ":$PATH:" in + *":$dir:"*) ;; + *) echo "$dir is not on your PATH. Add it with: export PATH=\"$dir:\$PATH\"" ;; +esac diff --git a/src/error.rs b/src/error.rs index cde7fc6..8f26dcf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +//! The error that stops a run, and how it reaches the user. + use std::fmt; /// An error that stops the run before or instead of linting: a bad argument, From 82e27d3351f8aaf6e14cc217be1e892b387aefb1 Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:38:24 +0200 Subject: [PATCH 7/9] Allow a dry run of the release build, and note behaviour changes Running the Release workflow by hand builds and tests every target without releasing, so the matrix can be checked before a tag exists. The CHANGELOG notes the two places the Rust version behaves differently from the gem. The pre-commit hook needs a C compiler, but no Rust: checked in a container without one. RuboCop is clean on the new Ruby helpers. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 7 +++++++ .pre-commit-hooks.yaml | 3 ++- CHANGELOG.md | 2 ++ README.md | 4 ++-- tests/fixtures/generate_round_cases.rb | 4 +++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 89a4492..a7cd780 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,9 @@ # for the version: "## [0.3.0] - 2026-10-01". # 2. git tag v0.3.0 && git push origin v0.3.0 # +# Running this workflow by hand (Actions > Release > Run workflow) only builds +# and tests the binaries, without releasing anything: a dry run before tagging. +# # crates.io uses trusted publishing (OIDC), so no token is stored in the # repository. It can only be set up for a crate that exists, so publish # jev-api and lintus once by hand with `cargo publish`, then add this @@ -17,12 +20,14 @@ on: push: tags: - "v*" + workflow_dispatch: permissions: contents: read jobs: check: + if: github.event_name == 'push' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -38,6 +43,7 @@ jobs: build: needs: check + if: ${{ !cancelled() && needs.check.result != 'failure' }} name: Build ${{ matrix.target }} runs-on: ${{ matrix.os }} strategy: @@ -99,6 +105,7 @@ jobs: release: needs: build + if: github.event_name == 'push' runs-on: ubuntu-latest permissions: contents: write diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 183ed5b..8d150ec 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -7,7 +7,8 @@ # - id: lintus # # pre-commit builds lintus from source the first time (it installs a Rust -# toolchain for that if the machine has none), then reuses the build. It passes +# toolchain for that if the machine has none, but a C compiler is needed), then +# reuses the build. It passes # the staged file names as arguments, so only the files in the commit are sent # to the model. The API key comes from JEV_API_KEY or `lintus auth login`. - id: lintus diff --git a/CHANGELOG.md b/CHANGELOG.md index 65e3637..c2d95a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - The GitHub Action downloads the binary instead of setting up Ruby; its `ruby-version` input is ignored. The pre-commit hook builds lintus from source. - A request that cannot reach the API fails its file instead of stopping the run. +- `--diff` and `--staged` together are an error; before, the last one given won. +- Skipped and failed files are listed in path order, not in the order they finished. - The Jev client ships as its own crate, `jev-api`. ## [0.2.0] - 2026-09-21 diff --git a/README.md b/README.md index 4492a59..d5699ca 100644 --- a/README.md +++ b/README.md @@ -229,8 +229,8 @@ repos: ``` pre-commit passes the staged file names, so only those are checked. The first run builds -lintus from source, installing a Rust toolchain for it if there is none; later runs reuse the -build. If lintus is already installed, a local hook skips the build: +lintus from source, installing a Rust toolchain for it if there is none (a C compiler is +still needed); later runs reuse the build. If lintus is already installed, a local hook skips the build: ```yaml repos: diff --git a/tests/fixtures/generate_round_cases.rb b/tests/fixtures/generate_round_cases.rb index 6e8fd35..041087a 100644 --- a/tests/fixtures/generate_round_cases.rb +++ b/tests/fixtures/generate_round_cases.rb @@ -21,6 +21,8 @@ def next_float(float, direction) = [bits(float) + direction].pack("Q<").unpack1( path = File.join(__dir__, "round_cases.tsv") File.open(path, "w") do |file| - values.uniq.each { |value| file.puts format("%016x\t%s\t%s", bits(value), value.round(2), value.round(3)) } + values.uniq.each do |value| + file.puts format("%016x\t%s\t%s", bits: bits(value), two: value.round(2), three: value.round(3)) + end end puts "Wrote #{values.uniq.size} cases to #{path}" From 05f65c1fe17df6671516b02f69cd927ee97e7115 Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:43:35 +0200 Subject: [PATCH 8/9] Remove the Ruby implementation The Rust binary passes the conformance suite that the gem passed. Against the real API, both checked the same 19 files, asked the same rules, and reported the same 8 offenses; the probabilities differed only by model noise (at most 0.12) on byte-identical requests. The gem stays published at 0.2.0. The glob fixture generator says which commit to regenerate it from. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 27 ----- .gitignore | 11 -- .rubocop.yml | 36 ------ CHANGELOG.md | 2 + Gemfile | 13 -- Rakefile | 12 -- bin/console | 8 -- bin/setup | 6 - exe/lintus | 6 - lib/lintus.rb | 22 ---- lib/lintus/cli.rb | 167 -------------------------- lib/lintus/config.rb | 77 ------------ lib/lintus/file_finder.rb | 67 ----------- lib/lintus/formatter.rb | 47 -------- lib/lintus/formatter/github.rb | 32 ----- lib/lintus/formatter/json.rb | 32 ----- lib/lintus/formatter/text.rb | 22 ---- lib/lintus/git.rb | 52 -------- lib/lintus/glob.rb | 38 ------ lib/lintus/offense.rb | 12 -- lib/lintus/progress.rb | 45 ------- lib/lintus/report.rb | 50 -------- lib/lintus/rule.rb | 91 -------------- lib/lintus/runner.rb | 102 ---------------- lib/lintus/schema.rb | 17 --- lib/lintus/source_file.rb | 21 ---- lib/lintus/template.rb | 50 -------- lib/lintus/version.rb | 5 - lintus.gemspec | 37 ------ test/support/jev_api_url.rb | 10 -- test/support/lintus-ruby | 7 -- test/test_cli.rb | 143 ---------------------- test/test_config.rb | 72 ----------- test/test_file_finder.rb | 111 ----------------- test/test_formatters.rb | 81 ------------- test/test_glob.rb | 37 ------ test/test_helper.rb | 80 ------------ test/test_progress.rb | 45 ------- test/test_report.rb | 33 ----- test/test_rule.rb | 84 ------------- test/test_runner.rb | 136 --------------------- tests/fixtures/generate_glob_cases.rb | 7 +- 42 files changed, 6 insertions(+), 1947 deletions(-) delete mode 100644 .rubocop.yml delete mode 100644 Gemfile delete mode 100644 Rakefile delete mode 100755 bin/console delete mode 100755 bin/setup delete mode 100755 exe/lintus delete mode 100644 lib/lintus.rb delete mode 100644 lib/lintus/cli.rb delete mode 100644 lib/lintus/config.rb delete mode 100644 lib/lintus/file_finder.rb delete mode 100644 lib/lintus/formatter.rb delete mode 100644 lib/lintus/formatter/github.rb delete mode 100644 lib/lintus/formatter/json.rb delete mode 100644 lib/lintus/formatter/text.rb delete mode 100644 lib/lintus/git.rb delete mode 100644 lib/lintus/glob.rb delete mode 100644 lib/lintus/offense.rb delete mode 100644 lib/lintus/progress.rb delete mode 100644 lib/lintus/report.rb delete mode 100644 lib/lintus/rule.rb delete mode 100644 lib/lintus/runner.rb delete mode 100644 lib/lintus/schema.rb delete mode 100644 lib/lintus/source_file.rb delete mode 100644 lib/lintus/template.rb delete mode 100644 lib/lintus/version.rb delete mode 100644 lintus.gemspec delete mode 100644 test/support/jev_api_url.rb delete mode 100755 test/support/lintus-ruby delete mode 100644 test/test_cli.rb delete mode 100644 test/test_config.rb delete mode 100644 test/test_file_finder.rb delete mode 100644 test/test_formatters.rb delete mode 100644 test/test_glob.rb delete mode 100644 test/test_helper.rb delete mode 100644 test/test_progress.rb delete mode 100644 test/test_report.rb delete mode 100644 test/test_rule.rb delete mode 100644 test/test_runner.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 992b162..a3a4a93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,30 +37,3 @@ jobs: - name: Test run: cargo test --workspace --locked - ruby: - runs-on: ubuntu-latest - name: Ruby ${{ matrix.ruby }} - strategy: - fail-fast: false - matrix: - ruby: - - "3.2" - - "3.3" - - "3.4" - - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - - name: Run tests and RuboCop - run: bundle exec rake - - uses: dtolnay/rust-toolchain@stable - - name: Run the conformance suite against the Ruby implementation - run: cargo test --locked --test conformance - env: - LINTUS_BIN: test/support/lintus-ruby diff --git a/.gitignore b/.gitignore index 0fd6508..b83d222 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1 @@ -/.bundle/ -/coverage/ -/doc/ -/pkg/ -/tmp/ -/vendor/bundle/ - -*.gem -/.irbrc -Gemfile.lock - /target/ diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index 312fe0f..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,36 +0,0 @@ -AllCops: - TargetRubyVersion: 3.2 - NewCops: enable - Exclude: - - "vendor/**/*" - - "tmp/**/*" - -Style/StringLiterals: - EnforcedStyle: double_quotes - -Style/StringLiteralsInInterpolation: - EnforcedStyle: double_quotes - -Style/Documentation: - Enabled: false - -Metrics/BlockLength: - Exclude: - - "test/**/*" - - "lintus.gemspec" - -Metrics/ClassLength: - Exclude: - - "test/**/*" - -Metrics/AbcSize: - Max: 40 - -Metrics/MethodLength: - Max: 25 - -Metrics/ClassLength: - Max: 150 - -Layout/LineLength: - Max: 130 diff --git a/CHANGELOG.md b/CHANGELOG.md index c2d95a4..9657f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ - `--diff` and `--staged` together are an error; before, the last one given won. - Skipped and failed files are listed in path order, not in the order they finished. - The Jev client ships as its own crate, `jev-api`. +- The Ruby implementation is removed. The `lintus` gem stays at 0.2.0, and is no longer + updated. ## [0.2.0] - 2026-09-21 diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 97b91dc..0000000 --- a/Gemfile +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gemspec - -gem "irb" -gem "rake", "~> 13.0" - -gem "minitest", "~> 5.16" -gem "webmock", "~> 3.0" - -gem "rubocop", "~> 1.21" diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 2bf771f..0000000 --- a/Rakefile +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "bundler/gem_tasks" -require "minitest/test_task" - -Minitest::TestTask.create - -require "rubocop/rake_task" - -RuboCop::RakeTask.new - -task default: %i[test rubocop] diff --git a/bin/console b/bin/console deleted file mode 100755 index 141743b..0000000 --- a/bin/console +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require "bundler/setup" -require "lintus" -require "irb" - -IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup deleted file mode 100755 index cf4ad25..0000000 --- a/bin/setup +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' -set -vx - -bundle install diff --git a/exe/lintus b/exe/lintus deleted file mode 100755 index c8d5e04..0000000 --- a/exe/lintus +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require "lintus" - -exit Lintus::CLI.new.run(ARGV) diff --git a/lib/lintus.rb b/lib/lintus.rb deleted file mode 100644 index 4edba85..0000000 --- a/lib/lintus.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require "zeitwerk" -require "jev" -require "yaml" -require "pathname" -require_relative "lintus/version" - -loader = Zeitwerk::Loader.for_gem -loader.inflector.inflect("cli" => "CLI") -loader.setup - -# Lintus turns a YAML file of plain-language rules into a linter: every rule is -# asked to the Jev model as a noul question against each file it applies to, and -# an offense is reported wherever the answer says so. -module Lintus - class Error < StandardError; end - class ConfigError < Error; end - class ReadError < Error; end - - CONFIG_FILENAMES = %w[.lintus.yml lintus.yml .lintus.yaml lintus.yaml].freeze -end diff --git a/lib/lintus/cli.rb b/lib/lintus/cli.rb deleted file mode 100644 index 8b5c5d1..0000000 --- a/lib/lintus/cli.rb +++ /dev/null @@ -1,167 +0,0 @@ -# frozen_string_literal: true - -require "optparse" - -module Lintus - # The `lintus` command. - class CLI - USAGE = <<~USAGE - Usage: lintus [options] [FILE...] - lintus init - - With no FILE, lints every file in the tree. FILE may be a file or a directory. - USAGE - - # `command` is what to do; `selection` is which files, with `ref` for --diff - # and `files` for paths given on the command line. - Options = Struct.new( - :command, :selection, :ref, :files, :config, :format, :jobs, :api_key, :list, - keyword_init: true - ) - - attr_reader :stdout, :stderr - - def initialize(stdout: $stdout, stderr: $stderr, env: ENV, dir: Dir.pwd) - @stdout = stdout - @stderr = stderr - @env = env - @dir = dir - end - - def run(argv) - options = parse(argv) - - case options.command - when :exit then 0 - when :init then init - else lint(options) - end - rescue Error, Jev::Error, OptionParser::ParseError => e - stderr.puts "lintus: #{e.message}" - 2 - end - - private - - def lint(options) - config = Config.load(options.config, dir: @dir) - runner = Runner.new(config, jobs: options.jobs) - tasks = runner.plan(find_files(config, options)) - return list(tasks) if options.list - - configure_api_key!(options) - report = with_progress(tasks.size) { |progress| runner.run(tasks) { |done| progress.update(done) } } - Formatter.for(options.format).new(stdout).render(report) - report.exit_status - end - - def parse(argv) - options = default_options - positional = build_parser(options).parse(argv) - return options if options.command == :exit - - if positional.first == "init" - raise OptionParser::InvalidArgument, "init takes no arguments" if positional.size > 1 - - options.command = :init - elsif positional.any? - if options.selection != :all - raise OptionParser::InvalidArgument, - "FILE arguments cannot be combined with --diff or --staged" - end - - options.selection = :explicit - options.files = positional - end - options - end - - def default_options - Options.new( - command: :lint, selection: :all, format: @env["GITHUB_ACTIONS"] == "true" ? "github" : "text", - jobs: 4, api_key: @env["JEV_API_KEY"], list: false - ) - end - - def build_parser(options) # rubocop:disable Metrics/MethodLength - OptionParser.new do |parser| # rubocop:disable Metrics/BlockLength - parser.banner = USAGE - parser.separator "" - parser.separator "Which files:" - parser.on("-d", "--diff [REF]", "Only files changed since REF (default HEAD: uncommitted changes)") do |ref| - options.selection = :diff - options.ref = ref || "HEAD" - end - parser.on("-s", "--staged", "Only staged files, reading their staged content (for pre-commit hooks)") do - options.selection = :staged - end - parser.separator "" - parser.separator "How to run:" - parser.on("-c", "--config PATH", - "Config file (default: nearest #{CONFIG_FILENAMES.first} upwards from the current directory)") do |path| - options.config = path - end - parser.on("-f", "--format FORMAT", Formatter::NAMES, - "Output format: #{Formatter::NAMES.join(", ")} (default: text, or github under GitHub Actions)") do |format| - options.format = format - end - parser.on("-j", "--jobs N", Integer, "Concurrent requests to the Jev API (default 4)") do |jobs| - options.jobs = jobs - end - parser.on("--api-key KEY", "Jev API key (default: $JEV_API_KEY)") { |key| options.api_key = key } - parser.on("-l", "--list", "List the files and rules that would be checked, without calling the API") do - options.list = true - end - parser.separator "" - parser.on("-v", "--version", "Print the version") do - stdout.puts "lintus #{VERSION}" - options.command = :exit - end - parser.on("-h", "--help", "Print this help") do - stdout.puts parser - options.command = :exit - end - end - end - - def find_files(config, options) - finder = FileFinder.new(config.root) - - case options.selection - when :diff then finder.changed_since(options.ref) - when :staged then finder.staged - when :explicit then finder.explicit(options.files, from: @dir) - else finder.all - end - end - - def with_progress(total) - progress = Progress.new(stderr, total) - progress.start - yield progress - ensure - progress.finish - end - - def configure_api_key!(options) - raise Error, "no API key: set JEV_API_KEY or pass --api-key" if options.api_key.nil? || options.api_key.empty? - - Jev.api_key = options.api_key - end - - def list(tasks) - tasks.each { |file, rules| stdout.puts "#{file.path}: #{rules.map(&:id).join(", ")}" } - stdout.puts "#{tasks.size} file(s) would be checked" - 0 - end - - def init - path = File.join(@dir, CONFIG_FILENAMES.first) - raise Error, "#{path} already exists" if File.exist?(path) - - File.write(path, Template::CONFIG) - stdout.puts "Wrote #{path}" - 0 - end - end -end diff --git a/lib/lintus/config.rb b/lib/lintus/config.rb deleted file mode 100644 index a69a369..0000000 --- a/lib/lintus/config.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # The parsed config file. Its directory is the root every path is relative to. - class Config - DEFAULT_MAX_FILE_SIZE = 100_000 - KEYS = %w[rules paths exclude max_file_size].freeze - - attr_reader :root, :rules, :paths, :exclude, :max_file_size - - class << self - def load(path = nil, dir: Dir.pwd) - path ||= locate(dir) - unless path - raise ConfigError, - "No config file found: looked for #{CONFIG_FILENAMES.join(", ")} in #{dir} and its parents" - end - raise ConfigError, "Config file not found: #{path}" unless File.file?(path) - - path = File.expand_path(path) - new(parse(path), root: File.dirname(path)) - end - - # Walks up from `dir` and returns the first config file found, like git does for .git. - def locate(dir) - Pathname(dir).expand_path.ascend do |ancestor| - CONFIG_FILENAMES.each do |name| - candidate = ancestor / name - return candidate.to_s if candidate.file? - end - end - nil - end - - private - - def parse(path) - YAML.safe_load_file(path, permitted_classes: [Symbol], aliases: true) || {} - rescue Psych::SyntaxError => e - raise ConfigError, "#{path} is not valid YAML: #{e.message}" - end - end - - def initialize(data, root: Dir.pwd) - raise ConfigError, "config must be a map, got #{data.class}" unless data.is_a?(Hash) - - data = data.transform_keys(&:to_s) - Schema.reject_unknown_keys!(data, KEYS, context: "config") - - @root = File.expand_path(root) - @paths = Schema.string_list(data["paths"]) - @exclude = Schema.string_list(data["exclude"]) - @max_file_size = build_max_file_size(data.fetch("max_file_size", DEFAULT_MAX_FILE_SIZE)) - @rules = build_rules(data["rules"]) - end - - # The rules that apply to a repository-relative path, in config order. - def rules_for(path) = rules.select { |rule| rule.applies_to?(path) } - - def rule(id) = rules.find { |rule| rule.id == id.to_s } - - private - - def build_max_file_size(value) - raise ConfigError, "`max_file_size` must be a positive integer of bytes" unless value.is_a?(Integer) && value.positive? - - value - end - - def build_rules(rules) - raise ConfigError, "`rules` must be a map of rule id => attributes" unless rules.is_a?(Hash) - raise ConfigError, "`rules` is empty: nothing to lint" if rules.empty? - - rules.map { |id, attrs| Rule.new(id, attrs, default_paths: paths, default_exclude: exclude) } - end - end -end diff --git a/lib/lintus/file_finder.rb b/lib/lintus/file_finder.rb deleted file mode 100644 index 186cbd3..0000000 --- a/lib/lintus/file_finder.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # Resolves which files a run should look at, always as paths relative to the - # config root with forward slashes, in a stable order. - class FileFinder - attr_reader :root, :git - - def initialize(root, git: Git.new(root)) - @root = root - @git = git - end - - # Every file in the tree: git's view when inside a repository, else a walk. - def all = sources(files_under(root)) - - def changed_since(ref) - require_repository! - sources(git.changed_files_since(ref)) - end - - def staged - require_repository! - sources(git.staged_files, existing_only: false) { |path| git.staged_content(path) } - end - - # Paths given on the command line, resolved against the current directory. - def explicit(paths, from: Dir.pwd) - sources(paths.flat_map { |path| files_under(File.expand_path(path, from)) }) - end - - private - - def require_repository! - raise Error, "#{root} is not a git repository: --diff and --staged need git" unless git.repository? - end - - # Repository-relative paths of the files at or beneath an absolute path. - def files_under(absolute) - return [relativize(absolute)] unless File.directory?(absolute) - - relative = relativize(absolute) - return git.files(relative) if git.repository? - - Dir.glob("**/*", File::FNM_DOTMATCH, base: absolute) - .reject { |path| path == ".git" || path.start_with?(".git/") } - .map { |path| relative == "." ? path : File.join(relative, path) } - end - - def sources(paths, existing_only: true, &reader) - reader ||= ->(path) { File.binread(File.join(root, path)) } - - paths.sort.uniq.filter_map do |path| - next if existing_only && !File.file?(File.join(root, path)) - - SourceFile.new(path) { reader.call(path) } - end - end - - def relativize(absolute) - relative = Pathname.new(absolute).relative_path_from(Pathname.new(root)).to_s - raise Error, "#{absolute} is outside the config root #{root}" if relative.start_with?("..") - - relative - end - end -end diff --git a/lib/lintus/formatter.rb b/lib/lintus/formatter.rb deleted file mode 100644 index 261419d..0000000 --- a/lib/lintus/formatter.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # Renders a Report to an IO. Line-oriented subclasses override the three - # `*_line` hooks; anything else overrides `render`. - class Formatter - NAMES = %w[text github json].freeze - - def self.for(name) - raise Error, "unknown format #{name.inspect} (expected one of #{NAMES.join(", ")})" unless NAMES.include?(name.to_s) - - const_get(name.to_s.capitalize) - end - - attr_reader :io - - def initialize(io = $stdout) - @io = io - end - - def render(report) - report.sorted_offenses.each { |offense| io.puts offense_line(offense) } - report.skipped.each { |skipped| io.puts skipped_line(skipped) } - report.failures.each { |failure| io.puts failure_line(failure) } - before_summary(report) - io.puts summary(report) - end - - private - - def offense_line(offense) = raise(NotImplementedError) - def skipped_line(skipped) = raise(NotImplementedError) - def failure_line(failure) = raise(NotImplementedError) - - def before_summary(report); end - - def summary(report) - parts = ["#{pluralize(report.checked.size, "file")} inspected", - "#{pluralize(report.offenses.size, "offense")} detected"] - parts << "#{pluralize(report.skipped.size, "file")} skipped" if report.skipped.any? - parts << "#{pluralize(report.failures.size, "file")} failed" if report.failures.any? - parts.join(", ") - end - - def pluralize(count, noun) = "#{count} #{noun}#{"s" unless count == 1}" - end -end diff --git a/lib/lintus/formatter/github.rb b/lib/lintus/formatter/github.rb deleted file mode 100644 index a9a2d69..0000000 --- a/lib/lintus/formatter/github.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Lintus - class Formatter - # GitHub Actions workflow commands: each offense becomes an annotation on - # its file in the pull request, then a plain summary for the job log. - class Github < Formatter - private - - def offense_line(offense) - command("error", offense.message, file: offense.path, title: "lintus: #{offense.rule.id}") - end - - def skipped_line(skipped) - command("notice", "Skipped: #{skipped.reason}", file: skipped.path, title: "lintus") - end - - def failure_line(failure) - command("error", failure.error.message, file: failure.path, title: "lintus: request failed") - end - - def command(level, message, **properties) - props = properties.map { |key, value| "#{key}=#{escape_property(value)}" }.join(",") - "::#{level} #{props}::#{escape_data(message)}" - end - - def escape_data(value) = value.to_s.gsub("%", "%25").gsub("\r", "%0D").gsub("\n", "%0A") - - def escape_property(value) = escape_data(value).gsub(":", "%3A").gsub(",", "%2C") - end - end -end diff --git a/lib/lintus/formatter/json.rb b/lib/lintus/formatter/json.rb deleted file mode 100644 index 0e94f20..0000000 --- a/lib/lintus/formatter/json.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require "json" - -module Lintus - class Formatter - # Machine-readable output for other tools to consume. - class Json < Formatter - def render(report) - io.puts ::JSON.pretty_generate( - summary: { - files_inspected: report.checked.size, - offenses: report.offenses.size, - skipped: report.skipped.size, - failures: report.failures.size - }, - offenses: report.sorted_offenses.map(&:to_h), - files: report.checked.sort_by(&:path).map { |checked| file_entry(checked) }, - skipped: report.skipped.map { |skipped| { path: skipped.path, reason: skipped.reason } }, - failures: report.failures.map { |failure| { path: failure.path, error: failure.error.message } } - ) - end - - private - - # Every rule asked about the file and the probability it got, offense or not. - def file_entry(checked) - { path: checked.path, answers: checked.answers.transform_values { |noul| noul.round(3) } } - end - end - end -end diff --git a/lib/lintus/formatter/text.rb b/lib/lintus/formatter/text.rb deleted file mode 100644 index d8437ef..0000000 --- a/lib/lintus/formatter/text.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Lintus - class Formatter - # Human-readable output, one line per offense. - class Text < Formatter - private - - def offense_line(offense) - "#{offense.path}: [#{offense.rule.id}] #{offense.message} (noul #{offense.noul.round(2)})" - end - - def skipped_line(skipped) = "#{skipped.path}: skipped, #{skipped.reason}" - - def failure_line(failure) = "#{failure.path}: failed, #{failure.error.message}" - - def before_summary(report) - io.puts if report.offenses.any? || report.skipped.any? || report.failures.any? - end - end - end -end diff --git a/lib/lintus/git.rb b/lib/lintus/git.rb deleted file mode 100644 index ac99644..0000000 --- a/lib/lintus/git.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require "open3" - -module Lintus - # A thin wrapper around the git commands file discovery needs. - class Git - class CommandError < Error; end - - CHANGE_FILTER = "--diff-filter=ACMR" - - attr_reader :root - - def initialize(root) - @root = root - end - - def repository? = run("rev-parse", "--is-inside-work-tree", allow_failure: true) == "true" - - # Tracked and untracked files, honouring .gitignore, optionally under the given directories. - def files(*pathspecs) = lines("ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", *pathspecs) - - def staged_files = lines("diff", "-z", "--cached", "--name-only", CHANGE_FILTER) - - # Files that differ between the working tree and the merge base of `ref` and HEAD, - # plus untracked files. With `ref` at HEAD this is "what I have not committed yet". - def changed_files_since(ref) - base = run("merge-base", ref, "HEAD", allow_failure: true) || ref - lines("diff", "-z", "--name-only", CHANGE_FILTER, base) + lines("ls-files", "-z", "--others", "--exclude-standard") - end - - def staged_content(path) = run("show", ":#{path}", chomp: false) - - private - - def lines(*) - run(*, chomp: false).split("\0").reject(&:empty?) - end - - def run(*args, allow_failure: false, chomp: true) - stdout, stderr, status = Open3.capture3("git", *args, chdir: root) - - unless status.success? - return nil if allow_failure - - raise CommandError, "git #{args.join(" ")} failed: #{stderr.strip}" - end - - chomp ? stdout.chomp : stdout - end - end -end diff --git a/lib/lintus/glob.rb b/lib/lintus/glob.rb deleted file mode 100644 index 5382e5c..0000000 --- a/lib/lintus/glob.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # Matches repository-relative paths against the globs used in the config file. - # - # Patterns follow Ruby's File.fnmatch with pathname semantics ("**/" spans - # directories, "{a,b}" alternation is allowed), with two conveniences: - # a bare directory ("vendor" or "vendor/") matches everything beneath it, - # and a trailing "**" ("vendor/**") does the same. - module Glob - FLAGS = File::FNM_PATHNAME | File::FNM_EXTGLOB | File::FNM_DOTMATCH - - # Every pattern is expanded once, however many paths it is matched against. - EXPANSIONS = Hash.new { |cache, pattern| cache[pattern] = expand(pattern) } - - module_function - - def match?(pattern, path) - EXPANSIONS[pattern].any? { |expanded| File.fnmatch?(expanded, path, FLAGS) } - end - - def match_any?(patterns, path) - patterns.any? { |pattern| match?(pattern, path) } - end - - def expand(pattern) - pattern = pattern.to_s.delete_prefix("./").delete_suffix("/") - - if pattern.end_with?("**") - [pattern, "#{pattern}/*"] - elsif pattern.match?(/[*?\[{]/) - [pattern] - else - [pattern, "#{pattern}/**/*"] - end - end - end -end diff --git a/lib/lintus/offense.rb b/lib/lintus/offense.rb deleted file mode 100644 index bdf7c0d..0000000 --- a/lib/lintus/offense.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # A rule the model flagged on a file, with the probability it gave. - Offense = Struct.new(:path, :rule, :noul, keyword_init: true) do - def message = rule.description - - def to_h - { path: path, rule: rule.id, message: message, noul: noul.round(3) } - end - end -end diff --git a/lib/lintus/progress.rb b/lib/lintus/progress.rb deleted file mode 100644 index 3981d80..0000000 --- a/lib/lintus/progress.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # Tells the person waiting that something is happening. On a terminal it is a - # counter updated in place and erased when done; elsewhere (CI logs, pipes) it - # is a single line announcing the run, so nothing garbles captured output. - class Progress - def initialize(io, total) - @io = io - @total = total - @tty = io.respond_to?(:tty?) && io.tty? - @width = 0 - end - - def start - return if @total.zero? - - if @tty - draw(0) - else - @io.puts("Checking #{@total} #{@total == 1 ? "file" : "files"}...") - end - end - - def update(done) - draw(done) if @tty - end - - def finish - return unless @tty && @width.positive? - - @io.print "\r#{" " * @width}\r" - @io.flush - end - - private - - def draw(done) - line = "Checking #{done}/#{@total} files" - @width = [@width, line.size].max - @io.print "\r#{line.ljust(@width)}" - @io.flush - end - end -end diff --git a/lib/lintus/report.rb b/lib/lintus/report.rb deleted file mode 100644 index 3f27f9f..0000000 --- a/lib/lintus/report.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # Everything a run produced: what was checked, what was flagged, what was skipped, what broke. - class Report - Checked = Struct.new(:path, :answers, keyword_init: true) - Skipped = Struct.new(:path, :reason, keyword_init: true) - Failure = Struct.new(:path, :error, keyword_init: true) - - attr_reader :checked, :offenses, :skipped, :failures - - def initialize - @checked = [] - @offenses = [] - @skipped = [] - @failures = [] - @mutex = Mutex.new - end - - # `answers` maps every rule asked about the file to the probability it got, - # offense or not, so a run can be read for confidence and not just verdicts. - def record_checked(path, offenses, answers = {}) - synchronize do - checked << Checked.new(path: path, answers: answers) - self.offenses.concat(offenses) - end - end - - def record_skipped(path, reason) - synchronize { skipped << Skipped.new(path: path, reason: reason) } - end - - def record_failure(path, error) - synchronize { failures << Failure.new(path: path, error: error) } - end - - def sorted_offenses = offenses.sort_by { |offense| [offense.path, offense.rule.id] } - - # 2 when a file could not be checked, 1 when there are offenses, else 0. - def exit_status - return 2 if failures.any? - - offenses.any? ? 1 : 0 - end - - private - - def synchronize(&) = @mutex.synchronize(&) - end -end diff --git a/lib/lintus/rule.rb b/lib/lintus/rule.rb deleted file mode 100644 index 5581dba..0000000 --- a/lib/lintus/rule.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # One entry of the `rules:` map in the config file. - # - # A rule is a noul question. It is asked against every file whose path - # matches its globs, and the file is an offense when the answer equals - # `offense_when` (true by default, so questions are phrased to describe the - # offense: "Does this file call sleep?"). - class Rule - KEYS = %w[question description criteria threshold paths exclude offense_when].freeze - ID_FORMAT = /\A[a-z][a-z0-9_]*\z/ - - attr_reader :id, :description, :question, :criteria, :threshold, :paths, :exclude, :offense_when - - def initialize(id, attrs, default_paths: [], default_exclude: []) - @id = validate_id(id) - attrs = normalize(attrs) - - @question = fetch_string(attrs, "question") - @description = attrs.fetch("description", @question).to_s - @criteria = build_criteria(attrs["criteria"]) - @threshold = build_threshold(attrs["threshold"]) - @paths = Schema.string_list(attrs.fetch("paths", default_paths)) - @exclude = default_exclude + Schema.string_list(attrs["exclude"]) - @offense_when = build_offense_when(attrs.fetch("offense_when", true)) - end - - def applies_to?(path) - return false if Glob.match_any?(exclude, path) - - paths.empty? || Glob.match_any?(paths, path) - end - - def add_to(query) - options = { criteria: criteria, threshold: threshold }.compact - query.ask(id, question, **options) - end - - def offense?(answer) = answer.result == offense_when - - private - - def validate_id(id) - id = id.to_s - unless id.match?(ID_FORMAT) - raise ConfigError, - "invalid rule id #{id.inspect}: use snake_case (letters, digits, underscores)" - end - - id - end - - def normalize(attrs) - raise ConfigError, "rule #{id}: expected a map of attributes, got #{attrs.inspect}" unless attrs.is_a?(Hash) - - attrs = attrs.transform_keys(&:to_s) - Schema.reject_unknown_keys!(attrs, KEYS, context: "rule #{id}") - attrs - end - - def fetch_string(attrs, key) - value = attrs[key] - raise ConfigError, "rule #{id}: `#{key}` is required" if value.nil? || value.to_s.strip.empty? - - value.to_s.strip - end - - def build_criteria(criteria) - return nil if criteria.nil? - raise ConfigError, "rule #{id}: `criteria` must be a map with `true` and `false` keys" unless criteria.is_a?(Hash) - - criteria.to_h { |key, value| [key.to_s, value.to_s] } - end - - def build_threshold(threshold) - return nil if threshold.nil? - - valid = threshold.is_a?(Numeric) && threshold.between?(0, 1) - raise ConfigError, "rule #{id}: `threshold` must be a number between 0 and 1" unless valid - - threshold.to_f - end - - def build_offense_when(value) - raise ConfigError, "rule #{id}: `offense_when` must be true or false" unless [true, false].include?(value) - - value - end - end -end diff --git a/lib/lintus/runner.rb b/lib/lintus/runner.rb deleted file mode 100644 index ad12473..0000000 --- a/lib/lintus/runner.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # Sends each file to Jev once, with every rule that applies to it as a - # separate noul question, and turns the answers into offenses. - class Runner - RETRYABLE = [Jev::RateLimitError, Jev::OverloadedError].freeze - - attr_reader :config, :jobs, :retries - - def initialize(config, jobs: 4, retries: 3, sleeper: Kernel.method(:sleep)) - @config = config - @jobs = [jobs.to_i, 1].max - @retries = retries - @sleeper = sleeper - end - - # Pairs every file with the rules that apply to it, dropping files no rule covers. - def plan(files) - files.filter_map do |file| - rules = config.rules_for(file.path) - [file, rules] unless rules.empty? - end - end - - # Checks the [file, rules] pairs from #plan concurrently. The block, if - # given, is called with the number of files done after each one finishes. - def run(tasks) - report = Report.new - queue = Queue.new - tasks.each { |task| queue << task } - queue.close - done = 0 - counter = Mutex.new - - workers = Array.new([jobs, tasks.size].min) do - Thread.new do - while (task = queue.pop) - check(*task, report) - counter.synchronize { yield(done += 1) } if block_given? - end - end - end - workers.each(&:join) - - report - end - - def check(file, rules, report) - content = file.content - if (reason = skip_reason(content)) - report.record_skipped(file.path, reason) - return - end - - response = with_retries { perform(file, rules, content) } - answers = rules.to_h { |rule| [rule.id, response.answers[rule.id].noul] } - report.record_checked(file.path, offenses_for(file, rules, response.answers), answers) - rescue Jev::Error, Error => e - report.record_failure(file.path, e) - end - - private - - def skip_reason(content) - return "binary file" if content.include?("\0") || !content.valid_encoding? - if content.bytesize > config.max_file_size - return "larger than max_file_size (#{content.bytesize} > #{config.max_file_size} bytes)" - end - - nil - end - - def perform(file, rules, content) - Jev::Query.new("File: #{file.path}\n\n#{content}").perform do |query| - rules.each { |rule| rule.add_to(query) } - end - end - - def offenses_for(file, rules, answers) - rules.filter_map do |rule| - answer = answers[rule.id] - Offense.new(path: file.path, rule: rule, noul: answer.noul) if rule.offense?(answer) - end - end - - def with_retries - attempt = 0 - begin - yield - rescue *RETRYABLE - raise if attempt >= retries - - attempt += 1 - @sleeper.call(backoff_for(attempt)) - retry - end - end - - def backoff_for(attempt) = (2**attempt) * (0.5 + (rand / 2)) - end -end diff --git a/lib/lintus/schema.rb b/lib/lintus/schema.rb deleted file mode 100644 index 7dd3bb2..0000000 --- a/lib/lintus/schema.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # Small checks shared by everything that reads the config file. - module Schema - module_function - - def reject_unknown_keys!(hash, allowed, context:) - unknown = hash.keys - allowed - return if unknown.empty? - - raise ConfigError, "#{context}: unknown key(s) #{unknown.join(", ")} (expected #{allowed.join(", ")})" - end - - def string_list(value) = Array(value).map(&:to_s) - end -end diff --git a/lib/lintus/source_file.rb b/lib/lintus/source_file.rb deleted file mode 100644 index d5c33fb..0000000 --- a/lib/lintus/source_file.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # A file to lint: a repository-relative path and a reader for its content. - # The reader is what lets `--staged` lint the index rather than the working tree. - class SourceFile - attr_reader :path - - def initialize(path, &reader) - @path = path - @reader = reader - end - - # Reads the content afresh on every call so a run holds only the files in flight. - def content - (+@reader.call).force_encoding(Encoding::UTF_8) - rescue SystemCallError, Git::CommandError => e - raise ReadError, "could not read #{path}: #{e.message}" - end - end -end diff --git a/lib/lintus/template.rb b/lib/lintus/template.rb deleted file mode 100644 index 7efba7e..0000000 --- a/lib/lintus/template.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Lintus - # The starter config written by `lintus init`. - module Template - CONFIG = <<~YAML - # Lintus configuration. Every rule is a question asked of the Jev model - # about each matching file. Answer "true" means the file is an offense. - # - # Top-level `paths` and `exclude` apply to every rule; a rule can narrow - # them with its own `paths` and `exclude`. Globs are relative to this file. - - paths: - - "**/*.rb" - - exclude: - - "vendor/**" - - "node_modules/**" - - "tmp/**" - - # Files larger than this (in bytes) are skipped rather than sent to the model. - max_file_size: 100000 - - rules: - no_debugging_leftovers: - description: Debugging statements must not be committed. - question: Does this file contain leftover debugging code, such as binding.irb, debugger, byebug, or a puts/p/pp used for debugging? - criteria: - "true": A debugger breakpoint or a throwaway print statement is present. - "false": Any output is deliberate program behaviour, or there is none. - - no_hardcoded_secrets: - description: Secrets must come from the environment or a credentials store, never source code. - question: Does this file contain a hardcoded secret, such as an API key, password, or private token? - criteria: - "true": A literal credential value is written in the source. - "false": Credentials are read from configuration, or there are none. - threshold: 0.7 - - classes_are_documented: - description: Classes should open with a short comment explaining their purpose. - question: Is there a class or module defined in this file whose definition is not preceded by a comment describing what it is for? - criteria: - "true": A class or module with a body of its own has no descriptive comment right above its definition. - "false": >- - Every class or module that carries behaviour has such a comment, or the file defines none. - Ignore namespace wrappers that only nest other definitions and one-line subclasses. - YAML - end -end diff --git a/lib/lintus/version.rb b/lib/lintus/version.rb deleted file mode 100644 index b90383b..0000000 --- a/lib/lintus/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module Lintus - VERSION = "0.2.0" -end diff --git a/lintus.gemspec b/lintus.gemspec deleted file mode 100644 index fe60f5f..0000000 --- a/lintus.gemspec +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require_relative "lib/lintus/version" - -Gem::Specification.new do |spec| - spec.name = "lintus" - spec.version = Lintus::VERSION - spec.authors = ["Vincent Rolea"] - spec.email = ["3525369+virolea@users.noreply.github.com"] - - spec.summary = "A linter whose rules are plain-language questions, answered by the Jev model." - spec.description = "Lintus reads linting rules from a YAML file at the root of your repository, " \ - "asks the Typesafe Jev model each rule as a noul question against every " \ - "matching file, and reports an offense wherever the model says so. " \ - "Runs on a whole tree, a git diff, or the staged files of a commit." - spec.homepage = "https://github.com/virolea/lintus" - spec.license = "MIT" - spec.required_ruby_version = ">= 3.2.0" - spec.metadata["allowed_push_host"] = "https://rubygems.org" - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md" - spec.metadata["rubygems_mfa_required"] = "true" - - gemspec = File.basename(__FILE__) - spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| - ls.readlines("\x0", chomp: true).reject do |f| - (f == gemspec) || - f.start_with?(*%w[bin/ Gemfile .gitignore test/ .github/ .rubocop.yml .lintus.yml]) - end - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] - - spec.add_dependency "jev", "~> 0.2" - spec.add_dependency "zeitwerk", "~> 2.6" -end diff --git a/test/support/jev_api_url.rb b/test/support/jev_api_url.rb deleted file mode 100644 index 0e3684b..0000000 --- a/test/support/jev_api_url.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -# Loaded through RUBYOPT by test/support/lintus-ruby: points the Jev client at -# $JEV_API_URL so the conformance suite can run the Ruby lintus against a fake server. -require "jev" - -if (url = ENV.fetch("JEV_API_URL", nil)) - Jev::Client.send(:remove_const, :API_URL) - Jev::Client.const_set(:API_URL, url) -end diff --git a/test/support/lintus-ruby b/test/support/lintus-ruby deleted file mode 100755 index 1589945..0000000 --- a/test/support/lintus-ruby +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -# Runs the Ruby lintus from this checkout, honouring $JEV_API_URL. Used to check -# the conformance suite against the Ruby implementation: -# -# LINTUS_BIN=test/support/lintus-ruby cargo test --test conformance -root=$(cd "$(dirname "$0")/../.." && pwd) -RUBYOPT="-I$root/lib -r$root/test/support/jev_api_url.rb $RUBYOPT" exec ruby "$root/exe/lintus" "$@" diff --git a/test/test_cli.rb b/test/test_cli.rb deleted file mode 100644 index 4512bda..0000000 --- a/test/test_cli.rb +++ /dev/null @@ -1,143 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestCLI < Minitest::Test - include LintusTestHelpers - - CONFIG = <<~YAML - paths: ["**/*.rb"] - rules: - no_sleep: - description: Jobs must not sleep. - question: Does this file call sleep? - paths: ["app/jobs/**/*.rb"] - documented: - question: Is every class documented? - offense_when: false - YAML - - def test_init_writes_a_loadable_config_and_refuses_to_overwrite - Dir.mktmpdir do |dir| - assert_equal 0, run_cli(%w[init], dir: dir) - assert_includes @stdout.string, "Wrote" - - config = Lintus::Config.load(dir: dir) - assert_equal 3, config.rules.size - - assert_equal 2, run_cli(%w[init], dir: dir) - assert_includes @stderr.string, "already exists" - end - end - - def test_version_and_help - assert_equal 0, run_cli(%w[--version]) - assert_equal "lintus #{Lintus::VERSION}\n", @stdout.string - - assert_equal 0, run_cli(%w[--help]) - assert_includes @stdout.string, "Usage: lintus" - end - - def test_list_does_not_need_an_api_key - with_git_repo do |dir| - write(".lintus.yml", CONFIG) - write("app/jobs/a_job.rb") - write("lib/b.rb") - write("README.md", "hi") - - assert_equal 0, run_cli(%w[--list], dir: dir, env: {}) - assert_equal "app/jobs/a_job.rb: no_sleep, documented\nlib/b.rb: documented\n2 file(s) would be checked\n", - @stdout.string - end - end - - def test_missing_api_key_is_a_usage_error - with_git_repo do |dir| - write(".lintus.yml", CONFIG) - write("lib/b.rb") - - assert_equal 2, run_cli([], dir: dir, env: {}) - assert_includes @stderr.string, "JEV_API_KEY" - end - end - - def test_full_run_reports_offenses_and_exit_status - stub_jev({ "no_sleep" => noul(0.95), "documented" => noul(0.9) }) - - with_git_repo do |dir| - write(".lintus.yml", CONFIG) - write("app/jobs/a_job.rb", "sleep 1\n") - - assert_equal 1, run_cli([], dir: dir) - assert_equal <<~TEXT, @stdout.string - app/jobs/a_job.rb: [no_sleep] Jobs must not sleep. (noul 0.95) - - 1 file inspected, 1 offense detected - TEXT - assert_equal "Checking 1 file...\n", @stderr.string - - assert_equal 1, run_cli(%w[--format json --api-key from-flag], dir: dir, env: {}) - assert_equal 1, JSON.parse(@stdout.string)["summary"]["offenses"] - end - end - - def test_diff_and_staged_and_explicit_files - stub_jev({ "documented" => noul(0.9) }) - - with_git_repo do |dir| - write(".lintus.yml", CONFIG) - write("lib/old.rb") - commit_all - write("lib/new.rb") - git "add", "lib/new.rb" - write("lib/untracked.rb") - - assert_equal 0, run_cli(%w[--diff --list], dir: dir) - assert_includes @stdout.string, "2 file(s) would be checked" - - assert_equal 0, run_cli(%w[--staged --list], dir: dir) - assert_equal "lib/new.rb: documented\n1 file(s) would be checked\n", @stdout.string - - assert_equal 0, run_cli(%w[lib/old.rb --list], dir: dir) - assert_equal "lib/old.rb: documented\n1 file(s) would be checked\n", @stdout.string - - assert_equal 0, run_cli(%w[--staged], dir: dir) - assert_equal "1 file inspected, 0 offenses detected\n", @stdout.string - end - end - - def test_conflicting_and_invalid_arguments - with_git_repo do |dir| - write(".lintus.yml", CONFIG) - - assert_equal 2, run_cli(%w[--staged lib/a.rb], dir: dir) - assert_includes @stderr.string, "cannot be combined" - - assert_equal 2, run_cli(%w[--format xml], dir: dir) - assert_equal 2, run_cli(%w[init extra], dir: dir) - assert_equal 2, run_cli(%w[--config nope.yml], dir: dir) - assert_includes @stderr.string, "nope.yml" - end - end - - def test_github_format_is_the_default_under_actions - stub_jev({ "documented" => noul(0.1) }) - - with_git_repo do |dir| - write(".lintus.yml", CONFIG) - write("lib/b.rb") - - run_cli([], dir: dir, env: { "JEV_API_KEY" => "k", "GITHUB_ACTIONS" => "true" }) - - assert_includes @stdout.string, "::error file=lib/b.rb,title=lintus%3A documented::Is every class documented?" - end - end - - private - - def run_cli(argv, dir: Dir.pwd, env: { "JEV_API_KEY" => "test-key" }) - @stdout = StringIO.new - @stderr = StringIO.new - Lintus::CLI.new(stdout: @stdout, stderr: @stderr, env: env, dir: dir).run(argv) - end -end diff --git a/test/test_config.rb b/test/test_config.rb deleted file mode 100644 index a8230e5..0000000 --- a/test/test_config.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestConfig < Minitest::Test - include LintusTestHelpers - - def test_rules_for_applies_global_exclude_then_rule_globs - config = build_config("exclude" => ["vendor/**"]) - - assert_equal %w[no_sleep documented], config.rules_for("app/jobs/a_job.rb").map(&:id) - assert_equal %w[documented], config.rules_for("lib/a.rb").map(&:id) - assert_empty config.rules_for("vendor/gem/a.rb") - assert_empty config.rules_for("README.md") - end - - def test_rule_lookup_and_defaults - config = build_config - - assert_equal "no_sleep", config.rule(:no_sleep).id - assert_equal Lintus::Config::DEFAULT_MAX_FILE_SIZE, config.max_file_size - end - - def test_load_walks_up_from_a_subdirectory - Dir.mktmpdir do |dir| - write(File.join(dir, ".lintus.yml"), "rules:\n r:\n question: q\n") - FileUtils.mkdir_p(File.join(dir, "a/b")) - - config = Lintus::Config.load(dir: File.join(dir, "a/b")) - - assert_equal File.realpath(dir), File.realpath(config.root) - assert_equal %w[r], config.rules.map(&:id) - end - end - - def test_load_with_explicit_path_uses_its_directory_as_root - Dir.mktmpdir do |dir| - path = File.join(dir, "custom.yml") - write(path, "rules:\n r:\n question: q\n") - - config = Lintus::Config.load(path, dir: "/") - - assert_equal File.expand_path(dir), config.root - end - end - - def test_load_errors - Dir.mktmpdir do |dir| - error = assert_raises(Lintus::ConfigError) { Lintus::Config.load(dir: dir) } - assert_includes error.message, "No config file found" - - assert_raises(Lintus::ConfigError) { Lintus::Config.load(File.join(dir, "missing.yml")) } - - write(File.join(dir, ".lintus.yml"), "rules: [\n") - error = assert_raises(Lintus::ConfigError) { Lintus::Config.load(dir: dir) } - assert_includes error.message, "not valid YAML" - end - end - - def test_validation - assert_raises(Lintus::ConfigError) { Lintus::Config.new([]) } - assert_raises(Lintus::ConfigError) { Lintus::Config.new({ "rules" => {} }) } - assert_raises(Lintus::ConfigError) { Lintus::Config.new({ "rules" => [] }) } - assert_raises(Lintus::ConfigError) { build_config("max_file_size" => "big") } - - error = assert_raises(Lintus::ConfigError) { build_config("rule" => {}) } - assert_includes error.message, "config: unknown key(s) rule" - - error = assert_raises(Lintus::ConfigError) { build_config("rules" => { "r" => { "question" => "q", "severty" => "x" } }) } - assert_includes error.message, "rule r: unknown key(s) severty" - end -end diff --git a/test/test_file_finder.rb b/test/test_file_finder.rb deleted file mode 100644 index d08fa25..0000000 --- a/test/test_file_finder.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestFileFinder < Minitest::Test - include LintusTestHelpers - - def test_all_lists_tracked_and_untracked_but_not_ignored_files - with_git_repo do |dir| - write("lib/a.rb") - write(".gitignore", "ignored.rb\n") - commit_all - write("untracked.rb") - write("ignored.rb") - - assert_equal %w[.gitignore lib/a.rb untracked.rb], Lintus::FileFinder.new(dir).all.map(&:path) - end - end - - def test_all_outside_git_globs_the_tree - Dir.mktmpdir do |dir| - write(File.join(dir, "a.rb")) - write(File.join(dir, "sub/.hidden")) - - assert_equal %w[.hidden a.rb].sort, Lintus::FileFinder.new(dir).all.map(&:path).map { File.basename(_1) }.sort - end - end - - def test_changed_since_uses_merge_base_and_includes_working_tree_and_untracked - with_git_repo do |dir| - write("lib/a.rb") - write("lib/b.rb") - commit_all - git "checkout", "-qb", "feature" - write("lib/a.rb", "class Foo; def x; end; end\n") - commit_all "change a" - git "checkout", "-q", "-" - write("lib/b.rb", "class Bar; end\n") - commit_all "change b on main" - git "checkout", "-q", "feature" - write("lib/c.rb") - write("lib/d.rb") - git "add", "lib/c.rb" - - finder = Lintus::FileFinder.new(dir) - main = git("rev-parse", "--abbrev-ref", "@{-1}").strip - - assert_equal %w[lib/a.rb lib/c.rb lib/d.rb], finder.changed_since(main).map(&:path) - assert_equal %w[lib/c.rb lib/d.rb], finder.changed_since("HEAD").map(&:path) - end - end - - def test_changed_since_omits_deleted_files - with_git_repo do |dir| - write("lib/a.rb") - commit_all - File.delete("lib/a.rb") - - assert_empty Lintus::FileFinder.new(dir).changed_since("HEAD") - end - end - - def test_staged_reads_index_content_not_working_tree - with_git_repo do |dir| - write("lib/a.rb", "staged\n") - git "add", "lib/a.rb" - write("lib/a.rb", "working tree\n") - write("lib/unstaged.rb") - - files = Lintus::FileFinder.new(dir).staged - - assert_equal %w[lib/a.rb], files.map(&:path) - assert_equal "staged\n", files.first.content - end - end - - def test_git_modes_require_a_repository - Dir.mktmpdir do |dir| - assert_raises(Lintus::Error) { Lintus::FileFinder.new(dir).staged } - assert_raises(Lintus::Error) { Lintus::FileFinder.new(dir).changed_since("HEAD") } - end - end - - def test_explicit_resolves_files_and_directories_relative_to_the_caller - Dir.mktmpdir do |dir| - dir = File.realpath(dir) - write(File.join(dir, "app/jobs/a.rb")) - write(File.join(dir, "app/b.rb")) - write(File.join(dir, "lib/c.rb")) - - files = Lintus::FileFinder.new(dir).explicit(["jobs", "../lib/c.rb", "missing.rb"], from: File.join(dir, "app")) - - assert_equal %w[app/jobs/a.rb lib/c.rb], files.map(&:path) - assert_raises(Lintus::Error) { Lintus::FileFinder.new(dir).explicit(["/etc/hostname"]) } - end - end - - def test_explicit_directories_honour_gitignore_inside_a_repository - with_git_repo do |dir| - write("app/a.rb") - write("app/ignored.rb") - write(".gitignore", "ignored.rb\n") - commit_all - write("app/untracked.rb") - - files = Lintus::FileFinder.new(dir).explicit(["app", "."], from: dir) - - assert_equal %w[.gitignore app/a.rb app/untracked.rb], files.map(&:path) - end - end -end diff --git a/test/test_formatters.rb b/test/test_formatters.rb deleted file mode 100644 index bbff7fb..0000000 --- a/test/test_formatters.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestFormatters < Minitest::Test - include LintusTestHelpers - - def setup - config = build_config - @report = Lintus::Report.new - @report.record_checked("lib/ok.rb", [], { "no_sleep" => 0.0123, "documented" => 0.9 }) - @report.record_checked("app/jobs/a_job.rb", [ - Lintus::Offense.new(path: "app/jobs/a_job.rb", rule: config.rule(:no_sleep), noul: 0.912), - Lintus::Offense.new(path: "app/jobs/a_job.rb", rule: config.rule(:documented), noul: 0.3) - ]) - @report.record_skipped("lib/blob.rb", "binary file") - @report.record_failure("lib/bad.rb", Jev::APIError.new(500, "boom")) - end - - def test_text - output = render("text") - - assert_equal <<~TEXT, output - app/jobs/a_job.rb: [documented] Classes carry a comment. (noul 0.3) - app/jobs/a_job.rb: [no_sleep] Jobs must not sleep. (noul 0.91) - lib/blob.rb: skipped, binary file - lib/bad.rb: failed, Jev API error 500: boom - - 2 files inspected, 2 offenses detected, 1 file skipped, 1 file failed - TEXT - end - - def test_text_when_clean - assert_equal "0 files inspected, 0 offenses detected\n", render("text", Lintus::Report.new) - end - - def test_github_emits_workflow_commands - lines = render("github").lines(chomp: true) - - assert_equal "::error file=app/jobs/a_job.rb,title=lintus%3A documented::Classes carry a comment.", lines[0] - assert_equal "::error file=app/jobs/a_job.rb,title=lintus%3A no_sleep::Jobs must not sleep.", lines[1] - assert_equal "::notice file=lib/blob.rb,title=lintus::Skipped: binary file", lines[2] - assert_equal "::error file=lib/bad.rb,title=lintus%3A request failed::Jev API error 500: boom", lines[3] - assert_equal "2 files inspected, 2 offenses detected, 1 file skipped, 1 file failed", lines[4] - end - - def test_github_escapes_newlines_and_percent_in_messages - config = Lintus::Config.new({ "rules" => { "r" => { "question" => "q", "description" => "100%\nsure" } } }) - report = Lintus::Report.new - report.record_checked("a.rb", [Lintus::Offense.new(path: "a.rb", rule: config.rule(:r), noul: 1.0)]) - - assert_equal "::error file=a.rb,title=lintus%3A r::100%25%0Asure\n1 file inspected, 1 offense detected\n", - render("github", report) - end - - def test_json - data = JSON.parse(render("json")) - - summary = { "files_inspected" => 2, "offenses" => 2, "skipped" => 1, "failures" => 1 } - offense = { "path" => "app/jobs/a_job.rb", "rule" => "no_sleep", "message" => "Jobs must not sleep.", "noul" => 0.912 } - - assert_equal summary, data["summary"] - assert_equal offense, data["offenses"].last - assert_equal [{ "path" => "app/jobs/a_job.rb", "answers" => {} }, - { "path" => "lib/ok.rb", "answers" => { "no_sleep" => 0.012, "documented" => 0.9 } }], data["files"] - assert_equal [{ "path" => "lib/blob.rb", "reason" => "binary file" }], data["skipped"] - assert_equal "Jev API error 500: boom", data["failures"].first["error"] - end - - def test_unknown_format - assert_raises(Lintus::Error) { Lintus::Formatter.for("xml") } - end - - private - - def render(format, report = @report) - io = StringIO.new - Lintus::Formatter.for(format).new(io).render(report) - io.string - end -end diff --git a/test/test_glob.rb b/test/test_glob.rb deleted file mode 100644 index f6a25ab..0000000 --- a/test/test_glob.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestGlob < Minitest::Test - def test_double_star_spans_directories - assert Lintus::Glob.match?("app/**/*.rb", "app/jobs/foo_job.rb") - assert Lintus::Glob.match?("app/**/*.rb", "app/foo.rb") - refute Lintus::Glob.match?("app/**/*.rb", "lib/foo.rb") - end - - def test_bare_directory_matches_everything_beneath_it - assert Lintus::Glob.match?("vendor", "vendor/gem/lib/x.rb") - assert Lintus::Glob.match?("vendor/", "vendor/x.rb") - assert Lintus::Glob.match?("./vendor", "vendor/x.rb") - refute Lintus::Glob.match?("vendor", "vendored/x.rb") - end - - def test_trailing_double_star_matches_nested_files - assert Lintus::Glob.match?("vendor/**", "vendor/a/b/c.rb") - assert Lintus::Glob.match?("vendor/**", "vendor/c.rb") - end - - def test_exact_file - assert Lintus::Glob.match?("Gemfile", "Gemfile") - refute Lintus::Glob.match?("Gemfile", "sub/Gemfile") - end - - def test_braces_and_dotfiles - assert Lintus::Glob.match?("**/*.{yml,yaml}", "config/a.yaml") - assert Lintus::Glob.match?("**/*.yml", ".github/workflows/ci.yml") - end - - def test_single_star_does_not_cross_directories - refute Lintus::Glob.match?("app/*.rb", "app/jobs/foo.rb") - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 48231e0..0000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -$LOAD_PATH.unshift File.expand_path("../lib", __dir__) -require "lintus" - -require "minitest/autorun" -require "webmock/minitest" -require "tmpdir" -require "stringio" - -Jev.api_key = "test-key" - -module LintusTestHelpers - API_URL = Jev::Client::API_URL - - RULES = { - "no_sleep" => { - "description" => "Jobs must not sleep.", - "question" => "Does this file call sleep?", - "paths" => ["app/jobs/**/*.rb"] - }, - "documented" => { - "description" => "Classes carry a comment.", - "question" => "Is every class documented?", - "offense_when" => false, - "threshold" => 0.7 - } - }.freeze - - private - - def build_config(overrides = {}) - Lintus::Config.new({ "paths" => ["**/*.rb"], "rules" => RULES }.merge(overrides)) - end - - def source(path, content = "class Foo; end\n") - Lintus::SourceFile.new(path) { content } - end - - def stub_jev(answers, status: 200, model: "jev-1.13.0") - body = status == 200 ? { model: model, answers: answers }.to_json : { error: "nope" }.to_json - stub_request(:post, API_URL).to_return(status: status, body: body) - end - - def noul(value) = { type: "noul", noul: value } - - # A throwaway git repository with an initial commit, yielded as the working directory. - def with_git_repo - Dir.mktmpdir("lintus") do |dir| - dir = File.realpath(dir) - Dir.chdir(dir) do - git "init", "-q" - git "config", "user.email", "test@example.com" - git "config", "user.name", "Lintus Test" - git "config", "commit.gpgsign", "false" - yield dir - end - end - end - - def git(*args) - out, status = Open3.capture2e("git", *args) - raise "git #{args.join(" ")} failed: #{out}" unless status.success? - - out - end - - def write(path, content = "class Foo; end\n") - FileUtils.mkdir_p(File.dirname(path)) - File.write(path, content) - end - - def commit_all(message = "commit") - git "add", "-A" - git "commit", "-qm", message - end -end - -require "fileutils" -require "open3" diff --git a/test/test_progress.rb b/test/test_progress.rb deleted file mode 100644 index 698f18e..0000000 --- a/test/test_progress.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestProgress < Minitest::Test - class Terminal < StringIO - def tty? = true - end - - def test_on_a_terminal_redraws_in_place_and_clears_when_done - io = Terminal.new - progress = Lintus::Progress.new(io, 12) - - progress.start - progress.update(1) - progress.update(12) - progress.finish - - assert_equal ["Checking 0/12 files", "Checking 1/12 files", "Checking 12/12 files", " " * 20], - io.string.split("\r").reject(&:empty?) - assert io.string.end_with?("\r") - end - - def test_off_a_terminal_prints_one_line_and_never_redraws - io = StringIO.new - progress = Lintus::Progress.new(io, 3) - - progress.start - progress.update(1) - progress.update(3) - progress.finish - - assert_equal "Checking 3 files...\n", io.string - end - - def test_says_nothing_when_there_is_nothing_to_check - io = Terminal.new - progress = Lintus::Progress.new(io, 0) - - progress.start - progress.finish - - assert_empty io.string - end -end diff --git a/test/test_report.rb b/test/test_report.rb deleted file mode 100644 index 889c564..0000000 --- a/test/test_report.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestReport < Minitest::Test - include LintusTestHelpers - - def setup - @config = build_config - @report = Lintus::Report.new - end - - def test_exit_status - assert_equal 0, @report.exit_status - - @report.record_checked("a.rb", [Lintus::Offense.new(path: "a.rb", rule: @config.rule(:documented), noul: 0.1)]) - assert_equal 1, @report.exit_status - - @report.record_failure("b.rb", Jev::APIError.new(500, "boom")) - assert_equal 2, @report.exit_status - end - - def test_sorted_offenses_are_stable_by_path_then_rule - @report.record_checked("x", [ - Lintus::Offense.new(path: "b.rb", rule: @config.rule(:no_sleep), noul: 0.9), - Lintus::Offense.new(path: "a.rb", rule: @config.rule(:no_sleep), noul: 0.9), - Lintus::Offense.new(path: "a.rb", rule: @config.rule(:documented), noul: 0.1) - ]) - - assert_equal([%w[a.rb documented], %w[a.rb no_sleep], %w[b.rb no_sleep]], - @report.sorted_offenses.map { |o| [o.path, o.rule.id] }) - end -end diff --git a/test/test_rule.rb b/test/test_rule.rb deleted file mode 100644 index 57af109..0000000 --- a/test/test_rule.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestRule < Minitest::Test - include LintusTestHelpers - - def test_defaults - rule = Lintus::Rule.new("no_sleep", { "question" => "Does this file call sleep?" }) - - assert_equal "no_sleep", rule.id - assert_equal "Does this file call sleep?", rule.description - assert_equal true, rule.offense_when - assert_nil rule.threshold - assert_nil rule.criteria - assert rule.applies_to?("anything/at/all.txt") - end - - def test_rule_paths_override_default_paths - rule = Lintus::Rule.new("r", { "question" => "q", "paths" => "app/**/*.rb" }, default_paths: ["**/*"]) - - assert rule.applies_to?("app/x.rb") - refute rule.applies_to?("lib/x.rb") - end - - def test_default_paths_apply_when_rule_has_none - rule = Lintus::Rule.new("r", { "question" => "q" }, default_paths: ["lib/**/*"]) - - assert rule.applies_to?("lib/x.rb") - refute rule.applies_to?("app/x.rb") - end - - def test_exclude_wins_over_paths - rule = Lintus::Rule.new("r", { "question" => "q", "paths" => ["**/*.rb"], "exclude" => ["**/*_test.rb"] }) - - assert rule.applies_to?("lib/x.rb") - refute rule.applies_to?("test/x_test.rb") - end - - def test_symbol_keys_are_accepted - rule = Lintus::Rule.new(:r, { question: "q", offense_when: false, criteria: { "true" => "yes", "false" => "no" } }) - - assert_equal false, rule.offense_when - assert_equal({ "true" => "yes", "false" => "no" }, rule.criteria) - end - - def test_add_to_query_forwards_criteria_and_threshold - rule = Lintus::Rule.new("r", - { "question" => "q?", "criteria" => { "true" => "t", "false" => "f" }, "threshold" => 0.8 }) - query = Jev::Query.new("state") - rule.add_to(query) - - question = query.questions[:r] - assert_equal({ type: :noul, instructions: "q?", criteria: { "true" => "t", "false" => "f" } }, question.to_h) - question.answer_with("noul" => 0.75) - refute question.result - end - - def test_offense_follows_offense_when - positive = Lintus::Rule.new("r", { "question" => "q" }) - negative = Lintus::Rule.new("r", { "question" => "q", "offense_when" => false }) - answer = Struct.new(:result).new(true) - - assert positive.offense?(answer) - refute negative.offense?(answer) - end - - def test_validation_errors - assert_config_error("invalid rule id") { Lintus::Rule.new("No-Sleep", { "question" => "q" }) } - assert_config_error("`question` is required") { Lintus::Rule.new("r", { "description" => "d" }) } - assert_config_error("expected a map") { Lintus::Rule.new("r", "just a string") } - assert_config_error("unknown key(s) severity") { Lintus::Rule.new("r", { "question" => "q", "severity" => "error" }) } - assert_config_error("`threshold` must be") { Lintus::Rule.new("r", { "question" => "q", "threshold" => 2 }) } - assert_config_error("`criteria` must be a map") { Lintus::Rule.new("r", { "question" => "q", "criteria" => "x" }) } - assert_config_error("`offense_when` must be") { Lintus::Rule.new("r", { "question" => "q", "offense_when" => "yes" }) } - end - - private - - def assert_config_error(message, &) - error = assert_raises(Lintus::ConfigError, &) - assert_includes error.message, message - end -end diff --git a/test/test_runner.rb b/test/test_runner.rb deleted file mode 100644 index 27a257e..0000000 --- a/test/test_runner.rb +++ /dev/null @@ -1,136 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class TestRunner < Minitest::Test - include LintusTestHelpers - - def setup - @config = build_config - @runner = Lintus::Runner.new(@config, jobs: 2, sleeper: ->(seconds) { (@slept ||= []) << seconds }) - end - - def test_plan_pairs_files_with_their_rules_and_drops_uncovered_files - plan = @runner.plan([source("app/jobs/a_job.rb"), source("lib/b.rb"), source("README.md")]) - - assert_equal([["app/jobs/a_job.rb", %w[no_sleep documented]], ["lib/b.rb", %w[documented]]], - plan.map { |file, rules| [file.path, rules.map(&:id)] }) - end - - def test_sends_one_query_per_file_with_every_applicable_rule - stub_jev({ "no_sleep" => noul(0.9), "documented" => noul(0.2) }) - - report = lint([source("app/jobs/a_job.rb", "sleep 1\n")]) - - assert_requested(:post, API_URL, times: 1) do |request| - body = JSON.parse(request.body) - assert_equal "File: app/jobs/a_job.rb\n\nsleep 1\n", body["state"] - assert_equal %w[no_sleep documented], body["questions"].keys - assert_equal({ "type" => "noul", "instructions" => "Does this file call sleep?" }, body["questions"]["no_sleep"]) - end - - assert_equal ["app/jobs/a_job.rb"], report.checked.map(&:path) - assert_equal({ "no_sleep" => 0.9, "documented" => 0.2 }, report.checked.first.answers) - assert_equal([["no_sleep", 0.9], ["documented", 0.2]], report.offenses.map { |o| [o.rule.id, o.noul] }) - end - - def test_thresholds_and_offense_when_are_honoured - stub_jev({ "no_sleep" => noul(0.4), "documented" => noul(0.75) }) - - report = lint([source("app/jobs/a_job.rb")]) - - assert_empty report.offenses - end - - def test_only_asks_the_rules_that_apply - stub_jev({ "documented" => noul(0.9) }) - - report = lint([source("lib/b.rb")]) - - assert_requested(:post, API_URL) { |request| JSON.parse(request.body)["questions"].keys == %w[documented] } - assert_empty report.offenses - end - - def test_skips_binary_and_oversized_files_without_calling_the_api - stub_jev({ "documented" => noul(0.1) }) - runner = Lintus::Runner.new(build_config("max_file_size" => 10)) - - report = lint([source("lib/bin.rb", "ab\0cd"), source("lib/big.rb", "x" * 11), source("lib/ok.rb", "x")], runner: runner) - - assert_requested(:post, API_URL, times: 1) { |request| JSON.parse(request.body)["state"].include?("lib/ok.rb") } - assert_equal ["lib/bin.rb", "lib/big.rb"], report.skipped.map(&:path) - assert_includes report.skipped.last.reason, "max_file_size" - assert_equal ["lib/ok.rb"], report.checked.map(&:path) - end - - def test_retries_rate_limits_with_backoff_then_succeeds - stub_request(:post, API_URL) - .to_return(status: 429, body: "slow down").times(2) - .then.to_return(status: 200, body: { model: "jev-1", answers: { "documented" => noul(0.9) } }.to_json) - - report = lint([source("lib/b.rb")]) - - assert_equal 2, @slept.size - assert @slept.first < @slept.last - assert_equal 1, report.checked.size - assert_empty report.failures - end - - def test_gives_up_after_retries_and_records_a_failure - stub_request(:post, API_URL).to_return(status: 529, body: "overloaded") - runner = Lintus::Runner.new(@config, retries: 1, sleeper: ->(_) {}) - - report = lint([source("lib/b.rb")], runner: runner) - - assert_equal 1, report.failures.size - assert_kind_of Jev::OverloadedError, report.failures.first.error - assert_requested :post, API_URL, times: 2 - end - - def test_other_api_errors_are_not_retried - stub_request(:post, API_URL).to_return(status: 401, body: "bad key") - - report = lint([source("lib/b.rb")]) - - assert_requested :post, API_URL, times: 1 - assert_kind_of Jev::AuthenticationError, report.failures.first.error - assert_equal 2, report.exit_status - end - - def test_runs_files_concurrently - threads = Queue.new - stub_request(:post, API_URL).to_return do - threads << Thread.current - sleep 0.02 # hold the request so the workers overlap - { status: 200, body: { model: "jev-1", answers: { "documented" => noul(0.9) } }.to_json } - end - - report = lint(Array.new(6) { |i| source("lib/f#{i}.rb") }, runner: Lintus::Runner.new(@config, jobs: 3)) - - assert_equal 6, report.checked.size - assert_operator Array.new(threads.size) { threads.pop }.uniq.size, :>, 1 - end - - def test_reports_progress_after_each_file - stub_jev({ "documented" => noul(0.1) }) - seen = [] - runner = Lintus::Runner.new(@config, jobs: 2) - - runner.run(runner.plan(Array.new(4) { |i| source("lib/f#{i}.rb") })) { |done| seen << done } - - assert_equal [1, 2, 3, 4], seen - end - - def test_unreadable_files_are_recorded_as_failures - file = Lintus::SourceFile.new("lib/gone.rb") { File.binread("/nonexistent/gone.rb") } - - report = lint([file]) - - assert_kind_of Lintus::ReadError, report.failures.first.error - assert_requested :post, API_URL, times: 0 - end - - private - - def lint(files, runner: @runner) = runner.run(runner.plan(files)) -end diff --git a/tests/fixtures/generate_glob_cases.rb b/tests/fixtures/generate_glob_cases.rb index 9824d6e..5ed66d1 100644 --- a/tests/fixtures/generate_glob_cases.rb +++ b/tests/fixtures/generate_glob_cases.rb @@ -2,10 +2,11 @@ # Writes glob_cases.tsv: whether the Ruby lintus matched each pattern against # each path, one "patternpath0|1" line per pair. The Rust matcher is -# tested against it (src/glob.rs). Run from the repository root while the Ruby -# implementation is still there: +# tested against it (src/glob.rs). The Ruby implementation was removed after +# 82e27d3, so regenerating needs a checkout of that commit: # -# ruby -Ilib tests/fixtures/generate_glob_cases.rb +# git worktree add /tmp/lintus-ruby 82e27d3 +# ruby -I/tmp/lintus-ruby/lib tests/fixtures/generate_glob_cases.rb require "lintus" PATTERNS = [ From de1dc1b5b4b4345db232ee90ba2a54e5089078df Mon Sep 17 00:00:00 2001 From: Vincent Rolea <3525369+virolea@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:54:32 +0200 Subject: [PATCH 9/9] Fix the auth tests on Windows, and make client errors actionable The tests built the expected credentials path with a / inside, where the binary rightly uses \ on Windows. CI now runs every test binary even when one fails. Lintus flagged crates/jev/src/client.rs in its own self-lint: an encoding failure surfaced a bare serde message, and a response that broke off was reported as the API being unreachable. Both now say what happened. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 2 +- crates/jev/src/client.rs | 7 +++++-- crates/jev/src/error.rs | 2 +- tests/auth.rs | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3a4a93..891dc16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,5 +35,5 @@ jobs: - name: Clippy run: cargo clippy --workspace --all-targets --locked -- -D warnings - name: Test - run: cargo test --workspace --locked + run: cargo test --workspace --locked --no-fail-fast diff --git a/crates/jev/src/client.rs b/crates/jev/src/client.rs index 7620a2c..3fcb4d2 100644 --- a/crates/jev/src/client.rs +++ b/crates/jev/src/client.rs @@ -61,7 +61,7 @@ impl Client { } let body = serde_json::to_vec(&Body { model: MODEL, state: query.state(), questions: query.question_map() }) - .map_err(|e| Error::InvalidQuestion(e.to_string()))?; + .map_err(|e| Error::InvalidQuestion(format!("could not encode the query as JSON: {e}")))?; let mut response = self .agent @@ -72,7 +72,10 @@ impl Client { .map_err(|e| Error::Transport(e.to_string()))?; let status = response.status().as_u16(); - let text = response.body_mut().read_to_string().map_err(|e| Error::Transport(e.to_string()))?; + let text = response + .body_mut() + .read_to_string() + .map_err(|e| Error::Transport(format!("the response broke off before its end ({e}); try again")))?; if !(200..300).contains(&status) { return Err(Error::Api(ApiError { status, body: text })); } diff --git a/crates/jev/src/error.rs b/crates/jev/src/error.rs index b375a63..6c18793 100644 --- a/crates/jev/src/error.rs +++ b/crates/jev/src/error.rs @@ -45,7 +45,7 @@ impl Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::MissingApiKey => write!(f, "Missing API key"), + Error::MissingApiKey => write!(f, "missing API key: pass the key of your Typesafe account to Client::new"), Error::Api(error) => error.fmt(f), Error::Transport(message) => write!(f, "could not reach the Jev API: {message}"), Error::InvalidResponse(message) => write!(f, "unexpected response from the Jev API: {message}"), diff --git a/tests/auth.rs b/tests/auth.rs index 45a085e..45934bb 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -17,7 +17,7 @@ fn project() -> Project { } fn credentials_file(project: &Project) -> std::path::PathBuf { - project.config_home().join("lintus/credentials.json") + project.config_home().join("lintus").join("credentials.json") } #[test]