From 93c0f899c4a4665b0a406d397f0b682acfd7f679 Mon Sep 17 00:00:00 2001 From: Ivan Tse Date: Wed, 13 Apr 2022 13:07:06 -0400 Subject: [PATCH] Add ssh server functionality (#150) * Add ssh server functionality via `wish` library This adds a `-server` flag which will spin up an ssh server that executes the slides bubbletea application upon each ssh session. --- go.mod | 10 ++++ go.sum | 26 ++++++++++ internal/cmd/serve.go | 89 +++++++++++++++++++++++++++++++++++ internal/server/middleware.go | 32 +++++++++++++ internal/server/server.go | 48 +++++++++++++++++++ main.go | 67 ++++++++++++++------------ 6 files changed, 243 insertions(+), 29 deletions(-) create mode 100644 internal/cmd/serve.go create mode 100644 internal/server/middleware.go create mode 100644 internal/server/server.go diff --git a/go.mod b/go.mod index c1ef831..4f8486d 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,9 @@ require ( github.com/charmbracelet/bubbletea v0.20.0 github.com/charmbracelet/glamour v0.5.0 github.com/charmbracelet/lipgloss v0.5.0 + github.com/charmbracelet/wish v0.3.1 + github.com/gliderlabs/ssh v0.3.3 + github.com/muesli/coral v1.0.0 github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 github.com/stretchr/testify v1.7.1 gopkg.in/yaml.v2 v2.4.0 @@ -14,23 +17,30 @@ require ( require ( github.com/alecthomas/chroma v0.10.0 // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3 // indirect + github.com/charmbracelet/keygen v0.3.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/microcosm-cc/bluemonday v1.0.18 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/yuin/goldmark v1.4.7 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect + golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect diff --git a/go.sum b/go.sum index 6b0cdc2..d4aa871 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3 h1:w2ANoiT4ubmh4Nssa3/QW1M7lj3FZkma8f8V5aBDxXM= +github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= github.com/charmbracelet/bubbles v0.10.3 h1:fKarbRaObLn/DCsZO4Y3vKCwRUzynQD9L+gGev1E/ho= github.com/charmbracelet/bubbles v0.10.3/go.mod h1:jOA+DUF1rjZm7gZHcNyIVW+YrBPALKfpGVdJu8UiJsA= github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA= @@ -12,19 +16,28 @@ github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNo github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g= github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc= github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y= +github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM= github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= +github.com/charmbracelet/wish v0.3.1 h1:B3GZop29tFf/Jh8oMyeTX+iSP3X2bR8AQduf/h4OdRQ= +github.com/charmbracelet/wish v0.3.1/go.mod h1:+EgtczC00Oocx5Y237jSGpD/rOcSdq97Intl0ITHwos= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA= +github.com/gliderlabs/ssh v0.3.3/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= @@ -39,9 +52,13 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo= github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/coral v1.0.0 h1:odyqkoEg4aJAINOzvnjN4tUsdp+Zleccs7tRIAkkYzU= +github.com/muesli/coral v1.0.0/go.mod h1:bf91M/dkp7iHQw73HOoR9PekdTJMTD6ihJgWoDitde8= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= @@ -57,7 +74,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= @@ -68,6 +88,10 @@ github.com/yuin/goldmark v1.4.7 h1:KHHlQL4EKBZ43vpA1KBEQHfodk4JeIgeb0xJLg7rvDI= github.com/yuin/goldmark v1.4.7/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -77,6 +101,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 h1:BXxu8t6QN0G1uff4bzZzSkpsax8+ALqTGUtz08QrV00= @@ -85,6 +110,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/cmd/serve.go b/internal/cmd/serve.go new file mode 100644 index 0000000..67ea6a0 --- /dev/null +++ b/internal/cmd/serve.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "context" + "log" + "os" + "os/signal" + "strconv" + "syscall" + "time" + + "github.com/maaslalani/slides/internal/model" + "github.com/maaslalani/slides/internal/navigation" + "github.com/maaslalani/slides/internal/server" + "github.com/muesli/coral" +) + +var ( + host string + port int + keyPath string + err error + fileName string + + ServeCmd = &coral.Command{ + Use: "serve ", + Aliases: []string{"server"}, + Short: "Start an SSH server to run slides", + Args: coral.ArbitraryArgs, + RunE: func(cmd *coral.Command, args []string) error { + k := os.Getenv("SLIDES_SERVER_KEY_PATH") + if k != "" { + keyPath = k + } + h := os.Getenv("SLIDES_SERVER_HOST") + if h != "" { + host = h + } + p := os.Getenv("SLIDES_SERVER_PORT") + if p != "" { + port, _ = strconv.Atoi(p) + } + + if len(args) > 0 { + fileName = args[0] + } + + presentation := model.Model{ + Page: 0, + Date: time.Now().Format("2006-01-02"), + FileName: fileName, + Search: navigation.NewSearch(), + } + err = presentation.Load() + if err != nil { + return err + } + + s, err := server.NewServer(keyPath, host, port, presentation) + if err != nil { + return err + } + + done := make(chan os.Signal, 1) + signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + log.Printf("Starting Slides server on %s:%d", host, port) + go func() { + if err = s.Start(); err != nil { + log.Fatalln(err) + } + }() + + <-done + log.Print("Stopping Slides server") + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer func() { cancel() }() + if err := s.Shutdown(ctx); err != nil { + return err + } + return err + }, + } +) + +func init() { + ServeCmd.Flags().StringVar(&keyPath, "keyPath", "slides", "Server private key path") + ServeCmd.Flags().StringVar(&host, "host", "localhost", "Server host to bind to") + ServeCmd.Flags().IntVar(&port, "port", 53531, "Server port to bind to") +} diff --git a/internal/server/middleware.go b/internal/server/middleware.go new file mode 100644 index 0000000..809d1d9 --- /dev/null +++ b/internal/server/middleware.go @@ -0,0 +1,32 @@ +package server + +import ( + "fmt" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/wish" + bm "github.com/charmbracelet/wish/bubbletea" + "github.com/gliderlabs/ssh" + "github.com/muesli/termenv" +) + +func slidesMiddleware(srv *Server) wish.Middleware { + newProg := func(m tea.Model, opts ...tea.ProgramOption) *tea.Program { + p := tea.NewProgram(m, opts...) + return p + } + teaHandler := func(s ssh.Session) *tea.Program { + _, _, active := s.Pty() + if !active { + fmt.Println("no active terminal, skipping") + err := s.Exit(1) + if err != nil { + fmt.Println("Error exiting session") + } + return nil + } + return newProg(srv.presentation, tea.WithInput(s), tea.WithOutput(s), tea.WithAltScreen()) + } + return bm.MiddlewareWithProgramHandler(teaHandler, termenv.ANSI256) +} + diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..05a06c9 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,48 @@ +package server + +import ( + "context" + "fmt" + + "github.com/charmbracelet/wish" + "github.com/gliderlabs/ssh" + "github.com/maaslalani/slides/internal/model" +) + +type Server struct { + host string + port int + srv *ssh.Server + presentation model.Model +} + +// NewServer creates a new server. +func NewServer(keyPath, host string, port int, presentation model.Model) (*Server, error) { + s := &Server{ + host: host, + port: port, + presentation: presentation, + } + srv, err := wish.NewServer( + wish.WithHostKeyPath(keyPath), + wish.WithAddress(fmt.Sprintf("%s:%d", host, port)), + wish.WithMiddleware( + slidesMiddleware(s), + ), + ) + if err != nil { + return nil, err + } + s.srv = srv + return s, nil +} + +// Start starts the ssh server. +func (s *Server) Start() error { + return s.srv.ListenAndServe() +} + +// Shutdown shuts down the server. +func (s *Server) Shutdown(ctx context.Context) error { + return s.srv.Shutdown(ctx) +} diff --git a/main.go b/main.go index 91c24d8..f215ab8 100644 --- a/main.go +++ b/main.go @@ -2,47 +2,56 @@ package main import ( _ "embed" - "fmt" "os" "time" tea "github.com/charmbracelet/bubbletea" + "github.com/maaslalani/slides/internal/cmd" "github.com/maaslalani/slides/internal/model" "github.com/maaslalani/slides/internal/navigation" + "github.com/muesli/coral" ) -func printError(err error) { - fmt.Fprintf(os.Stderr, `Error: %s -Usage: - slides +var ( + rootCmd = &coral.Command{ + Use: "slides ", + Short: "Terminal based presentation tool", + Args: coral.ArbitraryArgs, + RunE: func(cmd *coral.Command, args []string) error { + var err error + var fileName string -`, err.Error()) + if len(args) > 0 { + fileName = args[0] + } + + presentation := model.Model{ + Page: 0, + Date: time.Now().Format("2006-01-02"), + FileName: fileName, + Search: navigation.NewSearch(), + } + err = presentation.Load() + if err != nil { + return err + } + + p := tea.NewProgram(presentation, tea.WithAltScreen()) + err = p.Start() + return err + }, + } +) + +func init() { + rootCmd.AddCommand( + cmd.ServeCmd, + ) + rootCmd.CompletionOptions.DisableDefaultCmd = true } func main() { - var err error - var fileName string - - if len(os.Args) > 1 { - fileName = os.Args[1] - } - - presentation := model.Model{ - Page: 0, - Date: time.Now().Format("2006-01-02"), - FileName: fileName, - Search: navigation.NewSearch(), - } - err = presentation.Load() - if err != nil { - printError(err) - os.Exit(1) - } - - p := tea.NewProgram(presentation, tea.WithAltScreen()) - err = p.Start() - if err != nil { - printError(err) + if err := rootCmd.Execute(); err != nil { os.Exit(1) } }