├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cmd ├── compile.go ├── create.go ├── diff.go ├── root.go ├── set.go └── version.go ├── example ├── .gitignore ├── Makefile ├── doc │ └── spreadsheet.xlsx ├── ls │ ├── at_home.ls │ ├── at_pick.ls │ ├── at_place.ls │ ├── ensure_grip.ls │ ├── ensure_ungrip.ls │ ├── get_task.ls │ ├── get_zone.ls │ ├── grip.ls │ ├── init.ls │ ├── main.ls │ ├── mv_home.ls │ ├── mv_pick.ls │ ├── mv_place.ls │ ├── mv_retreat.ls │ ├── mv_retreat_home.ls │ ├── mv_retreat_pick.ls │ ├── mv_retreat_place.ls │ ├── mv_to_pick.ls │ ├── mv_to_place.ls │ ├── sv_abort.ls │ ├── sv_home.ls │ ├── sv_pick.ls │ ├── sv_place.ls │ ├── ungrip.ls │ └── unsafe.ls └── src │ ├── at_home.ls │ ├── at_pick.ls │ ├── at_place.ls │ ├── ensure_grip.ls │ ├── ensure_ungrip.ls │ ├── get_task.ls │ ├── get_zone.ls │ ├── grip.ls │ ├── init.ls │ ├── main.ls │ ├── mv_home.ls │ ├── mv_pick.ls │ ├── mv_place.ls │ ├── mv_retreat.ls │ ├── mv_retreat_home.ls │ ├── mv_retreat_pick.ls │ ├── mv_retreat_place.ls │ ├── mv_to_pick.ls │ ├── mv_to_place.ls │ ├── sv_abort.ls │ ├── sv_home.ls │ ├── sv_pick.ls │ ├── sv_place.ls │ ├── ungrip.ls │ └── unsafe.ls ├── fexcel ├── compile │ ├── ast.go │ ├── errors.go │ ├── parser.go │ ├── parser_test.go │ ├── printer.go │ ├── printer_test.go │ └── testdata │ │ ├── test.golden │ │ ├── test.ls │ │ └── test.xlsx ├── config.go ├── config_test.go ├── create.go ├── create_test.go ├── diff.go ├── diff_test.go ├── errors.go ├── fexcel.go ├── file.go ├── file_test.go ├── set.go ├── set_test.go ├── strings.go ├── strings_test.go ├── target.go ├── target_test.go ├── testdata │ ├── numreg.va │ └── test.xlsx ├── type.go └── update.go ├── go.mod ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .fexcel.yaml 2 | ~*.* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.13.x 5 | - tip 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## V1.2.0 - 2020-01-08 4 | 5 | - Big reorganization/cleanup but functionality is largely the same. 6 | - Added tests. 7 | 8 | ## v1.1.0 - 2019-06-19 9 | 10 | ### Added 11 | 12 | - Check for an update to fexcel after succesful comment updates 13 | - `-noUpdate` flag to skip the aforementioned update check 14 | 15 | ### Changed 16 | 17 | - Simplified task reporting 18 | - Multiple hosts are now updated concurrently 19 | 20 | ## v1.0.0 - 2019-06-19 21 | 22 | - Initial stable release 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ONE Robotics Company 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fexcel 2 | 3 | __ _ 4 | / _| | | 5 | | |_ _____ _____ ___| | 6 | | _/ _ \ \/ / __/ _ \ | 7 | | || __/> < (_| __/ | 8 | |_| \___/_/\_\___\___|_| 9 | 10 | 11 | Manage your FANUC robot data with an Excel spreadsheet. 12 | 13 | Download the latest release [here](https://github.com/onerobotics/fexcel/releases/latest). 14 | 15 | [![Build Status](https://travis-ci.org/onerobotics/fexcel.svg "Travis CI status")](https://travis-ci.org/onerobotics/fexcel) 16 | 17 | ## Usage 18 | 19 | Make sure KAREL is unlocked under `Setup > Host Comm > HTTP`. 20 | 21 | fexcel [flags] 22 | fexcel [commmand] [flags] 23 | 24 | Run `fexcel help` for more information on usage. 25 | 26 | ## Example 27 | 28 | There is a `fexcel compile` example located in the `./example` directory. 29 | 30 | ## Commands 31 | 32 | | Command | Description | 33 | | ------- | ----------- | 34 | | compile | Compile a fexcel source file to a FANUC .ls file | 35 | | create | Create a spreadsheet based on a target's comments | 36 | | diff | Compare robot comments to spreadsheet (remote or local) | 37 | | help | Help about any command | 38 | | set | Set remote robot comments from spreadsheet | 39 | | version | Print the version number of fexcel | 40 | 41 | ## Global Flags 42 | 43 | | | Flag | Type | Description | Default | 44 | | - | ----------- | ---- | ----------- | ------- | 45 | | | --ains | string | start cell\* of analog input ids | | 46 | | | --aouts | string | start cell\* of analog output ids | | 47 | | | --constants | string | start cell\* of constant definitions | | 48 | | | --dins | string | start cell\* of digital input ids | | 49 | | | --douts | string | start cell\* of digital output ids | | 50 | | | --flags | string | start cell\* of flag ids | | 51 | | | --gins | string | start cell\* of group input ids | | 52 | | | --gouts | string | start cell\* of group output ids | | 53 | | -h| --help | | help for fexcel | | 54 | | | --noupdate | | don't check for fexcel updates | | 55 | | | --numregs | string | start cell\* of numeric register ids | | 56 | | | --offset | int | column offset between ids and comments | 1 | 57 | | | --posregs | string | start cell\* of position register ids | | 58 | | | --rins | string | start cell\* of robot input ids | | 59 | | | --routs | string | start cell\* of robot output ids | | 60 | | | --save | | save flagset to config file | | 61 | | | --sheet | string | default sheet to look at when unspecified in the start cell\* | "Sheet1" | 62 | | | --sregs | string | start cell\* of string register ids | | 63 | | | --timeout | int | timeout value in seconds (default 5) | 64 | | | --ualms | string | start cell\* of user alarm ids | | 65 | 66 | \**start cell flags can be optionally prefixed with a sheet name that 67 | overrides the default `-sheet` flag. (e.g. `--numregs Data:A2`). They 68 | can also include a custom offset (e.g. `--dins 6:IO:A2) where digital 69 | inputs are located on the "IO" sheet starting at A2 and the comments 70 | are in column G.* 71 | 72 | ## Details 73 | 74 | fexcel assumes that your spreadsheet has indices for a given item that start 75 | in the provided cell, and the comments for that item are in the column you 76 | provided plus the offset flag (default 1). 77 | 78 | e.g. in the above usage example, the numeric register ids start in cell A2 with 79 | comments starting in cell B2. Position registers ids start in cell D2 with 80 | comments starting in E2. Digital input ids start in cell A2 on the IO sheet. 81 | -------------------------------------------------------------------------------- /cmd/compile.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "path/filepath" 8 | 9 | "github.com/onerobotics/fexcel/fexcel" 10 | "github.com/onerobotics/fexcel/fexcel/compile" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var compileCmd = &cobra.Command{ 15 | Use: "compile spreadsheet.xlsx filename", 16 | Short: "Compile a fexcel source file to a FANUC .ls file", 17 | Args: validateCompileArgs, 18 | RunE: compileMain, 19 | } 20 | 21 | var ( 22 | o string 23 | silent bool 24 | ) 25 | 26 | func init() { 27 | compileCmd.Flags().StringVarP(&o, "output", "o", "", "Output file (e.g. filename.ls)") 28 | compileCmd.Flags().BoolVar(&silent, "silent", false, "Don't print any output") 29 | rootCmd.AddCommand(compileCmd) 30 | } 31 | 32 | func validateCompileArgs(cmd *cobra.Command, args []string) error { 33 | if len(args) != 2 { 34 | return errors.New("requires a spreadsheet and a source filename") 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func compileMain(cmd *cobra.Command, args []string) error { 41 | cmd.SilenceUsage = true 42 | 43 | if !silent { 44 | fmt.Printf(fexcel.Logo()) 45 | } 46 | 47 | xlspath, fpath := args[0], args[1] 48 | 49 | p, err := compile.NewPrinter(xlspath, globalCfg.FileConfig) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | src, err := ioutil.ReadFile(fpath) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | filename := filepath.Base(fpath) 60 | f, err := compile.Parse(filename, string(src)) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | err = p.Print(f) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | if o != "" { 71 | err = ioutil.WriteFile(o, []byte(p.Output()), 0644) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | if !silent { 77 | fmt.Printf("Wrote output to %s\n", o) 78 | } 79 | } else { 80 | fmt.Print(p.Output()) 81 | } 82 | 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/onerobotics/fexcel/fexcel" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var createCmd = &cobra.Command{ 13 | Use: "create spreadsheet.xlsx target", 14 | Short: "Create a spreadsheet based on a target's comments", 15 | Example: " fexcel create ./doc/spreadsheet.xlsx 192.168.100.101", 16 | Args: validateCreateArgs, 17 | RunE: createMain, 18 | } 19 | 20 | var ( 21 | headers bool 22 | template bool 23 | ) 24 | 25 | func init() { 26 | createCmd.Flags().BoolVar(&headers, "headers", false, "write column header names") 27 | createCmd.Flags().BoolVar(&template, "template", false, "ignore config file and cell specs flags; use fexcel default template instead") 28 | rootCmd.AddCommand(createCmd) 29 | } 30 | 31 | func validateCreateArgs(cmd *cobra.Command, args []string) error { 32 | if len(args) != 2 { 33 | return errors.New("requires a spreadsheet path and a target (IP or backup directory)") 34 | } 35 | 36 | // TODO: validate args[1]? 37 | 38 | return nil 39 | } 40 | 41 | func templateConfig() fexcel.FileConfig { 42 | return fexcel.FileConfig{ 43 | Numregs: "Sheet1:A2", 44 | Posregs: "Sheet1:D2", 45 | Flags: "Sheet1:G2", 46 | Sregs: "Sheet1:J2", 47 | Dins: "A2", 48 | Douts: "D2", 49 | Gins: "G2", 50 | Gouts: "J2", 51 | Rins: "M2", 52 | Routs: "P2", 53 | Ains: "S2", 54 | Aouts: "V2", 55 | Ualms: "Alarms:A2", 56 | Sheet: "IO", 57 | Offset: 1, 58 | } 59 | } 60 | 61 | func createMain(cmd *cobra.Command, args []string) error { 62 | fmt.Printf(fexcel.Logo()) 63 | 64 | fpath, targetPath := args[0], args[1] 65 | 66 | if template { 67 | globalCfg.FileConfig = templateConfig() 68 | } 69 | 70 | c, err := fexcel.NewCreator(fpath, globalCfg, headers, targetPath) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | err = c.Create(os.Stdout) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /cmd/diff.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/onerobotics/fexcel/fexcel" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var diffCmd = &cobra.Command{ 13 | Use: "diff spreadsheet.xlsx target(s)...", 14 | Short: "Compare robot comments to spreadsheet (remote or local)", 15 | Example: " fexcel diff spreadsheet.xlsx 192.168.100.101 192.168.100.102 ./backup/dir ./some/other/backup/dir", 16 | Args: validateDiffArgs, 17 | RunE: diffMain, 18 | } 19 | 20 | var ( 21 | all bool 22 | ) 23 | 24 | func init() { 25 | diffCmd.Flags().BoolVar(&all, "all", false, "show all comparisons in summary tables instead of just differences") 26 | rootCmd.AddCommand(diffCmd) 27 | } 28 | 29 | func validateDiffArgs(cmd *cobra.Command, args []string) error { 30 | if len(args) < 2 { 31 | return errors.New("requires a spreadsheet and at least one target (IP or backup directory)") 32 | } 33 | 34 | // TODO validate args[1:]? 35 | 36 | return nil 37 | } 38 | 39 | func diffMain(cmd *cobra.Command, args []string) error { 40 | fmt.Printf(fexcel.Logo()) 41 | 42 | fpath := args[0] 43 | 44 | d, err := fexcel.NewDiffCommand(fpath, globalCfg, args[1:]...) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | for dataType, _ := range d.Locations() { 50 | err := d.FprintTable(os.Stdout, dataType, all) 51 | if err != nil { 52 | return err 53 | } 54 | fmt.Fprintln(os.Stdout, "") 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/onerobotics/fexcel/fexcel" 11 | "github.com/spf13/cobra" 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | const configFile = ".fexcel.yaml" 16 | 17 | var ( 18 | cfgFile string 19 | save bool 20 | globalCfg fexcel.Config 21 | ) 22 | 23 | var rootCmd = &cobra.Command{ 24 | Use: "fexcel", 25 | Short: "Process a spreadsheet and report what fexcel sees", 26 | Args: validateRootArgs, 27 | RunE: rootMain, 28 | PersistentPostRunE: func(cmd *cobra.Command, args []string) error { 29 | if save { 30 | fmt.Printf("saving flagset to config file... ") 31 | if _, err := os.Stat(configFile); os.IsNotExist(err) { 32 | _, err := os.Create(configFile) 33 | if err != nil { 34 | return err 35 | } 36 | } 37 | err := viper.WriteConfig() 38 | if err != nil { 39 | return err 40 | } 41 | fmt.Println("done!") 42 | } 43 | 44 | if !globalCfg.NoUpdate { 45 | var c fexcel.GitHubUpdateChecker 46 | err := c.UpdateCheck(os.Stdout) 47 | if err != nil { 48 | return errors.New("failed to get latest version id from GitHub.") 49 | } 50 | } 51 | 52 | return nil 53 | }, 54 | } 55 | 56 | func init() { 57 | cobra.OnInitialize(initConfig) 58 | 59 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./"+configFile+")") 60 | rootCmd.PersistentFlags().BoolVarP(&save, "save", "", false, "save flagset to config file") 61 | rootCmd.PersistentFlags().BoolVar(&globalCfg.NoUpdate, "noupdate", false, "don't check for fexcel updates") 62 | 63 | rootCmd.PersistentFlags().IntVarP(&globalCfg.Timeout, "timeout", "", 5, "timeout value in seconds") 64 | 65 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Sheet, "sheet", "Sheet1", "default sheet to look at when unspecified in the start cell") 66 | rootCmd.PersistentFlags().IntVar(&globalCfg.FileConfig.Offset, "offset", 1, "column offset between ids and comments") 67 | 68 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Constants, "constants", "", "start cell of constant ids") 69 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Numregs, "numregs", "", "start cell of numeric register ids") 70 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Posregs, "posregs", "", "start cell of position register ids") 71 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Sregs, "sregs", "", "start cell of string register ids") 72 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Ualms, "ualms", "", "start cell of user alarm ids") 73 | 74 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Ains, "ains", "", "start cell of analog input ids") 75 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Aouts, "aouts", "", "start cell of analog output ids") 76 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Dins, "dins", "", "start cell of digital input ids") 77 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Douts, "douts", "", "start cell of digital output ids") 78 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Flags, "flags", "", "start cell of flag ids") 79 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Gins, "gins", "", "start cell of group input ids") 80 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Gouts, "gouts", "", "start cell of group output ids") 81 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Rins, "rins", "", "start cell of robot input ids") 82 | rootCmd.PersistentFlags().StringVar(&globalCfg.FileConfig.Routs, "routs", "", "start cell of robot output ids") 83 | 84 | viper.BindPFlag("timeout", rootCmd.PersistentFlags().Lookup("timeout")) 85 | 86 | viper.BindPFlag("fileconfig.sheet", rootCmd.PersistentFlags().Lookup("sheet")) 87 | viper.BindPFlag("fileconfig.offset", rootCmd.PersistentFlags().Lookup("offset")) 88 | 89 | viper.BindPFlag("fileconfig.numregs", rootCmd.PersistentFlags().Lookup("numregs")) 90 | viper.BindPFlag("fileconfig.posregs", rootCmd.PersistentFlags().Lookup("posregs")) 91 | viper.BindPFlag("fileconfig.sregs", rootCmd.PersistentFlags().Lookup("sregs")) 92 | viper.BindPFlag("fileconfig.ualms", rootCmd.PersistentFlags().Lookup("ualms")) 93 | 94 | viper.BindPFlag("fileconfig.ains", rootCmd.PersistentFlags().Lookup("ains")) 95 | viper.BindPFlag("fileconfig.aouts", rootCmd.PersistentFlags().Lookup("aouts")) 96 | viper.BindPFlag("fileconfig.dins", rootCmd.PersistentFlags().Lookup("dins")) 97 | viper.BindPFlag("fileconfig.douts", rootCmd.PersistentFlags().Lookup("douts")) 98 | viper.BindPFlag("fileconfig.flags", rootCmd.PersistentFlags().Lookup("flags")) 99 | viper.BindPFlag("fileconfig.gins", rootCmd.PersistentFlags().Lookup("gins")) 100 | viper.BindPFlag("fileconfig.gouts", rootCmd.PersistentFlags().Lookup("gouts")) 101 | viper.BindPFlag("fileconfig.rins", rootCmd.PersistentFlags().Lookup("rins")) 102 | viper.BindPFlag("fileconfig.routs", rootCmd.PersistentFlags().Lookup("routs")) 103 | } 104 | 105 | func initConfig() { 106 | ext := filepath.Ext(configFile) 107 | name := strings.TrimSuffix(configFile, ext) 108 | viper.SetConfigName(name) 109 | viper.SetConfigType(ext[1:]) // remove . 110 | viper.AddConfigPath(".") 111 | 112 | if err := viper.ReadInConfig(); err == nil { 113 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 114 | viper.Unmarshal(&globalCfg) 115 | } else { 116 | if _, ok := err.(viper.ConfigFileNotFoundError); ok { 117 | // config file not found, but that's ok 118 | } else { 119 | // config file found, but another error was produced. 120 | fmt.Println("Error reading config file: ", err) 121 | } 122 | } 123 | } 124 | 125 | func validateRootArgs(cmd *cobra.Command, args []string) error { 126 | if len(args) != 1 { 127 | return errors.New("requires a spreadsheet") 128 | } 129 | 130 | return nil 131 | } 132 | 133 | func rootMain(cmd *cobra.Command, args []string) error { 134 | fmt.Printf(fexcel.Logo()) 135 | 136 | fpath := args[0] 137 | 138 | f, err := fexcel.OpenFile(fpath, globalCfg.FileConfig) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | if len(f.Locations) == 0 { 144 | fmt.Println("No location flags specified.") 145 | return nil 146 | } 147 | 148 | for d, _ := range f.Locations { 149 | defs, err := f.Definitions(d) 150 | if err != nil { 151 | return err 152 | } 153 | 154 | fmt.Printf("Found %d %ss.\n", len(defs), d) 155 | } 156 | 157 | return nil 158 | } 159 | 160 | func Execute() { 161 | if err := rootCmd.Execute(); err != nil { 162 | os.Exit(1) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /cmd/set.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/olekukonko/tablewriter" 12 | "github.com/onerobotics/fexcel/fexcel" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var setCmd = &cobra.Command{ 17 | Use: "set ./path/to/spreadsheet.xlsx ipAddress [more ipAddresses]", 18 | Short: "Set FANUC robots comments based on the provided Excel spreadsheet", 19 | Args: validateSetArgs, 20 | RunE: setMain, 21 | } 22 | 23 | func init() { 24 | rootCmd.AddCommand(setCmd) 25 | } 26 | 27 | func validateSetArgs(cmd *cobra.Command, args []string) error { 28 | if len(args) < 2 { 29 | return errors.New("requires a spreadsheet and at least one host IP address") 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func setMain(cmd *cobra.Command, args []string) error { 36 | fmt.Printf(fexcel.Logo()) 37 | 38 | fpath, hosts := args[0], args[1:] 39 | 40 | setCmd, err := fexcel.NewSetCommand(fpath, globalCfg, hosts...) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | startTime := time.Now() 46 | result, err := setCmd.Execute() 47 | // we will use err later 48 | 49 | table := tablewriter.NewWriter(os.Stdout) 50 | table.SetAutoWrapText(false) 51 | table.SetAutoFormatHeaders(false) 52 | 53 | header := []string{""} 54 | sheetRow := []string{filepath.Base(fpath)} 55 | total := 0 56 | 57 | types := []fexcel.Type{fexcel.Ain, fexcel.Aout, fexcel.Din, fexcel.Dout, fexcel.Flag, fexcel.Gin, fexcel.Gout, fexcel.Numreg, fexcel.Posreg, fexcel.Rin, fexcel.Rout, fexcel.Sreg, fexcel.Ualm} 58 | for _, t := range types { 59 | header = append(header, t.String()) 60 | defCount := len(setCmd.Definitions[t]) 61 | total += defCount 62 | sheetRow = append(sheetRow, strconv.Itoa(defCount)) 63 | } 64 | header = append(header, "Total") 65 | table.SetHeader(header) 66 | sheetRow = append(sheetRow, strconv.Itoa(total)) 67 | table.Append(sheetRow) 68 | 69 | for _, host := range setCmd.Hosts() { 70 | total = 0 71 | row := []string{host} 72 | 73 | for _, t := range types { 74 | count := result.Counts[host][t] 75 | row = append(row, strconv.Itoa(count)) 76 | total += count 77 | } 78 | 79 | row = append(row, strconv.Itoa(total)) 80 | table.Append(row) 81 | } 82 | 83 | table.Render() 84 | 85 | fmt.Printf("Finished in %s.\n\n", time.Since(startTime)) 86 | 87 | return err 88 | } 89 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/onerobotics/fexcel/fexcel" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var ( 11 | versionCmd = &cobra.Command{ 12 | Use: "version", 13 | Short: "Print the version number of fexcel", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | fmt.Printf("fexcel-v%s", fexcel.Version) 16 | }, 17 | } 18 | ) 19 | 20 | func init() { 21 | rootCmd.AddCommand(versionCmd) 22 | } 23 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | LSS_FILES := $(wildcard src/*.ls) 2 | 3 | LS_FILES = $(subst src,ls,$(patsubst %.ls,%.ls,$(LSS_FILES))) 4 | TP_FILES = $(subst ls,bin,$(patsubst %.ls,%.tp,$(LS_FILES))) 5 | 6 | ls/%.ls: src/%.ls doc/spreadsheet.xlsx 7 | fexcel compile --sheet Sheet1 --dins P2 --douts S2 --rins J2 --routs M2 --numregs A2 --posregs D2 --constants G2 --ualms V2 --noupdate "doc/spreadsheet.xlsx" $< -o $@ 8 | 9 | bin/%.tp: ls/%.ls 10 | #tplint $< -I src 11 | #maketp $< $@ 12 | touch $@ 13 | 14 | all: ${LS_FILES} ${TP_FILES} 15 | 16 | .PHONY: clean 17 | 18 | clean: 19 | rm ls/*.ls 20 | rm bin/*.tp 21 | -------------------------------------------------------------------------------- /example/doc/spreadsheet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onerobotics/fexcel/69bcbf10f4f2ffb851f44e971eb6fb0ddfb48c95/example/doc/spreadsheet.xlsx -------------------------------------------------------------------------------- /example/ls/at_home.ls: -------------------------------------------------------------------------------- 1 | /PROG AT_HOME 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /MN 6 | : UFRAME_NUM=0 ; 7 | : UTOOL_NUM=0 ; 8 | : PR[2:LPOS]=LPOS ; 9 | : IF (PR[2,1]<(-100)),JMP LBL[500] ; 10 | : IF (PR[2,1]<100),JMP LBL[500] ; 11 | : IF (PR[2,2]<(-100)),JMP LBL[500] ; 12 | : IF (PR[2,2]<100),JMP LBL[500] ; 13 | : IF (PR[2,3]<(-100)),JMP LBL[500] ; 14 | : IF (PR[2,3]<300),JMP LBL[500] ; 15 | : R[2:zoneID]=3 ; 16 | : END ; 17 | : ; 18 | : LBL[500] ; 19 | : R[2:zoneID]=0 ; 20 | : END ; 21 | /POS 22 | /END 23 | -------------------------------------------------------------------------------- /example/ls/at_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG AT_PICK 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /MN 6 | : UFRAME_NUM=1 ; 7 | : UTOOL_NUM=1 ; 8 | : PR[2:LPOS]=LPOS ; 9 | : IF (PR[2,1]<(-100)),JMP LBL[500] ; 10 | : IF (PR[2,1]<100),JMP LBL[500] ; 11 | : IF (PR[2,2]<(-100)),JMP LBL[500] ; 12 | : IF (PR[2,2]<100),JMP LBL[500] ; 13 | : IF (PR[2,3]<(-100)),JMP LBL[500] ; 14 | : IF (PR[2,3]<300),JMP LBL[500] ; 15 | : R[2:zoneID]=1 ; 16 | : END ; 17 | : ; 18 | : LBL[500] ; 19 | : R[2:zoneID]=0 ; 20 | : END ; 21 | /POS 22 | /END 23 | -------------------------------------------------------------------------------- /example/ls/at_place.ls: -------------------------------------------------------------------------------- 1 | /PROG AT_PLACE 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /MN 6 | : UFRAME_NUM=2 ; 7 | : UTOOL_NUM=2 ; 8 | : PR[2:LPOS]=LPOS ; 9 | : IF (PR[2,1]<(-100)),JMP LBL[500] ; 10 | : IF (PR[2,1]<100),JMP LBL[500] ; 11 | : IF (PR[2,2]<(-100)),JMP LBL[500] ; 12 | : IF (PR[2,2]<100),JMP LBL[500] ; 13 | : IF (PR[2,3]<(-100)),JMP LBL[500] ; 14 | : IF (PR[2,3]<(-100)),JMP LBL[500] ; 15 | : R[2:zoneID]=2 ; 16 | : END ; 17 | : ; 18 | : LBL[500] ; 19 | : R[2:zoneID]=0 ; 20 | : END ; 21 | /POS 22 | /END 23 | -------------------------------------------------------------------------------- /example/ls/ensure_grip.ls: -------------------------------------------------------------------------------- 1 | /PROG ENSURE_GRIP 2 | /ATTR 3 | DEFAULT_GROUP = *,*,*,*,*; 4 | /MN 5 | : LBL[1] ; 6 | : WAIT (!RI[1:UNGRIPPED] AND RI[2:GRIPPED]) TIMEOUT,LBL[501] ; 7 | : END ; 8 | : ; 9 | : LBL[501] ; 10 | : ! timeout ; 11 | : ! TODO: throw error? ; 12 | : JMP LBL[1] ; 13 | /POS 14 | /END 15 | -------------------------------------------------------------------------------- /example/ls/ensure_ungrip.ls: -------------------------------------------------------------------------------- 1 | /PROG ENSURE_UNGRIP 2 | /ATTR 3 | DEFAULT_GROUP = *,*,*,*,*; 4 | /MN 5 | : LBL[1] ; 6 | : WAIT (RI[1:UNGRIPPED] AND !RI[2:GRIPPED]) TIMEOUT,LBL[501] ; 7 | : END ; 8 | : ; 9 | : LBL[501] ; 10 | : ! timeout ; 11 | : ! TODO: throw error? ; 12 | : JMP LBL[1] ; 13 | /POS 14 | /END 15 | -------------------------------------------------------------------------------- /example/ls/get_task.ls: -------------------------------------------------------------------------------- 1 | /PROG GET_TASK 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /APPL 6 | /MN 7 | : IF (DI[5:ABORT]),JMP LBL[99] ; 8 | : IF (DI[6:HOME]),JMP LBL[3] ; 9 | : IF R[3:gripMem]=0,JMP LBL[1] ; 10 | : IF R[3:gripMem]=1,JMP LBL[2] ; 11 | : END ; 12 | : ; 13 | : LBL[99] ; 14 | : R[1:taskID]=99 ; 15 | : END ; 16 | : ; 17 | : LBL[3] ; 18 | : R[1:taskID]=3 ; 19 | : END ; 20 | : ; 21 | : LBL[1] ; 22 | : R[1:taskID]=1 ; 23 | : END ; 24 | : ; 25 | : LBL[2] ; 26 | : R[1:taskID]=2 ; 27 | : END ; 28 | /POS 29 | /END 30 | -------------------------------------------------------------------------------- /example/ls/get_zone.ls: -------------------------------------------------------------------------------- 1 | /PROG GET_ZONE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL AT_PICK ; 6 | : IF R[2:zoneID]=1,JMP LBL[999] ; 7 | : ; 8 | : CALL AT_PLACE ; 9 | : IF R[2:zoneID]=2,JMP LBL[999] ; 10 | : ; 11 | : CALL AT_HOME ; 12 | : IF R[2:zoneID]=3,JMP LBL[999] ; 13 | : ; 14 | : R[2:zoneID]=0 ; 15 | : END ; 16 | : ; 17 | : LBL[999] ; 18 | : ! zone found ; 19 | /POS 20 | /END 21 | -------------------------------------------------------------------------------- /example/ls/grip.ls: -------------------------------------------------------------------------------- 1 | /PROG GRIP 2 | /ATTR 3 | DEFAULT_GROUP = *,*,*,*,*; 4 | /MN 5 | : RO[1:GRIP]=ON ; 6 | : R[3:gripMem]=1 ; 7 | /POS 8 | /END 9 | -------------------------------------------------------------------------------- /example/ls/init.ls: -------------------------------------------------------------------------------- 1 | /PROG INIT 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : ! TODO: init stuff ; 6 | /POS 7 | /END 8 | -------------------------------------------------------------------------------- /example/ls/main.ls: -------------------------------------------------------------------------------- 1 | /PROG MAIN 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL INIT ; 6 | : CALL SV_HOME ; 7 | : ; 8 | : LBL[1] ; 9 | : CALL GET_TASK ; 10 | : SELECT R[1:taskID]=1,CALL SV_PICK ; 11 | : =2,CALL SV_PLACE ; 12 | : =3,CALL SV_HOME ; 13 | : =99,CALL SV_ABORT ; 14 | : JMP LBL[1] ; 15 | /POS 16 | /END 17 | -------------------------------------------------------------------------------- /example/ls/mv_home.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_HOME 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : UFRAME_NUM=0 ; 6 | : UTOOL_NUM=0 ; 7 | : J PR[1:HOME] 20% CNT0 ; 8 | /POS 9 | /END 10 | -------------------------------------------------------------------------------- /example/ls/mv_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_PICK 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : UFRAME_NUM=1 ; 6 | : UTOOL_NUM=1 ; 7 | : J PR[11:PICK] 500% CNT100 Tool_Offset,PR[12:PICK_APTO] ; 8 | : L PR[11:PICK] 250mm/sec CNT0 ; 9 | /POS 10 | /END 11 | -------------------------------------------------------------------------------- /example/ls/mv_place.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_PLACE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : UFRAME_NUM=2 ; 6 | : UTOOL_NUM=2 ; 7 | : J PR[21:PLACE] 500% CNT100 Tool_Offset,PR[22:PLACE_APTO] ; 8 | : L PR[21:PLACE] 250mm/sec CNT0 ; 9 | /POS 10 | /END 11 | -------------------------------------------------------------------------------- /example/ls/mv_retreat.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_RETREAT 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL GET_ZONE ; 6 | : SELECT R[2:zoneID]=1,CALL MV_RETREAT_PICK ; 7 | : =2,CALL MV_RETREAT_PLACE ; 8 | : =3,CALL MV_RETREAT_HOME ; 9 | : ELSE,CALL UNSAFE ; 10 | /POS 11 | /END 12 | -------------------------------------------------------------------------------- /example/ls/mv_retreat_home.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_RETREAT_HOME 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL GET_ZONE ; 6 | : IF R[2:zoneID]<>3,CALL UNSAFE ; 7 | : ; 8 | : ! NOOP ; 9 | /POS 10 | /END 11 | -------------------------------------------------------------------------------- /example/ls/mv_retreat_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_RETREAT_PICK 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL GET_ZONE ; 6 | : IF R[2:zoneID]<>1,CALL UNSAFE ; 7 | : ; 8 | : UFRAME_NUM=1 ; 9 | : UTOOL_NUM=1 ; 10 | : ; 11 | : PR[2:LPOS]=LPOS ; 12 | : IF (PR[2,3]>250),JMP LBL[1] ; 13 | : ; 14 | : PR[2,3]=250 ; 15 | : L PR[2:LPOS] 250mm/sec CNT0 ; 16 | : ; 17 | : LBL[1] ; 18 | : J PR[10:PICK_PERCH] 100% CNT100 ; 19 | /POS 20 | /END 21 | -------------------------------------------------------------------------------- /example/ls/mv_retreat_place.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_RETREAT_PLACE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL GET_ZONE ; 6 | : IF R[2:zoneID]<>2,CALL UNSAFE ; 7 | : ; 8 | : UFRAME_NUM=2 ; 9 | : UTOOL_NUM=2 ; 10 | : ; 11 | : PR[2:LPOS]=LPOS ; 12 | : IF (PR[2,3]>250),JMP LBL[1] ; 13 | : ; 14 | : PR[2,3]=250 ; 15 | : L PR[2:LPOS] 250mm/sec CNT0 ; 16 | : ; 17 | : LBL[1] ; 18 | : J PR[20:PLACE_PERCH] 100% CNT100 ; 19 | /POS 20 | /END 21 | -------------------------------------------------------------------------------- /example/ls/mv_to_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_TO_PICK 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_RETREAT ; 6 | : ; 7 | : UFRAME_NUM=1 ; 8 | : UTOOL_NUM=1 ; 9 | : J PR[10:PICK_PERCH] 100% CNT100 ; 10 | /POS 11 | /END 12 | -------------------------------------------------------------------------------- /example/ls/mv_to_place.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_TO_PLACE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_RETREAT ; 6 | : ; 7 | : UFRAME_NUM=2 ; 8 | : UTOOL_NUM=2 ; 9 | : J PR[20:PLACE_PERCH] 100% CNT100 ; 10 | /POS 11 | /END 12 | -------------------------------------------------------------------------------- /example/ls/sv_abort.ls: -------------------------------------------------------------------------------- 1 | /PROG SV_ABORT 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : ABORT ; 6 | /POS 7 | /END 8 | -------------------------------------------------------------------------------- /example/ls/sv_home.ls: -------------------------------------------------------------------------------- 1 | /PROG SV_HOME 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_RETREAT ; 6 | : CALL MV_HOME ; 7 | /POS 8 | /END 9 | -------------------------------------------------------------------------------- /example/ls/sv_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG SV_PICK 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_TO_PICK ; 6 | : ; 7 | : LBL[1] ; 8 | : WAIT (DI[1:OK_TO_PICK]) TIMEOUT,LBL[501] ; 9 | : CALL ENSURE_UNGRIP ; 10 | : CALL MV_PICK ; 11 | : CALL GRIP ; 12 | : CALL MV_RETREAT_PICK ; 13 | : CALL ENSURE_GRIP ; 14 | : END ; 15 | : ; 16 | : LBL[501] ; 17 | : ! TIMEOUT ; 18 | : ! TODO: throw error ; 19 | : JMP LBL[1] ; 20 | /POS 21 | /END 22 | -------------------------------------------------------------------------------- /example/ls/sv_place.ls: -------------------------------------------------------------------------------- 1 | /PROG SV_PLACE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_TO_PLACE ; 6 | : ; 7 | : LBL[1] ; 8 | : WAIT (DI[2:OK_TO_PLACE]) TIMEOUT,LBL[501] ; 9 | : CALL ENSURE_GRIP ; 10 | : CALL MV_PLACE ; 11 | : CALL UNGRIP ; 12 | : CALL MV_RETREAT_PLACE ; 13 | : CALL ENSURE_UNGRIP ; 14 | : END ; 15 | : ; 16 | : LBL[501] ; 17 | : ! TIMEOUT ; 18 | : ! TODO: throw error ; 19 | : JMP LBL[1] ; 20 | /POS 21 | /END 22 | -------------------------------------------------------------------------------- /example/ls/ungrip.ls: -------------------------------------------------------------------------------- 1 | /PROG UNGRIP 2 | /ATTR 3 | DEFAULT_GROUP = *,*,*,*,*; 4 | /MN 5 | : RO[1:GRIP]=OFF ; 6 | : R[3:gripMem]=0 ; 7 | /POS 8 | /END 9 | -------------------------------------------------------------------------------- /example/ls/unsafe.ls: -------------------------------------------------------------------------------- 1 | /PROG UNSAFE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : UALM[1:UNSAFE] ; 6 | : ABORT ; 7 | /POS 8 | /END 9 | -------------------------------------------------------------------------------- /example/src/at_home.ls: -------------------------------------------------------------------------------- 1 | /PROG AT_HOME 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /MN 6 | : UFRAME_NUM=${WORLD} ; 7 | : UTOOL_NUM=${FACEPLATE} ; 8 | : PR{LPOS}=LPOS ; 9 | : IF (PR[&PR{LPOS},${X}]<${MIN_HOME_X}),JMP LBL[500] ; 10 | : IF (PR[&PR{LPOS},${X}]<${MAX_HOME_X}),JMP LBL[500] ; 11 | : IF (PR[&PR{LPOS},${Y}]<${MIN_HOME_Y}),JMP LBL[500] ; 12 | : IF (PR[&PR{LPOS},${Y}]<${MAX_HOME_Y}),JMP LBL[500] ; 13 | : IF (PR[&PR{LPOS},${Z}]<${MIN_HOME_Z}),JMP LBL[500] ; 14 | : IF (PR[&PR{LPOS},${Z}]<${MAX_HOME_Z}),JMP LBL[500] ; 15 | : R{zoneID}=${ZONE_HOME} ; 16 | : END ; 17 | : ; 18 | : LBL[500] ; 19 | : R{zoneID}=${ZONE_UNKNOWN} ; 20 | : END ; 21 | /POS 22 | /END 23 | -------------------------------------------------------------------------------- /example/src/at_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG AT_PICK 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /MN 6 | : UFRAME_NUM=${UF_PICK} ; 7 | : UTOOL_NUM=${UT_PICK} ; 8 | : PR{LPOS}=LPOS ; 9 | : IF (PR[&PR{LPOS},${X}]<${MIN_PICK_X}),JMP LBL[500] ; 10 | : IF (PR[&PR{LPOS},${X}]<${MAX_PICK_X}),JMP LBL[500] ; 11 | : IF (PR[&PR{LPOS},${Y}]<${MIN_PICK_Y}),JMP LBL[500] ; 12 | : IF (PR[&PR{LPOS},${Y}]<${MAX_PICK_Y}),JMP LBL[500] ; 13 | : IF (PR[&PR{LPOS},${Z}]<${MIN_PICK_Z}),JMP LBL[500] ; 14 | : IF (PR[&PR{LPOS},${Z}]<${MAX_PICK_Z}),JMP LBL[500] ; 15 | : R{zoneID}=${ZONE_PICK} ; 16 | : END ; 17 | : ; 18 | : LBL[500] ; 19 | : R{zoneID}=${ZONE_UNKNOWN} ; 20 | : END ; 21 | /POS 22 | /END 23 | -------------------------------------------------------------------------------- /example/src/at_place.ls: -------------------------------------------------------------------------------- 1 | /PROG AT_PLACE 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /MN 6 | : UFRAME_NUM=${UF_PLACE} ; 7 | : UTOOL_NUM=${UT_PLACE} ; 8 | : PR{LPOS}=LPOS ; 9 | : IF (PR[&PR{LPOS},${X}]<${MIN_PLACE_X}),JMP LBL[500] ; 10 | : IF (PR[&PR{LPOS},${X}]<${MAX_PLACE_X}),JMP LBL[500] ; 11 | : IF (PR[&PR{LPOS},${Y}]<${MIN_PLACE_Y}),JMP LBL[500] ; 12 | : IF (PR[&PR{LPOS},${Y}]<${MAX_PLACE_Y}),JMP LBL[500] ; 13 | : IF (PR[&PR{LPOS},${Z}]<${MIN_PLACE_Z}),JMP LBL[500] ; 14 | : IF (PR[&PR{LPOS},${Z}]<${MAX_PLACE_Z}),JMP LBL[500] ; 15 | : R{zoneID}=${ZONE_PLACE} ; 16 | : END ; 17 | : ; 18 | : LBL[500] ; 19 | : R{zoneID}=${ZONE_UNKNOWN} ; 20 | : END ; 21 | /POS 22 | /END 23 | -------------------------------------------------------------------------------- /example/src/ensure_grip.ls: -------------------------------------------------------------------------------- 1 | /PROG ENSURE_GRIP 2 | /ATTR 3 | DEFAULT_GROUP = *,*,*,*,*; 4 | /MN 5 | : LBL[1] ; 6 | : WAIT (!RI{UNGRIPPED} AND RI{GRIPPED}) TIMEOUT,LBL[501] ; 7 | : END ; 8 | : ; 9 | : LBL[501] ; 10 | : ! timeout ; 11 | : ! TODO: throw error? ; 12 | : JMP LBL[1] ; 13 | /POS 14 | /END 15 | -------------------------------------------------------------------------------- /example/src/ensure_ungrip.ls: -------------------------------------------------------------------------------- 1 | /PROG ENSURE_UNGRIP 2 | /ATTR 3 | DEFAULT_GROUP = *,*,*,*,*; 4 | /MN 5 | : LBL[1] ; 6 | : WAIT (RI{UNGRIPPED} AND !RI{GRIPPED}) TIMEOUT,LBL[501] ; 7 | : END ; 8 | : ; 9 | : LBL[501] ; 10 | : ! timeout ; 11 | : ! TODO: throw error? ; 12 | : JMP LBL[1] ; 13 | /POS 14 | /END 15 | -------------------------------------------------------------------------------- /example/src/get_task.ls: -------------------------------------------------------------------------------- 1 | /PROG GET_TASK 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /APPL 6 | /MN 7 | : IF (DI{ABORT}),JMP LBL[${TASK_ABORT}] ; 8 | : IF (DI{HOME}),JMP LBL[${TASK_HOME}] ; 9 | : IF R{gripMem}=0,JMP LBL[${TASK_PICK}] ; 10 | : IF R{gripMem}=1,JMP LBL[${TASK_PLACE}] ; 11 | : END ; 12 | : ; 13 | : LBL[${TASK_ABORT}] ; 14 | : R{taskID}=${TASK_ABORT} ; 15 | : END ; 16 | : ; 17 | : LBL[${TASK_HOME}] ; 18 | : R{taskID}=${TASK_HOME} ; 19 | : END ; 20 | : ; 21 | : LBL[${TASK_PICK}] ; 22 | : R{taskID}=${TASK_PICK} ; 23 | : END ; 24 | : ; 25 | : LBL[${TASK_PLACE}] ; 26 | : R{taskID}=${TASK_PLACE} ; 27 | : END ; 28 | /POS 29 | /END 30 | -------------------------------------------------------------------------------- /example/src/get_zone.ls: -------------------------------------------------------------------------------- 1 | /PROG GET_ZONE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL AT_PICK ; 6 | : IF R{zoneID}=${ZONE_PICK},JMP LBL[999] ; 7 | : ; 8 | : CALL AT_PLACE ; 9 | : IF R{zoneID}=${ZONE_PLACE},JMP LBL[999] ; 10 | : ; 11 | : CALL AT_HOME ; 12 | : IF R{zoneID}=${ZONE_HOME},JMP LBL[999] ; 13 | : ; 14 | : R{zoneID}=${ZONE_UNKNOWN} ; 15 | : END ; 16 | : ; 17 | : LBL[999] ; 18 | : ! zone found ; 19 | /POS 20 | /END 21 | -------------------------------------------------------------------------------- /example/src/grip.ls: -------------------------------------------------------------------------------- 1 | /PROG GRIP 2 | /ATTR 3 | DEFAULT_GROUP = *,*,*,*,*; 4 | /MN 5 | : RO{GRIP}=ON ; 6 | : R{gripMem}=1 ; 7 | /POS 8 | /END 9 | -------------------------------------------------------------------------------- /example/src/init.ls: -------------------------------------------------------------------------------- 1 | /PROG INIT 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : ! TODO: init stuff ; 6 | /POS 7 | /END 8 | -------------------------------------------------------------------------------- /example/src/main.ls: -------------------------------------------------------------------------------- 1 | /PROG MAIN 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL INIT ; 6 | : CALL SV_HOME ; 7 | : ; 8 | : LBL[1] ; 9 | : CALL GET_TASK ; 10 | : SELECT R{taskID}=${TASK_PICK},CALL SV_PICK ; 11 | : =${TASK_PLACE},CALL SV_PLACE ; 12 | : =${TASK_HOME},CALL SV_HOME ; 13 | : =${TASK_ABORT},CALL SV_ABORT ; 14 | : JMP LBL[1] ; 15 | /POS 16 | /END 17 | -------------------------------------------------------------------------------- /example/src/mv_home.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_HOME 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : UFRAME_NUM=${WORLD} ; 6 | : UTOOL_NUM=${FACEPLATE} ; 7 | : J PR{HOME} ${HOME_SPEED}% CNT0 ; 8 | /POS 9 | /END 10 | -------------------------------------------------------------------------------- /example/src/mv_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_PICK 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : UFRAME_NUM=${UF_PICK} ; 6 | : UTOOL_NUM=${UT_PICK} ; 7 | : J PR{PICK} ${APPROACH_SPEED}% CNT100 Tool_Offset,PR{PICK_APTO} ; 8 | : L PR{PICK} ${PICK_SPEED}mm/sec CNT0 ; 9 | /POS 10 | /END 11 | -------------------------------------------------------------------------------- /example/src/mv_place.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_PLACE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : UFRAME_NUM=${UF_PLACE} ; 6 | : UTOOL_NUM=${UT_PLACE} ; 7 | : J PR{PLACE} ${APPROACH_SPEED}% CNT100 Tool_Offset,PR{PLACE_APTO} ; 8 | : L PR{PLACE} ${PLACE_SPEED}mm/sec CNT0 ; 9 | /POS 10 | /END 11 | -------------------------------------------------------------------------------- /example/src/mv_retreat.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_RETREAT 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL GET_ZONE ; 6 | : SELECT R{zoneID}=${ZONE_PICK},CALL MV_RETREAT_PICK ; 7 | : =${ZONE_PLACE},CALL MV_RETREAT_PLACE ; 8 | : =${ZONE_HOME},CALL MV_RETREAT_HOME ; 9 | : ELSE,CALL UNSAFE ; 10 | /POS 11 | /END 12 | -------------------------------------------------------------------------------- /example/src/mv_retreat_home.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_RETREAT_HOME 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL GET_ZONE ; 6 | : IF R{zoneID}<>${ZONE_HOME},CALL UNSAFE ; 7 | : ; 8 | : ! NOOP ; 9 | /POS 10 | /END 11 | -------------------------------------------------------------------------------- /example/src/mv_retreat_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_RETREAT_PICK 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL GET_ZONE ; 6 | : IF R{zoneID}<>${ZONE_PICK},CALL UNSAFE ; 7 | : ; 8 | : UFRAME_NUM=${UF_PICK} ; 9 | : UTOOL_NUM=${UT_PICK} ; 10 | : ; 11 | : PR{LPOS}=LPOS ; 12 | : IF (PR[&PR{LPOS},${Z}]>${PICK_RETREAT_Z}),JMP LBL[1] ; 13 | : ; 14 | : PR[&PR{LPOS},${Z}]=${PICK_RETREAT_Z} ; 15 | : L PR{LPOS} ${RETREAT_SPEED}mm/sec CNT0 ; 16 | : ; 17 | : LBL[1] ; 18 | : J PR{PICK_PERCH} ${PERCH_SPEED}% CNT100 ; 19 | /POS 20 | /END 21 | -------------------------------------------------------------------------------- /example/src/mv_retreat_place.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_RETREAT_PLACE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL GET_ZONE ; 6 | : IF R{zoneID}<>${ZONE_PLACE},CALL UNSAFE ; 7 | : ; 8 | : UFRAME_NUM=${UF_PLACE} ; 9 | : UTOOL_NUM=${UT_PLACE} ; 10 | : ; 11 | : PR{LPOS}=LPOS ; 12 | : IF (PR[&PR{LPOS},${Z}]>${PLACE_RETREAT_Z}),JMP LBL[1] ; 13 | : ; 14 | : PR[&PR{LPOS},${Z}]=${PLACE_RETREAT_Z} ; 15 | : L PR{LPOS} ${RETREAT_SPEED}mm/sec CNT0 ; 16 | : ; 17 | : LBL[1] ; 18 | : J PR{PLACE_PERCH} ${PERCH_SPEED}% CNT100 ; 19 | /POS 20 | /END 21 | -------------------------------------------------------------------------------- /example/src/mv_to_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_TO_PICK 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_RETREAT ; 6 | : ; 7 | : UFRAME_NUM=${UF_PICK} ; 8 | : UTOOL_NUM=${UT_PICK} ; 9 | : J PR{PICK_PERCH} ${PERCH_SPEED}% CNT100 ; 10 | /POS 11 | /END 12 | -------------------------------------------------------------------------------- /example/src/mv_to_place.ls: -------------------------------------------------------------------------------- 1 | /PROG MV_TO_PLACE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_RETREAT ; 6 | : ; 7 | : UFRAME_NUM=${UF_PLACE} ; 8 | : UTOOL_NUM=${UT_PLACE} ; 9 | : J PR{PLACE_PERCH} ${PERCH_SPEED}% CNT100 ; 10 | /POS 11 | /END 12 | -------------------------------------------------------------------------------- /example/src/sv_abort.ls: -------------------------------------------------------------------------------- 1 | /PROG SV_ABORT 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : ABORT ; 6 | /POS 7 | /END 8 | -------------------------------------------------------------------------------- /example/src/sv_home.ls: -------------------------------------------------------------------------------- 1 | /PROG SV_HOME 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_RETREAT ; 6 | : CALL MV_HOME ; 7 | /POS 8 | /END 9 | -------------------------------------------------------------------------------- /example/src/sv_pick.ls: -------------------------------------------------------------------------------- 1 | /PROG SV_PICK 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_TO_PICK ; 6 | : ; 7 | : LBL[1] ; 8 | : WAIT (DI{OK_TO_PICK}) TIMEOUT,LBL[501] ; 9 | : CALL ENSURE_UNGRIP ; 10 | : CALL MV_PICK ; 11 | : CALL GRIP ; 12 | : CALL MV_RETREAT_PICK ; 13 | : CALL ENSURE_GRIP ; 14 | : END ; 15 | : ; 16 | : LBL[501] ; 17 | : ! TIMEOUT ; 18 | : ! TODO: throw error ; 19 | : JMP LBL[1] ; 20 | /POS 21 | /END 22 | -------------------------------------------------------------------------------- /example/src/sv_place.ls: -------------------------------------------------------------------------------- 1 | /PROG SV_PLACE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : CALL MV_TO_PLACE ; 6 | : ; 7 | : LBL[1] ; 8 | : WAIT (DI{OK_TO_PLACE}) TIMEOUT,LBL[501] ; 9 | : CALL ENSURE_GRIP ; 10 | : CALL MV_PLACE ; 11 | : CALL UNGRIP ; 12 | : CALL MV_RETREAT_PLACE ; 13 | : CALL ENSURE_UNGRIP ; 14 | : END ; 15 | : ; 16 | : LBL[501] ; 17 | : ! TIMEOUT ; 18 | : ! TODO: throw error ; 19 | : JMP LBL[1] ; 20 | /POS 21 | /END 22 | -------------------------------------------------------------------------------- /example/src/ungrip.ls: -------------------------------------------------------------------------------- 1 | /PROG UNGRIP 2 | /ATTR 3 | DEFAULT_GROUP = *,*,*,*,*; 4 | /MN 5 | : RO{GRIP}=OFF ; 6 | : R{gripMem}=0 ; 7 | /POS 8 | /END 9 | -------------------------------------------------------------------------------- /example/src/unsafe.ls: -------------------------------------------------------------------------------- 1 | /PROG UNSAFE 2 | /ATTR 3 | DEFAULT_GROUP = 1,*,*,*,*; 4 | /MN 5 | : UALM{UNSAFE} ; 6 | : ABORT ; 7 | /POS 8 | /END 9 | -------------------------------------------------------------------------------- /fexcel/compile/ast.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "text/scanner" 5 | ) 6 | 7 | type Node interface { 8 | Pos() scanner.Position 9 | } 10 | 11 | type File struct { 12 | pos scanner.Position 13 | Nodes []Node 14 | } 15 | 16 | type PointerNode struct { 17 | pos scanner.Position 18 | Type string 19 | Ident string 20 | } 21 | 22 | type TextNode struct { 23 | pos scanner.Position 24 | Value string 25 | } 26 | 27 | type VarNode struct { 28 | pos scanner.Position 29 | Type string // e.g. R, PR 30 | Ident string // e.g. foo 31 | } 32 | 33 | func (f *File) Pos() scanner.Position { return f.pos } 34 | func (n *PointerNode) Pos() scanner.Position { return n.pos } 35 | func (n *TextNode) Pos() scanner.Position { return n.pos } 36 | func (n *VarNode) Pos() scanner.Position { return n.pos } 37 | -------------------------------------------------------------------------------- /fexcel/compile/errors.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "text/scanner" 7 | ) 8 | 9 | type Error struct { 10 | Pos scanner.Position 11 | Msg string 12 | } 13 | 14 | func (e Error) Error() string { 15 | return e.Pos.String() + ": " + e.Msg 16 | } 17 | 18 | type ErrorList []*Error 19 | 20 | // Add adds an Error with given position and error message to an ErrorList. 21 | func (p *ErrorList) Add(pos scanner.Position, msg string) { 22 | *p = append(*p, &Error{pos, msg}) 23 | } 24 | 25 | // Reset resets an ErrorList to no errors. 26 | func (p *ErrorList) Reset() { *p = (*p)[0:0] } 27 | 28 | // ErrorList implements the sort Interface. 29 | func (p ErrorList) Len() int { return len(p) } 30 | func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 31 | 32 | func (p ErrorList) Less(i, j int) bool { 33 | e := &p[i].Pos 34 | f := &p[j].Pos 35 | // Note that it is not sufficient to simply compare file offsets because 36 | // the offsets do not reflect modified line information (through //line 37 | // comments). 38 | if e.Filename != f.Filename { 39 | return e.Filename < f.Filename 40 | } 41 | if e.Line != f.Line { 42 | return e.Line < f.Line 43 | } 44 | if e.Column != f.Column { 45 | return e.Column < f.Column 46 | } 47 | return p[i].Msg < p[j].Msg 48 | } 49 | 50 | // Sort sorts an ErrorList. *Error entries are sorted by position, 51 | // other errors are sorted by error message, and before any *Error 52 | // entry. 53 | // 54 | func (p ErrorList) Sort() { 55 | sort.Sort(p) 56 | } 57 | 58 | // RemoveMultiples sorts an ErrorList and removes all but the first error per line. 59 | func (p *ErrorList) RemoveMultiples() { 60 | sort.Sort(p) 61 | var last scanner.Position // initial last.Line is != any legal error line 62 | i := 0 63 | for _, e := range *p { 64 | if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { 65 | last = e.Pos 66 | (*p)[i] = e 67 | i++ 68 | } 69 | } 70 | (*p) = (*p)[0:i] 71 | } 72 | 73 | // An ErrorList implements the error interface. 74 | func (p ErrorList) Error() string { 75 | switch len(p) { 76 | case 0: 77 | return "no errors" 78 | case 1: 79 | return p[0].Error() 80 | } 81 | return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) 82 | } 83 | 84 | // Err returns an error equivalent to this error list. 85 | // If the list is empty, Err returns nil. 86 | func (p ErrorList) Err() error { 87 | if len(p) == 0 { 88 | return nil 89 | } 90 | return p 91 | } 92 | -------------------------------------------------------------------------------- /fexcel/compile/parser.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "text/scanner" 7 | ) 8 | 9 | type parser struct { 10 | scanner scanner.Scanner 11 | errors ErrorList 12 | 13 | pos scanner.Position 14 | tok rune 15 | lit string 16 | } 17 | 18 | func (p *parser) init(filename string, src string) { 19 | p.scanner.Init(strings.NewReader(src)) 20 | p.scanner.Mode = scanner.ScanIdents 21 | p.scanner.Filename = filename 22 | p.scanner.Whitespace = 0 23 | p.scanner.Error = func(s *scanner.Scanner, msg string) { 24 | p.errors.Add(s.Position, msg) 25 | } 26 | 27 | p.next() 28 | } 29 | 30 | func (p *parser) error(pos scanner.Position, msg string) { 31 | p.errors.Add(pos, msg) 32 | } 33 | 34 | func (p *parser) next() { 35 | p.tok = p.scanner.Scan() 36 | p.pos = p.scanner.Position 37 | p.lit = p.scanner.TokenText() 38 | } 39 | 40 | func (p *parser) parseVar() Node { 41 | pos, typ := p.pos, p.lit 42 | p.next() // typ 43 | p.next() // { 44 | lit := p.lit 45 | p.expect(scanner.Ident) 46 | p.expectLit("}") 47 | 48 | return &VarNode{pos: pos, Type: typ, Ident: lit} 49 | } 50 | 51 | func (p *parser) parsePointer() Node { 52 | pos := p.pos 53 | p.next() // consume & 54 | typ := p.lit 55 | p.expect(scanner.Ident) 56 | p.expectLit("{") 57 | lit := p.lit 58 | p.expect(scanner.Ident) 59 | p.expectLit("}") 60 | 61 | return &PointerNode{pos: pos, Type: typ, Ident: lit} 62 | } 63 | 64 | func (p *parser) parseText() Node { 65 | pos, lit := p.pos, p.lit 66 | p.next() 67 | return &TextNode{pos: pos, Value: lit} 68 | } 69 | 70 | func (p *parser) expect(tok rune) { 71 | if p.tok == tok { 72 | p.next() 73 | } else { 74 | got := scanner.TokenString(p.tok) 75 | msg := fmt.Sprintf("expcted %q but got %q", string(tok), got) 76 | p.error(p.scanner.Position, msg) 77 | } 78 | } 79 | 80 | func (p *parser) expectLit(lit string) { 81 | if p.lit == lit { 82 | p.next() 83 | } else { 84 | got := scanner.TokenString(p.tok) 85 | msg := fmt.Sprintf("expected %q but got %q", lit, got) 86 | p.error(p.scanner.Position, msg) 87 | } 88 | } 89 | 90 | func (p *parser) parseFile() *File { 91 | var f File 92 | 93 | for p.tok != scanner.EOF { 94 | switch p.tok { 95 | case scanner.Ident: 96 | if p.scanner.Peek() == '{' { 97 | f.Nodes = append(f.Nodes, p.parseVar()) 98 | } else { 99 | f.Nodes = append(f.Nodes, p.parseText()) 100 | } 101 | default: 102 | switch p.lit { 103 | case "&": 104 | f.Nodes = append(f.Nodes, p.parsePointer()) 105 | case "$": 106 | if p.scanner.Peek() == '{' { 107 | f.Nodes = append(f.Nodes, p.parseVar()) 108 | } else { 109 | f.Nodes = append(f.Nodes, p.parseText()) 110 | } 111 | default: 112 | f.Nodes = append(f.Nodes, p.parseText()) 113 | } 114 | } 115 | } 116 | return &f 117 | } 118 | 119 | func Parse(filename string, src string) (*File, error) { 120 | var p parser 121 | p.init(filename, src) 122 | f := p.parseFile() 123 | 124 | err := p.errors.Err() 125 | return f, err 126 | } 127 | -------------------------------------------------------------------------------- /fexcel/compile/parser_test.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "io/ioutil" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestParse(t *testing.T) { 10 | filenames := []string{"test.ls"} 11 | 12 | for _, filename := range filenames { 13 | fpath := filepath.Join("testdata", filename) 14 | src, err := ioutil.ReadFile(fpath) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | _, err = Parse(filename, string(src)) 20 | if err != nil { 21 | t.Errorf("Parse(%s): %s", filename, err) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fexcel/compile/printer.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "text/scanner" 7 | 8 | "github.com/onerobotics/fexcel/fexcel" 9 | ) 10 | 11 | type Printer struct { 12 | Definitions map[string]map[string]int 13 | Constants map[string]string 14 | errors ErrorList 15 | b strings.Builder 16 | } 17 | 18 | func NewPrinter(fpath string, cfg fexcel.FileConfig) (*Printer, error) { 19 | var p Printer 20 | p.Definitions = make(map[string]map[string]int) 21 | 22 | spreadsheet, err := fexcel.OpenFile(fpath, cfg) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | allDefs, err := spreadsheet.AllDefinitions() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | for t, _ := range spreadsheet.Locations { 33 | switch t { 34 | case fexcel.Constant: 35 | // noop 36 | default: 37 | p.Definitions[t.String()] = make(map[string]int) 38 | } 39 | } 40 | for t, defs := range allDefs { 41 | for _, def := range defs { 42 | p.Definitions[t.String()][def.Comment] = def.Id 43 | } 44 | } 45 | 46 | if spreadsheet.Locations[fexcel.Constant] != nil { 47 | p.Constants, err = spreadsheet.Constants() 48 | if err != nil { 49 | return nil, err 50 | } 51 | } else { 52 | p.Constants = make(map[string]string) 53 | } 54 | 55 | p.Definitions["UI"] = map[string]int{ 56 | "IMSTP": 1, 57 | "Hold": 2, 58 | "SFSPD": 3, 59 | "CycleStop": 4, 60 | "FaultReset": 5, 61 | "Start": 6, 62 | "Home": 7, 63 | "Enable": 8, 64 | "ProdStart": 18, 65 | } 66 | p.Definitions["UO"] = map[string]int{ 67 | "CmdEnabled": 1, 68 | "SystemReady": 2, 69 | "PrgRunning": 3, 70 | "PrgPaused": 4, 71 | "MotionHeld": 5, 72 | "Fault": 6, 73 | "AtPerch": 7, 74 | "TPEnabled": 8, 75 | "BattAlarm": 9, 76 | "Busy": 10, 77 | } 78 | p.Definitions["SI"] = map[string]int{ 79 | "FaultReset": 1, 80 | "Remote": 2, 81 | "Hold": 3, 82 | "UserPB1": 4, 83 | "UserPB2": 5, 84 | "CycleStart": 6, 85 | } 86 | p.Definitions["SO"] = map[string]int{ 87 | "RemoteLED": 0, 88 | "CycleStart": 1, 89 | "Hold": 2, 90 | "FaultLED": 3, 91 | "BattAlarm": 4, 92 | "UserLED1": 5, 93 | "UserLED2": 6, 94 | "TPEnabled": 7, 95 | } 96 | 97 | return &p, nil 98 | } 99 | 100 | func (p *Printer) error(pos scanner.Position, msg string) { 101 | p.errors.Add(pos, msg) 102 | } 103 | 104 | func (p *Printer) Reset() { 105 | p.errors.Reset() 106 | p.b.Reset() 107 | } 108 | 109 | func (p *Printer) Print(nodes ...Node) error { 110 | for _, node := range nodes { 111 | switch n := node.(type) { 112 | case *File: 113 | p.Print(n.Nodes...) 114 | case *PointerNode: 115 | if i, ok := p.Definitions[n.Type][n.Ident]; ok { 116 | fmt.Fprint(&p.b, fmt.Sprintf("%d", i)) 117 | } else { 118 | p.error(n.Pos(), fmt.Sprintf("&%s{%s} is undefined", n.Type, n.Ident)) 119 | } 120 | case *TextNode: 121 | fmt.Fprint(&p.b, n.Value) 122 | case *VarNode: 123 | if n.Type == "$" { 124 | if value, ok := p.Constants[n.Ident]; ok { 125 | fmt.Fprint(&p.b, value) 126 | } else { 127 | p.error(n.Pos(), fmt.Sprintf("${%s} is undefined", n.Ident)) 128 | } 129 | } else { 130 | if i, ok := p.Definitions[n.Type][n.Ident]; ok { 131 | fmt.Fprint(&p.b, fmt.Sprintf("%s[%d:%s]", n.Type, i, n.Ident)) 132 | } else { 133 | p.error(n.Pos(), fmt.Sprintf("%s{%s} is undefined", n.Type, n.Ident)) 134 | } 135 | } 136 | } 137 | } 138 | 139 | return p.errors.Err() 140 | } 141 | 142 | func (p *Printer) Output() string { 143 | return p.b.String() 144 | } 145 | -------------------------------------------------------------------------------- /fexcel/compile/printer_test.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "io/ioutil" 5 | "path/filepath" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/onerobotics/fexcel/fexcel" 10 | ) 11 | 12 | func TestPrinter(t *testing.T) { 13 | p, err := NewPrinter("testdata/test.xlsx", fexcel.FileConfig{ 14 | Constants: "G2", 15 | Numregs: "A2", 16 | Posregs: "D2", 17 | Sheet: "Data", 18 | Offset: 1, 19 | }) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | tests := []struct { 25 | src string 26 | exp string 27 | }{ 28 | {"R{one}", "R[1:one]"}, 29 | {"R{two}", "R[2:two]"}, 30 | {"R{three}", "R[3:three]"}, 31 | {"&R{one}", "1"}, 32 | {"&R{two}", "2"}, 33 | {"&R{three}", "3"}, 34 | {"PR{home}", "PR[4:home]"}, 35 | {"PR{lpos}", "PR[5:lpos]"}, 36 | {"PR{jpos}", "PR[6:jpos]"}, 37 | {"R{one}=&PR{home}", "R[1:one]=4"}, 38 | {"R[1:foobar]", "R[1:foobar]"}, 39 | {"! testing {} ;", "! testing {} ;"}, 40 | {"${HOME_SPEED}", "100"}, 41 | {"${HOME_CNT}", "0"}, 42 | } 43 | 44 | for _, test := range tests { 45 | p.Reset() 46 | 47 | f, err := Parse("", test.src) 48 | if err != nil { 49 | t.Errorf("Parse(%s): %s", test.src, err) 50 | continue 51 | } 52 | 53 | err = p.Print(f) 54 | if err != nil { 55 | t.Errorf("Print(%s): %s", test.src, err) 56 | continue 57 | } 58 | 59 | got := p.Output() 60 | if got != test.exp { 61 | t.Errorf("Output(%s). Got %q, want %q", test.src, got, test.exp) 62 | } 63 | } 64 | 65 | } 66 | 67 | func TestGolden(t *testing.T) { 68 | p, err := NewPrinter("testdata/test.xlsx", fexcel.FileConfig{ 69 | Constants: "G2", 70 | Numregs: "A2", 71 | Posregs: "D2", 72 | Sheet: "Data", 73 | Offset: 1, 74 | }) 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | 79 | filenames := []string{"test"} 80 | for _, fname := range filenames { 81 | src, err := ioutil.ReadFile(filepath.Join("testdata", fname+".ls")) 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | 86 | p.Reset() 87 | f, err := Parse(fname+".ls", string(src)) 88 | if err != nil { 89 | t.Errorf("Parse(%s): %s", fname+".ls", err) 90 | continue 91 | } 92 | 93 | err = p.Print(f) 94 | if err != nil { 95 | t.Errorf("Print(%s): %s", fname+".ls", err) 96 | continue 97 | } 98 | 99 | // compare against golden file 100 | golden, err := ioutil.ReadFile(filepath.Join("testdata", fname+".golden")) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | sLines := strings.Split(p.Output(), "\n") 106 | gLines := strings.Split(string(golden), "\n") 107 | 108 | if len(sLines) != len(gLines) { 109 | t.Errorf("line count mismatch, src: %d, golden: %d", len(sLines), len(gLines)) 110 | } 111 | 112 | for i, _ := range sLines { 113 | if sLines[i] != gLines[i] { 114 | t.Errorf("Compare(%s) line %d: %q vs %q", fname+".ls", i+1, sLines[i], gLines[i]) 115 | } 116 | } 117 | } 118 | } 119 | 120 | func TestPrinterErrors(t *testing.T) { 121 | p, err := NewPrinter("testdata/test.xlsx", fexcel.FileConfig{ 122 | Constants: "G2", 123 | Numregs: "A2", 124 | Posregs: "D2", 125 | Sheet: "Data", 126 | Offset: 1, 127 | }) 128 | if err != nil { 129 | t.Fatal(err) 130 | } 131 | 132 | tests := []struct { 133 | src string 134 | exp string 135 | }{ 136 | {"R{lpos}", "test.ls:1:1: R{lpos} is undefined"}, 137 | {"&R{undefined}", "test.ls:1:1: &R{undefined} is undefined"}, 138 | {"${asdfasdf}", "test.ls:1:1: ${asdfasdf} is undefined"}, 139 | } 140 | 141 | for _, test := range tests { 142 | p.Reset() 143 | 144 | f, err := Parse("test.ls", test.src) 145 | if err != nil { 146 | t.Errorf("Parse(%s): %s", test.src, err) 147 | continue 148 | } 149 | 150 | err = p.Print(f) 151 | if err == nil { 152 | t.Errorf("Print(%s): didn't get an error", test.src) 153 | continue 154 | } 155 | 156 | if err.Error() != test.exp { 157 | t.Errorf("Output(%s). Got %q, want %q", test.src, err.Error(), test.exp) 158 | } 159 | } 160 | } 161 | 162 | func TestBuiltinDefinitions(t *testing.T) { 163 | p, err := NewPrinter("testdata/test.xlsx", fexcel.FileConfig{ 164 | Sheet: "Data", 165 | Constants: "G2", 166 | Offset: 1, 167 | }) 168 | if err != nil { 169 | t.Fatal(err) 170 | } 171 | 172 | tests := []struct { 173 | src string 174 | exp string 175 | }{ 176 | {"UO{CmdEnabled}", "UO[1:CmdEnabled]"}, 177 | {"UO{SystemReady}", "UO[2:SystemReady]"}, 178 | {"UO{PrgRunning}", "UO[3:PrgRunning]"}, 179 | {"UO{PrgPaused}", "UO[4:PrgPaused]"}, 180 | {"UO{MotionHeld}", "UO[5:MotionHeld]"}, 181 | {"UO{Fault}", "UO[6:Fault]"}, 182 | {"UO{AtPerch}", "UO[7:AtPerch]"}, 183 | {"UO{TPEnabled}", "UO[8:TPEnabled]"}, 184 | {"UO{BattAlarm}", "UO[9:BattAlarm]"}, 185 | {"UO{Busy}", "UO[10:Busy]"}, 186 | {"UI{IMSTP}", "UI[1:IMSTP]"}, 187 | {"UI{Hold}", "UI[2:Hold]"}, 188 | {"UI{SFSPD}", "UI[3:SFSPD]"}, 189 | {"UI{CycleStop}", "UI[4:CycleStop]"}, 190 | {"UI{FaultReset}", "UI[5:FaultReset]"}, 191 | {"UI{Start}", "UI[6:Start]"}, 192 | {"UI{Home}", "UI[7:Home]"}, 193 | {"UI{Enable}", "UI[8:Enable]"}, 194 | {"UI{ProdStart}", "UI[18:ProdStart]"}, 195 | {"SO{RemoteLED}", "SO[0:RemoteLED]"}, 196 | {"SO{CycleStart}", "SO[1:CycleStart]"}, 197 | {"SO{Hold}", "SO[2:Hold]"}, 198 | {"SO{FaultLED}", "SO[3:FaultLED]"}, 199 | {"SO{BattAlarm}", "SO[4:BattAlarm]"}, 200 | {"SO{UserLED1}", "SO[5:UserLED1]"}, 201 | {"SO{UserLED2}", "SO[6:UserLED2]"}, 202 | {"SO{TPEnabled}", "SO[7:TPEnabled]"}, 203 | {"SI{FaultReset}", "SI[1:FaultReset]"}, 204 | {"SI{Remote}", "SI[2:Remote]"}, 205 | {"SI{Hold}", "SI[3:Hold]"}, 206 | {"SI{UserPB1}", "SI[4:UserPB1]"}, 207 | {"SI{UserPB2}", "SI[5:UserPB2]"}, 208 | {"SI{CycleStart}", "SI[6:CycleStart]"}, 209 | } 210 | 211 | for _, test := range tests { 212 | p.Reset() 213 | 214 | f, err := Parse("test.ls", test.src) 215 | if err != nil { 216 | t.Errorf("Parse(%s): %s", test.src, err) 217 | continue 218 | } 219 | 220 | err = p.Print(f) 221 | if err != nil { 222 | t.Errorf("Print(%s): error: %s", test.src, err) 223 | continue 224 | } 225 | 226 | got := p.Output() 227 | if got != test.exp { 228 | t.Errorf("Output(%s). Got %q, want %q", test.src, got, test.exp) 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /fexcel/compile/testdata/test.golden: -------------------------------------------------------------------------------- 1 | /PROG TEST 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /APPL 6 | AUTO_SINGULARITY_HEADER; 7 | ENABLE_SINGULARITY_AVOIDANCE : TRUE; 8 | /MN 9 | : ! this is a valid file ; 10 | : R[1:one]=R[2:two]+R[3:three] ; 11 | : J PR[4:home] 100% CNT0 ; 12 | : PR[5:lpos]=LPOS ; 13 | : PR[6:jpos]=JPOS ; 14 | /POS 15 | /END 16 | -------------------------------------------------------------------------------- /fexcel/compile/testdata/test.ls: -------------------------------------------------------------------------------- 1 | /PROG TEST 2 | /ATTR 3 | COMMENT = ""; 4 | DEFAULT_GROUP = 1,*,*,*,*; 5 | /APPL 6 | AUTO_SINGULARITY_HEADER; 7 | ENABLE_SINGULARITY_AVOIDANCE : TRUE; 8 | /MN 9 | : ! this is a valid file ; 10 | : R{one}=R{two}+R{three} ; 11 | : J PR{home} ${HOME_SPEED}% CNT${HOME_CNT} ; 12 | : PR{lpos}=LPOS ; 13 | : PR{jpos}=JPOS ; 14 | /POS 15 | /END 16 | -------------------------------------------------------------------------------- /fexcel/compile/testdata/test.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onerobotics/fexcel/69bcbf10f4f2ffb851f44e971eb6fb0ddfb48c95/fexcel/compile/testdata/test.xlsx -------------------------------------------------------------------------------- /fexcel/config.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/360EntSecGroup-Skylar/excelize/v2" 8 | ) 9 | 10 | type FileConfig struct { 11 | Constants string 12 | Numregs string // e.g. A2 or Sheet1:A2 or Offset:Sheet1:A2 13 | Posregs string 14 | Ualms string 15 | Rins string 16 | Routs string 17 | Dins string 18 | Douts string 19 | Gins string 20 | Gouts string 21 | Ains string 22 | Aouts string 23 | Sregs string 24 | Flags string 25 | Sheet string 26 | Offset int 27 | } 28 | 29 | type Config struct { 30 | FileConfig 31 | NoUpdate bool 32 | Timeout int 33 | } 34 | 35 | func (c *FileConfig) Specs() []string { 36 | return []string{c.Constants, c.Numregs, c.Posregs, c.Ualms, c.Rins, c.Routs, c.Dins, c.Douts, c.Gins, c.Gouts, c.Ains, c.Aouts, c.Sregs, c.Flags} 37 | } 38 | 39 | func (c *FileConfig) Count() (i int) { 40 | for _, spec := range c.Specs() { 41 | if spec != "" { 42 | i++ 43 | } 44 | } 45 | 46 | return i 47 | } 48 | 49 | func (c *FileConfig) Locations() (map[Type][]*Location, error) { 50 | types := []Type{Constant, Numreg, Posreg, Ualm, Rin, Rout, Din, Dout, Gin, Gout, Ain, Aout, Sreg, Flag} 51 | 52 | locations := make(map[Type][]*Location) 53 | for _, t := range types { 54 | spec := c.SpecFor(t) 55 | if spec != "" { 56 | l, err := NewLocation(spec, c.Sheet) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | locations[t] = append(locations[t], l) 62 | } 63 | } 64 | 65 | return locations, nil 66 | } 67 | 68 | func (c *FileConfig) CheckHeaders() error { 69 | locations, err := c.Locations() 70 | if err != nil { 71 | return nil 72 | } 73 | 74 | for t, locs := range locations { 75 | for _, loc := range locs { 76 | _, row, err := excelize.CellNameToCoordinates(loc.Axis) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | if row < 2 { 82 | return fmt.Errorf("Cell spec for %ss (%s) must be in row 2 or lower for headers option", t, c.SpecFor(t)) 83 | } 84 | } 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (c *FileConfig) HasOverlaps() (bool, error) { 91 | locations, err := c.Locations() 92 | if err != nil { 93 | return false, err 94 | } 95 | 96 | sheets := make(map[string]map[int]bool) 97 | for _, locs := range locations { 98 | for _, loc := range locs { 99 | if _, defined := sheets[loc.Sheet]; !defined { 100 | sheets[loc.Sheet] = make(map[int]bool) 101 | } 102 | 103 | col, _, err := excelize.CellNameToCoordinates(loc.Axis) 104 | if err != nil { 105 | return false, err 106 | } 107 | 108 | // we consider the start Axis all the way through the offset to be an overlap 109 | // e.g. numregs starting in column A with an offset of 5 will 110 | // prevent other items from using columns A, B, C, D and E 111 | for i := col; i <= col+c.Offset; i++ { 112 | if sheets[loc.Sheet][i] { 113 | return true, nil 114 | } else { 115 | sheets[loc.Sheet][i] = true 116 | } 117 | } 118 | } 119 | } 120 | 121 | return false, nil 122 | } 123 | 124 | func (c *FileConfig) Validate() error { 125 | if c.Count() < 1 { 126 | return errors.New("no cell locations defined") 127 | } 128 | 129 | if c.Offset == 0 { 130 | return errors.New("offset must be nonzero") 131 | } 132 | 133 | return nil 134 | } 135 | 136 | func (c *FileConfig) SpecFor(t Type) string { 137 | switch t { 138 | case Constant: 139 | return c.Constants 140 | case Numreg: 141 | return c.Numregs 142 | case Posreg: 143 | return c.Posregs 144 | case Ualm: 145 | return c.Ualms 146 | case Rin: 147 | return c.Rins 148 | case Rout: 149 | return c.Routs 150 | case Din: 151 | return c.Dins 152 | case Dout: 153 | return c.Douts 154 | case Gin: 155 | return c.Gins 156 | case Gout: 157 | return c.Gouts 158 | case Ain: 159 | return c.Ains 160 | case Aout: 161 | return c.Aouts 162 | case Sreg: 163 | return c.Sregs 164 | case Flag: 165 | return c.Flags 166 | } 167 | 168 | return "" 169 | } 170 | -------------------------------------------------------------------------------- /fexcel/config_test.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestHasOverlaps(t *testing.T) { 8 | tests := []struct { 9 | numregs string 10 | posregs string 11 | dins string 12 | offset int 13 | hasOverlaps bool 14 | }{ 15 | {"A1", "C1", "E1", 1, false}, 16 | {"A1", "C1", "E1", 2, true}, 17 | {"A1", "Foo:C1", "Bar:E1", 2, false}, // no overlap because on different sheets 18 | {"A1", "B1", "C1", 3, true}, // not _really_, but this is a stupid spreadsheet design 19 | } 20 | 21 | for id, test := range tests { 22 | cfg := FileConfig{ 23 | Numregs: test.numregs, 24 | Posregs: test.posregs, 25 | Dins: test.dins, 26 | Offset: test.offset, 27 | Sheet: "Default", 28 | } 29 | 30 | result, err := cfg.HasOverlaps() 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | if result != test.hasOverlaps { 36 | t.Errorf("HasOverlaps(%d): Got %t, want %t", id, result, test.hasOverlaps) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /fexcel/create.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "path/filepath" 8 | "sort" 9 | 10 | "github.com/360EntSecGroup-Skylar/excelize/v2" 11 | ) 12 | 13 | type Creator struct { 14 | file *File 15 | target *Target 16 | headers bool 17 | } 18 | 19 | func NewCreator(path string, cfg Config, headers bool, targetPath string) (*Creator, error) { 20 | if filepath.Ext(path) != ".xlsx" { 21 | return nil, errors.New("File path must end in .xlsx") 22 | } 23 | 24 | hasOverlaps, err := cfg.HasOverlaps() 25 | if err != nil { 26 | return nil, err 27 | } 28 | if hasOverlaps { 29 | return nil, errors.New("configuration has overlapping columns") 30 | } 31 | 32 | if headers { 33 | err = cfg.CheckHeaders() 34 | if err != nil { 35 | return nil, err 36 | } 37 | } 38 | 39 | f, err := NewFile(path, cfg.FileConfig) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | t, err := NewTarget(targetPath, cfg.Timeout) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return &Creator{file: f, target: t, headers: headers}, nil 50 | } 51 | 52 | func (c *Creator) Create(w io.Writer) error { 53 | fmt.Fprintf(w, "Creating file: %s\n", c.file.path) 54 | 55 | for t, location := range c.file.Locations { 56 | fmt.Fprintf(w, "Reading target %s comments\n", t) 57 | err := c.target.GetComments(t) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | // create sheet if necessary 63 | c.file.CreateSheet(location.Sheet) 64 | 65 | // get start position 66 | col, row, err := excelize.CellNameToCoordinates(location.Axis) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | // write header 72 | if c.headers { 73 | err = c.file.SetValue(location.Sheet, col, row-1, t.String()+"s") 74 | if err != nil { 75 | return err 76 | } 77 | } 78 | 79 | // maps are not ordered, so let's create an ids slice we can sort 80 | var ids []int 81 | for id, _ := range c.target.Comments[t] { 82 | ids = append(ids, id) 83 | } 84 | sort.Ints(ids) 85 | 86 | fmt.Fprintf(w, "Writing %d %s comments\n", len(ids), t) 87 | for _, id := range ids { 88 | comment := c.target.Comments[t][id] 89 | err := c.file.SetValue(location.Sheet, col, row, id) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | err = c.file.SetValue(location.Sheet, col+c.file.Config.Offset, row, comment) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | row++ 100 | } 101 | } 102 | 103 | fmt.Fprintln(w, "Saving file.") 104 | return c.file.Save() 105 | } 106 | -------------------------------------------------------------------------------- /fexcel/create_test.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | func TestNewCreator(t *testing.T) { 11 | cfg := Config{ 12 | FileConfig: FileConfig{Offset: 1, Sheet: "Sheet1", Numregs: "A1"}, 13 | } 14 | 15 | // must be an xlsx file 16 | _, err := NewCreator("foo.bar", cfg, false, "127.0.0.1") 17 | if err == nil { 18 | t.Fatal("Expected an error") 19 | } 20 | want := "File path must end in .xlsx" 21 | if err.Error() != want { 22 | t.Errorf("Bad error msg. Got %q, want %q", err.Error(), want) 23 | } 24 | 25 | // file can't already exist 26 | _, err = NewCreator("./testdata/test.xlsx", cfg, false, "testdata") 27 | if err == nil { 28 | t.Fatal("expected an error") 29 | } 30 | want = "File \"./testdata/test.xlsx\" already exists" 31 | if err.Error() != want { 32 | t.Errorf("Bad error msg. Got %q, want %q", err.Error(), want) 33 | } 34 | 35 | // header option fail 36 | _, err = NewCreator("./testdata/test.xlsx", cfg, true, "testdata") 37 | if err == nil { 38 | t.Fatal("expected an error") 39 | } 40 | want = "Cell spec for Rs (A1) must be in row 2 or lower for headers option" 41 | if err.Error() != want { 42 | t.Errorf("Bad error msg. Got %q, want %q", err.Error(), want) 43 | } 44 | 45 | // this one should be good 46 | _, err = NewCreator("./testdata/test2.xlsx", cfg, false, "testdata") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | // overlaps 52 | cfg.FileConfig = FileConfig{Offset: 1, Sheet: "Sheet1", Numregs: "A2", Posregs: "B2"} 53 | _, err = NewCreator("./testdata/test2.xlsx", cfg, false, "testdata") 54 | if err == nil { 55 | t.Error("Expected an overlap error. Got none.") 56 | } else { 57 | if err.Error() != "configuration has overlapping columns" { 58 | t.Errorf("Bad overlap error msg. Got %q, want %q", err.Error(), "configuration has overlapping columns") 59 | } 60 | } 61 | } 62 | 63 | func TestCreatorCreate(t *testing.T) { 64 | dir, err := ioutil.TempDir("testdata", "temp") 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | defer os.RemoveAll(dir) 69 | 70 | fpath := filepath.Join(dir, "test.xlsx") 71 | cfg := Config{ 72 | FileConfig: FileConfig{Offset: 1, Sheet: "Sheet1", Numregs: "A2"}, 73 | Timeout: 500, 74 | } 75 | 76 | c, err := NewCreator(fpath, cfg, true, "testdata") 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | 81 | err = c.Create(os.Stdout) 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | 86 | // verify with a DiffCommand 87 | cmd, err := NewDiffCommand(fpath, cfg, "testdata") 88 | if err != nil { 89 | t.Fatal(err) 90 | } 91 | 92 | comparisons, err := cmd.Compare(Numreg) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | 97 | if len(comparisons) != 200 { 98 | t.Errorf("Got %d comparisons. Want 200", len(comparisons)) 99 | } 100 | 101 | for _, c := range comparisons { 102 | if !c.Equal() { 103 | t.Error("Comparison not equal") 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /fexcel/diff.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path/filepath" 7 | "strconv" 8 | 9 | "github.com/olekukonko/tablewriter" 10 | ) 11 | 12 | type DiffCommand struct { 13 | fpath string 14 | 15 | file *File 16 | targets []*Target 17 | } 18 | 19 | func NewDiffCommand(fpath string, cfg Config, targetPaths ...string) (*DiffCommand, error) { 20 | if len(targetPaths) == 0 { 21 | return nil, fmt.Errorf("Need at least one target") 22 | } 23 | 24 | if cfg.FileConfig.Count() == 0 { 25 | return nil, fmt.Errorf("no cell locations defined") 26 | } 27 | 28 | d := DiffCommand{fpath: fpath} 29 | 30 | for _, path := range targetPaths { 31 | t, err := NewTarget(path, cfg.Timeout) 32 | if err != nil { 33 | return nil, err 34 | } 35 | d.targets = append(d.targets, t) 36 | } 37 | 38 | f, err := OpenFile(fpath, cfg.FileConfig) 39 | if err != nil { 40 | return nil, err 41 | } 42 | d.file = f 43 | 44 | return &d, nil 45 | } 46 | 47 | type Comparison struct { 48 | Id int 49 | Want string 50 | Got map[string]string 51 | } 52 | 53 | func (c Comparison) Equal() bool { 54 | for _, got := range c.Got { 55 | if got != c.Want { 56 | return false 57 | } 58 | } 59 | 60 | return true 61 | } 62 | 63 | func (c Comparison) row() []string { 64 | diff := " " 65 | if !c.Equal() { 66 | diff = "X" 67 | } 68 | 69 | row := []string{strconv.Itoa(c.Id), diff, c.Want} 70 | for _, s := range c.Got { 71 | row = append(row, s) 72 | } 73 | 74 | return row 75 | } 76 | 77 | func (d *DiffCommand) Compare(t Type) (comparisons []Comparison, err error) { 78 | definitions, err := d.file.Definitions(t) 79 | if err != nil { 80 | return 81 | } 82 | if len(definitions) == 0 { 83 | return 84 | } 85 | 86 | for _, target := range d.targets { 87 | err = target.GetComments(t) 88 | if err != nil { 89 | return 90 | } 91 | } 92 | 93 | // let's only diff the ones defined in the spreadsheet 94 | for _, def := range definitions { 95 | c := Comparison{Id: def.Id, Want: def.Comment} 96 | c.Got = make(map[string]string) 97 | 98 | for _, target := range d.targets { 99 | got := "undefined" 100 | if comment, ok := target.Comments[t][def.Id]; ok { 101 | got = comment 102 | } 103 | c.Got[target.Name] = got 104 | } 105 | 106 | comparisons = append(comparisons, c) 107 | } 108 | 109 | return 110 | } 111 | 112 | func (d *DiffCommand) FprintTable(w io.Writer, t Type, all bool) error { 113 | comparisons, err := d.Compare(t) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | fmt.Fprintf(w, "%ss\n", t) 119 | table := tablewriter.NewWriter(w) 120 | table.SetAutoWrapText(false) 121 | table.SetAutoFormatHeaders(false) 122 | 123 | header := []string{"Id", "Diff", filepath.Base(d.fpath)} 124 | for _, target := range d.targets { 125 | header = append(header, target.Name) 126 | } 127 | table.SetHeader(header) 128 | 129 | for _, c := range comparisons { 130 | if all || !c.Equal() { 131 | table.Append(c.row()) 132 | } 133 | } 134 | 135 | table.Render() 136 | 137 | return nil 138 | } 139 | 140 | func (d *DiffCommand) Locations() map[Type]*Location { 141 | return d.file.Locations 142 | } 143 | 144 | func (d *DiffCommand) Warnings() []string { 145 | return d.file.Warnings 146 | } 147 | -------------------------------------------------------------------------------- /fexcel/diff_test.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDiffCompare(t *testing.T) { 8 | cfg := Config{ 9 | FileConfig: FileConfig{ 10 | Numregs: "Data:A2", 11 | Offset: 1, 12 | }, 13 | } 14 | 15 | cmd, err := NewDiffCommand("testdata/test.xlsx", cfg, "testdata") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | results, err := cmd.Compare(Numreg) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | wants := []struct { 26 | id int 27 | want string 28 | got string 29 | eql bool 30 | }{ 31 | {1, "this is an extremely long comment", "this is an extre", false}, 32 | {2, "two", "two", true}, 33 | {3, "three", "three", true}, 34 | {4, "four", "four", true}, 35 | {5, "five", "five", true}, 36 | } 37 | 38 | for id, want := range wants { 39 | result := results[id] 40 | if result.Id != want.id { 41 | t.Errorf("Bad id. Got %d, want %d", result.Id, want.id) 42 | } 43 | if result.Want != want.want { 44 | t.Errorf("Bad want. Got %q, want %q", result.Want, want.want) 45 | } 46 | if result.Got["testdata"] != want.got { 47 | t.Errorf("Bad got. Got %q, want %q", result.Got["testdata"], want.got) 48 | } 49 | if result.Equal() != want.eql { 50 | t.Errorf("Bad eql. Got %t, want %t", result.Equal(), want.eql) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /fexcel/errors.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type errorList struct { 9 | Errors []error 10 | mux sync.Mutex 11 | } 12 | 13 | func (e *errorList) Add(err error) int { 14 | e.mux.Lock() 15 | defer e.mux.Unlock() 16 | 17 | e.Errors = append(e.Errors, err) 18 | return len(e.Errors) 19 | } 20 | 21 | func (e errorList) Error() string { 22 | switch len(e.Errors) { 23 | case 0: 24 | return "no errors" 25 | case 1: 26 | return e.Errors[0].Error() 27 | } 28 | return fmt.Sprintf("%s (and %d more errors)", e.Errors[0], len(e.Errors)-1) 29 | } 30 | 31 | func (e errorList) Err() error { 32 | if len(e.Errors) == 0 { 33 | return nil 34 | } 35 | return e 36 | } 37 | -------------------------------------------------------------------------------- /fexcel/fexcel.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const Version = "2.0.2" 8 | 9 | func Logo() string { 10 | return fmt.Sprintf(` __ _ 11 | / _| | | 12 | | |_ _____ _____ ___| | 13 | | _/ _ \ \/ / __/ _ \ | 14 | | || __/> < (_| __/ | 15 | |_| \___/_/\_\___\___|_| 16 | v%s 17 | 18 | by ONE Robotics Company 19 | www.onerobotics.com 20 | 21 | `, Version) 22 | } 23 | -------------------------------------------------------------------------------- /fexcel/file.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/360EntSecGroup-Skylar/excelize/v2" 10 | ) 11 | 12 | type Location struct { 13 | Axis string // e.g. A2 14 | Sheet string 15 | Offset int 16 | } 17 | 18 | // returns a Location based on a cell specification 19 | // spec can be in the following forms: 20 | // 21 | // Offset:Sheet:Cell 22 | // Sheet:Cell 23 | // Cell 24 | // 25 | // if the sheet is not provided in the spec, the default 26 | // sheet is used. 27 | // 28 | func NewLocation(spec string, defaultSheet string) (*Location, error) { 29 | parts := strings.Split(spec, ":") 30 | 31 | switch len(parts) { 32 | case 3: 33 | offset, err := strconv.Atoi(parts[0]) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &Location{Sheet: parts[1], Axis: parts[2], Offset: offset}, nil 38 | case 2: 39 | return &Location{Sheet: parts[0], Axis: parts[1]}, nil 40 | case 1: 41 | // e.g. A2 42 | if defaultSheet == "" { 43 | return nil, fmt.Errorf("cell specification %q requires a default sheet, but none has been defined", spec) 44 | } 45 | return &Location{Sheet: defaultSheet, Axis: spec}, nil 46 | } 47 | 48 | return nil, fmt.Errorf("Cell specification %q is invalid. Should be in the form [Sheet:]Cell e.g. Sheet1:A2 or just A2.", spec) 49 | } 50 | 51 | type Definition struct { 52 | Type Type 53 | Id int 54 | Comment string 55 | } 56 | 57 | type File struct { 58 | path string 59 | xlsx *excelize.File 60 | 61 | Config FileConfig 62 | Locations map[Type]*Location 63 | Warnings []string 64 | } 65 | 66 | func newFile(path string, cfg FileConfig) (*File, error) { 67 | err := cfg.Validate() 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | f := File{path: path, Config: cfg} 73 | 74 | // set locations based on config 75 | f.Locations = make(map[Type]*Location) 76 | types := []Type{Constant, Numreg, Posreg, Ualm, Ain, Aout, Din, Dout, Gin, Gout, Rin, Rout, Sreg, Flag} 77 | for _, t := range types { 78 | spec := cfg.SpecFor(t) 79 | if spec != "" { 80 | loc, err := NewLocation(spec, cfg.Sheet) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | f.Locations[t] = loc 86 | } 87 | } 88 | 89 | return &f, nil 90 | } 91 | 92 | func OpenFile(path string, cfg FileConfig) (*File, error) { 93 | f, err := newFile(path, cfg) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | xlsx, err := excelize.OpenFile(f.path) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | f.xlsx = xlsx 104 | 105 | return f, nil 106 | } 107 | 108 | func NewFile(path string, cfg FileConfig) (*File, error) { 109 | f, err := newFile(path, cfg) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | // file must not exist 115 | if _, err := os.Stat(path); os.IsNotExist(err) { 116 | f.xlsx = excelize.NewFile() 117 | 118 | return f, nil 119 | } 120 | 121 | return nil, fmt.Errorf("File %q already exists", path) 122 | } 123 | 124 | func (f *File) Save() error { 125 | return f.xlsx.SaveAs(f.path) 126 | } 127 | 128 | func (f *File) readInt(sheet string, col, row int) (int, error) { 129 | axis, err := excelize.CoordinatesToCellName(col, row) 130 | if err != nil { 131 | return 0, err 132 | } 133 | 134 | value, err := f.xlsx.GetCellValue(sheet, axis) 135 | if err != nil { 136 | return 0, err 137 | } 138 | 139 | i, err := strconv.Atoi(value) 140 | if err != nil { 141 | return 0, err 142 | } 143 | 144 | return i, nil 145 | } 146 | 147 | func (f *File) readString(sheet string, col, row int) (string, error) { 148 | axis, err := excelize.CoordinatesToCellName(col, row) 149 | if err != nil { 150 | return "", err 151 | } 152 | 153 | value, err := f.xlsx.GetCellValue(sheet, axis) 154 | if err != nil { 155 | return "", err 156 | } 157 | 158 | return value, nil 159 | } 160 | 161 | func (f *File) readDefinition(t Type, sheet string, col, row, offset int) (d Definition, err error) { 162 | d.Type = t 163 | 164 | d.Id, err = f.readInt(sheet, col, row) 165 | if err != nil { 166 | return 167 | } 168 | 169 | d.Comment, err = f.readString(sheet, col+offset, row) 170 | if maxLength := MaxLengthFor(t); len(d.Comment) > maxLength { 171 | var axis string 172 | axis, err = excelize.CoordinatesToCellName(col+offset, row) 173 | if err != nil { 174 | return 175 | } 176 | 177 | f.Warnings = append(f.Warnings, fmt.Sprintf("comment in [%s]%s for %s[%d] will be truncated to %q (length %d > max length %d for %ss)", sheet, axis, d.Type, d.Id, d.Comment[:maxLength], len(d.Comment), maxLength, t)) 178 | } 179 | 180 | return 181 | } 182 | 183 | func (f *File) AllDefinitions() (map[Type][]Definition, error) { 184 | defs := make(map[Type][]Definition) 185 | 186 | for t, _ := range f.Locations { 187 | if t == Constant { 188 | continue 189 | } 190 | 191 | d, err := f.Definitions(t) 192 | if err != nil { 193 | return nil, err 194 | } 195 | 196 | defs[t] = d 197 | } 198 | 199 | return defs, nil 200 | } 201 | 202 | func (f *File) Definitions(t Type) ([]Definition, error) { 203 | loc, defined := f.Locations[t] 204 | if !defined { 205 | return nil, fmt.Errorf("Location for %s not defined", t) 206 | } 207 | 208 | col, row, err := excelize.CellNameToCoordinates(loc.Axis) 209 | if err != nil { 210 | return nil, fmt.Errorf("Invalid location for %s: %q", t, loc.Axis) 211 | } 212 | 213 | var defs []Definition 214 | for ; ; row++ { 215 | // check for blank id 216 | s, err := f.readString(loc.Sheet, col, row) 217 | if err != nil { 218 | return nil, err 219 | } 220 | if s == "" { 221 | break 222 | } 223 | 224 | offset := loc.Offset 225 | if offset == 0 { 226 | offset = f.Config.Offset 227 | } 228 | 229 | d, err := f.readDefinition(t, loc.Sheet, col, row, offset) 230 | if err != nil { 231 | return nil, err 232 | } 233 | 234 | defs = append(defs, d) 235 | 236 | } 237 | 238 | return defs, nil 239 | } 240 | 241 | func (f *File) SetValue(sheet string, col int, row int, value interface{}) error { 242 | axis, err := excelize.CoordinatesToCellName(col, row) 243 | if err != nil { 244 | return err 245 | } 246 | 247 | return f.xlsx.SetCellValue(sheet, axis, value) 248 | } 249 | 250 | // excelize does not create a new sheet if it already exists 251 | func (f *File) CreateSheet(name string) { 252 | f.xlsx.NewSheet(name) 253 | } 254 | 255 | func (f *File) Constants() (map[string]string, error) { 256 | loc, defined := f.Locations[Constant] 257 | if !defined { 258 | return nil, fmt.Errorf("Location for %s not defined", Constant) 259 | } 260 | 261 | constants := make(map[string]string) 262 | 263 | col, row, err := excelize.CellNameToCoordinates(loc.Axis) 264 | if err != nil { 265 | return nil, fmt.Errorf("Invalid location for %s: %q", Constant, loc.Axis) 266 | } 267 | 268 | for ; ; row++ { 269 | // check for blank identifier 270 | id, err := f.readString(loc.Sheet, col, row) 271 | if err != nil { 272 | return nil, err 273 | } 274 | if id == "" { 275 | break 276 | } 277 | 278 | offset := loc.Offset 279 | if offset == 0 { 280 | offset = f.Config.Offset 281 | } 282 | 283 | value, err := f.readString(loc.Sheet, col+offset, row) 284 | if err != nil { 285 | return nil, err 286 | } 287 | if value == "" { 288 | f.Warnings = append(f.Warnings, fmt.Sprintf("Definition for constant %q is blank", id)) 289 | continue 290 | } 291 | 292 | constants[id] = value 293 | } 294 | 295 | return constants, nil 296 | } 297 | -------------------------------------------------------------------------------- /fexcel/file_test.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | ) 7 | 8 | const testDir = "testdata" 9 | 10 | func TestOpenFile(t *testing.T) { 11 | fpath := filepath.Join(testDir, "test.xlsx") 12 | cfg := FileConfig{Offset: 1, Numregs: "Data:A2"} 13 | 14 | f, err := OpenFile(fpath, cfg) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | if f.Locations[Numreg].Sheet != "Data" { 20 | t.Errorf("Bad sheet. Got %q, want %q", f.Locations[Numreg].Sheet, "Data") 21 | } 22 | if f.Locations[Numreg].Axis != "A2" { 23 | t.Errorf("Bad axis. Got %q, want %q", f.Locations[Numreg].Axis, "A2") 24 | } 25 | } 26 | 27 | func TestConstants(t *testing.T) { 28 | fpath := filepath.Join(testDir, "test.xlsx") 29 | 30 | f, err := OpenFile(fpath, FileConfig{ 31 | Sheet: "Data", 32 | Offset: 1, 33 | Constants: "M2", 34 | }) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | constants, err := f.Constants() 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | if constants["FOO"] != "bar" { 45 | t.Errorf("Expected %q, got %q", "bar", constants["FOO"]) 46 | } 47 | 48 | if constants["BAZ"] != "3.14" { 49 | t.Errorf("Expected %q, got %q", "3.14", constants["BAZ"]) 50 | } 51 | } 52 | 53 | func TestDefinitions(t *testing.T) { 54 | fpath := filepath.Join(testDir, "test.xlsx") 55 | 56 | cfg := FileConfig{ 57 | Sheet: "Data", 58 | Offset: 1, 59 | Constants: "M2", 60 | Numregs: "A2", 61 | Posregs: "D2", 62 | Sregs: "G2", 63 | Flags: "J2", 64 | Dins: "IO:A2", 65 | Douts: "IO:C2", 66 | Rins: "IO:E2", 67 | Routs: "IO:G2", 68 | Gins: "IO:I2", 69 | Gouts: "IO:K2", 70 | Ains: "IO:M2", 71 | Aouts: "IO:O2", 72 | Ualms: "Alarms:A2", 73 | } 74 | 75 | f, err := OpenFile(fpath, cfg) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | expected := []struct { 81 | Type 82 | defs []Definition 83 | }{ 84 | { 85 | Numreg, 86 | []Definition{ 87 | {Numreg, 1, "this is an extremely long comment"}, 88 | {Numreg, 2, "two"}, 89 | {Numreg, 3, "three"}, 90 | {Numreg, 4, "four"}, 91 | {Numreg, 5, "five"}, 92 | }, 93 | }, 94 | { 95 | Posreg, 96 | []Definition{ 97 | {Posreg, 1, "pr1"}, 98 | {Posreg, 2, "pr2"}, 99 | {Posreg, 3, "pr3"}, 100 | {Posreg, 4, "pr4"}, 101 | {Posreg, 5, "pr5"}, 102 | }, 103 | }, 104 | { 105 | Sreg, 106 | []Definition{ 107 | {Sreg, 1, "sreg1"}, 108 | {Sreg, 2, "sreg2"}, 109 | }, 110 | }, 111 | { 112 | Din, 113 | []Definition{ 114 | {Din, 1, "din1"}, 115 | {Din, 2, "din2"}, 116 | {Din, 3, "din3"}, 117 | }, 118 | }, 119 | { 120 | Dout, 121 | []Definition{ 122 | {Dout, 1, "dout1"}, 123 | {Dout, 2, "dout2"}, 124 | {Dout, 3, "dout3"}, 125 | {Dout, 4, "dout4"}, 126 | }, 127 | }, 128 | { 129 | Rin, 130 | []Definition{ 131 | {Rin, 1, "rin1"}, 132 | {Rin, 2, "rin2"}, 133 | }, 134 | }, 135 | { 136 | Rout, 137 | []Definition{ 138 | {Rout, 1, "rout1"}, 139 | }, 140 | }, 141 | { 142 | Gin, 143 | []Definition{ 144 | {Gin, 1, "gin1"}, 145 | }, 146 | }, 147 | { 148 | Gout, 149 | []Definition{ 150 | {Gout, 1, "gout1"}, 151 | }, 152 | }, 153 | { 154 | Ain, 155 | []Definition{ 156 | {Ain, 1, "ain1"}, 157 | }, 158 | }, 159 | { 160 | Aout, 161 | []Definition{ 162 | {Aout, 1, "aout1"}, 163 | }, 164 | }, 165 | { 166 | Ualm, 167 | []Definition{ 168 | {Ualm, 1, "test"}, 169 | {Ualm, 2, "test two"}, 170 | {Ualm, 3, "test three"}, 171 | {Ualm, 4, "test four"}, 172 | }, 173 | }, 174 | } 175 | 176 | for _, e := range expected { 177 | defs, err := f.Definitions(e.Type) 178 | if err != nil { 179 | t.Errorf("Failed to get defs for %s: %q", e.Type, err) 180 | continue 181 | } 182 | 183 | if len(defs) != len(e.defs) { 184 | t.Errorf("Bad # of defs for %s. Got %d, want %d", e.Type, len(defs), len(e.defs)) 185 | continue 186 | } 187 | 188 | for id, def := range defs { 189 | if def.Type != e.Type { 190 | t.Errorf("Bad Type. Got %q, want %q", def.Type, e.Type) 191 | } 192 | 193 | if def.Id != e.defs[id].Id { 194 | t.Errorf("Bad id. Got %d, want %d", def.Id, e.defs[id].Id) 195 | } 196 | 197 | if def.Comment != e.defs[id].Comment { 198 | t.Errorf("Bad comment. Got %q, want %q", def.Comment, e.defs[id].Comment) 199 | } 200 | 201 | } 202 | } 203 | 204 | if len(f.Warnings) != 1 { 205 | t.Fatal("Expected 1 warning") 206 | } 207 | 208 | want := "comment in [Data]B2 for R[1] will be truncated to \"this is an extre\" (length 33 > max length 16 for Rs)" 209 | if f.Warnings[0] != want { 210 | t.Errorf("Bad warning. Got %q, want %q", f.Warnings[0], want) 211 | } 212 | } 213 | 214 | func TestNewLocation(t *testing.T) { 215 | tests := []struct { 216 | spec string 217 | defaultSheet string 218 | expAxis string 219 | expSheet string 220 | expOffset int 221 | }{ 222 | {"A2", "Foo", "A2", "Foo", 0}, 223 | {"Bar:A2", "Foo", "A2", "Bar", 0}, 224 | {"D2", "Baz", "D2", "Baz", 0}, 225 | {"5:Bar:A2", "Foo", "A2", "Bar", 5}, 226 | } 227 | 228 | for _, test := range tests { 229 | l, err := NewLocation(test.spec, test.defaultSheet) 230 | if err != nil { 231 | t.Fatal(err) 232 | } 233 | 234 | if l.Axis != test.expAxis { 235 | t.Errorf("Bad axis. Got %q, want %q", l.Axis, test.expAxis) 236 | } 237 | if l.Sheet != test.expSheet { 238 | t.Errorf("Bad sheet. Got %q, want %q", l.Sheet, test.expSheet) 239 | } 240 | } 241 | } 242 | 243 | func TestNewFile(t *testing.T) { 244 | fpath := filepath.Join(testDir, "newfile.xlsx") 245 | cfg := FileConfig{Offset: 1, Numregs: "Data:A2"} 246 | 247 | _, err := NewFile(fpath, cfg) 248 | if err != nil { 249 | t.Fatal(err) 250 | } 251 | } 252 | 253 | func TestNewFileAlreadyExists(t *testing.T) { 254 | fpath := filepath.Join(testDir, "test.xlsx") 255 | cfg := FileConfig{Offset: 1, Numregs: "Data:A2"} 256 | 257 | _, err := NewFile(fpath, cfg) 258 | if err == nil { 259 | t.Fatal("expected an error") 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /fexcel/set.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | fanuc "github.com/onerobotics/go-fanuc" 8 | ) 9 | 10 | type SetCommand struct { 11 | fpath string 12 | file *File 13 | targets []*Target 14 | 15 | Definitions map[Type][]Definition 16 | Errors map[string]*errorList 17 | } 18 | 19 | func NewSetCommand(fpath string, cfg Config, targets ...string) (*SetCommand, error) { 20 | if len(targets) == 0 { 21 | return nil, fmt.Errorf("Need at least one target") 22 | } 23 | 24 | err := cfg.FileConfig.Validate() 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | s := SetCommand{fpath: fpath} 30 | 31 | for _, path := range targets { 32 | t, err := NewTarget(path, cfg.Timeout) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | if _, ok := t.client.(*fanuc.HTTPClient); !ok { 38 | return nil, fmt.Errorf("%q is not a valid remote host", path) 39 | } 40 | 41 | s.targets = append(s.targets, t) 42 | } 43 | 44 | f, err := OpenFile(fpath, cfg.FileConfig) 45 | if err != nil { 46 | return nil, err 47 | } 48 | s.file = f 49 | 50 | s.Definitions, err = s.file.AllDefinitions() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | s.Errors = make(map[string]*errorList) 56 | for _, host := range s.Hosts() { 57 | s.Errors[host] = &errorList{} 58 | } 59 | 60 | return &s, nil 61 | } 62 | 63 | // host -> type -> count 64 | type setResult struct { 65 | Counts map[string]map[Type]int 66 | mux sync.Mutex 67 | } 68 | 69 | func (s *setResult) Inc(host string, t Type) { 70 | s.mux.Lock() 71 | defer s.mux.Unlock() 72 | 73 | s.Counts[host][t]++ 74 | } 75 | 76 | func newSetResult(targets []*Target) *setResult { 77 | var result setResult 78 | result.Counts = make(map[string]map[Type]int) 79 | for _, t := range targets { 80 | result.Counts[t.Name] = make(map[Type]int) 81 | } 82 | return &result 83 | } 84 | 85 | func (s *SetCommand) Hosts() []string { 86 | var hosts []string 87 | for _, t := range s.targets { 88 | hosts = append(hosts, t.Name) 89 | } 90 | return hosts 91 | } 92 | 93 | func (s *SetCommand) Set(wg *sync.WaitGroup, target *Target, result *setResult) { 94 | defer wg.Done() 95 | 96 | for typ, defs := range s.Definitions { 97 | err := target.GetComments(typ) 98 | if err != nil { 99 | s.Errors[target.Name].Add(err) 100 | return 101 | } 102 | 103 | for _, def := range defs { 104 | want := Truncated(def.Comment, typ) 105 | if got, ok := target.Comments[typ][def.Id]; ok && got == want { 106 | continue 107 | } 108 | 109 | err := target.SetComment(typ, def.Id, want) 110 | if err != nil { 111 | s.Errors[target.Name].Add(err) 112 | return 113 | } else { 114 | result.Inc(target.Name, typ) 115 | } 116 | } 117 | } 118 | } 119 | 120 | func (s *SetCommand) Execute() (*setResult, error) { 121 | result := newSetResult(s.targets) 122 | 123 | var wg sync.WaitGroup 124 | for _, target := range s.targets { 125 | wg.Add(1) 126 | go s.Set(&wg, target, result) 127 | } 128 | wg.Wait() 129 | 130 | return result, s.Err() 131 | } 132 | 133 | func (s SetCommand) Error() string { 134 | var str string 135 | for host, err := range s.Errors { 136 | if err.Err() != nil { 137 | str += fmt.Sprintf("%s: %s\n", host, err.Error()) 138 | } 139 | } 140 | return str 141 | } 142 | 143 | func (s SetCommand) Err() error { 144 | if s.Error() == "" { 145 | return nil 146 | } 147 | return s 148 | } 149 | -------------------------------------------------------------------------------- /fexcel/set_test.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "sync/atomic" 7 | "testing" 8 | ) 9 | 10 | func TestNewSetCommandErrors(t *testing.T) { 11 | tests := []struct { 12 | fpath string 13 | cfg Config 14 | targets []string 15 | err string 16 | }{ 17 | {"./testdata/test.xlsx", Config{}, []string{}, "Need at least one target"}, 18 | {"./testdata/test.xlsx", Config{}, []string{"foo"}, "no cell locations defined"}, 19 | {"./testdata/test.xlsx", Config{FileConfig: FileConfig{Numregs: "A2"}}, []string{"./testdata"}, "offset must be nonzero"}, 20 | {"./testdata/test.xlsx", Config{FileConfig: FileConfig{Numregs: "A2", Offset: 1}}, []string{"./testdata"}, "\"./testdata\" is not a valid remote host"}, 21 | } 22 | 23 | for id, test := range tests { 24 | _, err := NewSetCommand(test.fpath, test.cfg, test.targets...) 25 | if err == nil { 26 | t.Errorf("case(%d): expected an error", id) 27 | continue 28 | } 29 | 30 | if err.Error() != test.err { 31 | t.Errorf("bad error. Got %q, want %q", err.Error(), test.err) 32 | } 33 | } 34 | 35 | // valid 36 | _, err := NewSetCommand("./testdata/test.xlsx", Config{FileConfig: FileConfig{Numregs: "Data:A2", Offset: 1}}, "127.0.0.1") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | } 41 | 42 | func TestSetCommand(t *testing.T) { 43 | var commentCount uint32 44 | hf := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 45 | switch req.URL.Path { 46 | case "/KAREL/ComSet": 47 | rw.Write([]byte("OK")) 48 | atomic.AddUint32(&commentCount, 1) 49 | case "/MD/numreg.va": 50 | rw.Write([]byte(" [1] = 0 'this is an extre'\n")) 51 | default: 52 | http.Error(rw, "Not implemented", http.StatusNotImplemented) 53 | t.Fatalf("Unexpected request: %q", req.URL.Path) 54 | } 55 | }) 56 | 57 | s1 := httptest.NewServer(hf) 58 | defer s1.Close() 59 | s2 := httptest.NewServer(hf) 60 | defer s2.Close() 61 | 62 | hosts := []string{s1.URL, s2.URL} 63 | 64 | s, err := NewSetCommand("./testdata/test.xlsx", Config{FileConfig: FileConfig{Numregs: "Data:A2", Offset: 1}}, hosts...) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | result, err := s.Execute() 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | for _, host := range hosts { 75 | if count := result.Counts[host][Numreg]; count != 4 { 76 | t.Errorf("Result.Counts[%s][%s]: Got %d, want 4", host, Numreg, count) 77 | } 78 | } 79 | 80 | // (5 defs - 1 accurate) * 2 hosts = 8 81 | if commentCount != 8 { 82 | t.Errorf("comment request called %d times. Want 8", commentCount) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /fexcel/strings.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | // No, this does not account for special cases (like words ending in s or 4 | // or the word "fish"), but it's good enough for fexcel's purposes 5 | func Pluralize(word string, i int) string { 6 | if i == 1 { 7 | return word 8 | } else { 9 | return word + "s" 10 | } 11 | } 12 | 13 | func MaxLengthFor(t Type) int { 14 | switch t { 15 | case Numreg, Posreg, Sreg: 16 | return 16 17 | case Ualm: 18 | return 29 19 | default: 20 | return 24 21 | } 22 | } 23 | 24 | func Truncated(s string, t Type) string { 25 | if max := MaxLengthFor(t); len(s) > max { 26 | return s[:max] 27 | } 28 | return s 29 | } 30 | -------------------------------------------------------------------------------- /fexcel/strings_test.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPluralize(t *testing.T) { 8 | tests := []struct { 9 | word string 10 | count int 11 | want string 12 | }{ 13 | {"Numeric Register", 0, "Numeric Registers"}, 14 | {"Numeric Register", 1, "Numeric Register"}, 15 | {"Numeric Register", 2, "Numeric Registers"}, 16 | {"Ualm", 5, "Ualms"}, 17 | {"Digital Input", 1, "Digital Input"}, 18 | } 19 | 20 | for _, test := range tests { 21 | got := Pluralize(test.word, test.count) 22 | if got != test.want { 23 | t.Errorf("Got %q, want %q", got, test.want) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fexcel/target.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | fanuc "github.com/onerobotics/go-fanuc" 8 | ) 9 | 10 | var fanucType = [...]fanuc.Type{ 11 | Numreg: fanuc.Numreg, 12 | Posreg: fanuc.Posreg, 13 | Ualm: fanuc.Ualm, 14 | Ain: fanuc.Ain, 15 | Aout: fanuc.Aout, 16 | Din: fanuc.Din, 17 | Dout: fanuc.Dout, 18 | Gin: fanuc.Gin, 19 | Gout: fanuc.Gout, 20 | Rin: fanuc.Rin, 21 | Rout: fanuc.Rout, 22 | Sreg: fanuc.Sreg, 23 | Flag: fanuc.Flag, 24 | } 25 | 26 | type Target struct { 27 | client fanuc.Client 28 | 29 | Name string 30 | Comments map[Type]map[int]string 31 | } 32 | 33 | func NewTarget(path string, timeout int) (*Target, error) { 34 | client, err := fanuc.NewClient(path) 35 | if err != nil { 36 | return nil, err 37 | } 38 | if c, ok := client.(*fanuc.HTTPClient); ok { 39 | c.SetTimeout(time.Duration(timeout) * time.Second) 40 | } 41 | 42 | var t Target 43 | t.client = client 44 | t.Name = path 45 | t.Comments = make(map[Type]map[int]string) 46 | 47 | return &t, nil 48 | } 49 | 50 | func (t *Target) GetComments(typ Type) error { 51 | t.Comments[typ] = make(map[int]string) 52 | 53 | switch typ { 54 | case Numreg: 55 | numregs, err := t.client.NumericRegisters() 56 | if err != nil { 57 | return err 58 | } 59 | for _, r := range numregs { 60 | t.Comments[typ][r.Id] = r.Comment 61 | } 62 | case Posreg: 63 | posregs, err := t.client.PositionRegisters() 64 | if err != nil { 65 | return err 66 | } 67 | for _, r := range posregs { 68 | t.Comments[typ][r.Id] = r.Comment 69 | } 70 | case Ain, Aout, Din, Dout, Flag, Gin, Gout, Rin, Rout: 71 | ports, err := t.client.IO(fanucType[typ]) 72 | if err != nil { 73 | return err 74 | } 75 | for _, r := range ports { 76 | t.Comments[typ][r.Id] = r.Comment 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | 83 | func (t *Target) SetComment(typ Type, id int, comment string) error { 84 | if c, ok := t.client.(*fanuc.HTTPClient); ok { 85 | return c.SetComment(fanucType[typ], id, comment) 86 | } else { 87 | return errors.New("need a fanuc.HTTPClient") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /fexcel/target_test.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "testing" 5 | 6 | fanuc "github.com/onerobotics/go-fanuc" 7 | ) 8 | 9 | func TestNewTarget(t *testing.T) { 10 | target, err := NewTarget("127.0.0.1", 5) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | if _, ok := target.client.(*fanuc.HTTPClient); !ok { 16 | t.Errorf("Expected an HTTPClient. Got %T", target.client) 17 | } 18 | 19 | target, err = NewTarget("testdata", 0) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | if _, ok := target.client.(*fanuc.FileClient); !ok { 25 | t.Errorf("Expected a FileClient. Got %T", target.client) 26 | } 27 | } 28 | 29 | func TestGetComments(t *testing.T) { 30 | target, err := NewTarget("testdata", 0) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | err = target.GetComments(Numreg) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | if numregs, ok := target.Comments[Numreg]; ok { 41 | if len(numregs) != 200 { 42 | t.Fatalf("Only got %d numregs. Want 200", len(numregs)) 43 | } 44 | tests := []struct { 45 | id int 46 | comment string 47 | }{ 48 | {1, "this is an extre"}, 49 | {2, "two"}, 50 | {10, "UngripDelay"}, 51 | } 52 | 53 | for _, test := range tests { 54 | if r, ok := numregs[test.id]; ok { 55 | if r != test.comment { 56 | t.Errorf("Bad comment for R[%d]. Got %q, want %q", test.id, r, test.comment) 57 | } 58 | } else { 59 | t.Errorf("R[%d] undefined", test.id) 60 | } 61 | } 62 | } else { 63 | t.Errorf("numregs not found") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /fexcel/testdata/numreg.va: -------------------------------------------------------------------------------- 1 | [*NUMREG*]$NUMREG Storage: SHADOW Access: RW : ARRAY[200] OF Numeric Reg 2 | [1] = 0 'this is an extre' 3 | [2] = 0 'two' 4 | [3] = 0 'three' 5 | [4] = 0 'four' 6 | [5] = 0 'five' 7 | [6] = 0 'MaxInfeedTime' 8 | [7] = 0 'OutfeedTime' 9 | [8] = 0 'MaxOutfeedTime' 10 | [9] = 0 'GripDelay' 11 | [10] = 0 'UngripDelay' 12 | [11] = 0 'ModelID' 13 | [12] = 0 'AccumTarget' 14 | [13] = 0 'AccumulatorSpaci' 15 | [14] = 0 'InfeedAPLD' 16 | [15] = 0 'InfeedRTLD' 17 | [16] = 70.000000 'OutfeedAPLD' 18 | [17] = 70.000000 'OutfeedRTLD' 19 | [18] = 0 'AccumAPLD' 20 | [19] = 0 'AccumAPLD' 21 | [20] = 0 'PpChkWait' 22 | [21] = 0 'LTBucket1' 23 | [22] = 0 'LTBucket2' 24 | [23] = 0 'LTBucket3' 25 | [24] = 0 'LTBucket4' 26 | [25] = 0 'LTBucket5' 27 | [26] = 0 'LTBucket6' 28 | [27] = 0 'LTBucket7' 29 | [28] = 0 'LTBucket8' 30 | [29] = 0 'LTBucket9' 31 | [30] = 0 'BlowoffTime' 32 | [31] = 0 'Grip1' 33 | [32] = 0 'Grip2' 34 | [33] = 0 'Grip3' 35 | [34] = 0 'MinInfeedTime' 36 | [35] = 0 'MinOutfeedTime' 37 | [36] = 0 'InfeedTrkTime' 38 | [37] = 0 'OutfeedTrkTime' 39 | [38] = 0 'AccumPickTime' 40 | [39] = 0 'AccumPlaceTime' 41 | [40] = 0 'SP_HomeLin' 42 | [41] = 0 'SP_HomeJ' 43 | [42] = 0 'SP_TraverseJ' 44 | [43] = 0 'SP_ClearJ' 45 | [44] = 0 'SP_PerchJ' 46 | [45] = 0 'InfeedStatus' 47 | [46] = 0 'Pallet Pick Enb' 48 | [47] = 0 'SP_ApproachLin' 49 | [48] = 0 'Alt Infeed Enb' 50 | [49] = 0 'LoadTableGroup1' 51 | [50] = 0 'LoadTableGroup2' 52 | [51] = 0 'LoadTableGroup3' 53 | [52] = 0 'LoadTableCount' 54 | [53] = 0 'PLC Infeed Num' 55 | [54] = 0 'PLC Layer Num' 56 | [55] = 0 'AssyCount' 57 | [56] = 0 'AssySpacing' 58 | [57] = 0 'AssyMax' 59 | [58] = 0 'Pal Num For Dims' 60 | [59] = 0 '' 61 | [60] = 0 'HoodStackCount' 62 | [61] = 0 'HoodSpacing' 63 | [62] = 0 'HoodStackMax' 64 | [63] = 0 '' 65 | [64] = 0 '' 66 | [65] = 0 '' 67 | [66] = 0 '' 68 | [67] = 0 '' 69 | [68] = 0 '' 70 | [69] = 0 '' 71 | [70] = 0 '' 72 | [71] = 0 '' 73 | [72] = 0 '' 74 | [73] = 0 '' 75 | [74] = 0 '' 76 | [75] = 0 'Pick Orient DO' 77 | [76] = 0 '' 78 | [77] = 0 '' 79 | [78] = 0 'Units in next pk' 80 | [79] = 0 'Orient of nxt pk' 81 | [80] = 0 'Nxt Cycle' 82 | [81] = 0 'Nxt Unitload' 83 | [82] = 0 'Nxt Infeed' 84 | [83] = 0 'Nxt Pallet' 85 | [84] = 0 'Nxt Layer' 86 | [85] = 0 'Nxt Unit' 87 | [86] = 0 'Nxt Gripper' 88 | [87] = 0 'Nxt TotPkUnits' 89 | [88] = 0 'Nxt TotPkPlCyc' 90 | [89] = 0 'Nxt Pallet Done' 91 | [90] = 0 'Nxt Pallet Clear' 92 | [91] = 0 'Nxt SlipSheet Re' 93 | [92] = 0 'Nxt Slipsheet Hg' 94 | [93] = 0 'Nxt GripOff Dela' 95 | [94] = 0 'Part Drop Pos' 96 | [95] = 0 'TemporaryUse1' 97 | [96] = 0 'Internal Registe' 98 | [97] = 0 'Internal Registe' 99 | [98] = 0 'TemporaryUse4' 100 | [99] = 0 'ActiveRecipe' 101 | [100] = 0 'Speed Override' 102 | [101] = 0 'Cycle Stop G1' 103 | [102] = 0 '' 104 | [103] = 0 'RobotID' 105 | [104] = 0 'Gripper Id G1' 106 | [105] = 0 'speedOverride' 107 | [106] = 0 'cyc1 layer' 108 | [107] = 0 'cyc1 unit' 109 | [108] = 0 'cyc2 layer' 110 | [109] = 0 'Pk1x CvId' 111 | [110] = 0 'Pk1x StnId' 112 | [111] = 0 'MAX UNITS' 113 | [112] = 0 'P1 Current Layer' 114 | [113] = 0 'P1 Current Unit' 115 | [114] = 0 'P2 Current Layer' 116 | [115] = 0 'P2 Current Unit' 117 | [116] = 0 'cyc1 pal' 118 | [117] = 0 'cyc2 pal' 119 | [118] = 0 '' 120 | [119] = 0 'pal to index' 121 | [120] = 0 'inspect pass' 122 | [121] = 0 '' 123 | [122] = 0 '' 124 | [123] = 0 'Pk1x GetQ stat' 125 | [124] = 0 '' 126 | [125] = 0 'rot' 127 | [126] = 0 '' 128 | [127] = 0 '' 129 | [128] = 0 '' 130 | [129] = 0 'Dp2x CvId' 131 | [130] = 0 'Dp1x StnId' 132 | [131] = 0 'Dp1x Qty' 133 | [132] = 0 'Dp1x DpMult' 134 | [133] = 0 '' 135 | [134] = 0 '' 136 | [135] = 0 '' 137 | [136] = 0 'Dp1x UsePPChk' 138 | [137] = 0 'Dp1x PPStat' 139 | [138] = 0 '' 140 | [139] = 0 '' 141 | [140] = 0 '' 142 | [141] = 0 '' 143 | [142] = 0 'Dp1x TrayCtr' 144 | [143] = 0 'Dp1x GetQ stat' 145 | [144] = 0 '' 146 | [145] = 0 'Dp1x TB' 147 | [146] = 0 '' 148 | [147] = 0 '' 149 | [148] = 0 '' 150 | [149] = 0 '' 151 | [150] = 0 '' 152 | [151] = 0 '' 153 | [152] = 0 '' 154 | [153] = 0 '' 155 | [154] = 0 '' 156 | [155] = 0 '' 157 | [156] = 0 '' 158 | [157] = 0 '' 159 | [158] = 0 '' 160 | [159] = 0 '' 161 | [160] = 0 'Pk2x StnId' 162 | [161] = 0 '' 163 | [162] = 0 '' 164 | [163] = 0 '' 165 | [164] = 0 '' 166 | [165] = 0 '' 167 | [166] = 0 '' 168 | [167] = 0 '' 169 | [168] = 0 '' 170 | [169] = 0 '' 171 | [170] = 0 '' 172 | [171] = 0 '' 173 | [172] = 0 '' 174 | [173] = 0 '' 175 | [174] = 0 '' 176 | [175] = 0 '' 177 | [176] = 0 '' 178 | [177] = 0 '' 179 | [178] = 0 '' 180 | [179] = 0 '' 181 | [180] = 0 'Dp2x StnId' 182 | [181] = 0 '' 183 | [182] = 0 '' 184 | [183] = 0 '' 185 | [184] = 0 '' 186 | [185] = 0 '' 187 | [186] = 0 '' 188 | [187] = 0 '' 189 | [188] = 0 '' 190 | [189] = 0 '' 191 | [190] = 0 '' 192 | [191] = 0 '' 193 | [192] = 0 '' 194 | [193] = 0 '' 195 | [194] = 0 '' 196 | [195] = 0 '' 197 | [196] = 0 '' 198 | [197] = 0 '' 199 | [198] = 0 '' 200 | [199] = 0 '' 201 | [200] = 0 'SIM/DryRun' 202 | 203 | [*NUMREG*]$MAXREGNUM Storage: CMOS Access: RW : INTEGER = 200 204 | -------------------------------------------------------------------------------- /fexcel/testdata/test.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onerobotics/fexcel/69bcbf10f4f2ffb851f44e971eb6fb0ddfb48c95/fexcel/testdata/test.xlsx -------------------------------------------------------------------------------- /fexcel/type.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import "strconv" 4 | 5 | type Type int 6 | 7 | const ( 8 | Constant Type = iota 9 | Numreg 10 | Posreg 11 | Ualm 12 | Ain 13 | Aout 14 | Din 15 | Dout 16 | Gin 17 | Gout 18 | Rin 19 | Rout 20 | Sreg 21 | Flag 22 | Uin 23 | Uout 24 | Sin 25 | Sout 26 | ) 27 | 28 | var types = [...]string{ 29 | Constant: "Constant", 30 | Numreg: "R", 31 | Posreg: "PR", 32 | Ualm: "UALM", 33 | Ain: "AI", 34 | Aout: "AO", 35 | Din: "DI", 36 | Dout: "DO", 37 | Gin: "GI", 38 | Gout: "GO", 39 | Rin: "RI", 40 | Rout: "RO", 41 | Sreg: "SR", 42 | Flag: "F", 43 | Uin: "UI", 44 | Uout: "UOUT", 45 | Sin: "SI", 46 | Sout: "SO", 47 | } 48 | 49 | func (t Type) String() string { 50 | s := "" 51 | if 0 <= t && t < Type(len(types)) { 52 | s = types[t] 53 | } 54 | if s == "" { 55 | s = "type(" + strconv.Itoa(int(t)) + ")" 56 | } 57 | return s 58 | } 59 | -------------------------------------------------------------------------------- /fexcel/update.go: -------------------------------------------------------------------------------- 1 | package fexcel 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/blang/semver" 11 | wordwrap "github.com/mitchellh/go-wordwrap" 12 | ) 13 | 14 | type GitHubRelease struct { 15 | HTMLUrl string `json:"html_url"` 16 | TagName string `json:"tag_name"` 17 | Name string `json:"name"` 18 | Body string `json:"body"` 19 | PublishedAt time.Time `json:"published_at"` 20 | Assets []struct { 21 | DownloadUrl string `json:"browser_download_url"` 22 | } `json:"assets"` 23 | } 24 | 25 | type GitHubUpdateChecker struct { 26 | } 27 | 28 | func (g *GitHubUpdateChecker) UpdateCheck(w io.Writer) error { 29 | fmt.Fprintf(w, "\nChecking for updates... ") 30 | 31 | client := http.Client{ 32 | Timeout: 2 * time.Second, 33 | } 34 | 35 | req, err := http.NewRequest("GET", "https://api.github.com/repos/onerobotics/fexcel/releases/latest", nil) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | req.Header.Set("User-Agent", "fexcel-v"+Version) 41 | 42 | res, err := client.Do(req) 43 | if err != nil { 44 | return err 45 | } 46 | defer res.Body.Close() 47 | 48 | var release GitHubRelease 49 | err = json.NewDecoder(res.Body).Decode(&release) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | currentVersion, err := semver.Make(Version) 55 | if err != nil { 56 | return err 57 | } 58 | latestVersion, err := semver.Make(release.TagName[1:]) // tag has v prefix 59 | if err != nil { 60 | return err 61 | } 62 | 63 | if latestVersion.GT(currentVersion) { 64 | fmt.Fprintf(w, "an update is available!\n") 65 | fmt.Fprintf(w, "\nfexcel %s was released on %s (you are on v%s).\n", release.TagName, release.PublishedAt.Format("January 01, 2006"), Version) 66 | fmt.Fprintf(w, "Download here: %s\n\n", release.HTMLUrl) 67 | fmt.Fprintf(w, "%s\n", release.Name) 68 | fmt.Fprintf(w, "%s\n\n", wordwrap.WrapString(release.Body, 80)) 69 | } else { 70 | fmt.Fprintf(w, "You are on the latest version: v%s\n", Version) 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/onerobotics/fexcel 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 7 | github.com/blang/semver v3.5.1+incompatible 8 | github.com/mitchellh/go-wordwrap v1.0.0 9 | github.com/olekukonko/tablewriter v0.0.4 10 | github.com/onerobotics/go-fanuc v0.6.1 11 | github.com/spf13/cobra v0.0.5 12 | github.com/spf13/viper v1.6.2 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 h1:StMrA6UQ5Cm6206DxXGuV/NMqSIOIDoMXMYt8JPe1lE= 3 | github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2/go.mod h1:EfRHD2k+Kd7ijnqlwOrH1IifwgWB9yYJ0pdXtBZmlpU= 4 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 7 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 8 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 9 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 10 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 11 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 12 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 13 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 14 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 15 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 16 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 17 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 18 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 19 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 20 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 21 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 22 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 23 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 25 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 27 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 28 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 29 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 30 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 31 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 32 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 33 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 34 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 35 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 36 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 37 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 38 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 39 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 40 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 41 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 42 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 43 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 44 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 45 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 46 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 47 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 48 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 49 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 50 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 51 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 52 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 53 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 54 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 55 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 56 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 57 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 58 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 59 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 60 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 61 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 62 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 63 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 64 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 65 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 66 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 67 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 68 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 69 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 70 | github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= 71 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 72 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 73 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 74 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= 75 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 76 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 77 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 78 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 79 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 80 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 81 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 82 | github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= 83 | github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= 84 | github.com/onerobotics/go-fanuc v0.6.1 h1:w1+YaeW71JwvV+dQdflQ1s4iaz4nZCmUVeNpRpYoBn8= 85 | github.com/onerobotics/go-fanuc v0.6.1/go.mod h1:oPCFL6RBNpkiYu8/TjI30+rC2hvoCIksPdlbwo3mYiA= 86 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 87 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 88 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 89 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 90 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 91 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 92 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 93 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 94 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 95 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 96 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 97 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 98 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 99 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 100 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 101 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 102 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 103 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 104 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 105 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 106 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 107 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 108 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 109 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 110 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 111 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 112 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 113 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 114 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 115 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 116 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 117 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 118 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 119 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 120 | github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= 121 | github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= 122 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 123 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 124 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 125 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 126 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 127 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 128 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 129 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 130 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 131 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 132 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 133 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 134 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 135 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 136 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 137 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 138 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 139 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 140 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 141 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0= 142 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 143 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 144 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 145 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 146 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 147 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 148 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 149 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 150 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 151 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 152 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 153 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 154 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 155 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 156 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 157 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 158 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 159 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 160 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 161 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 162 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 163 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 164 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 165 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 166 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 167 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 168 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 169 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 170 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 171 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 172 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 173 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 174 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 175 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 176 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 177 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 178 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 179 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 180 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 181 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 182 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 183 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 184 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 185 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/onerobotics/fexcel/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | --------------------------------------------------------------------------------