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