gr/
init.rs

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::fs::OpenOptions;
use std::io::{ErrorKind, Write};

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

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

# NOTE: Substitute domain '.' for '_' in the section name
# Ex. if DOMAIN is gitlab.com -> [gitlab_com]
# Ex. if DOMAIN is gitlab.example.com -> [gitlab_example_com]

[<DOMAIN>]

api_token="<VALUE>"
cache_location=".cache/gitar"


# 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.
rate_limit_remaining_threshold=10

[<DOMAIN>.merge_requests]

# Single string username or map { "username" = "your_username", "id" = "your_user_id" }
preferred_assignee_username="<VALUE>"
description_signature=""

# Array of usernames if the remote is Github
# Array of hashmaps username => user ID if the remote is Gitlab or Github
# Ex:
# members = ["user1", "user2"]
# members = [{"username" = "user1", "id" = "1234"}, {"username" = "user2", "id" = "5678"}]

# You can use `gr pj members --format toml` to get the list of members for a project.
# Be aware that if there are lots of members, this can involve multiple HTTP calls. Make use of
# `gr pj members --num-pages` to query how many HTTP calls that might involve first.
# To get your user ID, use `gr us get <your_username> --format toml`. Check the manual for more
# information.


members = []

[<DOMAIN>.cache_expirations]

# Expire read merge requests in 5 minutes
merge_request="5m"
# Expire read project metadata, members of a project in 5 days
project="5d"
# Pipelines are read often, change often, so expire soon.
pipeline="30s"
# Container registry operations including listing image tags and repos
container_registry="1h"
# Expire read releases in 1 day
release="1d"
# Expire single page calls in 1 day. Ex. Trending repositories in github.com
single_page="1d"
# Expire your user gists in 1 day
gist="1d"
# Expire repository tags immediately
repository_tags="0s"

[<DOMAIN>.max_pages_api]

# Get up to 10 pages of merge requests when listing
merge_request=10
# Get up to 5 pages of project metadata, members of a project when listing
project=5
# Get up to 10 pages of pipelines when listing
pipeline=10
# Get up to 10 pages of container registry repositories when listing
container_registry=10
# Get up to 10 pages of releases when listing
release=10
# Get up to 5 pages of your gists
gist=5
# Get up to 10 pages of tags when listing
repository_tags=10

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

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

    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.file_name().display()
            ))
            .into())
        }
        Err(e) => {
            return Err(e).err_context(format!(
                "Unable to create config file at path {}",
                config_path.file_name().display()
            ))
        }
    };
    generate_and_persist(options, &mut file).err_context(format!(
        "Failed to generate and persist config at path {}",
        config_path.file_name().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 {
    let domain = domain.replace(".", "_");
    CONFIG_TEMPLATE.replace("<DOMAIN>", &domain)
}

#[cfg(test)]
mod test {

    use super::*;

    #[test]
    fn test_persist_config() {
        let options = InitCommandOptions {
            domain: "gitweb.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("gitweb_com"));
    }
}