💾 Archived View for 80h.dev › projects › gemgit › files › src › main.rs.gemini captured on 2022-03-01 at 15:23:43. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2020-09-24)
-=-=-=-=-=-=-
01 extern crate structopt;
02 #[macro_use]
03 extern crate structopt_derive;
04
05 use std::path;
06 use std::fs::File;
07 use std::io;
08 use std::fs;
09 use std::str;
10 use std::io::Write;
11 use std::io::BufRead;
12 use structopt::StructOpt;
13 use git2::{DiffFormat, Repository};
14 use chrono::{TimeZone, Utc};
15
16 #[macro_use]
17 extern crate serde_derive;
18 extern crate toml;
19
20 #[derive(Debug, Deserialize, Clone)]
21 struct Gitdesc {
22 name: String,
23 url: String,
24 desc: String,
25 readme: String,
26 }
27
28 fn gitdesc(file: path::PathBuf) -> Result<Gitdesc, io::Error> {
29 let fd = std::fs::read_to_string(file)?;
30 let gd: Gitdesc = toml::from_str(&fd)?;
31 return Ok(gd)
32 }
33
34 #[derive(StructOpt, Debug)]
35 #[structopt(name = "example", about = "An example of StructOpt usage.")]
36 struct Args {
37 #[structopt(short = "d", long = "dir", help = "Git dir")]
38 dir: String,
39 #[structopt(short = "o", long = "out", help = "Out dir")]
40 out: String,
41 }
42
43 fn read_file(file: &path::Path) -> io::Result<String> {
44 let fd = std::fs::File::open(&file)?;
45 let buf = io::BufReader::new(fd);
46 let mut s = String::new();
47 let mut n = 1;
48 for line in buf.lines() {
49 if let Ok(l) = line {
50 s.push_str(&format!("{:02} {}\n", n, l));
51 n += 1;
52 }
53 }
54 return Ok(s);
55 }
56
57 fn write_file(file: path::PathBuf, out: String) -> io::Result<()> {
58 let mut fd = File::create(file)?;
59 write!(fd, "{}", out)?;
60 Ok(())
61 }
62
63 fn files_index(mut path: path::PathBuf) -> io::Result<()> {
64 let mut idx = String::from(format!("# Files\n\nPath: {}\n\n", &path.display()));
65 for entry in std::fs::read_dir(&path)? {
66 if let Ok(entry) = entry {
67 let m = entry.metadata()?;
68 let p = entry.path();
69 let entry = p.strip_prefix(&path).unwrap();
70 if m.is_dir() {
71 idx.push_str(&format!("=> {}/ {}/\n", entry.display(), entry.display()));
72 files_index(p.to_path_buf())?;
73 } else {
74 let file = entry.to_str().unwrap().trim_end_matches(".gemini");
75 idx.push_str(&format!("=> {} {}\n", entry.display(), file));
76 }
77 }
78 }
79 path.push("index.gemini");
80 write_file(path,idx)?;
81 Ok(())
82 }
83
84 fn walk_dir(from: path::PathBuf, dst: path::PathBuf) -> io::Result<()> {
85 for entry in std::fs::read_dir(&from)? {
86 let entry = entry?;
87 if entry.file_name().to_str().unwrap().starts_with(".") {
88 continue;
89 }
90 let m = entry.metadata()?;
91 let mut dst = dst.clone();
92 let mut from = from.clone();
93 let p = entry.path();
94 let s = read_file(&p)?;
95 let p = p.strip_prefix(&from).unwrap();
96 if m.is_dir() {
97 if entry.path() == from {
98 continue;
99 }
100 dst.push(p);
101 from.push(p);
102 std::fs::create_dir_all(&dst)?;
103 walk_dir(from, dst)?;
104 continue
105 }
106 dst.push(format!("{}.gemini", p.display()));
107 write_file(dst, s)?;
108 }
109 Ok(())
110 }
111
112 fn commit_to_string(commit: git2::Commit) -> String {
113 let tm = commit.time().seconds();
114 let tm = Utc.timestamp(tm, 0);
115 let mut cm = commit.message().unwrap().trim().to_string();
116 if cm.len() >= 30 {
117 cm.truncate(29);
118 cm.push_str("…");
119 }
120 let s = String::from(format!("\n=> {}.txt {} {:<30} {}\n",
121 commit.id(),
122 tm.format("%Y-%m-%d %H:%M").to_string(),
123 cm,
124 commit.author()));
125 return s;
126 }
127
128 fn commits(mut pout: path::PathBuf, repo: Repository) -> Result<i64, git2::Error> {
129 let mut revwalk = repo.revwalk()?;
130 revwalk.push_head()?;
131 let mut com = String::from(format!("Date {:>26} {:>23}", "Commit Message","Author\n"));
132 let mut num: i64 = 0;
133
134 for id in revwalk {
135 let id = id?;
136 let commit = repo.find_commit(id)?;
137 let mut p = pout.clone();
138 p.push(format!("{}.txt",id));
139
140 let a = if commit.parents().len() == 1 {
141 let parent = commit.parent(0)?;
142 Some(parent.tree()?)
143 } else {
144 None
145 };
146 let b = commit.tree()?;
147 let mut d = String::new();
148 d.push_str(&format!("Commit: {}\nMessage:\n{}\n", id, commit.message().unwrap()));
149 let diff = repo.diff_tree_to_tree(a.as_ref(), Some(&b), None)?;
150 diff.print(DiffFormat::Patch, |_delta, _hunk, line| {
151 match line.origin() {
152 ' ' | '+' | '-' => d.push_str(&format!("{}", line.origin())),
153 _ => {}
154 }
155 d.push_str(&format!("{}", str::from_utf8(line.content()).unwrap()));
156 true
157 })?;
158 p.set_file_name(format!("{}.txt", id));
159 write_file(p, d).expect("Couldn't write file");
160 com.push_str(&commit_to_string(commit));
161 num += 1;
162 }
163
164 pout.push("index.gemini");
165 write_file(pout, com).expect("Couldn't write to file");
166 Ok(num)
167 }
168
169 fn index(path: path::PathBuf, gd: Gitdesc, num: i64) -> Result<String, io::Error> {
170 let mut index = String::from(format!(
171 "=== {} ===\n{}
172 \nClone the repo with: git clone {}\n
173 \n=== Git Details ===
174 \n=> commits/ View {} commits
175 \n=> files/ Files\n
176 \n=== Readme ===\n", gd.name, gd.desc, gd.url, num));
177
178 let readme = fs::read_to_string(path)?;
179 index.push_str(&readme);
180
181
182 Ok(index)
183 }
184
185 fn main() -> Result<(), io::Error> {
186 let args = Args::from_args();
187 let mut gpath = path::PathBuf::from(args.dir);
188 let gd = gpath.join(".gemgit");
189 let gd = gitdesc(gd)?;
190 let mut out = path::PathBuf::from(args.out);
191
192 let repo = Repository::open(&gpath).expect("Can't open repo");
193
194 out.push("commits/");
195 if !out.exists() {
196 std::fs::create_dir_all(&out)?;
197 }
198
199 let num = commits(out.clone(), repo).unwrap();
200 out.pop();
201
202 gpath.push(&gd.readme);
203 let idx = index(gpath.clone(), gd, num)?;
204 gpath.pop();
205
206 out.push("index.gemini");
207 write_file(out.clone(), idx)?;
208 out.pop();
209
210 out.push("files");
211 if !out.exists() {
212 std::fs::create_dir_all(&out)?;
213 }
214 walk_dir(gpath, out.clone())?;
215 files_index(out)?;
216
217 Ok(())
218 }