1use std::fs::OpenOptions;
2use std::io::{ErrorKind, Write};
3
4use crate::cli::init::InitCommandOptions;
5use crate::error::{AddContext, GRError};
6use crate::remote::ConfigFilePath;
7use crate::Result;
8
9const CONFIG_TEMPLATE: &str = r#"
10# Fill in the <VALUE> below with your own values
11# and tweak accordingly.
12
13# NOTE: Substitute domain '.' for '_' in the section name
14# Ex. if DOMAIN is gitlab.com -> [gitlab_com]
15# Ex. if DOMAIN is gitlab.example.com -> [gitlab_example_com]
16
17[<DOMAIN>]
18
19api_token="<VALUE>"
20cache_location=".cache/gitar"
21
22
23# Rate limit remaining threshold. Threshold by which the tool will stop
24# processing requests. Defaults to 10 if not provided. The remote has a counter
25# that decreases with each request. When we reach this threshold we stop for safety.
26# When it reaches 0 the remote will throw errors.
27rate_limit_remaining_threshold=10
28
29[<DOMAIN>.merge_requests]
30
31# Single string username or map { "username" = "your_username", "id" = "your_user_id" }
32preferred_assignee_username="<VALUE>"
33description_signature=""
34
35# Array of usernames if the remote is Github
36# Array of hashmaps username => user ID if the remote is Gitlab or Github
37# Ex:
38# members = ["user1", "user2"]
39# members = [{"username" = "user1", "id" = "1234"}, {"username" = "user2", "id" = "5678"}]
40
41# You can use `gr pj members --format toml` to get the list of members for a project.
42# Be aware that if there are lots of members, this can involve multiple HTTP calls. Make use of
43# `gr pj members --num-pages` to query how many HTTP calls that might involve first.
44# To get your user ID, use `gr us get <your_username> --format toml`. Check the manual for more
45# information.
46
47
48members = []
49
50[<DOMAIN>.cache_expirations]
51
52# Expire read merge requests in 5 minutes
53merge_request="5m"
54# Expire read project metadata, members of a project in 5 days
55project="5d"
56# Pipelines are read often, change often, so expire soon.
57pipeline="30s"
58# Container registry operations including listing image tags and repos
59container_registry="1h"
60# Expire read releases in 1 day
61release="1d"
62# Expire single page calls in 1 day. Ex. Trending repositories in github.com
63single_page="1d"
64# Expire your user gists in 1 day
65gist="1d"
66# Expire repository tags immediately
67repository_tags="0s"
68
69[<DOMAIN>.max_pages_api]
70
71# Get up to 10 pages of merge requests when listing
72merge_request=10
73# Get up to 5 pages of project metadata, members of a project when listing
74project=5
75# Get up to 10 pages of pipelines when listing
76pipeline=10
77# Get up to 10 pages of container registry repositories when listing
78container_registry=10
79# Get up to 10 pages of releases when listing
80release=10
81# Get up to 5 pages of your gists
82gist=5
83# Get up to 10 pages of tags when listing
84repository_tags=10
85
86### Other domains - add more if needed
87"#;
88
89pub fn execute(options: InitCommandOptions, config_path: ConfigFilePath) -> Result<()> {
90 let file = OpenOptions::new()
91 .write(true)
92 .create_new(true)
93 .open(config_path.file_name());
94
95 let mut file = match file {
96 Ok(f) => f,
97 Err(e) if e.kind() == ErrorKind::AlreadyExists => {
98 return Err(GRError::PreconditionNotMet(format!(
99 "Config file at {} already exists, move it aside before running `init` again",
100 config_path.file_name().display()
101 ))
102 .into())
103 }
104 Err(e) => {
105 return Err(e).err_context(format!(
106 "Unable to create config file at path {}",
107 config_path.file_name().display()
108 ))
109 }
110 };
111 generate_and_persist(options, &mut file).err_context(format!(
112 "Failed to generate and persist config at path {}",
113 config_path.file_name().display()
114 ))
115}
116
117fn generate_and_persist<W: Write>(options: InitCommandOptions, writer: &mut W) -> Result<()> {
118 let data = change_placeholders(&options.domain);
119 persist_config(data, writer)
120}
121
122fn persist_config<D: Into<String>, W: Write>(data: D, writer: &mut W) -> Result<()> {
123 writer
124 .write_all(data.into().as_bytes())
125 .err_context("Writing the data to disk failed")?;
126 Ok(())
127}
128
129fn change_placeholders(domain: &str) -> String {
130 let domain = domain.replace(".", "_");
131 CONFIG_TEMPLATE.replace("<DOMAIN>", &domain)
132}
133
134#[cfg(test)]
135mod test {
136
137 use super::*;
138
139 #[test]
140 fn test_persist_config() {
141 let options = InitCommandOptions {
142 domain: "gitweb.com".to_string(),
143 };
144 let mut writer = Vec::new();
145 let result = generate_and_persist(options, &mut writer);
146 assert!(result.is_ok());
147 assert!(!writer.is_empty());
148 let content = String::from_utf8(writer).unwrap();
149 assert!(content.contains("gitweb_com"));
150 }
151}