The presentation will begin on the next slide.
Created by Cyrus Yip.
My student id is 1225258.
May 27, 2024
Here are the goals I intended to achieve with this project.
I wanted to learn Rust because its a popular language with focus on performance, safety, and memory management.
Learning a new lower-level like Rust will also improve my understanding of computer science.
These are what I used to learn:
I wanted to create a program that parsed a previous project's Git history to display a formatted changelog.
My previous project, songs-backup, periodically records the current state of a YouTube playlist with a commit.
I had trouble with the complexity of the git2 library.
I overcame this by experimentation and looking at examples.
At the start, I just outputed dates to see if I parsed the commits correctly.
Fri Feb 2 23:12:42 2024 +0000
Fri Feb 2 22:15:27 2024 +0000
Fri Feb 2 21:12:29 2024 +0000
Fri Feb 2 20:14:50 2024 +0000
Fri Feb 2 19:15:53 2024 +0000
Fri Feb 2 18:16:57 2024 +0000
Fri Feb 2 17:43:11 2024 +0000
Fri Feb 2 16:52:16 2024 +0000
Fri Feb 2 15:14:01 2024 +0000
Fri Feb 2 14:12:27 2024 +0000
Fri Feb 2 13:12:14 2024 +0000
Fri Feb 2 12:13:26 2024 +0000
Fri Feb 2 11:14:32 2024 +0000
Fri Feb 2 10:18:17 2024 +0000
Fri Feb 2 09:14:14 2024 +0000
Fri Feb 2 08:15:02 2024 +0000
Fri Feb 2 07:14:14 2024 +0000
Fri Feb 2 06:13:05 2024 +0000
Fri Feb 2 05:14:00 2024 +0000
Fri Feb 2 04:22:57 2024 +0000
Fri Feb 2 03:12:16 2024 +0000
Fri Feb 2 02:14:40 2024 +0000
Fri Feb 2 01:15:01 2024 +0000
Fri Feb 2 00:16:26 2024 +0000
Thu Feb 1 23:12:34 2024 +0000
Thu Feb 1 22:17:17 2024 +0000
Thu Feb 1 21:14:15 2024 +0000
Thu Feb 1 20:16:29 2024 +0000
Thu Feb 1 19:16:22 2024 +0000
Thu Feb 1 18:19:24 2024 +0000
Thu Feb 1 17:46:01 2024 +0000
Thu Feb 1 16:52:06 2024 +0000
Thu Feb 1 15:13:22 2024 +0000
Thu Feb 1 14:13:34 2024 +0000
Thu Feb 1 13:12:33 2024 +0000
Thu Feb 1 12:13:51 2024 +0000
Thu Feb 1 11:12:37 2024 +0000
Thu Feb 1 10:15:57 2024 +0000
Thu Feb 1 09:14:45 2024 +0000
Thu Feb 1 08:18:40 2024 +0000
After some work, I started to filter them and list the video ids.
## Thu Feb 2 17:02:57 2023 +0000
Added rW843YCHrh0
## Thu Feb 2 18:45:16 2023 +0000
Added ZxY13m83UUw
## Mon Feb 6 21:11:32 2023 +0000
Added 2KlHN15BCNg
Added G8twM11msXU
## Wed Feb 8 18:24:02 2023 +0000
Removed EFjtIV45Vw4
## Thu Feb 9 00:15:49 2023 +0000
Added bB2GSoDYhLw
Added gCc_dqj_W1s
Added hiFH2I7xaeU
## Thu Feb 9 08:16:47 2023 +0000
Added WgVWLa6klKo
## Thu Feb 9 18:49:44 2023 +0000
Added Isd5ViaHLbg
Added xSg5wI3hcy4
## Fri Feb 10 00:15:48 2023 +0000
Added pEEEe9xnv_Y
## Sat Feb 11 12:11:56 2023 +0000
Added edid66UmfiQ
## Sun Feb 12 10:14:00 2023 +0000
Added Ycc7ZW27VEY
## Sun Feb 12 11:09:38 2023 +0000
Added AROi9sNCVKs
Added SID2OofwYyM
Added iiw9Z1I1AcE
Added qLTCh1WpDBU
## Sun Feb 12 18:44:34 2023 +0000
Added fW1Cgv63naI
Added iPcX6EpJI3o
Added w4nihuYVTW0
## Tue Feb 14 00:16:26 2023 +0000
Added J3KA6WDAYPM
## Wed Feb 15 00:16:22 2023 +0000
Added CSo0rfnWUQM
Added WgTms03-syU
## Wed Feb 15 08:17:34 2023 +0000
Added cHgfFyyMXf4
## Wed Feb 15 09:12:03 2023 +0000
Added mgmfkIAXHjg
## Wed Feb 15 17:02:09 2023 +0000
Added 1NU9Pk607uk
Removed m0TRYx894RE
Finally, I added a link to the video.
# songs-history
## Mon Nov 7 09:27:34 2022 -0800
Added [--99FF6xFNs](https://youtu.be/--99FF6xFNs)
Added [-1c6y3qsR2g](https://youtu.be/-1c6y3qsR2g)
Added [-3xrSr9a5vg](https://youtu.be/-3xrSr9a5vg)
Added [-40fLtf9Hio](https://youtu.be/-40fLtf9Hio)
Added [-GoZOCNSIYw](https://youtu.be/-GoZOCNSIYw)
Added [-IudP7eYVLc](https://youtu.be/-IudP7eYVLc)
Added [-RGYC87IZ-E](https://youtu.be/-RGYC87IZ-E)
Added [-RfkZve6cIg](https://youtu.be/-RfkZve6cIg)
Added [-TOtm_9qBf8](https://youtu.be/-TOtm_9qBf8)
Added [-TcBZiit5a8](https://youtu.be/-TcBZiit5a8)
Added [-ZxMDgCpGlg](https://youtu.be/-ZxMDgCpGlg)
Added [-bWqcKzbQBY](https://youtu.be/-bWqcKzbQBY)
Added [-biOGdYiF-I](https://youtu.be/-biOGdYiF-I)
Added [-gkwJEkS4_M](https://youtu.be/-gkwJEkS4_M)
Added [-jPgsg3xs0I](https://youtu.be/-jPgsg3xs0I)
Added [-lcVA_rn-7s](https://youtu.be/-lcVA_rn-7s)
Added [-nfnbrICydI](https://youtu.be/-nfnbrICydI)
Added [-tKVN2mAKRI](https://youtu.be/-tKVN2mAKRI)
Added [-u3NXWgQpjY](https://youtu.be/-u3NXWgQpjY)
Added [-wpTY3LM5bc](https://youtu.be/-wpTY3LM5bc)
Added [-y6J28I5z0c](https://youtu.be/-y6J28I5z0c)
Added [0-q1KafFCLU](https://youtu.be/0-q1KafFCLU)
Added [03OvXCvtV0E](https://youtu.be/03OvXCvtV0E)
Added [04tYkKUPPv4](https://youtu.be/04tYkKUPPv4)
Added [0AFBaZp1wPU](https://youtu.be/0AFBaZp1wPU)
Added [0BZLxBp4wgU](https://youtu.be/0BZLxBp4wgU)
Added [0GeCPanRHU0](https://youtu.be/0GeCPanRHU0)
Added [0KPJE-NiwWs](https://youtu.be/0KPJE-NiwWs)
Added [0Q5Ra4suNPE](https://youtu.be/0Q5Ra4suNPE)
Added [0SqwzA0FVTU](https://youtu.be/0SqwzA0FVTU)
Added [0T9guE-LyHg](https://youtu.be/0T9guE-LyHg)
Added [0_1MxVlid7s](https://youtu.be/0_1MxVlid7s)
Added [0cOAUSVBGX8](https://youtu.be/0cOAUSVBGX8)
Added [0eEWxzW2wJU](https://youtu.be/0eEWxzW2wJU)
Added [0imk0ByfgoM](https://youtu.be/0imk0ByfgoM)
Added [0ivQwwgW4OY](https://youtu.be/0ivQwwgW4OY)
Added [0kA9HQmHm9c](https://youtu.be/0kA9HQmHm9c)
Added [0l-qw9yRFOA](https://youtu.be/0l-qw9yRFOA)
Added [0t2tjNqGyJI](https://youtu.be/0t2tjNqGyJI)
Added [0wAEZouvlj8](https://youtu.be/0wAEZouvlj8)
Added [1-xGerv5FOk](https://youtu.be/1-xGerv5FOk)
Please enjoy this demo of the program in action.
Here's the full source code.
use std::{
collections::HashSet,
fs::OpenOptions,
io::{BufWriter, Read, Write},
path::{Path, PathBuf},
};
use clap::Parser;
use git2::{
Delta::{Added, Deleted},
Repository, Time,
};
use serde_json::Deserializer;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Path to the songs-backup git repository
directory: PathBuf,
/// Overwrite the output file
#[arg(short, long)]
force: bool,
}
fn main() -> std::io::Result<()> {
let args = Args::parse();
let repo = match Repository::open(args.directory) {
Ok(repo) => repo,
Err(e) => panic!("failed to open: {}", e),
};
let mut revwalk = repo.revwalk().unwrap();
revwalk.push_head().unwrap();
let file = match OpenOptions::new()
.write(true)
.truncate(true)
.create_new(!args.force)
.create(args.force)
.open("output.txt")
{
Ok(f) => f,
Err(e) => match e.kind() {
std::io::ErrorKind::AlreadyExists => {
panic!("Output file output.txt already exists. Use -f, --force to force overwriting the destination");
}
_ => panic!("failed to open file: {}", e),
},
};
let mut writer = BufWriter::new(file);
writeln!(writer, "# songs-history")?;
let current_ids = get_current_ids(&repo).unwrap();
let mut already_added: HashSet<String> = HashSet::new();
for commit in revwalk.collect::<Vec<_>>().iter().rev() {
let commit = repo.find_commit(*commit.as_ref().unwrap()).unwrap();
let parent = match commit.parent(0) {
Ok(parent) => parent,
Err(_) => continue,
};
let diff = repo
.diff_tree_to_tree(
Some(&parent.tree().unwrap()),
Some(&commit.tree().unwrap()),
None,
)
.unwrap();
let mut added: Vec<String> = Vec::new();
let mut deleted: Vec<String> = Vec::new();
for delta in diff.deltas() {
if !matches!(delta.status(), Added | Deleted) {
continue;
}
let new_file = delta.new_file();
let path = new_file.path().unwrap();
if !path.starts_with("output/songs") {
continue;
}
let video = path.file_stem().unwrap().to_string_lossy();
match delta.status() {
Added => {
if already_added.contains(&video.to_string()) {
continue;
}
already_added.insert(video.to_string());
added.push(video.to_string());
}
Deleted => {
if current_ids.contains(&video.to_string()) {
continue;
}
deleted.push(video.to_string());
}
_ => {}
}
}
if added.len() + deleted.len() == 0 {
continue;
}
writeln!(writer, "## {}", format_time(&commit.time()))?;
for video in added {
writeln!(writer, "Added {} ", format_video(&video))?;
}
for video in deleted {
writeln!(writer, "Removed {} ", format_video(&video))?;
}
}
println!("Wrote to output.txt");
Ok(())
}
fn format_time(time: &Time) -> String {
let (offset, sign) = match time.offset_minutes() {
n if n < 0 => (-n, '-'),
n => (n, '+'),
};
let (hours, minutes) = (offset / 60, offset % 60);
let ts = time::Timespec::new(time.seconds() + (time.offset_minutes() as i64) * 60, 0);
let time = time::at(ts);
format!(
"{} {}{:02}{:02}",
time.strftime("%a %b %e %T %Y").unwrap(),
sign,
hours,
minutes
)
}
fn get_current_ids(repo: &Repository) -> Result<HashSet<String>, git2::Error> {
let obj = repo
.head()?
.peel_to_tree()?
.get_path(Path::new("output/summary.json"))?
.to_object(&repo)?
.peel_to_blob()?;
let mut file_content = String::new();
obj.content().read_to_string(&mut file_content).unwrap();
let mut ids: HashSet<String> = HashSet::new();
let stream = Deserializer::from_str(&file_content).into_iter::<serde_json::Value>();
for value in stream {
if let Ok(obj) = value {
if let Some(items) = obj.get("items") {
if let Some(items_array) = items.as_array() {
for item in items_array {
let id = item["id"].as_str().unwrap();
ids.insert(id.to_string());
}
}
}
}
}
Ok(ids)
}
fn format_video(video: &str) -> String {
format!("[{}](https://youtu.be/{})", video, video)
}
I posted it to a GitHub repository, realcyguy/songs-history.
I'm happy with the result because I created something that I was missing and I learned Rust. Specifically, my understanding has improved the most in these areas:
Thank you!