Skip to content

Commit 33b012d

Browse files
author
zer0yu
committed
Add unique mode and multi-file support to enhance file processing capabilities.
1 parent 60a7159 commit 33b012d

File tree

3 files changed

+167
-33
lines changed

3 files changed

+167
-33
lines changed

.github/workflows/release.yml

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Create release artifacts
33
on:
44
push:
55
tags:
6-
- 'v[0-9]+.[0-9]+.[0-9]'
6+
- 'v[0-9]+.[0-9]+.[0-9]+'
77
workflow_dispatch:
88

99
permissions:
@@ -15,56 +15,80 @@ jobs:
1515
runs-on: ${{ matrix.os }}
1616

1717
strategy:
18+
fail-fast: false
1819
matrix:
19-
# You can add more, for any target you'd like!
2020
include:
21-
- build: linux
21+
# Linux targets
22+
- build: linux-amd64
2223
os: ubuntu-latest
2324
target: x86_64-unknown-linux-musl
24-
- build: macos
25+
- build: linux-arm64
26+
os: ubuntu-latest
27+
target: aarch64-unknown-linux-musl
28+
- build: linux-arm
29+
os: ubuntu-latest
30+
target: armv7-unknown-linux-musleabihf
31+
32+
# macOS targets
33+
- build: macos-amd64
2534
os: macos-latest
2635
target: x86_64-apple-darwin
27-
- build: windows
36+
- build: macos-arm64
37+
os: macos-latest
38+
target: aarch64-apple-darwin
39+
40+
# Windows targets
41+
- build: windows-amd64
2842
os: windows-latest
2943
target: x86_64-pc-windows-msvc
44+
- build: windows-arm64
45+
os: windows-latest
46+
target: aarch64-pc-windows-msvc
3047

3148
steps:
32-
- name: Checkout
33-
uses: actions/checkout@v3
49+
- name: Checkout repository
50+
uses: actions/checkout@v4
3451

35-
- name: Get the release version from the tag
52+
- name: Get version from tag
3653
shell: bash
3754
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
3855

39-
- name: Install Rust
40-
# Or @nightly if you want
56+
- name: Install Rust toolchain
4157
uses: dtolnay/rust-toolchain@stable
42-
# Arguments to pass in
4358
with:
44-
# Make Rust compile to our target (defined in the matrix)
4559
targets: ${{ matrix.target }}
4660

47-
- name: Build
61+
- name: Install cross-compilation tools
62+
if: runner.os == 'Linux'
63+
run: |
64+
sudo apt-get update
65+
sudo apt-get install -y musl-tools
66+
67+
- name: Build binary
4868
uses: actions-rs/cargo@v1
4969
with:
5070
use-cross: true
5171
command: build
5272
args: --verbose --release --target ${{ matrix.target }}
5373

54-
- name: Build archive
74+
- name: Prepare release archive
5575
shell: bash
5676
run: |
57-
# Replace with the name of your binary
5877
binary_name="anew"
59-
60-
dirname="$binary_name-${{ env.VERSION }}-${{ matrix.target }}"
78+
dirname="$binary_name-${{ env.VERSION }}-${{ matrix.build }}"
6179
mkdir "$dirname"
80+
81+
# Copy binary
6282
if [ "${{ matrix.os }}" = "windows-latest" ]; then
63-
mv "target/${{ matrix.target }}/release/$binary_name.exe" "$dirname"
83+
mv "target/${{ matrix.target }}/release/$binary_name.exe" "$dirname/"
6484
else
65-
mv "target/${{ matrix.target }}/release/$binary_name" "$dirname"
85+
mv "target/${{ matrix.target }}/release/$binary_name" "$dirname/"
6686
fi
67-
87+
88+
# Copy documentation
89+
cp README.md LICENSE "$dirname/"
90+
91+
# Create archive
6892
if [ "${{ matrix.os }}" = "windows-latest" ]; then
6993
7z a "$dirname.zip" "$dirname"
7094
echo "ASSET=$dirname.zip" >> $GITHUB_ENV
@@ -73,8 +97,9 @@ jobs:
7397
echo "ASSET=$dirname.tar.gz" >> $GITHUB_ENV
7498
fi
7599
76-
- name: Release
100+
- name: Upload release artifacts
77101
uses: softprops/action-gh-release@v1
102+
if: startsWith(github.ref, 'refs/tags/')
78103
with:
79-
files: |
80-
${{ env.ASSET }}
104+
files: ${{ env.ASSET }}
105+
generate_release_notes: true

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,33 @@ Five
113113
Four
114114
```
115115

116+
Usage 5: Merge multiple files and remove duplicates (like `sort -u`)
117+
```
118+
❯ cat things.txt
119+
One
120+
Zero
121+
Two
122+
123+
❯ cat newthings.txt
124+
Three
125+
One
126+
Five
127+
128+
❯ cat morethings.txt
129+
Four
130+
One
131+
Two
132+
133+
❯ cat *.txt | anew -u
134+
Five
135+
Four
136+
One
137+
Three
138+
Two
139+
Zero
140+
```
141+
Note: The `-u` option automatically sorts the output and removes duplicates from all input files.
142+
116143
## Efficiency Comparison
117144

118145
We use two files `newoutput.txt` and `output.txt` of size 10MB as input to the program to compare the difference in speed between tomnomnom's Go implementation, rwese's Rust implementation, and this project's Rust implementation.

src/main.rs

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,81 @@ struct Options {
3333
)]
3434
dry_run: bool,
3535

36-
#[arg(help = "Destination file")]
37-
filepath: String,
36+
#[arg(
37+
short = 'u',
38+
long = "unique",
39+
help = "Merge all input files, sort and remove duplicates (like 'sort -u')"
40+
)]
41+
unique_mode: bool,
42+
43+
#[arg(help = "Destination file", required_unless_present = "unique_mode")]
44+
filepath: Option<String>,
45+
46+
#[arg(help = "Additional input files for unique mode", num_args = 0..)]
47+
additional_files: Vec<String>,
3848
}
3949

4050
fn main() -> io::Result<()> {
4151
let args = Options::parse();
4252

53+
if args.unique_mode {
54+
// Handle unique mode (sort -u equivalent)
55+
let mut all_lines = IndexSet::new();
56+
57+
// Process files if provided
58+
if let Some(filepath) = &args.filepath {
59+
if let Ok(lines) = load_file_content(filepath) {
60+
for line in lines {
61+
if should_add_line(&args, &all_lines, &line) {
62+
all_lines.insert(line);
63+
}
64+
}
65+
}
66+
}
67+
68+
// Process additional files
69+
for file in &args.additional_files {
70+
if let Ok(lines) = load_file_content(file) {
71+
for line in lines {
72+
if should_add_line(&args, &all_lines, &line) {
73+
all_lines.insert(line);
74+
}
75+
}
76+
}
77+
}
78+
79+
// Process stdin
80+
let stdin = io::stdin();
81+
let mut handle = stdin.lock();
82+
let mut buffer = String::new();
83+
84+
while handle.read_line(&mut buffer)? > 0 {
85+
let line = buffer.trim_end().to_string();
86+
if should_add_line(&args, &all_lines, &line) {
87+
all_lines.insert(line);
88+
}
89+
buffer.clear();
90+
}
91+
92+
// Sort if requested
93+
let mut final_lines: Vec<_> = all_lines.into_iter().collect();
94+
if args.sort {
95+
final_lines.sort_by(|a, b| natsort::compare(a, b, false));
96+
}
97+
98+
// Output results
99+
for line in final_lines {
100+
println!("{}", line);
101+
}
102+
103+
return Ok(());
104+
}
105+
106+
// Original functionality for non-unique mode
107+
let filepath = args.filepath.as_ref().expect("Destination file is required in non-unique mode");
108+
43109
// Ensure the directories in the filepath exist before attempting to open the file
44-
if let Some(parent) = Path::new(&args.filepath).parent() {
110+
if let Some(parent) = Path::new(filepath).parent() {
45111
fs::create_dir_all(parent)?;
46112
}
47113

@@ -52,7 +118,7 @@ fn main() -> io::Result<()> {
52118
.write(true)
53119
.create(true)
54120
.truncate(true)
55-
.open(&args.filepath)?;
121+
.open(filepath)?;
56122
let mut writer = BufWriter::new(file);
57123

58124
for line in lines.iter() {
@@ -65,7 +131,7 @@ fn main() -> io::Result<()> {
65131
.append(true)
66132
.write(true)
67133
.create(true)
68-
.open(&args.filepath)?;
134+
.open(filepath)?;
69135
let mut writer = BufWriter::new(file);
70136

71137
for stdin_line in stdin.lock().lines() {
@@ -88,23 +154,24 @@ fn main() -> io::Result<()> {
88154
let mut sorted_lines: Vec<_> = lines.into_iter().collect();
89155
sorted_lines.sort_by(|a, b| natsort::compare(a, b, false));
90156

91-
let soet_file = OpenOptions::new()
157+
let sort_file = OpenOptions::new()
92158
.write(true)
93159
.create(true)
94160
.truncate(true)
95-
.open(&args.filepath)?;
96-
let mut soet_writer = BufWriter::new(soet_file);
161+
.open(filepath)?;
162+
let mut sort_writer = BufWriter::new(sort_file);
97163

98164
for line in sorted_lines.iter() {
99-
writeln!(soet_writer, "{}", line)?;
165+
writeln!(sort_writer, "{}", line)?;
100166
}
101167
}
102168

103169
Ok(())
104170
}
105171

106172
fn load_file(args: &Options) -> Result<IndexSet<String>, io::Error> {
107-
match File::open(&args.filepath) {
173+
let filepath = args.filepath.as_ref().expect("Destination file is required");
174+
match File::open(filepath) {
108175
Ok(file) => {
109176
let reader = BufReader::new(file);
110177
let mut lines = IndexSet::new();
@@ -126,3 +193,18 @@ fn should_add_line(args: &Options, lines: &IndexSet<String>, line: &str) -> bool
126193
let trimmed_line = if args.trim { line.trim() } else { line };
127194
!trimmed_line.is_empty() && !lines.contains(trimmed_line)
128195
}
196+
197+
// New helper function to load file content
198+
fn load_file_content(filepath: &str) -> io::Result<Vec<String>> {
199+
match File::open(filepath) {
200+
Ok(file) => {
201+
let reader = BufReader::new(file);
202+
let mut lines = Vec::new();
203+
for line in reader.lines() {
204+
lines.push(line?);
205+
}
206+
Ok(lines)
207+
}
208+
Err(_) => Ok(Vec::new()),
209+
}
210+
}

0 commit comments

Comments
 (0)