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 show 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 5, 2023
1 parent 66f9370 commit 313e6a1
Show file tree
Hide file tree
Showing 13 changed files with 813 additions and 6 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 show > 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
57 changes: 56 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,43 @@ func (b CommandBuilder) loadAutocompleteDefinitions(args []string, version strin
return b.loadDefinitions(args, version)
}

func (b CommandBuilder) createShowCommand(definitions []parser.Definition, commands []*cli.Command) *cli.Command {
return &cli.Command{
Name: "commands",
Description: "Command to inspect available uipath CLI operations",
Flags: []cli.Flag{
b.HelpFlag(),
},
Subcommands: []*cli.Command{
{
Name: "show",
Description: "Print available uipath CLI commands",
Flags: []cli.Flag{
b.HelpFlag(),
},
Action: func(context *cli.Context) error {
flagBuilder := newFlagBuilder()
flagBuilder.AddFlags(b.CreateDefaultFlags(false))
flagBuilder.AddFlag(b.HelpFlag())
flags := flagBuilder.ToList()

handler := newShowCommandHandler()
output, err := handler.Execute(definitions, flags)
if err != nil {
return err
}
fmt.Fprintln(b.StdOut, output)
return nil
},
HideHelp: true,
Hidden: true,
},
},
HideHelp: true,
Hidden: true,
}
}

func (b CommandBuilder) createServiceCommands(definitions []parser.Definition) []*cli.Command {
commands := []*cli.Command{}
for _, e := range definitions {
Expand Down Expand Up @@ -740,7 +794,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)
showCommand := b.createShowCommand(definitions, servicesCommands)
commands := append(servicesCommands, autocompleteCommand, configCommand, showCommand)
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 @@ -60,7 +60,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
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
178 changes: 178 additions & 0 deletions commandline/show_command_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package commandline

import (
"encoding/json"
"sort"

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

// showCommandHandler prints all available uipathcli commands
type showCommandHandler 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 showCommandHandler) Execute(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 showCommandHandler) convertDefinitionsToCommands(definitions []parser.Definition) []commandJson {
commands := []commandJson{}
for _, d := range definitions {
command := h.convertDefinitionToCommands(d)
commands = append(commands, command)
}
return commands
}

func (h showCommandHandler) 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,
Description: definition.Description,
Subcommands: commands,
}
}

func (h showCommandHandler) 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 showCommandHandler) createCategoryCommand(operation parser.Operation) commandJson {
return commandJson{
Name: operation.Category.Name,
Description: operation.Category.Description,
}
}

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

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

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

func (h showCommandHandler) convertFlagToCommandParameter(flag cli.Flag) parameterJson {
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,
}
}
stringFlag := flag.(*cli.StringFlag)
return parameterJson{
Name: stringFlag.Name,
Description: stringFlag.Usage,
Type: "string",
Required: false,
AllowedValues: []interface{}{},
DefaultValue: stringFlag.Value,
}
}

func (h showCommandHandler) 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 showCommandHandler) sort(commands []commandJson) {
sort.Slice(commands, func(i, j int) bool {
return commands[i].Name < commands[j].Name
})
}

func newShowCommandHandler() *showCommandHandler {
return &showCommandHandler{}
}
2 changes: 1 addition & 1 deletion definitions/identity.token.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
openapi: 3.0.1
info:
title: IdentityServer Internal API
title: IdentityServer External API
version: v1
servers:
- url: https://cloud.uipath.com/identity_
Expand Down
Loading

0 comments on commit 313e6a1

Please sign in to comment.