1use std::path::{Path, PathBuf};
2
3use anyhow::Context;
4use clap::Parser;
5use dirs::{config_dir, state_dir};
6
7#[derive(clap::Args)]
8#[group(required = false, multiple = false)]
9struct ClapImageSource {
10 #[arg(short = 'd', long = "docker")]
12 force_docker: bool,
13 #[arg(short = 'f', long = "fs")]
15 force_fs: bool,
16 #[arg(long = "podman")]
18 force_podman: bool,
19}
20
21impl ClapImageSource {
22 fn into_enum(self) -> ImageSource {
23 if self.force_docker {
24 ImageSource::ForceDocker
25 } else if self.force_fs {
26 ImageSource::ForceFS
27 } else if self.force_podman {
28 ImageSource::ForcePodman
29 } else {
30 ImageSource::Default
31 }
32 }
33}
34
35#[derive(Parser)]
36#[command(version, about)]
37struct Arg {
38 #[arg(short = 'p', long)]
42 config_path: Option<PathBuf>,
43 #[clap(flatten)]
47 image_source: ClapImageSource,
48 #[arg()]
49 image: String,
50}
51
52#[derive(Debug, Clone, Copy)]
54pub enum ImageSource {
55 Default,
57 ForceDocker,
59 ForceFS,
61 ForcePodman,
63}
64
65#[derive(Debug)]
66pub struct Config {
67 config_path: PathBuf,
68 state_path: PathBuf,
69 image: String,
70 image_source: ImageSource,
71}
72
73impl Config {
74 pub fn new() -> anyhow::Result<Self> {
75 let Arg {
76 config_path,
77 image,
78 image_source,
79 } = Arg::parse();
80 let image_source = image_source.into_enum();
81
82 let config_path = config_path
83 .or_else(default_config_path)
84 .context("failed to get the config directory")?;
85 let state_path = default_state_path()
86 .or_else(|| Some(config_path.clone()))
87 .context("failed to get the state directory")?;
88
89 std::fs::create_dir_all(&config_path)
90 .context("failed to create the config directory")?;
91 std::fs::create_dir_all(&state_path)
92 .context("failed to create the state directory")?;
93
94 Ok(Config {
95 config_path,
96 state_path,
97 image,
98 image_source,
99 })
100 }
101
102 pub fn make_config_path(&self, path: impl AsRef<Path>) -> PathBuf {
103 let mut config_path = self.config_path.clone();
104 config_path.push(path.as_ref());
105 config_path
106 }
107
108 pub fn config_path(&self) -> &Path {
109 &self.config_path
110 }
111
112 pub fn state_path(&self) -> &Path {
113 &self.state_path
114 }
115
116 pub fn image(&self) -> &str {
117 &self.image
118 }
119
120 pub fn image_source(&self) -> ImageSource {
121 self.image_source
122 }
123}
124
125fn default_config_path() -> Option<PathBuf> {
126 config_dir().map(|mut path| {
127 path.push("xray");
128 path
129 })
130}
131
132fn default_state_path() -> Option<PathBuf> {
133 state_dir().map(|mut path| {
134 path.push("xray");
135 path
136 })
137}