This commit is contained in:
2026-08-27 20:09:59 +01:00
parent 8bfd6b22c5
commit ab670b3ecd
9 changed files with 215 additions and 880 deletions
+2 -2
View File
@@ -87,7 +87,8 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&outputPath, "output", "o", "", "output directory (required)") rootCmd.PersistentFlags().StringVarP(&outputPath, "output", "o", "", "output directory (required)")
rootCmd.Flags().BoolVarP(&appendMode, "append", "a", false, "append to existing index.md") rootCmd.Flags().BoolVarP(&appendMode, "append", "a", false, "append to existing index.md")
rootCmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite existing output directory") rootCmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite existing output directory")
rootCmd.Flags().StringVarP(&singleFile, "input-file", "i", "", "single file input (append mode only)") rootCmd.Flags().StringVar(&singleFile, "input-file", "", "single file input (append mode only)")
rootCmd.Flags().BoolVarP(&tuiMode, "interactive", "i", false, "launch interactive TUI configuration wizard")
rootCmd.Flags().Int64Var(&maxSize, "max-size", config.DefaultMaxSize, "max file size in bytes") rootCmd.Flags().Int64Var(&maxSize, "max-size", config.DefaultMaxSize, "max file size in bytes")
rootCmd.Flags().IntVar(&maxDepth, "max-depth", config.DefaultMaxDepth, "max directory recursion depth") rootCmd.Flags().IntVar(&maxDepth, "max-depth", config.DefaultMaxDepth, "max directory recursion depth")
rootCmd.Flags().StringArrayVar(&ignorePatterns, "ignore", nil, "additional ignore patterns (glob)") rootCmd.Flags().StringArrayVar(&ignorePatterns, "ignore", nil, "additional ignore patterns (glob)")
@@ -99,7 +100,6 @@ func init() {
rootCmd.Flags().BoolVar(&frontendOnly, "frontend-only", false, "include only frontend file types and README.md") rootCmd.Flags().BoolVar(&frontendOnly, "frontend-only", false, "include only frontend file types and README.md")
rootCmd.Flags().BoolVar(&omitTest, "omit-test", false, "exclude test files (Go *_test.go, Java *Test.java) and strip #[cfg(test)] blocks from Rust") rootCmd.Flags().BoolVar(&omitTest, "omit-test", false, "exclude test files (Go *_test.go, Java *Test.java) and strip #[cfg(test)] blocks from Rust")
rootCmd.Flags().BoolVar(&omitMd, "omit-md", false, "omit all .md files except README.md") rootCmd.Flags().BoolVar(&omitMd, "omit-md", false, "omit all .md files except README.md")
rootCmd.Flags().BoolVar(&tuiMode, "tui", false, "launch interactive TUI configuration wizard")
} }
func run(cmd *cobra.Command, args []string) error { func run(cmd *cobra.Command, args []string) error {
+6
View File
@@ -5,23 +5,29 @@ go 1.26.4
require ( require (
charm.land/bubbles/v2 v2.2.1 charm.land/bubbles/v2 v2.2.1
charm.land/bubbletea/v2 v2.0.9 charm.land/bubbletea/v2 v2.0.9
charm.land/huh/v2 v2.0.3
charm.land/lipgloss/v2 v2.0.6 charm.land/lipgloss/v2 v2.0.6
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
) )
require ( require (
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 // indirect github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 // indirect
github.com/charmbracelet/x/ansi v0.11.8 // indirect github.com/charmbracelet/x/ansi v0.11.8 // indirect
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
github.com/mattn/go-runewidth v0.0.27 // indirect github.com/mattn/go-runewidth v0.0.27 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.9 // indirect github.com/spf13/pflag v1.0.9 // indirect
+20
View File
@@ -2,6 +2,8 @@ charm.land/bubbles/v2 v2.2.1 h1:Fq1+qm5hV6GkvzLQDhCBpXXE5tLgvh1PRriCLwSvIQU=
charm.land/bubbles/v2 v2.2.1/go.mod h1:wdMgn+sje1KNXdwFizIWjbf328fIUBxqEmJ/vYPo8yc= charm.land/bubbles/v2 v2.2.1/go.mod h1:wdMgn+sje1KNXdwFizIWjbf328fIUBxqEmJ/vYPo8yc=
charm.land/bubbletea/v2 v2.0.9 h1:DpJCMWKgzQK8SJv4zbKKFHAI10ymWy/evClPFk0k0f8= charm.land/bubbletea/v2 v2.0.9 h1:DpJCMWKgzQK8SJv4zbKKFHAI10ymWy/evClPFk0k0f8=
charm.land/bubbletea/v2 v2.0.9/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss= charm.land/bubbletea/v2 v2.0.9/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ= charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q= charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
@@ -10,31 +12,49 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 h1:rdnVWKgJpTVXKuKuJyxDJ+NFJdUaUqGvyGy61OcvlbA= github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 h1:rdnVWKgJpTVXKuKuJyxDJ+NFJdUaUqGvyGy61OcvlbA=
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro= github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro=
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ= github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0= github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=
github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw=
github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss= github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=
github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
-29
View File
@@ -1,29 +0,0 @@
package tui
type Checkbox struct {
Label string
Checked bool
}
func (c *Checkbox) Toggle() {
c.Checked = !c.Checked
}
func (c Checkbox) Render(focused bool) string {
marker := "[ ]"
if c.Checked {
marker = "[x]"
}
if c.Checked {
marker = checkedStyle.Render(marker)
} else {
marker = uncheckedStyle.Render(marker)
}
if focused {
return focusedStyle.Render("▶ " + marker + " " + c.Label)
}
return " " + marker + " " + labelStyle.Render(c.Label)
}
-487
View File
@@ -1,487 +0,0 @@
package tui
import (
"fmt"
"os"
"path/filepath"
"strings"
"charm.land/bubbles/v2/textarea"
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"dirmd/internal/config"
)
const (
fieldInputDir = iota
fieldOutputDir
fieldForce
fieldVerticalSlices
fieldSkipFrontend
fieldFrontendOnly
fieldOmitTest
fieldOmitMd
fieldProtonDrive
fieldDrivePath
fieldMaxDepth
fieldMaxSize
fieldIgnorePatterns
fieldGenerateBtn
fieldCancelBtn
fieldCount
)
type FormModel struct {
focusIndex int
inputDir textinput.Model
outputDir textinput.Model
drivePath textinput.Model
maxDepth textinput.Model
maxSize textinput.Model
ignoreArea textarea.Model
force Checkbox
verticalSlices Checkbox
skipFrontend Checkbox
frontendOnly Checkbox
omitTest Checkbox
omitMd Checkbox
protonDrive Checkbox
isDark bool
width int
height int
err string
}
func defaultOutputDir() string {
cwd, err := os.Getwd()
if err != nil {
return "/tmp/output_dirmd"
}
base := filepath.Base(cwd)
return filepath.Join("/tmp", base+"_dirmd")
}
func NewForm() FormModel {
inputDir := textinput.New()
inputDir.Placeholder = "."
inputDir.SetValue(".")
inputDir.SetWidth(60)
defOut := defaultOutputDir()
outputDir := textinput.New()
outputDir.Placeholder = defOut
outputDir.SetValue(defOut)
outputDir.SetWidth(60)
drivePath := textinput.New()
drivePath.Placeholder = "/my-files/md/"
drivePath.SetWidth(60)
maxDepth := textinput.New()
maxDepth.SetValue("20")
maxDepth.SetWidth(10)
maxSize := textinput.New()
maxSize.SetValue("512")
maxSize.SetWidth(10)
ignoreArea := textarea.New()
ignoreArea.Placeholder = "One pattern per line..."
ignoreArea.SetHeight(5)
ignoreArea.ShowLineNumbers = false
ignoreArea.CharLimit = 0
m := FormModel{
inputDir: inputDir,
outputDir: outputDir,
drivePath: drivePath,
maxDepth: maxDepth,
maxSize: maxSize,
ignoreArea: ignoreArea,
force: Checkbox{Label: "Force overwrite"},
verticalSlices: Checkbox{Label: "Vertical slices"},
skipFrontend: Checkbox{Label: "Skip frontend files"},
frontendOnly: Checkbox{Label: "Frontend only"},
omitTest: Checkbox{Label: "Omit test files"},
omitMd: Checkbox{Label: "Omit non-README markdown"},
protonDrive: Checkbox{Label: "Proton Drive upload"},
focusIndex: fieldInputDir,
isDark: true,
}
m.applyStyles()
m.focusActiveField()
return m
}
func (m *FormModel) SetDark(isDark bool) {
if m.isDark == isDark {
return
}
m.isDark = isDark
m.applyStyles()
}
func (m *FormModel) applyStyles() {
tiStyles := getDefaultInputStyles(m.isDark)
m.inputDir.SetStyles(tiStyles)
m.outputDir.SetStyles(tiStyles)
m.drivePath.SetStyles(tiStyles)
m.maxDepth.SetStyles(tiStyles)
m.maxSize.SetStyles(tiStyles)
taStyles := getDefaultTextareaStyles(m.isDark)
m.ignoreArea.SetStyles(taStyles)
}
func (m *FormModel) shouldSkipField(idx int) bool {
return idx == fieldDrivePath && !m.protonDrive.Checked
}
func isTextInputField(idx int) bool {
switch idx {
case fieldInputDir, fieldOutputDir, fieldDrivePath, fieldMaxDepth, fieldMaxSize, fieldIgnorePatterns:
return true
}
return false
}
func (m *FormModel) focusActiveField() tea.Cmd {
m.inputDir.Blur()
m.outputDir.Blur()
m.drivePath.Blur()
m.maxDepth.Blur()
m.maxSize.Blur()
m.ignoreArea.Blur()
var cmd tea.Cmd
switch m.focusIndex {
case fieldInputDir:
cmd = m.inputDir.Focus()
case fieldOutputDir:
cmd = m.outputDir.Focus()
case fieldDrivePath:
cmd = m.drivePath.Focus()
case fieldMaxDepth:
cmd = m.maxDepth.Focus()
case fieldMaxSize:
cmd = m.maxSize.Focus()
case fieldIgnorePatterns:
cmd = m.ignoreArea.Focus()
}
return cmd
}
func (m FormModel) Update(msg tea.Msg) (FormModel, tea.Cmd) {
var cmds []tea.Cmd
if k, ok := msg.(tea.KeyPressMsg); ok {
if isTextInputField(m.focusIndex) {
if isNavigateDown(k) || isNavigateUp(k) {
if isNavigateDown(k) {
m.focusIndex++
if m.focusIndex >= fieldCount {
m.focusIndex = 0
}
for m.shouldSkipField(m.focusIndex) {
m.focusIndex++
if m.focusIndex >= fieldCount {
m.focusIndex = 0
}
}
} else {
m.focusIndex--
if m.focusIndex < 0 {
m.focusIndex = fieldCount - 1
}
for m.shouldSkipField(m.focusIndex) {
m.focusIndex--
if m.focusIndex < 0 {
m.focusIndex = fieldCount - 1
}
}
}
cmd := m.focusActiveField()
if cmd != nil {
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
}
} else {
switch {
case isNavigateDown(k):
m.focusIndex++
if m.focusIndex >= fieldCount {
m.focusIndex = 0
}
for m.shouldSkipField(m.focusIndex) {
m.focusIndex++
if m.focusIndex >= fieldCount {
m.focusIndex = 0
}
}
cmd := m.focusActiveField()
if cmd != nil {
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
case isNavigateUp(k):
m.focusIndex--
if m.focusIndex < 0 {
m.focusIndex = fieldCount - 1
}
for m.shouldSkipField(m.focusIndex) {
m.focusIndex--
if m.focusIndex < 0 {
m.focusIndex = fieldCount - 1
}
}
cmd := m.focusActiveField()
if cmd != nil {
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
case isActivate(k):
switch m.focusIndex {
case fieldForce:
m.force.Toggle()
case fieldVerticalSlices:
m.verticalSlices.Toggle()
case fieldSkipFrontend:
m.skipFrontend.Toggle()
case fieldFrontendOnly:
m.frontendOnly.Toggle()
case fieldOmitTest:
m.omitTest.Toggle()
case fieldOmitMd:
m.omitMd.Toggle()
case fieldProtonDrive:
m.protonDrive.Toggle()
case fieldGenerateBtn:
return m, nil
case fieldCancelBtn:
return m, tea.Quit
}
return m, nil
}
}
}
switch m.focusIndex {
case fieldInputDir:
var cmd tea.Cmd
m.inputDir, cmd = m.inputDir.Update(msg)
cmds = append(cmds, cmd)
case fieldOutputDir:
var cmd tea.Cmd
m.outputDir, cmd = m.outputDir.Update(msg)
cmds = append(cmds, cmd)
case fieldDrivePath:
var cmd tea.Cmd
m.drivePath, cmd = m.drivePath.Update(msg)
cmds = append(cmds, cmd)
case fieldMaxDepth:
var cmd tea.Cmd
m.maxDepth, cmd = m.maxDepth.Update(msg)
cmds = append(cmds, cmd)
case fieldMaxSize:
var cmd tea.Cmd
m.maxSize, cmd = m.maxSize.Update(msg)
cmds = append(cmds, cmd)
case fieldIgnorePatterns:
var cmd tea.Cmd
m.ignoreArea, cmd = m.ignoreArea.Update(msg)
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
}
func (m FormModel) View() string {
usableWidth := m.width - 30
if usableWidth < 50 {
usableWidth = 50
}
if usableWidth > 80 {
usableWidth = 80
}
m.inputDir.SetWidth(usableWidth)
m.outputDir.SetWidth(usableWidth)
m.drivePath.SetWidth(usableWidth)
m.ignoreArea.SetWidth(usableWidth)
var b strings.Builder
b.WriteString(titleStyle.Render("dirmd"))
b.WriteString("\n\n")
b.WriteString(m.renderTextField("Input directory", m.inputDir, m.focusIndex == fieldInputDir))
b.WriteString("\n")
b.WriteString(m.renderTextField("Output directory", m.outputDir, m.focusIndex == fieldOutputDir))
b.WriteString("\n\n")
b.WriteString(m.force.Render(m.focusIndex == fieldForce))
b.WriteString("\n")
b.WriteString(m.verticalSlices.Render(m.focusIndex == fieldVerticalSlices))
b.WriteString("\n")
b.WriteString(m.skipFrontend.Render(m.focusIndex == fieldSkipFrontend))
b.WriteString("\n")
b.WriteString(m.frontendOnly.Render(m.focusIndex == fieldFrontendOnly))
b.WriteString("\n")
b.WriteString(m.omitTest.Render(m.focusIndex == fieldOmitTest))
b.WriteString("\n")
b.WriteString(m.omitMd.Render(m.focusIndex == fieldOmitMd))
b.WriteString("\n")
b.WriteString(m.protonDrive.Render(m.focusIndex == fieldProtonDrive))
b.WriteString("\n")
if m.protonDrive.Checked {
b.WriteString(m.renderTextField("Remote path", m.drivePath, m.focusIndex == fieldDrivePath))
b.WriteString("\n")
}
b.WriteString("\n")
b.WriteString(m.renderTextField("Max depth", m.maxDepth, m.focusIndex == fieldMaxDepth))
b.WriteString("\n")
b.WriteString(m.renderTextField("Max size (KB)", m.maxSize, m.focusIndex == fieldMaxSize))
b.WriteString("\n\n")
ignoreFocused := m.focusIndex == fieldIgnorePatterns
if ignoreFocused {
b.WriteString(focusedStyle.Render("▶ Ignore patterns:"))
} else {
b.WriteString(" " + labelStyle.Render("Ignore patterns:"))
}
b.WriteString("\n")
b.WriteString(m.ignoreArea.View())
b.WriteString("\n\n")
btnFocused := m.focusIndex == fieldGenerateBtn
if btnFocused {
b.WriteString(focusedStyle.Render("▶ [ Generate ]"))
} else {
b.WriteString(" " + labelStyle.Render("[ Generate ]"))
}
b.WriteString(" ")
cancelFocused := m.focusIndex == fieldCancelBtn
if cancelFocused {
b.WriteString(focusedStyle.Render("▶ [ Cancel ]"))
} else {
b.WriteString(" " + labelStyle.Render("[ Cancel ]"))
}
if m.err != "" {
b.WriteString("\n\n")
b.WriteString(errorStyle.Render(m.err))
}
b.WriteString("\n\n")
b.WriteString(footerStyle.Render("Tab/↑↓: navigate Space: toggle Enter: submit Esc: quit"))
content := appBorder.Render(b.String())
if m.width > 0 && m.height > 0 {
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content)
}
return content
}
func (m FormModel) renderTextField(label string, input textinput.Model, focused bool) string {
if focused {
return focusedStyle.Render("▶ "+label+":") + " " + input.View()
}
return " " + labelStyle.Render(label+":") + " " + input.View()
}
func (m FormModel) ToConfig() (*config.Config, error) {
inputDir := m.inputDir.Value()
if inputDir == "" {
inputDir = "."
}
outputDir := m.outputDir.Value()
if outputDir == "" {
return nil, fmt.Errorf("output directory required")
}
maxDepth, err := parseIntDefault(m.maxDepth.Value(), 20)
if err != nil {
return nil, fmt.Errorf("max depth: %w", err)
}
maxSizeKB, err := parseIntDefault(m.maxSize.Value(), 512)
if err != nil {
return nil, fmt.Errorf("max size: %w", err)
}
var ignores []string
raw := strings.TrimSpace(m.ignoreArea.Value())
if raw != "" {
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line != "" {
ignores = append(ignores, line)
}
}
}
if m.skipFrontend.Checked && m.frontendOnly.Checked {
return nil, fmt.Errorf("--skip-frontend and --frontend-only are mutually exclusive")
}
excludedExts := append([]string{}, config.DefaultExts...)
if m.skipFrontend.Checked {
excludedExts = append(excludedExts, config.FrontendExts...)
}
allIgnores := append([]string{}, config.DefaultIgnores...)
allIgnores = append(allIgnores, ignores...)
cfg := &config.Config{
InputPath: inputDir,
OutputPath: outputDir,
Force: m.force.Checked,
MaxSize: int64(maxSizeKB) * 1024,
MaxDepth: maxDepth,
Ignores: allIgnores,
Extensions: excludedExts,
ProtonDrive: m.protonDrive.Checked,
DrivePath: m.drivePath.Value(),
VerticalSlices: m.verticalSlices.Checked,
SkipFrontend: m.skipFrontend.Checked,
FrontendOnly: m.frontendOnly.Checked,
OmitTest: m.omitTest.Checked,
OmitMd: m.omitMd.Checked,
}
return cfg, nil
}
func (m *FormModel) SetError(msg string) {
m.err = msg
}
func (m *FormModel) SetSize(w, h int) {
m.width = w
m.height = h
}
func parseIntDefault(s string, def int) (int, error) {
s = strings.TrimSpace(s)
if s == "" {
return def, nil
}
var n int
_, err := fmt.Sscanf(s, "%d", &n)
if err != nil {
return 0, err
}
if n <= 0 {
return def, nil
}
return n, nil
}
-29
View File
@@ -1,29 +0,0 @@
package tui
import (
tea "charm.land/bubbletea/v2"
)
func isQuit(msg tea.KeyPressMsg) bool {
s := msg.String()
return s == "ctrl+c" || s == "esc"
}
func isNavigateDown(msg tea.KeyPressMsg) bool {
s := msg.String()
return s == "down" || s == "tab"
}
func isNavigateUp(msg tea.KeyPressMsg) bool {
s := msg.String()
return s == "up" || s == "shift+tab"
}
func isActivate(msg tea.KeyPressMsg) bool {
s := msg.String()
return s == "enter" || s == "space"
}
func isSubmit(msg tea.KeyPressMsg) bool {
return msg.String() == "enter"
}
-253
View File
@@ -1,253 +0,0 @@
package tui
import (
"fmt"
"strings"
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"dirmd/internal/config"
)
type State int
const (
StateForm State = iota
StateGenerating
StateDone
StateError
)
type genResultMsg struct {
result GenerateResult
}
type spinnerState struct {
frame int
}
func (s *spinnerState) View() string {
frames := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
return frames[s.frame%len(frames)]
}
type Model struct {
state State
form FormModel
result GenerateResult
spinner spinnerState
isDark bool
width int
height int
command string
}
func New() Model {
return Model{
state: StateForm,
form: NewForm(),
isDark: true,
}
}
func (m Model) Init() tea.Cmd {
return textinput.Blink
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.BackgroundColorMsg:
m.isDark = msg.IsDark()
m.form.SetDark(m.isDark)
return m, nil
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
m.form.SetSize(msg.Width, msg.Height)
return m, nil
case tea.KeyPressMsg:
if isQuit(msg) && m.state != StateGenerating {
return m, tea.Quit
}
switch m.state {
case StateForm:
if isSubmit(msg) && m.form.focusIndex == fieldGenerateBtn {
cfg, err := m.form.ToConfig()
if err != nil {
m.form.SetError(err.Error())
return m, nil
}
m.command = buildCommand(cfg)
m.state = StateGenerating
return m, func() tea.Msg {
return genResultMsg{result: Generate(cfg)}
}
}
if isSubmit(msg) && m.form.focusIndex == fieldCancelBtn {
return m, tea.Quit
}
var cmd tea.Cmd
m.form, cmd = m.form.Update(msg)
return m, cmd
case StateDone, StateError:
return m, tea.Quit
}
case genResultMsg:
m.result = msg.result
if m.result.Error != nil {
m.state = StateError
} else {
m.state = StateDone
}
return m, nil
}
if m.state == StateForm {
var cmd tea.Cmd
m.form, cmd = m.form.Update(msg)
return m, cmd
}
return m, nil
}
func buildCommand(cfg *config.Config) string {
var parts []string
parts = append(parts, "dirmd")
parts = append(parts, "-o", cfg.OutputPath)
if cfg.Force {
parts = append(parts, "-f")
}
if cfg.VerticalSlices {
parts = append(parts, "--vertical-slices")
}
if cfg.SkipFrontend {
parts = append(parts, "--skip-frontend")
}
if cfg.FrontendOnly {
parts = append(parts, "--frontend-only")
}
if cfg.OmitTest {
parts = append(parts, "--omit-test")
}
if cfg.OmitMd {
parts = append(parts, "--omit-md")
}
if cfg.ProtonDrive {
parts = append(parts, "--proton-drive")
if cfg.DrivePath != "" {
parts = append(parts, "--drive-path", cfg.DrivePath)
}
}
if cfg.MaxDepth != 20 {
parts = append(parts, "--max-depth", fmt.Sprintf("%d", cfg.MaxDepth))
}
if cfg.MaxSize != 512*1024 {
parts = append(parts, "--max-size", fmt.Sprintf("%d", cfg.MaxSize))
}
extraIgnores := extraIgnores(cfg)
for _, ig := range extraIgnores {
parts = append(parts, "--ignore", ig)
}
parts = append(parts, cfg.InputPath)
return strings.Join(parts, " ")
}
func extraIgnores(cfg *config.Config) []string {
defaultSet := make(map[string]bool)
for _, d := range config.DefaultIgnores {
defaultSet[d] = true
}
var extra []string
for _, ig := range cfg.Ignores {
if !defaultSet[ig] {
extra = append(extra, ig)
}
}
return extra
}
func (m Model) View() tea.View {
var content string
switch m.state {
case StateForm:
content = m.form.View()
case StateGenerating:
content = m.renderGenerating()
case StateDone:
content = m.renderDone()
case StateError:
content = m.renderError()
}
v := tea.NewView(content)
v.AltScreen = true
return v
}
func (m Model) renderGenerating() string {
var b strings.Builder
b.WriteString(titleStyle.Render("dirmd"))
b.WriteString("\n\n")
b.WriteString(fmt.Sprintf(" %s Generating...\n", m.spinner.View()))
b.WriteString("\n")
b.WriteString(footerStyle.Render("Please wait."))
content := appBorder.Render(b.String())
if m.width > 0 && m.height > 0 {
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content)
}
return content
}
func (m Model) renderDone() string {
var b strings.Builder
b.WriteString(titleStyle.Render("dirmd"))
b.WriteString("\n\n")
b.WriteString(successStyle.Render("✓ Done"))
b.WriteString("\n\n")
for _, f := range m.result.Files {
b.WriteString(fmt.Sprintf(" %s\n", f))
}
if m.result.Uploaded {
b.WriteString(successStyle.Render("\n Uploaded to Proton Drive"))
}
b.WriteString("\n\n")
b.WriteString(footerStyle.Render("Press any key to exit."))
content := appBorder.Render(b.String())
if m.width > 0 && m.height > 0 {
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content)
}
return content
}
func (m Model) renderError() string {
var b strings.Builder
b.WriteString(titleStyle.Render("dirmd"))
b.WriteString("\n\n")
b.WriteString(errorStyle.Render("✗ Error"))
b.WriteString("\n\n")
b.WriteString(fmt.Sprintf(" %s\n", m.result.Error.Error()))
b.WriteString("\n")
b.WriteString(footerStyle.Render("Press any key to exit."))
content := appBorder.Render(b.String())
if m.width > 0 && m.height > 0 {
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content)
}
return content
}
+187 -12
View File
@@ -3,27 +3,202 @@ package tui
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"strconv"
"strings"
tea "charm.land/bubbletea/v2" "charm.land/huh/v2"
"charm.land/huh/v2/spinner"
"dirmd/internal/config"
) )
func Run() error { func Run() error {
p := tea.NewProgram(New()) var (
m, err := p.Run() inputDir string
if err != nil { outputDir string
return fmt.Errorf("tui error: %w", err) force bool
verticalSlices bool
skipFrontend bool
frontendOnly bool
omitTest bool
mitMd bool
protonDrive bool
drivePath string
maxDepthStr string
maxSizeStr string
ignorePatterns string
)
inputDir = "."
maxDepthStr = "20"
maxSizeStr = "512"
defOut := defaultOutputDir()
outputDir = defOut
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Input directory").Value(&inputDir).Placeholder("."),
huh.NewInput().Title("Output directory").Value(&outputDir).Placeholder(defOut),
huh.NewConfirm().Title("Force overwrite?").Value(&force),
huh.NewConfirm().Title("Vertical slices?").Value(&verticalSlices),
huh.NewConfirm().Title("Skip frontend files?").Value(&skipFrontend),
huh.NewConfirm().Title("Frontend only?").Value(&frontendOnly),
huh.NewConfirm().Title("Omit test files?").Value(&omitTest),
huh.NewConfirm().Title("Omit non-README markdown?").Value(&mitMd),
huh.NewConfirm().Title("Upload to Proton Drive?").Value(&protonDrive),
huh.NewInput().Title("Max depth").Value(&maxDepthStr).Placeholder("20"),
huh.NewInput().Title("Max size (KB)").Value(&maxSizeStr).Placeholder("512"),
huh.NewText().Title("Ignore patterns (one per line)").Value(&ignorePatterns).Placeholder("One pattern per line..."),
),
huh.NewGroup(
huh.NewInput().Title("Remote path on Proton Drive").Value(&drivePath).Placeholder("/my-files/md/"),
).WithHideFunc(func() bool {
return !protonDrive
}),
)
if err := form.Run(); err != nil {
return fmt.Errorf("form: %w", err)
} }
if model, ok := m.(Model); ok { maxDepth, err := strconv.Atoi(strings.TrimSpace(maxDepthStr))
if model.command != "" { if err != nil || maxDepth <= 0 {
if model.result.Error != nil { maxDepth = 20
fmt.Fprintf(os.Stderr, "failed: %s\n", model.command) }
fmt.Fprintf(os.Stderr, "error: %v\n", model.result.Error)
} else { maxSizeKB, err := strconv.Atoi(strings.TrimSpace(maxSizeStr))
fmt.Fprintf(os.Stdout, "%s\n", model.command) if err != nil || maxSizeKB <= 0 {
maxSizeKB = 512
}
var ignores []string
raw := strings.TrimSpace(ignorePatterns)
if raw != "" {
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line != "" {
ignores = append(ignores, line)
} }
} }
} }
if skipFrontend && frontendOnly {
return fmt.Errorf("--skip-frontend and --frontend-only are mutually exclusive")
}
excludedExts := append([]string{}, config.DefaultExts...)
if skipFrontend {
excludedExts = append(excludedExts, config.FrontendExts...)
}
allIgnores := append([]string{}, config.DefaultIgnores...)
allIgnores = append(allIgnores, ignores...)
cfg := &config.Config{
InputPath: inputDir,
OutputPath: outputDir,
Force: force,
MaxSize: int64(maxSizeKB) * 1024,
MaxDepth: maxDepth,
Ignores: allIgnores,
Extensions: excludedExts,
ProtonDrive: protonDrive,
DrivePath: drivePath,
VerticalSlices: verticalSlices,
SkipFrontend: skipFrontend,
FrontendOnly: frontendOnly,
OmitTest: omitTest,
OmitMd: mitMd,
}
cmd := buildCommand(cfg)
fmt.Fprintf(os.Stderr, "%s\n", cmd)
var result GenerateResult
if err := spinner.New().
Title("Generating...").
Action(func() {
result = Generate(cfg)
}).
Run(); err != nil {
return fmt.Errorf("interrupted: %w", err)
}
if result.Error != nil {
return result.Error
}
for _, f := range result.Files {
fmt.Fprintf(os.Stdout, " %s\n", f)
}
if result.Uploaded {
fmt.Fprintln(os.Stdout, "Uploaded to Proton Drive")
}
return nil return nil
} }
func defaultOutputDir() string {
cwd, err := os.Getwd()
if err != nil {
return "/tmp/output_dirmd"
}
base := filepath.Base(cwd)
return filepath.Join("/tmp", base+"_dirmd")
}
func buildCommand(cfg *config.Config) string {
var parts []string
parts = append(parts, "dirmd")
parts = append(parts, "-o", cfg.OutputPath)
if cfg.Force {
parts = append(parts, "-f")
}
if cfg.VerticalSlices {
parts = append(parts, "--vertical-slices")
}
if cfg.SkipFrontend {
parts = append(parts, "--skip-frontend")
}
if cfg.FrontendOnly {
parts = append(parts, "--frontend-only")
}
if cfg.OmitTest {
parts = append(parts, "--omit-test")
}
if cfg.OmitMd {
parts = append(parts, "--omit-md")
}
if cfg.ProtonDrive {
parts = append(parts, "--proton-drive")
if cfg.DrivePath != "" {
parts = append(parts, "--drive-path", cfg.DrivePath)
}
}
if cfg.MaxDepth != 20 {
parts = append(parts, "--max-depth", strconv.Itoa(cfg.MaxDepth))
}
if cfg.MaxSize != 512*1024 {
parts = append(parts, "--max-size", strconv.FormatInt(cfg.MaxSize, 10))
}
for _, ig := range extraIgnores(cfg) {
parts = append(parts, "--ignore", ig)
}
parts = append(parts, cfg.InputPath)
return strings.Join(parts, " ")
}
func extraIgnores(cfg *config.Config) []string {
defaultSet := make(map[string]bool)
for _, d := range config.DefaultIgnores {
defaultSet[d] = true
}
var extra []string
for _, ig := range cfg.Ignores {
if !defaultSet[ig] {
extra = append(extra, ig)
}
}
return extra
}
-68
View File
@@ -1,68 +0,0 @@
package tui
import (
"charm.land/bubbles/v2/textarea"
"charm.land/bubbles/v2/textinput"
"charm.land/lipgloss/v2"
)
var (
appBorder = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("62")).
Padding(1, 2)
titleStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("117")).
MarginBottom(1)
labelStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("250"))
focusedStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("117"))
checkedStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("82"))
uncheckedStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("250"))
footerStyle = lipgloss.NewStyle().
MarginTop(1).
Foreground(lipgloss.Color("248"))
errorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("203")).
Bold(true)
successStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("82")).
Bold(true)
)
func getDefaultInputStyles(isDark bool) textinput.Styles {
s := textinput.DefaultStyles(isDark)
s.Focused.Prompt = lipgloss.NewStyle().Foreground(lipgloss.Color("117")).Bold(true)
s.Focused.Text = lipgloss.NewStyle().Foreground(lipgloss.Color("255"))
s.Focused.Placeholder = lipgloss.NewStyle().Foreground(lipgloss.Color("243"))
s.Blurred.Prompt = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
s.Blurred.Text = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
s.Blurred.Placeholder = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
return s
}
func getDefaultTextareaStyles(isDark bool) textarea.Styles {
s := textarea.DefaultStyles(isDark)
s.Focused.Prompt = lipgloss.NewStyle().Foreground(lipgloss.Color("117")).Bold(true)
s.Focused.Text = lipgloss.NewStyle().Foreground(lipgloss.Color("255"))
s.Focused.Placeholder = lipgloss.NewStyle().Foreground(lipgloss.Color("243"))
s.Focused.Base = lipgloss.NewStyle().Foreground(lipgloss.Color("255"))
s.Blurred.Prompt = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
s.Blurred.Text = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
s.Blurred.Placeholder = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
s.Blurred.Base = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
return s
}