Skip to main content

terminal_magic/modules/
update.rs

1use std::{path::Path, process::Command};
2
3use colored::Colorize;
4use indexmap::IndexMap;
5use mustache::MapBuilder;
6
7use crate::{models::{GlobalConfig, ModuleState, EntryType, PluginType}, prompts::{boolean_prompt, read, read_array, get_short_names}, modules::print_diff, template::{add_files_as_vars, render}};
8
9use super::{read_config, check_module_state, install::{install, write_file}, get_old_script};
10
11// Copyright (c) 2022 Patrick Amrein <amrein@ubique.ch>
12//
13// This software is released under the MIT License.
14// https://opensource.org/licenses/MIT
15
16pub fn update(
17    global_config: &GlobalConfig,
18    git_repo: &str,
19    plugin_name: &str,
20    fail_on_error: bool,
21    silent: bool,
22) {
23    let home_path = global_config.home.join(plugin_name);
24    if !home_path.exists() {
25        eprintln!("module is not installed");
26        if fail_on_error {
27            std::process::exit(1);
28        }
29        return;
30    }
31    let path_to_module = Path::new(git_repo).join(plugin_name);
32    if !path_to_module.exists() {
33        eprintln!(
34            "{}",
35            "Could not find module in the git repo. Did you execute `git pull`?".red()
36        );
37        if fail_on_error {
38            std::process::exit(1);
39        }
40        return;
41    }
42    let mustache = mustache::compile_path(path_to_module.join("template.sh"))
43        .expect("Could not parse mustache template");
44
45    let mut toml = read_config(&home_path.join("data.toml")).expect("Cannot find TOML");
46    let old_toml = toml.clone();
47    let old_config = read_config(&home_path.join("config.toml"))
48        .expect("Cannot find old config (maybe you did update terminal-magic)");
49    let new_config =
50        read_config(&path_to_module.join("config.toml")).expect("module config not found");
51    let mut mustache_map_builder = MapBuilder::new();
52    if old_config != new_config {
53        println!("{}", "Config changed check the changes".yellow());
54
55        let old_config_str = toml::to_string(&old_config).unwrap();
56        let new_config_str = toml::to_string(&new_config).unwrap();
57        print_diff(&old_config_str, &new_config_str);
58        if old_config.placeholders != new_config.placeholders {
59            let mut update_map = IndexMap::new();
60            if let Some(new_placeholders) = &new_config.placeholders {
61                for (key, entry) in new_placeholders {
62                    if let Some(v) = toml.placeholders.as_ref().and_then(|a| a.get(key)) {
63                        update_map.insert(key.to_owned(), v.to_owned());
64                    } else {
65                        // prompt new value
66                        let (new_mustache_map_builder, object) =
67                            read(key, entry, mustache_map_builder);
68                        mustache_map_builder = new_mustache_map_builder;
69                        update_map.insert(key.to_owned(), object);
70                    }
71                }
72                toml.placeholders = Some(update_map);
73            } else {
74                toml.placeholders = None;
75            }
76        }
77    }
78    toml.plugin_info = new_config.plugin_info.clone();
79
80    if let Some(internal_deps) = toml.plugin_info.internal_dependencies.as_mut() {
81        for dep in internal_deps {
82            match check_module_state(global_config, git_repo, dep) {
83                ModuleState::NotInstalled => install(global_config, git_repo, dep),
84                ModuleState::UpToDate => {}
85                ModuleState::NeedsUpdate(reason) => {
86                    println!("Update since: {:?}", reason);
87                    update(global_config, git_repo, dep, false, true)
88                }
89            }
90        }
91    }
92    if let Some(external_deps) = toml.plugin_info.external_dependencies.as_ref() {
93        for dep in external_deps {
94            println!(
95                "[{}] needs external dependency {}",
96                plugin_name.yellow(),
97                dep.yellow()
98            );
99        }
100    }
101
102    if let Some(placeholders) = toml.placeholders.as_mut() {
103        for placeholder in placeholders.iter_mut() {
104            if old_toml
105                .placeholders
106                .as_ref()
107                .and_then(|a| a.get(placeholder.0))
108                .is_some()
109            {
110                if let EntryType::Array(arr) = placeholder.1 {
111                    if !silent && boolean_prompt(&format!("Add new elements [{}]? ", placeholder.0))
112                    {
113                        if old_config
114                            .placeholders
115                            .as_ref()
116                            .unwrap()
117                            .get(placeholder.0)
118                            .is_some()
119                        {
120                            let (new_mustache_map_builder, object) =
121                                read_array(placeholder.0, &arr[0], mustache_map_builder);
122                            mustache_map_builder = new_mustache_map_builder;
123                            arr.extend(if let EntryType::Array(a) = object {
124                                a
125                            } else {
126                                unreachable!("read_array MUST always return an EntryType::Array")
127                            });
128                        }
129                    } else {
130                        let name = get_short_names(arr);
131                        mustache_map_builder = mustache_map_builder
132                            .insert_str(format!("{}_shortNames", placeholder.0), name);
133                    }
134                }
135            }
136            mustache_map_builder = mustache_map_builder
137                .insert(placeholder.0, &placeholder.1)
138                .expect("Could not parse object");
139        }
140    }
141    let should_overwrite = boolean_prompt("Update supporting files?");
142
143    if let Some(files) = &new_config.supporting_files {
144        let home_path = global_config.home.join(plugin_name);
145        mustache_map_builder = add_files_as_vars(
146            files,
147            mustache_map_builder,
148            &home_path,
149            &path_to_module,
150            &home_path,
151            should_overwrite,
152        );
153    }
154
155    let mustache_map = mustache_map_builder.build();
156    let script = render(mustache, mustache_map);
157    let old_script = get_old_script(global_config, plugin_name);
158
159    print_diff(&old_script, &script);
160
161    if !boolean_prompt("Update?") {
162        return;
163    }
164
165    if let PluginType::RustPackage { path, git, tag } = new_config.plugin_info.plugin_type {
166        let mut install_command = Command::new("cargo");
167        if let Some(git) = git {
168            install_command
169                .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true")
170                .arg("install")
171                .arg("--git")
172                .arg(git);
173            if let Some(tag) = tag {
174                install_command.arg("--tag").arg(tag);
175            }
176        } else if let Some(path) = path {
177            install_command
178                .arg("install")
179                .arg("--path")
180                .arg(&path_to_module.join(path));
181        } else {
182            panic!("either path or git should be set");
183        };
184        match install_command
185            .spawn()
186            .expect("Could not install module")
187            .wait_with_output()
188        {
189            Ok(_) => println!("Successfully installed rust-module"),
190            Err(err) => panic!("{:?}", err),
191        }
192    }
193    write_file(global_config, toml, script, plugin_name, &path_to_module);
194}