Dateien nach "/" hochladen
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
mod language;
|
||||
mod state;
|
||||
mod detect_filetype;
|
||||
mod highlighter;
|
||||
mod keywords;
|
||||
mod specials;
|
||||
mod col;
|
||||
mod ccat_markdown;
|
||||
|
||||
use crate::state::State;
|
||||
use crate::detect_filetype::detect_language;
|
||||
use crate::highlighter::{highlight_line, print_binary_file};
|
||||
use crate::col::HighlightConfig;
|
||||
use crate::language::Language;
|
||||
use crate::ccat_markdown::{md_html_main, md_ansi_main};
|
||||
use crate::specials::{print_hex_file, log_highlight_line, to_c_array};
|
||||
|
||||
const INFO_TEXT: &str = r#"
|
||||
Syntax: ccat <datei> [Optionen]
|
||||
@VERSION 2.9.4 Rust
|
||||
|
||||
c <filename> <options ...>
|
||||
|
||||
Optionen:
|
||||
n = Zeilennummern anzeigen
|
||||
b = Binärdatei als Hex-Dump ausgeben
|
||||
r = Remove all ANSI (save mode)
|
||||
h = print as HTML Source
|
||||
a = print file as C Hexadezimal Array
|
||||
x = print as hex dump
|
||||
|
||||
-s, --stop <nr> letzte auszugebende Zeile
|
||||
"#;
|
||||
|
||||
fn read_file_with_hex_fallback(path: &str, output: &mut String) {
|
||||
output.clear();
|
||||
let bytes = match fs::read(path) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
eprintln!("Fehler beim Lesen von '{}': {}", path, e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
output.reserve(bytes.len() * 2);
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
if let Some((ch, len)) = decode_next_utf8_char(&bytes[i..]) {
|
||||
output.push(ch);
|
||||
i += len;
|
||||
} else {
|
||||
output.push_str(&format!(" 0x{:02X}", bytes[i]));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_next_utf8_char(slice: &[u8]) -> Option<(char, usize)> {
|
||||
if slice.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let byte = slice[0];
|
||||
if matches!(byte, b'\n' | b'\r' | b'\t') {
|
||||
return Some((byte as char, 1));
|
||||
}
|
||||
if (32..=126).contains(&byte) {
|
||||
return Some((byte as char, 1));
|
||||
}
|
||||
if byte >= 128 {
|
||||
for len in 2..=4 {
|
||||
if len > slice.len() {
|
||||
break;
|
||||
}
|
||||
if let Ok(s) = std::str::from_utf8(&slice[..len]) {
|
||||
if let Some(ch) = s.chars().next() {
|
||||
if ch.len_utf8() == len {
|
||||
if !ch.is_control() {
|
||||
return Some((ch, len));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn remove_ansi_escapes(input: &str, output: &mut String) {
|
||||
output.clear();
|
||||
let bytes = input.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
if bytes[i] == 0x1B {
|
||||
if i + 1 < bytes.len() && bytes[i + 1] == b'[' {
|
||||
i += 2;
|
||||
while i < bytes.len() {
|
||||
let b = bytes[i];
|
||||
i += 1;
|
||||
if (b'A'..=b'Z').contains(&b) || (b'a'..=b'z').contains(&b) || (b'@'..=b'~').contains(&b) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
output.push(bytes[i] as char);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_int_by_string(s: String) -> u64
|
||||
{
|
||||
let num: u64 = s.parse().unwrap();
|
||||
return num;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() == 1 {
|
||||
print!("{}", INFO_TEXT);
|
||||
return;
|
||||
}
|
||||
let mut state = State::new();
|
||||
if args.len() >= 3 {
|
||||
for ch in args[2].chars() {
|
||||
match ch {
|
||||
'n' => state.flag_nr = true,
|
||||
'b' => state.print_binary = true,
|
||||
'r' => state.remove_all_ansi = true,
|
||||
'h' => state.html_color = true,
|
||||
'x' => state.hex = true,
|
||||
'a' => state.to_c_array = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if args[2] == "-s" || args[2] == "--stop" {
|
||||
state.stop_at_line = get_int_by_string(args[3].clone());
|
||||
} else {
|
||||
state.stop_at_line = 0xFFFFFFFFFFFFF000;
|
||||
}
|
||||
}
|
||||
let filename = &args[1];
|
||||
let lang = detect_language(filename);
|
||||
let mut source = String::new();
|
||||
let mut source_len = 0x1000;
|
||||
if !state.print_binary {
|
||||
read_file_with_hex_fallback(filename, &mut source);
|
||||
source_len = source.len();
|
||||
}
|
||||
let mut output = String::with_capacity(source_len * 2);
|
||||
let mut jump: bool = false;
|
||||
let config = if state.html_color {
|
||||
HighlightConfig::html_colors()
|
||||
} else {
|
||||
HighlightConfig::default()
|
||||
};
|
||||
if state.hex {
|
||||
print_hex_file(filename, &mut state, &config);
|
||||
jump = true;
|
||||
}
|
||||
if lang == Language::LogFile {
|
||||
for line in source.lines() {
|
||||
log_highlight_line(line, &mut state, &config);
|
||||
}
|
||||
jump = true;
|
||||
}
|
||||
if state.to_c_array {
|
||||
to_c_array(filename, &config);
|
||||
jump = true;
|
||||
}
|
||||
if state.print_binary {
|
||||
print_binary_file(filename, &mut output, &mut state, &config);
|
||||
jump = true;
|
||||
}
|
||||
if state.remove_all_ansi {
|
||||
remove_ansi_escapes(&source, &mut output);
|
||||
jump = true;
|
||||
}
|
||||
if !jump {
|
||||
if lang == Language::Markdown {
|
||||
if state.html_color {
|
||||
md_html_main(&source, &mut output);
|
||||
} else {
|
||||
md_ansi_main(&source, &mut output);
|
||||
}
|
||||
} else {
|
||||
for line in source.lines() {
|
||||
let stop_flag: bool = highlight_line(line, lang, &config, &mut output, &mut state);
|
||||
if stop_flag {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{}", output);
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn test_with_file(filename: &str, content: &[u8], expected: &str) {
|
||||
let filepath = format!("/tmp/{}", filename);
|
||||
fs::write(&filepath, content).unwrap_or_else(|e| {
|
||||
panic!("Konnte Testdatei nicht erstellen {}: {}", filepath, e);
|
||||
});
|
||||
let mut result = String::new();
|
||||
read_file_with_hex_fallback(&filepath, &mut result);
|
||||
assert_eq!(result, expected, "Testdatei {} fehlgeschlagen", filename);
|
||||
let _ = fs::remove_file(&filepath);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode_and_hex() {
|
||||
test_with_file(
|
||||
"rust_test_unicode.bin",
|
||||
b"Hello \xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F \xE2\x82\xAC \xF0\x9F\x9A\x80 World!\n\x00\x07\xFF",
|
||||
"Hello äöüß € 🚀 World!\n 0x00 0x07 0xFF",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_utf8_sequences() {
|
||||
test_with_file(
|
||||
"rust_test_invalid.bin",
|
||||
b"Valid text \xFF\xFE\xFD invalid again \xC3( broken \xE2\x82\xFF end",
|
||||
"Valid text 0xFF 0xFE 0xFD invalid again 0xC3( broken 0xE2 0x82 0xFF end",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_only_printable_text() {
|
||||
test_with_file(
|
||||
"rust_test_text.txt",
|
||||
b"Hello World!\nThis is a test.\nLine 3 with umlauts: \xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F",
|
||||
"Hello World!\nThis is a test.\nLine 3 with umlauts: äöüß",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_control_characters() {
|
||||
test_with_file(
|
||||
"rust_test_control.bin",
|
||||
b"Line1\nLine2\r\nTab\t\x00\x01\x02\x1F\x7F\xFF",
|
||||
"Line1\nLine2\r\nTab\t 0x00 0x01 0x02 0x1F 0x7F 0xFF",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_file() {
|
||||
test_with_file("rust_test_empty.bin", b"", "");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user