Skip to content

Instantly share code, notes, and snippets.

@dutchLuck
Last active October 19, 2024 10:32
Show Gist options
  • Save dutchLuck/e2820cac1b6f1a6d83755fc295611d33 to your computer and use it in GitHub Desktop.
Save dutchLuck/e2820cac1b6f1a6d83755fc295611d33 to your computer and use it in GitHub Desktop.
Read and output up to 1000 (UTF-8) characters from either the head or tail of a file or from stdin if no file is specified:

This "head_tail_file" gist is a contrived example of a command line utility facilitating AI code generation experimentation.

H E A D _ T A I L _ F I L E
===========================
This head_tail_file gist is a contrived example of a command line utility. It is a utility that displays either the head
or tail of a file or stdin. It is meant to facilitate experimentation with AI generated Rust code, particularly in the area
of command line parameter handling (with clap).
The first attempt was with Copilot AI and resulted in the head_tail_file.rs version 0.1.0 code. Although Copilot told me that
it was clap version 4 code, I couldn't get cargo to do a successful build until I put clap = "2.0" in the Cargo.toml file.
It seems the code doesn't handle any negetive number values for parameters well, interpretting them as unknown short form
options.
The second attempt was with ChatGPT and resulted in the head_tail_file.rs version 0.1.1 code. Again the AI told me that it was
clap version 4 code, but I couldn't get cargo to do a successful build until I specified clap version 3.0 in the Cargo.toml
file. The generated code looked good, but the head option always output the whole file, not just the first 1000 chars.
The tail option did appear to work. In the end I put some of the Copilot code in to fix the head option.
The Cargo.toml file is; -
[package]
name = "head_tail_file"
version = "0.1.2"
edition = "2021"
[dependencies]
clap = { version = "4.0", features = ["derive"] }
//
// H E A D _ T A I L _ F I L E
//
// Display up to 1000 characters from the start of a file
// or up to a 1000 characters at the end of the file
// or display characters from stdin
//
// ChatGPT AI generated code Sep-Oct 2024
//
// This code is described by ChatGPT AI as; -
// Here is a Rust command-line utility that reads a designated
// number of characters from either the head or tail of a file or stdin.
// We'll use the clap crate for parsing command-line arguments.
//
// Please heed the warning shown at the bottom of the AI
// response to my code generation requests; -
// "ChatGPT can make mistakes. Check important info."
//
// Please consider yourself duly warned.
//
// To build the code the line; -
// clap = { version = "4.0", features = ["derive"] }
// needs to be added below the [dependencies] line in the Cargo.toml file
//
// Usage:
//
// $ head_tail_file --help
// Displays up to a 1000 characters from the head or tail of multiple files or stdin
//
// Usage: head_tail_file [OPTIONS] [FILES]...
//
// Arguments:
// [FILES]... stdin or file(s) to display
//
// Options:
// -v, --verbose turns on verbose mode and prints the file name before display
// -t, --tail turns on tail mode
// -c, --count <COUNT> Number of characters to read (1 to 1000) [default: 1000]
// -h, --help Print help
// -V, --version Print version
//
// Version:
//
// $ head_tail_file --version
// head_tail_file 0.1.2
//
// Note:
//
// 1. both --help and --version terminate the program immediately.
// 2. invalid UTF-8 in the file may stop any output of file characters
// irrespective of the value of --count being smaller than the location
// of the invalid characters.
// 3. unknown command line arguments, don't just get flagged with an
// error message then ignored, they make the program terminate immediately
// 4. Inefficient for large files? All of files are read before display?
//
use clap::Parser;
use std::fs::File;
use std::io::{self, Read};
#[derive(Parser,Debug)]
#[command(
author = "Copilot & ChatGPT AI & OFH",
version,
about = "Displays up to a 1000 characters from the head or tail of multiple files or stdin"
)]
struct Cli {
/// turns on verbose mode and prints the file name before display
#[arg(short,long = "verbose")]
verbose: bool,
/// turns on tail mode
#[arg(short,long = "tail")]
tail: bool,
/// Number of characters to read (1 to 1000).
#[arg(short,long = "count",default_value = "1000")]
count: usize,
/// stdin or file(s) to display
#[arg(name = "FILES")]
files: Vec<String>,
}
fn main() -> io::Result<()> {
let args = Cli::parse();
let mut count = args.count;
// Limit the count to the range [1, 1000]
if args.count < 1 || args.count > 1000 {
count = args.count.clamp(1, 1000);
eprintln!(
"Warning: Adjusted character count from {} to {} (limit is between 1 and 1000).",
args.count, count
);
}
if args.files.is_empty() {
// Read from stdin if no files are provided
if args.verbose {
println!("==> stdin <=="); // Print file name in verbose mode
}
read_and_print_chars(&mut io::stdin(), count, args.tail);
} else {
for file in args.files {
if args.verbose {
println!("==> {} <==", file); // Print file name in verbose mode
}
let mut input = match File::open(&file) {
Ok(input) => input,
Err(err) => {
eprintln!("Error: Cannot open file named '{}'", file);
eprintln!("Error: {} ... aborting!", err);
std::process::exit(1);
},
};
read_and_print_chars(&mut input, count, args.tail);
}
}
Ok(())
}
// Residual Copilot AI code here as it works, whereas ChatGPT code only worked for tail case
fn read_and_print_chars<R: Read>(input: &mut R, num_chars: usize, tail: bool) {
let mut buffer = String::new();
input.read_to_string(&mut buffer).expect("Failed to read input");
let output = if tail {
buffer.chars().rev().take(num_chars).collect::<String>().chars().rev().collect::<String>()
} else {
buffer.chars().take(num_chars).collect()
};
println!("{}", output);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment