0
0
mirror of https://github.com/TeFiLeDo/listload.git synced 2024-11-23 20:56:17 +01:00

target creation and selection

This commit is contained in:
Adrian Wannenmacher 2023-06-27 21:52:10 +02:00
parent 74cbd9d6f8
commit d10022fec8
Signed by: tfld
GPG Key ID: 19D986ECB1E492D5
5 changed files with 111 additions and 29 deletions

View File

@ -11,6 +11,7 @@ anyhow = "1.0.71"
clap = { version = "4.3.8", features = ["cargo", "derive", "env", "wrap_help"] } clap = { version = "4.3.8", features = ["cargo", "derive", "env", "wrap_help"] }
directories = "5.0.1" directories = "5.0.1"
downloader = { version = "0.2.7", default-features = false, features = ["rustls-tls"] } downloader = { version = "0.2.7", default-features = false, features = ["rustls-tls"] }
indoc = "2.0.1"
regex = "1.8.4" regex = "1.8.4"
serde = { version = "1.0.164", features = ["derive"] } serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.99" serde_json = "1.0.99"

View File

@ -1,11 +1,13 @@
use std::sync::OnceLock; use std::{path::PathBuf, sync::OnceLock};
use anyhow::{ensure, Context}; use anyhow::{ensure, Context};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use config::Config; use config::Config;
use directories::{ProjectDirs, UserDirs}; use directories::{ProjectDirs, UserDirs};
use persistent_state::PersistentState; use persistent_state::PersistentState;
use target::Target;
use target_list::TargetList; use target_list::TargetList;
use url::Url;
mod config; mod config;
mod persistent_state; mod persistent_state;
@ -50,13 +52,12 @@ fn main() -> anyhow::Result<()> {
CMD::PersistentState => { CMD::PersistentState => {
println!("{persistent}"); println!("{persistent}");
} }
CMD::List { cmd } => 'list: { CMD::List { cmd } => match cmd {
if let ListCommand::Create { ListCommand::Create {
name, name,
keep_current_active, keep_current_selected: keep_current_active,
comment, comment,
} = cmd } => {
{
if TargetList::exists(&name) { if TargetList::exists(&name) {
eprintln!("list already exists"); eprintln!("list already exists");
} else { } else {
@ -67,30 +68,42 @@ fn main() -> anyhow::Result<()> {
if !keep_current_active { if !keep_current_active {
persistent.set_list(&name); persistent.set_list(&name);
} }
}
break 'list; ListCommand::Select { name } => {
} else if let ListCommand::Select { name } = cmd {
if name == "none" { if name == "none" {
persistent.clear_list(); persistent.clear_list();
} else if !TargetList::exists(&name) { } else if TargetList::exists(&name) {
eprintln!("list doesn't exist");
} else {
persistent.set_list(&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 list = persistent.list().context("no list selected")?;
let mut list = TargetList::load(&list).context("failed to load list")?; let mut list = TargetList::load(&list).context("failed to load list")?;
match cmd { match cmd {
ListCommand::Create { TargetCommand::Create {
name: _, file,
keep_current_active: _, url,
comment: _, 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);
}
}
TargetCommand::Select { index } => {
if index < list.len_targets() {
persistent.set_target(index);
} else {
eprintln!("target doesn't exist");
} }
| ListCommand::Select { name: _ } => {
panic!("late list command");
} }
} }
@ -124,6 +137,11 @@ enum CMD {
#[clap(subcommand)] #[clap(subcommand)]
cmd: ListCommand, cmd: ListCommand,
}, },
/// Individual target operations.
Target {
#[clap(subcommand)]
cmd: TargetCommand,
},
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -140,14 +158,16 @@ enum ListCommand {
/// ///
/// Invalid examples: none, 14, _hi, hi_, h__i /// Invalid examples: none, 14, _hi, hi_, h__i
name: String, name: String,
/// Don't activate the newly created list. /// A comment to remember what the list is meant to do.
#[clap(long, short)]
keep_current_active: bool,
/// A comment to remember what a list is for.
#[clap(long, short)] #[clap(long, short)]
comment: Option<String>, comment: Option<String>,
/// Don't select the newly created list.
#[clap(long, short)]
keep_current_selected: bool,
}, },
/// Select an existing list. /// Select an existing list.
///
/// List selection is important for the `target` subcommand.
Select { Select {
/// The name of the list. /// The name of the list.
/// ///
@ -156,3 +176,26 @@ enum ListCommand {
name: String, 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,
},
}

View File

@ -6,6 +6,7 @@ use std::{
}; };
use anyhow::{bail, ensure, Context}; use anyhow::{bail, ensure, Context};
use indoc::writedoc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{target_list::TargetList, PROJ_DIRS}; use crate::{target_list::TargetList, PROJ_DIRS};
@ -16,6 +17,7 @@ const PERSISTENT_FILE: &str = "persistent_state.json";
#[serde(deny_unknown_fields, default)] #[serde(deny_unknown_fields, default)]
pub struct PersistentState { pub struct PersistentState {
list: Option<String>, list: Option<String>,
target: Option<usize>,
} }
impl PersistentState { impl PersistentState {
@ -25,10 +27,22 @@ impl PersistentState {
pub fn set_list(&mut self, list: &str) { pub fn set_list(&mut self, list: &str) {
self.list = Some(list.to_string()); self.list = Some(list.to_string());
self.target = None;
} }
pub fn clear_list(&mut self) { pub fn clear_list(&mut self) {
self.list = None; 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 { impl Display for PersistentState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( writedoc!(
f, 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())
) )
} }
} }

View File

@ -23,6 +23,11 @@ impl Target {
"file must be file or nonexistent" "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 { Ok(Self {
urls, urls,
file: file.to_path_buf(), file: file.to_path_buf(),

View File

@ -1,6 +1,6 @@
use std::{ use std::{
fs::{create_dir_all, File, OpenOptions}, fs::{create_dir_all, File, OpenOptions},
io::{BufReader, BufWriter}, io::{BufReader, BufWriter, Seek, SeekFrom},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -20,6 +20,14 @@ impl TargetList {
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.inner.name &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 { impl TargetList {
@ -115,7 +123,13 @@ impl TargetList {
} }
pub fn save(&mut self) -> anyhow::Result<()> { 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) serde_json::to_writer(BufWriter::new(&self.file), &self.inner)
.context("failed to save target list") .context("failed to save target list")
} }