mirror of
https://github.com/uutils/diffutils.git
synced 2026-06-10 15:48:59 -07:00
134 lines
4.6 KiB
Rust
134 lines
4.6 KiB
Rust
// This file is part of the uutils diffutils package.
|
|
//
|
|
// For the full copyright and license information, please view the LICENSE-*
|
|
// files that was distributed with this source code.
|
|
|
|
use std::io::Write;
|
|
|
|
use unicode_width::UnicodeWidthStr;
|
|
|
|
/// Replace tabs by spaces in the input line.
|
|
/// Correctly handle multi-bytes characters.
|
|
/// This assumes that line does not contain any line breaks (if it does, the result is undefined).
|
|
pub fn do_expand_tabs(line: &[u8], tabsize: usize) -> Vec<u8> {
|
|
let tab = b'\t';
|
|
let ntabs = line.iter().filter(|c| **c == tab).count();
|
|
if ntabs == 0 {
|
|
return line.to_vec();
|
|
}
|
|
let mut result = Vec::with_capacity(line.len() + ntabs * (tabsize - 1));
|
|
let mut offset = 0;
|
|
|
|
let mut iter = line.split(|c| *c == tab).peekable();
|
|
while let Some(chunk) = iter.next() {
|
|
match String::from_utf8(chunk.to_vec()) {
|
|
Ok(s) => offset += UnicodeWidthStr::width(s.as_str()),
|
|
Err(_) => offset += chunk.len(),
|
|
}
|
|
result.extend_from_slice(chunk);
|
|
if iter.peek().is_some() {
|
|
result.resize(result.len() + tabsize - offset % tabsize, b' ');
|
|
offset = 0;
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
/// Write a single line to an output stream, expanding tabs to space if necessary.
|
|
/// This assumes that line does not contain any line breaks
|
|
/// (if it does and tabs are to be expanded to spaces, the result is undefined).
|
|
pub fn do_write_line(
|
|
output: &mut Vec<u8>,
|
|
line: &[u8],
|
|
expand_tabs: bool,
|
|
tabsize: usize,
|
|
) -> std::io::Result<()> {
|
|
if expand_tabs {
|
|
output.write_all(do_expand_tabs(line, tabsize).as_slice())
|
|
} else {
|
|
output.write_all(line)
|
|
}
|
|
}
|
|
|
|
pub fn get_modification_time(file_path: &str) -> String {
|
|
use chrono::{DateTime, Local};
|
|
use std::fs;
|
|
|
|
let metadata = fs::metadata(file_path).expect("Failed to get metadata");
|
|
let modification_time = metadata
|
|
.modified()
|
|
.expect("Failed to get modification time");
|
|
let modification_time: DateTime<Local> = modification_time.into();
|
|
let modification_time: String = modification_time
|
|
.format("%Y-%m-%d %H:%M:%S%.9f %z")
|
|
.to_string();
|
|
|
|
modification_time
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
mod expand_tabs {
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
fn assert_tab_expansion(line: &str, tabsize: usize, expected: &str) {
|
|
assert_eq!(
|
|
do_expand_tabs(line.as_bytes(), tabsize),
|
|
expected.as_bytes()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn basics() {
|
|
assert_tab_expansion("foo barr baz", 8, "foo barr baz");
|
|
assert_tab_expansion("foo\tbarr\tbaz", 8, "foo barr baz");
|
|
assert_tab_expansion("foo\tbarr\tbaz", 5, "foo barr baz");
|
|
assert_tab_expansion("foo\tbarr\tbaz", 2, "foo barr baz");
|
|
}
|
|
|
|
#[test]
|
|
fn multibyte_chars() {
|
|
assert_tab_expansion("foo\tƩpƩe\tbaz", 8, "foo ƩpƩe baz");
|
|
assert_tab_expansion("foo\tš\tbaz", 5, "foo š baz");
|
|
|
|
// Note: The Woman Scientist emoji (š©āš¬) is a ZWJ sequence combining
|
|
// the Woman emoji (š©) and the Microscope emoji (š¬). On supported platforms
|
|
// it is displayed as a single emoji and should have a print size of 2 columns,
|
|
// but terminal emulators tend to not support this, and display the two emojis
|
|
// side by side, thus accounting for a print size of 4 columns.
|
|
assert_tab_expansion("foo\tš©āš¬\tbaz", 6, "foo š©āš¬ baz");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_utf8() {
|
|
// [240, 240, 152, 137] is an invalid UTF-8 sequence, so it is handled as 4 bytes
|
|
assert_eq!(
|
|
do_expand_tabs(&[240, 240, 152, 137, 9, 102, 111, 111], 8),
|
|
&[240, 240, 152, 137, 32, 32, 32, 32, 102, 111, 111]
|
|
);
|
|
}
|
|
}
|
|
|
|
mod write_line {
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
fn assert_line_written(line: &str, expand_tabs: bool, tabsize: usize, expected: &str) {
|
|
let mut output: Vec<u8> = Vec::new();
|
|
assert!(do_write_line(&mut output, line.as_bytes(), expand_tabs, tabsize).is_ok());
|
|
assert_eq!(output, expected.as_bytes());
|
|
}
|
|
|
|
#[test]
|
|
fn basics() {
|
|
assert_line_written("foo bar baz", false, 8, "foo bar baz");
|
|
assert_line_written("foo bar\tbaz", false, 8, "foo bar\tbaz");
|
|
assert_line_written("foo bar\tbaz", true, 8, "foo bar baz");
|
|
}
|
|
}
|
|
}
|