irongit

The dotfiles manager.

initial commit

huncholanehuncholaneauthored
commit b2b56a561ab913ec61178f87535faeb52edd0edaBrowse files

4 files changed, +867 -0

+583-0Cargo.lock
@@ -0,0 +1,583 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
3+version = 4
4+
5+[[package]]
6+name = "anstream"
7+version = "0.6.21"
8+source = "registry+https://github.com/rust-lang/crates.io-index"
9+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
10+dependencies = [
11+ "anstyle",
12+ "anstyle-parse",
13+ "anstyle-query",
14+ "anstyle-wincon",
15+ "colorchoice",
16+ "is_terminal_polyfill",
17+ "utf8parse",
18+]
19+
20+[[package]]
21+name = "anstyle"
22+version = "1.0.13"
23+source = "registry+https://github.com/rust-lang/crates.io-index"
24+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
25+
26+[[package]]
27+name = "anstyle-parse"
28+version = "0.2.7"
29+source = "registry+https://github.com/rust-lang/crates.io-index"
30+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
31+dependencies = [
32+ "utf8parse",
33+]
34+
35+[[package]]
36+name = "anstyle-query"
37+version = "1.1.4"
38+source = "registry+https://github.com/rust-lang/crates.io-index"
39+checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
40+dependencies = [
41+ "windows-sys 0.60.2",
42+]
43+
44+[[package]]
45+name = "anstyle-wincon"
46+version = "3.0.10"
47+source = "registry+https://github.com/rust-lang/crates.io-index"
48+checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
49+dependencies = [
50+ "anstyle",
51+ "once_cell_polyfill",
52+ "windows-sys 0.60.2",
53+]
54+
55+[[package]]
56+name = "anyhow"
57+version = "1.0.100"
58+source = "registry+https://github.com/rust-lang/crates.io-index"
59+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
60+
61+[[package]]
62+name = "bitflags"
63+version = "2.10.0"
64+source = "registry+https://github.com/rust-lang/crates.io-index"
65+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
66+
67+[[package]]
68+name = "cfg-if"
69+version = "1.0.4"
70+source = "registry+https://github.com/rust-lang/crates.io-index"
71+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
72+
73+[[package]]
74+name = "clap"
75+version = "4.5.51"
76+source = "registry+https://github.com/rust-lang/crates.io-index"
77+checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
78+dependencies = [
79+ "clap_builder",
80+ "clap_derive",
81+]
82+
83+[[package]]
84+name = "clap_builder"
85+version = "4.5.51"
86+source = "registry+https://github.com/rust-lang/crates.io-index"
87+checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
88+dependencies = [
89+ "anstream",
90+ "anstyle",
91+ "clap_lex",
92+ "strsim",
93+]
94+
95+[[package]]
96+name = "clap_complete"
97+version = "4.5.60"
98+source = "registry+https://github.com/rust-lang/crates.io-index"
99+checksum = "8e602857739c5a4291dfa33b5a298aeac9006185229a700e5810a3ef7272d971"
100+dependencies = [
101+ "clap",
102+]
103+
104+[[package]]
105+name = "clap_derive"
106+version = "4.5.49"
107+source = "registry+https://github.com/rust-lang/crates.io-index"
108+checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
109+dependencies = [
110+ "heck",
111+ "proc-macro2",
112+ "quote",
113+ "syn",
114+]
115+
116+[[package]]
117+name = "clap_lex"
118+version = "0.7.6"
119+source = "registry+https://github.com/rust-lang/crates.io-index"
120+checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
121+
122+[[package]]
123+name = "colorchoice"
124+version = "1.0.4"
125+source = "registry+https://github.com/rust-lang/crates.io-index"
126+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
127+
128+[[package]]
129+name = "dirs"
130+version = "5.0.1"
131+source = "registry+https://github.com/rust-lang/crates.io-index"
132+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
133+dependencies = [
134+ "dirs-sys",
135+]
136+
137+[[package]]
138+name = "dirs-sys"
139+version = "0.4.1"
140+source = "registry+https://github.com/rust-lang/crates.io-index"
141+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
142+dependencies = [
143+ "libc",
144+ "option-ext",
145+ "redox_users",
146+ "windows-sys 0.48.0",
147+]
148+
149+[[package]]
150+name = "dotman"
151+version = "0.1.0"
152+dependencies = [
153+ "anyhow",
154+ "clap",
155+ "clap_complete",
156+ "dirs",
157+ "which",
158+]
159+
160+[[package]]
161+name = "either"
162+version = "1.15.0"
163+source = "registry+https://github.com/rust-lang/crates.io-index"
164+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
165+
166+[[package]]
167+name = "errno"
168+version = "0.3.14"
169+source = "registry+https://github.com/rust-lang/crates.io-index"
170+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
171+dependencies = [
172+ "libc",
173+ "windows-sys 0.61.2",
174+]
175+
176+[[package]]
177+name = "getrandom"
178+version = "0.2.16"
179+source = "registry+https://github.com/rust-lang/crates.io-index"
180+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
181+dependencies = [
182+ "cfg-if",
183+ "libc",
184+ "wasi",
185+]
186+
187+[[package]]
188+name = "heck"
189+version = "0.5.0"
190+source = "registry+https://github.com/rust-lang/crates.io-index"
191+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
192+
193+[[package]]
194+name = "home"
195+version = "0.5.12"
196+source = "registry+https://github.com/rust-lang/crates.io-index"
197+checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
198+dependencies = [
199+ "windows-sys 0.61.2",
200+]
201+
202+[[package]]
203+name = "is_terminal_polyfill"
204+version = "1.70.2"
205+source = "registry+https://github.com/rust-lang/crates.io-index"
206+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
207+
208+[[package]]
209+name = "libc"
210+version = "0.2.177"
211+source = "registry+https://github.com/rust-lang/crates.io-index"
212+checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
213+
214+[[package]]
215+name = "libredox"
216+version = "0.1.10"
217+source = "registry+https://github.com/rust-lang/crates.io-index"
218+checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
219+dependencies = [
220+ "bitflags",
221+ "libc",
222+]
223+
224+[[package]]
225+name = "linux-raw-sys"
226+version = "0.4.15"
227+source = "registry+https://github.com/rust-lang/crates.io-index"
228+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
229+
230+[[package]]
231+name = "once_cell_polyfill"
232+version = "1.70.2"
233+source = "registry+https://github.com/rust-lang/crates.io-index"
234+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
235+
236+[[package]]
237+name = "option-ext"
238+version = "0.2.0"
239+source = "registry+https://github.com/rust-lang/crates.io-index"
240+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
241+
242+[[package]]
243+name = "proc-macro2"
244+version = "1.0.103"
245+source = "registry+https://github.com/rust-lang/crates.io-index"
246+checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
247+dependencies = [
248+ "unicode-ident",
249+]
250+
251+[[package]]
252+name = "quote"
253+version = "1.0.42"
254+source = "registry+https://github.com/rust-lang/crates.io-index"
255+checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
256+dependencies = [
257+ "proc-macro2",
258+]
259+
260+[[package]]
261+name = "redox_users"
262+version = "0.4.6"
263+source = "registry+https://github.com/rust-lang/crates.io-index"
264+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
265+dependencies = [
266+ "getrandom",
267+ "libredox",
268+ "thiserror",
269+]
270+
271+[[package]]
272+name = "rustix"
273+version = "0.38.44"
274+source = "registry+https://github.com/rust-lang/crates.io-index"
275+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
276+dependencies = [
277+ "bitflags",
278+ "errno",
279+ "libc",
280+ "linux-raw-sys",
281+ "windows-sys 0.59.0",
282+]
283+
284+[[package]]
285+name = "strsim"
286+version = "0.11.1"
287+source = "registry+https://github.com/rust-lang/crates.io-index"
288+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
289+
290+[[package]]
291+name = "syn"
292+version = "2.0.109"
293+source = "registry+https://github.com/rust-lang/crates.io-index"
294+checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f"
295+dependencies = [
296+ "proc-macro2",
297+ "quote",
298+ "unicode-ident",
299+]
300+
301+[[package]]
302+name = "thiserror"
303+version = "1.0.69"
304+source = "registry+https://github.com/rust-lang/crates.io-index"
305+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
306+dependencies = [
307+ "thiserror-impl",
308+]
309+
310+[[package]]
311+name = "thiserror-impl"
312+version = "1.0.69"
313+source = "registry+https://github.com/rust-lang/crates.io-index"
314+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
315+dependencies = [
316+ "proc-macro2",
317+ "quote",
318+ "syn",
319+]
320+
321+[[package]]
322+name = "unicode-ident"
323+version = "1.0.22"
324+source = "registry+https://github.com/rust-lang/crates.io-index"
325+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
326+
327+[[package]]
328+name = "utf8parse"
329+version = "0.2.2"
330+source = "registry+https://github.com/rust-lang/crates.io-index"
331+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
332+
333+[[package]]
334+name = "wasi"
335+version = "0.11.1+wasi-snapshot-preview1"
336+source = "registry+https://github.com/rust-lang/crates.io-index"
337+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
338+
339+[[package]]
340+name = "which"
341+version = "6.0.3"
342+source = "registry+https://github.com/rust-lang/crates.io-index"
343+checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
344+dependencies = [
345+ "either",
346+ "home",
347+ "rustix",
348+ "winsafe",
349+]
350+
351+[[package]]
352+name = "windows-link"
353+version = "0.2.1"
354+source = "registry+https://github.com/rust-lang/crates.io-index"
355+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
356+
357+[[package]]
358+name = "windows-sys"
359+version = "0.48.0"
360+source = "registry+https://github.com/rust-lang/crates.io-index"
361+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
362+dependencies = [
363+ "windows-targets 0.48.5",
364+]
365+
366+[[package]]
367+name = "windows-sys"
368+version = "0.59.0"
369+source = "registry+https://github.com/rust-lang/crates.io-index"
370+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
371+dependencies = [
372+ "windows-targets 0.52.6",
373+]
374+
375+[[package]]
376+name = "windows-sys"
377+version = "0.60.2"
378+source = "registry+https://github.com/rust-lang/crates.io-index"
379+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
380+dependencies = [
381+ "windows-targets 0.53.5",
382+]
383+
384+[[package]]
385+name = "windows-sys"
386+version = "0.61.2"
387+source = "registry+https://github.com/rust-lang/crates.io-index"
388+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
389+dependencies = [
390+ "windows-link",
391+]
392+
393+[[package]]
394+name = "windows-targets"
395+version = "0.48.5"
396+source = "registry+https://github.com/rust-lang/crates.io-index"
397+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
398+dependencies = [
399+ "windows_aarch64_gnullvm 0.48.5",
400+ "windows_aarch64_msvc 0.48.5",
401+ "windows_i686_gnu 0.48.5",
402+ "windows_i686_msvc 0.48.5",
403+ "windows_x86_64_gnu 0.48.5",
404+ "windows_x86_64_gnullvm 0.48.5",
405+ "windows_x86_64_msvc 0.48.5",
406+]
407+
408+[[package]]
409+name = "windows-targets"
410+version = "0.52.6"
411+source = "registry+https://github.com/rust-lang/crates.io-index"
412+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
413+dependencies = [
414+ "windows_aarch64_gnullvm 0.52.6",
415+ "windows_aarch64_msvc 0.52.6",
416+ "windows_i686_gnu 0.52.6",
417+ "windows_i686_gnullvm 0.52.6",
418+ "windows_i686_msvc 0.52.6",
419+ "windows_x86_64_gnu 0.52.6",
420+ "windows_x86_64_gnullvm 0.52.6",
421+ "windows_x86_64_msvc 0.52.6",
422+]
423+
424+[[package]]
425+name = "windows-targets"
426+version = "0.53.5"
427+source = "registry+https://github.com/rust-lang/crates.io-index"
428+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
429+dependencies = [
430+ "windows-link",
431+ "windows_aarch64_gnullvm 0.53.1",
432+ "windows_aarch64_msvc 0.53.1",
433+ "windows_i686_gnu 0.53.1",
434+ "windows_i686_gnullvm 0.53.1",
435+ "windows_i686_msvc 0.53.1",
436+ "windows_x86_64_gnu 0.53.1",
437+ "windows_x86_64_gnullvm 0.53.1",
438+ "windows_x86_64_msvc 0.53.1",
439+]
440+
441+[[package]]
442+name = "windows_aarch64_gnullvm"
443+version = "0.48.5"
444+source = "registry+https://github.com/rust-lang/crates.io-index"
445+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
446+
447+[[package]]
448+name = "windows_aarch64_gnullvm"
449+version = "0.52.6"
450+source = "registry+https://github.com/rust-lang/crates.io-index"
451+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
452+
453+[[package]]
454+name = "windows_aarch64_gnullvm"
455+version = "0.53.1"
456+source = "registry+https://github.com/rust-lang/crates.io-index"
457+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
458+
459+[[package]]
460+name = "windows_aarch64_msvc"
461+version = "0.48.5"
462+source = "registry+https://github.com/rust-lang/crates.io-index"
463+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
464+
465+[[package]]
466+name = "windows_aarch64_msvc"
467+version = "0.52.6"
468+source = "registry+https://github.com/rust-lang/crates.io-index"
469+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
470+
471+[[package]]
472+name = "windows_aarch64_msvc"
473+version = "0.53.1"
474+source = "registry+https://github.com/rust-lang/crates.io-index"
475+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
476+
477+[[package]]
478+name = "windows_i686_gnu"
479+version = "0.48.5"
480+source = "registry+https://github.com/rust-lang/crates.io-index"
481+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
482+
483+[[package]]
484+name = "windows_i686_gnu"
485+version = "0.52.6"
486+source = "registry+https://github.com/rust-lang/crates.io-index"
487+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
488+
489+[[package]]
490+name = "windows_i686_gnu"
491+version = "0.53.1"
492+source = "registry+https://github.com/rust-lang/crates.io-index"
493+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
494+
495+[[package]]
496+name = "windows_i686_gnullvm"
497+version = "0.52.6"
498+source = "registry+https://github.com/rust-lang/crates.io-index"
499+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
500+
501+[[package]]
502+name = "windows_i686_gnullvm"
503+version = "0.53.1"
504+source = "registry+https://github.com/rust-lang/crates.io-index"
505+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
506+
507+[[package]]
508+name = "windows_i686_msvc"
509+version = "0.48.5"
510+source = "registry+https://github.com/rust-lang/crates.io-index"
511+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
512+
513+[[package]]
514+name = "windows_i686_msvc"
515+version = "0.52.6"
516+source = "registry+https://github.com/rust-lang/crates.io-index"
517+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
518+
519+[[package]]
520+name = "windows_i686_msvc"
521+version = "0.53.1"
522+source = "registry+https://github.com/rust-lang/crates.io-index"
523+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
524+
525+[[package]]
526+name = "windows_x86_64_gnu"
527+version = "0.48.5"
528+source = "registry+https://github.com/rust-lang/crates.io-index"
529+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
530+
531+[[package]]
532+name = "windows_x86_64_gnu"
533+version = "0.52.6"
534+source = "registry+https://github.com/rust-lang/crates.io-index"
535+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
536+
537+[[package]]
538+name = "windows_x86_64_gnu"
539+version = "0.53.1"
540+source = "registry+https://github.com/rust-lang/crates.io-index"
541+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
542+
543+[[package]]
544+name = "windows_x86_64_gnullvm"
545+version = "0.48.5"
546+source = "registry+https://github.com/rust-lang/crates.io-index"
547+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
548+
549+[[package]]
550+name = "windows_x86_64_gnullvm"
551+version = "0.52.6"
552+source = "registry+https://github.com/rust-lang/crates.io-index"
553+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
554+
555+[[package]]
556+name = "windows_x86_64_gnullvm"
557+version = "0.53.1"
558+source = "registry+https://github.com/rust-lang/crates.io-index"
559+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
560+
561+[[package]]
562+name = "windows_x86_64_msvc"
563+version = "0.48.5"
564+source = "registry+https://github.com/rust-lang/crates.io-index"
565+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
566+
567+[[package]]
568+name = "windows_x86_64_msvc"
569+version = "0.52.6"
570+source = "registry+https://github.com/rust-lang/crates.io-index"
571+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
572+
573+[[package]]
574+name = "windows_x86_64_msvc"
575+version = "0.53.1"
576+source = "registry+https://github.com/rust-lang/crates.io-index"
577+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
578+
579+[[package]]
580+name = "winsafe"
581+version = "0.0.19"
582+source = "registry+https://github.com/rust-lang/crates.io-index"
583+checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
+11-0Cargo.toml
@@ -0,0 +1,11 @@
1+[package]
2+name = "dotman"
3+version = "0.1.0"
4+edition = "2021"
5+
6+[dependencies]
7+anyhow = "1.0"
8+clap = { version = "4.5", features = ["derive"] }
9+clap_complete = "4.5"
10+dirs = "5"
11+which = "6"
+27-0README.md
@@ -0,0 +1,27 @@
1+dotman
2+=====
3+
4+Small Rust CLI to manage dotfile-like repos under `/usr/local/share/dotman`, and link Neovim config.
5+
6+Commands
7+- `dotman install <repo-url>`: Clones the repo into `/usr/local/share/dotman/<name>` where `<name>` is derived from the URL (e.g., `https://github.com/hygo-nvim` -> `hygo-nvim`). Requires write access to `/usr/local/share/dotman` (use sudo if needed).
8+- `dotman link <name> <target>`: Deletes `~/.config/<target>` (if present) and creates a symlink to `/usr/local/share/dotman/<name>`. This is universal; e.g., `dotman link hygo-nvim nvim`, `dotman link my-fish fish`.
9+- `dotman update`: Runs `git pull --ff-only` for every git repo inside `/usr/local/share/dotman`.
10+- `dotman completions <SHELL>`: Prints shell completion script to stdout. Supported shells: bash, zsh, fish, powershell, elvish.
11+
12+Usage examples
13+- Install: `dotman install https://github.com/hygo-nvim`
14+- Link Neovim: `dotman link hygo-nvim nvim`
15+- Link Fish: `dotman link my-fish fish`
16+- Update all: `dotman update`
17+
18+Completions
19+- Bash: `dotman completions bash > /etc/bash_completion.d/dotman` (may require sudo)
20+- Zsh: `dotman completions zsh > "${fpath[1]}/_dotman"`
21+- Fish: `dotman completions fish > ~/.config/fish/completions/dotman.fish`
22+- Powershell: `dotman completions powershell | Out-String | Set-Content $PROFILE.CurrentUserAllHosts`
23+- Elvish: `dotman completions elvish > ~/.elvish/lib/dotman.elv`
24+
25+Notes
26+- Ensure `git` is installed and in PATH.
27+- Installing and updating may require `sudo` due to the `/usr/local/share/dotman` location.
+246-0src/main.rs
@@ -0,0 +1,246 @@
1+use anyhow::{bail, Context, Result};
2+use clap::{Args, Parser, Subcommand, ValueEnum};
3+use clap_complete::{generate, shells};
4+use std::fs;
5+use std::io;
6+use std::path::Path;
7+use std::process::Command;
8+
9+const DOTMAN_DIR: &str = "/usr/local/share/dotman";
10+
11+#[derive(Parser)]
12+#[command(name = "dotman", about = "Manage dotfile repos and links", version)]
13+#[command(arg_required_else_help = true)]
14+struct Cli {
15+ #[command(subcommand)]
16+ command: Commands,
17+}
18+
19+#[derive(Subcommand)]
20+enum Commands {
21+ /// Clone a git repository into the dotman store
22+ Install(InstallArgs),
23+ /// Replace ~/.config/<target> with a symlink to a stored repo
24+ Link(LinkArgs),
25+ /// Pull latest changes for all stored repos
26+ Update,
27+ /// Generate shell completions to stdout (bash|zsh|fish|powershell|elvish)
28+ Completions { shell: Shell },
29+}
30+
31+#[derive(Args)]
32+struct InstallArgs {
33+ /// Git repository URL, e.g. https://github.com/hygo-nvim
34+ repo: String,
35+}
36+
37+#[derive(Args)]
38+struct LinkArgs {
39+ /// Repository name stored under dotman (e.g. hygo-nvim)
40+ name: String,
41+ /// Target directory name under ~/.config (e.g. nvim, alacritty, fish)
42+ target: String,
43+}
44+
45+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
46+enum Shell {
47+ Bash,
48+ Zsh,
49+ Fish,
50+ PowerShell,
51+ Elvish,
52+}
53+
54+fn main() -> Result<()> {
55+ let cli = Cli::parse();
56+
57+ match cli.command {
58+ Commands::Install(args) => cmd_install(&args.repo),
59+ Commands::Link(args) => cmd_link(&args.name, &args.target),
60+ Commands::Update => cmd_update(),
61+ Commands::Completions { shell } => cmd_completions(shell),
62+ }
63+}
64+
65+fn ensure_dotman_dir() -> Result<()> {
66+ let path = Path::new(DOTMAN_DIR);
67+ if !path.exists() {
68+ fs::create_dir_all(path)
69+ .with_context(|| format!("Failed creating {} (need sudo?)", DOTMAN_DIR))?;
70+ }
71+ Ok(())
72+}
73+
74+fn derive_repo_name(repo_url: &str) -> String {
75+ let trimmed = repo_url.trim_end_matches('/')
76+ .trim_end_matches(".git");
77+ trimmed
78+ .rsplit('/')
79+ .next()
80+ .unwrap_or(trimmed)
81+ .to_string()
82+}
83+
84+fn cmd_install(repo: &str) -> Result<()> {
85+ ensure_dotman_dir()?;
86+
87+ // Determine repo name
88+ let name = derive_repo_name(repo);
89+ if name.is_empty() {
90+ bail!("Could not infer repository name from URL: {}", repo);
91+ }
92+
93+ let dest = Path::new(DOTMAN_DIR).join(&name);
94+ if dest.exists() {
95+ println!("Repo already exists: {}", dest.display());
96+ return Ok(());
97+ }
98+
99+ // Ensure git is available
100+ if which::which("git").is_err() {
101+ bail!("git is not installed or not found in PATH");
102+ }
103+
104+ println!("Cloning {} -> {}", repo, dest.display());
105+ let status = Command::new("git")
106+ .args(["clone", repo, dest.to_string_lossy().as_ref()])
107+ .status()
108+ .with_context(|| "Failed to spawn git clone")?;
109+
110+ if !status.success() {
111+ bail!("git clone failed with status: {}", status);
112+ }
113+
114+ println!("Installed {}", name);
115+ Ok(())
116+}
117+
118+fn cmd_link(name: &str, target_name: &str) -> Result<()> {
119+ let source = Path::new(DOTMAN_DIR).join(name);
120+ if !source.exists() {
121+ bail!("Source repo not found: {}", source.display());
122+ }
123+
124+ // Target: ~/.config/<target_name>
125+ let home = dirs::home_dir().context("Unable to determine home directory")?;
126+ let config_dir = home.join(".config");
127+ let target = config_dir.join(target_name);
128+
129+ if !config_dir.exists() {
130+ fs::create_dir_all(&config_dir)
131+ .with_context(|| format!("Failed creating {}", config_dir.display()))?;
132+ }
133+
134+ if target.exists() || symlink_exists(&target) {
135+ remove_path(&target)
136+ .with_context(|| format!("Failed removing existing {}", target.display()))?;
137+ }
138+
139+ // Create symlink
140+ #[cfg(unix)]
141+ {
142+ std::os::unix::fs::symlink(&source, &target)
143+ .with_context(|| format!(
144+ "Failed creating symlink {} -> {}",
145+ target.display(),
146+ source.display()
147+ ))?;
148+ }
149+ #[cfg(windows)]
150+ {
151+ if source.is_dir() {
152+ std::os::windows::fs::symlink_dir(&source, &target)
153+ .with_context(|| format!(
154+ "Failed creating symlink {} -> {}",
155+ target.display(),
156+ source.display()
157+ ))?;
158+ } else {
159+ std::os::windows::fs::symlink_file(&source, &target)
160+ .with_context(|| format!(
161+ "Failed creating symlink {} -> {}",
162+ target.display(),
163+ source.display()
164+ ))?;
165+ }
166+ }
167+
168+ println!(
169+ "Linked {} -> {}",
170+ source.display(),
171+ target.display()
172+ );
173+ Ok(())
174+}
175+
176+fn cmd_update() -> Result<()> {
177+ ensure_dotman_dir()?;
178+ if which::which("git").is_err() {
179+ bail!("git is not installed or not found in PATH");
180+ }
181+
182+ let root = Path::new(DOTMAN_DIR);
183+ let mut updated = 0usize;
184+ let mut skipped = 0usize;
185+
186+ for entry in fs::read_dir(root).with_context(|| format!("Reading {}", DOTMAN_DIR))? {
187+ let entry = entry?;
188+ let path = entry.path();
189+ if !path.is_dir() {
190+ continue;
191+ }
192+ if !path.join(".git").exists() {
193+ skipped += 1;
194+ continue;
195+ }
196+
197+ println!("Updating {}", path.display());
198+ let status = Command::new("git")
199+ .args(["-C", path.to_string_lossy().as_ref(), "pull", "--ff-only"])
200+ .status()
201+ .with_context(|| format!("Running git pull in {}", path.display()))?;
202+ if status.success() {
203+ updated += 1;
204+ } else {
205+ eprintln!("git pull failed in {} with status {}", path.display(), status);
206+ }
207+ }
208+
209+ println!("Updated {} repositories (skipped {}).", updated, skipped);
210+ Ok(())
211+}
212+
213+fn cmd_completions(shell: Shell) -> Result<()> {
214+ use clap::CommandFactory;
215+ let mut cmd = Cli::command();
216+ let name = cmd.get_name().to_string();
217+ match shell {
218+ Shell::Bash => generate(shells::Bash, &mut cmd, name, &mut io::stdout()),
219+ Shell::Zsh => generate(shells::Zsh, &mut cmd, name, &mut io::stdout()),
220+ Shell::Fish => generate(shells::Fish, &mut cmd, name, &mut io::stdout()),
221+ Shell::PowerShell => generate(shells::PowerShell, &mut cmd, name, &mut io::stdout()),
222+ Shell::Elvish => generate(shells::Elvish, &mut cmd, name, &mut io::stdout()),
223+ }
224+ Ok(())
225+}
226+
227+fn symlink_exists(path: &Path) -> bool {
228+ match fs::symlink_metadata(path) {
229+ Ok(md) => md.file_type().is_symlink(),
230+ Err(_) => false,
231+ }
232+}
233+
234+fn remove_path(path: &Path) -> Result<()> {
235+ match fs::symlink_metadata(path) {
236+ Ok(md) if md.file_type().is_symlink() => {
237+ fs::remove_file(path).with_context(|| format!("Removing symlink {}", path.display()))
238+ }
239+ Ok(md) if md.is_dir() => {
240+ fs::remove_dir_all(path).with_context(|| format!("Removing directory {}", path.display()))
241+ }
242+ Ok(_md) => fs::remove_file(path).with_context(|| format!("Removing file {}", path.display())),
243+ Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
244+ Err(e) => Err(e).with_context(|| format!("Accessing {}", path.display())),
245+ }
246+}