package main import ( "context" "os" "os/signal" "syscall" "time" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" "go.arsenm.dev/itd/api" ) var client *api.Client func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) ctx := context.Background() ctx, _ = signal.NotifyContext( ctx, syscall.SIGINT, syscall.SIGTERM, ) // This goroutine ensures that itctl will exit // at most 200ms after the user sends SIGINT/SIGTERM. go func() { <-ctx.Done() time.Sleep(200 * time.Millisecond) os.Exit(0) }() app := cli.App{ Name: "itctl", HideHelpCommand: true, Flags: []cli.Flag{ &cli.StringFlag{ Name: "socket-path", Aliases: []string{"s"}, Value: api.DefaultAddr, Usage: "Path to itd socket", }, }, Commands: []*cli.Command{ { Name: "help", ArgsUsage: "", Usage: "Display help screen for a command", Action: helpCmd, }, { Name: "resources", Aliases: []string{"res"}, Usage: "Handle InfiniTime resource loading", Subcommands: []*cli.Command{ { Name: "load", ArgsUsage: "", Usage: "Load an InifiniTime resources package", Action: resourcesLoad, }, }, }, { Name: "filesystem", Aliases: []string{"fs"}, Usage: "Perform filesystem operations on the PineTime", Subcommands: []*cli.Command{ { Name: "list", ArgsUsage: "[dir]", Aliases: []string{"ls"}, Usage: "List a directory", Action: fsList, }, { Flags: []cli.Flag{ &cli.BoolFlag{ Name: "parents", Aliases: []string{"p"}, Usage: "Make parent directories if needed, no error if already existing", }, }, Name: "mkdir", ArgsUsage: "", Usage: "Create new directories", Action: fsMkdir, }, { Name: "move", ArgsUsage: " ", Aliases: []string{"mv"}, Usage: "Move a file or directory", Action: fsMove, }, { Name: "read", ArgsUsage: ` `, Usage: "Read a file from InfiniTime.", Description: `Read is used to read files from InfiniTime's filesystem. A "-" can be used to signify stdout`, Action: fsRead, }, { Flags: []cli.Flag{ &cli.BoolFlag{ Name: "recursive", Aliases: []string{"r", "R"}, Usage: "Remove directories and their contents recursively", }, }, Name: "remove", ArgsUsage: "", Aliases: []string{"rm"}, Usage: "Remove a file from InfiniTime", Action: fsRemove, }, { Name: "write", ArgsUsage: ` `, Usage: "Write a file to InfiniTime", Description: `Write is used to write files to InfiniTime's filesystem. A "-" can be used to signify stdin`, Action: fsWrite, }, }, }, { Name: "firmware", Aliases: []string{"fw"}, Usage: "Manage InfiniTime firmware", Subcommands: []*cli.Command{ { Flags: []cli.Flag{ &cli.PathFlag{ Name: "init-packet", Aliases: []string{"i"}, Usage: "Path to init packet (.dat file)", }, &cli.PathFlag{ Name: "firmware", Aliases: []string{"f"}, Usage: "Path to firmware image (.bin file)", }, &cli.PathFlag{ Name: "resources", Aliases: []string{"r"}, Usage: "Path to resources file (.zip file)", }, &cli.PathFlag{ Name: "archive", Aliases: []string{"a"}, Usage: "Path to firmware archive (.zip file)", }, }, Name: "upgrade", Aliases: []string{"upg"}, Usage: "Upgrade InfiniTime firmware using files or archive", Action: fwUpgrade, }, { Name: "version", Aliases: []string{"ver"}, Usage: "Get firmware version of InfiniTime", Action: fwVersion, }, }, }, { Name: "get", Usage: "Get information from InfiniTime", Subcommands: []*cli.Command{ { Name: "address", Aliases: []string{"addr"}, Usage: "Get InfiniTime's bluetooth address", Action: getAddress, }, { Name: "battery", Aliases: []string{"batt"}, Usage: "Get InfiniTime's battery percentage", Action: getBattery, }, { Name: "heart", Usage: "Get heart rate from InfiniTime", Action: getHeart, }, { Flags: []cli.Flag{ &cli.BoolFlag{Name: "shell"}, }, Name: "motion", Usage: "Get motion values from InfiniTime", Action: getMotion, }, { Name: "steps", Usage: "Get step count from InfiniTime", Action: getSteps, }, }, }, { Name: "notify", Usage: "Send notification to InfiniTime", Action: notify, }, { Name: "set", Usage: "Set information on InfiniTime", Subcommands: []*cli.Command{ { Name: "time", ArgsUsage: ``, Usage: "Set InfiniTime's clock to specified time", Action: setTime, }, }, }, { Name: "update", Usage: "Update information on InfiniTime", Aliases: []string{"upd"}, Subcommands: []*cli.Command{ { Name: "weather", Usage: "Force an immediate update of weather data", Action: updateWeather, }, }, }, { Name: "watch", Usage: "Watch a value for changes", Subcommands: []*cli.Command{ { Flags: []cli.Flag{ &cli.BoolFlag{Name: "json"}, &cli.BoolFlag{Name: "shell"}, }, Name: "heart", Usage: "Watch heart rate value for changes", Action: watchHeart, }, { Flags: []cli.Flag{ &cli.BoolFlag{Name: "json"}, &cli.BoolFlag{Name: "shell"}, }, Name: "steps", Usage: "Watch step count value for changes", Action: watchStepCount, }, { Flags: []cli.Flag{ &cli.BoolFlag{Name: "json"}, &cli.BoolFlag{Name: "shell"}, }, Name: "motion", Usage: "Watch motion coordinates for changes", Action: watchMotion, }, { Flags: []cli.Flag{ &cli.BoolFlag{Name: "json"}, &cli.BoolFlag{Name: "shell"}, }, Name: "battery", Aliases: []string{"batt"}, Usage: "Watch battery level value for changes", Action: watchBattLevel, }, }, }, }, Before: func(c *cli.Context) error { if !isHelpCmd() { newClient, err := api.New(c.String("socket-path")) if err != nil { return err } client = newClient } return nil }, After: func(*cli.Context) error { if client != nil { client.Close() } return nil }, } err := app.RunContext(ctx, os.Args) if err != nil { log.Fatal().Err(err).Msg("Error while running app") } } func helpCmd(c *cli.Context) error { cmdArgs := append([]string{os.Args[0]}, c.Args().Slice()...) cmdArgs = append(cmdArgs, "-h") return c.App.RunContext(c.Context, cmdArgs) } func isHelpCmd() bool { if len(os.Args) == 1 { return true } for _, arg := range os.Args { if arg == "-h" || arg == "help" { return true } } return false }