Skip to content

Commit b6d3b84

Browse files
authored
Merge pull request #167 from rustcoreutils/sccs
Sccs: what
2 parents 04dd350 + e549f12 commit b6d3b84

13 files changed

+528
-0
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ members = [
1212
"pathnames",
1313
"plib",
1414
"process",
15+
"sccs",
1516
"screen",
1617
"sys",
1718
"text",

plib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod group;
1212
pub mod io;
1313
pub mod lzw;
1414
pub mod modestr;
15+
pub mod sccsfile;
1516
pub mod testing;
1617
pub mod utmpx;
1718

plib/src/sccsfile.rs

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
//
2+
// Copyright (c) 2024 Hemi Labs, Inc.
3+
//
4+
// This file is part of the posixutils-rs project covered under
5+
// the MIT License. For the full license text, please see the LICENSE
6+
// file in the root directory of this project.
7+
// SPDX-License-Identifier: MIT
8+
//
9+
10+
use std::collections::HashMap;
11+
use std::str::FromStr;
12+
13+
#[derive(Debug)]
14+
pub struct SccsFile {
15+
pub header: SccsHeader,
16+
pub deltas: Vec<SccsDelta>,
17+
pub edits: Vec<SccsEdit>,
18+
pub user_info: SccsUserInfo,
19+
pub stats: SccsStats,
20+
}
21+
22+
#[derive(Debug)]
23+
pub struct SccsHeader {
24+
pub format_version: String,
25+
pub creation_date: String,
26+
pub author: String,
27+
pub description: String,
28+
}
29+
30+
#[derive(Debug)]
31+
pub struct SccsDelta {
32+
pub sid: String,
33+
pub date: String,
34+
pub time: String,
35+
pub author: String,
36+
pub added_lines: usize,
37+
pub deleted_lines: usize,
38+
pub comments: String,
39+
}
40+
41+
#[derive(Debug)]
42+
pub enum SccsEdit {
43+
Insert(String),
44+
Delete(usize),
45+
}
46+
47+
#[derive(Debug)]
48+
pub struct SccsUserInfo {
49+
pub users: HashMap<String, String>,
50+
}
51+
52+
#[derive(Debug)]
53+
pub struct SccsStats {
54+
pub total_deltas: usize,
55+
pub total_lines: usize,
56+
}
57+
58+
impl SccsFile {
59+
pub fn from_string(s: &str) -> Result<Self, &'static str> {
60+
let lines: Vec<&str> = s.lines().collect();
61+
62+
let header = parse_header(&lines)?;
63+
let deltas = parse_deltas(&lines)?;
64+
let edits = parse_edits(&lines)?;
65+
let user_info = parse_user_info(&lines)?;
66+
let stats = parse_stats(&deltas)?;
67+
68+
Ok(SccsFile {
69+
header,
70+
deltas,
71+
edits,
72+
user_info,
73+
stats,
74+
})
75+
}
76+
77+
pub fn serialize(&self) -> String {
78+
serialize_sccs_file(self)
79+
}
80+
}
81+
82+
fn parse_header(lines: &[&str]) -> Result<SccsHeader, &'static str> {
83+
let mut format_version = String::new();
84+
let mut creation_date = String::new();
85+
let author = String::new();
86+
let description = String::new();
87+
88+
for line in lines {
89+
if line.starts_with('h') {
90+
format_version = line.to_string();
91+
} else if line.starts_with('s') && creation_date.is_empty() {
92+
creation_date = line.to_string();
93+
} else if line.starts_with('d') {
94+
break; // End of header section
95+
}
96+
}
97+
98+
if format_version.is_empty() || creation_date.is_empty() {
99+
return Err("Missing header");
100+
}
101+
102+
Ok(SccsHeader {
103+
format_version,
104+
creation_date,
105+
author,
106+
description,
107+
})
108+
}
109+
110+
fn parse_edits(lines: &[&str]) -> Result<Vec<SccsEdit>, &'static str> {
111+
let mut edits = Vec::new();
112+
let mut current_insert = String::new();
113+
let mut in_insert_section = false;
114+
115+
for line in lines.iter() {
116+
if line.starts_with("I ") {
117+
in_insert_section = true;
118+
current_insert.push_str(line);
119+
current_insert.push('\n');
120+
} else if line.starts_with("E ") && in_insert_section {
121+
current_insert.push_str(line);
122+
current_insert.push('\n');
123+
edits.push(SccsEdit::Insert(current_insert.clone()));
124+
current_insert.clear();
125+
in_insert_section = false;
126+
} else if in_insert_section {
127+
current_insert.push_str(line);
128+
current_insert.push('\n');
129+
}
130+
}
131+
132+
Ok(edits)
133+
}
134+
135+
fn parse_user_info(lines: &[&str]) -> Result<SccsUserInfo, &'static str> {
136+
// Simplified user info parsing
137+
let mut users = HashMap::new();
138+
for line in lines.iter().skip_while(|l| !l.starts_with("users")).skip(1) {
139+
let parts: Vec<&str> = line.split_whitespace().collect();
140+
if parts.len() < 2 {
141+
return Err("Invalid user info format");
142+
}
143+
users.insert(parts[0].to_string(), parts[1].to_string());
144+
}
145+
Ok(SccsUserInfo { users })
146+
}
147+
148+
fn parse_deltas(lines: &[&str]) -> Result<Vec<SccsDelta>, &'static str> {
149+
let mut deltas = Vec::new();
150+
let mut current_comments = String::new();
151+
let mut in_delta_section = false;
152+
153+
for line in lines.iter() {
154+
if line.starts_with("d D") {
155+
in_delta_section = true;
156+
let parts: Vec<&str> = line.split_whitespace().collect();
157+
if parts.len() < 8 {
158+
return Err("Invalid delta format");
159+
}
160+
deltas.push(SccsDelta {
161+
sid: parts[2].to_string(),
162+
date: parts[3].to_string(),
163+
time: parts[4].to_string(),
164+
author: parts[5].to_string(),
165+
added_lines: usize::from_str(parts[6]).map_err(|_| "Invalid number format")?,
166+
deleted_lines: usize::from_str(parts[7]).map_err(|_| "Invalid number format")?,
167+
comments: String::new(),
168+
});
169+
} else if in_delta_section {
170+
if line.starts_with("c ") {
171+
current_comments.push_str(&line[2..]);
172+
current_comments.push('\n');
173+
} else if line.starts_with("e") {
174+
if let Some(last_delta) = deltas.last_mut() {
175+
last_delta.comments = current_comments.clone();
176+
}
177+
current_comments.clear();
178+
in_delta_section = false;
179+
}
180+
}
181+
}
182+
183+
Ok(deltas)
184+
}
185+
186+
fn parse_stats(deltas: &[SccsDelta]) -> Result<SccsStats, &'static str> {
187+
let total_deltas = deltas.len();
188+
let total_lines = deltas.iter().map(|d| d.added_lines + d.deleted_lines).sum();
189+
190+
Ok(SccsStats {
191+
total_deltas,
192+
total_lines,
193+
})
194+
}
195+
196+
fn serialize_sccs_file(sccs_file: &SccsFile) -> String {
197+
let mut result = String::new();
198+
result.push_str(&sccs_file.header.format_version);
199+
result.push('\n');
200+
result.push_str(&sccs_file.header.creation_date);
201+
result.push('\n');
202+
result.push_str(&sccs_file.header.author);
203+
result.push('\n');
204+
result.push_str(&sccs_file.header.description);
205+
result.push('\n');
206+
207+
for delta in &sccs_file.deltas {
208+
result.push_str(&format!(
209+
"d D {} {} {} {} {} {}\n",
210+
delta.sid, delta.date, delta.time, delta.author, delta.added_lines, delta.deleted_lines
211+
));
212+
if !delta.comments.is_empty() {
213+
result.push_str(&format!("c {}\n", delta.comments.trim_end()));
214+
}
215+
}
216+
217+
result.push_str("edits\n");
218+
for edit in &sccs_file.edits {
219+
match edit {
220+
SccsEdit::Insert(line) => result.push_str(&format!("insert {}\n", line)),
221+
SccsEdit::Delete(line_no) => result.push_str(&format!("delete {}\n", line_no)),
222+
}
223+
}
224+
225+
result.push_str("users\n");
226+
for (user, info) in &sccs_file.user_info.users {
227+
result.push_str(&format!("{} {}\n", user, info));
228+
}
229+
230+
result.push_str(&format!(
231+
"stats total_deltas:{} total_lines:{}\n",
232+
sccs_file.stats.total_deltas, sccs_file.stats.total_lines
233+
));
234+
235+
result
236+
}
237+
238+
#[cfg(test)]
239+
mod sccstest {
240+
use super::*;
241+
242+
#[test]
243+
fn basic_sccs_file_parse() {
244+
let simple = r#"
245+
h23005
246+
s 00003/00000/00013
247+
d D 1.2 24/07/09 19:42:04 jgarzik 2 1
248+
c added more data
249+
e
250+
s 00013/00000/00000
251+
d D 1.1 24/07/09 19:38:28 jgarzik 1 0
252+
c date and time created 24/07/09 19:38:28 by jgarzik
253+
e
254+
u
255+
U
256+
f e 0
257+
t
258+
T
259+
I 1
260+
apple
261+
banana
262+
charlie
263+
delta
264+
echo
265+
foxtrot
266+
golf
267+
hotel
268+
india
269+
juliet
270+
kilo
271+
lima
272+
mike
273+
E 1
274+
I 2
275+
november
276+
october
277+
pauly
278+
E 2
279+
"#;
280+
281+
let sccs_file = SccsFile::from_string(simple).expect("Failed to parse SCCS file");
282+
283+
// Verify header
284+
assert_eq!(sccs_file.header.format_version, "h23005");
285+
assert_eq!(sccs_file.header.creation_date, "s 00003/00000/00013");
286+
assert_eq!(sccs_file.header.author, "");
287+
assert_eq!(sccs_file.header.description, "");
288+
289+
// Verify deltas
290+
assert_eq!(sccs_file.deltas.len(), 2);
291+
292+
assert_eq!(sccs_file.deltas[0].sid, "1.2");
293+
assert_eq!(sccs_file.deltas[0].date, "24/07/09");
294+
assert_eq!(sccs_file.deltas[0].time, "19:42:04");
295+
assert_eq!(sccs_file.deltas[0].author, "jgarzik");
296+
assert_eq!(sccs_file.deltas[0].added_lines, 2);
297+
assert_eq!(sccs_file.deltas[0].deleted_lines, 1);
298+
assert_eq!(sccs_file.deltas[0].comments, "added more data\n");
299+
300+
assert_eq!(sccs_file.deltas[1].sid, "1.1");
301+
assert_eq!(sccs_file.deltas[1].date, "24/07/09");
302+
assert_eq!(sccs_file.deltas[1].time, "19:38:28");
303+
assert_eq!(sccs_file.deltas[1].author, "jgarzik");
304+
assert_eq!(sccs_file.deltas[1].added_lines, 1);
305+
assert_eq!(sccs_file.deltas[1].deleted_lines, 0);
306+
assert_eq!(
307+
sccs_file.deltas[1].comments,
308+
"date and time created 24/07/09 19:38:28 by jgarzik\n"
309+
);
310+
311+
// Verify edits
312+
assert_eq!(sccs_file.edits.len(), 2);
313+
314+
match &sccs_file.edits[0] {
315+
SccsEdit::Insert(line) => {
316+
assert_eq!(line, "I 1\napple\nbanana\ncharlie\ndelta\necho\nfoxtrot\ngolf\nhotel\nindia\njuliet\nkilo\nlima\nmike\nE 1\n");
317+
}
318+
_ => panic!("Unexpected edit type"),
319+
}
320+
321+
match &sccs_file.edits[1] {
322+
SccsEdit::Insert(line) => {
323+
assert_eq!(line, "I 2\nnovember\noctober\npauly\nE 2\n");
324+
}
325+
_ => panic!("Unexpected edit type"),
326+
}
327+
328+
// Verify user info
329+
assert_eq!(sccs_file.user_info.users.len(), 0);
330+
331+
// Verify stats
332+
assert_eq!(sccs_file.stats.total_deltas, 2);
333+
assert_eq!(sccs_file.stats.total_lines, 4);
334+
}
335+
}

sccs/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "posixutils-sccs"
3+
version = "0.1.12"
4+
edition = "2021"
5+
authors = ["Jeff Garzik"]
6+
license = "MIT"
7+
repository = "https://github.com/rustcoreutils/posixutils-rs.git"
8+
9+
[dependencies]
10+
plib = { path = "../plib" }
11+
clap.workspace = true
12+
gettext-rs.workspace = true
13+
14+
[[bin]]
15+
name = "what"
16+
path = "src/what.rs"
17+

0 commit comments

Comments
 (0)