1use std::{env, error::Error, fs, path::Path};
2
3use clap::Args;
4
5use crate::{config, github::git};
6
7#[derive(Args, Debug)]
9pub struct DeployOptions {
10 fork_url: String,
11}
12
13struct SshUrl(String);
14
15impl std::ops::Deref for SshUrl {
17 type Target = String;
18
19 fn deref(&self) -> &Self::Target {
20 &self.0
21 }
22}
23
24impl TryFrom<String> for SshUrl {
33 type Error = Box<dyn Error>;
34
35 fn try_from(value: String) -> Result<Self, Self::Error> {
36 let suffix = value
40 .strip_prefix("https://github.com/")
41 .ok_or("Could not get SSH URL")?;
42
43 Ok(SshUrl(format!(
44 "git@github.com:{}.git",
45 suffix.trim_end_matches('/')
46 )))
47 }
48}
49
50pub fn deploy(opts: DeployOptions) -> Result<(), Box<dyn Error>> {
59 let tmp_dir = tempfile::tempdir()?;
60 let repo_path = tmp_dir.path();
61
62 let ssh_url: SshUrl = opts.fork_url.try_into()?;
63
64 git(
65 repo_path,
66 &["clone", "--no-checkout", ssh_url.0.as_str(), "."],
67 )?;
68
69 println!("Running git with sparse checkout");
70 git(repo_path, &["sparse-checkout", "init", "--cone"])?;
71 git(repo_path, &["sparse-checkout", "set", "courses"])?;
72 git(repo_path, &["checkout"])?;
73
74 let course_name = get_current_dir_name()?;
75 let new_course_dir = repo_path.join("courses").join(&course_name);
76
77 copy_dir_all(env::current_dir()?, &new_course_dir)?;
78
79 let conf = config::read_and_validate_config()?;
83
84 config::update_time(conf)?;
85 println!("Committing files to remote...");
86 git(repo_path, &["add", "."])?;
87 git(repo_path, &["commit", "-m", "Add new course"])?;
88 git(repo_path, &["push"])?;
89
90 println!("Project sucessfully pushed to remote.");
91
92 Ok(())
93}
94
95fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
101 let cwd = env::current_dir()?;
106 let name = cwd
107 .file_name()
108 .ok_or("couldn't get directory name")?
109 .to_string_lossy()
110 .to_string();
111 Ok(name)
112}
113
114fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
117 fs::create_dir_all(&dst)?;
118 for entry in fs::read_dir(src)? {
119 let entry = entry?;
120 let ty = entry.file_type()?;
121 if ty.is_dir() {
122 copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
123 } else {
124 fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
125 }
126 }
127 Ok(())
128}