~neon/activity-graph

d331856eb570da4af08a2c74871d3ca674e2b9ff — Jens Pitkanen 2 years ago 91b095c
Tweak subcommands and flags
5 files changed, 65 insertions(+), 119 deletions(-)

M README.md
M src/activity-graph.css
M src/main.rs
M src/render.rs
M src/server.rs
M README.md => README.md +5 -74
@@ 7,21 7,21 @@ This program has 3 general use-cases:
1. Printing out a nice visualization of your commits to stdout.

   ```
   activity-graph -i <dirs-with-your-repos> --stdout
   activity-graph stdout -i <dirs-with-your-repos>
   ```

2. Generating a html file (and, optionally, a css file instead of a
   `<style>`) to be looked at / served via a file server.

   ```
   activity-graph -i <dirs-with-your-repos> generate -o test.html [-c test.css]
   activity-graph generate -i <dirs-with-your-repos> -o test.html [-c test.css]
   ```

3. Serving the generated html and css straight from memory via
   [`hyper`][hyper]:

   ```
   activity-graph -i <dirs-with-your-repos> server --host 0.0.0.0:80
   activity-graph server -i <dirs-with-your-repos> --host 0.0.0.0:80
   ```

## Building


@@ 53,83 53,14 @@ but those versions are what I wrote this with.
  `--cache-lifetime` parameter.

## License

I recommend writing your own, it's a fun little project. But even
though I would not recommend using this code, you may use it under the
terms of the [GNU GPLv3 license][license].

## Usage

You could just use the `--help` flag, but here you go.

```
activity-graph 0.1.0
Jens Pitkanen <jens@neon.moe>
Generates a visualization of your commit activity in a set of git repositories.

USAGE:
    activity-graph [FLAGS] [OPTIONS] [SUBCOMMAND]

FLAGS:
    -h, --help       Prints help information
        --stdout     Prints a visualization into stdout
    -V, --version    Prints version information
    -v, --verbose    Prints verbose information

OPTIONS:
    -a, --author <author>                      Regex that matches the author(s) whose commits are being counted (if not
                                               set, all commits will be counted)
    -d, --depth <depth>                        How many subdirectories deep the program should search (if not set, there
                                               is no limit)
        --external-css <external-css>          A css file that will be pasted at the end of the css
        --external-footer <external-footer>    A html file that will be pasted at the end of the <body> element
        --external-head <external-head>        A html file that will be pasted in the <head> element
        --external-header <external-header>    A html file that will be pasted at the beginning of the <body> element
    -i, --input <input>...                     Path(s) to the directory (or directories) containing the repositories you
                                               want to include

SUBCOMMANDS:
    generate    Output the generated html into a file
    help        Prints this message or the help of the given subcommand(s)
    server      Run a server that serves the generated activity graph html
```

The `generate` command:

```
activity-graph-generate 0.1.0
Output the generated html into a file

USAGE:
    activity-graph generate [OPTIONS]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -c, --css <css>      The file that the stylesheet will be printed out to (if not set, it will be included in the
                         html inside a style-element)
    -o, --html <html>    The file that the resulting html will be printed out to [default: activity-graph.html]
```

The `server` command:

```
activity-graph-server 0.1.0
Run a server that serves the generated activity graph html

USAGE:
    activity-graph server [OPTIONS]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
        --cache-lifetime <cache-lifetime>    The minimum amount of seconds between regenerating the html and css
                                             [default: 1]
        --host <host>                        The address that the server is hosted on [default: 127.0.0.1:80]
```
Run `activity-graph --help` for the manual.

[hyper]: https://crates.io/crates/hyper "A fast HTTP 1/2 server written in Rust"
[license]: LICENSE.md "The GNU GPLv3 license text in Markdown."

M src/activity-graph.css => src/activity-graph.css +11 -11
@@ 60,8 60,8 @@ body {

@media (prefers-color-scheme: dark) {
    html {
	background-color: #000;
	color: #AAA;
        background-color: #000;
        color: #AAA;
    }

    .lvl0 { background-color: #1b1b1b; /* 265 / 0 / 10 */ }


@@ 79,23 79,23 @@ body {

@media (max-width: 58.89em) {
    body {
	width: 90vw;
	margin: auto;
        width: 90vw;
        margin: auto;
    }

    .blob-row {
	height: 1.698vw;
        height: 1.698vw;
    }

    .blob {
	width: 1.019vw;
	height: 1.019vw;
	margin: 0.17vw;
	padding: 0.17vw;
        width: 1.019vw;
        height: 1.019vw;
        margin: 0.17vw;
        padding: 0.17vw;
    }

    .blob:hover {
	padding: 0.15em;
	margin: 0.05em;
        padding: 0.15em;
        margin: 0.05em;
    }
}

M src/main.rs => src/main.rs +44 -26
@@ 50,16 50,6 @@ pub struct Year {
pub struct Args {
    #[structopt(subcommand)]
    command: Option<CommandArgs>,
    /// Prints verbose information
    #[structopt(short, long)]
    verbose: bool,
    /// Prints a visualization into stdout
    #[structopt(long)]
    stdout: bool,
    #[structopt(flatten)]
    gen: GenerationData,
    #[structopt(flatten)]
    ext: ExternalResources,
}

#[derive(StructOpt, Default, Clone)]


@@ 100,6 90,13 @@ pub struct ExternalResources {
enum CommandArgs {
    /// Output the generated html into a file
    Generate {
        /// Prints verbose information
        #[structopt(short, long)]
        verbose: bool,
        #[structopt(flatten)]
        gen: GenerationData,
        #[structopt(flatten)]
        ext: ExternalResources,
        /// The file that the resulting html will be printed out to
        #[structopt(short = "o", long, default_value = "activity-graph.html")]
        html: PathBuf,


@@ 109,9 106,25 @@ enum CommandArgs {
        css: Option<PathBuf>,
    },

    /// Prints a visualization into stdout
    Stdout {
        /// Prints verbose information
        #[structopt(short, long)]
        verbose: bool,
        #[structopt(flatten)]
        gen: GenerationData,
    },

    #[cfg(feature = "server")]
    /// Run a server that serves the generated activity graph html
    Server {
        /// Prints verbose information
        #[structopt(short, long)]
        verbose: bool,
        #[structopt(flatten)]
        gen: GenerationData,
        #[structopt(flatten)]
        ext: ExternalResources,
        /// The address that the server is hosted on
        #[structopt(long, default_value = "127.0.0.1:80")]
        host: SocketAddr,


@@ 125,13 138,18 @@ enum CommandArgs {
fn main() {
    let start_time = time::Instant::now();
    let args = Args::from_args();
    log::set_verbosity(args.verbose);

    let stdout_years;

    if let Some(command) = &args.command {
        match command {
            CommandArgs::Generate { html, css } => {
            CommandArgs::Generate {
                verbose,
                gen,
                ext,
                html,
                css,
            } => {
                log::set_verbosity(*verbose);

                let write_to_file = |path: &Path, s: String, name: &str| {
                    let mut writer = File::create(path).map(BufWriter::new);
                    match &mut writer {


@@ 152,34 170,34 @@ fn main() {
                    }
                };

                let years = generate_years(&args.gen);
                let years = generate_years(&gen);

                let output_html = render::html(&args.ext, &html, css.as_ref(), &years);
                let output_html = render::html(&ext, &html, css.as_ref(), &years);
                write_to_file(&html, output_html, "html");

                if let Some(css) = css {
                    let output_css = render::css(&args.ext);
                    let output_css = render::css(&ext);
                    write_to_file(&css, output_css, "css");
                }
            }

                stdout_years = years;
            CommandArgs::Stdout { verbose, gen } => {
                log::set_verbosity(*verbose);
                println!("{}", render::ascii(&generate_years(gen)));
            }

            #[cfg(feature = "server")]
            CommandArgs::Server {
                verbose,
                gen,
                ext,
                host,
                cache_lifetime,
            } => {
                server::run(&args, *host, *cache_lifetime);
                return;
                log::set_verbosity(*verbose);
                server::run(&gen, &ext, *host, *cache_lifetime);
            }
        }
    } else {
        stdout_years = generate_years(&args.gen);
    }

    if args.stdout {
        println!("{}", render::ascii(&stdout_years));
    }

    log::verbose_println(

M src/render.rs => src/render.rs +0 -3
@@ 235,9 235,6 @@ fn create_web_path(path: &Path) -> String {
            _ => None,
        })
        .fold(String::new(), |mut a, b| {
            if a.is_empty() {
                a += "/";
            }
            a += b;
            a
        })

M src/server.rs => src/server.rs +5 -5
@@ 10,7 10,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::RwLock;
use std::time::{Duration, Instant};

use crate::{generate_years, log, render, Args, ExternalResources, GenerationData};
use crate::{generate_years, log, render, ExternalResources, GenerationData};

lazy_static::lazy_static! {
    // These are set before the server is run, and only used in responses


@@ 28,17 28,17 @@ static CACHE_INITIALIZED: AtomicBool = AtomicBool::new(false);

static INDEX_PATHS: &[&str] = &["/", "/index.html", "/index.htm", ""];

pub fn run(args: &Args, host: SocketAddr, cache_lifetime: u64) {
pub fn run(gen: &GenerationData, ext: &ExternalResources, host: SocketAddr, cache_lifetime: u64) {
    log::verbose_println(&format!("starting server on {}...", host), true);

    if let (Ok(mut gen), Ok(mut ext), Ok(mut lifetime), Ok(mut last_cache)) = (
    if let (Ok(mut gen_), Ok(mut ext_), Ok(mut lifetime), Ok(mut last_cache)) = (
        GENERATION_DATA.write(),
        EXTERNAL_HTML.write(),
        CACHE_LIFETIME.write(),
        LAST_CACHE.write(),
    ) {
        *gen = args.gen.clone();
        *ext = args.ext.clone();
        *gen_ = gen.clone();
        *ext_ = ext.clone();
        *lifetime = Duration::from_secs(cache_lifetime);
        *last_cache = Instant::now() - Duration::from_secs(cache_lifetime * 2);
    } else {