1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use std::fs::OpenOptions;
use std::io::{ErrorKind, Write};
use std::path::Path;

use crate::cli::init::InitCommandOptions;
use crate::error::{AddContext, GRError};
use crate::Result;

const CONFIG_TEMPLATE: &str = r#"
# Fill in the <VALUE> below with your own values
# and tweak accordingly.

<DOMAIN>.api_token=<VALUE>
<DOMAIN>.cache_location="~/.cache/gitar"
<DOMAIN>.preferred_assignee_username=<VALUE>
<DOMAIN>.merge_request_description_signature=""

## Cache expiration configuration

# Expire read merge requests in 5 minutes
<DOMAIN>.cache_api_merge_request_expiration=5m
# Expire read project metadata, members of a project in 5 days
<DOMAIN>.cache_api_project_expiration=5d
# Pipelines are read often, change often, so expire soon.
<DOMAIN>.cache_api_pipeline_expiration=30s
# Container registry operations including listing image tags and repos
<DOMAIN>.cache_api_container_registry_expiration=1h
# Expire read releases in 1 day
<DOMAIN>.cache_api_release_expiration=1d
# Expire single page calls in 1 day. Ex. Trending repositories in github.com
<DOMAIN>.cache_api_single_page_expiration=1d

## Max pages configuration

# Get up to 10 pages of merge requests when listing
<DOMAIN>.max_pages_api_merge_request=10
# Get up to 5 pages of project metadata, members of a project when listing
<DOMAIN>.max_pages_api_project=5
# Get up to 10 pages of pipelines when listing
<DOMAIN>.max_pages_api_pipeline=10
# Get up to 10 pages of container registry repositories when listing
<DOMAIN>.max_pages_api_container_registry=10
# Get up to 10 pages of releases when listing
<DOMAIN>.max_pages_api_release=10

# Rate limit remaining threshold. Threshold by which the tool will stop
# processing requests. Defaults to 10 if not provided. The remote has a counter
# that decreases with each request. When we reach this threshold we stop for safety.
# When it reaches 0 the remote will throw errors.
<DOMAIN>.rate_limit_remaining_threshold=10

### Other domains - add more if needed
"#;

pub fn execute<P: AsRef<Path>>(options: InitCommandOptions, config_path: P) -> Result<()> {
    let file = OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(config_path.as_ref());

    let mut file = match file {
        Ok(f) => f,
        Err(e) if e.kind() == ErrorKind::AlreadyExists => {
            return Err(GRError::PreconditionNotMet(format!(
                "Config file at {} already exists, move it aside before running `init` again",
                config_path.as_ref().display()
            ))
            .into())
        }
        Err(e) => {
            return Err(e).err_context(format!(
                "Unable to create config file at path {}",
                config_path.as_ref().display()
            ))
        }
    };
    generate_and_persist(options, &mut file).err_context(format!(
        "Failed to generate and persist config at path {}",
        config_path.as_ref().display()
    ))
}

fn generate_and_persist<W: Write>(options: InitCommandOptions, writer: &mut W) -> Result<()> {
    let data = change_placeholders(&options.domain);
    persist_config(data, writer)
}

fn persist_config<D: Into<String>, W: Write>(data: D, writer: &mut W) -> Result<()> {
    writer
        .write_all(data.into().as_bytes())
        .err_context("Writing the data to disk failed")?;
    Ok(())
}

fn change_placeholders(domain: &str) -> String {
    CONFIG_TEMPLATE.replace("<DOMAIN>", domain)
}

#[cfg(test)]
mod test {

    use super::*;

    #[test]
    fn test_persist_config() {
        let options = InitCommandOptions {
            domain: "gitlab.com".to_string(),
        };
        let mut writer = Vec::new();
        let result = generate_and_persist(options, &mut writer);
        assert!(result.is_ok());
        assert!(writer.len() > 0);
        let content = String::from_utf8(writer).unwrap();
        assert!(content.contains("gitlab.com"));
    }
}