mirror of
https://github.com/TeFiLeDo/listload.git
synced 2024-11-01 01:56:17 +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"] }
|
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"
|
||||||
|
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 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -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())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user