summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Burke <rich.g.burke@gmail.com>2018-12-29 11:15:45 +0000
committerRichard Burke <rich.g.burke@gmail.com>2018-12-29 11:15:45 +0000
commit9d1119dfdddcbbc18d69056ceba787e61bd269e6 (patch)
tree5ac66f5c4a87712599ce8e4fc21d95a06594b713
parente8a9af6892869ab7f93cd089382dbe3c27dd20d1 (diff)
Added def command
GRV can now parse command definitions
-rw-r--r--cmd/grv/config_command_help.go27
-rw-r--r--cmd/grv/config_parse.go156
-rw-r--r--cmd/grv/config_parse_test.go56
-rw-r--r--doc/documentation.md21
4 files changed, 232 insertions, 28 deletions
diff --git a/cmd/grv/config_command_help.go b/cmd/grv/config_command_help.go
index 3a838ef..5a85087 100644
--- a/cmd/grv/config_command_help.go
+++ b/cmd/grv/config_command_help.go
@@ -375,3 +375,30 @@ func GenerateHelpCommandHelpSections(config Config) (helpSections []*HelpSection
},
}
}
+
+// GenerateDefCommandHelpSections generates help documentation for the def command
+func GenerateDefCommandHelpSections(config Config) (helpSections []*HelpSection) {
+ description := []HelpSectionText{
+ {text: "def", themeComponentID: CmpHelpViewSectionSubTitle},
+ {},
+ {text: "The def command allows a custom GRV command to be defined. It has the form:"},
+ {},
+ {text: "def NAME {", themeComponentID: CmpHelpViewSectionCodeBlock},
+ {text: "\tBODY", themeComponentID: CmpHelpViewSectionCodeBlock},
+ {text: "}", themeComponentID: CmpHelpViewSectionCodeBlock},
+ {},
+ {text: "where NAME is the name of the new command and BODY is a sequence of commands to execute."},
+ {text: "For example, to define a command \"maintab\" to open a new tab containing the CommitView for master:"},
+ {},
+ {text: "def maintab {", themeComponentID: CmpHelpViewSectionCodeBlock},
+ {text: "\taddtab Main", themeComponentID: CmpHelpViewSectionCodeBlock},
+ {text: "\taddview CommitView master", themeComponentID: CmpHelpViewSectionCodeBlock},
+ {text: "}", themeComponentID: CmpHelpViewSectionCodeBlock},
+ }
+
+ return []*HelpSection{
+ {
+ description: description,
+ },
+ }
+}
diff --git a/cmd/grv/config_parse.go b/cmd/grv/config_parse.go
index ebd8597..f71e1aa 100644
--- a/cmd/grv/config_parse.go
+++ b/cmd/grv/config_parse.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
+ "regexp"
slice "github.com/bradfitz/slice"
)
@@ -24,8 +25,16 @@ const (
gitCommand = "git"
gitInteractiveCommand = "giti"
helpCommand = "help"
+ defCommand = "def"
)
+const (
+ openingBrace = "{"
+ closingBrace = "}"
+)
+
+var isIdentifier = regexp.MustCompile(`[[:alnum:]]+`).MatchString
+
type commandConstructor func(parser *ConfigParser, commandToken *ConfigToken, tokens []*ConfigToken) (ConfigCommand, error)
// ConfigCommand represents a config command
@@ -124,8 +133,16 @@ type ShellCommand struct {
func (shellCommand *ShellCommand) configCommand() {}
+// DefCommand represents a function definition command
+type DefCommand struct {
+ commandName string
+ functionBody string
+}
+
+func (defCommand *DefCommand) configCommand() {}
+
type commandHelpGenerator func(config Config) []*HelpSection
-type commandCustomParser func(parser *ConfigParser, commandDescriptor *commandDescriptor, commandToken *ConfigToken) (command ConfigCommand, eof bool, err error)
+type commandCustomParser func(parser *ConfigParser) (tokens []*ConfigToken, err error)
type commandDescriptor struct {
tokenTypes []ConfigTokenType
@@ -202,6 +219,11 @@ var commandDescriptors = map[string]*commandDescriptor{
constructor: helpCommandConstructor,
commandHelpGenerator: GenerateHelpCommandHelpSections,
},
+ defCommand: {
+ customParser: parseDefCommand,
+ constructor: defCommandConstructor,
+ commandHelpGenerator: GenerateDefCommandHelpSections,
+ },
}
// GenerateConfigCommandHelpSections generates help documentation for all configuration commands
@@ -313,6 +335,10 @@ func (parser *ConfigParser) scan() (token *ConfigToken, err error) {
return
}
+func (parser *ConfigParser) scanRaw() (token *ConfigToken, err error) {
+ return parser.scanner.Scan()
+}
+
func (parser *ConfigParser) generateParseError(token *ConfigToken, errorMessage string, args ...interface{}) error {
return generateConfigError(parser.inputSource, token, errorMessage, args...)
}
@@ -355,16 +381,48 @@ func (parser *ConfigParser) parseCommand(commandToken *ConfigToken) (command Con
return
}
+ var tokens []*ConfigToken
+
if commandDescriptor.customParser != nil {
- return commandDescriptor.customParser(parser, commandDescriptor, commandToken)
+ if tokens, err = commandDescriptor.customParser(parser); err != nil {
+ return
+ }
+ } else {
+ for i := 0; i < len(commandDescriptor.tokenTypes); i++ {
+ var token *ConfigToken
+ token, err = parser.scan()
+ expectedConfigTokenType := commandDescriptor.tokenTypes[i]
+
+ switch {
+ case err != nil:
+ return
+ case token.err != nil:
+ err = parser.generateParseError(token, "Syntax Error")
+ return
+ case token.tokenType == CtkEOF:
+ err = parser.generateParseError(token, "Unexpected EOF")
+ eof = true
+ return
+ case (token.tokenType & expectedConfigTokenType) == 0:
+ err = parser.generateParseError(token, "Expected %v but got %v: \"%v\"",
+ ConfigTokenName(expectedConfigTokenType), ConfigTokenName(token.tokenType), token.value)
+ return
+ }
+
+ tokens = append(tokens, token)
+ }
}
- var tokens []*ConfigToken
+ command, err = commandDescriptor.constructor(parser, commandToken, tokens)
- for i := 0; i < len(commandDescriptor.tokenTypes); i++ {
+ return
+}
+
+func parseVarArgsCommand(parser *ConfigParser) (tokens []*ConfigToken, err error) {
+OuterLoop:
+ for {
var token *ConfigToken
token, err = parser.scan()
- expectedConfigTokenType := commandDescriptor.tokenTypes[i]
switch {
case err != nil:
@@ -373,47 +431,66 @@ func (parser *ConfigParser) parseCommand(commandToken *ConfigToken) (command Con
err = parser.generateParseError(token, "Syntax Error")
return
case token.tokenType == CtkEOF:
- err = parser.generateParseError(token, "Unexpected EOF")
- eof = true
- return
- case (token.tokenType & expectedConfigTokenType) == 0:
- err = parser.generateParseError(token, "Expected %v but got %v: \"%v\"",
- ConfigTokenName(expectedConfigTokenType), ConfigTokenName(token.tokenType), token.value)
- return
+ break OuterLoop
+ case token.tokenType == CtkTerminator:
+ break OuterLoop
}
tokens = append(tokens, token)
}
- command, err = commandDescriptor.constructor(parser, commandToken, tokens)
-
return
}
-func parseVarArgsCommand(parser *ConfigParser, commandDescriptor *commandDescriptor, commandToken *ConfigToken) (command ConfigCommand, eof bool, err error) {
- var tokens []*ConfigToken
+func parseDefCommand(parser *ConfigParser) (tokens []*ConfigToken, err error) {
+ commandNameToken, err := parser.scan()
+ if err != nil {
+ return
+ } else if commandNameToken.err != nil {
+ err = commandNameToken.err
+ return
+ } else if commandNameToken.tokenType != CtkWord {
+ err = parser.generateParseError(commandNameToken, "Expected function name but found %v", commandNameToken.value)
+ return
+ } else if !isIdentifier(commandNameToken.value) {
+ err = parser.generateParseError(commandNameToken, "Invalid function identifier %v", commandNameToken.value)
+ return
+ }
+
+ tokens = append(tokens, commandNameToken)
+
+ openingBraceToken, err := parser.scan()
+ if err != nil {
+ return
+ } else if openingBraceToken.err != nil {
+ err = openingBraceToken.err
+ return
+ } else if openingBraceToken.tokenType != CtkWord || openingBraceToken.value != openingBrace {
+ err = parser.generateParseError(openingBraceToken, "Expected %v but found %v", openingBrace, openingBraceToken.value)
+ return
+ }
+
+ tokens = append(tokens, openingBraceToken)
-OuterLoop:
for {
var token *ConfigToken
- token, err = parser.scan()
-
- switch {
- case err != nil:
+ if token, err = parser.scanRaw(); err != nil {
return
- case token.err != nil:
- err = parser.generateParseError(token, "Syntax Error")
+ } else if token.err != nil {
+ err = token.err
return
- case token.tokenType == CtkEOF:
- break OuterLoop
- case token.tokenType == CtkTerminator:
- break OuterLoop
}
tokens = append(tokens, token)
+
+ if token.tokenType == CtkWord && token.value == closingBrace {
+ break
+ } else if token.tokenType == CtkEOF {
+ err = parser.generateParseError(token, "Expected %v but reached EOF", closingBrace)
+ return
+ }
}
- command, err = commandDescriptor.constructor(parser, commandToken, tokens)
return
}
@@ -533,3 +610,26 @@ func gitCommandConstructor(parser *ConfigParser, commandToken *ConfigToken, toke
func helpCommandConstructor(parser *ConfigParser, commandToken *ConfigToken, tokens []*ConfigToken) (ConfigCommand, error) {
return &HelpCommand{}, nil
}
+
+func defCommandConstructor(parser *ConfigParser, commandToken *ConfigToken, tokens []*ConfigToken) (configCommand ConfigCommand, err error) {
+ if len(tokens) < 4 {
+ err = parser.generateParseError(commandToken, "Too few tokens (%v) for function definition", len(tokens))
+ return
+ }
+
+ commandName := tokens[0].value
+ var functionBodyBuffer bytes.Buffer
+
+ for i := 2; i < len(tokens)-1; i++ {
+ functionBodyBuffer.WriteString(tokens[i].value)
+ }
+
+ functionBody := functionBodyBuffer.String()
+
+ configCommand = &DefCommand{
+ commandName: commandName,
+ functionBody: functionBody,
+ }
+
+ return
+}
diff --git a/cmd/grv/config_parse_test.go b/cmd/grv/config_parse_test.go
index d4c75c8..22e2338 100644
--- a/cmd/grv/config_parse_test.go
+++ b/cmd/grv/config_parse_test.go
@@ -254,6 +254,25 @@ func (shellCommandValues *ShellCommandValues) Equal(command ConfigCommand) bool
return shellCommandValues.command == other.command.value
}
+type DefCommandValues struct {
+ commandName string
+ functionBody string
+}
+
+func (defCommandValues *DefCommandValues) Equal(command ConfigCommand) bool {
+ if command == nil {
+ return false
+ }
+
+ other, ok := command.(*DefCommand)
+ if !ok {
+ return false
+ }
+
+ return defCommandValues.commandName == other.commandName &&
+ defCommandValues.functionBody == other.functionBody
+}
+
func TestParseSingleCommand(t *testing.T) {
var singleCommandTests = []struct {
input string
@@ -359,6 +378,27 @@ func TestParseSingleCommand(t *testing.T) {
command: "!git add -A",
},
},
+ {
+ input: "def myFunc { addview RefView }",
+ expectedCommand: &DefCommandValues{
+ commandName: "myFunc",
+ functionBody: " addview RefView ",
+ },
+ },
+ {
+ input: "def myFunc {\n\taddview CommitView master\n\taddview RefView\n}",
+ expectedCommand: &DefCommandValues{
+ commandName: "myFunc",
+ functionBody: "\n\taddview CommitView master\n\taddview RefView\n",
+ },
+ },
+ {
+ input: "def nop { }",
+ expectedCommand: &DefCommandValues{
+ commandName: "nop",
+ functionBody: " ",
+ },
+ },
}
for _, singleCommandTest := range singleCommandTests {
@@ -495,6 +535,22 @@ func TestErrorsAreReceivedForInvalidConfigTokenSequences(t *testing.T) {
input: "addtab",
expectedErrorMessage: ConfigFile + ":1:6 Unexpected EOF",
},
+ {
+ input: "def --name",
+ expectedErrorMessage: ConfigFile + ":1:5 Expected function name but found --name",
+ },
+ {
+ input: "def {",
+ expectedErrorMessage: ConfigFile + ":1:5 Invalid function identifier {",
+ },
+ {
+ input: "def myfunc (",
+ expectedErrorMessage: ConfigFile + ":1:12 Expected { but found (",
+ },
+ {
+ input: "def myfunc { addview RefView ",
+ expectedErrorMessage: ConfigFile + ":1:29 Expected } but reached EOF",
+ },
}
for _, errorTest := range errorTests {
diff --git a/doc/documentation.md b/doc/documentation.md
index 97f85ec..b0d07f5 100644
--- a/doc/documentation.md
+++ b/doc/documentation.md
@@ -27,6 +27,7 @@ The sections below provide an overview of the ways to configure and interact wit
- [Configuration Commands](#configuration-commands)
* [addtab](#addtab)
* [addview](#addview)
+ * [def](#def)
* [git](#git)
* [giti](#giti)
* [help](#help)
@@ -272,6 +273,26 @@ addview GitStatusView
addview RefView
```
+### def
+
+The def command allows a custom GRV command to be defined. It has the form:
+
+```
+def NAME {
+ BODY
+}
+```
+
+where NAME is the name of the new command and BODY is a sequence of commands to execute.
+For example, to define a command "maintab" to open a new tab containing the CommitView for master:
+
+```
+def maintab {
+ addtab Main
+ addview CommitView master
+}
+```
+
### git
The git command is an alias to the git cli command.