feat: upgrade wfassoc_exec
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -762,7 +762,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wfassoc_dylib"
|
||||
name = "wfassoc_cdylib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
@@ -779,7 +779,6 @@ dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml 0.9.8",
|
||||
"wfassoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["wfassoc", "wfassoc_dylib", "wfassoc_exec"]
|
||||
members = ["wfassoc", "wfassoc_cdylib", "wfassoc_exec"]
|
||||
|
||||
[workspace.dependencies]
|
||||
thiserror = "2.0.12"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "wfassoc_dylib"
|
||||
name = "wfassoc_cdylib"
|
||||
version = "0.1.0"
|
||||
authors = ["yyc12345"]
|
||||
edition = "2024"
|
||||
@@ -8,7 +8,7 @@ license = "SPDX:MIT"
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
wfassoc = { path="../wfassoc" }
|
||||
#wfassoc = { path="../wfassoc" }
|
||||
clap = { version="4.5.48", features=["derive"]}
|
||||
serde = { version = "1.0.228", features=["derive"]}
|
||||
toml = "0.9.8"
|
||||
|
||||
@@ -1,48 +1,39 @@
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
use wfassoc::Scope;
|
||||
|
||||
#[derive(Debug, Clone, ValueEnum)]
|
||||
pub enum Target {
|
||||
// region: Clap Declaration
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ValueEnum)]
|
||||
enum Target {
|
||||
#[value(name = "user")]
|
||||
User,
|
||||
#[value(name = "system")]
|
||||
System,
|
||||
}
|
||||
|
||||
impl From<Target> for Scope {
|
||||
fn from(target: Target) -> Self {
|
||||
match target {
|
||||
Target::User => Scope::User,
|
||||
Target::System => Scope::System,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple program to manage Windows file associations
|
||||
#[derive(Parser)]
|
||||
#[command(name = "Windows File Association Operator", version, about)]
|
||||
pub struct Cli {
|
||||
/// The TOML file representing the complete program
|
||||
struct Cli {
|
||||
/// The TOML manifest file representing the complete program
|
||||
#[arg(
|
||||
short = 'c',
|
||||
long = "config",
|
||||
value_name = "PROG_CONFIG",
|
||||
short = 'm',
|
||||
long = "manifest",
|
||||
value_name = "PROG_MANIFEST",
|
||||
required = true
|
||||
)]
|
||||
pub(crate) config_file: String,
|
||||
|
||||
manifest_file: String,
|
||||
#[command(subcommand)]
|
||||
pub(crate) command: Commands,
|
||||
command: CliCommands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
enum CliCommands {
|
||||
/// Register the program
|
||||
#[command(name = "register")]
|
||||
#[command(about = "Register application with given manifest and scope.")]
|
||||
Register {
|
||||
/// The scope where wfassoc operate
|
||||
#[arg(short = 't', long = "target", value_name = "TARGET", value_enum, default_value_t = Target::User)]
|
||||
#[arg(short = 't', long = "target", value_name = "TARGET", required = true, value_enum, default_value_t = Target::User)]
|
||||
target: Target,
|
||||
},
|
||||
/// Unregister the program
|
||||
@@ -50,11 +41,76 @@ pub enum Commands {
|
||||
#[command(about = "Unregister application with given manifest and scope.")]
|
||||
Unregister {
|
||||
/// The scope where wfassoc operate
|
||||
#[arg(short = 't', long = "target", value_name = "TARGET", value_enum, default_value_t = Target::User)]
|
||||
#[arg(short = 't', long = "target", value_name = "TARGET", required = true, value_enum, default_value_t = Target::User)]
|
||||
target: Target,
|
||||
},
|
||||
/// Query file associations
|
||||
#[command(name = "query")]
|
||||
#[command(about = "Query file extensions association infos according toh given manifest represented application.")]
|
||||
Query,
|
||||
/// Fetch current registration status
|
||||
#[command(name = "status")]
|
||||
#[command(
|
||||
about = "Fetch the status of registration for given manifest represented application."
|
||||
)]
|
||||
Status,
|
||||
#[command(name = "ext")]
|
||||
#[command(about = "File extension related operations according to given program manifest.")]
|
||||
Ext {
|
||||
#[command(subcommand)]
|
||||
command: CliExtCommands,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum CliExtCommands {
|
||||
#[command(name = "link")]
|
||||
#[command(about = "Link user given file extension to the program declared in manifest file.")]
|
||||
Link {
|
||||
// The file extensions used for this operation. Specify * for all file extension.
|
||||
#[arg(required = true, value_name = "EXTS", num_args = 1..)]
|
||||
exts: Vec<String>,
|
||||
},
|
||||
#[command(name = "unlink")]
|
||||
#[command(
|
||||
about = "Unlink user given file extension from the program declared in manifest file."
|
||||
)]
|
||||
Unlink {
|
||||
// The file extensions used for this operation. Specify * for all file extension.
|
||||
#[arg(required = true, value_name = "EXTS", num_args = 1..)]
|
||||
exts: Vec<String>,
|
||||
},
|
||||
#[command(name = "list")]
|
||||
#[command(
|
||||
about = "List the association status for all extensions declared in given program manifest."
|
||||
)]
|
||||
List,
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Exposed Type
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request {
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RequestCommand {
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RequestExtCommand {
|
||||
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
///
|
||||
///
|
||||
/// Please note that if there is "help" or "version" command matched,
|
||||
/// or any command line parse error occurs,
|
||||
/// this function will order program exit immediately.
|
||||
/// This is the mechanism of clap crate.
|
||||
pub fn parse() -> Request {
|
||||
let cli = Cli::parse();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,161 +1,33 @@
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod manifest;
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod runner;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands, Target};
|
||||
use comfy_table::Table;
|
||||
use manifest::Manifest;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use thiserror::Error as TeError;
|
||||
use wfassoc::{Program, Scope, Token, View};
|
||||
|
||||
// region: Basic Types
|
||||
|
||||
/// All errors occurs in this executable.
|
||||
#[derive(TeError, Debug)]
|
||||
enum Error {
|
||||
/// Error from wfassoc core.
|
||||
#[error("{0}")]
|
||||
Core(#[from] wfassoc::Error),
|
||||
/// Error when parsing manifest TOML file.
|
||||
#[error("invalid manifest file: {0}")]
|
||||
#[error("{0}")]
|
||||
Manifest(#[from] manifest::Error),
|
||||
|
||||
/// Error when specifying invalid manner name for extension.
|
||||
#[error("extension {ext} associated manner {manner} is invalid in manifest file")]
|
||||
InvalidMannerName { manner: String, ext: String },
|
||||
}
|
||||
|
||||
/// Result type used in this executable.
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Correponding Runner
|
||||
|
||||
struct Composition {
|
||||
program: Program,
|
||||
ext_tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Composition {
|
||||
pub fn new(cli: &Cli) -> Result<Composition> {
|
||||
// Open file and read manifest TOML file
|
||||
let mf = Manifest::from_file(&cli.config_file)?;
|
||||
// Create instance
|
||||
let mut program = Program::new(&mf.identifier, &cli.config_file)?;
|
||||
// Setup manner
|
||||
let mut manner_token_map: HashMap<&str, Token> = HashMap::new();
|
||||
for (k, v) in mf.manners.iter() {
|
||||
let token = program.add_manner(v.as_str())?;
|
||||
manner_token_map.insert(k.as_str(), token);
|
||||
}
|
||||
// Setup extension
|
||||
let mut ext_tokens = Vec::new();
|
||||
for (k, v) in mf.exts.iter() {
|
||||
let token = match manner_token_map.get(v.as_str()) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(Error::InvalidMannerName {
|
||||
manner: v.to_string(),
|
||||
ext: k.clone(),
|
||||
});
|
||||
}
|
||||
};
|
||||
let token = program.add_ext(k.as_str(), *token)?;
|
||||
ext_tokens.push(token);
|
||||
}
|
||||
// Okey
|
||||
Ok(Self {
|
||||
program,
|
||||
ext_tokens,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_program(&self) -> &Program {
|
||||
&self.program
|
||||
}
|
||||
|
||||
pub fn iter_ext_tokens(&self) -> impl Iterator<Item = Token> {
|
||||
self.ext_tokens.iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
fn run_register(cli: &Cli, target: &Target) -> Result<()> {
|
||||
let composition = Composition::new(cli)?;
|
||||
let scope = target.clone().into();
|
||||
|
||||
// Register program
|
||||
let program = composition.get_program();
|
||||
program.register(scope)?;
|
||||
// Link all file extensions
|
||||
for token in composition.iter_ext_tokens() {
|
||||
program.link_ext(token, scope)?;
|
||||
}
|
||||
|
||||
println!("Register OK.");
|
||||
fn runner() -> Result<()> {
|
||||
let raw_mf = manifest::RawManifest::from_file(Path::new(r#"D:\Repo\wfassoc\example\ppic.toml"#))?;
|
||||
//let mf = raw_mf.to_checked()?;
|
||||
println!("{:?}", raw_mf);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_unregister(cli: &Cli, target: &Target) -> Result<()> {
|
||||
let composition = Composition::new(cli)?;
|
||||
let scope = target.clone().into();
|
||||
|
||||
// Unlink all file extensions
|
||||
let program = composition.get_program();
|
||||
for token in composition.iter_ext_tokens() {
|
||||
program.unlink_ext(token, scope)?;
|
||||
}
|
||||
// Unregister prorgam
|
||||
program.unregister(scope)?;
|
||||
|
||||
println!("Unregister OK.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_query(cli: &Cli) -> Result<()> {
|
||||
let composition = Composition::new(cli)?;
|
||||
|
||||
// Show file association
|
||||
let mut table = Table::new();
|
||||
table.set_header(["Extension", "Hybrid", "User", "System"]);
|
||||
|
||||
let program = composition.get_program();
|
||||
for token in composition.iter_ext_tokens() {
|
||||
let cell_ext = program.get_ext_str(token).unwrap();
|
||||
let cell_hybrid = program
|
||||
.query_ext(token, View::Hybrid)?
|
||||
.map(|pi| pi.to_string())
|
||||
.unwrap_or_default();
|
||||
let cell_user = program
|
||||
.query_ext(token, View::User)?
|
||||
.map(|pi| pi.to_string())
|
||||
.unwrap_or_default();
|
||||
let cell_system = program
|
||||
.query_ext(token, View::System)?
|
||||
.map(|pi| pi.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
table.add_row([cell_ext, cell_hybrid, cell_user, cell_system]);
|
||||
}
|
||||
|
||||
println!("{table}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let cli = cli::parse();
|
||||
|
||||
let rv = match &cli.command {
|
||||
Commands::Register { target } => run_register(&cli, target),
|
||||
Commands::Unregister { target } => run_unregister(&cli, target),
|
||||
Commands::Query => run_query(&cli),
|
||||
};
|
||||
rv.unwrap_or_else(|e| {
|
||||
runner().unwrap_or_else(|e| {
|
||||
eprintln!("Runtime error: {}.", e);
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
@@ -1,30 +1,90 @@
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use thiserror::Error as TeError;
|
||||
use toml;
|
||||
|
||||
/// Error occurs in this module.
|
||||
#[derive(Debug, TeError)]
|
||||
#[error("{0}")]
|
||||
pub enum Error {
|
||||
/// Io error
|
||||
Io(#[from] std::io::Error),
|
||||
/// Toml deserialization error
|
||||
Toml(#[from] toml::de::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
/// Result type used in this module.
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// region: Raw Manifest
|
||||
|
||||
/// Raw user input manifest.
|
||||
///
|
||||
/// This manifest is the raw input of user.
|
||||
/// Some fields may not be checked due to user invalid input.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RawManifest {
|
||||
identifier: String,
|
||||
path: String,
|
||||
clsid: String,
|
||||
icons: HashMap<String, String>,
|
||||
behaviors: HashMap<String, String>,
|
||||
exts: HashMap<String, RawManifestExt>,
|
||||
}
|
||||
|
||||
impl RawManifest {
|
||||
pub fn into_checked(&self) -> Result<Manifest> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// The sub-type in raw user input manifest.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RawManifestExt {
|
||||
name: String,
|
||||
icon: String,
|
||||
behavior: String,
|
||||
}
|
||||
|
||||
impl RawManifestExt {
|
||||
pub fn into_checked(&self) -> Result<ManifestExt> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl RawManifest {
|
||||
/// Read raw user manifest.
|
||||
pub fn from_file(path: &Path) -> Result<RawManifest> {
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
let config: RawManifest = toml::from_str(&contents)?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Checked Manifest
|
||||
|
||||
/// Converted user input manifest.
|
||||
///
|
||||
/// This manifest struct is prepared for final use.
|
||||
/// All fields loacted in this struct is checked and ready to be used.
|
||||
#[derive(Debug)]
|
||||
pub struct Manifest {
|
||||
pub(crate) identifier: String,
|
||||
pub(crate) path: String,
|
||||
pub(crate) clsid: String,
|
||||
pub(crate) manners: HashMap<String, String>,
|
||||
pub(crate) exts: HashMap<String, String>,
|
||||
|
||||
}
|
||||
|
||||
impl Manifest {
|
||||
pub fn from_file(path: &str) -> Result<Manifest> {
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
let config: Manifest = toml::from_str(&contents)?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct ManifestExt {
|
||||
|
||||
}
|
||||
|
||||
impl ManifestExt {
|
||||
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
162
wfassoc_exec/src/old_main.rs
Normal file
162
wfassoc_exec/src/old_main.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod manifest;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands, Target};
|
||||
use comfy_table::Table;
|
||||
use manifest::Manifest;
|
||||
use std::collections::HashMap;
|
||||
use std::process;
|
||||
use thiserror::Error as TeError;
|
||||
use wfassoc::{Program, Scope, Token, View};
|
||||
|
||||
// region: Basic Types
|
||||
|
||||
/// All errors occurs in this executable.
|
||||
#[derive(TeError, Debug)]
|
||||
enum Error {
|
||||
/// Error from wfassoc core.
|
||||
#[error("{0}")]
|
||||
Core(#[from] wfassoc::Error),
|
||||
/// Error when parsing manifest TOML file.
|
||||
#[error("invalid manifest file: {0}")]
|
||||
Manifest(#[from] manifest::Error),
|
||||
|
||||
/// Error when specifying invalid manner name for extension.
|
||||
#[error("extension {ext} associated manner {manner} is invalid in manifest file")]
|
||||
InvalidMannerName { manner: String, ext: String },
|
||||
}
|
||||
|
||||
/// Result type used in this executable.
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Correponding Runner
|
||||
|
||||
struct Composition {
|
||||
program: Program,
|
||||
ext_tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Composition {
|
||||
pub fn new(cli: &Cli) -> Result<Composition> {
|
||||
// Open file and read manifest TOML file
|
||||
let mf = Manifest::from_file(&cli.config_file)?;
|
||||
// Create instance
|
||||
let mut program = Program::new(&mf.identifier, &cli.config_file)?;
|
||||
// Setup manner
|
||||
let mut manner_token_map: HashMap<&str, Token> = HashMap::new();
|
||||
for (k, v) in mf.manners.iter() {
|
||||
let token = program.add_manner(v.as_str())?;
|
||||
manner_token_map.insert(k.as_str(), token);
|
||||
}
|
||||
// Setup extension
|
||||
let mut ext_tokens = Vec::new();
|
||||
for (k, v) in mf.exts.iter() {
|
||||
let token = match manner_token_map.get(v.as_str()) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(Error::InvalidMannerName {
|
||||
manner: v.to_string(),
|
||||
ext: k.clone(),
|
||||
});
|
||||
}
|
||||
};
|
||||
let token = program.add_ext(k.as_str(), *token)?;
|
||||
ext_tokens.push(token);
|
||||
}
|
||||
// Okey
|
||||
Ok(Self {
|
||||
program,
|
||||
ext_tokens,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_program(&self) -> &Program {
|
||||
&self.program
|
||||
}
|
||||
|
||||
pub fn iter_ext_tokens(&self) -> impl Iterator<Item = Token> {
|
||||
self.ext_tokens.iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
fn run_register(cli: &Cli, target: &Target) -> Result<()> {
|
||||
let composition = Composition::new(cli)?;
|
||||
let scope = target.clone().into();
|
||||
|
||||
// Register program
|
||||
let program = composition.get_program();
|
||||
program.register(scope)?;
|
||||
// Link all file extensions
|
||||
for token in composition.iter_ext_tokens() {
|
||||
program.link_ext(token, scope)?;
|
||||
}
|
||||
|
||||
println!("Register OK.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_unregister(cli: &Cli, target: &Target) -> Result<()> {
|
||||
let composition = Composition::new(cli)?;
|
||||
let scope = target.clone().into();
|
||||
|
||||
// Unlink all file extensions
|
||||
let program = composition.get_program();
|
||||
for token in composition.iter_ext_tokens() {
|
||||
program.unlink_ext(token, scope)?;
|
||||
}
|
||||
// Unregister prorgam
|
||||
program.unregister(scope)?;
|
||||
|
||||
println!("Unregister OK.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_query(cli: &Cli) -> Result<()> {
|
||||
let composition = Composition::new(cli)?;
|
||||
|
||||
// Show file association
|
||||
let mut table = Table::new();
|
||||
table.set_header(["Extension", "Hybrid", "User", "System"]);
|
||||
|
||||
let program = composition.get_program();
|
||||
for token in composition.iter_ext_tokens() {
|
||||
let cell_ext = program.get_ext_str(token).unwrap();
|
||||
let cell_hybrid = program
|
||||
.query_ext(token, View::Hybrid)?
|
||||
.map(|pi| pi.to_string())
|
||||
.unwrap_or_default();
|
||||
let cell_user = program
|
||||
.query_ext(token, View::User)?
|
||||
.map(|pi| pi.to_string())
|
||||
.unwrap_or_default();
|
||||
let cell_system = program
|
||||
.query_ext(token, View::System)?
|
||||
.map(|pi| pi.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
table.add_row([cell_ext, cell_hybrid, cell_user, cell_system]);
|
||||
}
|
||||
|
||||
println!("{table}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let rv = match &cli.command {
|
||||
Commands::Register { target } => run_register(&cli, target),
|
||||
Commands::Unregister { target } => run_unregister(&cli, target),
|
||||
Commands::Query => run_query(&cli),
|
||||
};
|
||||
rv.unwrap_or_else(|e| {
|
||||
eprintln!("Runtime error: {}.", e);
|
||||
process::exit(1)
|
||||
});
|
||||
}
|
||||
0
wfassoc_exec/src/runner.rs
Normal file
0
wfassoc_exec/src/runner.rs
Normal file
Reference in New Issue
Block a user