Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

[target.aarch64-linux-android]
linker = "aarch64-linux-android33-clang++"

[profile.release]
strip = "symbols"
lto = true
codegen-units = 1
opt-level = "s"
142 changes: 142 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: ⚙️🚀

on:
pull_request:
workflow_dispatch:
push:
branches: [master]
tags: ["*"]

concurrency:
group: ci-cd-${{ github.ref }}
cancel-in-progress: true

jobs:
code-quality:
name: 🦀 Code Quality
runs-on: ubuntu-latest
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v3

- name: 🦀 Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt

- name: ♻️ Manage Cache
uses: actions/cache@v3
with:
path: |
~/.cargo/
target/
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
cargo-${{ runner.os }}-

- name: 🎨 Check Formatting
run: cargo fmt --check --all

- name: 📎 Check Linting
run: cargo clippy --locked --all-targets --all-features -- -D warnings

- name: 🧪 Run Tests
run: cargo test --locked --all-targets --all-features

build-artifacts:
name: ⚙️ Build (${{ matrix.artifact-name }})
needs: [code-quality]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
artifact-name: vimv-x86_64-unknown-linux-gnu
cargo-target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
artifact-name: vimv-aarch64-unknown-linux-gnu
cargo-target: aarch64-unknown-linux-gnu
linker: gcc-aarch64-linux-gnu
- os: ubuntu-latest
artifact-name: vimv-aarch64-linux-android
cargo-target: aarch64-linux-android
- os: macos-latest
artifact-name: vimv-aarch64-apple-darwin
cargo-target: aarch64-apple-darwin
- os: windows-latest
artifact-name: vimv-x86_64-pc-windows-gnu
cargo-target: x86_64-pc-windows-gnu

steps:
- name: 🛎️ Checkout
uses: actions/checkout@v3

- name: 🦀 Install Rust
uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.cargo-target }}

- name: 🔗 Install Linker packages
if: matrix.linker != ''
run: |
sudo apt-get -y update
sudo apt-get -y install ${{ matrix.linker }}

- name: 🛣️ Set Linker Path
if: matrix.cargo-target == 'aarch64-linux-android'
run: echo "$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH

- name: ♻️ Manage Build Cache
uses: actions/cache@v3
with:
path: |
~/.cargo/
target/
key: cargo-${{ matrix.artifact-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ matrix.artifact-name }}-${{ hashFiles('**/Cargo.lock') }}
cargo-${{ matrix.artifact-name }}-

- name: 🛠️ Build Binary
run: cargo build --locked --release --target ${{ matrix.cargo-target }}

- name: 📁 Setup Archive + Extension
shell: bash
run: |
mkdir -p staging
if [ "${{ matrix.os }}" = "windows-latest" ]; then
cp "target/${{ matrix.cargo-target }}/release/vimv.exe" staging/
cd staging
7z a ../${{ matrix.artifact-name }}.zip *
else
cp "target/${{ matrix.cargo-target }}/release/vimv" staging/
cd staging
zip ../${{ matrix.artifact-name }}.zip *
fi

- name: ⬆️ Upload Binary Artifact
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.artifact-name }}
path: ${{ matrix.artifact-name }}.zip
retention-days: 5

release:
name: 🚀 Create Release
if: github.ref_type == 'tag'
needs: [build-artifacts]
runs-on: ubuntu-latest

steps:
- name: ⬇️ Download All Binary Artifacts
uses: actions/download-artifact@v3

- name: 🗃️ Create Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
generate_release_notes: true
files: vimv-*/*.zip
76 changes: 51 additions & 25 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use arguably::ArgParser;
use std::path::Path;
use std::process::exit;
use colored::*;
use rand::Rng;
use std::collections::HashSet;
use std::env;
use std::fs;
use std::collections::HashSet;
use rand::Rng;
use std::io::Read;
use colored::*;

use std::path::Path;
use std::process::exit;

const HELPTEXT: &str = "
Usage: vimv [files]
Expand Down Expand Up @@ -50,7 +49,6 @@ Flags:
-v, --version Print the version number and exit.
";


fn main() {
let mut parser = ArgParser::new()
.helptext(HELPTEXT)
Expand Down Expand Up @@ -88,10 +86,18 @@ fn main() {
eprintln!("error: failed to read current directory entry: {}", err);
exit(1);
});
let entry_as_string = entry.path().into_os_string().into_string().unwrap_or_else(|err| {
eprintln!("error: failed to decode current directory entry name: {:?}", err);
exit(1);
});
let entry_as_string =
entry
.path()
.into_os_string()
.into_string()
.unwrap_or_else(|err| {
eprintln!(
"error: failed to decode current directory entry name: {:?}",
err
);
exit(1);
});
input_files.push(entry_as_string);
}
input_files.sort();
Expand All @@ -101,7 +107,10 @@ fn main() {
if parser.found("stdin") {
let mut buffer = String::new();
if let Err(err) = std::io::stdin().read_to_string(&mut buffer) {
eprintln!("error: failed to read filenames from standard input: {}", err);
eprintln!(
"error: failed to read filenames from standard input: {}",
err
);
exit(1);
}
if !buffer.trim().is_empty() {
Expand All @@ -116,7 +125,7 @@ fn main() {

// Sanity check - verify that no input filename begins with '#'.
for input_file in &input_files {
if input_file.starts_with("#") {
if input_file.starts_with('#') {
eprintln!("error: input filenames cannot begin with '#'");
exit(1);
}
Expand All @@ -134,7 +143,10 @@ fn main() {
let mut input_set = HashSet::new();
for input_file in &input_files {
if input_set.contains(input_file) {
eprintln!("error: the filename '{}' appears in the input list multiple times", input_file);
eprintln!(
"error: the filename '{}' appears in the input list multiple times",
input_file
);
exit(1);
}
input_set.insert(input_file);
Expand Down Expand Up @@ -163,17 +175,24 @@ fn main() {

// Sanity check - verify that the output filenames are unique.
let mut case_sensitive_output_set = HashSet::new();
for output_file in output_files.iter().filter(|s| !s.starts_with("#")) {
for output_file in output_files.iter().filter(|s| !s.starts_with('#')) {
if case_sensitive_output_set.contains(output_file) {
eprintln!("error: the filename '{}' appears in the output list multiple times", output_file);
eprintln!(
"error: the filename '{}' appears in the output list multiple times",
output_file
);
exit(1);
}
case_sensitive_output_set.insert(output_file);
}

// Sanity check - verify that the output filenames are case-insensitively unique.
let mut case_insensitive_output_set = HashSet::new();
for output_file in output_files.iter().filter(|s| !s.starts_with("#")).map(|s| s.to_lowercase()) {
for output_file in output_files
.iter()
.filter(|s| !s.starts_with('#'))
.map(|s| s.to_lowercase())
{
if case_insensitive_output_set.contains(&output_file) {
eprintln!(
"error: the filename '{}' appears multiple times in the output list (case \
Expand Down Expand Up @@ -208,11 +227,14 @@ fn main() {
rename_set.insert(input_file.to_string());
continue;
}
eprintln!("error: cannot overwrite the existing directory '{}'", output_file);
eprintln!(
"error: cannot overwrite the existing directory '{}'",
output_file
);
exit(1);
}

if output_file.starts_with("#") {
if output_file.starts_with('#') {
delete_list.push(input_file);
continue;
}
Expand All @@ -224,7 +246,7 @@ fn main() {
continue;
}

if parser.found("force") {
if parser.found("force") {
rename_list.push((input_file.to_string(), output_file.to_string()));
rename_set.insert(input_file.to_string());
continue;
Expand Down Expand Up @@ -264,7 +286,6 @@ fn main() {
}
}


// Generate a unique temporary filename.
fn get_temp_filename(base: &str) -> String {
let mut rng = rand::thread_rng();
Expand All @@ -281,7 +302,6 @@ fn get_temp_filename(base: &str) -> String {
exit(1);
}


// Move the specified file to the system's trash/recycle bin.
fn delete_file(input_file: &str, quiet: bool) {
if !quiet {
Expand All @@ -293,7 +313,6 @@ fn delete_file(input_file: &str, quiet: bool) {
}
}


// Rename `input_file` to `output_file`.
fn move_file(input_file: &str, output_file: &str, quiet: bool) {
if !quiet {
Expand All @@ -303,13 +322,20 @@ fn move_file(input_file: &str, output_file: &str, quiet: bool) {
if let Some(parent_path) = Path::new(output_file).parent() {
if !parent_path.is_dir() {
if let Err(err) = std::fs::create_dir_all(parent_path) {
eprintln!("error: cannot create the required directory '{}': {}", parent_path.display(), err);
eprintln!(
"error: cannot create the required directory '{}': {}",
parent_path.display(),
err
);
exit(1);
}
}
}
if let Err(err) = std::fs::rename(input_file, output_file) {
eprintln!("error: cannot rename the file '{}' to '{}': {}", input_file, output_file, err);
eprintln!(
"error: cannot rename the file '{}' to '{}': {}",
input_file, output_file, err
);
exit(1);
}
}