1use std::{env, error::Error, fs, path::Path};
2
3use clap::Args;
4
5use crate::{config, github::git};
6
7#[derive(Args, Debug)]
8pub struct DeployOptions {
9 fork_url: String,
10}
11
12struct SshUrl(String);
13
14impl std::ops::Deref for SshUrl {
15 type Target = String;
16
17 fn deref(&self) -> &Self::Target {
18 &self.0
19 }
20}
21
22impl TryFrom<String> for SshUrl {
23 type Error = Box<dyn Error>;
24
25 fn try_from(value: String) -> Result<Self, Self::Error> {
26 let suffix = value
30 .strip_prefix("https://github.com/")
31 .ok_or("Could not get SSH URL")?;
32
33 Ok(SshUrl(format!(
34 "git@github.com:{}.git",
35 suffix.trim_end_matches('/')
36 )))
37 }
38}
39
40pub fn deploy(opts: DeployOptions) -> Result<(), Box<dyn Error>> {
41 let tmp_dir = tempfile::tempdir()?;
42 let repo_path = tmp_dir.path();
43
44 let ssh_url: SshUrl = opts.fork_url.try_into()?;
45
46 git(
47 repo_path,
48 &["clone", "--no-checkout", ssh_url.0.as_str(), "."],
49 )?;
50
51 println!("Running git with sparse checkout");
52 git(repo_path, &["sparse-checkout", "init", "--cone"])?;
53 git(repo_path, &["sparse-checkout", "set", "courses"])?;
54 git(repo_path, &["checkout"])?;
55
56 let course_name = get_current_dir_name()?;
57 let new_course_dir = repo_path.join("courses").join(&course_name);
58
59 copy_dir_all(env::current_dir()?, &new_course_dir)?;
60
61 let conf = config::read_and_validate_config(false)?;
65
66 config::update_time(conf)?;
67 println!("Committing files to remote...");
68 git(repo_path, &["add", "."])?;
69 git(repo_path, &["commit", "-m", "Add new course"])?;
70 git(repo_path, &["push"])?;
71
72 println!("Project sucessfully pushed to remote.");
73
74 Ok(())
75}
76
77fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
82 let cwd = env::current_dir()?;
83 let name = cwd
84 .file_name()
85 .ok_or("couldn't get directory name")?
86 .to_string_lossy()
87 .to_string();
88 Ok(name)
89}
90
91fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
93 fs::create_dir_all(&dst)?;
94 for entry in fs::read_dir(src)? {
95 let entry = entry?;
96 let ty = entry.file_type()?;
97 if ty.is_dir() {
98 copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
99 } else {
100 fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
101 }
102 }
103 Ok(())
104}