Skip to content

Commit

Permalink
Generate commands json and render uipathcli documentation pages
Browse files Browse the repository at this point in the history
- Added a new command to display all available commands
- Implemented simple website to display the available commands
- Extended github actions CI workflow to deploy static assets
  on GitHub Pages
  • Loading branch information
thschmitt committed Oct 3, 2023
1 parent 049b26d commit 5878450
Show file tree
Hide file tree
Showing 10 changed files with 682 additions and 2 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,34 @@ jobs:
path: build/packages/
if-no-files-found: error

publish_pages:
needs: build
permissions:
pages: write
id-token: write
#if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download packages
uses: actions/download-artifact@v3
with:
name: packages
path: build/packages/
- name: Generate commands
run: |
tar -xzvf build/packages/uipathcli-linux-amd64.tar.gz
./uipath commands > documentation/commands.json
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: 'documentation'
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v2

release:
needs: build
if: github.ref == 'refs/heads/main'
Expand Down
46 changes: 45 additions & 1 deletion commandline/command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,23 @@ func (b CommandBuilder) createConfigSetCommand() *cli.Command {
}

func (b CommandBuilder) loadDefinitions(args []string, version string) ([]parser.Definition, error) {
if len(args) > 1 && args[1] == "commands" {
all, err := b.DefinitionProvider.Index(version)
if err != nil {
return nil, err
}
definitions := []parser.Definition{}
for _, d := range all {
definition, err := b.DefinitionProvider.Load(d.Name, version)
if err != nil {
return nil, err
}
if definition != nil {
definitions = append(definitions, *definition)
}
}
return definitions, nil
}
if len(args) <= 1 || strings.HasPrefix(args[1], "--") {
return b.DefinitionProvider.Index(version)
}
Expand All @@ -699,6 +716,32 @@ func (b CommandBuilder) loadAutocompleteDefinitions(args []string, version strin
return b.loadDefinitions(args, version)
}

func (b CommandBuilder) createDisplayCommands(definitions []parser.Definition, commands []*cli.Command) *cli.Command {
return &cli.Command{
Name: "commands",
Description: "Display available commands",
Flags: []cli.Flag{
b.HelpFlag(),
},
Hidden: true,
HideHelp: true,
Action: func(context *cli.Context) error {

flagBuilder := newFlagBuilder()
flagBuilder.AddFlags(b.CreateDefaultFlags(false))
flagBuilder.AddFlag(b.HelpFlag())

handler := newDisplayCommandsHandler()
output, err := handler.Display(definitions, flagBuilder.ToList())
if err != nil {
return err
}
fmt.Fprintln(b.StdOut, output)
return nil
},
}
}

func (b CommandBuilder) createServiceCommands(definitions []parser.Definition) []*cli.Command {
commands := []*cli.Command{}
for _, e := range definitions {
Expand Down Expand Up @@ -740,7 +783,8 @@ func (b CommandBuilder) Create(args []string) ([]*cli.Command, error) {
servicesCommands := b.createServiceCommands(definitions)
autocompleteCommand := b.createAutoCompleteCommand(version)
configCommand := b.createConfigCommand()
commands := append(servicesCommands, autocompleteCommand, configCommand)
displayCommands := b.createDisplayCommands(definitions, servicesCommands)
commands := append(servicesCommands, autocompleteCommand, configCommand, displayCommands)
return commands, nil
}

Expand Down
1 change: 0 additions & 1 deletion commandline/definition_file_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ func (s *DefinitionFileStore) Read(name string, version string) (*DefinitionData
return nil, err
}
definition := NewDefinitionData(name, version, data)
s.definitions = append(s.definitions, *definition)
return definition, err
}
}
Expand Down
186 changes: 186 additions & 0 deletions commandline/display_commands_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package commandline

import (
"encoding/json"
"sort"

"github.com/UiPath/uipathcli/parser"
"github.com/urfave/cli/v2"
)

// displayCommandsHandler shows all the available commands
type displayCommandsHandler struct {
}

type parameterJson struct {
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
Required bool `json:"required"`
AllowedValues []interface{} `json:"allowedValues"`
DefaultValue interface{} `json:"defaultValue"`
Example string `json:"example"`
}

type commandJson struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters []parameterJson `json:"parameters"`
Subcommands []commandJson `json:"subcommands"`
}

func (h displayCommandsHandler) Display(definitions []parser.Definition, globalFlags []cli.Flag) (string, error) {
result := commandJson{
Name: "uipath",
Description: "Command line interface to simplify, script and automate API calls for UiPath services",
Parameters: h.convertFlagsToCommandParameters(globalFlags),
Subcommands: h.convertDefinitionsToCommands(definitions),
}
bytes, err := json.MarshalIndent(result, "", " ")
if err != nil {
return "", err
}
return string(bytes), nil
}

func (h displayCommandsHandler) convertDefinitionsToCommands(definitions []parser.Definition) []commandJson {
commands := []commandJson{}
for _, d := range definitions {
command := h.convertDefinitionToCommands(d)
commands = append(commands, command)
}
return commands
}

func (h displayCommandsHandler) convertDefinitionToCommands(definition parser.Definition) commandJson {
categories := map[string]commandJson{}

for _, op := range definition.Operations {
if op.Category == nil {
command := h.convertOperationToCommand(op)
categories[command.Name] = command
} else {
h.createOrUpdateCategory(op, categories)
}
}

commands := []commandJson{}
for _, command := range categories {
commands = append(commands, command)
}

h.sort(commands)
for _, command := range commands {
h.sort(command.Subcommands)
}
return commandJson{
Name: definition.Name,
Subcommands: commands,
}
}

func (h displayCommandsHandler) createOrUpdateCategory(operation parser.Operation, categories map[string]commandJson) {
command, found := categories[operation.Category.Name]
if !found {
command = h.createCategoryCommand(operation)
}
command.Subcommands = append(command.Subcommands, h.convertOperationToCommand(operation))
categories[operation.Category.Name] = command
}

func (h displayCommandsHandler) createCategoryCommand(operation parser.Operation) commandJson {
return commandJson{
Name: operation.Category.Name,
Description: operation.Category.Description,
}
}

func (h displayCommandsHandler) convertOperationToCommand(operation parser.Operation) commandJson {
return commandJson{
Name: operation.Name,
Description: operation.Description,
Parameters: h.convertParametersToCommandParameters(operation.Parameters),
}
}

func (h displayCommandsHandler) convertFlagsToCommandParameters(flags []cli.Flag) []parameterJson {
result := []parameterJson{}
for _, f := range flags {
result = append(result, h.convertFlagToCommandParameter(f))
}
return result
}

func (h displayCommandsHandler) convertParametersToCommandParameters(parameters []parser.Parameter) []parameterJson {
result := []parameterJson{}
for _, p := range parameters {
result = append(result, h.convertParameterToCommandParameter(p))
}
return result
}

func (h displayCommandsHandler) convertFlagToCommandParameter(flag cli.Flag) parameterJson {
stringFlag, ok := flag.(*cli.StringFlag)
if ok {
return parameterJson{
Name: stringFlag.Name,
Description: stringFlag.Usage,
Type: "string",
Required: false,
AllowedValues: []interface{}{},
DefaultValue: stringFlag.Value,
}
}
intFlag, ok := flag.(*cli.IntFlag)
if ok {
return parameterJson{
Name: intFlag.Name,
Description: intFlag.Usage,
Type: "integer",
Required: false,
AllowedValues: []interface{}{},
DefaultValue: intFlag.Value,
}
}
boolFlag, ok := flag.(*cli.BoolFlag)
if ok {
return parameterJson{
Name: boolFlag.Name,
Description: boolFlag.Usage,
Type: "boolean",
Required: false,
AllowedValues: []interface{}{},
DefaultValue: boolFlag.Value,
}
}
return parameterJson{
Name: flag.Names()[0],
Description: "",
Type: "string",
Required: false,
AllowedValues: []interface{}{},
}
}

func (h displayCommandsHandler) convertParameterToCommandParameter(parameter parser.Parameter) parameterJson {
formatter := newParameterFormatter(parameter)
return parameterJson{
Name: parameter.Name,
Description: parameter.Description,
Type: parameter.Type,
Required: parameter.Required,
AllowedValues: parameter.AllowedValues,
DefaultValue: parameter.DefaultValue,
Example: formatter.UsageExample(),
}
}

func (h displayCommandsHandler) sort(commands []commandJson) {
sort.Slice(commands, func(i, j int) bool {
return commands[i].Name < commands[j].Name
})
}

func newDisplayCommandsHandler() *displayCommandsHandler {
return &displayCommandsHandler{}
}
4 changes: 4 additions & 0 deletions commandline/parameter_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ func (f parameterFormatter) Description() string {
return f.description(f.parameter)
}

func (f parameterFormatter) UsageExample() string {
return f.usageExample(f.parameter)
}

func (f parameterFormatter) description(parameter parser.Parameter) string {
builder := strings.Builder{}

Expand Down
Loading

0 comments on commit 5878450

Please sign in to comment.