feat: add --dry-run flag (#6)

* feat: add --dry-run flag to all commands

* feat: update copy-go with single copy.Replace

* refactor: make Config interface

* chore(config): cleanup unused

* feat: add config demo (1/?)

* chore: better naming for cfg

* refactor: move to pkg config

* refactor: use data for store configs

* refactor: split real and demo config

* feat: make use of dry run flag

Co-authored-by: Tran Hau <ngtranhau@gmail.com>
main
sudo pacman -Syu 2021-04-22 16:36:11 +07:00 committed by GitHub
parent ecafc5b74d
commit 4fa12066d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 127 additions and 490 deletions

View File

@ -1,13 +0,0 @@
import:
- ~/.config/alacritty/one-dark.yml
font:
normal:
family: Cascadia Code
style: Regular
bold:
family: Cascadia Code
style: Bold
size: 12

View File

@ -1,21 +0,0 @@
# https://github.com/alacritty/alacritty/wiki/Color-schemes
# Colors (One Dark)
colors:
primary:
background: "#282c34"
foreground: "#abb2bf"
cursor:
text: CellBackground
cursor: "#528bff"
selection:
text: CellForeground
background: "#3e4451"
normal:
black: "#5c6370"
red: "#e06c75"
green: "#98c379"
yellow: "#e5c07b"
blue: "#61afef"
magenta: "#c678dd"
cyan: "#56b6c2"
white: "#828997"

View File

@ -1,3 +0,0 @@
--theme="TwoDark"
--style="plain"

View File

@ -1,88 +0,0 @@
{
"apps": {
"nvim": {
"files": [
{
"internal": "config/nvim/init.vim",
"external": "~/.config/nvim/init.vim"
}
]
},
"vim": {
"files": [
{
"internal": "config/vim/vimrc",
"external": "~/.vimrc"
}
]
},
"bat": {
"dirs": [
{
"internal": "config/bat",
"external": "~/.config/bat"
}
]
},
"tmux": {
"files": [
{
"internal": "config/tmux/tmux.conf",
"external": "~/.tmux.conf"
}
]
},
"alacritty": {
"files": [
{
"internal": "config/alacritty/alacritty.yml",
"external": "~/.config/alacritty/alacritty.yml"
},
{
"internal": "config/alacritty/one-dark.yml",
"external": "~/.config/alacritty/one-dark.yml"
}
]
},
"i3": {
"files": [
{
"internal": "config/i3/config",
"external": "~/.config/i3/config"
}
]
},
"i3status": {
"files": [
{
"internal": "config/i3status/config",
"external": "~/.config/i3status/config"
}
]
},
"rofi": {
"files": [
{
"internal": "config/rofi/config.rasi",
"external": "~/.config/rofi/config.rasi"
}
]
},
"xinit": {
"files": [
{
"internal": "config/xinit/xinitrc",
"external": "~/.xinitrc"
}
]
},
"redshift": {
"files": [
{
"internal": "config/redshift/redshift.conf",
"external": "~/.config/redshift/redshift.conf"
}
]
}
}
}

View File

@ -1,139 +0,0 @@
# https://i3wm.org/docs/userguide.html
set $mod Mod4
font pango:Cascadia Code 12
# feh
# https://wiki.archlinux.org/index.php/Feh
exec --no-startup-id feh --no-fehbg --bg-fill --randomize /usr/share/backgrounds/archlinux/*
# redshift
# https://wiki.archlinux.org/index.php/Redshift
exec --no-startup-id redshift-gtk
# fcitx
# https://wiki.archlinux.org/index.php/fcitx
exec --no-startup-id fcitx
# i3lock
bindsym $mod+l exec --no-startup-id i3lock -e -c 000000
# adjust volume
set $refresh_i3status killall -SIGUSR1 i3status
bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +5% && $refresh_i3status
bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% && $refresh_i3status
bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status
bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle && $refresh_i3status
# adjust brightness
bindsym XF86MonBrightnessUp exec --no-startup-id xbacklight -inc 10
bindsym XF86MonBrightnessDown exec --no-startup-id xbacklight -dec 10
# use Mouse+$mod to move floating windows
floating_modifier $mod
# start a terminal
bindsym $mod+Return exec i3-sensible-terminal
# kill focused window
bindsym $mod+Shift+q kill
# rofi (a program launcher)
# https://wiki.archlinux.org/index.php/Rofi
bindsym $mod+d exec "rofi -combi-modi window,drun -show combi"
# change focus
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
# move focused window
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
# split in horizontal orientation
bindsym $mod+h split h
# split in vertical orientation
bindsym $mod+v split v
# enter fullscreen mode
bindsym $mod+f fullscreen toggle
# change container layout (stacked, tabbed, toggle split)
bindsym $mod+s layout stacking
bindsym $mod+w layout tabbed
bindsym $mod+e layout toggle split
# toggle tiling / floating
bindsym $mod+Shift+space floating toggle
# change focus between tiling / floating windows
bindsym $mod+space focus mode_toggle
# focus the parent container
bindsym $mod+a focus parent
# workspace
set $ws1 "1"
set $ws2 "2"
set $ws3 "3"
set $ws4 "4"
set $ws5 "5"
set $ws6 "6"
set $ws7 "7"
set $ws8 "8"
set $ws9 "9"
set $ws10 "10"
# switch to workspace
bindsym $mod+1 workspace number $ws1
bindsym $mod+2 workspace number $ws2
bindsym $mod+3 workspace number $ws3
bindsym $mod+4 workspace number $ws4
bindsym $mod+5 workspace number $ws5
bindsym $mod+6 workspace number $ws6
bindsym $mod+7 workspace number $ws7
bindsym $mod+8 workspace number $ws8
bindsym $mod+9 workspace number $ws9
bindsym $mod+0 workspace number $ws10
# move focused container to workspace
bindsym $mod+Shift+1 move container to workspace number $ws1
bindsym $mod+Shift+2 move container to workspace number $ws2
bindsym $mod+Shift+3 move container to workspace number $ws3
bindsym $mod+Shift+4 move container to workspace number $ws4
bindsym $mod+Shift+5 move container to workspace number $ws5
bindsym $mod+Shift+6 move container to workspace number $ws6
bindsym $mod+Shift+7 move container to workspace number $ws7
bindsym $mod+Shift+8 move container to workspace number $ws8
bindsym $mod+Shift+9 move container to workspace number $ws9
bindsym $mod+Shift+0 move container to workspace number $ws10
bindsym $mod+Shift+c reload
bindsym $mod+Shift+r restart
bindsym $mod+Shift+e exec "i3-nagbar -t warning -f 'pango:Cascadia Code 12' -m 'Do you want to exit i3?' -B 'Yes, exit i3' 'i3-msg exit'"
# resize window
mode "resize" {
bindsym Left resize shrink width 10 px or 10 ppt
bindsym Down resize grow height 10 px or 10 ppt
bindsym Up resize shrink height 10 px or 10 ppt
bindsym Right resize grow width 10 px or 10 ppt
bindsym Return mode "default"
bindsym Escape mode "default"
bindsym $mod+r mode "default"
}
bindsym $mod+r mode "resize"
# use i3status
bar {
position top
status_command i3status
}

View File

@ -1,39 +0,0 @@
general {
interval = 5
# https://github.com/joshdick/onedark.vim
colors = true
color_good = "#98C379"
color_degraded = "#E5C07B"
color_bad = "#E06C75"
}
order += "wireless _first_"
order += "ethernet _first_"
order += "battery all"
order += "volume master"
order += "tztime local"
wireless _first_ {
format_up = "Wifi: %essid"
format_down = "Wifi: down"
}
ethernet _first_ {
format_up = "Ethernet: %ip (%speed)"
format_down = "Ethernet: down"
}
battery all {
format = "%status %percentage %remaining"
}
volume master {
format = "Sound: %volume"
format_muted = "Sound: muted (%volume)"
device = "pulse"
}
tztime local {
format = "%Y-%m-%d %H:%M:%S"
}

View File

@ -1,7 +0,0 @@
[redshift]
location-provider=manual
[manual]
; Ho Chi Minh, Vietnam
lat=10.8188
lon=106.65186

View File

@ -1,4 +0,0 @@
configuration {
modi: "window,drun,combi";
font: "Cascadia Code 12";
}

View File

@ -1,19 +0,0 @@
# https://github.com/tmux/tmux/wiki/FAQ
# Switch panes using Alt without prefix
bind -n M-Left select-pane -L
bind -n M-Right select-pane -R
bind -n M-Up select-pane -U
bind -n M-Down select-pane -D
# Color
set -g default-terminal "tmux-256color"
# https://github.com/tmux-plugins/tpm
# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-pain-control'
# Initialize TMUX plugin manager
run '~/.tmux/plugins/tpm/tpm'

View File

@ -1,52 +0,0 @@
set breakindent
set completeopt=menuone,noinsert,noselect
set noswapfile
set number
set relativenumber
set scrolloff=4
set virtualedit=block
set whichwrap=<,>,[,]
" True color
" https://gist.github.com/XVilka/8346728
if (empty($TMUX))
if (has('termguicolors'))
set termguicolors
endif
endif
" Disable cursor styling
set guicursor=
" Install xclip or xsel
set clipboard+=unnamedplus
" FZF
set rtp+=~/.fzf
" Plugins config
let g:go_fmt_command='gopls'
let g:go_gopls_gofumpt=1
let g:go_version_warning=0
let g:lightline={'colorscheme':'onedark'}
" vim-plug
" https://github.com/junegunn/vim-plug
call plug#begin()
" Should use
Plug 'sheerun/vim-polyglot'
Plug 'preservim/nerdtree'
Plug 'machakann/vim-sandwich'
Plug 'itchyny/lightline.vim'
" Colorschemes
Plug 'joshdick/onedark.vim'
" Languages
Plug 'fatih/vim-go', {'tag': '*'}
call plug#end()
set background=dark
colorscheme onedark

View File

@ -1,7 +0,0 @@
# https://wiki.archlinux.org/index.php/fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx
# https://wiki.archlinux.org/index.php/i3
exec i3

12
data/data.json Normal file
View File

@ -0,0 +1,12 @@
{
"apps": {
"nvim": {
"files": [
{
"internal": "config/nvim/init.vim",
"external": "~/.config/nvim/init.vim"
}
]
}
}
}

62
file.go
View File

@ -1,62 +0,0 @@
package main
import (
"fmt"
"os"
"os/user"
"path/filepath"
copier "github.com/haunt98/copy-go"
)
const (
homeSymbol = '~'
)
type copyFn func(from, to string) error
func replaceFile(from, to string) error {
return replace(from, to, copier.CopyFile)
}
func replaceDir(from, to string) error {
return replace(from, to, copier.CopyDir)
}
func replace(from, to string, fn copyFn) error {
newFrom, err := replaceHomeSymbol(from)
if err != nil {
return fmt.Errorf("failed to replace home symbol %s: %w", from, err)
}
newTo, err := replaceHomeSymbol(to)
if err != nil {
return fmt.Errorf("failed to replace home symbol %s: %w", to, err)
}
if err := os.RemoveAll(newTo); err != nil {
return fmt.Errorf("failed to remove %s: %w", newTo, err)
}
if err := fn(newFrom, newTo); err != nil {
return fmt.Errorf("failed to copy from %s to %s: %w", newFrom, newTo, err)
}
return nil
}
// replace ~
// https://stackoverflow.com/a/17609894
func replaceHomeSymbol(path string) (string, error) {
if path == "" || path[0] != homeSymbol {
return path, nil
}
currentUser, err := user.Current()
if err != nil {
return "", err
}
newPath := filepath.Join(currentUser.HomeDir, path[1:])
return newPath, nil
}

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.16
require (
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/haunt98/color v0.1.0
github.com/haunt98/copy-go v0.4.0
github.com/haunt98/copy-go v0.5.0
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli/v2 v2.3.0
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect

4
go.sum
View File

@ -6,8 +6,8 @@ github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/haunt98/color v0.1.0 h1:qfP5oNI3aoUC8T+bH/JNVAg79ljyhTGpgfqSKWhkiQQ=
github.com/haunt98/color v0.1.0/go.mod h1:V4BPVUSuiOItuVZHRHUTkpxO7OYQiP0DSgIWMpC/2qs=
github.com/haunt98/copy-go v0.4.0 h1:ts47dExLyIWWrnLQmVc475Af3/LO111zeB58waSe02A=
github.com/haunt98/copy-go v0.4.0/go.mod h1:cK1mRlW7QXPHhe5YI1VtL/U4OjUbRLAtZnO/oucrwRI=
github.com/haunt98/copy-go v0.5.0 h1:8yy7Dx47BBtlHIFIXxcCIECZRoQB/JSgLN9yunqtLAQ=
github.com/haunt98/copy-go v0.5.0/go.mod h1:cK1mRlW7QXPHhe5YI1VtL/U4OjUbRLAtZnO/oucrwRI=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=

61
main.go
View File

@ -7,6 +7,7 @@ import (
"runtime"
"github.com/haunt98/color"
"github.com/haunt98/dotfiles/pkg/config"
"github.com/urfave/cli/v2"
)
@ -16,6 +17,7 @@ const (
// flags
verboseFlag = "verbose"
dryRunFlag = "dry-run"
// commands
installCommand = "install"
@ -24,6 +26,7 @@ const (
// usages
verboseUsage = "show what is going on"
dryRunUsage = "demo mode without actually changing anything"
installUsage = "install user configs from dotfiles"
updateUsage = "update dotfiles from user configs"
cleanUsage = "clean unused dotfiles"
@ -66,6 +69,10 @@ func main() {
Name: verboseFlag,
Usage: verboseUsage,
},
&cli.BoolFlag{
Name: dryRunFlag,
Usage: dryRunUsage,
},
},
Action: a.RunInstall,
},
@ -73,13 +80,33 @@ func main() {
Name: updateCommand,
Aliases: updateAliases,
Usage: updateUsage,
Action: a.RunUpdate,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: verboseFlag,
Usage: verboseUsage,
},
&cli.BoolFlag{
Name: dryRunFlag,
Usage: dryRunUsage,
},
},
Action: a.RunUpdate,
},
{
Name: cleanCommand,
Aliases: cleanAliases,
Usage: cleanUsage,
Action: a.RunClean,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: verboseFlag,
Usage: verboseUsage,
},
&cli.BoolFlag{
Name: dryRunFlag,
Usage: dryRunUsage,
},
},
Action: a.RunClean,
},
},
Action: a.RunHelp,
@ -93,6 +120,7 @@ func main() {
type action struct {
flags struct {
verbose bool
dryRun bool
}
}
@ -105,11 +133,10 @@ func (a *action) RunInstall(c *cli.Context) error {
a.getFlags(c)
a.log("start %s\n", installCommand)
cfg, err := LoadConfig(currentDir)
cfg, err := a.loadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
return err
}
a.log("config %+v\n", cfg)
if err := cfg.Install(); err != nil {
return fmt.Errorf("failed to install config: %w", err)
@ -122,11 +149,10 @@ func (a *action) RunUpdate(c *cli.Context) error {
a.getFlags(c)
a.log("start %s\n", updateCommand)
cfg, err := LoadConfig(currentDir)
cfg, err := a.loadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
return err
}
a.log("config %+v\n", cfg)
if err := cfg.Update(); err != nil {
return fmt.Errorf("failed to update config: %w", err)
@ -139,11 +165,10 @@ func (a *action) RunClean(c *cli.Context) error {
a.getFlags(c)
a.log("start %s\n", cleanCommand)
cfg, err := LoadConfig(currentDir)
cfg, err := a.loadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
return err
}
a.log("config %+v\n", cfg)
if err := cfg.Clean(); err != nil {
return fmt.Errorf("failed to clean config: %w", err)
@ -152,8 +177,22 @@ func (a *action) RunClean(c *cli.Context) error {
return nil
}
func (a *action) loadConfig() (config.Config, error) {
cfgReal, cfgDemo, err := config.LoadConfig(currentDir)
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
}
if a.flags.dryRun {
return cfgDemo, nil
}
return cfgReal, nil
}
func (a *action) getFlags(c *cli.Context) {
a.flags.verbose = c.Bool(verboseFlag)
a.flags.dryRun = c.Bool(dryRunFlag)
}
func (a *action) log(format string, v ...interface{}) {

7
pkg/config/config.go Normal file
View File

@ -0,0 +1,7 @@
package config
type Config interface {
Install() error
Update() error
Clean() error
}

19
pkg/config/config_demo.go Normal file
View File

@ -0,0 +1,19 @@
package config
type configDemo struct {
configApps
}
var _ Config = (*configDemo)(nil)
func (cd *configDemo) Install() error {
return nil
}
func (cd *configDemo) Update() error {
return nil
}
func (cd *configDemo) Clean() error {
return nil
}

View File

@ -1,4 +1,4 @@
package main
package config
import (
"encoding/json"
@ -6,18 +6,26 @@ import (
"fmt"
"os"
"path/filepath"
"github.com/haunt98/copy-go"
)
const (
configDirPath = "config"
configFile = "config.json"
configDirPath = "data"
configFile = "data.json"
)
type Config struct {
// Read from file
type config struct {
configApps
}
var _ Config = (*config)(nil)
type configApps struct {
Apps map[string]App `json:"apps"`
}
// Read from file
type App struct {
Files []Path `json:"files"`
Dirs []Path `json:"dirs"`
@ -28,39 +36,45 @@ type Path struct {
External string `json:"external"`
}
// Load config from file
func LoadConfig(path string) (result Config, err error) {
// LoadConfig return config, configDemo
func LoadConfig(path string) (*config, *configDemo, error) {
configPath := getConfigPath(path)
bytes, err := os.ReadFile(configPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
err = fmt.Errorf("file not exist %s: %w", configPath, err)
return
return nil, nil, fmt.Errorf("file not exist %s: %w", configPath, err)
}
err = fmt.Errorf("failed to read file%s: %w", configPath, err)
return
return nil, nil, fmt.Errorf("failed to read file%s: %w", configPath, err)
}
if err = json.Unmarshal(bytes, &result); err != nil {
err = fmt.Errorf("failed to unmarshal: %w", err)
return
var cfgApps configApps
if err = json.Unmarshal(bytes, &cfgApps); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal: %w", err)
}
return
cfg := config{
configApps: cfgApps,
}
cfgDemo := configDemo{
configApps: cfgApps,
}
return &cfg, &cfgDemo, nil
}
// internal -> external
func (c *Config) Install() error {
func (c *config) Install() error {
for _, app := range c.Apps {
for _, file := range app.Files {
if err := replaceFile(file.Internal, file.External); err != nil {
if err := copy.Replace(file.Internal, file.External); err != nil {
return fmt.Errorf("failed to remove and copy from %s to %s: %w", file.Internal, file.External, err)
}
}
for _, dir := range app.Dirs {
if err := replaceDir(dir.Internal, dir.External); err != nil {
if err := copy.Replace(dir.Internal, dir.External); err != nil {
return fmt.Errorf("failed to remove and copy from %s to %s: %w", dir.Internal, dir.External, err)
}
}
@ -70,16 +84,16 @@ func (c *Config) Install() error {
}
// external -> internal
func (c *Config) Update() error {
func (c *config) Update() error {
for _, app := range c.Apps {
for _, file := range app.Files {
if err := replaceFile(file.External, file.Internal); err != nil {
if err := copy.Replace(file.External, file.Internal); err != nil {
return fmt.Errorf("failed to remove and copy from %s to %s: %w", file.External, file.Internal, err)
}
}
for _, dir := range app.Dirs {
if err := replaceDir(dir.External, dir.Internal); err != nil {
if err := copy.Replace(dir.External, dir.Internal); err != nil {
return fmt.Errorf("failed to remove and copy from %s to %s: %w", dir.External, dir.Internal, err)
}
}
@ -88,7 +102,7 @@ func (c *Config) Update() error {
return nil
}
func (c *Config) Clean() error {
func (c *config) Clean() error {
files, err := os.ReadDir(configDirPath)
if err != nil {
return fmt.Errorf("failed to read dir %s: %w", configDirPath, err)