Skip to content

Commit

Permalink
refactor and support windows
Browse files Browse the repository at this point in the history
  • Loading branch information
CNCSMonster committed Jan 2, 2025
1 parent ea414a3 commit 4ac7a19
Show file tree
Hide file tree
Showing 17 changed files with 343 additions and 299 deletions.
21 changes: 20 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
[package]
name = "xdotter"
version = "0.0.10"
version = "0.0.11"
edition = "2021"
authors = ["cncsmonster <[email protected]>"]
description = "A simple dotfile manager"
license = "MIT"
repository = "https://github.com/cncsmonster/xdotter"

[[bin]]
name = "xdotter"
path = "src/main.rs"

[dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.2", features = ["cargo", "wrap_help"] }
clap = { version = "4.5.2", features = ["cargo", "wrap_help","derive"] }
clap_complete = "4.5.5"
dirs = "5.0.1"
env_logger = "0.11.3"
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# xdotter: a simple dotfile manager

## Inspired

this project is inspired by [dotter](https://github.com/SuperCuber/dotter), but with a different design and has a much more simple implementation.

## Install

Use cargo:
Expand All @@ -19,13 +23,13 @@ From github repo:
git clone github.com/cncsmonster/xdotter
cd xdotter
cargo install --path .
echo 'xdotter is already installed in .cargo/bin/xdotter.'
echo 'xdotter is already installed as .cargo/bin/xd.'
```

## Usage

```shell
xdotter -h
xd -h
```

if want some example, you can check my [dotfiles](https://github.com/cncsmonster/dotfiles).
Expand Down
11 changes: 11 additions & 0 deletions src/actions/complete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use clap::CommandFactory;
use clap_complete::Shell;

use crate::XdotterCli;

pub fn complete(shell: &Shell) -> anyhow::Result<()> {
let mut cli = XdotterCli::command();
let bin_name = cli.get_name().to_string();
clap_complete::generate(*shell, &mut cli, bin_name, &mut std::io::stdout().lock());
Ok(())
}
52 changes: 52 additions & 0 deletions src/actions/deploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::fs;

use log::*;

use crate::{create_symlink, init_run_mode, mlog::init_logger, on_dry_run_mode, Config, RunArgs};

pub fn deploy(args: &RunArgs) -> anyhow::Result<()> {
init_logger(args)?;
init_run_mode(args)?;
info!("deploying...");
deploy_on(&args.config).unwrap_or_else(|e| {
error!("{e}");
});
Ok(())
}

fn deploy_on(conf: &str) -> anyhow::Result<()> {
info!("deploying on {}", conf);
let dry_run = on_dry_run_mode();
let config_str = fs::read_to_string(conf)?;
let config: Config = toml::from_str(&config_str)?;
if let Some(links) = config.links.as_ref() {
for (actual_path, link) in links {
info!("deploy: {} -> {}", link, actual_path);
if !dry_run {
create_symlink(actual_path, link).unwrap_or_else(|e| {
error!("failed to create link: {}", e);
});
}
}
}
let current_dir = std::env::current_dir()?;
if let Some(deps) = config.dependencies.as_ref() {
for (dependency, path) in deps {
info!("dependency: {}, path: {}", dependency, path);
let path = current_dir.join(path);
if let Err(e) = std::env::set_current_dir(&path) {
error!("failed to enter {}: {}", path.display(), e);
continue;
}
info!("entering {}", path.display());
deploy_on(&format!("{}/xdotter.toml", path.display())).unwrap_or_else(|e| {
error!("{}", e);
});
std::env::set_current_dir(&current_dir).unwrap_or_else(|e| {
error!("failed to leave {}: {}", path.display(), e);
});
info!("leaving {}", path.display());
}
}
Ok(())
}
9 changes: 9 additions & 0 deletions src/actions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub mod complete;
pub mod deploy;
pub mod new;
pub mod undeploy;

pub use complete::*;
pub use deploy::*;
pub use new::*;
pub use undeploy::*;
22 changes: 22 additions & 0 deletions src/actions/new.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use log::{error, info};
use maplit::hashmap;

use crate::Config;

pub fn new() -> anyhow::Result<()> {
let config = Config {
dependencies: Some(hashmap! {
"go".to_string() => "testdata/go".to_string(),
}),
links: Some(hashmap! {
"testdata/mm".to_string() => "~/.cache/mm".to_string(),
}),
};
let config_str = toml::to_string(&config).unwrap();
info!("creating xdotter.toml");
std::fs::write("xdotter.toml", config_str).unwrap_or_else(|e| {
error!("failed to create xdotter.toml: {}", e);
});
info!("Created xdotter.toml");
Ok(())
}
53 changes: 53 additions & 0 deletions src/actions/undeploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::fs;

use log::*;

use crate::{delete_symlink, init_run_mode, mlog::init_logger, on_dry_run_mode, Config, RunArgs};

pub fn undeploy(args: &RunArgs) -> anyhow::Result<()> {
init_logger(args)?;
init_run_mode(args)?;

info!("undeploying...");
undeploy_on(&args.config).unwrap_or_else(|e| {
error!("{e}");
});
Ok(())
}

fn undeploy_on(conf: &str) -> anyhow::Result<()> {
info!("undeploying on {}", conf);
let dry_run = on_dry_run_mode();
let config_str = fs::read_to_string(conf)?;
let config: Config = toml::from_str(&config_str)?;
if let Some(links) = config.links.as_ref() {
for (actual_path, link) in links {
info!("undeploy: {} -> {}", link, actual_path);
if !dry_run {
delete_symlink(link).unwrap_or_else(|e| {
error!("failed to delete link: {}", e);
});
}
}
}
let current_dir = std::env::current_dir()?;
if let Some(deps) = config.dependencies.as_ref() {
for (dependency, path) in deps {
debug!("dependency: {}, path: {}", dependency, path);
let path = current_dir.join(path);
if let Err(e) = std::env::set_current_dir(&path) {
error!("failed to enter {}: {}", path.display(), e);
continue;
}
debug!("entering {}", path.display());
undeploy_on(&format!("{}/xdotter.toml", path.display())).unwrap_or_else(|e| {
error!("{}", e);
});
std::env::set_current_dir(&current_dir).unwrap_or_else(|e| {
error!("failed to leave {}: {}", path.display(), e);
});
debug!("leaving {}", path.display());
}
}
Ok(())
}
23 changes: 0 additions & 23 deletions src/args.rs

This file was deleted.

19 changes: 19 additions & 0 deletions src/bin/xd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use clap::Parser;
use xdotter::{
complete::complete, deploy::deploy, new::new, undeploy::undeploy, Action, RunArgs, XdotterCli,
};

extern crate xdotter;

fn main() -> anyhow::Result<()> {
let cli = XdotterCli::parse();
match &cli.subcommand {
Some(action) => match action {
Action::Deploy(args) => deploy(args),
Action::Undeploy(args) => undeploy(args),
Action::New => new(),
Action::Complete { shell } => complete(shell),
},
None => deploy(&RunArgs::default()),
}
}
54 changes: 54 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use clap::{Args, Parser, Subcommand};
use clap_complete::Shell;

#[derive(Debug, Parser)]
pub struct XdotterCli {
#[command(subcommand)]
pub subcommand: Option<Action>,
}

#[derive(Debug, Subcommand)]
pub enum Action {
Deploy(RunArgs),
Undeploy(RunArgs),
New,
Complete {
#[arg(short, long)]
shell: Shell,
},
}

#[derive(Debug, Args)]
pub struct RunArgs {
/// Specify the configuration file
#[arg(short, long, default_value_t = String::from("xdotter.toml"))]
pub(crate) config: String,
/// Show more information in execution
#[arg(short, long)]
pub(crate) verbose: bool,
/// Do not actually work,but show you what will happen
#[arg(short, long)]
pub(crate) dry_run: bool,
/// Ask for confirmation while unsure,in case like conflict with existing file entry
#[arg(short, long, conflicts_with = "force", conflicts_with = "quiet")]
pub(crate) interactive: bool,
/// If conflict with existed file entry,just remove it
#[arg(short, long)]
pub(crate) force: bool,
/// Do not print any output
#[arg(short, long)]
pub(crate) quiet: bool,
}

impl Default for RunArgs {
fn default() -> Self {
Self {
config: "xdotter.toml".to_string(),
verbose: true,
dry_run: Default::default(),
interactive: Default::default(),
force: Default::default(),
quiet: Default::default(),
}
}
}
12 changes: 12 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Config {
/// dependencies,子成员的路径,每个子成员内部应该有配置
#[serde(skip_serializing_if = "Option::is_none")]
pub dependencies: Option<HashMap<String, String>>,
/// 子成员路径',如果子成员路径存在,则在子成员路径中创建配置文件,左边为子成员路径,右边为目标链接创建路径
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<HashMap<String, String>>,
}
Loading

0 comments on commit 4ac7a19

Please sign in to comment.