mirror of
https://github.com/TeFiLeDo/listload.git
synced 2024-10-31 17:46:16 +01:00
target creation and selection
This commit is contained in:
parent
74cbd9d6f8
commit
d10022fec8
@ -11,6 +11,7 @@ anyhow = "1.0.71"
|
||||
clap = { version = "4.3.8", features = ["cargo", "derive", "env", "wrap_help"] }
|
||||
directories = "5.0.1"
|
||||
downloader = { version = "0.2.7", default-features = false, features = ["rustls-tls"] }
|
||||
indoc = "2.0.1"
|
||||
regex = "1.8.4"
|
||||
serde = { version = "1.0.164", features = ["derive"] }
|
||||
serde_json = "1.0.99"
|
||||
|
91
src/main.rs
91
src/main.rs
@ -1,11 +1,13 @@
|
||||
use std::sync::OnceLock;
|
||||
use std::{path::PathBuf, sync::OnceLock};
|
||||
|
||||
use anyhow::{ensure, Context};
|
||||
use clap::{Parser, Subcommand};
|
||||
use config::Config;
|
||||
use directories::{ProjectDirs, UserDirs};
|
||||
use persistent_state::PersistentState;
|
||||
use target::Target;
|
||||
use target_list::TargetList;
|
||||
use url::Url;
|
||||
|
||||
mod config;
|
||||
mod persistent_state;
|
||||
@ -50,13 +52,12 @@ fn main() -> anyhow::Result<()> {
|
||||
CMD::PersistentState => {
|
||||
println!("{persistent}");
|
||||
}
|
||||
CMD::List { cmd } => 'list: {
|
||||
if let ListCommand::Create {
|
||||
CMD::List { cmd } => match cmd {
|
||||
ListCommand::Create {
|
||||
name,
|
||||
keep_current_active,
|
||||
keep_current_selected: keep_current_active,
|
||||
comment,
|
||||
} = cmd
|
||||
{
|
||||
} => {
|
||||
if TargetList::exists(&name) {
|
||||
eprintln!("list already exists");
|
||||
} else {
|
||||
@ -67,30 +68,42 @@ fn main() -> anyhow::Result<()> {
|
||||
if !keep_current_active {
|
||||
persistent.set_list(&name);
|
||||
}
|
||||
|
||||
break 'list;
|
||||
} else if let ListCommand::Select { name } = cmd {
|
||||
}
|
||||
ListCommand::Select { name } => {
|
||||
if name == "none" {
|
||||
persistent.clear_list();
|
||||
} else if !TargetList::exists(&name) {
|
||||
eprintln!("list doesn't exist");
|
||||
} else {
|
||||
} else if TargetList::exists(&name) {
|
||||
persistent.set_list(&name);
|
||||
} else {
|
||||
eprintln!("list doesn't exist");
|
||||
}
|
||||
break 'list;
|
||||
}
|
||||
|
||||
},
|
||||
CMD::Target { cmd } => {
|
||||
let list = persistent.list().context("no list selected")?;
|
||||
let mut list = TargetList::load(&list).context("failed to load list")?;
|
||||
|
||||
match cmd {
|
||||
ListCommand::Create {
|
||||
name: _,
|
||||
keep_current_active: _,
|
||||
comment: _,
|
||||
TargetCommand::Create {
|
||||
file,
|
||||
url,
|
||||
comment,
|
||||
keep_current_selected,
|
||||
} => {
|
||||
let target = Target::new(url, &file, comment.as_ref().map(|c| c.as_str()))
|
||||
.context("invalid target")?;
|
||||
list.add_target(target);
|
||||
|
||||
if !keep_current_selected {
|
||||
persistent.set_target(list.len_targets() - 1);
|
||||
}
|
||||
}
|
||||
| ListCommand::Select { name: _ } => {
|
||||
panic!("late list command");
|
||||
TargetCommand::Select { index } => {
|
||||
if index < list.len_targets() {
|
||||
persistent.set_target(index);
|
||||
} else {
|
||||
eprintln!("target doesn't exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +137,11 @@ enum CMD {
|
||||
#[clap(subcommand)]
|
||||
cmd: ListCommand,
|
||||
},
|
||||
/// Individual target operations.
|
||||
Target {
|
||||
#[clap(subcommand)]
|
||||
cmd: TargetCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@ -140,14 +158,16 @@ enum ListCommand {
|
||||
///
|
||||
/// Invalid examples: none, 14, _hi, hi_, h__i
|
||||
name: String,
|
||||
/// Don't activate the newly created list.
|
||||
#[clap(long, short)]
|
||||
keep_current_active: bool,
|
||||
/// A comment to remember what a list is for.
|
||||
/// A comment to remember what the list is meant to do.
|
||||
#[clap(long, short)]
|
||||
comment: Option<String>,
|
||||
/// Don't select the newly created list.
|
||||
#[clap(long, short)]
|
||||
keep_current_selected: bool,
|
||||
},
|
||||
/// Select an existing list.
|
||||
///
|
||||
/// List selection is important for the `target` subcommand.
|
||||
Select {
|
||||
/// The name of the list.
|
||||
///
|
||||
@ -156,3 +176,26 @@ enum ListCommand {
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum TargetCommand {
|
||||
/// Create a new target.
|
||||
Create {
|
||||
/// The local file name.
|
||||
file: PathBuf,
|
||||
/// A list of URLs the file is available at.
|
||||
url: Vec<Url>,
|
||||
/// A comment to remember why the target is in the list.
|
||||
#[clap(long, short)]
|
||||
comment: Option<String>,
|
||||
/// Don't select the newly created target.
|
||||
#[clap(long, short)]
|
||||
keep_current_selected: bool,
|
||||
},
|
||||
/// Select an existing target.
|
||||
/// Target selection is important for the `url` subcommand.
|
||||
Select {
|
||||
/// The index of the target.
|
||||
index: usize,
|
||||
},
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use indoc::writedoc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{target_list::TargetList, PROJ_DIRS};
|
||||
@ -16,6 +17,7 @@ const PERSISTENT_FILE: &str = "persistent_state.json";
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
pub struct PersistentState {
|
||||
list: Option<String>,
|
||||
target: Option<usize>,
|
||||
}
|
||||
|
||||
impl PersistentState {
|
||||
@ -25,10 +27,22 @@ impl PersistentState {
|
||||
|
||||
pub fn set_list(&mut self, list: &str) {
|
||||
self.list = Some(list.to_string());
|
||||
self.target = None;
|
||||
}
|
||||
|
||||
pub fn clear_list(&mut self) {
|
||||
self.list = None;
|
||||
self.target = None;
|
||||
}
|
||||
|
||||
pub fn target(&self) -> Option<usize> {
|
||||
self.target
|
||||
}
|
||||
|
||||
pub fn set_target(&mut self, index: usize) {
|
||||
if self.list.is_some() {
|
||||
self.target = Some(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,10 +109,15 @@ impl PersistentState {
|
||||
|
||||
impl Display for PersistentState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
writedoc!(
|
||||
f,
|
||||
"current list: {}",
|
||||
self.list.as_ref().map(AsRef::as_ref).unwrap_or("none")
|
||||
"
|
||||
current list: {}
|
||||
current target: {}",
|
||||
self.list.as_ref().map(AsRef::as_ref).unwrap_or("none"),
|
||||
self.target
|
||||
.map(|t| t.to_string())
|
||||
.unwrap_or("none".to_string())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,11 @@ impl Target {
|
||||
"file must be file or nonexistent"
|
||||
);
|
||||
|
||||
for url in &urls {
|
||||
let scheme = url.scheme();
|
||||
ensure!(scheme == "http" || scheme == "https", "url is not http(s)");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
urls,
|
||||
file: file.to_path_buf(),
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
fs::{create_dir_all, File, OpenOptions},
|
||||
io::{BufReader, BufWriter},
|
||||
io::{BufReader, BufWriter, Seek, SeekFrom},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
@ -20,6 +20,14 @@ impl TargetList {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.inner.name
|
||||
}
|
||||
|
||||
pub fn add_target(&mut self, target: Target) {
|
||||
self.inner.targets.push(target);
|
||||
}
|
||||
|
||||
pub fn len_targets(&self) -> usize {
|
||||
self.inner.targets.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl TargetList {
|
||||
@ -115,7 +123,13 @@ impl TargetList {
|
||||
}
|
||||
|
||||
pub fn save(&mut self) -> anyhow::Result<()> {
|
||||
self.file.set_len(0);
|
||||
let _ = self
|
||||
.file
|
||||
.set_len(0)
|
||||
.context("failed to clear target list file");
|
||||
self.file
|
||||
.seek(SeekFrom::Start(0))
|
||||
.context("failed to rewind target list file")?;
|
||||
serde_json::to_writer(BufWriter::new(&self.file), &self.inner)
|
||||
.context("failed to save target list")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user