diff --git a/Cargo.lock b/Cargo.lock index ec05e8a..a8bcd00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,7 @@ dependencies = [ "anyhow", "chrono", "duct", + "glob", "quantiles", "serde", "serde_json", @@ -93,6 +94,12 @@ dependencies = [ "shared_child", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "heck" version = "0.3.2" diff --git a/Cargo.toml b/Cargo.toml index cd245d9..b23e57e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,4 @@ serde_json = "1.0.62" chrono = "0.4.19" quantiles = "0.7.1" structopt = "0.3.21" +glob = "0.3.0" diff --git a/Dockerfile b/Dockerfile index ef8f113..a446d52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,3 +15,5 @@ RUN apt-get update \ COPY --from=builder /analyzer/target/release/codeprints-analyzer /usr/local/bin/codeprints-analyzer WORKDIR /repo ENTRYPOINT [ "codeprints-analyzer" ] +# The standard command parses the commits of a repository +CMD ["run"] diff --git a/README.md b/README.md index 38cf786..fe040dd 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ codeprints-analyzer You can set the start- and end-date of the output. ``` -docker run -v `pwd`:/repo codeprints/analyzer --after "2020-12-24" --before "2021-02-10" +docker run -v `pwd`:/repo codeprints/analyzer run --after "2020-12-24" --before "2021-02-10" ``` The syntax is exactly the same that `git` also uses. @@ -65,7 +65,7 @@ If you work in a bigger team, you might want to filter the contributions by author. Here is how: ``` -docker run -v `pwd`:/repo codeprints/analyzer --author "Matthias" --author "Octocat" +docker run -v `pwd`:/repo codeprints/analyzer run --author "Matthias" --author "Octocat" ``` To get a list of all author names, run `git shortlog --summary --numbered --email`. @@ -73,6 +73,19 @@ To get a list of all author names, run `git shortlog --summary --numbered --emai (You can also filter by committers. The difference is subtle, but in contrast to authors, these are the contributors who pushed/committed a patch to the repository.) +## Merging multiple codeprints output files + +Do you have multiple repositories that you want to analyze? +No problem! Just run the tool in every repository folder. +After that, copy the files into a single folder and run the following command: + +``` +docker run -v `pwd`:/repo codeprints/analyzer merge +``` + +This will merge all codeprints\_\*.json files in the `repo` directory into one file. +(It will accumulate all contribution counts for each day.) + ## More options To get an exhaustive list of options, run diff --git a/fixtures/mre_raw_2020_from_api.json b/fixtures/mre_raw_2020_from_api.json new file mode 100644 index 0000000..5838bf4 --- /dev/null +++ b/fixtures/mre_raw_2020_from_api.json @@ -0,0 +1,2210 @@ +{ + "contributions" : [ + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-12-31", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-12-30", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-12-29", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-12-28", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-12-27", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 27, + "date" : "2020-12-26", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-12-25", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-12-24", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-12-23", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-12-22", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-12-21", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-12-20", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-12-19", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-12-18", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-12-17", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-12-16", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 31, + "date" : "2020-12-15", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 52, + "date" : "2020-12-14", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-12-13", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-12-12", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-12-11", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-12-10", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-12-09", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-12-08", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-12-07", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-12-06", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-12-05", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 28, + "date" : "2020-12-04", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 20, + "date" : "2020-12-03", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-12-02", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-12-01", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-11-30", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-11-29", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-11-28", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 16, + "date" : "2020-11-27", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 16, + "date" : "2020-11-26", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 29, + "date" : "2020-11-25", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-11-24", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-11-23", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-11-22", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-11-21", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-11-20", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-11-19", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-11-18", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 17, + "date" : "2020-11-17", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 9, + "date" : "2020-11-16", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-11-15", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-11-14", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-11-13", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-11-12", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 16, + "date" : "2020-11-11", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-11-10", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-11-09", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-11-08", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-11-07", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-11-06", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-11-05", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-11-04", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-11-03", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-11-02", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-11-01", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-10-31", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-10-30", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-10-29", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-10-28", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-10-27", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-10-26", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-10-25", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-10-24", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-10-23", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-10-22", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-10-21", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 9, + "date" : "2020-10-20", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-10-19", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-10-18", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-10-17", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-10-16", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-10-15", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-10-14", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-10-13", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-10-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-10-11", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-10-10", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 15, + "date" : "2020-10-09", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-10-08", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-10-07", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-10-06", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-10-05", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-10-04", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-10-03", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 15, + "date" : "2020-10-02", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 18, + "date" : "2020-10-01", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-09-30", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 21, + "date" : "2020-09-29", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 9, + "date" : "2020-09-28", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 15, + "date" : "2020-09-27", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-09-26", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-09-25", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 22, + "date" : "2020-09-24", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 20, + "date" : "2020-09-23", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 19, + "date" : "2020-09-22", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 25, + "date" : "2020-09-21", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 15, + "date" : "2020-09-20", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-09-19", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 33, + "date" : "2020-09-18", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 16, + "date" : "2020-09-17", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-09-16", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-09-15", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-09-14", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-09-13", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-09-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 22, + "date" : "2020-09-11", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 14, + "date" : "2020-09-10", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 14, + "date" : "2020-09-09", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 15, + "date" : "2020-09-08", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 18, + "date" : "2020-09-07", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-09-06", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-09-05", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-09-04", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-09-03", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-09-02", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-09-01", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 9, + "date" : "2020-08-31", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-08-30", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-08-29", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-08-28", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-08-27", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 15, + "date" : "2020-08-26", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-08-25", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-08-24", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-08-23", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-08-22", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 17, + "date" : "2020-08-21", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-08-20", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-08-19", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-08-18", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-08-17", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-08-16", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-08-15", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-08-14", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 15, + "date" : "2020-08-13", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-08-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-08-11", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-08-10", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-08-09", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-08-08", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-08-07", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-08-06", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-08-05", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-08-04", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-08-03", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-08-02", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-08-01", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-07-31", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-07-30", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-29", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-07-28", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-07-27", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-26", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-25", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-24", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-07-23", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-07-22", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-07-21", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-20", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-07-19", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-18", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 26, + "date" : "2020-07-17", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 15, + "date" : "2020-07-16", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 23, + "date" : "2020-07-15", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-07-14", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-07-13", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-07-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-11", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-07-10", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-07-09", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-07-08", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-07-07", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 9, + "date" : "2020-07-06", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-05", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-07-04", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-07-03", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-07-02", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-07-01", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-06-30", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 19, + "date" : "2020-06-29", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-06-28", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-06-27", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 24, + "date" : "2020-06-26", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-06-25", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-06-24", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 32, + "date" : "2020-06-23", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 17, + "date" : "2020-06-22", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-06-21", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-06-20", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-06-19", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-06-18", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-06-17", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 10, + "date" : "2020-06-16", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-06-15", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-06-14", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-06-13", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-06-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-06-11", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-06-10", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-06-09", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-06-08", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 9, + "date" : "2020-06-07", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-06-06", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-06-05", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-06-04", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-06-03", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-06-02", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-06-01", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-31", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-30", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-05-29", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 30, + "date" : "2020-05-28", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-05-27", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 14, + "date" : "2020-05-26", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-05-25", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-05-24", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-23", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-05-22", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 16, + "date" : "2020-05-21", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 22, + "date" : "2020-05-20", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 17, + "date" : "2020-05-19", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-05-18", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-17", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-16", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-15", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-14", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-05-13", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-05-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-05-11", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-10", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-09", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-05-08", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-05-07", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-05-06", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-05", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-05-04", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-05-03", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-02", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-05-01", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-04-30", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-04-29", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-04-28", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-04-27", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-04-26", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-04-25", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 19, + "date" : "2020-04-24", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-04-23", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 26, + "date" : "2020-04-22", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-04-21", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-04-20", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-04-19", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-04-18", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-04-17", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-04-16", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-04-15", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-04-14", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-04-13", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-04-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-04-11", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-04-10", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-04-09", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-04-08", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 25, + "date" : "2020-04-07", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 9, + "date" : "2020-04-06", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-04-05", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-04-04", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-04-03", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-04-02", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-04-01", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-03-31", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-03-30", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-03-29", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 16, + "date" : "2020-03-28", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-03-27", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-03-26", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-03-25", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-03-24", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-03-23", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-03-22", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-03-21", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 14, + "date" : "2020-03-20", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-03-19", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-03-18", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-03-17", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 20, + "date" : "2020-03-16", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-03-15", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-03-14", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-03-13", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-03-12", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 7, + "date" : "2020-03-11", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-03-10", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-03-09", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-03-08", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-03-07", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-03-06", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-03-05", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-03-04", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-03-03", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-03-02", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-03-01", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-02-29", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-02-28", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 21, + "date" : "2020-02-27", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 24, + "date" : "2020-02-26", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-02-25", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-02-24", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-02-23", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-02-22", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-02-21", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-02-20", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-02-19", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-02-18", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-02-17", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-02-16", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-02-15", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-02-14", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 21, + "date" : "2020-02-13", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-02-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-02-11", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-02-10", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-02-09", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-02-08", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-02-07", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-02-06", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-02-05", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 18, + "date" : "2020-02-04", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 18, + "date" : "2020-02-03", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-02-02", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 31, + "date" : "2020-02-01", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 14, + "date" : "2020-01-31", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-01-30", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 6, + "date" : "2020-01-29", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 11, + "date" : "2020-01-28", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l3-bg)", + "count" : 23, + "date" : "2020-01-27", + "intensity" : 3 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 16, + "date" : "2020-01-26", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-25", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-24", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-23", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-01-22", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-01-21", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-01-20", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 12, + "date" : "2020-01-19", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-18", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 8, + "date" : "2020-01-17", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 9, + "date" : "2020-01-16", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l4-bg)", + "count" : 25, + "date" : "2020-01-15", + "intensity" : 4 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 16, + "date" : "2020-01-14", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 4, + "date" : "2020-01-13", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-01-12", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-11", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-01-10", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 1, + "date" : "2020-01-09", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 3, + "date" : "2020-01-08", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 5, + "date" : "2020-01-07", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-l1-bg)", + "count" : 2, + "date" : "2020-01-06", + "intensity" : 1 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-05", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-04", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-03", + "intensity" : 0 + }, + { + "color" : "var(--color-calendar-graph-day-l2-bg)", + "count" : 13, + "date" : "2020-01-02", + "intensity" : 2 + }, + { + "color" : "var(--color-calendar-graph-day-bg)", + "count" : 0, + "date" : "2020-01-01", + "intensity" : 0 + } + ], + "years" : [ + { + "range" : { + "end" : "2020-12-31", + "start" : "2020-01-01" + }, + "total" : 2602, + "year" : "2020" + } + ] +} diff --git a/src/bin/analyzer/main.rs b/src/bin/analyzer/main.rs index c669a54..eeb1f5a 100644 --- a/src/bin/analyzer/main.rs +++ b/src/bin/analyzer/main.rs @@ -10,27 +10,81 @@ )] use anyhow::{Context, Result}; -use codeprints_analyzer::count_commits; +use codeprints_analyzer::git; +use codeprints_analyzer::Merger; use codeprints_analyzer::Parser; +use codeprints_analyzer::Timeline; +use glob::glob; use std::fs; +use std::time::SystemTime; use structopt::StructOpt; mod options; -use options::Opt; +use options::Command; -const OUTPUT_FILE: &str = "codeprints.json"; - -fn main() -> Result<()> { - let opt = Opt::from_args(); +fn timestamp() -> Result { + Ok(SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_secs()) +} - print!("Analyzing commits in current repository..."); - let input = count_commits(opt.before, opt.after, opt.author, opt.committer) - .context("Cannot read project history. Make sure there is no typo in the command")?; - let mut parser = Parser::new(input); - let timeline = parser.parse()?; +fn write(timeline: &Timeline, output_file: &str) -> Result<()> { let output = serde_json::to_string_pretty(&timeline)?; - fs::write(OUTPUT_FILE, output)?; + fs::write(output_file, output)?; println!("done!"); - println!("Output file: {}", OUTPUT_FILE); + println!("Output file: {}", output_file); + Ok(()) +} + +fn main() -> Result<()> { + let opt = Command::from_args(); + + match opt { + Command::Run { + before, + after, + author, + committer, + } => { + print!("Analyzing commits in current repository..."); + let input = git::count_commits(&before, &after, author, committer).context( + "Cannot read project history. Make sure there is no typo in the command", + )?; + let mut parser = Parser::new(input); + if let Some(before) = before { + parser.set_before(before)?; + } + if let Some(after) = after { + parser.set_after(after)?; + } + let timeline = parser.parse()?; + + let sha = git::sha()?; + write( + &timeline, + &format!("codeprints_{}_{}.json", sha, timestamp()?), + )?; + } + Command::Merge {} => { + // Find all `codeprints*.json` files in the current directory + // using glob. + let mut merger = Merger::new(); + for entry in glob("codeprints*.json")? { + match entry { + Ok(path) => { + println!("Merging {}", path.display()); + let input = fs::read_to_string(path)?; + let timeline: Timeline = serde_json::from_str(&input)?; + merger.merge_timeline(&timeline)?; + } + Err(e) => println!("{:?}", e), + } + } + write( + &merger.timeline()?, + &format!("merged_codeprints_{}.json", timestamp()?), + )?; + } + }; Ok(()) } diff --git a/src/bin/analyzer/options.rs b/src/bin/analyzer/options.rs index 0605b3c..8ebed26 100644 --- a/src/bin/analyzer/options.rs +++ b/src/bin/analyzer/options.rs @@ -2,24 +2,29 @@ use structopt::StructOpt; #[derive(StructOpt, Debug)] #[structopt(name = "codeprints-analyzer")] -pub struct Opt { - /// Limit the commits output to ones with author header lines - /// that match the specified pattern. - /// This is passed verbatim to git. See `git log --help` for more info. - #[structopt(short, long)] - pub author: Vec, +pub enum Command { + /// Run the analyzer and generate json summary of contributions + Run { + /// Limit the commits output to ones with author header lines + /// that match the specified pattern. + /// This is passed verbatim to git. See `git log --help` for more info. + #[structopt(short, long)] + author: Vec, - /// Limit the commits output to ones with committer header lines - /// that match the specified pattern. - /// This is passed verbatim to git. See `git log --help` for more info. - #[structopt(short, long)] - pub committer: Vec, + /// Limit the commits output to ones with committer header lines + /// that match the specified pattern. + /// This is passed verbatim to git. See `git log --help` for more info. + #[structopt(short, long)] + committer: Vec, - // Show commits older than a specific date. - #[structopt(alias = "until", long)] - pub before: Option, + // Show commits older than a specific date. + #[structopt(alias = "until", long)] + before: Option, - // Show commits more recent than a specific date. - #[structopt(alias = "since", long)] - pub after: Option, + // Show commits more recent than a specific date. + #[structopt(alias = "since", long)] + after: Option, + }, + /// Merge all codeprints*.json to one file named merged_codeprints_.json + Merge {}, } diff --git a/src/git.rs b/src/git.rs index 6a7f92c..d02070f 100644 --- a/src/git.rs +++ b/src/git.rs @@ -1,10 +1,11 @@ -use anyhow::Result; +use anyhow::{Context, Result}; +use chrono::NaiveDate; use duct::cmd; /// Get the count of commits for each day from the git logs pub fn count_commits( - before: Option, - after: Option, + before: &Option, + after: &Option, authors: Vec, committers: Vec, ) -> Result { @@ -28,3 +29,19 @@ pub fn count_commits( let commits = cmd("git", &args).read()?; Ok(commits) } + +/// Parse a date from the git log +pub fn parse_date(line: &str) -> Result> { + if line.trim().is_empty() { + // Empty lines are allowed, but skipped + return Ok(None); + } + let date: NaiveDate = line.parse().context(format!("Invalid date: {}", line))?; + Ok(Some(date)) +} + +/// Get the current git commit sha +pub fn sha() -> Result { + let sha = cmd("git", &["rev-parse", "--short", "HEAD"]).read()?; + Ok(sha) +} diff --git a/src/lib.rs b/src/lib.rs index c1f346c..bf65370 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,14 @@ unused_qualifications )] -mod git; +/// Git helper functions +pub mod git; + +mod merge; mod parser; mod quartiles; mod types; -pub use crate::git::count_commits; +pub use crate::merge::Merger; pub use crate::parser::Parser; +pub use crate::types::Timeline; diff --git a/src/merge.rs b/src/merge.rs new file mode 100644 index 0000000..9580ec2 --- /dev/null +++ b/src/merge.rs @@ -0,0 +1,180 @@ +use std::convert::TryFrom; + +use crate::{git, parser::ParseState, types::Timeline}; +use anyhow::Result; + +/// Merger merges multiple timelines into one +#[derive(Debug, Default)] +pub struct Merger { + state: ParseState, +} + +impl Merger { + /// Create a new merger with a clean state (no timeline merged) + pub fn new() -> Self { + let state = ParseState::default(); + Merger { state } + } + + /// Merge a single timeline + pub fn merge_timeline(&mut self, timeline: &Timeline) -> Result<()> { + // TODO: make sure that all days are filled with 0 between start and end + for contribution in &timeline.contributions { + let date = contribution.date.clone(); + let date = git::parse_date(&date)?; + let count = contribution.count; + + if let Some(date) = date { + self.state.update_years(date, count); + self.state.update_days(date, count); + } + } + Ok(()) + } + + /// Merge multiple timelines together into one + /// This is helpful when analyzing multiple repositories and trying to combine + /// the individual results. + pub fn merge(&mut self, timelines: &[Timeline]) -> Result { + for timeline in timelines { + self.merge_timeline(timeline)? + } + Timeline::try_from(&self.state) + } + + /// Return the merged timeline of all inputs + pub fn timeline(&self) -> Result { + Timeline::try_from(&self.state) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::types::{Contribution, Range, Year}; + + #[test] + fn test_merge_none() { + let mut merger = Merger::new(); + assert_eq!(merger.merge(&[]).unwrap(), Timeline::default()); + } + + #[test] + fn test_merge_one() { + let mut timeline = Timeline::default(); + + let year = "2020".into(); + let total = 1234; + let range = Range { + start: "2020-01-01".into(), + end: "2020-01-02".into(), + }; + + let year1 = Year { year, total, range }; + let years = vec![year1]; + timeline.years = years; + + let contributions = vec![ + Contribution { + date: "2020-01-01".into(), + count: 1000, + color: "".into(), + intensity: 4, + }, + Contribution { + date: "2020-01-02".into(), + count: 234, + color: "".into(), + intensity: 4, + }, + ]; + + timeline.contributions = contributions; + + let mut merger = Merger::new(); + let merged = merger.merge(&[timeline]).unwrap(); + assert_eq!(merged.years.len(), 1); + let year = &merged.years[0]; + assert_eq!(year.year, "2020"); + assert_eq!(year.total, 1234); + assert_eq!(year.range.start, "2020-01-01"); + assert_eq!(year.range.end, "2020-01-02"); + } + + #[test] + fn test_merge_multiple() { + let mut timeline1 = Timeline::default(); + let mut timeline2 = Timeline::default(); + + let range1 = Range { + start: "2020-01-01".into(), + end: "2020-01-02".into(), + }; + let range2 = Range { + start: "2020-01-01".into(), + end: "2020-01-03".into(), + }; + + let year1 = Year { + year: "2020".into(), + total: 1234, + range: range1, + }; + timeline1.years = vec![year1]; + + let year2 = Year { + year: "2020".into(), + total: 6, + range: range2, + }; + timeline2.years = vec![year2]; + + let contributions1 = vec![ + Contribution { + date: "2020-01-01".into(), + count: 1000, + color: "".into(), + intensity: 4, + }, + Contribution { + date: "2020-01-02".into(), + count: 234, + color: "".into(), + intensity: 4, + }, + ]; + + let contributions2 = vec![ + Contribution { + date: "2020-01-01".into(), + count: 0, + color: "".into(), + intensity: 0, + }, + Contribution { + date: "2020-01-02".into(), + count: 5, + color: "".into(), + intensity: 4, + }, + Contribution { + date: "2020-01-03".into(), + count: 1, + color: "".into(), + intensity: 1, + }, + ]; + + timeline1.contributions = contributions1; + timeline2.contributions = contributions2; + + let mut merger = Merger::new(); + let merged = merger.merge(&[timeline1, timeline2]).unwrap(); + assert_eq!(merged.years.len(), 1); + let year = &merged.years[0]; + assert_eq!(year.year, "2020"); + assert_eq!(year.total, 1240); + assert_eq!(year.range.start, "2020-01-01"); + assert_eq!(year.range.end, "2020-01-03"); + } +} diff --git a/src/parser.rs b/src/parser.rs index 2658ce2..f01b37e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,149 +1,132 @@ -use crate::quartiles::quartiles; use crate::types::{Contribution, Contributions, Range, Timeline, Year}; +use crate::{git, quartiles::quartiles}; use anyhow::{Context, Result}; use chrono::prelude::*; -use std::collections::HashMap; +use std::{ + cmp::{max, min}, + collections::HashMap, + convert::TryFrom, +}; -/// A parser that converts git log output -/// into the JSON format understood by the -/// API of codeprints.dev. -#[derive(Debug)] -pub struct Parser { - input: String, +/// The internal state of the parser +#[derive(Debug, Default)] +pub struct ParseState { years_map: HashMap, days: HashMap, + before: Option, + after: Option, } -impl Parser { - /// Create a new parser that analyzes the given input - pub fn new(input: String) -> Self { - let years_map = HashMap::new(); - let days = HashMap::new(); +impl ParseState { + /// Add a single day to the map of years + pub fn update_years(&mut self, date: NaiveDate, contributions_to_add: usize) { + let y = date.year(); + let date_str = date.format("%Y-%m-%d").to_string(); - Parser { - input, - years_map, - days, - } - } + let mut year = self.years_map.entry(y).or_insert(Year { + year: y.to_string(), + total: 0, + // range: Range::default, + // Always show full year + range: Range { + start: date_str.clone(), + end: date_str.clone(), + }, + }); - // Each cell in the timeline is shaded with one of 5 possible colors. These - // colors correspond to the quartiles of the normal distribution over the range - // [0, max(v)] where v is the sum of issues opened, pull requests proposed and - // commits authored per day. - // https://bd808.com/blog/2013/04/17/hacking-github-contributions-calendar/ - // https://github.community/t/the-color-coding-of-contribution-graph-is-showing-wrong-information/18572 - fn get_intensity(quartiles: &[usize], commits: usize) -> usize { - for (index, quartile) in quartiles.iter().enumerate() { - if commits < *quartile { - return index - 1; - } - } - quartiles.len() - 1 - } + year.range.start = min(year.range.start.clone(), date_str.clone()); + year.range.end = max(year.range.end.clone(), date_str); - fn map_color(intensity: usize) -> String { - match intensity { - 1 => "var(--color-calendar-graph-day-L1-bg)", - 2 => "var(--color-calendar-graph-day-L2-bg)", - 3 => "var(--color-calendar-graph-day-L3-bg)", - 4 => "var(--color-calendar-graph-day-L4-bg)", - _ => "var(--color-calendar-graph-day-bg)", - } - .to_string() + year.total += contributions_to_add; } - /// Backfill missing days with zero commits - fn backfill(year: i32, days: &mut HashMap) { - for d in NaiveDate::from_ymd(year, 1, 1).iter_days() { - if d.year() != year { - break; - } - days.entry(d).or_insert(0); - } + /// Add a single day to the map of days + pub fn update_days(&mut self, date: NaiveDate, contributions_to_add: usize) { + *self.days.entry(date).or_insert(0) += contributions_to_add; } +} - fn create_contributions( - &self, - days: &HashMap, - quartiles: &[usize], - ) -> Vec { - let mut contributions = Vec::new(); - for (date, commits) in days { - let intensity = Self::get_intensity(&quartiles, *commits); - let color = Self::map_color(intensity); - - contributions.push(Contribution { - date: date.format("%Y-%m-%d").to_string(), - count: *commits, - color, - intensity, - }); +/// Backfill missing days with zero commits +fn backfill(year: i32, days: &mut HashMap) -> Result<()> { + let last_day = days + .keys() + .max_by_key(|key| *key) + .cloned() + .context("cannot get last day for backfilling")?; + for d in NaiveDate::from_ymd(year, 1, 1).iter_days() { + if d > last_day { + break; } - contributions + days.entry(d).or_insert(0); } + Ok(()) +} - fn parse_date(&self, line: &str) -> Result> { - if line.trim().is_empty() { - // Empty lines are allowed, but skipped - return Ok(None); +// Each cell in the timeline is shaded with one of 5 possible colors. These +// colors correspond to the quartiles of the normal distribution over the range +// [0, max(v)] where v is the sum of issues opened, pull requests proposed and +// commits authored per day. +// https://bd808.com/blog/2013/04/17/hacking-github-contributions-calendar/ +// https://github.community/t/the-color-coding-of-contribution-graph-is-showing-wrong-information/18572 +fn get_intensity(quartiles: &[usize], commits: usize) -> usize { + for (index, quartile) in quartiles.iter().enumerate() { + if commits < *quartile { + return index - 1; } - let date: NaiveDate = line.parse().context(format!("Invalid date {}", line))?; - Ok(Some(date)) } + quartiles.len() - 1 +} - /// Add a single day to the map of years - fn update_years(&mut self, date: NaiveDate) { - let y = date.year(); - let mut year = self.years_map.entry(y).or_insert(Year { - year: y.to_string(), - total: 0, - range: Range::default(), - }); - year.total += 1; - - let date = date.format("%Y-%m-%d").to_string(); - if year.range.start.is_empty() || date < year.range.start { - year.range.start = date.clone(); - } - if year.range.end.is_empty() || date > year.range.end { - year.range.end = date - } +fn map_color(intensity: usize) -> String { + match intensity { + 1 => "var(--color-calendar-graph-day-l1-bg)", + 2 => "var(--color-calendar-graph-day-l2-bg)", + 3 => "var(--color-calendar-graph-day-l3-bg)", + 4 => "var(--color-calendar-graph-day-l4-bg)", + _ => "var(--color-calendar-graph-day-bg)", } + .to_string() +} - /// Add a single day to the map of days - fn update_days(&mut self, date: NaiveDate) { - *self.days.entry(date).or_insert(0) += 1; - } +fn create_contributions( + days: &HashMap, + quartiles: &[usize], +) -> Vec { + let mut contributions = Vec::new(); + for (date, commits) in days { + let intensity = get_intensity(&quartiles, *commits); + let color = map_color(intensity); - /// Execute the parsing step - pub fn parse(&mut self) -> Result { - let input = self.input.clone(); - for line in input.lines() { - let day = self.parse_date(&line)?; - if let Some(d) = day { - self.update_days(d); - self.update_years(d); - } - } + contributions.push(Contribution { + date: date.format("%Y-%m-%d").to_string(), + count: *commits, + color, + intensity, + }); + } + contributions +} - let mut years: Vec = self.years_map.values().cloned().collect(); +impl TryFrom<&ParseState> for Timeline { + type Error = anyhow::Error; + fn try_from(state: &ParseState) -> Result { + let mut years: Vec = state.years_map.values().cloned().collect(); years.sort(); years.reverse(); let mut contributions = Contributions::new(); for year in &years { - let mut year_contribs: HashMap = self + let mut year_contribs: HashMap = state .days .clone() .into_iter() .filter(|(date, _commits)| date.year().to_string() == year.year) .collect(); - Self::backfill(year.year.parse::()?, &mut year_contribs); + backfill(year.year.parse::()?, &mut year_contribs)?; let commits: Vec = year_contribs.values().cloned().collect(); let quartiles = quartiles(&commits)?; - let mut contribs = self.create_contributions(&year_contribs, &quartiles); + let mut contribs = create_contributions(&year_contribs, &quartiles); contributions.append(&mut contribs); } contributions.sort(); @@ -155,8 +138,78 @@ impl Parser { } } +/// A parser that converts git log output +/// into the JSON format understood by the +/// API of codeprints.dev. +#[derive(Debug, Default)] +pub struct Parser { + input: String, + state: ParseState, +} + +impl Parser { + /// Create a new parser that analyzes the given input + pub fn new(input: String) -> Self { + Parser { + input, + ..Default::default() + } + } + + /// Set the minimum date of the timeline. Commits older than the given date + /// will not be counted and won't be in the final output + pub fn set_before(&mut self, before: String) -> Result<&mut Self> { + let before = git::parse_date(&before)?; + if let Some(before) = before { + self.state.before = Some(before); + } + Ok(self) + } + + /// Set the maximum date of the timeline. Commits newer than the given date + /// will not be counted and won't be in the final output + pub fn set_after(&mut self, after: String) -> Result<&mut Self> { + let after = git::parse_date(&after)?; + if let Some(after) = after { + self.state.after = Some(after); + } + Ok(self) + } + + fn out_of_range(&self, day: NaiveDate) -> bool { + if let Some(before) = self.state.before { + if day >= before { + return true; + } + } + if let Some(after) = self.state.after { + if day <= after { + return true; + } + } + false + } + + /// Execute the parsing step + pub fn parse(&mut self) -> Result { + let input = self.input.clone(); + for line in input.lines() { + let day = git::parse_date(&line)?; + if let Some(d) = day { + if self.out_of_range(d) { + continue; + } + self.state.update_days(d, 1); + self.state.update_years(d, 1); + } + } + + Timeline::try_from(&self.state) + } +} + #[cfg(test)] -mod test_super { +mod test { use super::*; @@ -185,15 +238,43 @@ mod test_super { assert_eq!(parser.parse().unwrap().years, expected); } + #[test] + fn test_parse_years_range() { + let input = r###" + 2020-04-15 + 2020-04-15 + 2020-04-16 + 2020-04-17 + 2020-04-17 + 2020-04-17 + 2020-04-17 + 2020-04-18 + "###; + + let range = Range { + start: "2020-04-16".to_string(), + end: "2020-04-17".to_string(), + }; + let expected = vec![Year { + year: "2020".to_string(), + total: 5, + range, + }]; + let mut parser = Parser::new(input.to_string()); + parser.set_before("2020-04-18".into()).unwrap(); + parser.set_after("2020-04-15".into()).unwrap(); + assert_eq!(parser.parse().unwrap().years, expected); + } + #[test] fn test_intensities() { let quartiles = [0, 1, 11, 22, 32]; - assert_eq!(0, Parser::get_intensity(&quartiles, 0)); - assert_eq!(1, Parser::get_intensity(&quartiles, 1)); - assert_eq!(1, Parser::get_intensity(&quartiles, 10)); - assert_eq!(2, Parser::get_intensity(&quartiles, 18)); - assert_eq!(3, Parser::get_intensity(&quartiles, 22)); - assert_eq!(4, Parser::get_intensity(&quartiles, 32)); - assert_eq!(4, Parser::get_intensity(&quartiles, 100)); + assert_eq!(0, get_intensity(&quartiles, 0)); + assert_eq!(1, get_intensity(&quartiles, 1)); + assert_eq!(1, get_intensity(&quartiles, 10)); + assert_eq!(2, get_intensity(&quartiles, 18)); + assert_eq!(3, get_intensity(&quartiles, 22)); + assert_eq!(4, get_intensity(&quartiles, 32)); + assert_eq!(4, get_intensity(&quartiles, 100)); } } diff --git a/src/types.rs b/src/types.rs index 70e3df2..9e3a4e8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,7 +7,7 @@ pub struct Day { pub date: NaiveDate, } -#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Year { pub year: String, @@ -18,10 +18,13 @@ pub struct Year { pub type Years = Vec; pub type Contributions = Vec; +/// A timeline represents a codeprints.json file's contents #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Timeline { + /// Years belonging to the timeline pub years: Years, + /// Contributions belonging to the timeline pub contributions: Contributions, } diff --git a/tests/test_merger.rs b/tests/test_merger.rs new file mode 100644 index 0000000..12c2c02 --- /dev/null +++ b/tests/test_merger.rs @@ -0,0 +1,30 @@ +use codeprints_analyzer::Merger; +use codeprints_analyzer::Timeline; +use std::fs; + +#[test] +fn test_intensity() { + let input = fs::read_to_string("fixtures/mre_raw_2020_from_api.json").unwrap(); + let timeline: Timeline = serde_json::from_str(&input).unwrap(); + let mut merger = Merger::new(); + merger.merge_timeline(&timeline).unwrap(); + + let new_timeline = merger.timeline().unwrap(); + + for year in new_timeline.years { + let orig_year = timeline.years.iter().find(|y| y.year == year.year).unwrap(); + assert_eq!(year.total, orig_year.total); + } + + for actual in new_timeline.contributions { + let expected = timeline + .contributions + .iter() + .find(|c| c.date == actual.date) + .unwrap(); + + assert_eq!(actual.intensity, expected.intensity); + assert_eq!(actual.count, expected.count); + assert_eq!(actual.color, expected.color); + } +}