Skip to content

Commit

Permalink
fix(cli): prevent truncation of components if basename contains dots
Browse files Browse the repository at this point in the history
Resolves: #907

This rolls an in-house version of `add_extension()` function which always adds an extension to a `PathBuf`.  This is different from `PathBuf::with_extension()` which may replace or add extension depending on what the path is.

This solves a problem with basenames containing dots, as described in the issue: `PathBuf::with_extension()` thought that they are extensions and replaced the last one. But we always want to add, not replace.
  • Loading branch information
ivan-aksamentov committed Jun 29, 2022
1 parent e9df843 commit d0d550a
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 18 deletions.
13 changes: 7 additions & 6 deletions packages_rs/nextclade-cli/src/cli/nextalign_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use eyre::{eyre, ContextCompat, Report, WrapErr};
use itertools::Itertools;
use lazy_static::lazy_static;
use nextclade::align::params::AlignPairwiseParamsOptional;
use nextclade::io::fs::add_extension;
use nextclade::make_error;
use nextclade::utils::global_init::setup_logger;
use std::fmt::Debug;
Expand Down Expand Up @@ -346,23 +347,23 @@ pub fn nextalign_get_output_filenames(run_args: &mut NextalignRunArgs) -> Result
// to set default output filenames only if they are not provided.

if output_selection.contains(&NextalignOutputSelection::Fasta) {
output_fasta.get_or_insert(default_output_file_path.with_extension("aligned.fasta"));
output_fasta.get_or_insert(add_extension(&default_output_file_path, "aligned.fasta"));
}

if output_selection.contains(&NextalignOutputSelection::Insertions) {
let output_insertions =
output_insertions.get_or_insert(default_output_file_path.with_extension("insertions.csv"));
output_insertions.get_or_insert(add_extension(&default_output_file_path, "insertions.csv"));
}

if output_selection.contains(&NextalignOutputSelection::Errors) {
let output_errors = output_errors.get_or_insert(default_output_file_path.with_extension("errors.csv"));
let output_errors = output_errors.get_or_insert(add_extension(&default_output_file_path, "errors.csv"));
}

if output_selection.contains(&NextalignOutputSelection::Translations) {
let output_translations = {
let output_translations_path = default_output_file_path
.with_file_name(format!("{output_basename}_gene_{{gene}}"))
.with_extension("translation.fasta");
let output_translations_path =
default_output_file_path.with_file_name(format!("{output_basename}_gene_{{gene}}"));
let output_translations_path = add_extension(&output_translations_path, "translation.fasta");

let output_translations_template = output_translations_path
.to_str()
Expand Down
23 changes: 12 additions & 11 deletions packages_rs/nextclade-cli/src/cli/nextclade_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use eyre::{eyre, ContextCompat, Report, WrapErr};
use itertools::Itertools;
use lazy_static::lazy_static;
use nextclade::align::params::AlignPairwiseParamsOptional;
use nextclade::io::fs::add_extension;
use nextclade::utils::global_init::setup_logger;
use nextclade::{getenv, make_error};
use std::fmt::Debug;
Expand Down Expand Up @@ -628,21 +629,21 @@ pub fn nextclade_get_output_filenames(run_args: &mut NextcladeRunArgs) -> Result
// to set default output filenames only if they are not provided.

if output_selection.contains(&NextcladeOutputSelection::Fasta) {
output_fasta.get_or_insert(default_output_file_path.with_extension("aligned.fasta"));
output_fasta.get_or_insert(add_extension(&default_output_file_path, "aligned.fasta"));
}

if output_selection.contains(&NextcladeOutputSelection::Insertions) {
output_insertions.get_or_insert(default_output_file_path.with_extension("insertions.csv"));
output_insertions.get_or_insert(add_extension(&default_output_file_path, "insertions.csv"));
}

if output_selection.contains(&NextcladeOutputSelection::Errors) {
output_errors.get_or_insert(default_output_file_path.with_extension("errors.csv"));
output_errors.get_or_insert(add_extension(&default_output_file_path, "errors.csv"));
}

if output_selection.contains(&NextcladeOutputSelection::Translations) {
let output_translations_path = default_output_file_path
.with_file_name(format!("{output_basename}_gene_{{gene}}"))
.with_extension("translation.fasta");
let output_translations_path =
default_output_file_path.with_file_name(format!("{output_basename}_gene_{{gene}}"));
let output_translations_path = add_extension(&output_translations_path, "translation.fasta");

let output_translations_template = output_translations_path
.to_str()
Expand All @@ -653,23 +654,23 @@ pub fn nextclade_get_output_filenames(run_args: &mut NextcladeRunArgs) -> Result
}

if output_selection.contains(&NextcladeOutputSelection::Ndjson) {
output_ndjson.get_or_insert(default_output_file_path.with_extension("ndjson"));
output_ndjson.get_or_insert(add_extension(&default_output_file_path, "ndjson"));
}

if output_selection.contains(&NextcladeOutputSelection::Json) {
output_json.get_or_insert(default_output_file_path.with_extension("json"));
output_json.get_or_insert(add_extension(&default_output_file_path, "json"));
}

if output_selection.contains(&NextcladeOutputSelection::Csv) {
output_csv.get_or_insert(default_output_file_path.with_extension("csv"));
output_csv.get_or_insert(add_extension(&default_output_file_path, "csv"));
}

if output_selection.contains(&NextcladeOutputSelection::Tsv) {
output_tsv.get_or_insert(default_output_file_path.with_extension("tsv"));
output_tsv.get_or_insert(add_extension(&default_output_file_path, "tsv"));
}

if output_selection.contains(&NextcladeOutputSelection::Tree) {
output_tree.get_or_insert(default_output_file_path.with_extension("auspice.json"));
output_tree.get_or_insert(add_extension(&default_output_file_path, "auspice.json"));
}
}

Expand Down
23 changes: 22 additions & 1 deletion packages_rs/nextclade/src/io/fs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use eyre::{eyre, Report, WrapErr};
use std::ffi::OsStr;
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -48,6 +48,27 @@ pub fn has_extension(filepath: impl AsRef<Path>, ext: impl AsRef<str>) -> bool {
extension(filepath.as_ref()).map_or(false, |fext| fext.eq_ignore_ascii_case(ext.as_ref()))
}

pub fn add_extension(filepath: impl AsRef<Path>, extension: impl AsRef<OsStr>) -> PathBuf {
let filepath = filepath.as_ref();
let extension = extension.as_ref();

if filepath.file_name().is_none() {
return filepath.to_owned();
}

let mut stem = match filepath.file_name() {
Some(stem) => stem.to_os_string(),
None => OsString::new(),
};

if !extension.is_empty() {
stem.push(".");
stem.push(extension);
}

filepath.to_owned().with_file_name(&stem)
}

/// Reads entire file into a string.
/// Compared to `std::fs::read_to_string` uses buffered reader
pub fn read_file_to_string(filepath: impl AsRef<Path>) -> Result<String, Report> {
Expand Down

0 comments on commit d0d550a

Please sign in to comment.