Support breakpoint debugger integrated to IDEs
This commit adds the IDE-integrated breakpoint debugger based on walker. Now buildx provides DAP (Debug Adapter Protocol) API to IDEs so DAP-aware IDEs can call buildx and allow users to perform breakpoint-based debugging on the IDE's UI/UX. Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>pull/1656/head
							parent
							
								
									bcf21dee44
								
							
						
					
					
						commit
						37763ec29f
					
				@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/docker/buildx/monitor/dap"
 | 
				
			||||||
 | 
						"github.com/docker/cli/cli/command"
 | 
				
			||||||
 | 
						"github.com/pkg/errors"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func addDAPCommands(cmd *cobra.Command, dockerCli command.Cli) {
 | 
				
			||||||
 | 
						cmd.AddCommand(
 | 
				
			||||||
 | 
							dapCmd(dockerCli),
 | 
				
			||||||
 | 
							attachContainerCmd(dockerCli),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dapCmd(dockerCli command.Cli) *cobra.Command {
 | 
				
			||||||
 | 
						cmd := &cobra.Command{
 | 
				
			||||||
 | 
							Use:    "dap",
 | 
				
			||||||
 | 
							Hidden: true,
 | 
				
			||||||
 | 
							RunE: func(cmd *cobra.Command, args []string) error {
 | 
				
			||||||
 | 
								logrus.SetOutput(os.Stderr)
 | 
				
			||||||
 | 
								s, err := dap.NewServer(dockerCli, os.Stdin, os.Stdout)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := s.Serve(); err != nil {
 | 
				
			||||||
 | 
									logrus.WithError(err).Warnf("failed to serve")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								logrus.Info("finishing server")
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return cmd
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func attachContainerCmd(dockerCli command.Cli) *cobra.Command {
 | 
				
			||||||
 | 
						var setTtyRaw bool
 | 
				
			||||||
 | 
						cmd := &cobra.Command{
 | 
				
			||||||
 | 
							Use:    fmt.Sprintf("%s [OPTIONS] rootdir", dap.AttachContainerCommand),
 | 
				
			||||||
 | 
							Hidden: true,
 | 
				
			||||||
 | 
							RunE: func(cmd *cobra.Command, args []string) error {
 | 
				
			||||||
 | 
								if len(args) < 1 || args[0] == "" {
 | 
				
			||||||
 | 
									return errors.Errorf("specify root dir: %+v", args)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return dap.AttachContainerIO(args[0], os.Stdin, os.Stdout, os.Stderr, setTtyRaw)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						flags := cmd.Flags()
 | 
				
			||||||
 | 
						flags.BoolVar(&setTtyRaw, "set-tty-raw", false, "set tty raw")
 | 
				
			||||||
 | 
						return cmd
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,292 @@
 | 
				
			|||||||
 | 
					# Breakpoint debugger (experimental)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Debugger on the monitor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can use `--invoke=debug-step` to use breakpoint debugger.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This doesn't perform the build but immediately launches the debug monitor.
 | 
				
			||||||
 | 
					You can continue the build on the monitor in a step-by-step manner.
 | 
				
			||||||
 | 
					`show` command shows the source file with breakpoint information.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ BUILDX_EXPERIMENTAL=1 buildx build --invoke=debug-step /tmp/ctx3
 | 
				
			||||||
 | 
					INFO: connecting to buildx server
 | 
				
			||||||
 | 
					INFO: no buildx server found; launching...
 | 
				
			||||||
 | 
					[+] Building 3.2s (5/5) FINISHED                                                
 | 
				
			||||||
 | 
					 => [internal] booting buildkit                                            0.6s
 | 
				
			||||||
 | 
					 => => starting container buildx_buildkit_testbuilder0                     0.6s
 | 
				
			||||||
 | 
					 => [internal] load build definition from Dockerfile                       0.0s
 | 
				
			||||||
 | 
					 => => transferring dockerfile: 87B                                        0.0s
 | 
				
			||||||
 | 
					 => [internal] load .dockerignore                                          0.0s
 | 
				
			||||||
 | 
					 => => transferring context: 2B                                            0.0s
 | 
				
			||||||
 | 
					 => [internal] load metadata for docker.io/library/busybox:latest          2.3s
 | 
				
			||||||
 | 
					 => [auth] library/busybox:pull token for registry-1.docker.io             0.0s
 | 
				
			||||||
 | 
					Launching interactive container. Press Ctrl-a-c to switch to monitor console
 | 
				
			||||||
 | 
					/ # Switched IO
 | 
				
			||||||
 | 
					(buildx) show
 | 
				
			||||||
 | 
					Filename: "Dockerfile"
 | 
				
			||||||
 | 
					      1| FROM busybox
 | 
				
			||||||
 | 
					      2| RUN echo hi > /hi
 | 
				
			||||||
 | 
					      3| RUN echo aaaa > /a
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can start the build using `continue` command.
 | 
				
			||||||
 | 
					`=>` shows the position where the build is pausing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					(buildx) continue
 | 
				
			||||||
 | 
					...
 | 
				
			||||||
 | 
					Break at [stopOnEntry]
 | 
				
			||||||
 | 
					Filename: "Dockerfile"
 | 
				
			||||||
 | 
					 =>   1| FROM busybox
 | 
				
			||||||
 | 
					      2| RUN echo hi > /hi
 | 
				
			||||||
 | 
					      3| RUN echo aaaa > /a
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can set a breakpoint using `break` command.
 | 
				
			||||||
 | 
					The position of the breakpoint is shown by `*` on the `show` command output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					(buildx) break 3
 | 
				
			||||||
 | 
					(buildx) show
 | 
				
			||||||
 | 
					Filename: "Dockerfile"
 | 
				
			||||||
 | 
					 =>   1| FROM busybox
 | 
				
			||||||
 | 
					      2| RUN echo hi > /hi
 | 
				
			||||||
 | 
					*     3| RUN echo aaaa > /a
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`continue` resumes the build and stops when it encounters a breakpoint.
 | 
				
			||||||
 | 
					You can switch to the shell using `C-a-c` and inspect the filesystem.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					(buildx) continue
 | 
				
			||||||
 | 
					[+] Building 0.0s (2/2) FINISHED                                                
 | 
				
			||||||
 | 
					 => CACHED [1/3] FROM docker.io/library/busybox@sha256:7b3ccabffc97de872a  0.0s
 | 
				
			||||||
 | 
					 => => resolve docker.io/library/busybox@sha256:7b3ccabffc97de872a30dfd23  0.0s
 | 
				
			||||||
 | 
					 => CACHED [2/3] RUN echo hi > /hi                                         0.0s
 | 
				
			||||||
 | 
					[+] Building 0.0s (3/3) FINISHED                                                
 | 
				
			||||||
 | 
					 => CACHED [1/3] FROM docker.io/library/busybox@sha256:7b3ccabffc97de872a  0.0s
 | 
				
			||||||
 | 
					 => => resolve docker.io/library/busybox@sha256:7b3ccabffc97de872a30dfd23  0.0s
 | 
				
			||||||
 | 
					 => CACHED [3/3] RUN echo aaaa > /a                                        0.0s
 | 
				
			||||||
 | 
					 => CACHED [2/3] RUN echo hi > /hi                                         0.0s
 | 
				
			||||||
 | 
					Break at [1]
 | 
				
			||||||
 | 
					Filename: "Dockerfile"
 | 
				
			||||||
 | 
					      1| FROM busybox
 | 
				
			||||||
 | 
					      2| RUN echo hi > /hi
 | 
				
			||||||
 | 
					*=>   3| RUN echo aaaa > /a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(buildx) Switched IO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/ # cat /a
 | 
				
			||||||
 | 
					aaaa
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Implemented commands
 | 
				
			||||||
 | 
					  - `show`: shows the Dockerfile
 | 
				
			||||||
 | 
					  - `break`: set a breakpoint at the specified line
 | 
				
			||||||
 | 
					  - `breakpoints`: list key-value pairs of available breakpoints
 | 
				
			||||||
 | 
					  - `clear`: clear the breakpoint specified by the key
 | 
				
			||||||
 | 
					  - `clearall`: clear all breakpoints
 | 
				
			||||||
 | 
					  - `next`: proceed to the next line
 | 
				
			||||||
 | 
					  - `continue`: resume the build until the next breakpoint
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					## Debugger on IDEs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The breakpoint debugger can be used on IDEs.
 | 
				
			||||||
 | 
					The monitor provides DAP API for IDEs so that the breakpoint debugger can be controlled via the editor's UI.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### VS Code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can use `vscode-buildx-debugger` extension.
 | 
				
			||||||
 | 
					Buildx needs to be available via PATH environtmen variable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Install the VSIX file to VS Code as the following:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					code --install-extension vscode-buildx-debugger-${VERSION}.vsix
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can lanuch the debugger by putting `launch.json` in `.vscode` in the project directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    "version": "0.2.0",
 | 
				
			||||||
 | 
					    "configurations": [
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						    "type": "dockerfile",
 | 
				
			||||||
 | 
						    "request": "launch",
 | 
				
			||||||
 | 
						    "name": "Debug Dockerfile",
 | 
				
			||||||
 | 
						    "program": "${workspaceFolder}/Dockerfile",
 | 
				
			||||||
 | 
						    "stopOnEntry": true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Emacs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Requirements
 | 
				
			||||||
 | 
					  - [dap-mode](https://github.com/emacs-lsp/dap-mode)
 | 
				
			||||||
 | 
					    - configuration guide: https://emacs-lsp.github.io/dap-mode/page/configuration/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The following config enables emacs to use buildx as a breakpoint debugger for Dockerfile.
 | 
				
			||||||
 | 
					Buildx needs to be available via PATH environtmen variable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					(require 'dap-mode)
 | 
				
			||||||
 | 
					(require 'dap-utils)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(dap-register-debug-provider "dockerfile" 'dap-dockerfile--populate-default-args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defun dap-dockerfile--populate-default-args (conf)
 | 
				
			||||||
 | 
					  "Populate CONF with the default arguments."
 | 
				
			||||||
 | 
					  (-> conf
 | 
				
			||||||
 | 
					    (dap--put-if-absent :program buffer-file-name)
 | 
				
			||||||
 | 
					    (dap--put-if-absent :dap-server-path (list "buildx" "dap"))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(dap-register-debug-template "Dockerfile Debug Configuration"
 | 
				
			||||||
 | 
					                             (list :type "dockerfile"
 | 
				
			||||||
 | 
					                                   :request "launch"
 | 
				
			||||||
 | 
					                                   :stopOnEntry t
 | 
				
			||||||
 | 
					                                   :name "Debug Dockerfile"
 | 
				
			||||||
 | 
					                                   :environment-variables '(("BUILDX_EXPERIMENTAL" . "1"))
 | 
				
			||||||
 | 
					                                   ))
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To launch the debugger, `M-x dap-debug` then select `Dockerfile Debug Configuration` template.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dap-mode also supports `launch.json` of VS Code. 
 | 
				
			||||||
 | 
					dap-mode looks up `launch.json` under `(lsp-workspace-root)` and loads it.
 | 
				
			||||||
 | 
					Refer to the [dap-mode document](https://github.com/emacs-lsp/dap-mode/blob/5e449c864107e08353fd21c44897c4d480190d94/docs/page/features.md#launchjson-support) for details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Neovim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Requirements
 | 
				
			||||||
 | 
					  - Neovim (>= 0.6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The following [packer.nvim](https://github.com/wbthomason/packer.nvim) config enables neovim to use buildx as a breakpoint debugger for Dockerfile.
 | 
				
			||||||
 | 
					Add it to your packer.nvim config location (e.g. `~/.config/nvim/init.lua`, `~/.config/nvim/lua/plugins.lua`, etc.).
 | 
				
			||||||
 | 
					Buildx needs to be available via PATH environtmen variable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					require'packer'.startup(function()
 | 
				
			||||||
 | 
					        use "mfussenegger/nvim-dap"
 | 
				
			||||||
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local dap = require("dap")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dap.adapters.dockerfile = {
 | 
				
			||||||
 | 
					  type = 'executable';
 | 
				
			||||||
 | 
					  command = '/path/to/buildx';
 | 
				
			||||||
 | 
					  args = { 'dap' };
 | 
				
			||||||
 | 
					  options = {
 | 
				
			||||||
 | 
					    env = {
 | 
				
			||||||
 | 
					      'BUILDX_EXPERIMENTAL=1'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dap.configurations.dockerfile = {
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        type = "dockerfile",
 | 
				
			||||||
 | 
					        name = "Dockerfile Configuration",
 | 
				
			||||||
 | 
					        request = "launch",
 | 
				
			||||||
 | 
					        stopOnEntry = true,
 | 
				
			||||||
 | 
					        program = "${file}",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nvim-dap also supports a subset of `launch.json` of VS Code. 
 | 
				
			||||||
 | 
					`:lua require('dap.ext.vscode').load_launchjs()` searches `launch.json` under the current working directory and loads it.
 | 
				
			||||||
 | 
					Refer to the [nvim-dap document](https://github.com/mfussenegger/nvim-dap/blob/f4a3be57f61893cffa1e22aa5e1e7bded495fcf2/doc/dap.txt#L231-L283) for details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See also [`:help dap.txt`](https://github.com/mfussenegger/nvim-dap/blob/master/doc/dap.txt) of nvim-dap for available commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Launch Configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In the launch configuration (e.g. `launch.json` on VS Code), the following properties are provided.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `program` *string* **REQUIRED** : Absolute path to Dockerfile.
 | 
				
			||||||
 | 
					- `stopOnEntry` *boolean* : Automatically stop after launch. (default: `true`)
 | 
				
			||||||
 | 
					- `target` *string* : Target build stage to build.
 | 
				
			||||||
 | 
					- `build-args` *array* : Build-time variables.
 | 
				
			||||||
 | 
					- `ssh` *array* : Allow forwarding SSH agent to the build. Format: `default|<id>[=<socket>|<key>[,<key>]]`
 | 
				
			||||||
 | 
					- `secrets` *array* : Expose secret value to the build. Format: `id=secretname,src=filepath`
 | 
				
			||||||
 | 
					- `root` *string* : Root directory for controller server.
 | 
				
			||||||
 | 
					- `controller-mode` *string* : Mode to launch the controller (`local` vs `remote`(default))
 | 
				
			||||||
 | 
					- `server-config` *string* : Path to the controller server configuration file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Common and mandatory properties are the following (see [VS Code documentation](https://code.visualstudio.com/docs/editor/debugging#_launchjson-attributes) for details).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `type` : Type of debugger to use. Must be `dockerfile`.
 | 
				
			||||||
 | 
					- `request` : The request type. `launch` is only supported as of now.
 | 
				
			||||||
 | 
					- `name` : The reader-friendly name of this configuration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Repl commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### exec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					NAME:
 | 
				
			||||||
 | 
					   buildx exec - Execute command in the step
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USAGE:
 | 
				
			||||||
 | 
					   exec [OPTIONS] [ARGS...]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If ARGS isn't provided, "/bin/sh" is used by default.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OPTIONS:
 | 
				
			||||||
 | 
					   --init-state               Execute commands in an initial state of that step
 | 
				
			||||||
 | 
					   --tty, -t                  Allocate tty (enabled by default)
 | 
				
			||||||
 | 
					   -i                         Enable stdin (FIXME: must be set with tty) (enabled by default)
 | 
				
			||||||
 | 
					   --env value, -e value      Set environment variables
 | 
				
			||||||
 | 
					   --workdir value, -w value  Working directory inside the container
 | 
				
			||||||
 | 
					   --rollback                 Kill running processes and recreate the debugging container
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### ps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					NAME:
 | 
				
			||||||
 | 
					   buildx ps - List attachable processes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USAGE:
 | 
				
			||||||
 | 
					   ps
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### attach
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					NAME:
 | 
				
			||||||
 | 
					   buildx attach - Attach to a processes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USAGE:
 | 
				
			||||||
 | 
					   attach PID
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### help
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					NAME:
 | 
				
			||||||
 | 
					    - Shows a list of commands or help for one command
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USAGE:
 | 
				
			||||||
 | 
					    [command]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Known Limitations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Following lists the current known limitations that should be eliminated in the futural version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `Step Into` and `Step Out` in Debug toolbar is unsupported. To inspect instructions deeper, you can use [`exec`](#exec) REPL command instead.
 | 
				
			||||||
 | 
					- Logpoint, conditional, function and data breakpoints aren't supported.
 | 
				
			||||||
 | 
					- Setting value to a variable isn't supported.
 | 
				
			||||||
 | 
					- `attach` request type in launch configuration isn't supported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 53 KiB  | 
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 67 KiB  | 
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 89 KiB  | 
@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					# docker buildx dap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!---MARKER_GEN_START-->
 | 
				
			||||||
 | 
					### Options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| Name        | Type     | Default | Description                              |
 | 
				
			||||||
 | 
					|:------------|:---------|:--------|:-----------------------------------------|
 | 
				
			||||||
 | 
					| `--builder` | `string` |         | Override the configured builder instance |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!---MARKER_GEN_END-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					//go:build linux
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package dap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Ported from https://github.com/ktock/buildg/blob/v0.4.1/pkg/dap/dap.go
 | 
				
			||||||
 | 
					// Copyright The buildg Authors.
 | 
				
			||||||
 | 
					// Licensed under the Apache License, Version 2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/containerd/console"
 | 
				
			||||||
 | 
						"github.com/containerd/fifo"
 | 
				
			||||||
 | 
						"github.com/pkg/errors"
 | 
				
			||||||
 | 
						"golang.org/x/sync/errgroup"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AttachContainerIO(root string, stdin io.Reader, stdout, stderr io.Writer, setTtyRaw bool) error {
 | 
				
			||||||
 | 
						if root == "" {
 | 
				
			||||||
 | 
							return errors.Errorf("root needs to be specified")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type ioSet struct {
 | 
				
			||||||
 | 
							stdin  io.WriteCloser
 | 
				
			||||||
 | 
							stdout io.ReadCloser
 | 
				
			||||||
 | 
							stderr io.ReadCloser
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ioSetCh := make(chan ioSet)
 | 
				
			||||||
 | 
						errCh := make(chan error)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stdin, stdout, stderr, err := openFifosClient(context.TODO(), root)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								errCh <- err
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ioSetCh <- ioSet{stdin, stdout, stderr}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							pStdin  io.WriteCloser
 | 
				
			||||||
 | 
							pStdout io.ReadCloser
 | 
				
			||||||
 | 
							pStderr io.ReadCloser
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case ioSet := <-ioSetCh:
 | 
				
			||||||
 | 
							pStdin, pStdout, pStderr = ioSet.stdin, ioSet.stdout, ioSet.stderr
 | 
				
			||||||
 | 
						case err := <-errCh:
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						case <-time.After(3 * time.Second):
 | 
				
			||||||
 | 
							return errors.Errorf("i/o timeout; check server is up and running")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() { pStdin.Close(); pStdout.Close(); pStderr.Close() }()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if setTtyRaw {
 | 
				
			||||||
 | 
							con := console.Current()
 | 
				
			||||||
 | 
							if err := con.SetRaw(); err != nil {
 | 
				
			||||||
 | 
								return errors.Errorf("failed to configure terminal: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer con.Reset()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go io.Copy(pStdin, stdin)
 | 
				
			||||||
 | 
						eg, _ := errgroup.WithContext(context.TODO())
 | 
				
			||||||
 | 
						eg.Go(func() error { _, err := io.Copy(stdout, pStdout); return err })
 | 
				
			||||||
 | 
						eg.Go(func() error { _, err := io.Copy(stderr, pStderr); return err })
 | 
				
			||||||
 | 
						if err := eg.Wait(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fmt.Fprintf(stderr, "exec finished\n")
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func serveContainerIO(ctx context.Context, root string) (io.ReadCloser, io.WriteCloser, io.WriteCloser, func(), error) {
 | 
				
			||||||
 | 
						stdin, stdout, stderr, err := openFifosServer(ctx, root)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return stdin, stdout, stderr, func() {
 | 
				
			||||||
 | 
							stdin.Close()
 | 
				
			||||||
 | 
							stdout.Close()
 | 
				
			||||||
 | 
							stderr.Close()
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func openFifosClient(ctx context.Context, fifosDir string) (stdin io.WriteCloser, stdout, stderr io.ReadCloser, retErr error) {
 | 
				
			||||||
 | 
						if stdin, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stdin"), syscall.O_WRONLY, 0700); retErr != nil {
 | 
				
			||||||
 | 
							return nil, nil, nil, errors.Errorf("failed to open stdin fifo: %v", retErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if retErr != nil && stdin != nil {
 | 
				
			||||||
 | 
								stdin.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						if stdout, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stdout"), syscall.O_RDONLY, 0700); retErr != nil {
 | 
				
			||||||
 | 
							return nil, nil, nil, errors.Errorf("failed to open stdout fifo: %v", retErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if retErr != nil && stdout != nil {
 | 
				
			||||||
 | 
								stdout.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						if stderr, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stderr"), syscall.O_RDONLY, 0700); retErr != nil {
 | 
				
			||||||
 | 
							return nil, nil, nil, errors.Errorf("failed to open stderr fifo: %v", retErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if retErr != nil && stderr != nil {
 | 
				
			||||||
 | 
								stderr.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						return stdin, stdout, stderr, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func openFifosServer(ctx context.Context, fifosDir string) (stdin io.ReadCloser, stdout, stderr io.WriteCloser, retErr error) {
 | 
				
			||||||
 | 
						if stdin, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stdin"), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {
 | 
				
			||||||
 | 
							return nil, nil, nil, errors.Errorf("failed to open stdin fifo: %v", retErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if retErr != nil && stdin != nil {
 | 
				
			||||||
 | 
								stdin.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						if stdout, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stdout"), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {
 | 
				
			||||||
 | 
							return nil, nil, nil, errors.Errorf("failed to open stdout fifo: %v", retErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if retErr != nil && stdout != nil {
 | 
				
			||||||
 | 
								stdout.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						if stderr, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stderr"), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {
 | 
				
			||||||
 | 
							return nil, nil, nil, errors.Errorf("failed to open stderr fifo: %v", retErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if retErr != nil && stderr != nil {
 | 
				
			||||||
 | 
								stderr.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						return stdin, stdout, stderr, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					//go:build !linux
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package dap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/pkg/errors"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AttachContainerIO(root string, stdin io.Reader, stdout, stderr io.Writer, setTtyRaw bool) error {
 | 
				
			||||||
 | 
						return errors.Errorf("unsupported")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func serveContainerIO(ctx context.Context, root string) (io.ReadCloser, io.WriteCloser, io.WriteCloser, func(), error) {
 | 
				
			||||||
 | 
						return nil, nil, nil, nil, errors.Errorf("unsupported")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					*.go text eol=lf
 | 
				
			||||||
@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					coverage.txt
 | 
				
			||||||
 | 
					vendor/
 | 
				
			||||||
@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					linters:
 | 
				
			||||||
 | 
					  enable:
 | 
				
			||||||
 | 
					    - exportloopref # Checks for pointers to enclosing loop variables
 | 
				
			||||||
 | 
					    - gofmt
 | 
				
			||||||
 | 
					    - goimports
 | 
				
			||||||
 | 
					    - gosec
 | 
				
			||||||
 | 
					    - ineffassign
 | 
				
			||||||
 | 
					    - misspell
 | 
				
			||||||
 | 
					    - nolintlint
 | 
				
			||||||
 | 
					    - revive
 | 
				
			||||||
 | 
					    - staticcheck
 | 
				
			||||||
 | 
					    - tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17
 | 
				
			||||||
 | 
					    - unconvert
 | 
				
			||||||
 | 
					    - unused
 | 
				
			||||||
 | 
					    - vet
 | 
				
			||||||
 | 
					    - dupword # Checks for duplicate words in the source code
 | 
				
			||||||
 | 
					  disable:
 | 
				
			||||||
 | 
					    - errcheck
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					linters-settings:
 | 
				
			||||||
 | 
					  gosec:
 | 
				
			||||||
 | 
					    # The following issues surfaced when `gosec` linter
 | 
				
			||||||
 | 
					    # was enabled. They are temporarily excluded to unblock
 | 
				
			||||||
 | 
					    # the existing workflow, but still to be addressed by
 | 
				
			||||||
 | 
					    # future works.
 | 
				
			||||||
 | 
					    excludes:
 | 
				
			||||||
 | 
					      - G204
 | 
				
			||||||
 | 
					      - G305
 | 
				
			||||||
 | 
					      - G306
 | 
				
			||||||
 | 
					      - G402
 | 
				
			||||||
 | 
					      - G404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					run:
 | 
				
			||||||
 | 
					  timeout: 3m
 | 
				
			||||||
 | 
					  skip-dirs:
 | 
				
			||||||
 | 
					    - vendor
 | 
				
			||||||
@ -0,0 +1,201 @@
 | 
				
			|||||||
 | 
					                                 Apache License
 | 
				
			||||||
 | 
					                           Version 2.0, January 2004
 | 
				
			||||||
 | 
					                        http://www.apache.org/licenses/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   1. Definitions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "License" shall mean the terms and conditions for use, reproduction,
 | 
				
			||||||
 | 
					      and distribution as defined by Sections 1 through 9 of this document.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Licensor" shall mean the copyright owner or entity authorized by
 | 
				
			||||||
 | 
					      the copyright owner that is granting the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Legal Entity" shall mean the union of the acting entity and all
 | 
				
			||||||
 | 
					      other entities that control, are controlled by, or are under common
 | 
				
			||||||
 | 
					      control with that entity. For the purposes of this definition,
 | 
				
			||||||
 | 
					      "control" means (i) the power, direct or indirect, to cause the
 | 
				
			||||||
 | 
					      direction or management of such entity, whether by contract or
 | 
				
			||||||
 | 
					      otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
				
			||||||
 | 
					      outstanding shares, or (iii) beneficial ownership of such entity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "You" (or "Your") shall mean an individual or Legal Entity
 | 
				
			||||||
 | 
					      exercising permissions granted by this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Source" form shall mean the preferred form for making modifications,
 | 
				
			||||||
 | 
					      including but not limited to software source code, documentation
 | 
				
			||||||
 | 
					      source, and configuration files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Object" form shall mean any form resulting from mechanical
 | 
				
			||||||
 | 
					      transformation or translation of a Source form, including but
 | 
				
			||||||
 | 
					      not limited to compiled object code, generated documentation,
 | 
				
			||||||
 | 
					      and conversions to other media types.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Work" shall mean the work of authorship, whether in Source or
 | 
				
			||||||
 | 
					      Object form, made available under the License, as indicated by a
 | 
				
			||||||
 | 
					      copyright notice that is included in or attached to the work
 | 
				
			||||||
 | 
					      (an example is provided in the Appendix below).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Derivative Works" shall mean any work, whether in Source or Object
 | 
				
			||||||
 | 
					      form, that is based on (or derived from) the Work and for which the
 | 
				
			||||||
 | 
					      editorial revisions, annotations, elaborations, or other modifications
 | 
				
			||||||
 | 
					      represent, as a whole, an original work of authorship. For the purposes
 | 
				
			||||||
 | 
					      of this License, Derivative Works shall not include works that remain
 | 
				
			||||||
 | 
					      separable from, or merely link (or bind by name) to the interfaces of,
 | 
				
			||||||
 | 
					      the Work and Derivative Works thereof.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Contribution" shall mean any work of authorship, including
 | 
				
			||||||
 | 
					      the original version of the Work and any modifications or additions
 | 
				
			||||||
 | 
					      to that Work or Derivative Works thereof, that is intentionally
 | 
				
			||||||
 | 
					      submitted to Licensor for inclusion in the Work by the copyright owner
 | 
				
			||||||
 | 
					      or by an individual or Legal Entity authorized to submit on behalf of
 | 
				
			||||||
 | 
					      the copyright owner. For the purposes of this definition, "submitted"
 | 
				
			||||||
 | 
					      means any form of electronic, verbal, or written communication sent
 | 
				
			||||||
 | 
					      to the Licensor or its representatives, including but not limited to
 | 
				
			||||||
 | 
					      communication on electronic mailing lists, source code control systems,
 | 
				
			||||||
 | 
					      and issue tracking systems that are managed by, or on behalf of, the
 | 
				
			||||||
 | 
					      Licensor for the purpose of discussing and improving the Work, but
 | 
				
			||||||
 | 
					      excluding communication that is conspicuously marked or otherwise
 | 
				
			||||||
 | 
					      designated in writing by the copyright owner as "Not a Contribution."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
				
			||||||
 | 
					      on behalf of whom a Contribution has been received by Licensor and
 | 
				
			||||||
 | 
					      subsequently incorporated within the Work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   2. Grant of Copyright License. Subject to the terms and conditions of
 | 
				
			||||||
 | 
					      this License, each Contributor hereby grants to You a perpetual,
 | 
				
			||||||
 | 
					      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
				
			||||||
 | 
					      copyright license to reproduce, prepare Derivative Works of,
 | 
				
			||||||
 | 
					      publicly display, publicly perform, sublicense, and distribute the
 | 
				
			||||||
 | 
					      Work and such Derivative Works in Source or Object form.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   3. Grant of Patent License. Subject to the terms and conditions of
 | 
				
			||||||
 | 
					      this License, each Contributor hereby grants to You a perpetual,
 | 
				
			||||||
 | 
					      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
				
			||||||
 | 
					      (except as stated in this section) patent license to make, have made,
 | 
				
			||||||
 | 
					      use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
				
			||||||
 | 
					      where such license applies only to those patent claims licensable
 | 
				
			||||||
 | 
					      by such Contributor that are necessarily infringed by their
 | 
				
			||||||
 | 
					      Contribution(s) alone or by combination of their Contribution(s)
 | 
				
			||||||
 | 
					      with the Work to which such Contribution(s) was submitted. If You
 | 
				
			||||||
 | 
					      institute patent litigation against any entity (including a
 | 
				
			||||||
 | 
					      cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
				
			||||||
 | 
					      or a Contribution incorporated within the Work constitutes direct
 | 
				
			||||||
 | 
					      or contributory patent infringement, then any patent licenses
 | 
				
			||||||
 | 
					      granted to You under this License for that Work shall terminate
 | 
				
			||||||
 | 
					      as of the date such litigation is filed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   4. Redistribution. You may reproduce and distribute copies of the
 | 
				
			||||||
 | 
					      Work or Derivative Works thereof in any medium, with or without
 | 
				
			||||||
 | 
					      modifications, and in Source or Object form, provided that You
 | 
				
			||||||
 | 
					      meet the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (a) You must give any other recipients of the Work or
 | 
				
			||||||
 | 
					          Derivative Works a copy of this License; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (b) You must cause any modified files to carry prominent notices
 | 
				
			||||||
 | 
					          stating that You changed the files; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (c) You must retain, in the Source form of any Derivative Works
 | 
				
			||||||
 | 
					          that You distribute, all copyright, patent, trademark, and
 | 
				
			||||||
 | 
					          attribution notices from the Source form of the Work,
 | 
				
			||||||
 | 
					          excluding those notices that do not pertain to any part of
 | 
				
			||||||
 | 
					          the Derivative Works; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (d) If the Work includes a "NOTICE" text file as part of its
 | 
				
			||||||
 | 
					          distribution, then any Derivative Works that You distribute must
 | 
				
			||||||
 | 
					          include a readable copy of the attribution notices contained
 | 
				
			||||||
 | 
					          within such NOTICE file, excluding those notices that do not
 | 
				
			||||||
 | 
					          pertain to any part of the Derivative Works, in at least one
 | 
				
			||||||
 | 
					          of the following places: within a NOTICE text file distributed
 | 
				
			||||||
 | 
					          as part of the Derivative Works; within the Source form or
 | 
				
			||||||
 | 
					          documentation, if provided along with the Derivative Works; or,
 | 
				
			||||||
 | 
					          within a display generated by the Derivative Works, if and
 | 
				
			||||||
 | 
					          wherever such third-party notices normally appear. The contents
 | 
				
			||||||
 | 
					          of the NOTICE file are for informational purposes only and
 | 
				
			||||||
 | 
					          do not modify the License. You may add Your own attribution
 | 
				
			||||||
 | 
					          notices within Derivative Works that You distribute, alongside
 | 
				
			||||||
 | 
					          or as an addendum to the NOTICE text from the Work, provided
 | 
				
			||||||
 | 
					          that such additional attribution notices cannot be construed
 | 
				
			||||||
 | 
					          as modifying the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      You may add Your own copyright statement to Your modifications and
 | 
				
			||||||
 | 
					      may provide additional or different license terms and conditions
 | 
				
			||||||
 | 
					      for use, reproduction, or distribution of Your modifications, or
 | 
				
			||||||
 | 
					      for any such Derivative Works as a whole, provided Your use,
 | 
				
			||||||
 | 
					      reproduction, and distribution of the Work otherwise complies with
 | 
				
			||||||
 | 
					      the conditions stated in this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
				
			||||||
 | 
					      any Contribution intentionally submitted for inclusion in the Work
 | 
				
			||||||
 | 
					      by You to the Licensor shall be under the terms and conditions of
 | 
				
			||||||
 | 
					      this License, without any additional terms or conditions.
 | 
				
			||||||
 | 
					      Notwithstanding the above, nothing herein shall supersede or modify
 | 
				
			||||||
 | 
					      the terms of any separate license agreement you may have executed
 | 
				
			||||||
 | 
					      with Licensor regarding such Contributions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   6. Trademarks. This License does not grant permission to use the trade
 | 
				
			||||||
 | 
					      names, trademarks, service marks, or product names of the Licensor,
 | 
				
			||||||
 | 
					      except as required for reasonable and customary use in describing the
 | 
				
			||||||
 | 
					      origin of the Work and reproducing the content of the NOTICE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   7. Disclaimer of Warranty. Unless required by applicable law or
 | 
				
			||||||
 | 
					      agreed to in writing, Licensor provides the Work (and each
 | 
				
			||||||
 | 
					      Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
				
			||||||
 | 
					      implied, including, without limitation, any warranties or conditions
 | 
				
			||||||
 | 
					      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
				
			||||||
 | 
					      PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
				
			||||||
 | 
					      appropriateness of using or redistributing the Work and assume any
 | 
				
			||||||
 | 
					      risks associated with Your exercise of permissions under this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   8. Limitation of Liability. In no event and under no legal theory,
 | 
				
			||||||
 | 
					      whether in tort (including negligence), contract, or otherwise,
 | 
				
			||||||
 | 
					      unless required by applicable law (such as deliberate and grossly
 | 
				
			||||||
 | 
					      negligent acts) or agreed to in writing, shall any Contributor be
 | 
				
			||||||
 | 
					      liable to You for damages, including any direct, indirect, special,
 | 
				
			||||||
 | 
					      incidental, or consequential damages of any character arising as a
 | 
				
			||||||
 | 
					      result of this License or out of the use or inability to use the
 | 
				
			||||||
 | 
					      Work (including but not limited to damages for loss of goodwill,
 | 
				
			||||||
 | 
					      work stoppage, computer failure or malfunction, or any and all
 | 
				
			||||||
 | 
					      other commercial damages or losses), even if such Contributor
 | 
				
			||||||
 | 
					      has been advised of the possibility of such damages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   9. Accepting Warranty or Additional Liability. While redistributing
 | 
				
			||||||
 | 
					      the Work or Derivative Works thereof, You may choose to offer,
 | 
				
			||||||
 | 
					      and charge a fee for, acceptance of support, warranty, indemnity,
 | 
				
			||||||
 | 
					      or other liability obligations and/or rights consistent with this
 | 
				
			||||||
 | 
					      License. However, in accepting such obligations, You may act only
 | 
				
			||||||
 | 
					      on Your own behalf and on Your sole responsibility, not on behalf
 | 
				
			||||||
 | 
					      of any other Contributor, and only if You agree to indemnify,
 | 
				
			||||||
 | 
					      defend, and hold each Contributor harmless for any liability
 | 
				
			||||||
 | 
					      incurred by, or claims asserted against, such Contributor by reason
 | 
				
			||||||
 | 
					      of your accepting any such warranty or additional liability.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   END OF TERMS AND CONDITIONS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   APPENDIX: How to apply the Apache License to your work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      To apply the Apache License to your work, attach the following
 | 
				
			||||||
 | 
					      boilerplate notice, with the fields enclosed by brackets "[]"
 | 
				
			||||||
 | 
					      replaced with your own identifying information. (Don't include
 | 
				
			||||||
 | 
					      the brackets!)  The text should be enclosed in the appropriate
 | 
				
			||||||
 | 
					      comment syntax for the file format. We also recommend that a
 | 
				
			||||||
 | 
					      file or class name and description of purpose be included on the
 | 
				
			||||||
 | 
					      same "printed page" as the copyright notice for easier
 | 
				
			||||||
 | 
					      identification within third-party archives.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Copyright [yyyy] [name of copyright owner]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					#   Copyright The containerd Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					#   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					#   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					#   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					#   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					#   limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: check test deps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test: deps
 | 
				
			||||||
 | 
						go test -v -race ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					deps:
 | 
				
			||||||
 | 
						go mod vendor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					check:
 | 
				
			||||||
 | 
						GOGC=75 golangci-lint run
 | 
				
			||||||
@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright The containerd Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package fifo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						ErrClosed      = errors.New("fifo closed")
 | 
				
			||||||
 | 
						ErrCtrlClosed  = errors.New("control of closed fifo")
 | 
				
			||||||
 | 
						ErrRdFrmWRONLY = errors.New("reading from write-only fifo")
 | 
				
			||||||
 | 
						ErrReadClosed  = errors.New("reading from a closed fifo")
 | 
				
			||||||
 | 
						ErrWrToRDONLY  = errors.New("writing to read-only fifo")
 | 
				
			||||||
 | 
						ErrWriteClosed = errors.New("writing to a closed fifo")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@ -0,0 +1,268 @@
 | 
				
			|||||||
 | 
					//go:build !windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright The containerd Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package fifo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"golang.org/x/sys/unix"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type fifo struct {
 | 
				
			||||||
 | 
						flag        int
 | 
				
			||||||
 | 
						opened      chan struct{}
 | 
				
			||||||
 | 
						closed      chan struct{}
 | 
				
			||||||
 | 
						closing     chan struct{}
 | 
				
			||||||
 | 
						err         error
 | 
				
			||||||
 | 
						file        *os.File
 | 
				
			||||||
 | 
						closingOnce sync.Once // close has been called
 | 
				
			||||||
 | 
						closedOnce  sync.Once // fifo is closed
 | 
				
			||||||
 | 
						handle      *handle
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var leakCheckWg *sync.WaitGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OpenFifoDup2 is same as OpenFifo, but additionally creates a copy of the FIFO file descriptor with dup2 syscall.
 | 
				
			||||||
 | 
					func OpenFifoDup2(ctx context.Context, fn string, flag int, perm os.FileMode, fd int) (io.ReadWriteCloser, error) {
 | 
				
			||||||
 | 
						f, err := openFifo(ctx, fn, flag, perm)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("fifo error: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := unix.Dup2(int(f.file.Fd()), fd); err != nil {
 | 
				
			||||||
 | 
							_ = f.Close()
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("dup2 error: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return f, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OpenFifo opens a fifo. Returns io.ReadWriteCloser.
 | 
				
			||||||
 | 
					// Context can be used to cancel this function until open(2) has not returned.
 | 
				
			||||||
 | 
					// Accepted flags:
 | 
				
			||||||
 | 
					//   - syscall.O_CREAT - create new fifo if one doesn't exist
 | 
				
			||||||
 | 
					//   - syscall.O_RDONLY - open fifo only from reader side
 | 
				
			||||||
 | 
					//   - syscall.O_WRONLY - open fifo only from writer side
 | 
				
			||||||
 | 
					//   - syscall.O_RDWR - open fifo from both sides, never block on syscall level
 | 
				
			||||||
 | 
					//   - syscall.O_NONBLOCK - return io.ReadWriteCloser even if other side of the
 | 
				
			||||||
 | 
					//     fifo isn't open. read/write will be connected after the actual fifo is
 | 
				
			||||||
 | 
					//     open or after fifo is closed.
 | 
				
			||||||
 | 
					func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
 | 
				
			||||||
 | 
						fifo, err := openFifo(ctx, fn, flag, perm)
 | 
				
			||||||
 | 
						if fifo == nil {
 | 
				
			||||||
 | 
							// Do not return a non-nil ReadWriteCloser((*fifo)(nil)) value
 | 
				
			||||||
 | 
							// as that can confuse callers.
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fifo, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func openFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (*fifo, error) {
 | 
				
			||||||
 | 
						if _, err := os.Stat(fn); err != nil {
 | 
				
			||||||
 | 
							if os.IsNotExist(err) && flag&syscall.O_CREAT != 0 {
 | 
				
			||||||
 | 
								if err := syscall.Mkfifo(fn, uint32(perm&os.ModePerm)); err != nil && !os.IsExist(err) {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("error creating fifo %v: %w", fn, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						block := flag&syscall.O_NONBLOCK == 0 || flag&syscall.O_RDWR != 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag &= ^syscall.O_CREAT
 | 
				
			||||||
 | 
						flag &= ^syscall.O_NONBLOCK
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h, err := getHandle(fn)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						f := &fifo{
 | 
				
			||||||
 | 
							handle:  h,
 | 
				
			||||||
 | 
							flag:    flag,
 | 
				
			||||||
 | 
							opened:  make(chan struct{}),
 | 
				
			||||||
 | 
							closed:  make(chan struct{}),
 | 
				
			||||||
 | 
							closing: make(chan struct{}),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wg := leakCheckWg
 | 
				
			||||||
 | 
						if wg != nil {
 | 
				
			||||||
 | 
							wg.Add(2)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							if wg != nil {
 | 
				
			||||||
 | 
								defer wg.Done()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								select {
 | 
				
			||||||
 | 
								case <-f.opened:
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									f.Close()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case <-f.opened:
 | 
				
			||||||
 | 
							case <-f.closed:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							if wg != nil {
 | 
				
			||||||
 | 
								defer wg.Done()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var file *os.File
 | 
				
			||||||
 | 
							fn, err := h.Path()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								file, err = os.OpenFile(fn, flag, 0)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-f.closing:
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-ctx.Done():
 | 
				
			||||||
 | 
										err = ctx.Err()
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										err = fmt.Errorf("fifo %v was closed before opening", h.Name())
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if file != nil {
 | 
				
			||||||
 | 
										file.Close()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								f.closedOnce.Do(func() {
 | 
				
			||||||
 | 
									f.err = err
 | 
				
			||||||
 | 
									close(f.closed)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							f.file = file
 | 
				
			||||||
 | 
							close(f.opened)
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						if block {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-f.opened:
 | 
				
			||||||
 | 
							case <-f.closed:
 | 
				
			||||||
 | 
								return nil, f.err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return f, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read from a fifo to a byte array.
 | 
				
			||||||
 | 
					func (f *fifo) Read(b []byte) (int, error) {
 | 
				
			||||||
 | 
						if f.flag&syscall.O_WRONLY > 0 {
 | 
				
			||||||
 | 
							return 0, ErrRdFrmWRONLY
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-f.opened:
 | 
				
			||||||
 | 
							return f.file.Read(b)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-f.opened:
 | 
				
			||||||
 | 
							return f.file.Read(b)
 | 
				
			||||||
 | 
						case <-f.closed:
 | 
				
			||||||
 | 
							return 0, ErrReadClosed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Write from byte array to a fifo.
 | 
				
			||||||
 | 
					func (f *fifo) Write(b []byte) (int, error) {
 | 
				
			||||||
 | 
						if f.flag&(syscall.O_WRONLY|syscall.O_RDWR) == 0 {
 | 
				
			||||||
 | 
							return 0, ErrWrToRDONLY
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-f.opened:
 | 
				
			||||||
 | 
							return f.file.Write(b)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-f.opened:
 | 
				
			||||||
 | 
							return f.file.Write(b)
 | 
				
			||||||
 | 
						case <-f.closed:
 | 
				
			||||||
 | 
							return 0, ErrWriteClosed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Close the fifo. Next reads/writes will error. This method can also be used
 | 
				
			||||||
 | 
					// before open(2) has returned and fifo was never opened.
 | 
				
			||||||
 | 
					func (f *fifo) Close() (retErr error) {
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							if f == nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-f.closed:
 | 
				
			||||||
 | 
								f.handle.Close()
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								select {
 | 
				
			||||||
 | 
								case <-f.opened:
 | 
				
			||||||
 | 
									f.closedOnce.Do(func() {
 | 
				
			||||||
 | 
										retErr = f.file.Close()
 | 
				
			||||||
 | 
										f.err = retErr
 | 
				
			||||||
 | 
										close(f.closed)
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									if f.flag&syscall.O_RDWR != 0 {
 | 
				
			||||||
 | 
										runtime.Gosched()
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									f.closingOnce.Do(func() {
 | 
				
			||||||
 | 
										close(f.closing)
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									reverseMode := syscall.O_WRONLY
 | 
				
			||||||
 | 
									if f.flag&syscall.O_WRONLY > 0 {
 | 
				
			||||||
 | 
										reverseMode = syscall.O_RDONLY
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									fn, err := f.handle.Path()
 | 
				
			||||||
 | 
									// if Close() is called concurrently(shouldn't) it may cause error
 | 
				
			||||||
 | 
									// because handle is closed
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-f.closed:
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											// Path has become invalid. We will leak a goroutine.
 | 
				
			||||||
 | 
											// This case should not happen in linux.
 | 
				
			||||||
 | 
											f.closedOnce.Do(func() {
 | 
				
			||||||
 | 
												f.err = err
 | 
				
			||||||
 | 
												close(f.closed)
 | 
				
			||||||
 | 
											})
 | 
				
			||||||
 | 
											<-f.closed
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										f, err := os.OpenFile(fn, reverseMode|syscall.O_NONBLOCK, 0)
 | 
				
			||||||
 | 
										if err == nil {
 | 
				
			||||||
 | 
											f.Close()
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										runtime.Gosched()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					//go:build linux
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright The containerd Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package fifo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//nolint:revive
 | 
				
			||||||
 | 
					const O_PATH = 010000000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type handle struct {
 | 
				
			||||||
 | 
						f         *os.File
 | 
				
			||||||
 | 
						fd        uintptr
 | 
				
			||||||
 | 
						dev       uint64
 | 
				
			||||||
 | 
						ino       uint64
 | 
				
			||||||
 | 
						closeOnce sync.Once
 | 
				
			||||||
 | 
						name      string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getHandle(fn string) (*handle, error) {
 | 
				
			||||||
 | 
						f, err := os.OpenFile(fn, O_PATH, 0)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to open %v with O_PATH: %w", fn, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							stat syscall.Stat_t
 | 
				
			||||||
 | 
							fd   = f.Fd()
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err := syscall.Fstat(int(fd), &stat); err != nil {
 | 
				
			||||||
 | 
							f.Close()
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to stat handle %v: %w", fd, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h := &handle{
 | 
				
			||||||
 | 
							f:    f,
 | 
				
			||||||
 | 
							name: fn,
 | 
				
			||||||
 | 
							//nolint:unconvert
 | 
				
			||||||
 | 
							dev: uint64(stat.Dev),
 | 
				
			||||||
 | 
							ino: stat.Ino,
 | 
				
			||||||
 | 
							fd:  fd,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check /proc just in case
 | 
				
			||||||
 | 
						if _, err := os.Stat(h.procPath()); err != nil {
 | 
				
			||||||
 | 
							f.Close()
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("couldn't stat %v: %w", h.procPath(), err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return h, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *handle) procPath() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("/proc/self/fd/%d", h.fd)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *handle) Name() string {
 | 
				
			||||||
 | 
						return h.name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *handle) Path() (string, error) {
 | 
				
			||||||
 | 
						var stat syscall.Stat_t
 | 
				
			||||||
 | 
						if err := syscall.Stat(h.procPath(), &stat); err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("path %v could not be statted: %w", h.procPath(), err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						//nolint:unconvert
 | 
				
			||||||
 | 
						if uint64(stat.Dev) != h.dev || stat.Ino != h.ino {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("failed to verify handle %v/%v %v/%v", stat.Dev, h.dev, stat.Ino, h.ino)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return h.procPath(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *handle) Close() error {
 | 
				
			||||||
 | 
						h.closeOnce.Do(func() {
 | 
				
			||||||
 | 
							h.f.Close()
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					//go:build !linux && !windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright The containerd Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package fifo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type handle struct {
 | 
				
			||||||
 | 
						fn  string
 | 
				
			||||||
 | 
						dev uint64
 | 
				
			||||||
 | 
						ino uint64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getHandle(fn string) (*handle, error) {
 | 
				
			||||||
 | 
						var stat syscall.Stat_t
 | 
				
			||||||
 | 
						if err := syscall.Stat(fn, &stat); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to stat %v: %w", fn, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h := &handle{
 | 
				
			||||||
 | 
							fn:  fn,
 | 
				
			||||||
 | 
							dev: uint64(stat.Dev), //nolint:unconvert,nolintlint
 | 
				
			||||||
 | 
							ino: uint64(stat.Ino), //nolint:unconvert,nolintlint
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return h, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *handle) Path() (string, error) {
 | 
				
			||||||
 | 
						var stat syscall.Stat_t
 | 
				
			||||||
 | 
						if err := syscall.Stat(h.fn, &stat); err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("path %v could not be statted: %w", h.fn, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if uint64(stat.Dev) != h.dev || uint64(stat.Ino) != h.ino { //nolint:unconvert,nolintlint
 | 
				
			||||||
 | 
							return "", fmt.Errorf("failed to verify handle %v/%v %v/%v for %v", stat.Dev, h.dev, stat.Ino, h.ino, h.fn)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return h.fn, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *handle) Name() string {
 | 
				
			||||||
 | 
						return h.fn
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *handle) Close() error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,114 @@
 | 
				
			|||||||
 | 
					//go:build !windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright The containerd Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package fifo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SyscallConn provides raw access to the fifo's underlying filedescrptor.
 | 
				
			||||||
 | 
					// See syscall.Conn for guarantees provided by this interface.
 | 
				
			||||||
 | 
					func (f *fifo) SyscallConn() (syscall.RawConn, error) {
 | 
				
			||||||
 | 
						// deterministic check for closed
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-f.closed:
 | 
				
			||||||
 | 
							return nil, ErrClosed
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-f.closed:
 | 
				
			||||||
 | 
							return nil, ErrClosed
 | 
				
			||||||
 | 
						case <-f.opened:
 | 
				
			||||||
 | 
							return f.file.SyscallConn()
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Not opened and not closed, this means open is non-blocking AND it's not open yet
 | 
				
			||||||
 | 
						// Use rawConn to deal with non-blocking open.
 | 
				
			||||||
 | 
						rc := &rawConn{f: f, ready: make(chan struct{})}
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-f.closed:
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							case <-f.opened:
 | 
				
			||||||
 | 
								rc.raw, rc.err = f.file.SyscallConn()
 | 
				
			||||||
 | 
								close(rc.ready)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return rc, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type rawConn struct {
 | 
				
			||||||
 | 
						f     *fifo
 | 
				
			||||||
 | 
						ready chan struct{}
 | 
				
			||||||
 | 
						raw   syscall.RawConn
 | 
				
			||||||
 | 
						err   error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *rawConn) Control(f func(fd uintptr)) error {
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-r.f.closed:
 | 
				
			||||||
 | 
							return ErrCtrlClosed
 | 
				
			||||||
 | 
						case <-r.ready:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if r.err != nil {
 | 
				
			||||||
 | 
							return r.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return r.raw.Control(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *rawConn) Read(f func(fd uintptr) (done bool)) error {
 | 
				
			||||||
 | 
						if r.f.flag&syscall.O_WRONLY > 0 {
 | 
				
			||||||
 | 
							return ErrRdFrmWRONLY
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-r.f.closed:
 | 
				
			||||||
 | 
							return ErrReadClosed
 | 
				
			||||||
 | 
						case <-r.ready:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if r.err != nil {
 | 
				
			||||||
 | 
							return r.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return r.raw.Read(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *rawConn) Write(f func(fd uintptr) (done bool)) error {
 | 
				
			||||||
 | 
						if r.f.flag&(syscall.O_WRONLY|syscall.O_RDWR) == 0 {
 | 
				
			||||||
 | 
							return ErrWrToRDONLY
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-r.f.closed:
 | 
				
			||||||
 | 
							return ErrWriteClosed
 | 
				
			||||||
 | 
						case <-r.ready:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if r.err != nil {
 | 
				
			||||||
 | 
							return r.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return r.raw.Write(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					### fifo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://pkg.go.dev/github.com/containerd/fifo)
 | 
				
			||||||
 | 
					[](https://github.com/containerd/fifo/actions?query=workflow%3ACI)
 | 
				
			||||||
 | 
					[](https://codecov.io/gh/containerd/fifo)
 | 
				
			||||||
 | 
					[](https://goreportcard.com/report/github.com/containerd/fifo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Go package for handling fifos in a sane way.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					// OpenFifo opens a fifo. Returns io.ReadWriteCloser.
 | 
				
			||||||
 | 
					// Context can be used to cancel this function until open(2) has not returned.
 | 
				
			||||||
 | 
					// Accepted flags:
 | 
				
			||||||
 | 
					// - syscall.O_CREAT - create new fifo if one doesn't exist
 | 
				
			||||||
 | 
					// - syscall.O_RDONLY - open fifo only from reader side
 | 
				
			||||||
 | 
					// - syscall.O_WRONLY - open fifo only from writer side
 | 
				
			||||||
 | 
					// - syscall.O_RDWR - open fifo from both sides, never block on syscall level
 | 
				
			||||||
 | 
					// - syscall.O_NONBLOCK - return io.ReadWriteCloser even if other side of the
 | 
				
			||||||
 | 
					//     fifo isn't open. read/write will be connected after the actual fifo is
 | 
				
			||||||
 | 
					//     open or after fifo is closed.
 | 
				
			||||||
 | 
					func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read from a fifo to a byte array.
 | 
				
			||||||
 | 
					func (f *fifo) Read(b []byte) (int, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Write from byte array to a fifo.
 | 
				
			||||||
 | 
					func (f *fifo) Write(b []byte) (int, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Close the fifo. Next reads/writes will error. This method can also be used
 | 
				
			||||||
 | 
					// before open(2) has returned and fifo was never opened.
 | 
				
			||||||
 | 
					func (f *fifo) Close() error 
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Project details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The fifo is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
 | 
				
			||||||
 | 
					As a containerd sub-project, you will find the:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
 | 
				
			||||||
 | 
					 * [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
 | 
				
			||||||
 | 
					 * and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					information in our [`containerd/project`](https://github.com/containerd/project) repository.
 | 
				
			||||||
@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright The containerd Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package fifo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsFifo checks if a file is a (named pipe) fifo
 | 
				
			||||||
 | 
					// if the file does not exist then it returns false
 | 
				
			||||||
 | 
					func IsFifo(path string) (bool, error) {
 | 
				
			||||||
 | 
						stat, err := os.Stat(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if os.IsNotExist(err) {
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if stat.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					The MIT License (MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2014 Brian Goff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					package md2man
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/russross/blackfriday/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Render converts a markdown document into a roff formatted document.
 | 
				
			||||||
 | 
					func Render(doc []byte) []byte {
 | 
				
			||||||
 | 
						renderer := NewRoffRenderer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return blackfriday.Run(doc,
 | 
				
			||||||
 | 
							[]blackfriday.Option{blackfriday.WithRenderer(renderer),
 | 
				
			||||||
 | 
								blackfriday.WithExtensions(renderer.GetExtensions())}...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,336 @@
 | 
				
			|||||||
 | 
					package md2man
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/russross/blackfriday/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// roffRenderer implements the blackfriday.Renderer interface for creating
 | 
				
			||||||
 | 
					// roff format (manpages) from markdown text
 | 
				
			||||||
 | 
					type roffRenderer struct {
 | 
				
			||||||
 | 
						extensions   blackfriday.Extensions
 | 
				
			||||||
 | 
						listCounters []int
 | 
				
			||||||
 | 
						firstHeader  bool
 | 
				
			||||||
 | 
						firstDD      bool
 | 
				
			||||||
 | 
						listDepth    int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						titleHeader      = ".TH "
 | 
				
			||||||
 | 
						topLevelHeader   = "\n\n.SH "
 | 
				
			||||||
 | 
						secondLevelHdr   = "\n.SH "
 | 
				
			||||||
 | 
						otherHeader      = "\n.SS "
 | 
				
			||||||
 | 
						crTag            = "\n"
 | 
				
			||||||
 | 
						emphTag          = "\\fI"
 | 
				
			||||||
 | 
						emphCloseTag     = "\\fP"
 | 
				
			||||||
 | 
						strongTag        = "\\fB"
 | 
				
			||||||
 | 
						strongCloseTag   = "\\fP"
 | 
				
			||||||
 | 
						breakTag         = "\n.br\n"
 | 
				
			||||||
 | 
						paraTag          = "\n.PP\n"
 | 
				
			||||||
 | 
						hruleTag         = "\n.ti 0\n\\l'\\n(.lu'\n"
 | 
				
			||||||
 | 
						linkTag          = "\n\\[la]"
 | 
				
			||||||
 | 
						linkCloseTag     = "\\[ra]"
 | 
				
			||||||
 | 
						codespanTag      = "\\fB\\fC"
 | 
				
			||||||
 | 
						codespanCloseTag = "\\fR"
 | 
				
			||||||
 | 
						codeTag          = "\n.PP\n.RS\n\n.nf\n"
 | 
				
			||||||
 | 
						codeCloseTag     = "\n.fi\n.RE\n"
 | 
				
			||||||
 | 
						quoteTag         = "\n.PP\n.RS\n"
 | 
				
			||||||
 | 
						quoteCloseTag    = "\n.RE\n"
 | 
				
			||||||
 | 
						listTag          = "\n.RS\n"
 | 
				
			||||||
 | 
						listCloseTag     = "\n.RE\n"
 | 
				
			||||||
 | 
						dtTag            = "\n.TP\n"
 | 
				
			||||||
 | 
						dd2Tag           = "\n"
 | 
				
			||||||
 | 
						tableStart       = "\n.TS\nallbox;\n"
 | 
				
			||||||
 | 
						tableEnd         = ".TE\n"
 | 
				
			||||||
 | 
						tableCellStart   = "T{\n"
 | 
				
			||||||
 | 
						tableCellEnd     = "\nT}\n"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
 | 
				
			||||||
 | 
					// from markdown
 | 
				
			||||||
 | 
					func NewRoffRenderer() *roffRenderer { // nolint: golint
 | 
				
			||||||
 | 
						var extensions blackfriday.Extensions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						extensions |= blackfriday.NoIntraEmphasis
 | 
				
			||||||
 | 
						extensions |= blackfriday.Tables
 | 
				
			||||||
 | 
						extensions |= blackfriday.FencedCode
 | 
				
			||||||
 | 
						extensions |= blackfriday.SpaceHeadings
 | 
				
			||||||
 | 
						extensions |= blackfriday.Footnotes
 | 
				
			||||||
 | 
						extensions |= blackfriday.Titleblock
 | 
				
			||||||
 | 
						extensions |= blackfriday.DefinitionLists
 | 
				
			||||||
 | 
						return &roffRenderer{
 | 
				
			||||||
 | 
							extensions: extensions,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetExtensions returns the list of extensions used by this renderer implementation
 | 
				
			||||||
 | 
					func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
 | 
				
			||||||
 | 
						return r.extensions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RenderHeader handles outputting the header at document start
 | 
				
			||||||
 | 
					func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
 | 
				
			||||||
 | 
						// disable hyphenation
 | 
				
			||||||
 | 
						out(w, ".nh\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RenderFooter handles outputting the footer at the document end; the roff
 | 
				
			||||||
 | 
					// renderer has no footer information
 | 
				
			||||||
 | 
					func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RenderNode is called for each node in a markdown document; based on the node
 | 
				
			||||||
 | 
					// type the equivalent roff output is sent to the writer
 | 
				
			||||||
 | 
					func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var walkAction = blackfriday.GoToNext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch node.Type {
 | 
				
			||||||
 | 
						case blackfriday.Text:
 | 
				
			||||||
 | 
							escapeSpecialChars(w, node.Literal)
 | 
				
			||||||
 | 
						case blackfriday.Softbreak:
 | 
				
			||||||
 | 
							out(w, crTag)
 | 
				
			||||||
 | 
						case blackfriday.Hardbreak:
 | 
				
			||||||
 | 
							out(w, breakTag)
 | 
				
			||||||
 | 
						case blackfriday.Emph:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								out(w, emphTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								out(w, emphCloseTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case blackfriday.Strong:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								out(w, strongTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								out(w, strongCloseTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case blackfriday.Link:
 | 
				
			||||||
 | 
							if !entering {
 | 
				
			||||||
 | 
								out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case blackfriday.Image:
 | 
				
			||||||
 | 
							// ignore images
 | 
				
			||||||
 | 
							walkAction = blackfriday.SkipChildren
 | 
				
			||||||
 | 
						case blackfriday.Code:
 | 
				
			||||||
 | 
							out(w, codespanTag)
 | 
				
			||||||
 | 
							escapeSpecialChars(w, node.Literal)
 | 
				
			||||||
 | 
							out(w, codespanCloseTag)
 | 
				
			||||||
 | 
						case blackfriday.Document:
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						case blackfriday.Paragraph:
 | 
				
			||||||
 | 
							// roff .PP markers break lists
 | 
				
			||||||
 | 
							if r.listDepth > 0 {
 | 
				
			||||||
 | 
								return blackfriday.GoToNext
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								out(w, paraTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								out(w, crTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case blackfriday.BlockQuote:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								out(w, quoteTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								out(w, quoteCloseTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case blackfriday.Heading:
 | 
				
			||||||
 | 
							r.handleHeading(w, node, entering)
 | 
				
			||||||
 | 
						case blackfriday.HorizontalRule:
 | 
				
			||||||
 | 
							out(w, hruleTag)
 | 
				
			||||||
 | 
						case blackfriday.List:
 | 
				
			||||||
 | 
							r.handleList(w, node, entering)
 | 
				
			||||||
 | 
						case blackfriday.Item:
 | 
				
			||||||
 | 
							r.handleItem(w, node, entering)
 | 
				
			||||||
 | 
						case blackfriday.CodeBlock:
 | 
				
			||||||
 | 
							out(w, codeTag)
 | 
				
			||||||
 | 
							escapeSpecialChars(w, node.Literal)
 | 
				
			||||||
 | 
							out(w, codeCloseTag)
 | 
				
			||||||
 | 
						case blackfriday.Table:
 | 
				
			||||||
 | 
							r.handleTable(w, node, entering)
 | 
				
			||||||
 | 
						case blackfriday.TableHead:
 | 
				
			||||||
 | 
						case blackfriday.TableBody:
 | 
				
			||||||
 | 
						case blackfriday.TableRow:
 | 
				
			||||||
 | 
							// no action as cell entries do all the nroff formatting
 | 
				
			||||||
 | 
							return blackfriday.GoToNext
 | 
				
			||||||
 | 
						case blackfriday.TableCell:
 | 
				
			||||||
 | 
							r.handleTableCell(w, node, entering)
 | 
				
			||||||
 | 
						case blackfriday.HTMLSpan:
 | 
				
			||||||
 | 
							// ignore other HTML tags
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return walkAction
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
 | 
				
			||||||
 | 
						if entering {
 | 
				
			||||||
 | 
							switch node.Level {
 | 
				
			||||||
 | 
							case 1:
 | 
				
			||||||
 | 
								if !r.firstHeader {
 | 
				
			||||||
 | 
									out(w, titleHeader)
 | 
				
			||||||
 | 
									r.firstHeader = true
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								out(w, topLevelHeader)
 | 
				
			||||||
 | 
							case 2:
 | 
				
			||||||
 | 
								out(w, secondLevelHdr)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								out(w, otherHeader)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
 | 
				
			||||||
 | 
						openTag := listTag
 | 
				
			||||||
 | 
						closeTag := listCloseTag
 | 
				
			||||||
 | 
						if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
 | 
				
			||||||
 | 
							// tags for definition lists handled within Item node
 | 
				
			||||||
 | 
							openTag = ""
 | 
				
			||||||
 | 
							closeTag = ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if entering {
 | 
				
			||||||
 | 
							r.listDepth++
 | 
				
			||||||
 | 
							if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
 | 
				
			||||||
 | 
								r.listCounters = append(r.listCounters, 1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							out(w, openTag)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
 | 
				
			||||||
 | 
								r.listCounters = r.listCounters[:len(r.listCounters)-1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							out(w, closeTag)
 | 
				
			||||||
 | 
							r.listDepth--
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
 | 
				
			||||||
 | 
						if entering {
 | 
				
			||||||
 | 
							if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
 | 
				
			||||||
 | 
								out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
 | 
				
			||||||
 | 
								r.listCounters[len(r.listCounters)-1]++
 | 
				
			||||||
 | 
							} else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
 | 
				
			||||||
 | 
								// DT (definition term): line just before DD (see below).
 | 
				
			||||||
 | 
								out(w, dtTag)
 | 
				
			||||||
 | 
								r.firstDD = true
 | 
				
			||||||
 | 
							} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
 | 
				
			||||||
 | 
								// DD (definition description): line that starts with ": ".
 | 
				
			||||||
 | 
								//
 | 
				
			||||||
 | 
								// We have to distinguish between the first DD and the
 | 
				
			||||||
 | 
								// subsequent ones, as there should be no vertical
 | 
				
			||||||
 | 
								// whitespace between the DT and the first DD.
 | 
				
			||||||
 | 
								if r.firstDD {
 | 
				
			||||||
 | 
									r.firstDD = false
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									out(w, dd2Tag)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								out(w, ".IP \\(bu 2\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							out(w, "\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
 | 
				
			||||||
 | 
						if entering {
 | 
				
			||||||
 | 
							out(w, tableStart)
 | 
				
			||||||
 | 
							// call walker to count cells (and rows?) so format section can be produced
 | 
				
			||||||
 | 
							columns := countColumns(node)
 | 
				
			||||||
 | 
							out(w, strings.Repeat("l ", columns)+"\n")
 | 
				
			||||||
 | 
							out(w, strings.Repeat("l ", columns)+".\n")
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							out(w, tableEnd)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
 | 
				
			||||||
 | 
						if entering {
 | 
				
			||||||
 | 
							var start string
 | 
				
			||||||
 | 
							if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
 | 
				
			||||||
 | 
								start = "\t"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if node.IsHeader {
 | 
				
			||||||
 | 
								start += codespanTag
 | 
				
			||||||
 | 
							} else if nodeLiteralSize(node) > 30 {
 | 
				
			||||||
 | 
								start += tableCellStart
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							out(w, start)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							var end string
 | 
				
			||||||
 | 
							if node.IsHeader {
 | 
				
			||||||
 | 
								end = codespanCloseTag
 | 
				
			||||||
 | 
							} else if nodeLiteralSize(node) > 30 {
 | 
				
			||||||
 | 
								end = tableCellEnd
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if node.Next == nil && end != tableCellEnd {
 | 
				
			||||||
 | 
								// Last cell: need to carriage return if we are at the end of the
 | 
				
			||||||
 | 
								// header row and content isn't wrapped in a "tablecell"
 | 
				
			||||||
 | 
								end += crTag
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							out(w, end)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func nodeLiteralSize(node *blackfriday.Node) int {
 | 
				
			||||||
 | 
						total := 0
 | 
				
			||||||
 | 
						for n := node.FirstChild; n != nil; n = n.FirstChild {
 | 
				
			||||||
 | 
							total += len(n.Literal)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return total
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// because roff format requires knowing the column count before outputting any table
 | 
				
			||||||
 | 
					// data we need to walk a table tree and count the columns
 | 
				
			||||||
 | 
					func countColumns(node *blackfriday.Node) int {
 | 
				
			||||||
 | 
						var columns int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
 | 
				
			||||||
 | 
							switch node.Type {
 | 
				
			||||||
 | 
							case blackfriday.TableRow:
 | 
				
			||||||
 | 
								if !entering {
 | 
				
			||||||
 | 
									return blackfriday.Terminate
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case blackfriday.TableCell:
 | 
				
			||||||
 | 
								if entering {
 | 
				
			||||||
 | 
									columns++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return blackfriday.GoToNext
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return columns
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func out(w io.Writer, output string) {
 | 
				
			||||||
 | 
						io.WriteString(w, output) // nolint: errcheck
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func escapeSpecialChars(w io.Writer, text []byte) {
 | 
				
			||||||
 | 
						for i := 0; i < len(text); i++ {
 | 
				
			||||||
 | 
							// escape initial apostrophe or period
 | 
				
			||||||
 | 
							if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
 | 
				
			||||||
 | 
								out(w, "\\&")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// directly copy normal characters
 | 
				
			||||||
 | 
							org := i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i < len(text) && text[i] != '\\' {
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if i > org {
 | 
				
			||||||
 | 
								w.Write(text[org:i]) // nolint: errcheck
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// escape a character
 | 
				
			||||||
 | 
							if i >= len(text) {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							w.Write([]byte{'\\', text[i]}) // nolint: errcheck
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,202 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					                                 Apache License
 | 
				
			||||||
 | 
					                           Version 2.0, January 2004
 | 
				
			||||||
 | 
					                        http://www.apache.org/licenses/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   1. Definitions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "License" shall mean the terms and conditions for use, reproduction,
 | 
				
			||||||
 | 
					      and distribution as defined by Sections 1 through 9 of this document.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Licensor" shall mean the copyright owner or entity authorized by
 | 
				
			||||||
 | 
					      the copyright owner that is granting the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Legal Entity" shall mean the union of the acting entity and all
 | 
				
			||||||
 | 
					      other entities that control, are controlled by, or are under common
 | 
				
			||||||
 | 
					      control with that entity. For the purposes of this definition,
 | 
				
			||||||
 | 
					      "control" means (i) the power, direct or indirect, to cause the
 | 
				
			||||||
 | 
					      direction or management of such entity, whether by contract or
 | 
				
			||||||
 | 
					      otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
				
			||||||
 | 
					      outstanding shares, or (iii) beneficial ownership of such entity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "You" (or "Your") shall mean an individual or Legal Entity
 | 
				
			||||||
 | 
					      exercising permissions granted by this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Source" form shall mean the preferred form for making modifications,
 | 
				
			||||||
 | 
					      including but not limited to software source code, documentation
 | 
				
			||||||
 | 
					      source, and configuration files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Object" form shall mean any form resulting from mechanical
 | 
				
			||||||
 | 
					      transformation or translation of a Source form, including but
 | 
				
			||||||
 | 
					      not limited to compiled object code, generated documentation,
 | 
				
			||||||
 | 
					      and conversions to other media types.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Work" shall mean the work of authorship, whether in Source or
 | 
				
			||||||
 | 
					      Object form, made available under the License, as indicated by a
 | 
				
			||||||
 | 
					      copyright notice that is included in or attached to the work
 | 
				
			||||||
 | 
					      (an example is provided in the Appendix below).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Derivative Works" shall mean any work, whether in Source or Object
 | 
				
			||||||
 | 
					      form, that is based on (or derived from) the Work and for which the
 | 
				
			||||||
 | 
					      editorial revisions, annotations, elaborations, or other modifications
 | 
				
			||||||
 | 
					      represent, as a whole, an original work of authorship. For the purposes
 | 
				
			||||||
 | 
					      of this License, Derivative Works shall not include works that remain
 | 
				
			||||||
 | 
					      separable from, or merely link (or bind by name) to the interfaces of,
 | 
				
			||||||
 | 
					      the Work and Derivative Works thereof.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Contribution" shall mean any work of authorship, including
 | 
				
			||||||
 | 
					      the original version of the Work and any modifications or additions
 | 
				
			||||||
 | 
					      to that Work or Derivative Works thereof, that is intentionally
 | 
				
			||||||
 | 
					      submitted to Licensor for inclusion in the Work by the copyright owner
 | 
				
			||||||
 | 
					      or by an individual or Legal Entity authorized to submit on behalf of
 | 
				
			||||||
 | 
					      the copyright owner. For the purposes of this definition, "submitted"
 | 
				
			||||||
 | 
					      means any form of electronic, verbal, or written communication sent
 | 
				
			||||||
 | 
					      to the Licensor or its representatives, including but not limited to
 | 
				
			||||||
 | 
					      communication on electronic mailing lists, source code control systems,
 | 
				
			||||||
 | 
					      and issue tracking systems that are managed by, or on behalf of, the
 | 
				
			||||||
 | 
					      Licensor for the purpose of discussing and improving the Work, but
 | 
				
			||||||
 | 
					      excluding communication that is conspicuously marked or otherwise
 | 
				
			||||||
 | 
					      designated in writing by the copyright owner as "Not a Contribution."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
				
			||||||
 | 
					      on behalf of whom a Contribution has been received by Licensor and
 | 
				
			||||||
 | 
					      subsequently incorporated within the Work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   2. Grant of Copyright License. Subject to the terms and conditions of
 | 
				
			||||||
 | 
					      this License, each Contributor hereby grants to You a perpetual,
 | 
				
			||||||
 | 
					      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
				
			||||||
 | 
					      copyright license to reproduce, prepare Derivative Works of,
 | 
				
			||||||
 | 
					      publicly display, publicly perform, sublicense, and distribute the
 | 
				
			||||||
 | 
					      Work and such Derivative Works in Source or Object form.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   3. Grant of Patent License. Subject to the terms and conditions of
 | 
				
			||||||
 | 
					      this License, each Contributor hereby grants to You a perpetual,
 | 
				
			||||||
 | 
					      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
				
			||||||
 | 
					      (except as stated in this section) patent license to make, have made,
 | 
				
			||||||
 | 
					      use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
				
			||||||
 | 
					      where such license applies only to those patent claims licensable
 | 
				
			||||||
 | 
					      by such Contributor that are necessarily infringed by their
 | 
				
			||||||
 | 
					      Contribution(s) alone or by combination of their Contribution(s)
 | 
				
			||||||
 | 
					      with the Work to which such Contribution(s) was submitted. If You
 | 
				
			||||||
 | 
					      institute patent litigation against any entity (including a
 | 
				
			||||||
 | 
					      cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
				
			||||||
 | 
					      or a Contribution incorporated within the Work constitutes direct
 | 
				
			||||||
 | 
					      or contributory patent infringement, then any patent licenses
 | 
				
			||||||
 | 
					      granted to You under this License for that Work shall terminate
 | 
				
			||||||
 | 
					      as of the date such litigation is filed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   4. Redistribution. You may reproduce and distribute copies of the
 | 
				
			||||||
 | 
					      Work or Derivative Works thereof in any medium, with or without
 | 
				
			||||||
 | 
					      modifications, and in Source or Object form, provided that You
 | 
				
			||||||
 | 
					      meet the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (a) You must give any other recipients of the Work or
 | 
				
			||||||
 | 
					          Derivative Works a copy of this License; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (b) You must cause any modified files to carry prominent notices
 | 
				
			||||||
 | 
					          stating that You changed the files; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (c) You must retain, in the Source form of any Derivative Works
 | 
				
			||||||
 | 
					          that You distribute, all copyright, patent, trademark, and
 | 
				
			||||||
 | 
					          attribution notices from the Source form of the Work,
 | 
				
			||||||
 | 
					          excluding those notices that do not pertain to any part of
 | 
				
			||||||
 | 
					          the Derivative Works; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (d) If the Work includes a "NOTICE" text file as part of its
 | 
				
			||||||
 | 
					          distribution, then any Derivative Works that You distribute must
 | 
				
			||||||
 | 
					          include a readable copy of the attribution notices contained
 | 
				
			||||||
 | 
					          within such NOTICE file, excluding those notices that do not
 | 
				
			||||||
 | 
					          pertain to any part of the Derivative Works, in at least one
 | 
				
			||||||
 | 
					          of the following places: within a NOTICE text file distributed
 | 
				
			||||||
 | 
					          as part of the Derivative Works; within the Source form or
 | 
				
			||||||
 | 
					          documentation, if provided along with the Derivative Works; or,
 | 
				
			||||||
 | 
					          within a display generated by the Derivative Works, if and
 | 
				
			||||||
 | 
					          wherever such third-party notices normally appear. The contents
 | 
				
			||||||
 | 
					          of the NOTICE file are for informational purposes only and
 | 
				
			||||||
 | 
					          do not modify the License. You may add Your own attribution
 | 
				
			||||||
 | 
					          notices within Derivative Works that You distribute, alongside
 | 
				
			||||||
 | 
					          or as an addendum to the NOTICE text from the Work, provided
 | 
				
			||||||
 | 
					          that such additional attribution notices cannot be construed
 | 
				
			||||||
 | 
					          as modifying the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      You may add Your own copyright statement to Your modifications and
 | 
				
			||||||
 | 
					      may provide additional or different license terms and conditions
 | 
				
			||||||
 | 
					      for use, reproduction, or distribution of Your modifications, or
 | 
				
			||||||
 | 
					      for any such Derivative Works as a whole, provided Your use,
 | 
				
			||||||
 | 
					      reproduction, and distribution of the Work otherwise complies with
 | 
				
			||||||
 | 
					      the conditions stated in this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
				
			||||||
 | 
					      any Contribution intentionally submitted for inclusion in the Work
 | 
				
			||||||
 | 
					      by You to the Licensor shall be under the terms and conditions of
 | 
				
			||||||
 | 
					      this License, without any additional terms or conditions.
 | 
				
			||||||
 | 
					      Notwithstanding the above, nothing herein shall supersede or modify
 | 
				
			||||||
 | 
					      the terms of any separate license agreement you may have executed
 | 
				
			||||||
 | 
					      with Licensor regarding such Contributions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   6. Trademarks. This License does not grant permission to use the trade
 | 
				
			||||||
 | 
					      names, trademarks, service marks, or product names of the Licensor,
 | 
				
			||||||
 | 
					      except as required for reasonable and customary use in describing the
 | 
				
			||||||
 | 
					      origin of the Work and reproducing the content of the NOTICE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   7. Disclaimer of Warranty. Unless required by applicable law or
 | 
				
			||||||
 | 
					      agreed to in writing, Licensor provides the Work (and each
 | 
				
			||||||
 | 
					      Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
				
			||||||
 | 
					      implied, including, without limitation, any warranties or conditions
 | 
				
			||||||
 | 
					      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
				
			||||||
 | 
					      PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
				
			||||||
 | 
					      appropriateness of using or redistributing the Work and assume any
 | 
				
			||||||
 | 
					      risks associated with Your exercise of permissions under this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   8. Limitation of Liability. In no event and under no legal theory,
 | 
				
			||||||
 | 
					      whether in tort (including negligence), contract, or otherwise,
 | 
				
			||||||
 | 
					      unless required by applicable law (such as deliberate and grossly
 | 
				
			||||||
 | 
					      negligent acts) or agreed to in writing, shall any Contributor be
 | 
				
			||||||
 | 
					      liable to You for damages, including any direct, indirect, special,
 | 
				
			||||||
 | 
					      incidental, or consequential damages of any character arising as a
 | 
				
			||||||
 | 
					      result of this License or out of the use or inability to use the
 | 
				
			||||||
 | 
					      Work (including but not limited to damages for loss of goodwill,
 | 
				
			||||||
 | 
					      work stoppage, computer failure or malfunction, or any and all
 | 
				
			||||||
 | 
					      other commercial damages or losses), even if such Contributor
 | 
				
			||||||
 | 
					      has been advised of the possibility of such damages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   9. Accepting Warranty or Additional Liability. While redistributing
 | 
				
			||||||
 | 
					      the Work or Derivative Works thereof, You may choose to offer,
 | 
				
			||||||
 | 
					      and charge a fee for, acceptance of support, warranty, indemnity,
 | 
				
			||||||
 | 
					      or other liability obligations and/or rights consistent with this
 | 
				
			||||||
 | 
					      License. However, in accepting such obligations, You may act only
 | 
				
			||||||
 | 
					      on Your own behalf and on Your sole responsibility, not on behalf
 | 
				
			||||||
 | 
					      of any other Contributor, and only if You agree to indemnify,
 | 
				
			||||||
 | 
					      defend, and hold each Contributor harmless for any liability
 | 
				
			||||||
 | 
					      incurred by, or claims asserted against, such Contributor by reason
 | 
				
			||||||
 | 
					      of your accepting any such warranty or additional liability.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   END OF TERMS AND CONDITIONS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   APPENDIX: How to apply the Apache License to your work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      To apply the Apache License to your work, attach the following
 | 
				
			||||||
 | 
					      boilerplate notice, with the fields enclosed by brackets "[]"
 | 
				
			||||||
 | 
					      replaced with your own identifying information. (Don't include
 | 
				
			||||||
 | 
					      the brackets!)  The text should be enclosed in the appropriate
 | 
				
			||||||
 | 
					      comment syntax for the file format. We also recommend that a
 | 
				
			||||||
 | 
					      file or class name and description of purpose be included on the
 | 
				
			||||||
 | 
					      same "printed page" as the copyright notice for easier
 | 
				
			||||||
 | 
					      identification within third-party archives.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Copyright [yyyy] [name of copyright owner]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					# go-dap: Go implementation of the Debug Adapter Protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://pkg.go.dev/github.com/google/go-dap)
 | 
				
			||||||
 | 
					[](https://github.com/google/go-dap/actions)
 | 
				
			||||||
 | 
					[](https://goreportcard.com/report/github.com/google/go-dap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For an overview of DAP, see
 | 
				
			||||||
 | 
					https://microsoft.github.io/debug-adapter-protocol/overview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Contributing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We'd love to accept your patches and contributions to this project. See
 | 
				
			||||||
 | 
					[docs/contributing](https://github.com/google/go-dap/blob/master/docs/contributing.md)
 | 
				
			||||||
 | 
					for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This project is licensed under the Apache License 2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is not an officially supported Google product.
 | 
				
			||||||
@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 Google LLC
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					// you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					// You may obtain a copy of the License at
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     https://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					// distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					// See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This file contains utilities for decoding JSON-encoded bytes into DAP message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package dap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DecodeProtocolMessageFieldError describes which JSON attribute
 | 
				
			||||||
 | 
					// has an unsupported value that the decoding cannot handle.
 | 
				
			||||||
 | 
					type DecodeProtocolMessageFieldError struct {
 | 
				
			||||||
 | 
						Seq        int
 | 
				
			||||||
 | 
						SubType    string
 | 
				
			||||||
 | 
						FieldName  string
 | 
				
			||||||
 | 
						FieldValue string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *DecodeProtocolMessageFieldError) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s %s '%s' is not supported (seq: %d)", e.SubType, e.FieldName, e.FieldValue, e.Seq)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DecodeProtocolMessage parses the JSON-encoded data and returns the result of
 | 
				
			||||||
 | 
					// the appropriate type within the ProtocolMessage hierarchy. If message type,
 | 
				
			||||||
 | 
					// command, etc cannot be cast, returns DecodeProtocolMessageFieldError.
 | 
				
			||||||
 | 
					// See also godoc for json.Unmarshal, which is used for underlying decoding.
 | 
				
			||||||
 | 
					func DecodeProtocolMessage(data []byte) (Message, error) {
 | 
				
			||||||
 | 
						var protomsg ProtocolMessage
 | 
				
			||||||
 | 
						if err := json.Unmarshal(data, &protomsg); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						switch protomsg.Type {
 | 
				
			||||||
 | 
						case "request":
 | 
				
			||||||
 | 
							return decodeRequest(data)
 | 
				
			||||||
 | 
						case "response":
 | 
				
			||||||
 | 
							return decodeResponse(data)
 | 
				
			||||||
 | 
						case "event":
 | 
				
			||||||
 | 
							return decodeEvent(data)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return nil, &DecodeProtocolMessageFieldError{protomsg.GetSeq(), "ProtocolMessage", "type", protomsg.Type}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type messageCtor func() Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// decodeRequest determines what request type in the ProtocolMessage hierarchy
 | 
				
			||||||
 | 
					// data corresponds to and uses json.Unmarshal to populate the corresponding
 | 
				
			||||||
 | 
					// struct to be returned.
 | 
				
			||||||
 | 
					func decodeRequest(data []byte) (Message, error) {
 | 
				
			||||||
 | 
						var r Request
 | 
				
			||||||
 | 
						if err := json.Unmarshal(data, &r); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if ctor, ok := requestCtor[r.Command]; ok {
 | 
				
			||||||
 | 
							requestPtr := ctor()
 | 
				
			||||||
 | 
							err := json.Unmarshal(data, requestPtr)
 | 
				
			||||||
 | 
							return requestPtr, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, &DecodeProtocolMessageFieldError{r.GetSeq(), "Request", "command", r.Command}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// decodeResponse determines what response type in the ProtocolMessage hierarchy
 | 
				
			||||||
 | 
					// data corresponds to and uses json.Unmarshal to populate the corresponding
 | 
				
			||||||
 | 
					// struct to be returned.
 | 
				
			||||||
 | 
					func decodeResponse(data []byte) (Message, error) {
 | 
				
			||||||
 | 
						var r Response
 | 
				
			||||||
 | 
						if err := json.Unmarshal(data, &r); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !r.Success {
 | 
				
			||||||
 | 
							var er ErrorResponse
 | 
				
			||||||
 | 
							err := json.Unmarshal(data, &er)
 | 
				
			||||||
 | 
							return &er, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if ctor, ok := responseCtor[r.Command]; ok {
 | 
				
			||||||
 | 
							responsePtr := ctor()
 | 
				
			||||||
 | 
							err := json.Unmarshal(data, responsePtr)
 | 
				
			||||||
 | 
							return responsePtr, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, &DecodeProtocolMessageFieldError{r.GetSeq(), "Response", "command", r.Command}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// decodeEvent determines what event type in the ProtocolMessage hierarchy
 | 
				
			||||||
 | 
					// data corresponds to and uses json.Unmarshal to populate the corresponding
 | 
				
			||||||
 | 
					// struct to be returned.
 | 
				
			||||||
 | 
					func decodeEvent(data []byte) (Message, error) {
 | 
				
			||||||
 | 
						var e Event
 | 
				
			||||||
 | 
						if err := json.Unmarshal(data, &e); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if ctor, ok := eventCtor[e.Event]; ok {
 | 
				
			||||||
 | 
							eventPtr := ctor()
 | 
				
			||||||
 | 
							err := json.Unmarshal(data, eventPtr)
 | 
				
			||||||
 | 
							return eventPtr, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, &DecodeProtocolMessageFieldError{e.GetSeq(), "Event", "event", e.Event}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 Google LLC
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					// you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					// You may obtain a copy of the License at
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     https://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					// distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					// See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Package dap contains data types and code for Debug Adapter Protocol (DAP) specification.
 | 
				
			||||||
 | 
					// https://github.com/microsoft/vscode-debugadapter-node/blob/main/debugProtocol.json
 | 
				
			||||||
 | 
					package dap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:generate go run ./cmd/gentypes/gentypes.go -o schematypes.go -u cmd/gentypes/debugProtocol.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 Google LLC
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					// you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					// You may obtain a copy of the License at
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     https://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					// distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					// See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This file contains utilities for DAP Base protocol I/O.
 | 
				
			||||||
 | 
					// For additional information, see "Base protocol" section in
 | 
				
			||||||
 | 
					// https://microsoft.github.io/debug-adapter-protocol/overview.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package dap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BaseProtocolError represents base protocol error, which occurs when the raw
 | 
				
			||||||
 | 
					// message does not conform to the header+content format of the base protocol.
 | 
				
			||||||
 | 
					type BaseProtocolError struct {
 | 
				
			||||||
 | 
						Err string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (bpe *BaseProtocolError) Error() string { return bpe.Err }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						// ErrHeaderDelimiterNotCrLfCrLf is returned when only partial header
 | 
				
			||||||
 | 
						// delimiter \r\n\r\n is encountered.
 | 
				
			||||||
 | 
						ErrHeaderDelimiterNotCrLfCrLf = &BaseProtocolError{fmt.Sprintf("header delimiter is not %q", crLfcrLf)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ErrHeaderNotContentLength is returned when the parsed header is
 | 
				
			||||||
 | 
						// not of valid Content-Length format.
 | 
				
			||||||
 | 
						ErrHeaderNotContentLength = &BaseProtocolError{fmt.Sprintf("header format is not %q", contentLengthHeaderRegex)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ErrHeaderContentTooLong is returned when the content length specified in
 | 
				
			||||||
 | 
						// the header is above contentMaxLength.
 | 
				
			||||||
 | 
						ErrHeaderContentTooLong = &BaseProtocolError{fmt.Sprintf("content length over %v bytes", contentMaxLength)}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						crLfcrLf               = "\r\n\r\n"
 | 
				
			||||||
 | 
						contentLengthHeaderFmt = "Content-Length: %d\r\n\r\n"
 | 
				
			||||||
 | 
						contentMaxLength       = 4 * 1024 * 1024
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						contentLengthHeaderRegex = regexp.MustCompile("^Content-Length: ([0-9]+)$")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WriteBaseMessage formats content with Content-Length header and delimiters
 | 
				
			||||||
 | 
					// as per the base protocol and writes the resulting message to w.
 | 
				
			||||||
 | 
					func WriteBaseMessage(w io.Writer, content []byte) error {
 | 
				
			||||||
 | 
						header := fmt.Sprintf(contentLengthHeaderFmt, len(content))
 | 
				
			||||||
 | 
						if _, err := w.Write([]byte(header)); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err := w.Write(content)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadBaseMessage reads one message from r consisting of a Content-Length
 | 
				
			||||||
 | 
					// header and a content part. It parses the header to determine the size of
 | 
				
			||||||
 | 
					// the content part and extracts and returns the actual content of the message.
 | 
				
			||||||
 | 
					// Returns nil bytes on error, which can be one of the standard IO errors or
 | 
				
			||||||
 | 
					// a BaseProtocolError defined in this package.
 | 
				
			||||||
 | 
					func ReadBaseMessage(r *bufio.Reader) ([]byte, error) {
 | 
				
			||||||
 | 
						contentLength, err := readContentLengthHeader(r)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if contentLength > contentMaxLength {
 | 
				
			||||||
 | 
							return nil, ErrHeaderContentTooLong
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						content := make([]byte, contentLength)
 | 
				
			||||||
 | 
						if _, err = io.ReadFull(r, content); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return content, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// readContentLengthHeader looks for the only header field that is supported
 | 
				
			||||||
 | 
					// and required:
 | 
				
			||||||
 | 
					// 		Content-Length: [0-9]+\r\n\r\n
 | 
				
			||||||
 | 
					// Extracts and returns the content length.
 | 
				
			||||||
 | 
					func readContentLengthHeader(r *bufio.Reader) (contentLength int64, err error) {
 | 
				
			||||||
 | 
						// Look for <some header>\r\n\r\n
 | 
				
			||||||
 | 
						headerWithCr, err := r.ReadString('\r')
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						nextThree := make([]byte, 3)
 | 
				
			||||||
 | 
						if _, err = io.ReadFull(r, nextThree); err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if string(nextThree) != "\n\r\n" {
 | 
				
			||||||
 | 
							return 0, ErrHeaderDelimiterNotCrLfCrLf
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If header is in the right format, get the length
 | 
				
			||||||
 | 
						header := strings.TrimSuffix(headerWithCr, "\r")
 | 
				
			||||||
 | 
						headerAndLength := contentLengthHeaderRegex.FindStringSubmatch(header)
 | 
				
			||||||
 | 
						if len(headerAndLength) < 2 {
 | 
				
			||||||
 | 
							return 0, ErrHeaderNotContentLength
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return strconv.ParseInt(headerAndLength[1], 10, 64)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WriteProtocolMessage encodes message and writes it to w.
 | 
				
			||||||
 | 
					func WriteProtocolMessage(w io.Writer, message Message) error {
 | 
				
			||||||
 | 
						b, err := json.Marshal(message)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return WriteBaseMessage(w, b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadProtocolMessage reads a message from r, decodes and returns it.
 | 
				
			||||||
 | 
					func ReadProtocolMessage(r *bufio.Reader) (Message, error) {
 | 
				
			||||||
 | 
						content, err := ReadBaseMessage(r)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return DecodeProtocolMessage(content)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					*.out
 | 
				
			||||||
 | 
					*.swp
 | 
				
			||||||
 | 
					*.8
 | 
				
			||||||
 | 
					*.6
 | 
				
			||||||
 | 
					_obj
 | 
				
			||||||
 | 
					_test*
 | 
				
			||||||
 | 
					markdown
 | 
				
			||||||
 | 
					tags
 | 
				
			||||||
@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					sudo: false
 | 
				
			||||||
 | 
					language: go
 | 
				
			||||||
 | 
					go:
 | 
				
			||||||
 | 
					  - "1.10.x"
 | 
				
			||||||
 | 
					  - "1.11.x"
 | 
				
			||||||
 | 
					  - tip
 | 
				
			||||||
 | 
					matrix:
 | 
				
			||||||
 | 
					  fast_finish: true
 | 
				
			||||||
 | 
					  allow_failures:
 | 
				
			||||||
 | 
					    - go: tip
 | 
				
			||||||
 | 
					install:
 | 
				
			||||||
 | 
					  - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
 | 
				
			||||||
 | 
					script:
 | 
				
			||||||
 | 
					  - go get -t -v ./...
 | 
				
			||||||
 | 
					  - diff -u <(echo -n) <(gofmt -d -s .)
 | 
				
			||||||
 | 
					  - go tool vet .
 | 
				
			||||||
 | 
					  - go test -v ./...
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					Blackfriday is distributed under the Simplified BSD License:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Copyright © 2011 Russ Ross
 | 
				
			||||||
 | 
					> All rights reserved.
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					> Redistribution and use in source and binary forms, with or without
 | 
				
			||||||
 | 
					> modification, are permitted provided that the following conditions
 | 
				
			||||||
 | 
					> are met:
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					> 1.  Redistributions of source code must retain the above copyright
 | 
				
			||||||
 | 
					>     notice, this list of conditions and the following disclaimer.
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					> 2.  Redistributions in binary form must reproduce the above
 | 
				
			||||||
 | 
					>     copyright notice, this list of conditions and the following
 | 
				
			||||||
 | 
					>     disclaimer in the documentation and/or other materials provided with
 | 
				
			||||||
 | 
					>     the distribution.
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
				
			||||||
 | 
					> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
				
			||||||
 | 
					> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 | 
				
			||||||
 | 
					> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 | 
				
			||||||
 | 
					> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 | 
				
			||||||
 | 
					> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 | 
				
			||||||
 | 
					> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 | 
				
			||||||
 | 
					> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
				
			||||||
 | 
					> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 | 
				
			||||||
 | 
					> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
				
			||||||
 | 
					> POSSIBILITY OF SUCH DAMAGE.
 | 
				
			||||||
@ -0,0 +1,335 @@
 | 
				
			|||||||
 | 
					Blackfriday
 | 
				
			||||||
 | 
					[![Build Status][BuildV2SVG]][BuildV2URL]
 | 
				
			||||||
 | 
					[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL]
 | 
				
			||||||
 | 
					===========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
 | 
				
			||||||
 | 
					is paranoid about its input (so you can safely feed it user-supplied
 | 
				
			||||||
 | 
					data), it is fast, it supports common extensions (tables, smart
 | 
				
			||||||
 | 
					punctuation substitutions, etc.), and it is safe for all utf-8
 | 
				
			||||||
 | 
					(unicode) input.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HTML output is currently supported, along with Smartypants
 | 
				
			||||||
 | 
					extensions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It started as a translation from C of [Sundown][3].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Installation
 | 
				
			||||||
 | 
					------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Blackfriday is compatible with modern Go releases in module mode.
 | 
				
			||||||
 | 
					With Go installed:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    go get github.com/russross/blackfriday/v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					will resolve and add the package to the current development module,
 | 
				
			||||||
 | 
					then build and install it. Alternatively, you can achieve the same
 | 
				
			||||||
 | 
					if you import it in a package:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import "github.com/russross/blackfriday/v2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					and `go get` without parameters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Legacy GOPATH mode is unsupported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Versions
 | 
				
			||||||
 | 
					--------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Currently maintained and recommended version of Blackfriday is `v2`. It's being
 | 
				
			||||||
 | 
					developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
 | 
				
			||||||
 | 
					documentation is available at
 | 
				
			||||||
 | 
					https://pkg.go.dev/github.com/russross/blackfriday/v2.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Version 2 offers a number of improvements over v1:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Cleaned up API
 | 
				
			||||||
 | 
					* A separate call to [`Parse`][4], which produces an abstract syntax tree for
 | 
				
			||||||
 | 
					  the document
 | 
				
			||||||
 | 
					* Latest bug fixes
 | 
				
			||||||
 | 
					* Flexibility to easily add your own rendering extensions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Potential drawbacks:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Our benchmarks show v2 to be slightly slower than v1. Currently in the
 | 
				
			||||||
 | 
					  ballpark of around 15%.
 | 
				
			||||||
 | 
					* API breakage. If you can't afford modifying your code to adhere to the new API
 | 
				
			||||||
 | 
					  and don't care too much about the new features, v2 is probably not for you.
 | 
				
			||||||
 | 
					* Several bug fixes are trailing behind and still need to be forward-ported to
 | 
				
			||||||
 | 
					  v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
 | 
				
			||||||
 | 
					  tracking.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you are still interested in the legacy `v1`, you can import it from
 | 
				
			||||||
 | 
					`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found
 | 
				
			||||||
 | 
					here: https://pkg.go.dev/github.com/russross/blackfriday.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For the most sensible markdown processing, it is as simple as getting your input
 | 
				
			||||||
 | 
					into a byte slice and calling:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					output := blackfriday.Run(input)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Your input will be parsed and the output rendered with a set of most popular
 | 
				
			||||||
 | 
					extensions enabled. If you want the most basic feature set, corresponding with
 | 
				
			||||||
 | 
					the bare Markdown specification, use:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					output := blackfriday.Run(input, blackfriday.WithNoExtensions())
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Sanitize untrusted content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Blackfriday itself does nothing to protect against malicious content. If you are
 | 
				
			||||||
 | 
					dealing with user-supplied markdown, we recommend running Blackfriday's output
 | 
				
			||||||
 | 
					through HTML sanitizer such as [Bluemonday][5].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here's an example of simple usage of Blackfriday together with Bluemonday:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
					    "github.com/microcosm-cc/bluemonday"
 | 
				
			||||||
 | 
					    "github.com/russross/blackfriday/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ...
 | 
				
			||||||
 | 
					unsafe := blackfriday.Run(input)
 | 
				
			||||||
 | 
					html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Custom options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you want to customize the set of options, use `blackfriday.WithExtensions`,
 | 
				
			||||||
 | 
					`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `blackfriday-tool`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can also check out `blackfriday-tool` for a more complete example
 | 
				
			||||||
 | 
					of how to use it. Download and install it using:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    go get github.com/russross/blackfriday-tool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is a simple command-line tool that allows you to process a
 | 
				
			||||||
 | 
					markdown file using a standalone program.  You can also browse the
 | 
				
			||||||
 | 
					source directly on github if you are just looking for some example
 | 
				
			||||||
 | 
					code:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* <https://github.com/russross/blackfriday-tool>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that if you have not already done so, installing
 | 
				
			||||||
 | 
					`blackfriday-tool` will be sufficient to download and install
 | 
				
			||||||
 | 
					blackfriday in addition to the tool itself. The tool binary will be
 | 
				
			||||||
 | 
					installed in `$GOPATH/bin`.  This is a statically-linked binary that
 | 
				
			||||||
 | 
					can be copied to wherever you need it without worrying about
 | 
				
			||||||
 | 
					dependencies and library versions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Sanitized anchor names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Blackfriday includes an algorithm for creating sanitized anchor names
 | 
				
			||||||
 | 
					corresponding to a given input text. This algorithm is used to create
 | 
				
			||||||
 | 
					anchors for headings when `AutoHeadingIDs` extension is enabled. The
 | 
				
			||||||
 | 
					algorithm has a specification, so that other packages can create
 | 
				
			||||||
 | 
					compatible anchor names and links to those anchors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The specification is located at https://pkg.go.dev/github.com/russross/blackfriday/v2#hdr-Sanitized_Anchor_Names.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[`SanitizedAnchorName`](https://pkg.go.dev/github.com/russross/blackfriday/v2#SanitizedAnchorName) exposes this functionality, and can be used to
 | 
				
			||||||
 | 
					create compatible links to the anchor names generated by blackfriday.
 | 
				
			||||||
 | 
					This algorithm is also implemented in a small standalone package at
 | 
				
			||||||
 | 
					[`github.com/shurcooL/sanitized_anchor_name`](https://pkg.go.dev/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
 | 
				
			||||||
 | 
					that want a small package and don't need full functionality of blackfriday.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Features
 | 
				
			||||||
 | 
					--------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					All features of Sundown are supported, including:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Compatibility**. The Markdown v1.0.3 test suite passes with
 | 
				
			||||||
 | 
					    the `--tidy` option.  Without `--tidy`, the differences are
 | 
				
			||||||
 | 
					    mostly in whitespace and entity escaping, where blackfriday is
 | 
				
			||||||
 | 
					    more consistent and cleaner.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Common extensions**, including table support, fenced code
 | 
				
			||||||
 | 
					    blocks, autolinks, strikethroughs, non-strict emphasis, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Safety**. Blackfriday is paranoid when parsing, making it safe
 | 
				
			||||||
 | 
					    to feed untrusted user input without fear of bad things
 | 
				
			||||||
 | 
					    happening. The test suite stress tests this and there are no
 | 
				
			||||||
 | 
					    known inputs that make it crash.  If you find one, please let me
 | 
				
			||||||
 | 
					    know and send me the input that does it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NOTE: "safety" in this context means *runtime safety only*. In order to
 | 
				
			||||||
 | 
					    protect yourself against JavaScript injection in untrusted content, see
 | 
				
			||||||
 | 
					    [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Fast processing**. It is fast enough to render on-demand in
 | 
				
			||||||
 | 
					    most web applications without having to cache the output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Thread safety**. You can run multiple parsers in different
 | 
				
			||||||
 | 
					    goroutines without ill effect. There is no dependence on global
 | 
				
			||||||
 | 
					    shared state.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Minimal dependencies**. Blackfriday only depends on standard
 | 
				
			||||||
 | 
					    library packages in Go. The source code is pretty
 | 
				
			||||||
 | 
					    self-contained, so it is easy to add to any project, including
 | 
				
			||||||
 | 
					    Google App Engine projects.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Standards compliant**. Output successfully validates using the
 | 
				
			||||||
 | 
					    W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Extensions
 | 
				
			||||||
 | 
					----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In addition to the standard markdown syntax, this package
 | 
				
			||||||
 | 
					implements the following extensions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Intra-word emphasis supression**. The `_` character is
 | 
				
			||||||
 | 
					    commonly used inside words when discussing code, so having
 | 
				
			||||||
 | 
					    markdown interpret it as an emphasis command is usually the
 | 
				
			||||||
 | 
					    wrong thing. Blackfriday lets you treat all emphasis markers as
 | 
				
			||||||
 | 
					    normal characters when they occur inside a word.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Tables**. Tables can be created by drawing them in the input
 | 
				
			||||||
 | 
					    using a simple syntax:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					    Name    | Age
 | 
				
			||||||
 | 
					    --------|------
 | 
				
			||||||
 | 
					    Bob     | 27
 | 
				
			||||||
 | 
					    Alice   | 23
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Fenced code blocks**. In addition to the normal 4-space
 | 
				
			||||||
 | 
					    indentation to mark code blocks, you can explicitly mark them
 | 
				
			||||||
 | 
					    and supply a language (to make syntax highlighting simple). Just
 | 
				
			||||||
 | 
					    mark it like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ```go
 | 
				
			||||||
 | 
					        func getTrue() bool {
 | 
				
			||||||
 | 
					            return true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    You can use 3 or more backticks to mark the beginning of the
 | 
				
			||||||
 | 
					    block, and the same number to mark the end of the block.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    To preserve classes of fenced code blocks while using the bluemonday
 | 
				
			||||||
 | 
					    HTML sanitizer, use the following policy:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    p := bluemonday.UGCPolicy()
 | 
				
			||||||
 | 
					    p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
 | 
				
			||||||
 | 
					    html := p.SanitizeBytes(unsafe)
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Definition lists**. A simple definition list is made of a single-line
 | 
				
			||||||
 | 
					    term followed by a colon and the definition for that term.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Cat
 | 
				
			||||||
 | 
					        : Fluffy animal everyone likes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Internet
 | 
				
			||||||
 | 
					        : Vector of transmission for pictures of cats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Terms must be separated from the previous definition by a blank line.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Footnotes**. A marker in the text that will become a superscript number;
 | 
				
			||||||
 | 
					    a footnote definition that will be placed in a list of footnotes at the
 | 
				
			||||||
 | 
					    end of the document. A footnote looks like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        This is a footnote.[^1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [^1]: the footnote text.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Autolinking**. Blackfriday can find URLs that have not been
 | 
				
			||||||
 | 
					    explicitly marked as links and turn them into links.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Strikethrough**. Use two tildes (`~~`) to mark text that
 | 
				
			||||||
 | 
					    should be crossed out.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Hard line breaks**. With this extension enabled newlines in the input
 | 
				
			||||||
 | 
					    translate into line breaks in the output. This extension is off by default.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Smart quotes**. Smartypants-style punctuation substitution is
 | 
				
			||||||
 | 
					    supported, turning normal double- and single-quote marks into
 | 
				
			||||||
 | 
					    curly quotes, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **LaTeX-style dash parsing** is an additional option, where `--`
 | 
				
			||||||
 | 
					    is translated into `–`, and `---` is translated into
 | 
				
			||||||
 | 
					    `—`. This differs from most smartypants processors, which
 | 
				
			||||||
 | 
					    turn a single hyphen into an ndash and a double hyphen into an
 | 
				
			||||||
 | 
					    mdash.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **Smart fractions**, where anything that looks like a fraction
 | 
				
			||||||
 | 
					    is translated into suitable HTML (instead of just a few special
 | 
				
			||||||
 | 
					    cases like most smartypant processors). For example, `4/5`
 | 
				
			||||||
 | 
					    becomes `<sup>4</sup>⁄<sub>5</sub>`, which renders as
 | 
				
			||||||
 | 
					    <sup>4</sup>⁄<sub>5</sub>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Other renderers
 | 
				
			||||||
 | 
					---------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Blackfriday is structured to allow alternative rendering engines. Here
 | 
				
			||||||
 | 
					are a few of note:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   [github_flavored_markdown](https://pkg.go.dev/github.com/shurcooL/github_flavored_markdown):
 | 
				
			||||||
 | 
					    provides a GitHub Flavored Markdown renderer with fenced code block
 | 
				
			||||||
 | 
					    highlighting, clickable heading anchor links.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    It's not customizable, and its goal is to produce HTML output
 | 
				
			||||||
 | 
					    equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
 | 
				
			||||||
 | 
					    except the rendering is performed locally.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
 | 
				
			||||||
 | 
					    but for markdown.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   [LaTeX output](https://gitlab.com/ambrevar/blackfriday-latex):
 | 
				
			||||||
 | 
					    renders output as LaTeX.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   [bfchroma](https://github.com/Depado/bfchroma/): provides convenience
 | 
				
			||||||
 | 
					    integration with the [Chroma](https://github.com/alecthomas/chroma) code
 | 
				
			||||||
 | 
					    highlighting library. bfchroma is only compatible with v2 of Blackfriday and
 | 
				
			||||||
 | 
					    provides a drop-in renderer ready to use with Blackfriday, as well as
 | 
				
			||||||
 | 
					    options and means for further customization.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   [Blackfriday-Slack](https://github.com/karriereat/blackfriday-slack): converts markdown to slack message style
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   More unit testing
 | 
				
			||||||
 | 
					*   Improve Unicode support. It does not understand all Unicode
 | 
				
			||||||
 | 
					    rules (about what constitutes a letter, a punctuation symbol,
 | 
				
			||||||
 | 
					    etc.), so it may fail to detect word boundaries correctly in
 | 
				
			||||||
 | 
					    some instances. It is safe on all UTF-8 input.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					License
 | 
				
			||||||
 | 
					-------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   [1]: https://daringfireball.net/projects/markdown/ "Markdown"
 | 
				
			||||||
 | 
					   [2]: https://golang.org/ "Go Language"
 | 
				
			||||||
 | 
					   [3]: https://github.com/vmg/sundown "Sundown"
 | 
				
			||||||
 | 
					   [4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func"
 | 
				
			||||||
 | 
					   [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   [BuildV2SVG]: https://travis-ci.org/russross/blackfriday.svg?branch=v2
 | 
				
			||||||
 | 
					   [BuildV2URL]: https://travis-ci.org/russross/blackfriday
 | 
				
			||||||
 | 
					   [PkgGoDevV2SVG]: https://pkg.go.dev/badge/github.com/russross/blackfriday/v2
 | 
				
			||||||
 | 
					   [PkgGoDevV2URL]: https://pkg.go.dev/github.com/russross/blackfriday/v2
 | 
				
			||||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					// Package blackfriday is a markdown processor.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// It translates plain text with simple formatting rules into an AST, which can
 | 
				
			||||||
 | 
					// then be further processed to HTML (provided by Blackfriday itself) or other
 | 
				
			||||||
 | 
					// formats (provided by the community).
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The simplest way to invoke Blackfriday is to call the Run function. It will
 | 
				
			||||||
 | 
					// take a text input and produce a text output in HTML (or other format).
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// A slightly more sophisticated way to use Blackfriday is to create a Markdown
 | 
				
			||||||
 | 
					// processor and to call Parse, which returns a syntax tree for the input
 | 
				
			||||||
 | 
					// document. You can leverage Blackfriday's parsing for content extraction from
 | 
				
			||||||
 | 
					// markdown documents. You can assign a custom renderer and set various options
 | 
				
			||||||
 | 
					// to the Markdown processor.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If you're interested in calling Blackfriday from command line, see
 | 
				
			||||||
 | 
					// https://github.com/russross/blackfriday-tool.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Sanitized Anchor Names
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Blackfriday includes an algorithm for creating sanitized anchor names
 | 
				
			||||||
 | 
					// corresponding to a given input text. This algorithm is used to create
 | 
				
			||||||
 | 
					// anchors for headings when AutoHeadingIDs extension is enabled. The
 | 
				
			||||||
 | 
					// algorithm is specified below, so that other packages can create
 | 
				
			||||||
 | 
					// compatible anchor names and links to those anchors.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The algorithm iterates over the input text, interpreted as UTF-8,
 | 
				
			||||||
 | 
					// one Unicode code point (rune) at a time. All runes that are letters (category L)
 | 
				
			||||||
 | 
					// or numbers (category N) are considered valid characters. They are mapped to
 | 
				
			||||||
 | 
					// lower case, and included in the output. All other runes are considered
 | 
				
			||||||
 | 
					// invalid characters. Invalid characters that precede the first valid character,
 | 
				
			||||||
 | 
					// as well as invalid character that follow the last valid character
 | 
				
			||||||
 | 
					// are dropped completely. All other sequences of invalid characters
 | 
				
			||||||
 | 
					// between two valid characters are replaced with a single dash character '-'.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// SanitizedAnchorName exposes this functionality, and can be used to
 | 
				
			||||||
 | 
					// create compatible links to the anchor names generated by blackfriday.
 | 
				
			||||||
 | 
					// This algorithm is also implemented in a small standalone package at
 | 
				
			||||||
 | 
					// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
 | 
				
			||||||
 | 
					// that want a small package and don't need full functionality of blackfriday.
 | 
				
			||||||
 | 
					package blackfriday
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
 | 
				
			||||||
 | 
					//       github.com/shurcooL/sanitized_anchor_name.
 | 
				
			||||||
 | 
					//       Otherwise, users of sanitized_anchor_name will get anchor names
 | 
				
			||||||
 | 
					//       that are incompatible with those generated by blackfriday.
 | 
				
			||||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					package blackfriday
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"html"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var htmlEscaper = [256][]byte{
 | 
				
			||||||
 | 
						'&': []byte("&"),
 | 
				
			||||||
 | 
						'<': []byte("<"),
 | 
				
			||||||
 | 
						'>': []byte(">"),
 | 
				
			||||||
 | 
						'"': []byte("""),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func escapeHTML(w io.Writer, s []byte) {
 | 
				
			||||||
 | 
						escapeEntities(w, s, false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func escapeAllHTML(w io.Writer, s []byte) {
 | 
				
			||||||
 | 
						escapeEntities(w, s, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func escapeEntities(w io.Writer, s []byte, escapeValidEntities bool) {
 | 
				
			||||||
 | 
						var start, end int
 | 
				
			||||||
 | 
						for end < len(s) {
 | 
				
			||||||
 | 
							escSeq := htmlEscaper[s[end]]
 | 
				
			||||||
 | 
							if escSeq != nil {
 | 
				
			||||||
 | 
								isEntity, entityEnd := nodeIsEntity(s, end)
 | 
				
			||||||
 | 
								if isEntity && !escapeValidEntities {
 | 
				
			||||||
 | 
									w.Write(s[start : entityEnd+1])
 | 
				
			||||||
 | 
									start = entityEnd + 1
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									w.Write(s[start:end])
 | 
				
			||||||
 | 
									w.Write(escSeq)
 | 
				
			||||||
 | 
									start = end + 1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							end++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if start < len(s) && end <= len(s) {
 | 
				
			||||||
 | 
							w.Write(s[start:end])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func nodeIsEntity(s []byte, end int) (isEntity bool, endEntityPos int) {
 | 
				
			||||||
 | 
						isEntity = false
 | 
				
			||||||
 | 
						endEntityPos = end + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if s[end] == '&' {
 | 
				
			||||||
 | 
							for endEntityPos < len(s) {
 | 
				
			||||||
 | 
								if s[endEntityPos] == ';' {
 | 
				
			||||||
 | 
									if entities[string(s[end:endEntityPos+1])] {
 | 
				
			||||||
 | 
										isEntity = true
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !isalnum(s[endEntityPos]) && s[endEntityPos] != '&' && s[endEntityPos] != '#' {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								endEntityPos++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return isEntity, endEntityPos
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func escLink(w io.Writer, text []byte) {
 | 
				
			||||||
 | 
						unesc := html.UnescapeString(string(text))
 | 
				
			||||||
 | 
						escapeHTML(w, []byte(unesc))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,952 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// Blackfriday Markdown Processor
 | 
				
			||||||
 | 
					// Available at http://github.com/russross/blackfriday
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
				
			||||||
 | 
					// Distributed under the Simplified BSD License.
 | 
				
			||||||
 | 
					// See README.md for details.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// HTML rendering backend
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package blackfriday
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HTMLFlags control optional behavior of HTML renderer.
 | 
				
			||||||
 | 
					type HTMLFlags int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HTML renderer configuration options.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						HTMLFlagsNone           HTMLFlags = 0
 | 
				
			||||||
 | 
						SkipHTML                HTMLFlags = 1 << iota // Skip preformatted HTML blocks
 | 
				
			||||||
 | 
						SkipImages                                    // Skip embedded images
 | 
				
			||||||
 | 
						SkipLinks                                     // Skip all links
 | 
				
			||||||
 | 
						Safelink                                      // Only link to trusted protocols
 | 
				
			||||||
 | 
						NofollowLinks                                 // Only link with rel="nofollow"
 | 
				
			||||||
 | 
						NoreferrerLinks                               // Only link with rel="noreferrer"
 | 
				
			||||||
 | 
						NoopenerLinks                                 // Only link with rel="noopener"
 | 
				
			||||||
 | 
						HrefTargetBlank                               // Add a blank target
 | 
				
			||||||
 | 
						CompletePage                                  // Generate a complete HTML page
 | 
				
			||||||
 | 
						UseXHTML                                      // Generate XHTML output instead of HTML
 | 
				
			||||||
 | 
						FootnoteReturnLinks                           // Generate a link at the end of a footnote to return to the source
 | 
				
			||||||
 | 
						Smartypants                                   // Enable smart punctuation substitutions
 | 
				
			||||||
 | 
						SmartypantsFractions                          // Enable smart fractions (with Smartypants)
 | 
				
			||||||
 | 
						SmartypantsDashes                             // Enable smart dashes (with Smartypants)
 | 
				
			||||||
 | 
						SmartypantsLatexDashes                        // Enable LaTeX-style dashes (with Smartypants)
 | 
				
			||||||
 | 
						SmartypantsAngledQuotes                       // Enable angled double quotes (with Smartypants) for double quotes rendering
 | 
				
			||||||
 | 
						SmartypantsQuotesNBSP                         // Enable « French guillemets » (with Smartypants)
 | 
				
			||||||
 | 
						TOC                                           // Generate a table of contents
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" +
 | 
				
			||||||
 | 
							processingInstruction + "|" + declaration + "|" + cdata + ")"
 | 
				
			||||||
 | 
						closeTag              = "</" + tagName + "\\s*[>]"
 | 
				
			||||||
 | 
						openTag               = "<" + tagName + attribute + "*" + "\\s*/?>"
 | 
				
			||||||
 | 
						attribute             = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)"
 | 
				
			||||||
 | 
						attributeValue        = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")"
 | 
				
			||||||
 | 
						attributeValueSpec    = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")"
 | 
				
			||||||
 | 
						attributeName         = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
 | 
				
			||||||
 | 
						cdata                 = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
 | 
				
			||||||
 | 
						declaration           = "<![A-Z]+" + "\\s+[^>]*>"
 | 
				
			||||||
 | 
						doubleQuotedValue     = "\"[^\"]*\""
 | 
				
			||||||
 | 
						htmlComment           = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
 | 
				
			||||||
 | 
						processingInstruction = "[<][?].*?[?][>]"
 | 
				
			||||||
 | 
						singleQuotedValue     = "'[^']*'"
 | 
				
			||||||
 | 
						tagName               = "[A-Za-z][A-Za-z0-9-]*"
 | 
				
			||||||
 | 
						unquotedValue         = "[^\"'=<>`\\x00-\\x20]+"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HTMLRendererParameters is a collection of supplementary parameters tweaking
 | 
				
			||||||
 | 
					// the behavior of various parts of HTML renderer.
 | 
				
			||||||
 | 
					type HTMLRendererParameters struct {
 | 
				
			||||||
 | 
						// Prepend this text to each relative URL.
 | 
				
			||||||
 | 
						AbsolutePrefix string
 | 
				
			||||||
 | 
						// Add this text to each footnote anchor, to ensure uniqueness.
 | 
				
			||||||
 | 
						FootnoteAnchorPrefix string
 | 
				
			||||||
 | 
						// Show this text inside the <a> tag for a footnote return link, if the
 | 
				
			||||||
 | 
						// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
 | 
				
			||||||
 | 
						// <sup>[return]</sup> is used.
 | 
				
			||||||
 | 
						FootnoteReturnLinkContents string
 | 
				
			||||||
 | 
						// If set, add this text to the front of each Heading ID, to ensure
 | 
				
			||||||
 | 
						// uniqueness.
 | 
				
			||||||
 | 
						HeadingIDPrefix string
 | 
				
			||||||
 | 
						// If set, add this text to the back of each Heading ID, to ensure uniqueness.
 | 
				
			||||||
 | 
						HeadingIDSuffix string
 | 
				
			||||||
 | 
						// Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
 | 
				
			||||||
 | 
						// Negative offset is also valid.
 | 
				
			||||||
 | 
						// Resulting levels are clipped between 1 and 6.
 | 
				
			||||||
 | 
						HeadingLevelOffset int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Title string // Document title (used if CompletePage is set)
 | 
				
			||||||
 | 
						CSS   string // Optional CSS file URL (used if CompletePage is set)
 | 
				
			||||||
 | 
						Icon  string // Optional icon file URL (used if CompletePage is set)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Flags HTMLFlags // Flags allow customizing this renderer's behavior
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HTMLRenderer is a type that implements the Renderer interface for HTML output.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Do not create this directly, instead use the NewHTMLRenderer function.
 | 
				
			||||||
 | 
					type HTMLRenderer struct {
 | 
				
			||||||
 | 
						HTMLRendererParameters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						closeTag string // how to end singleton tags: either " />" or ">"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track heading IDs to prevent ID collision in a single generation.
 | 
				
			||||||
 | 
						headingIDs map[string]int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastOutputLen int
 | 
				
			||||||
 | 
						disableTags   int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sr *SPRenderer
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						xhtmlClose = " />"
 | 
				
			||||||
 | 
						htmlClose  = ">"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewHTMLRenderer creates and configures an HTMLRenderer object, which
 | 
				
			||||||
 | 
					// satisfies the Renderer interface.
 | 
				
			||||||
 | 
					func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
 | 
				
			||||||
 | 
						// configure the rendering engine
 | 
				
			||||||
 | 
						closeTag := htmlClose
 | 
				
			||||||
 | 
						if params.Flags&UseXHTML != 0 {
 | 
				
			||||||
 | 
							closeTag = xhtmlClose
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if params.FootnoteReturnLinkContents == "" {
 | 
				
			||||||
 | 
							// U+FE0E is VARIATION SELECTOR-15.
 | 
				
			||||||
 | 
							// It suppresses automatic emoji presentation of the preceding
 | 
				
			||||||
 | 
							// U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS.
 | 
				
			||||||
 | 
							params.FootnoteReturnLinkContents = "<span aria-label='Return'>↩\ufe0e</span>"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &HTMLRenderer{
 | 
				
			||||||
 | 
							HTMLRendererParameters: params,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							closeTag:   closeTag,
 | 
				
			||||||
 | 
							headingIDs: make(map[string]int),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sr: NewSmartypantsRenderer(params.Flags),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isHTMLTag(tag []byte, tagname string) bool {
 | 
				
			||||||
 | 
						found, _ := findHTMLTagPos(tag, tagname)
 | 
				
			||||||
 | 
						return found
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Look for a character, but ignore it when it's in any kind of quotes, it
 | 
				
			||||||
 | 
					// might be JavaScript
 | 
				
			||||||
 | 
					func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
 | 
				
			||||||
 | 
						inSingleQuote := false
 | 
				
			||||||
 | 
						inDoubleQuote := false
 | 
				
			||||||
 | 
						inGraveQuote := false
 | 
				
			||||||
 | 
						i := start
 | 
				
			||||||
 | 
						for i < len(html) {
 | 
				
			||||||
 | 
							switch {
 | 
				
			||||||
 | 
							case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
 | 
				
			||||||
 | 
								return i
 | 
				
			||||||
 | 
							case html[i] == '\'':
 | 
				
			||||||
 | 
								inSingleQuote = !inSingleQuote
 | 
				
			||||||
 | 
							case html[i] == '"':
 | 
				
			||||||
 | 
								inDoubleQuote = !inDoubleQuote
 | 
				
			||||||
 | 
							case html[i] == '`':
 | 
				
			||||||
 | 
								inGraveQuote = !inGraveQuote
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return start
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
 | 
				
			||||||
 | 
						i := 0
 | 
				
			||||||
 | 
						if i < len(tag) && tag[0] != '<' {
 | 
				
			||||||
 | 
							return false, -1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						i++
 | 
				
			||||||
 | 
						i = skipSpace(tag, i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if i < len(tag) && tag[i] == '/' {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						i = skipSpace(tag, i)
 | 
				
			||||||
 | 
						j := 0
 | 
				
			||||||
 | 
						for ; i < len(tag); i, j = i+1, j+1 {
 | 
				
			||||||
 | 
							if j >= len(tagname) {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if strings.ToLower(string(tag[i]))[0] != tagname[j] {
 | 
				
			||||||
 | 
								return false, -1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if i == len(tag) {
 | 
				
			||||||
 | 
							return false, -1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
 | 
				
			||||||
 | 
						if rightAngle >= i {
 | 
				
			||||||
 | 
							return true, rightAngle
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false, -1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func skipSpace(tag []byte, i int) int {
 | 
				
			||||||
 | 
						for i < len(tag) && isspace(tag[i]) {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return i
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isRelativeLink(link []byte) (yes bool) {
 | 
				
			||||||
 | 
						// a tag begin with '#'
 | 
				
			||||||
 | 
						if link[0] == '#' {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// link begin with '/' but not '//', the second maybe a protocol relative link
 | 
				
			||||||
 | 
						if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// only the root '/'
 | 
				
			||||||
 | 
						if len(link) == 1 && link[0] == '/' {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// current directory : begin with "./"
 | 
				
			||||||
 | 
						if bytes.HasPrefix(link, []byte("./")) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// parent directory : begin with "../"
 | 
				
			||||||
 | 
						if bytes.HasPrefix(link, []byte("../")) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
 | 
				
			||||||
 | 
						for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
 | 
				
			||||||
 | 
							tmp := fmt.Sprintf("%s-%d", id, count+1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
 | 
				
			||||||
 | 
								r.headingIDs[id] = count + 1
 | 
				
			||||||
 | 
								id = tmp
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								id = id + "-1"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, found := r.headingIDs[id]; !found {
 | 
				
			||||||
 | 
							r.headingIDs[id] = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte {
 | 
				
			||||||
 | 
						if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
 | 
				
			||||||
 | 
							newDest := r.AbsolutePrefix
 | 
				
			||||||
 | 
							if link[0] != '/' {
 | 
				
			||||||
 | 
								newDest += "/"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							newDest += string(link)
 | 
				
			||||||
 | 
							return []byte(newDest)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return link
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
 | 
				
			||||||
 | 
						if isRelativeLink(link) {
 | 
				
			||||||
 | 
							return attrs
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						val := []string{}
 | 
				
			||||||
 | 
						if flags&NofollowLinks != 0 {
 | 
				
			||||||
 | 
							val = append(val, "nofollow")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if flags&NoreferrerLinks != 0 {
 | 
				
			||||||
 | 
							val = append(val, "noreferrer")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if flags&NoopenerLinks != 0 {
 | 
				
			||||||
 | 
							val = append(val, "noopener")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if flags&HrefTargetBlank != 0 {
 | 
				
			||||||
 | 
							attrs = append(attrs, "target=\"_blank\"")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(val) == 0 {
 | 
				
			||||||
 | 
							return attrs
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
 | 
				
			||||||
 | 
						return append(attrs, attr)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isMailto(link []byte) bool {
 | 
				
			||||||
 | 
						return bytes.HasPrefix(link, []byte("mailto:"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func needSkipLink(flags HTMLFlags, dest []byte) bool {
 | 
				
			||||||
 | 
						if flags&SkipLinks != 0 {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isSmartypantable(node *Node) bool {
 | 
				
			||||||
 | 
						pt := node.Parent.Type
 | 
				
			||||||
 | 
						return pt != Link && pt != CodeBlock && pt != Code
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func appendLanguageAttr(attrs []string, info []byte) []string {
 | 
				
			||||||
 | 
						if len(info) == 0 {
 | 
				
			||||||
 | 
							return attrs
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						endOfLang := bytes.IndexAny(info, "\t ")
 | 
				
			||||||
 | 
						if endOfLang < 0 {
 | 
				
			||||||
 | 
							endOfLang = len(info)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
 | 
				
			||||||
 | 
						w.Write(name)
 | 
				
			||||||
 | 
						if len(attrs) > 0 {
 | 
				
			||||||
 | 
							w.Write(spaceBytes)
 | 
				
			||||||
 | 
							w.Write([]byte(strings.Join(attrs, " ")))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.Write(gtBytes)
 | 
				
			||||||
 | 
						r.lastOutputLen = 1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func footnoteRef(prefix string, node *Node) []byte {
 | 
				
			||||||
 | 
						urlFrag := prefix + string(slugify(node.Destination))
 | 
				
			||||||
 | 
						anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
 | 
				
			||||||
 | 
						return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func footnoteItem(prefix string, slug []byte) []byte {
 | 
				
			||||||
 | 
						return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
 | 
				
			||||||
 | 
						const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
 | 
				
			||||||
 | 
						return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func itemOpenCR(node *Node) bool {
 | 
				
			||||||
 | 
						if node.Prev == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ld := node.Parent.ListData
 | 
				
			||||||
 | 
						return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func skipParagraphTags(node *Node) bool {
 | 
				
			||||||
 | 
						grandparent := node.Parent.Parent
 | 
				
			||||||
 | 
						if grandparent == nil || grandparent.Type != List {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
 | 
				
			||||||
 | 
						return grandparent.Type == List && tightOrTerm
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func cellAlignment(align CellAlignFlags) string {
 | 
				
			||||||
 | 
						switch align {
 | 
				
			||||||
 | 
						case TableAlignmentLeft:
 | 
				
			||||||
 | 
							return "left"
 | 
				
			||||||
 | 
						case TableAlignmentRight:
 | 
				
			||||||
 | 
							return "right"
 | 
				
			||||||
 | 
						case TableAlignmentCenter:
 | 
				
			||||||
 | 
							return "center"
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) out(w io.Writer, text []byte) {
 | 
				
			||||||
 | 
						if r.disableTags > 0 {
 | 
				
			||||||
 | 
							w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							w.Write(text)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r.lastOutputLen = len(text)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) cr(w io.Writer) {
 | 
				
			||||||
 | 
						if r.lastOutputLen > 0 {
 | 
				
			||||||
 | 
							r.out(w, nlBytes)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						nlBytes    = []byte{'\n'}
 | 
				
			||||||
 | 
						gtBytes    = []byte{'>'}
 | 
				
			||||||
 | 
						spaceBytes = []byte{' '}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						brTag              = []byte("<br>")
 | 
				
			||||||
 | 
						brXHTMLTag         = []byte("<br />")
 | 
				
			||||||
 | 
						emTag              = []byte("<em>")
 | 
				
			||||||
 | 
						emCloseTag         = []byte("</em>")
 | 
				
			||||||
 | 
						strongTag          = []byte("<strong>")
 | 
				
			||||||
 | 
						strongCloseTag     = []byte("</strong>")
 | 
				
			||||||
 | 
						delTag             = []byte("<del>")
 | 
				
			||||||
 | 
						delCloseTag        = []byte("</del>")
 | 
				
			||||||
 | 
						ttTag              = []byte("<tt>")
 | 
				
			||||||
 | 
						ttCloseTag         = []byte("</tt>")
 | 
				
			||||||
 | 
						aTag               = []byte("<a")
 | 
				
			||||||
 | 
						aCloseTag          = []byte("</a>")
 | 
				
			||||||
 | 
						preTag             = []byte("<pre>")
 | 
				
			||||||
 | 
						preCloseTag        = []byte("</pre>")
 | 
				
			||||||
 | 
						codeTag            = []byte("<code>")
 | 
				
			||||||
 | 
						codeCloseTag       = []byte("</code>")
 | 
				
			||||||
 | 
						pTag               = []byte("<p>")
 | 
				
			||||||
 | 
						pCloseTag          = []byte("</p>")
 | 
				
			||||||
 | 
						blockquoteTag      = []byte("<blockquote>")
 | 
				
			||||||
 | 
						blockquoteCloseTag = []byte("</blockquote>")
 | 
				
			||||||
 | 
						hrTag              = []byte("<hr>")
 | 
				
			||||||
 | 
						hrXHTMLTag         = []byte("<hr />")
 | 
				
			||||||
 | 
						ulTag              = []byte("<ul>")
 | 
				
			||||||
 | 
						ulCloseTag         = []byte("</ul>")
 | 
				
			||||||
 | 
						olTag              = []byte("<ol>")
 | 
				
			||||||
 | 
						olCloseTag         = []byte("</ol>")
 | 
				
			||||||
 | 
						dlTag              = []byte("<dl>")
 | 
				
			||||||
 | 
						dlCloseTag         = []byte("</dl>")
 | 
				
			||||||
 | 
						liTag              = []byte("<li>")
 | 
				
			||||||
 | 
						liCloseTag         = []byte("</li>")
 | 
				
			||||||
 | 
						ddTag              = []byte("<dd>")
 | 
				
			||||||
 | 
						ddCloseTag         = []byte("</dd>")
 | 
				
			||||||
 | 
						dtTag              = []byte("<dt>")
 | 
				
			||||||
 | 
						dtCloseTag         = []byte("</dt>")
 | 
				
			||||||
 | 
						tableTag           = []byte("<table>")
 | 
				
			||||||
 | 
						tableCloseTag      = []byte("</table>")
 | 
				
			||||||
 | 
						tdTag              = []byte("<td")
 | 
				
			||||||
 | 
						tdCloseTag         = []byte("</td>")
 | 
				
			||||||
 | 
						thTag              = []byte("<th")
 | 
				
			||||||
 | 
						thCloseTag         = []byte("</th>")
 | 
				
			||||||
 | 
						theadTag           = []byte("<thead>")
 | 
				
			||||||
 | 
						theadCloseTag      = []byte("</thead>")
 | 
				
			||||||
 | 
						tbodyTag           = []byte("<tbody>")
 | 
				
			||||||
 | 
						tbodyCloseTag      = []byte("</tbody>")
 | 
				
			||||||
 | 
						trTag              = []byte("<tr>")
 | 
				
			||||||
 | 
						trCloseTag         = []byte("</tr>")
 | 
				
			||||||
 | 
						h1Tag              = []byte("<h1")
 | 
				
			||||||
 | 
						h1CloseTag         = []byte("</h1>")
 | 
				
			||||||
 | 
						h2Tag              = []byte("<h2")
 | 
				
			||||||
 | 
						h2CloseTag         = []byte("</h2>")
 | 
				
			||||||
 | 
						h3Tag              = []byte("<h3")
 | 
				
			||||||
 | 
						h3CloseTag         = []byte("</h3>")
 | 
				
			||||||
 | 
						h4Tag              = []byte("<h4")
 | 
				
			||||||
 | 
						h4CloseTag         = []byte("</h4>")
 | 
				
			||||||
 | 
						h5Tag              = []byte("<h5")
 | 
				
			||||||
 | 
						h5CloseTag         = []byte("</h5>")
 | 
				
			||||||
 | 
						h6Tag              = []byte("<h6")
 | 
				
			||||||
 | 
						h6CloseTag         = []byte("</h6>")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						footnotesDivBytes      = []byte("\n<div class=\"footnotes\">\n\n")
 | 
				
			||||||
 | 
						footnotesCloseDivBytes = []byte("\n</div>\n")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func headingTagsFromLevel(level int) ([]byte, []byte) {
 | 
				
			||||||
 | 
						if level <= 1 {
 | 
				
			||||||
 | 
							return h1Tag, h1CloseTag
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						switch level {
 | 
				
			||||||
 | 
						case 2:
 | 
				
			||||||
 | 
							return h2Tag, h2CloseTag
 | 
				
			||||||
 | 
						case 3:
 | 
				
			||||||
 | 
							return h3Tag, h3CloseTag
 | 
				
			||||||
 | 
						case 4:
 | 
				
			||||||
 | 
							return h4Tag, h4CloseTag
 | 
				
			||||||
 | 
						case 5:
 | 
				
			||||||
 | 
							return h5Tag, h5CloseTag
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return h6Tag, h6CloseTag
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) outHRTag(w io.Writer) {
 | 
				
			||||||
 | 
						if r.Flags&UseXHTML == 0 {
 | 
				
			||||||
 | 
							r.out(w, hrTag)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							r.out(w, hrXHTMLTag)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RenderNode is a default renderer of a single node of a syntax tree. For
 | 
				
			||||||
 | 
					// block nodes it will be called twice: first time with entering=true, second
 | 
				
			||||||
 | 
					// time with entering=false, so that it could know when it's working on an open
 | 
				
			||||||
 | 
					// tag and when on close. It writes the result to w.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The return value is a way to tell the calling walker to adjust its walk
 | 
				
			||||||
 | 
					// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
 | 
				
			||||||
 | 
					// can ask the walker to skip a subtree of this node by returning SkipChildren.
 | 
				
			||||||
 | 
					// The typical behavior is to return GoToNext, which asks for the usual
 | 
				
			||||||
 | 
					// traversal to the next node.
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
 | 
				
			||||||
 | 
						attrs := []string{}
 | 
				
			||||||
 | 
						switch node.Type {
 | 
				
			||||||
 | 
						case Text:
 | 
				
			||||||
 | 
							if r.Flags&Smartypants != 0 {
 | 
				
			||||||
 | 
								var tmp bytes.Buffer
 | 
				
			||||||
 | 
								escapeHTML(&tmp, node.Literal)
 | 
				
			||||||
 | 
								r.sr.Process(w, tmp.Bytes())
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if node.Parent.Type == Link {
 | 
				
			||||||
 | 
									escLink(w, node.Literal)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									escapeHTML(w, node.Literal)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case Softbreak:
 | 
				
			||||||
 | 
							r.cr(w)
 | 
				
			||||||
 | 
							// TODO: make it configurable via out(renderer.softbreak)
 | 
				
			||||||
 | 
						case Hardbreak:
 | 
				
			||||||
 | 
							if r.Flags&UseXHTML == 0 {
 | 
				
			||||||
 | 
								r.out(w, brTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, brXHTMLTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r.cr(w)
 | 
				
			||||||
 | 
						case Emph:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								r.out(w, emTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, emCloseTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case Strong:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								r.out(w, strongTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, strongCloseTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case Del:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								r.out(w, delTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, delCloseTag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case HTMLSpan:
 | 
				
			||||||
 | 
							if r.Flags&SkipHTML != 0 {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r.out(w, node.Literal)
 | 
				
			||||||
 | 
						case Link:
 | 
				
			||||||
 | 
							// mark it but don't link it if it is not a safe link: no smartypants
 | 
				
			||||||
 | 
							dest := node.LinkData.Destination
 | 
				
			||||||
 | 
							if needSkipLink(r.Flags, dest) {
 | 
				
			||||||
 | 
								if entering {
 | 
				
			||||||
 | 
									r.out(w, ttTag)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									r.out(w, ttCloseTag)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if entering {
 | 
				
			||||||
 | 
									dest = r.addAbsPrefix(dest)
 | 
				
			||||||
 | 
									var hrefBuf bytes.Buffer
 | 
				
			||||||
 | 
									hrefBuf.WriteString("href=\"")
 | 
				
			||||||
 | 
									escLink(&hrefBuf, dest)
 | 
				
			||||||
 | 
									hrefBuf.WriteByte('"')
 | 
				
			||||||
 | 
									attrs = append(attrs, hrefBuf.String())
 | 
				
			||||||
 | 
									if node.NoteID != 0 {
 | 
				
			||||||
 | 
										r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									attrs = appendLinkAttrs(attrs, r.Flags, dest)
 | 
				
			||||||
 | 
									if len(node.LinkData.Title) > 0 {
 | 
				
			||||||
 | 
										var titleBuff bytes.Buffer
 | 
				
			||||||
 | 
										titleBuff.WriteString("title=\"")
 | 
				
			||||||
 | 
										escapeHTML(&titleBuff, node.LinkData.Title)
 | 
				
			||||||
 | 
										titleBuff.WriteByte('"')
 | 
				
			||||||
 | 
										attrs = append(attrs, titleBuff.String())
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									r.tag(w, aTag, attrs)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									if node.NoteID != 0 {
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									r.out(w, aCloseTag)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case Image:
 | 
				
			||||||
 | 
							if r.Flags&SkipImages != 0 {
 | 
				
			||||||
 | 
								return SkipChildren
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								dest := node.LinkData.Destination
 | 
				
			||||||
 | 
								dest = r.addAbsPrefix(dest)
 | 
				
			||||||
 | 
								if r.disableTags == 0 {
 | 
				
			||||||
 | 
									//if options.safe && potentiallyUnsafe(dest) {
 | 
				
			||||||
 | 
									//out(w, `<img src="" alt="`)
 | 
				
			||||||
 | 
									//} else {
 | 
				
			||||||
 | 
									r.out(w, []byte(`<img src="`))
 | 
				
			||||||
 | 
									escLink(w, dest)
 | 
				
			||||||
 | 
									r.out(w, []byte(`" alt="`))
 | 
				
			||||||
 | 
									//}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r.disableTags++
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.disableTags--
 | 
				
			||||||
 | 
								if r.disableTags == 0 {
 | 
				
			||||||
 | 
									if node.LinkData.Title != nil {
 | 
				
			||||||
 | 
										r.out(w, []byte(`" title="`))
 | 
				
			||||||
 | 
										escapeHTML(w, node.LinkData.Title)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									r.out(w, []byte(`" />`))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case Code:
 | 
				
			||||||
 | 
							r.out(w, codeTag)
 | 
				
			||||||
 | 
							escapeAllHTML(w, node.Literal)
 | 
				
			||||||
 | 
							r.out(w, codeCloseTag)
 | 
				
			||||||
 | 
						case Document:
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						case Paragraph:
 | 
				
			||||||
 | 
							if skipParagraphTags(node) {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								// TODO: untangle this clusterfuck about when the newlines need
 | 
				
			||||||
 | 
								// to be added and when not.
 | 
				
			||||||
 | 
								if node.Prev != nil {
 | 
				
			||||||
 | 
									switch node.Prev.Type {
 | 
				
			||||||
 | 
									case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
 | 
				
			||||||
 | 
										r.cr(w)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if node.Parent.Type == BlockQuote && node.Prev == nil {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r.out(w, pTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, pCloseTag)
 | 
				
			||||||
 | 
								if !(node.Parent.Type == Item && node.Next == nil) {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case BlockQuote:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
								r.out(w, blockquoteTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, blockquoteCloseTag)
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case HTMLBlock:
 | 
				
			||||||
 | 
							if r.Flags&SkipHTML != 0 {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r.cr(w)
 | 
				
			||||||
 | 
							r.out(w, node.Literal)
 | 
				
			||||||
 | 
							r.cr(w)
 | 
				
			||||||
 | 
						case Heading:
 | 
				
			||||||
 | 
							headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
 | 
				
			||||||
 | 
							openTag, closeTag := headingTagsFromLevel(headingLevel)
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								if node.IsTitleblock {
 | 
				
			||||||
 | 
									attrs = append(attrs, `class="title"`)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if node.HeadingID != "" {
 | 
				
			||||||
 | 
									id := r.ensureUniqueHeadingID(node.HeadingID)
 | 
				
			||||||
 | 
									if r.HeadingIDPrefix != "" {
 | 
				
			||||||
 | 
										id = r.HeadingIDPrefix + id
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if r.HeadingIDSuffix != "" {
 | 
				
			||||||
 | 
										id = id + r.HeadingIDSuffix
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
								r.tag(w, openTag, attrs)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, closeTag)
 | 
				
			||||||
 | 
								if !(node.Parent.Type == Item && node.Next == nil) {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case HorizontalRule:
 | 
				
			||||||
 | 
							r.cr(w)
 | 
				
			||||||
 | 
							r.outHRTag(w)
 | 
				
			||||||
 | 
							r.cr(w)
 | 
				
			||||||
 | 
						case List:
 | 
				
			||||||
 | 
							openTag := ulTag
 | 
				
			||||||
 | 
							closeTag := ulCloseTag
 | 
				
			||||||
 | 
							if node.ListFlags&ListTypeOrdered != 0 {
 | 
				
			||||||
 | 
								openTag = olTag
 | 
				
			||||||
 | 
								closeTag = olCloseTag
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if node.ListFlags&ListTypeDefinition != 0 {
 | 
				
			||||||
 | 
								openTag = dlTag
 | 
				
			||||||
 | 
								closeTag = dlCloseTag
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								if node.IsFootnotesList {
 | 
				
			||||||
 | 
									r.out(w, footnotesDivBytes)
 | 
				
			||||||
 | 
									r.outHRTag(w)
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
								if node.Parent.Type == Item && node.Parent.Parent.Tight {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r.tag(w, openTag[:len(openTag)-1], attrs)
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, closeTag)
 | 
				
			||||||
 | 
								//cr(w)
 | 
				
			||||||
 | 
								//if node.parent.Type != Item {
 | 
				
			||||||
 | 
								//	cr(w)
 | 
				
			||||||
 | 
								//}
 | 
				
			||||||
 | 
								if node.Parent.Type == Item && node.Next != nil {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if node.IsFootnotesList {
 | 
				
			||||||
 | 
									r.out(w, footnotesCloseDivBytes)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case Item:
 | 
				
			||||||
 | 
							openTag := liTag
 | 
				
			||||||
 | 
							closeTag := liCloseTag
 | 
				
			||||||
 | 
							if node.ListFlags&ListTypeDefinition != 0 {
 | 
				
			||||||
 | 
								openTag = ddTag
 | 
				
			||||||
 | 
								closeTag = ddCloseTag
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if node.ListFlags&ListTypeTerm != 0 {
 | 
				
			||||||
 | 
								openTag = dtTag
 | 
				
			||||||
 | 
								closeTag = dtCloseTag
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								if itemOpenCR(node) {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if node.ListData.RefLink != nil {
 | 
				
			||||||
 | 
									slug := slugify(node.ListData.RefLink)
 | 
				
			||||||
 | 
									r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r.out(w, openTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if node.ListData.RefLink != nil {
 | 
				
			||||||
 | 
									slug := slugify(node.ListData.RefLink)
 | 
				
			||||||
 | 
									if r.Flags&FootnoteReturnLinks != 0 {
 | 
				
			||||||
 | 
										r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r.out(w, closeTag)
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case CodeBlock:
 | 
				
			||||||
 | 
							attrs = appendLanguageAttr(attrs, node.Info)
 | 
				
			||||||
 | 
							r.cr(w)
 | 
				
			||||||
 | 
							r.out(w, preTag)
 | 
				
			||||||
 | 
							r.tag(w, codeTag[:len(codeTag)-1], attrs)
 | 
				
			||||||
 | 
							escapeAllHTML(w, node.Literal)
 | 
				
			||||||
 | 
							r.out(w, codeCloseTag)
 | 
				
			||||||
 | 
							r.out(w, preCloseTag)
 | 
				
			||||||
 | 
							if node.Parent.Type != Item {
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case Table:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
								r.out(w, tableTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, tableCloseTag)
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case TableCell:
 | 
				
			||||||
 | 
							openTag := tdTag
 | 
				
			||||||
 | 
							closeTag := tdCloseTag
 | 
				
			||||||
 | 
							if node.IsHeader {
 | 
				
			||||||
 | 
								openTag = thTag
 | 
				
			||||||
 | 
								closeTag = thCloseTag
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								align := cellAlignment(node.Align)
 | 
				
			||||||
 | 
								if align != "" {
 | 
				
			||||||
 | 
									attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if node.Prev == nil {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r.tag(w, openTag, attrs)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, closeTag)
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case TableHead:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
								r.out(w, theadTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, theadCloseTag)
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case TableBody:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
								r.out(w, tbodyTag)
 | 
				
			||||||
 | 
								// XXX: this is to adhere to a rather silly test. Should fix test.
 | 
				
			||||||
 | 
								if node.FirstChild == nil {
 | 
				
			||||||
 | 
									r.cr(w)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, tbodyCloseTag)
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case TableRow:
 | 
				
			||||||
 | 
							if entering {
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
								r.out(w, trTag)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								r.out(w, trCloseTag)
 | 
				
			||||||
 | 
								r.cr(w)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							panic("Unknown node type " + node.Type.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return GoToNext
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RenderHeader writes HTML document preamble and TOC if requested.
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
 | 
				
			||||||
 | 
						r.writeDocumentHeader(w)
 | 
				
			||||||
 | 
						if r.Flags&TOC != 0 {
 | 
				
			||||||
 | 
							r.writeTOC(w, ast)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RenderFooter writes HTML document footer.
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
 | 
				
			||||||
 | 
						if r.Flags&CompletePage == 0 {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						io.WriteString(w, "\n</body>\n</html>\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) {
 | 
				
			||||||
 | 
						if r.Flags&CompletePage == 0 {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ending := ""
 | 
				
			||||||
 | 
						if r.Flags&UseXHTML != 0 {
 | 
				
			||||||
 | 
							io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
 | 
				
			||||||
 | 
							io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
 | 
				
			||||||
 | 
							io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
 | 
				
			||||||
 | 
							ending = " /"
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							io.WriteString(w, "<!DOCTYPE html>\n")
 | 
				
			||||||
 | 
							io.WriteString(w, "<html>\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						io.WriteString(w, "<head>\n")
 | 
				
			||||||
 | 
						io.WriteString(w, "  <title>")
 | 
				
			||||||
 | 
						if r.Flags&Smartypants != 0 {
 | 
				
			||||||
 | 
							r.sr.Process(w, []byte(r.Title))
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							escapeHTML(w, []byte(r.Title))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						io.WriteString(w, "</title>\n")
 | 
				
			||||||
 | 
						io.WriteString(w, "  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
 | 
				
			||||||
 | 
						io.WriteString(w, Version)
 | 
				
			||||||
 | 
						io.WriteString(w, "\"")
 | 
				
			||||||
 | 
						io.WriteString(w, ending)
 | 
				
			||||||
 | 
						io.WriteString(w, ">\n")
 | 
				
			||||||
 | 
						io.WriteString(w, "  <meta charset=\"utf-8\"")
 | 
				
			||||||
 | 
						io.WriteString(w, ending)
 | 
				
			||||||
 | 
						io.WriteString(w, ">\n")
 | 
				
			||||||
 | 
						if r.CSS != "" {
 | 
				
			||||||
 | 
							io.WriteString(w, "  <link rel=\"stylesheet\" type=\"text/css\" href=\"")
 | 
				
			||||||
 | 
							escapeHTML(w, []byte(r.CSS))
 | 
				
			||||||
 | 
							io.WriteString(w, "\"")
 | 
				
			||||||
 | 
							io.WriteString(w, ending)
 | 
				
			||||||
 | 
							io.WriteString(w, ">\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if r.Icon != "" {
 | 
				
			||||||
 | 
							io.WriteString(w, "  <link rel=\"icon\" type=\"image/x-icon\" href=\"")
 | 
				
			||||||
 | 
							escapeHTML(w, []byte(r.Icon))
 | 
				
			||||||
 | 
							io.WriteString(w, "\"")
 | 
				
			||||||
 | 
							io.WriteString(w, ending)
 | 
				
			||||||
 | 
							io.WriteString(w, ">\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						io.WriteString(w, "</head>\n")
 | 
				
			||||||
 | 
						io.WriteString(w, "<body>\n\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) {
 | 
				
			||||||
 | 
						buf := bytes.Buffer{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						inHeading := false
 | 
				
			||||||
 | 
						tocLevel := 0
 | 
				
			||||||
 | 
						headingCount := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ast.Walk(func(node *Node, entering bool) WalkStatus {
 | 
				
			||||||
 | 
							if node.Type == Heading && !node.HeadingData.IsTitleblock {
 | 
				
			||||||
 | 
								inHeading = entering
 | 
				
			||||||
 | 
								if entering {
 | 
				
			||||||
 | 
									node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
 | 
				
			||||||
 | 
									if node.Level == tocLevel {
 | 
				
			||||||
 | 
										buf.WriteString("</li>\n\n<li>")
 | 
				
			||||||
 | 
									} else if node.Level < tocLevel {
 | 
				
			||||||
 | 
										for node.Level < tocLevel {
 | 
				
			||||||
 | 
											tocLevel--
 | 
				
			||||||
 | 
											buf.WriteString("</li>\n</ul>")
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										buf.WriteString("</li>\n\n<li>")
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										for node.Level > tocLevel {
 | 
				
			||||||
 | 
											tocLevel++
 | 
				
			||||||
 | 
											buf.WriteString("\n<ul>\n<li>")
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
 | 
				
			||||||
 | 
									headingCount++
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									buf.WriteString("</a>")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return GoToNext
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if inHeading {
 | 
				
			||||||
 | 
								return r.RenderNode(&buf, node, entering)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return GoToNext
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for ; tocLevel > 0; tocLevel-- {
 | 
				
			||||||
 | 
							buf.WriteString("</li>\n</ul>")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if buf.Len() > 0 {
 | 
				
			||||||
 | 
							io.WriteString(w, "<nav>\n")
 | 
				
			||||||
 | 
							w.Write(buf.Bytes())
 | 
				
			||||||
 | 
							io.WriteString(w, "\n\n</nav>\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r.lastOutputLen = buf.Len()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,950 @@
 | 
				
			|||||||
 | 
					// Blackfriday Markdown Processor
 | 
				
			||||||
 | 
					// Available at http://github.com/russross/blackfriday
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
				
			||||||
 | 
					// Distributed under the Simplified BSD License.
 | 
				
			||||||
 | 
					// See README.md for details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package blackfriday
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Markdown parsing and processing
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Version string of the package. Appears in the rendered document when
 | 
				
			||||||
 | 
					// CompletePage flag is on.
 | 
				
			||||||
 | 
					const Version = "2.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Extensions is a bitwise or'ed collection of enabled Blackfriday's
 | 
				
			||||||
 | 
					// extensions.
 | 
				
			||||||
 | 
					type Extensions int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// These are the supported markdown parsing extensions.
 | 
				
			||||||
 | 
					// OR these values together to select multiple extensions.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						NoExtensions           Extensions = 0
 | 
				
			||||||
 | 
						NoIntraEmphasis        Extensions = 1 << iota // Ignore emphasis markers inside words
 | 
				
			||||||
 | 
						Tables                                        // Render tables
 | 
				
			||||||
 | 
						FencedCode                                    // Render fenced code blocks
 | 
				
			||||||
 | 
						Autolink                                      // Detect embedded URLs that are not explicitly marked
 | 
				
			||||||
 | 
						Strikethrough                                 // Strikethrough text using ~~test~~
 | 
				
			||||||
 | 
						LaxHTMLBlocks                                 // Loosen up HTML block parsing rules
 | 
				
			||||||
 | 
						SpaceHeadings                                 // Be strict about prefix heading rules
 | 
				
			||||||
 | 
						HardLineBreak                                 // Translate newlines into line breaks
 | 
				
			||||||
 | 
						TabSizeEight                                  // Expand tabs to eight spaces instead of four
 | 
				
			||||||
 | 
						Footnotes                                     // Pandoc-style footnotes
 | 
				
			||||||
 | 
						NoEmptyLineBeforeBlock                        // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
 | 
				
			||||||
 | 
						HeadingIDs                                    // specify heading IDs  with {#id}
 | 
				
			||||||
 | 
						Titleblock                                    // Titleblock ala pandoc
 | 
				
			||||||
 | 
						AutoHeadingIDs                                // Create the heading ID from the text
 | 
				
			||||||
 | 
						BackslashLineBreak                            // Translate trailing backslashes into line breaks
 | 
				
			||||||
 | 
						DefinitionLists                               // Render definition lists
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants |
 | 
				
			||||||
 | 
							SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
 | 
				
			||||||
 | 
							Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
 | 
				
			||||||
 | 
							BackslashLineBreak | DefinitionLists
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListType contains bitwise or'ed flags for list and list item objects.
 | 
				
			||||||
 | 
					type ListType int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// These are the possible flag values for the ListItem renderer.
 | 
				
			||||||
 | 
					// Multiple flag values may be ORed together.
 | 
				
			||||||
 | 
					// These are mostly of interest if you are writing a new output format.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ListTypeOrdered ListType = 1 << iota
 | 
				
			||||||
 | 
						ListTypeDefinition
 | 
				
			||||||
 | 
						ListTypeTerm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ListItemContainsBlock
 | 
				
			||||||
 | 
						ListItemBeginningOfList // TODO: figure out if this is of any use now
 | 
				
			||||||
 | 
						ListItemEndOfList
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CellAlignFlags holds a type of alignment in a table cell.
 | 
				
			||||||
 | 
					type CellAlignFlags int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// These are the possible flag values for the table cell renderer.
 | 
				
			||||||
 | 
					// Only a single one of these values will be used; they are not ORed together.
 | 
				
			||||||
 | 
					// These are mostly of interest if you are writing a new output format.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						TableAlignmentLeft CellAlignFlags = 1 << iota
 | 
				
			||||||
 | 
						TableAlignmentRight
 | 
				
			||||||
 | 
						TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The size of a tab stop.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						TabSizeDefault = 4
 | 
				
			||||||
 | 
						TabSizeDouble  = 8
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// blockTags is a set of tags that are recognized as HTML block tags.
 | 
				
			||||||
 | 
					// Any of these can be included in markdown text without special escaping.
 | 
				
			||||||
 | 
					var blockTags = map[string]struct{}{
 | 
				
			||||||
 | 
						"blockquote": {},
 | 
				
			||||||
 | 
						"del":        {},
 | 
				
			||||||
 | 
						"div":        {},
 | 
				
			||||||
 | 
						"dl":         {},
 | 
				
			||||||
 | 
						"fieldset":   {},
 | 
				
			||||||
 | 
						"form":       {},
 | 
				
			||||||
 | 
						"h1":         {},
 | 
				
			||||||
 | 
						"h2":         {},
 | 
				
			||||||
 | 
						"h3":         {},
 | 
				
			||||||
 | 
						"h4":         {},
 | 
				
			||||||
 | 
						"h5":         {},
 | 
				
			||||||
 | 
						"h6":         {},
 | 
				
			||||||
 | 
						"iframe":     {},
 | 
				
			||||||
 | 
						"ins":        {},
 | 
				
			||||||
 | 
						"math":       {},
 | 
				
			||||||
 | 
						"noscript":   {},
 | 
				
			||||||
 | 
						"ol":         {},
 | 
				
			||||||
 | 
						"pre":        {},
 | 
				
			||||||
 | 
						"p":          {},
 | 
				
			||||||
 | 
						"script":     {},
 | 
				
			||||||
 | 
						"style":      {},
 | 
				
			||||||
 | 
						"table":      {},
 | 
				
			||||||
 | 
						"ul":         {},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HTML5
 | 
				
			||||||
 | 
						"address":    {},
 | 
				
			||||||
 | 
						"article":    {},
 | 
				
			||||||
 | 
						"aside":      {},
 | 
				
			||||||
 | 
						"canvas":     {},
 | 
				
			||||||
 | 
						"figcaption": {},
 | 
				
			||||||
 | 
						"figure":     {},
 | 
				
			||||||
 | 
						"footer":     {},
 | 
				
			||||||
 | 
						"header":     {},
 | 
				
			||||||
 | 
						"hgroup":     {},
 | 
				
			||||||
 | 
						"main":       {},
 | 
				
			||||||
 | 
						"nav":        {},
 | 
				
			||||||
 | 
						"output":     {},
 | 
				
			||||||
 | 
						"progress":   {},
 | 
				
			||||||
 | 
						"section":    {},
 | 
				
			||||||
 | 
						"video":      {},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Renderer is the rendering interface. This is mostly of interest if you are
 | 
				
			||||||
 | 
					// implementing a new rendering format.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Only an HTML implementation is provided in this repository, see the README
 | 
				
			||||||
 | 
					// for external implementations.
 | 
				
			||||||
 | 
					type Renderer interface {
 | 
				
			||||||
 | 
						// RenderNode is the main rendering method. It will be called once for
 | 
				
			||||||
 | 
						// every leaf node and twice for every non-leaf node (first with
 | 
				
			||||||
 | 
						// entering=true, then with entering=false). The method should write its
 | 
				
			||||||
 | 
						// rendition of the node to the supplied writer w.
 | 
				
			||||||
 | 
						RenderNode(w io.Writer, node *Node, entering bool) WalkStatus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// RenderHeader is a method that allows the renderer to produce some
 | 
				
			||||||
 | 
						// content preceding the main body of the output document. The header is
 | 
				
			||||||
 | 
						// understood in the broad sense here. For example, the default HTML
 | 
				
			||||||
 | 
						// renderer will write not only the HTML document preamble, but also the
 | 
				
			||||||
 | 
						// table of contents if it was requested.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// The method will be passed an entire document tree, in case a particular
 | 
				
			||||||
 | 
						// implementation needs to inspect it to produce output.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// The output should be written to the supplied writer w. If your
 | 
				
			||||||
 | 
						// implementation has no header to write, supply an empty implementation.
 | 
				
			||||||
 | 
						RenderHeader(w io.Writer, ast *Node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// RenderFooter is a symmetric counterpart of RenderHeader.
 | 
				
			||||||
 | 
						RenderFooter(w io.Writer, ast *Node)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Callback functions for inline parsing. One such function is defined
 | 
				
			||||||
 | 
					// for each character that triggers a response when parsing inline data.
 | 
				
			||||||
 | 
					type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Markdown is a type that holds extensions and the runtime state used by
 | 
				
			||||||
 | 
					// Parse, and the renderer. You can not use it directly, construct it with New.
 | 
				
			||||||
 | 
					type Markdown struct {
 | 
				
			||||||
 | 
						renderer          Renderer
 | 
				
			||||||
 | 
						referenceOverride ReferenceOverrideFunc
 | 
				
			||||||
 | 
						refs              map[string]*reference
 | 
				
			||||||
 | 
						inlineCallback    [256]inlineParser
 | 
				
			||||||
 | 
						extensions        Extensions
 | 
				
			||||||
 | 
						nesting           int
 | 
				
			||||||
 | 
						maxNesting        int
 | 
				
			||||||
 | 
						insideLink        bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Footnotes need to be ordered as well as available to quickly check for
 | 
				
			||||||
 | 
						// presence. If a ref is also a footnote, it's stored both in refs and here
 | 
				
			||||||
 | 
						// in notes. Slice is nil if footnotes not enabled.
 | 
				
			||||||
 | 
						notes []*reference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						doc                  *Node
 | 
				
			||||||
 | 
						tip                  *Node // = doc
 | 
				
			||||||
 | 
						oldTip               *Node
 | 
				
			||||||
 | 
						lastMatchedContainer *Node // = doc
 | 
				
			||||||
 | 
						allClosed            bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
 | 
				
			||||||
 | 
						if p.referenceOverride != nil {
 | 
				
			||||||
 | 
							r, overridden := p.referenceOverride(refid)
 | 
				
			||||||
 | 
							if overridden {
 | 
				
			||||||
 | 
								if r == nil {
 | 
				
			||||||
 | 
									return nil, false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return &reference{
 | 
				
			||||||
 | 
									link:     []byte(r.Link),
 | 
				
			||||||
 | 
									title:    []byte(r.Title),
 | 
				
			||||||
 | 
									noteID:   0,
 | 
				
			||||||
 | 
									hasBlock: false,
 | 
				
			||||||
 | 
									text:     []byte(r.Text)}, true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// refs are case insensitive
 | 
				
			||||||
 | 
						ref, found = p.refs[strings.ToLower(refid)]
 | 
				
			||||||
 | 
						return ref, found
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Markdown) finalize(block *Node) {
 | 
				
			||||||
 | 
						above := block.Parent
 | 
				
			||||||
 | 
						block.open = false
 | 
				
			||||||
 | 
						p.tip = above
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
 | 
				
			||||||
 | 
						return p.addExistingChild(NewNode(node), offset)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
 | 
				
			||||||
 | 
						for !p.tip.canContain(node.Type) {
 | 
				
			||||||
 | 
							p.finalize(p.tip)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						p.tip.AppendChild(node)
 | 
				
			||||||
 | 
						p.tip = node
 | 
				
			||||||
 | 
						return node
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Markdown) closeUnmatchedBlocks() {
 | 
				
			||||||
 | 
						if !p.allClosed {
 | 
				
			||||||
 | 
							for p.oldTip != p.lastMatchedContainer {
 | 
				
			||||||
 | 
								parent := p.oldTip.Parent
 | 
				
			||||||
 | 
								p.finalize(p.oldTip)
 | 
				
			||||||
 | 
								p.oldTip = parent
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							p.allClosed = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Public interface
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reference represents the details of a link.
 | 
				
			||||||
 | 
					// See the documentation in Options for more details on use-case.
 | 
				
			||||||
 | 
					type Reference struct {
 | 
				
			||||||
 | 
						// Link is usually the URL the reference points to.
 | 
				
			||||||
 | 
						Link string
 | 
				
			||||||
 | 
						// Title is the alternate text describing the link in more detail.
 | 
				
			||||||
 | 
						Title string
 | 
				
			||||||
 | 
						// Text is the optional text to override the ref with if the syntax used was
 | 
				
			||||||
 | 
						// [refid][]
 | 
				
			||||||
 | 
						Text string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReferenceOverrideFunc is expected to be called with a reference string and
 | 
				
			||||||
 | 
					// return either a valid Reference type that the reference string maps to or
 | 
				
			||||||
 | 
					// nil. If overridden is false, the default reference logic will be executed.
 | 
				
			||||||
 | 
					// See the documentation in Options for more details on use-case.
 | 
				
			||||||
 | 
					type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// New constructs a Markdown processor. You can use the same With* functions as
 | 
				
			||||||
 | 
					// for Run() to customize parser's behavior and the renderer.
 | 
				
			||||||
 | 
					func New(opts ...Option) *Markdown {
 | 
				
			||||||
 | 
						var p Markdown
 | 
				
			||||||
 | 
						for _, opt := range opts {
 | 
				
			||||||
 | 
							opt(&p)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						p.refs = make(map[string]*reference)
 | 
				
			||||||
 | 
						p.maxNesting = 16
 | 
				
			||||||
 | 
						p.insideLink = false
 | 
				
			||||||
 | 
						docNode := NewNode(Document)
 | 
				
			||||||
 | 
						p.doc = docNode
 | 
				
			||||||
 | 
						p.tip = docNode
 | 
				
			||||||
 | 
						p.oldTip = docNode
 | 
				
			||||||
 | 
						p.lastMatchedContainer = docNode
 | 
				
			||||||
 | 
						p.allClosed = true
 | 
				
			||||||
 | 
						// register inline parsers
 | 
				
			||||||
 | 
						p.inlineCallback[' '] = maybeLineBreak
 | 
				
			||||||
 | 
						p.inlineCallback['*'] = emphasis
 | 
				
			||||||
 | 
						p.inlineCallback['_'] = emphasis
 | 
				
			||||||
 | 
						if p.extensions&Strikethrough != 0 {
 | 
				
			||||||
 | 
							p.inlineCallback['~'] = emphasis
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						p.inlineCallback['`'] = codeSpan
 | 
				
			||||||
 | 
						p.inlineCallback['\n'] = lineBreak
 | 
				
			||||||
 | 
						p.inlineCallback['['] = link
 | 
				
			||||||
 | 
						p.inlineCallback['<'] = leftAngle
 | 
				
			||||||
 | 
						p.inlineCallback['\\'] = escape
 | 
				
			||||||
 | 
						p.inlineCallback['&'] = entity
 | 
				
			||||||
 | 
						p.inlineCallback['!'] = maybeImage
 | 
				
			||||||
 | 
						p.inlineCallback['^'] = maybeInlineFootnote
 | 
				
			||||||
 | 
						if p.extensions&Autolink != 0 {
 | 
				
			||||||
 | 
							p.inlineCallback['h'] = maybeAutoLink
 | 
				
			||||||
 | 
							p.inlineCallback['m'] = maybeAutoLink
 | 
				
			||||||
 | 
							p.inlineCallback['f'] = maybeAutoLink
 | 
				
			||||||
 | 
							p.inlineCallback['H'] = maybeAutoLink
 | 
				
			||||||
 | 
							p.inlineCallback['M'] = maybeAutoLink
 | 
				
			||||||
 | 
							p.inlineCallback['F'] = maybeAutoLink
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if p.extensions&Footnotes != 0 {
 | 
				
			||||||
 | 
							p.notes = make([]*reference, 0)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &p
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Option customizes the Markdown processor's default behavior.
 | 
				
			||||||
 | 
					type Option func(*Markdown)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithRenderer allows you to override the default renderer.
 | 
				
			||||||
 | 
					func WithRenderer(r Renderer) Option {
 | 
				
			||||||
 | 
						return func(p *Markdown) {
 | 
				
			||||||
 | 
							p.renderer = r
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithExtensions allows you to pick some of the many extensions provided by
 | 
				
			||||||
 | 
					// Blackfriday. You can bitwise OR them.
 | 
				
			||||||
 | 
					func WithExtensions(e Extensions) Option {
 | 
				
			||||||
 | 
						return func(p *Markdown) {
 | 
				
			||||||
 | 
							p.extensions = e
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithNoExtensions turns off all extensions and custom behavior.
 | 
				
			||||||
 | 
					func WithNoExtensions() Option {
 | 
				
			||||||
 | 
						return func(p *Markdown) {
 | 
				
			||||||
 | 
							p.extensions = NoExtensions
 | 
				
			||||||
 | 
							p.renderer = NewHTMLRenderer(HTMLRendererParameters{
 | 
				
			||||||
 | 
								Flags: HTMLFlagsNone,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithRefOverride sets an optional function callback that is called every
 | 
				
			||||||
 | 
					// time a reference is resolved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// In Markdown, the link reference syntax can be made to resolve a link to
 | 
				
			||||||
 | 
					// a reference instead of an inline URL, in one of the following ways:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  * [link text][refid]
 | 
				
			||||||
 | 
					//  * [refid][]
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Usually, the refid is defined at the bottom of the Markdown document. If
 | 
				
			||||||
 | 
					// this override function is provided, the refid is passed to the override
 | 
				
			||||||
 | 
					// function first, before consulting the defined refids at the bottom. If
 | 
				
			||||||
 | 
					// the override function indicates an override did not occur, the refids at
 | 
				
			||||||
 | 
					// the bottom will be used to fill in the link details.
 | 
				
			||||||
 | 
					func WithRefOverride(o ReferenceOverrideFunc) Option {
 | 
				
			||||||
 | 
						return func(p *Markdown) {
 | 
				
			||||||
 | 
							p.referenceOverride = o
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Run is the main entry point to Blackfriday. It parses and renders a
 | 
				
			||||||
 | 
					// block of markdown-encoded text.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The simplest invocation of Run takes one argument, input:
 | 
				
			||||||
 | 
					//     output := Run(input)
 | 
				
			||||||
 | 
					// This will parse the input with CommonExtensions enabled and render it with
 | 
				
			||||||
 | 
					// the default HTMLRenderer (with CommonHTMLFlags).
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Variadic arguments opts can customize the default behavior. Since Markdown
 | 
				
			||||||
 | 
					// type does not contain exported fields, you can not use it directly. Instead,
 | 
				
			||||||
 | 
					// use the With* functions. For example, this will call the most basic
 | 
				
			||||||
 | 
					// functionality, with no extensions:
 | 
				
			||||||
 | 
					//     output := Run(input, WithNoExtensions())
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// You can use any number of With* arguments, even contradicting ones. They
 | 
				
			||||||
 | 
					// will be applied in order of appearance and the latter will override the
 | 
				
			||||||
 | 
					// former:
 | 
				
			||||||
 | 
					//     output := Run(input, WithNoExtensions(), WithExtensions(exts),
 | 
				
			||||||
 | 
					//         WithRenderer(yourRenderer))
 | 
				
			||||||
 | 
					func Run(input []byte, opts ...Option) []byte {
 | 
				
			||||||
 | 
						r := NewHTMLRenderer(HTMLRendererParameters{
 | 
				
			||||||
 | 
							Flags: CommonHTMLFlags,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)}
 | 
				
			||||||
 | 
						optList = append(optList, opts...)
 | 
				
			||||||
 | 
						parser := New(optList...)
 | 
				
			||||||
 | 
						ast := parser.Parse(input)
 | 
				
			||||||
 | 
						var buf bytes.Buffer
 | 
				
			||||||
 | 
						parser.renderer.RenderHeader(&buf, ast)
 | 
				
			||||||
 | 
						ast.Walk(func(node *Node, entering bool) WalkStatus {
 | 
				
			||||||
 | 
							return parser.renderer.RenderNode(&buf, node, entering)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						parser.renderer.RenderFooter(&buf, ast)
 | 
				
			||||||
 | 
						return buf.Bytes()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parse is an entry point to the parsing part of Blackfriday. It takes an
 | 
				
			||||||
 | 
					// input markdown document and produces a syntax tree for its contents. This
 | 
				
			||||||
 | 
					// tree can then be rendered with a default or custom renderer, or
 | 
				
			||||||
 | 
					// analyzed/transformed by the caller to whatever non-standard needs they have.
 | 
				
			||||||
 | 
					// The return value is the root node of the syntax tree.
 | 
				
			||||||
 | 
					func (p *Markdown) Parse(input []byte) *Node {
 | 
				
			||||||
 | 
						p.block(input)
 | 
				
			||||||
 | 
						// Walk the tree and finish up some of unfinished blocks
 | 
				
			||||||
 | 
						for p.tip != nil {
 | 
				
			||||||
 | 
							p.finalize(p.tip)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Walk the tree again and process inline markdown in each block
 | 
				
			||||||
 | 
						p.doc.Walk(func(node *Node, entering bool) WalkStatus {
 | 
				
			||||||
 | 
							if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
 | 
				
			||||||
 | 
								p.inline(node, node.content)
 | 
				
			||||||
 | 
								node.content = nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return GoToNext
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						p.parseRefsToAST()
 | 
				
			||||||
 | 
						return p.doc
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Markdown) parseRefsToAST() {
 | 
				
			||||||
 | 
						if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						p.tip = p.doc
 | 
				
			||||||
 | 
						block := p.addBlock(List, nil)
 | 
				
			||||||
 | 
						block.IsFootnotesList = true
 | 
				
			||||||
 | 
						block.ListFlags = ListTypeOrdered
 | 
				
			||||||
 | 
						flags := ListItemBeginningOfList
 | 
				
			||||||
 | 
						// Note: this loop is intentionally explicit, not range-form. This is
 | 
				
			||||||
 | 
						// because the body of the loop will append nested footnotes to p.notes and
 | 
				
			||||||
 | 
						// we need to process those late additions. Range form would only walk over
 | 
				
			||||||
 | 
						// the fixed initial set.
 | 
				
			||||||
 | 
						for i := 0; i < len(p.notes); i++ {
 | 
				
			||||||
 | 
							ref := p.notes[i]
 | 
				
			||||||
 | 
							p.addExistingChild(ref.footnote, 0)
 | 
				
			||||||
 | 
							block := ref.footnote
 | 
				
			||||||
 | 
							block.ListFlags = flags | ListTypeOrdered
 | 
				
			||||||
 | 
							block.RefLink = ref.link
 | 
				
			||||||
 | 
							if ref.hasBlock {
 | 
				
			||||||
 | 
								flags |= ListItemContainsBlock
 | 
				
			||||||
 | 
								p.block(ref.title)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								p.inline(block, ref.title)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							flags &^= ListItemBeginningOfList | ListItemContainsBlock
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						above := block.Parent
 | 
				
			||||||
 | 
						finalizeList(block)
 | 
				
			||||||
 | 
						p.tip = above
 | 
				
			||||||
 | 
						block.Walk(func(node *Node, entering bool) WalkStatus {
 | 
				
			||||||
 | 
							if node.Type == Paragraph || node.Type == Heading {
 | 
				
			||||||
 | 
								p.inline(node, node.content)
 | 
				
			||||||
 | 
								node.content = nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return GoToNext
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Link references
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This section implements support for references that (usually) appear
 | 
				
			||||||
 | 
					// as footnotes in a document, and can be referenced anywhere in the document.
 | 
				
			||||||
 | 
					// The basic format is:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    [1]: http://www.google.com/ "Google"
 | 
				
			||||||
 | 
					//    [2]: http://www.github.com/ "Github"
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Anywhere in the document, the reference can be linked by referring to its
 | 
				
			||||||
 | 
					// label, i.e., 1 and 2 in this example, as in:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    This library is hosted on [Github][2], a git hosting site.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Actual footnotes as specified in Pandoc and supported by some other Markdown
 | 
				
			||||||
 | 
					// libraries such as php-markdown are also taken care of. They look like this:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    This sentence needs a bit of further explanation.[^note]
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    [^note]: This is the explanation.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Footnotes should be placed at the end of the document in an ordered list.
 | 
				
			||||||
 | 
					// Finally, there are inline footnotes such as:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    Inline footnotes^[Also supported.] provide a quick inline explanation,
 | 
				
			||||||
 | 
					//    but are rendered at the bottom of the document.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reference holds all information necessary for a reference-style links or
 | 
				
			||||||
 | 
					// footnotes.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Consider this markdown with reference-style links:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     [link][ref]
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     [ref]: /url/ "tooltip title"
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// It will be ultimately converted to this HTML:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     <p><a href=\"/url/\" title=\"title\">link</a></p>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// And a reference structure will be populated as follows:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     p.refs["ref"] = &reference{
 | 
				
			||||||
 | 
					//         link: "/url/",
 | 
				
			||||||
 | 
					//         title: "tooltip title",
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Alternatively, reference can contain information about a footnote. Consider
 | 
				
			||||||
 | 
					// this markdown:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     Text needing a footnote.[^a]
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     [^a]: This is the note
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// A reference structure will be populated as follows:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     p.refs["a"] = &reference{
 | 
				
			||||||
 | 
					//         link: "a",
 | 
				
			||||||
 | 
					//         title: "This is the note",
 | 
				
			||||||
 | 
					//         noteID: <some positive int>,
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// TODO: As you can see, it begs for splitting into two dedicated structures
 | 
				
			||||||
 | 
					// for refs and for footnotes.
 | 
				
			||||||
 | 
					type reference struct {
 | 
				
			||||||
 | 
						link     []byte
 | 
				
			||||||
 | 
						title    []byte
 | 
				
			||||||
 | 
						noteID   int // 0 if not a footnote ref
 | 
				
			||||||
 | 
						hasBlock bool
 | 
				
			||||||
 | 
						footnote *Node // a link to the Item node within a list of footnotes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						text []byte // only gets populated by refOverride feature with Reference.Text
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *reference) String() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
 | 
				
			||||||
 | 
							r.link, r.title, r.text, r.noteID, r.hasBlock)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Check whether or not data starts with a reference link.
 | 
				
			||||||
 | 
					// If so, it is parsed and stored in the list of references
 | 
				
			||||||
 | 
					// (in the render struct).
 | 
				
			||||||
 | 
					// Returns the number of bytes to skip to move past it,
 | 
				
			||||||
 | 
					// or zero if the first line is not a reference.
 | 
				
			||||||
 | 
					func isReference(p *Markdown, data []byte, tabSize int) int {
 | 
				
			||||||
 | 
						// up to 3 optional leading spaces
 | 
				
			||||||
 | 
						if len(data) < 4 {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						i := 0
 | 
				
			||||||
 | 
						for i < 3 && data[i] == ' ' {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						noteID := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// id part: anything but a newline between brackets
 | 
				
			||||||
 | 
						if data[i] != '[' {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						i++
 | 
				
			||||||
 | 
						if p.extensions&Footnotes != 0 {
 | 
				
			||||||
 | 
							if i < len(data) && data[i] == '^' {
 | 
				
			||||||
 | 
								// we can set it to anything here because the proper noteIds will
 | 
				
			||||||
 | 
								// be assigned later during the second pass. It just has to be != 0
 | 
				
			||||||
 | 
								noteID = 1
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						idOffset := i
 | 
				
			||||||
 | 
						for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if i >= len(data) || data[i] != ']' {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						idEnd := i
 | 
				
			||||||
 | 
						// footnotes can have empty ID, like this: [^], but a reference can not be
 | 
				
			||||||
 | 
						// empty like this: []. Break early if it's not a footnote and there's no ID
 | 
				
			||||||
 | 
						if noteID == 0 && idOffset == idEnd {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// spacer: colon (space | tab)* newline? (space | tab)*
 | 
				
			||||||
 | 
						i++
 | 
				
			||||||
 | 
						if i >= len(data) || data[i] != ':' {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						i++
 | 
				
			||||||
 | 
						for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
							if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if i >= len(data) {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							linkOffset, linkEnd   int
 | 
				
			||||||
 | 
							titleOffset, titleEnd int
 | 
				
			||||||
 | 
							lineEnd               int
 | 
				
			||||||
 | 
							raw                   []byte
 | 
				
			||||||
 | 
							hasBlock              bool
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if p.extensions&Footnotes != 0 && noteID != 0 {
 | 
				
			||||||
 | 
							linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
 | 
				
			||||||
 | 
							lineEnd = linkEnd
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if lineEnd == 0 {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// a valid ref has been found
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ref := &reference{
 | 
				
			||||||
 | 
							noteID:   noteID,
 | 
				
			||||||
 | 
							hasBlock: hasBlock,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if noteID > 0 {
 | 
				
			||||||
 | 
							// reusing the link field for the id since footnotes don't have links
 | 
				
			||||||
 | 
							ref.link = data[idOffset:idEnd]
 | 
				
			||||||
 | 
							// if footnote, it's not really a title, it's the contained text
 | 
				
			||||||
 | 
							ref.title = raw
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ref.link = data[linkOffset:linkEnd]
 | 
				
			||||||
 | 
							ref.title = data[titleOffset:titleEnd]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// id matches are case-insensitive
 | 
				
			||||||
 | 
						id := string(bytes.ToLower(data[idOffset:idEnd]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.refs[id] = ref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return lineEnd
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
 | 
				
			||||||
 | 
						// link: whitespace-free sequence, optionally between angle brackets
 | 
				
			||||||
 | 
						if data[i] == '<' {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						linkOffset = i
 | 
				
			||||||
 | 
						for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						linkEnd = i
 | 
				
			||||||
 | 
						if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
 | 
				
			||||||
 | 
							linkOffset++
 | 
				
			||||||
 | 
							linkEnd--
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
 | 
				
			||||||
 | 
						for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// compute end-of-line
 | 
				
			||||||
 | 
						if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
 | 
				
			||||||
 | 
							lineEnd = i
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
 | 
				
			||||||
 | 
							lineEnd++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// optional (space|tab)* spacer after a newline
 | 
				
			||||||
 | 
						if lineEnd > 0 {
 | 
				
			||||||
 | 
							i = lineEnd + 1
 | 
				
			||||||
 | 
							for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// optional title: any non-newline sequence enclosed in '"() alone on its line
 | 
				
			||||||
 | 
						if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
							titleOffset = i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// look for EOL
 | 
				
			||||||
 | 
							for i < len(data) && data[i] != '\n' && data[i] != '\r' {
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
 | 
				
			||||||
 | 
								titleEnd = i + 1
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								titleEnd = i
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// step back
 | 
				
			||||||
 | 
							i--
 | 
				
			||||||
 | 
							for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
 | 
				
			||||||
 | 
								i--
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
 | 
				
			||||||
 | 
								lineEnd = titleEnd
 | 
				
			||||||
 | 
								titleEnd = i
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The first bit of this logic is the same as Parser.listItem, but the rest
 | 
				
			||||||
 | 
					// is much simpler. This function simply finds the entire block and shifts it
 | 
				
			||||||
 | 
					// over by one tab if it is indeed a block (just returns the line if it's not).
 | 
				
			||||||
 | 
					// blockEnd is the end of the section in the input buffer, and contents is the
 | 
				
			||||||
 | 
					// extracted text that was shifted over one tab. It will need to be rendered at
 | 
				
			||||||
 | 
					// the end of the document.
 | 
				
			||||||
 | 
					func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
 | 
				
			||||||
 | 
						if i == 0 || len(data) == 0 {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// skip leading whitespace on first line
 | 
				
			||||||
 | 
						for i < len(data) && data[i] == ' ' {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						blockStart = i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// find the end of the line
 | 
				
			||||||
 | 
						blockEnd = i
 | 
				
			||||||
 | 
						for i < len(data) && data[i-1] != '\n' {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// get working buffer
 | 
				
			||||||
 | 
						var raw bytes.Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// put the first line into the working buffer
 | 
				
			||||||
 | 
						raw.Write(data[blockEnd:i])
 | 
				
			||||||
 | 
						blockEnd = i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// process the following lines
 | 
				
			||||||
 | 
						containsBlankLine := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gatherLines:
 | 
				
			||||||
 | 
						for blockEnd < len(data) {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// find the end of this line
 | 
				
			||||||
 | 
							for i < len(data) && data[i-1] != '\n' {
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// if it is an empty line, guess that it is part of this item
 | 
				
			||||||
 | 
							// and move on to the next line
 | 
				
			||||||
 | 
							if p.isEmpty(data[blockEnd:i]) > 0 {
 | 
				
			||||||
 | 
								containsBlankLine = true
 | 
				
			||||||
 | 
								blockEnd = i
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							n := 0
 | 
				
			||||||
 | 
							if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
 | 
				
			||||||
 | 
								// this is the end of the block.
 | 
				
			||||||
 | 
								// we don't want to include this last line in the index.
 | 
				
			||||||
 | 
								break gatherLines
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// if there were blank lines before this one, insert a new one now
 | 
				
			||||||
 | 
							if containsBlankLine {
 | 
				
			||||||
 | 
								raw.WriteByte('\n')
 | 
				
			||||||
 | 
								containsBlankLine = false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// get rid of that first tab, write to buffer
 | 
				
			||||||
 | 
							raw.Write(data[blockEnd+n : i])
 | 
				
			||||||
 | 
							hasBlock = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							blockEnd = i
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if data[blockEnd-1] != '\n' {
 | 
				
			||||||
 | 
							raw.WriteByte('\n')
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						contents = raw.Bytes()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Miscellaneous helper functions
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test if a character is a punctuation symbol.
 | 
				
			||||||
 | 
					// Taken from a private function in regexp in the stdlib.
 | 
				
			||||||
 | 
					func ispunct(c byte) bool {
 | 
				
			||||||
 | 
						for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
 | 
				
			||||||
 | 
							if c == r {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test if a character is a whitespace character.
 | 
				
			||||||
 | 
					func isspace(c byte) bool {
 | 
				
			||||||
 | 
						return ishorizontalspace(c) || isverticalspace(c)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test if a character is a horizontal whitespace character.
 | 
				
			||||||
 | 
					func ishorizontalspace(c byte) bool {
 | 
				
			||||||
 | 
						return c == ' ' || c == '\t'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test if a character is a vertical character.
 | 
				
			||||||
 | 
					func isverticalspace(c byte) bool {
 | 
				
			||||||
 | 
						return c == '\n' || c == '\r' || c == '\f' || c == '\v'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test if a character is letter.
 | 
				
			||||||
 | 
					func isletter(c byte) bool {
 | 
				
			||||||
 | 
						return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test if a character is a letter or a digit.
 | 
				
			||||||
 | 
					// TODO: check when this is looking for ASCII alnum and when it should use unicode
 | 
				
			||||||
 | 
					func isalnum(c byte) bool {
 | 
				
			||||||
 | 
						return (c >= '0' && c <= '9') || isletter(c)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
 | 
				
			||||||
 | 
					// always ends output with a newline
 | 
				
			||||||
 | 
					func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
 | 
				
			||||||
 | 
						// first, check for common cases: no tabs, or only tabs at beginning of line
 | 
				
			||||||
 | 
						i, prefix := 0, 0
 | 
				
			||||||
 | 
						slowcase := false
 | 
				
			||||||
 | 
						for i = 0; i < len(line); i++ {
 | 
				
			||||||
 | 
							if line[i] == '\t' {
 | 
				
			||||||
 | 
								if prefix == i {
 | 
				
			||||||
 | 
									prefix++
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									slowcase = true
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// no need to decode runes if all tabs are at the beginning of the line
 | 
				
			||||||
 | 
						if !slowcase {
 | 
				
			||||||
 | 
							for i = 0; i < prefix*tabSize; i++ {
 | 
				
			||||||
 | 
								out.WriteByte(' ')
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							out.Write(line[prefix:])
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// the slow case: we need to count runes to figure out how
 | 
				
			||||||
 | 
						// many spaces to insert for each tab
 | 
				
			||||||
 | 
						column := 0
 | 
				
			||||||
 | 
						i = 0
 | 
				
			||||||
 | 
						for i < len(line) {
 | 
				
			||||||
 | 
							start := i
 | 
				
			||||||
 | 
							for i < len(line) && line[i] != '\t' {
 | 
				
			||||||
 | 
								_, size := utf8.DecodeRune(line[i:])
 | 
				
			||||||
 | 
								i += size
 | 
				
			||||||
 | 
								column++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if i > start {
 | 
				
			||||||
 | 
								out.Write(line[start:i])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if i >= len(line) {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								out.WriteByte(' ')
 | 
				
			||||||
 | 
								column++
 | 
				
			||||||
 | 
								if column%tabSize == 0 {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Find if a line counts as indented or not.
 | 
				
			||||||
 | 
					// Returns number of characters the indent is (0 = not indented).
 | 
				
			||||||
 | 
					func isIndented(data []byte, indentSize int) int {
 | 
				
			||||||
 | 
						if len(data) == 0 {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if data[0] == '\t' {
 | 
				
			||||||
 | 
							return 1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(data) < indentSize {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for i := 0; i < indentSize; i++ {
 | 
				
			||||||
 | 
							if data[i] != ' ' {
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return indentSize
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create a url-safe slug for fragments
 | 
				
			||||||
 | 
					func slugify(in []byte) []byte {
 | 
				
			||||||
 | 
						if len(in) == 0 {
 | 
				
			||||||
 | 
							return in
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out := make([]byte, 0, len(in))
 | 
				
			||||||
 | 
						sym := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, ch := range in {
 | 
				
			||||||
 | 
							if isalnum(ch) {
 | 
				
			||||||
 | 
								sym = false
 | 
				
			||||||
 | 
								out = append(out, ch)
 | 
				
			||||||
 | 
							} else if sym {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								out = append(out, '-')
 | 
				
			||||||
 | 
								sym = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var a, b int
 | 
				
			||||||
 | 
						var ch byte
 | 
				
			||||||
 | 
						for a, ch = range out {
 | 
				
			||||||
 | 
							if ch != '-' {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for b = len(out) - 1; b > 0; b-- {
 | 
				
			||||||
 | 
							if out[b] != '-' {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return out[a : b+1]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,360 @@
 | 
				
			|||||||
 | 
					package blackfriday
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NodeType specifies a type of a single node of a syntax tree. Usually one
 | 
				
			||||||
 | 
					// node (and its type) corresponds to a single markdown feature, e.g. emphasis
 | 
				
			||||||
 | 
					// or code block.
 | 
				
			||||||
 | 
					type NodeType int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Constants for identifying different types of nodes. See NodeType.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						Document NodeType = iota
 | 
				
			||||||
 | 
						BlockQuote
 | 
				
			||||||
 | 
						List
 | 
				
			||||||
 | 
						Item
 | 
				
			||||||
 | 
						Paragraph
 | 
				
			||||||
 | 
						Heading
 | 
				
			||||||
 | 
						HorizontalRule
 | 
				
			||||||
 | 
						Emph
 | 
				
			||||||
 | 
						Strong
 | 
				
			||||||
 | 
						Del
 | 
				
			||||||
 | 
						Link
 | 
				
			||||||
 | 
						Image
 | 
				
			||||||
 | 
						Text
 | 
				
			||||||
 | 
						HTMLBlock
 | 
				
			||||||
 | 
						CodeBlock
 | 
				
			||||||
 | 
						Softbreak
 | 
				
			||||||
 | 
						Hardbreak
 | 
				
			||||||
 | 
						Code
 | 
				
			||||||
 | 
						HTMLSpan
 | 
				
			||||||
 | 
						Table
 | 
				
			||||||
 | 
						TableCell
 | 
				
			||||||
 | 
						TableHead
 | 
				
			||||||
 | 
						TableBody
 | 
				
			||||||
 | 
						TableRow
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var nodeTypeNames = []string{
 | 
				
			||||||
 | 
						Document:       "Document",
 | 
				
			||||||
 | 
						BlockQuote:     "BlockQuote",
 | 
				
			||||||
 | 
						List:           "List",
 | 
				
			||||||
 | 
						Item:           "Item",
 | 
				
			||||||
 | 
						Paragraph:      "Paragraph",
 | 
				
			||||||
 | 
						Heading:        "Heading",
 | 
				
			||||||
 | 
						HorizontalRule: "HorizontalRule",
 | 
				
			||||||
 | 
						Emph:           "Emph",
 | 
				
			||||||
 | 
						Strong:         "Strong",
 | 
				
			||||||
 | 
						Del:            "Del",
 | 
				
			||||||
 | 
						Link:           "Link",
 | 
				
			||||||
 | 
						Image:          "Image",
 | 
				
			||||||
 | 
						Text:           "Text",
 | 
				
			||||||
 | 
						HTMLBlock:      "HTMLBlock",
 | 
				
			||||||
 | 
						CodeBlock:      "CodeBlock",
 | 
				
			||||||
 | 
						Softbreak:      "Softbreak",
 | 
				
			||||||
 | 
						Hardbreak:      "Hardbreak",
 | 
				
			||||||
 | 
						Code:           "Code",
 | 
				
			||||||
 | 
						HTMLSpan:       "HTMLSpan",
 | 
				
			||||||
 | 
						Table:          "Table",
 | 
				
			||||||
 | 
						TableCell:      "TableCell",
 | 
				
			||||||
 | 
						TableHead:      "TableHead",
 | 
				
			||||||
 | 
						TableBody:      "TableBody",
 | 
				
			||||||
 | 
						TableRow:       "TableRow",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t NodeType) String() string {
 | 
				
			||||||
 | 
						return nodeTypeNames[t]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListData contains fields relevant to a List and Item node type.
 | 
				
			||||||
 | 
					type ListData struct {
 | 
				
			||||||
 | 
						ListFlags       ListType
 | 
				
			||||||
 | 
						Tight           bool   // Skip <p>s around list item data if true
 | 
				
			||||||
 | 
						BulletChar      byte   // '*', '+' or '-' in bullet lists
 | 
				
			||||||
 | 
						Delimiter       byte   // '.' or ')' after the number in ordered lists
 | 
				
			||||||
 | 
						RefLink         []byte // If not nil, turns this list item into a footnote item and triggers different rendering
 | 
				
			||||||
 | 
						IsFootnotesList bool   // This is a list of footnotes
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LinkData contains fields relevant to a Link node type.
 | 
				
			||||||
 | 
					type LinkData struct {
 | 
				
			||||||
 | 
						Destination []byte // Destination is what goes into a href
 | 
				
			||||||
 | 
						Title       []byte // Title is the tooltip thing that goes in a title attribute
 | 
				
			||||||
 | 
						NoteID      int    // NoteID contains a serial number of a footnote, zero if it's not a footnote
 | 
				
			||||||
 | 
						Footnote    *Node  // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CodeBlockData contains fields relevant to a CodeBlock node type.
 | 
				
			||||||
 | 
					type CodeBlockData struct {
 | 
				
			||||||
 | 
						IsFenced    bool   // Specifies whether it's a fenced code block or an indented one
 | 
				
			||||||
 | 
						Info        []byte // This holds the info string
 | 
				
			||||||
 | 
						FenceChar   byte
 | 
				
			||||||
 | 
						FenceLength int
 | 
				
			||||||
 | 
						FenceOffset int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TableCellData contains fields relevant to a TableCell node type.
 | 
				
			||||||
 | 
					type TableCellData struct {
 | 
				
			||||||
 | 
						IsHeader bool           // This tells if it's under the header row
 | 
				
			||||||
 | 
						Align    CellAlignFlags // This holds the value for align attribute
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HeadingData contains fields relevant to a Heading node type.
 | 
				
			||||||
 | 
					type HeadingData struct {
 | 
				
			||||||
 | 
						Level        int    // This holds the heading level number
 | 
				
			||||||
 | 
						HeadingID    string // This might hold heading ID, if present
 | 
				
			||||||
 | 
						IsTitleblock bool   // Specifies whether it's a title block
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Node is a single element in the abstract syntax tree of the parsed document.
 | 
				
			||||||
 | 
					// It holds connections to the structurally neighboring nodes and, for certain
 | 
				
			||||||
 | 
					// types of nodes, additional information that might be needed when rendering.
 | 
				
			||||||
 | 
					type Node struct {
 | 
				
			||||||
 | 
						Type       NodeType // Determines the type of the node
 | 
				
			||||||
 | 
						Parent     *Node    // Points to the parent
 | 
				
			||||||
 | 
						FirstChild *Node    // Points to the first child, if any
 | 
				
			||||||
 | 
						LastChild  *Node    // Points to the last child, if any
 | 
				
			||||||
 | 
						Prev       *Node    // Previous sibling; nil if it's the first child
 | 
				
			||||||
 | 
						Next       *Node    // Next sibling; nil if it's the last child
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Literal []byte // Text contents of the leaf nodes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						HeadingData   // Populated if Type is Heading
 | 
				
			||||||
 | 
						ListData      // Populated if Type is List
 | 
				
			||||||
 | 
						CodeBlockData // Populated if Type is CodeBlock
 | 
				
			||||||
 | 
						LinkData      // Populated if Type is Link
 | 
				
			||||||
 | 
						TableCellData // Populated if Type is TableCell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						content []byte // Markdown content of the block nodes
 | 
				
			||||||
 | 
						open    bool   // Specifies an open block node that has not been finished to process yet
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewNode allocates a node of a specified type.
 | 
				
			||||||
 | 
					func NewNode(typ NodeType) *Node {
 | 
				
			||||||
 | 
						return &Node{
 | 
				
			||||||
 | 
							Type: typ,
 | 
				
			||||||
 | 
							open: true,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *Node) String() string {
 | 
				
			||||||
 | 
						ellipsis := ""
 | 
				
			||||||
 | 
						snippet := n.Literal
 | 
				
			||||||
 | 
						if len(snippet) > 16 {
 | 
				
			||||||
 | 
							snippet = snippet[:16]
 | 
				
			||||||
 | 
							ellipsis = "..."
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Unlink removes node 'n' from the tree.
 | 
				
			||||||
 | 
					// It panics if the node is nil.
 | 
				
			||||||
 | 
					func (n *Node) Unlink() {
 | 
				
			||||||
 | 
						if n.Prev != nil {
 | 
				
			||||||
 | 
							n.Prev.Next = n.Next
 | 
				
			||||||
 | 
						} else if n.Parent != nil {
 | 
				
			||||||
 | 
							n.Parent.FirstChild = n.Next
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if n.Next != nil {
 | 
				
			||||||
 | 
							n.Next.Prev = n.Prev
 | 
				
			||||||
 | 
						} else if n.Parent != nil {
 | 
				
			||||||
 | 
							n.Parent.LastChild = n.Prev
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						n.Parent = nil
 | 
				
			||||||
 | 
						n.Next = nil
 | 
				
			||||||
 | 
						n.Prev = nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AppendChild adds a node 'child' as a child of 'n'.
 | 
				
			||||||
 | 
					// It panics if either node is nil.
 | 
				
			||||||
 | 
					func (n *Node) AppendChild(child *Node) {
 | 
				
			||||||
 | 
						child.Unlink()
 | 
				
			||||||
 | 
						child.Parent = n
 | 
				
			||||||
 | 
						if n.LastChild != nil {
 | 
				
			||||||
 | 
							n.LastChild.Next = child
 | 
				
			||||||
 | 
							child.Prev = n.LastChild
 | 
				
			||||||
 | 
							n.LastChild = child
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							n.FirstChild = child
 | 
				
			||||||
 | 
							n.LastChild = child
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InsertBefore inserts 'sibling' immediately before 'n'.
 | 
				
			||||||
 | 
					// It panics if either node is nil.
 | 
				
			||||||
 | 
					func (n *Node) InsertBefore(sibling *Node) {
 | 
				
			||||||
 | 
						sibling.Unlink()
 | 
				
			||||||
 | 
						sibling.Prev = n.Prev
 | 
				
			||||||
 | 
						if sibling.Prev != nil {
 | 
				
			||||||
 | 
							sibling.Prev.Next = sibling
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sibling.Next = n
 | 
				
			||||||
 | 
						n.Prev = sibling
 | 
				
			||||||
 | 
						sibling.Parent = n.Parent
 | 
				
			||||||
 | 
						if sibling.Prev == nil {
 | 
				
			||||||
 | 
							sibling.Parent.FirstChild = sibling
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsContainer returns true if 'n' can contain children.
 | 
				
			||||||
 | 
					func (n *Node) IsContainer() bool {
 | 
				
			||||||
 | 
						switch n.Type {
 | 
				
			||||||
 | 
						case Document:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case BlockQuote:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case List:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Item:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Paragraph:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Heading:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Emph:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Strong:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Del:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Link:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Image:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case Table:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case TableHead:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case TableBody:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case TableRow:
 | 
				
			||||||
 | 
							fallthrough
 | 
				
			||||||
 | 
						case TableCell:
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsLeaf returns true if 'n' is a leaf node.
 | 
				
			||||||
 | 
					func (n *Node) IsLeaf() bool {
 | 
				
			||||||
 | 
						return !n.IsContainer()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *Node) canContain(t NodeType) bool {
 | 
				
			||||||
 | 
						if n.Type == List {
 | 
				
			||||||
 | 
							return t == Item
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
 | 
				
			||||||
 | 
							return t != Item
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if n.Type == Table {
 | 
				
			||||||
 | 
							return t == TableHead || t == TableBody
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if n.Type == TableHead || n.Type == TableBody {
 | 
				
			||||||
 | 
							return t == TableRow
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if n.Type == TableRow {
 | 
				
			||||||
 | 
							return t == TableCell
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WalkStatus allows NodeVisitor to have some control over the tree traversal.
 | 
				
			||||||
 | 
					// It is returned from NodeVisitor and different values allow Node.Walk to
 | 
				
			||||||
 | 
					// decide which node to go to next.
 | 
				
			||||||
 | 
					type WalkStatus int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// GoToNext is the default traversal of every node.
 | 
				
			||||||
 | 
						GoToNext WalkStatus = iota
 | 
				
			||||||
 | 
						// SkipChildren tells walker to skip all children of current node.
 | 
				
			||||||
 | 
						SkipChildren
 | 
				
			||||||
 | 
						// Terminate tells walker to terminate the traversal.
 | 
				
			||||||
 | 
						Terminate
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NodeVisitor is a callback to be called when traversing the syntax tree.
 | 
				
			||||||
 | 
					// Called twice for every node: once with entering=true when the branch is
 | 
				
			||||||
 | 
					// first visited, then with entering=false after all the children are done.
 | 
				
			||||||
 | 
					type NodeVisitor func(node *Node, entering bool) WalkStatus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Walk is a convenience method that instantiates a walker and starts a
 | 
				
			||||||
 | 
					// traversal of subtree rooted at n.
 | 
				
			||||||
 | 
					func (n *Node) Walk(visitor NodeVisitor) {
 | 
				
			||||||
 | 
						w := newNodeWalker(n)
 | 
				
			||||||
 | 
						for w.current != nil {
 | 
				
			||||||
 | 
							status := visitor(w.current, w.entering)
 | 
				
			||||||
 | 
							switch status {
 | 
				
			||||||
 | 
							case GoToNext:
 | 
				
			||||||
 | 
								w.next()
 | 
				
			||||||
 | 
							case SkipChildren:
 | 
				
			||||||
 | 
								w.entering = false
 | 
				
			||||||
 | 
								w.next()
 | 
				
			||||||
 | 
							case Terminate:
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type nodeWalker struct {
 | 
				
			||||||
 | 
						current  *Node
 | 
				
			||||||
 | 
						root     *Node
 | 
				
			||||||
 | 
						entering bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func newNodeWalker(root *Node) *nodeWalker {
 | 
				
			||||||
 | 
						return &nodeWalker{
 | 
				
			||||||
 | 
							current:  root,
 | 
				
			||||||
 | 
							root:     root,
 | 
				
			||||||
 | 
							entering: true,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (nw *nodeWalker) next() {
 | 
				
			||||||
 | 
						if (!nw.current.IsContainer() || !nw.entering) && nw.current == nw.root {
 | 
				
			||||||
 | 
							nw.current = nil
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if nw.entering && nw.current.IsContainer() {
 | 
				
			||||||
 | 
							if nw.current.FirstChild != nil {
 | 
				
			||||||
 | 
								nw.current = nw.current.FirstChild
 | 
				
			||||||
 | 
								nw.entering = true
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								nw.entering = false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if nw.current.Next == nil {
 | 
				
			||||||
 | 
							nw.current = nw.current.Parent
 | 
				
			||||||
 | 
							nw.entering = false
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							nw.current = nw.current.Next
 | 
				
			||||||
 | 
							nw.entering = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dump(ast *Node) {
 | 
				
			||||||
 | 
						fmt.Println(dumpString(ast))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dumpR(ast *Node, depth int) string {
 | 
				
			||||||
 | 
						if ast == nil {
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						indent := bytes.Repeat([]byte("\t"), depth)
 | 
				
			||||||
 | 
						content := ast.Literal
 | 
				
			||||||
 | 
						if content == nil {
 | 
				
			||||||
 | 
							content = ast.content
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
 | 
				
			||||||
 | 
						for n := ast.FirstChild; n != nil; n = n.Next {
 | 
				
			||||||
 | 
							result += dumpR(n, depth+1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dumpString(ast *Node) string {
 | 
				
			||||||
 | 
						return dumpR(ast, 0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					[flake8]
 | 
				
			||||||
 | 
					max-line-length = 120
 | 
				
			||||||
@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					*.coverprofile
 | 
				
			||||||
 | 
					coverage.txt
 | 
				
			||||||
 | 
					node_modules/
 | 
				
			||||||
 | 
					vendor
 | 
				
			||||||
 | 
					.idea
 | 
				
			||||||
 | 
					/.local/
 | 
				
			||||||
 | 
					/internal/
 | 
				
			||||||
 | 
					/site/
 | 
				
			||||||
 | 
					package.json
 | 
				
			||||||
 | 
					package-lock.json
 | 
				
			||||||
@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					# Contributor Covenant Code of Conduct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Our Pledge
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In the interest of fostering an open and welcoming environment, we as
 | 
				
			||||||
 | 
					contributors and maintainers pledge to making participation in our project and
 | 
				
			||||||
 | 
					our community a harassment-free experience for everyone, regardless of age, body
 | 
				
			||||||
 | 
					size, disability, ethnicity, gender identity and expression, level of experience,
 | 
				
			||||||
 | 
					education, socio-economic status, nationality, personal appearance, race,
 | 
				
			||||||
 | 
					religion, or sexual identity and orientation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Our Standards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Examples of behavior that contributes to creating a positive environment
 | 
				
			||||||
 | 
					include:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Using welcoming and inclusive language
 | 
				
			||||||
 | 
					* Being respectful of differing viewpoints and experiences
 | 
				
			||||||
 | 
					* Gracefully accepting constructive criticism
 | 
				
			||||||
 | 
					* Focusing on what is best for the community
 | 
				
			||||||
 | 
					* Showing empathy towards other community members
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Examples of unacceptable behavior by participants include:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* The use of sexualized language or imagery and unwelcome sexual attention or
 | 
				
			||||||
 | 
					  advances
 | 
				
			||||||
 | 
					* Trolling, insulting/derogatory comments, and personal or political attacks
 | 
				
			||||||
 | 
					* Public or private harassment
 | 
				
			||||||
 | 
					* Publishing others' private information, such as a physical or electronic
 | 
				
			||||||
 | 
					  address, without explicit permission
 | 
				
			||||||
 | 
					* Other conduct which could reasonably be considered inappropriate in a
 | 
				
			||||||
 | 
					  professional setting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Our Responsibilities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Project maintainers are responsible for clarifying the standards of acceptable
 | 
				
			||||||
 | 
					behavior and are expected to take appropriate and fair corrective action in
 | 
				
			||||||
 | 
					response to any instances of unacceptable behavior.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Project maintainers have the right and responsibility to remove, edit, or
 | 
				
			||||||
 | 
					reject comments, commits, code, wiki edits, issues, and other contributions
 | 
				
			||||||
 | 
					that are not aligned to this Code of Conduct, or to ban temporarily or
 | 
				
			||||||
 | 
					permanently any contributor for other behaviors that they deem inappropriate,
 | 
				
			||||||
 | 
					threatening, offensive, or harmful.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This Code of Conduct applies both within project spaces and in public spaces
 | 
				
			||||||
 | 
					when an individual is representing the project or its community. Examples of
 | 
				
			||||||
 | 
					representing a project or community include using an official project e-mail
 | 
				
			||||||
 | 
					address, posting via an official social media account, or acting as an appointed
 | 
				
			||||||
 | 
					representative at an online or offline event. Representation of a project may be
 | 
				
			||||||
 | 
					further defined and clarified by project maintainers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Enforcement
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Instances of abusive, harassing, or otherwise unacceptable behavior may be
 | 
				
			||||||
 | 
					reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
 | 
				
			||||||
 | 
					reviewed and investigated and will result in a response that is deemed necessary
 | 
				
			||||||
 | 
					and appropriate to the circumstances. The project team is obligated to maintain
 | 
				
			||||||
 | 
					confidentiality with regard to the reporter of an incident.  Further details of
 | 
				
			||||||
 | 
					specific enforcement policies may be posted separately.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Project maintainers who do not follow or enforce the Code of Conduct in good
 | 
				
			||||||
 | 
					faith may face temporary or permanent repercussions as determined by other
 | 
				
			||||||
 | 
					members of the project's leadership.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Attribution
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
 | 
				
			||||||
 | 
					available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[homepage]: https://www.contributor-covenant.org
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					MIT License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2023 Jeremy Saenz & Contributors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					cli
 | 
				
			||||||
 | 
					===
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://github.com/urfave/cli/actions/workflows/cli.yml)
 | 
				
			||||||
 | 
					[](https://pkg.go.dev/github.com/urfave/cli/)
 | 
				
			||||||
 | 
					[](https://goreportcard.com/report/urfave/cli)
 | 
				
			||||||
 | 
					[](https://codecov.io/gh/urfave/cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cli is a simple, fast, and fun package for building command line apps in Go. The
 | 
				
			||||||
 | 
					goal is to enable developers to write fast and distributable command line
 | 
				
			||||||
 | 
					applications in an expressive way.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usage Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage documentation for `v1` is available [at the docs
 | 
				
			||||||
 | 
					site](https://cli.urfave.org/v1/getting-started/) or in-tree at
 | 
				
			||||||
 | 
					[./docs/v1/manual.md](./docs/v1/manual.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Make sure you have a working Go environment. Go version 1.18+ is supported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Supported platforms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cli is tested against multiple versions of Go on Linux, and against the latest released
 | 
				
			||||||
 | 
					version of Go on OS X and Windows.  For full details, see
 | 
				
			||||||
 | 
					[./.github/workflows/cli.yml](./.github/workflows/cli.yml).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Build tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can use the following build tags:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### `urfave_cli_no_docs`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When set, this removes `ToMarkdown` and `ToMan` methods, so your application
 | 
				
			||||||
 | 
					won't be able to call those. This reduces the resulting binary size by about
 | 
				
			||||||
 | 
					300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to less dependencies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Using `v1` releases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ go get github.com/urfave/cli
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					...
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
					  "github.com/urfave/cli"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					...
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
@ -0,0 +1,531 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						changeLogURL            = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
 | 
				
			||||||
 | 
						appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
 | 
				
			||||||
 | 
						// unused variable. commented for now. will remove in future if agreed upon by everyone
 | 
				
			||||||
 | 
						//runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						contactSysadmin = "This is an error in the application.  Please contact the distributor of this application if this is not you."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errInvalidActionType = NewExitError("ERROR invalid Action type. "+
 | 
				
			||||||
 | 
							fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error).  %s", contactSysadmin)+
 | 
				
			||||||
 | 
							fmt.Sprintf("See %s", appActionDeprecationURL), 2)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// App is the main structure of a cli application. It is recommended that
 | 
				
			||||||
 | 
					// an app be created with the cli.NewApp() function
 | 
				
			||||||
 | 
					type App struct {
 | 
				
			||||||
 | 
						// The name of the program. Defaults to path.Base(os.Args[0])
 | 
				
			||||||
 | 
						Name string
 | 
				
			||||||
 | 
						// Full name of command for help, defaults to Name
 | 
				
			||||||
 | 
						HelpName string
 | 
				
			||||||
 | 
						// Description of the program.
 | 
				
			||||||
 | 
						Usage string
 | 
				
			||||||
 | 
						// Text to override the USAGE section of help
 | 
				
			||||||
 | 
						UsageText string
 | 
				
			||||||
 | 
						// Description of the program argument format.
 | 
				
			||||||
 | 
						ArgsUsage string
 | 
				
			||||||
 | 
						// Version of the program
 | 
				
			||||||
 | 
						Version string
 | 
				
			||||||
 | 
						// Description of the program
 | 
				
			||||||
 | 
						Description string
 | 
				
			||||||
 | 
						// List of commands to execute
 | 
				
			||||||
 | 
						Commands []Command
 | 
				
			||||||
 | 
						// List of flags to parse
 | 
				
			||||||
 | 
						Flags []Flag
 | 
				
			||||||
 | 
						// Boolean to enable bash completion commands
 | 
				
			||||||
 | 
						EnableBashCompletion bool
 | 
				
			||||||
 | 
						// Boolean to hide built-in help command
 | 
				
			||||||
 | 
						HideHelp bool
 | 
				
			||||||
 | 
						// Boolean to hide built-in version flag and the VERSION section of help
 | 
				
			||||||
 | 
						HideVersion bool
 | 
				
			||||||
 | 
						// Populate on app startup, only gettable through method Categories()
 | 
				
			||||||
 | 
						categories CommandCategories
 | 
				
			||||||
 | 
						// An action to execute when the bash-completion flag is set
 | 
				
			||||||
 | 
						BashComplete BashCompleteFunc
 | 
				
			||||||
 | 
						// An action to execute before any subcommands are run, but after the context is ready
 | 
				
			||||||
 | 
						// If a non-nil error is returned, no subcommands are run
 | 
				
			||||||
 | 
						Before BeforeFunc
 | 
				
			||||||
 | 
						// An action to execute after any subcommands are run, but after the subcommand has finished
 | 
				
			||||||
 | 
						// It is run even if Action() panics
 | 
				
			||||||
 | 
						After AfterFunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The action to execute when no subcommands are specified
 | 
				
			||||||
 | 
						// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
 | 
				
			||||||
 | 
						// *Note*: support for the deprecated `Action` signature will be removed in a future version
 | 
				
			||||||
 | 
						Action interface{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Execute this function if the proper command cannot be found
 | 
				
			||||||
 | 
						CommandNotFound CommandNotFoundFunc
 | 
				
			||||||
 | 
						// Execute this function if an usage error occurs
 | 
				
			||||||
 | 
						OnUsageError OnUsageErrorFunc
 | 
				
			||||||
 | 
						// Compilation date
 | 
				
			||||||
 | 
						Compiled time.Time
 | 
				
			||||||
 | 
						// List of all authors who contributed
 | 
				
			||||||
 | 
						Authors []Author
 | 
				
			||||||
 | 
						// Copyright of the binary if any
 | 
				
			||||||
 | 
						Copyright string
 | 
				
			||||||
 | 
						// Name of Author (Note: Use App.Authors, this is deprecated)
 | 
				
			||||||
 | 
						Author string
 | 
				
			||||||
 | 
						// Email of Author (Note: Use App.Authors, this is deprecated)
 | 
				
			||||||
 | 
						Email string
 | 
				
			||||||
 | 
						// Writer writer to write output to
 | 
				
			||||||
 | 
						Writer io.Writer
 | 
				
			||||||
 | 
						// ErrWriter writes error output
 | 
				
			||||||
 | 
						ErrWriter io.Writer
 | 
				
			||||||
 | 
						// Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to
 | 
				
			||||||
 | 
						// function as a default, so this is optional.
 | 
				
			||||||
 | 
						ExitErrHandler ExitErrHandlerFunc
 | 
				
			||||||
 | 
						// Other custom info
 | 
				
			||||||
 | 
						Metadata map[string]interface{}
 | 
				
			||||||
 | 
						// Carries a function which returns app specific info.
 | 
				
			||||||
 | 
						ExtraInfo func() map[string]string
 | 
				
			||||||
 | 
						// CustomAppHelpTemplate the text template for app help topic.
 | 
				
			||||||
 | 
						// cli.go uses text/template to render templates. You can
 | 
				
			||||||
 | 
						// render custom help text by setting this variable.
 | 
				
			||||||
 | 
						CustomAppHelpTemplate string
 | 
				
			||||||
 | 
						// Boolean to enable short-option handling so user can combine several
 | 
				
			||||||
 | 
						// single-character bool arguements into one
 | 
				
			||||||
 | 
						// i.e. foobar -o -v -> foobar -ov
 | 
				
			||||||
 | 
						UseShortOptionHandling bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						didSetup bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Tries to find out when this binary was compiled.
 | 
				
			||||||
 | 
					// Returns the current time if it fails to find it.
 | 
				
			||||||
 | 
					func compileTime() time.Time {
 | 
				
			||||||
 | 
						info, err := os.Stat(os.Args[0])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return time.Now()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return info.ModTime()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewApp creates a new cli Application with some reasonable defaults for Name,
 | 
				
			||||||
 | 
					// Usage, Version and Action.
 | 
				
			||||||
 | 
					func NewApp() *App {
 | 
				
			||||||
 | 
						return &App{
 | 
				
			||||||
 | 
							Name:         filepath.Base(os.Args[0]),
 | 
				
			||||||
 | 
							HelpName:     filepath.Base(os.Args[0]),
 | 
				
			||||||
 | 
							Usage:        "A new cli application",
 | 
				
			||||||
 | 
							UsageText:    "",
 | 
				
			||||||
 | 
							BashComplete: DefaultAppComplete,
 | 
				
			||||||
 | 
							Action:       helpCommand.Action,
 | 
				
			||||||
 | 
							Compiled:     compileTime(),
 | 
				
			||||||
 | 
							Writer:       os.Stdout,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Setup runs initialization code to ensure all data structures are ready for
 | 
				
			||||||
 | 
					// `Run` or inspection prior to `Run`.  It is internally called by `Run`, but
 | 
				
			||||||
 | 
					// will return early if setup has already happened.
 | 
				
			||||||
 | 
					func (a *App) Setup() {
 | 
				
			||||||
 | 
						if a.didSetup {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.didSetup = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.Author != "" || a.Email != "" {
 | 
				
			||||||
 | 
							a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var newCmds []Command
 | 
				
			||||||
 | 
						for _, c := range a.Commands {
 | 
				
			||||||
 | 
							if c.HelpName == "" {
 | 
				
			||||||
 | 
								c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							newCmds = append(newCmds, c)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a.Commands = newCmds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.Command(helpCommand.Name) == nil && !a.HideHelp {
 | 
				
			||||||
 | 
							a.Commands = append(a.Commands, helpCommand)
 | 
				
			||||||
 | 
							if (HelpFlag != BoolFlag{}) {
 | 
				
			||||||
 | 
								a.appendFlag(HelpFlag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.Version == "" {
 | 
				
			||||||
 | 
							a.HideVersion = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !a.HideVersion {
 | 
				
			||||||
 | 
							a.appendFlag(VersionFlag)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.categories = CommandCategories{}
 | 
				
			||||||
 | 
						for _, command := range a.Commands {
 | 
				
			||||||
 | 
							a.categories = a.categories.AddCommand(command.Category, command)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sort.Sort(a.categories)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.Metadata == nil {
 | 
				
			||||||
 | 
							a.Metadata = make(map[string]interface{})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.Writer == nil {
 | 
				
			||||||
 | 
							a.Writer = os.Stdout
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) newFlagSet() (*flag.FlagSet, error) {
 | 
				
			||||||
 | 
						return flagSet(a.Name, a.Flags)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) useShortOptionHandling() bool {
 | 
				
			||||||
 | 
						return a.UseShortOptionHandling
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Run is the entry point to the cli app. Parses the arguments slice and routes
 | 
				
			||||||
 | 
					// to the proper flag/args combination
 | 
				
			||||||
 | 
					func (a *App) Run(arguments []string) (err error) {
 | 
				
			||||||
 | 
						a.Setup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// handle the completion flag separately from the flagset since
 | 
				
			||||||
 | 
						// completion could be attempted after a flag, but before its value was put
 | 
				
			||||||
 | 
						// on the command line. this causes the flagset to interpret the completion
 | 
				
			||||||
 | 
						// flag name as the value of the flag before it which is undesirable
 | 
				
			||||||
 | 
						// note that we can only do this because the shell autocomplete function
 | 
				
			||||||
 | 
						// always appends the completion flag at the end of the command
 | 
				
			||||||
 | 
						shellComplete, arguments := checkShellCompleteFlag(a, arguments)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set, err := a.newFlagSet()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = parseIter(set, a, arguments[1:], shellComplete)
 | 
				
			||||||
 | 
						nerr := normalizeFlags(a.Flags, set)
 | 
				
			||||||
 | 
						context := NewContext(a, set, nil)
 | 
				
			||||||
 | 
						if nerr != nil {
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintln(a.Writer, nerr)
 | 
				
			||||||
 | 
							_ = ShowAppHelp(context)
 | 
				
			||||||
 | 
							return nerr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						context.shellComplete = shellComplete
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if checkCompletions(context) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if a.OnUsageError != nil {
 | 
				
			||||||
 | 
								err := a.OnUsageError(context, err, false)
 | 
				
			||||||
 | 
								a.handleExitCoder(context, err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
 | 
				
			||||||
 | 
							_ = ShowAppHelp(context)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !a.HideHelp && checkHelp(context) {
 | 
				
			||||||
 | 
							_ = ShowAppHelp(context)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !a.HideVersion && checkVersion(context) {
 | 
				
			||||||
 | 
							ShowVersion(context)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cerr := checkRequiredFlags(a.Flags, context)
 | 
				
			||||||
 | 
						if cerr != nil {
 | 
				
			||||||
 | 
							_ = ShowAppHelp(context)
 | 
				
			||||||
 | 
							return cerr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.After != nil && !context.shellComplete {
 | 
				
			||||||
 | 
							defer func() {
 | 
				
			||||||
 | 
								if afterErr := a.After(context); afterErr != nil {
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										err = NewMultiError(err, afterErr)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										err = afterErr
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.Before != nil && !context.shellComplete {
 | 
				
			||||||
 | 
							beforeErr := a.Before(context)
 | 
				
			||||||
 | 
							if beforeErr != nil {
 | 
				
			||||||
 | 
								a.handleExitCoder(context, beforeErr)
 | 
				
			||||||
 | 
								err = beforeErr
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						args := context.Args()
 | 
				
			||||||
 | 
						if args.Present() {
 | 
				
			||||||
 | 
							name := args.First()
 | 
				
			||||||
 | 
							c := a.Command(name)
 | 
				
			||||||
 | 
							if c != nil {
 | 
				
			||||||
 | 
								return c.Run(context)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.Action == nil {
 | 
				
			||||||
 | 
							a.Action = helpCommand.Action
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run default Action
 | 
				
			||||||
 | 
						err = HandleAction(a.Action, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.handleExitCoder(context, err)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Deprecated: instead you should return an error that fulfills cli.ExitCoder
 | 
				
			||||||
 | 
					// to cli.App.Run. This will cause the application to exit with the given eror
 | 
				
			||||||
 | 
					// code in the cli.ExitCoder
 | 
				
			||||||
 | 
					func (a *App) RunAndExitOnError() {
 | 
				
			||||||
 | 
						if err := a.Run(os.Args); err != nil {
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintln(a.errWriter(), err)
 | 
				
			||||||
 | 
							OsExiter(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
 | 
				
			||||||
 | 
					// generate command-specific flags
 | 
				
			||||||
 | 
					func (a *App) RunAsSubcommand(ctx *Context) (err error) {
 | 
				
			||||||
 | 
						// append help to commands
 | 
				
			||||||
 | 
						if len(a.Commands) > 0 {
 | 
				
			||||||
 | 
							if a.Command(helpCommand.Name) == nil && !a.HideHelp {
 | 
				
			||||||
 | 
								a.Commands = append(a.Commands, helpCommand)
 | 
				
			||||||
 | 
								if (HelpFlag != BoolFlag{}) {
 | 
				
			||||||
 | 
									a.appendFlag(HelpFlag)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						newCmds := []Command{}
 | 
				
			||||||
 | 
						for _, c := range a.Commands {
 | 
				
			||||||
 | 
							if c.HelpName == "" {
 | 
				
			||||||
 | 
								c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							newCmds = append(newCmds, c)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a.Commands = newCmds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set, err := a.newFlagSet()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
 | 
				
			||||||
 | 
						nerr := normalizeFlags(a.Flags, set)
 | 
				
			||||||
 | 
						context := NewContext(a, set, ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if nerr != nil {
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintln(a.Writer, nerr)
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintln(a.Writer)
 | 
				
			||||||
 | 
							if len(a.Commands) > 0 {
 | 
				
			||||||
 | 
								_ = ShowSubcommandHelp(context)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = ShowCommandHelp(ctx, context.Args().First())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nerr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if checkCompletions(context) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if a.OnUsageError != nil {
 | 
				
			||||||
 | 
								err = a.OnUsageError(context, err, true)
 | 
				
			||||||
 | 
								a.handleExitCoder(context, err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
 | 
				
			||||||
 | 
							_ = ShowSubcommandHelp(context)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(a.Commands) > 0 {
 | 
				
			||||||
 | 
							if checkSubcommandHelp(context) {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if checkCommandHelp(ctx, context.Args().First()) {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cerr := checkRequiredFlags(a.Flags, context)
 | 
				
			||||||
 | 
						if cerr != nil {
 | 
				
			||||||
 | 
							_ = ShowSubcommandHelp(context)
 | 
				
			||||||
 | 
							return cerr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.After != nil && !context.shellComplete {
 | 
				
			||||||
 | 
							defer func() {
 | 
				
			||||||
 | 
								afterErr := a.After(context)
 | 
				
			||||||
 | 
								if afterErr != nil {
 | 
				
			||||||
 | 
									a.handleExitCoder(context, err)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										err = NewMultiError(err, afterErr)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										err = afterErr
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.Before != nil && !context.shellComplete {
 | 
				
			||||||
 | 
							beforeErr := a.Before(context)
 | 
				
			||||||
 | 
							if beforeErr != nil {
 | 
				
			||||||
 | 
								a.handleExitCoder(context, beforeErr)
 | 
				
			||||||
 | 
								err = beforeErr
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						args := context.Args()
 | 
				
			||||||
 | 
						if args.Present() {
 | 
				
			||||||
 | 
							name := args.First()
 | 
				
			||||||
 | 
							c := a.Command(name)
 | 
				
			||||||
 | 
							if c != nil {
 | 
				
			||||||
 | 
								return c.Run(context)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run default Action
 | 
				
			||||||
 | 
						err = HandleAction(a.Action, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.handleExitCoder(context, err)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Command returns the named command on App. Returns nil if the command does not exist
 | 
				
			||||||
 | 
					func (a *App) Command(name string) *Command {
 | 
				
			||||||
 | 
						for _, c := range a.Commands {
 | 
				
			||||||
 | 
							if c.HasName(name) {
 | 
				
			||||||
 | 
								return &c
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Categories returns a slice containing all the categories with the commands they contain
 | 
				
			||||||
 | 
					func (a *App) Categories() CommandCategories {
 | 
				
			||||||
 | 
						return a.categories
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VisibleCategories returns a slice of categories and commands that are
 | 
				
			||||||
 | 
					// Hidden=false
 | 
				
			||||||
 | 
					func (a *App) VisibleCategories() []*CommandCategory {
 | 
				
			||||||
 | 
						ret := []*CommandCategory{}
 | 
				
			||||||
 | 
						for _, category := range a.categories {
 | 
				
			||||||
 | 
							if visible := func() *CommandCategory {
 | 
				
			||||||
 | 
								for _, command := range category.Commands {
 | 
				
			||||||
 | 
									if !command.Hidden {
 | 
				
			||||||
 | 
										return category
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}(); visible != nil {
 | 
				
			||||||
 | 
								ret = append(ret, visible)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VisibleCommands returns a slice of the Commands with Hidden=false
 | 
				
			||||||
 | 
					func (a *App) VisibleCommands() []Command {
 | 
				
			||||||
 | 
						var ret []Command
 | 
				
			||||||
 | 
						for _, command := range a.Commands {
 | 
				
			||||||
 | 
							if !command.Hidden {
 | 
				
			||||||
 | 
								ret = append(ret, command)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VisibleFlags returns a slice of the Flags with Hidden=false
 | 
				
			||||||
 | 
					func (a *App) VisibleFlags() []Flag {
 | 
				
			||||||
 | 
						return visibleFlags(a.Flags)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) hasFlag(flag Flag) bool {
 | 
				
			||||||
 | 
						for _, f := range a.Flags {
 | 
				
			||||||
 | 
							if flag == f {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) errWriter() io.Writer {
 | 
				
			||||||
 | 
						// When the app ErrWriter is nil use the package level one.
 | 
				
			||||||
 | 
						if a.ErrWriter == nil {
 | 
				
			||||||
 | 
							return ErrWriter
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return a.ErrWriter
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) appendFlag(flag Flag) {
 | 
				
			||||||
 | 
						if !a.hasFlag(flag) {
 | 
				
			||||||
 | 
							a.Flags = append(a.Flags, flag)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) handleExitCoder(context *Context, err error) {
 | 
				
			||||||
 | 
						if a.ExitErrHandler != nil {
 | 
				
			||||||
 | 
							a.ExitErrHandler(context, err)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							HandleExitCoder(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Author represents someone who has contributed to a cli project.
 | 
				
			||||||
 | 
					type Author struct {
 | 
				
			||||||
 | 
						Name  string // The Authors name
 | 
				
			||||||
 | 
						Email string // The Authors email
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
 | 
				
			||||||
 | 
					func (a Author) String() string {
 | 
				
			||||||
 | 
						e := ""
 | 
				
			||||||
 | 
						if a.Email != "" {
 | 
				
			||||||
 | 
							e = " <" + a.Email + ">"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Sprintf("%v%v", a.Name, e)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HandleAction attempts to figure out which Action signature was used.  If
 | 
				
			||||||
 | 
					// it's an ActionFunc or a func with the legacy signature for Action, the func
 | 
				
			||||||
 | 
					// is run!
 | 
				
			||||||
 | 
					func HandleAction(action interface{}, context *Context) (err error) {
 | 
				
			||||||
 | 
						switch a := action.(type) {
 | 
				
			||||||
 | 
						case ActionFunc:
 | 
				
			||||||
 | 
							return a(context)
 | 
				
			||||||
 | 
						case func(*Context) error:
 | 
				
			||||||
 | 
							return a(context)
 | 
				
			||||||
 | 
						case func(*Context): // deprecated function signature
 | 
				
			||||||
 | 
							a(context)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errInvalidActionType
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CommandCategories is a slice of *CommandCategory.
 | 
				
			||||||
 | 
					type CommandCategories []*CommandCategory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CommandCategory is a category containing commands.
 | 
				
			||||||
 | 
					type CommandCategory struct {
 | 
				
			||||||
 | 
						Name     string
 | 
				
			||||||
 | 
						Commands Commands
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c CommandCategories) Less(i, j int) bool {
 | 
				
			||||||
 | 
						return lexicographicLess(c[i].Name, c[j].Name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c CommandCategories) Len() int {
 | 
				
			||||||
 | 
						return len(c)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c CommandCategories) Swap(i, j int) {
 | 
				
			||||||
 | 
						c[i], c[j] = c[j], c[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AddCommand adds a command to a category.
 | 
				
			||||||
 | 
					func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
 | 
				
			||||||
 | 
						for _, commandCategory := range c {
 | 
				
			||||||
 | 
							if commandCategory.Name == category {
 | 
				
			||||||
 | 
								commandCategory.Commands = append(commandCategory.Commands, command)
 | 
				
			||||||
 | 
								return c
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VisibleCommands returns a slice of the Commands with Hidden=false
 | 
				
			||||||
 | 
					func (c *CommandCategory) VisibleCommands() []Command {
 | 
				
			||||||
 | 
						ret := []Command{}
 | 
				
			||||||
 | 
						for _, command := range c.Commands {
 | 
				
			||||||
 | 
							if !command.Hidden {
 | 
				
			||||||
 | 
								ret = append(ret, command)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					// Package cli provides a minimal framework for creating and organizing command line
 | 
				
			||||||
 | 
					// Go applications. cli is designed to be easy to understand and write, the most simple
 | 
				
			||||||
 | 
					// cli application can be written as follows:
 | 
				
			||||||
 | 
					//   func main() {
 | 
				
			||||||
 | 
					//     cli.NewApp().Run(os.Args)
 | 
				
			||||||
 | 
					//   }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Of course this application does not do much, so let's make this an actual application:
 | 
				
			||||||
 | 
					//   func main() {
 | 
				
			||||||
 | 
					//     app := cli.NewApp()
 | 
				
			||||||
 | 
					//     app.Name = "greet"
 | 
				
			||||||
 | 
					//     app.Usage = "say a greeting"
 | 
				
			||||||
 | 
					//     app.Action = func(c *cli.Context) error {
 | 
				
			||||||
 | 
					//       println("Greetings")
 | 
				
			||||||
 | 
					//       return nil
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     app.Run(os.Args)
 | 
				
			||||||
 | 
					//   }
 | 
				
			||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go
 | 
				
			||||||
@ -0,0 +1,386 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Command is a subcommand for a cli.App.
 | 
				
			||||||
 | 
					type Command struct {
 | 
				
			||||||
 | 
						// The name of the command
 | 
				
			||||||
 | 
						Name string
 | 
				
			||||||
 | 
						// short name of the command. Typically one character (deprecated, use `Aliases`)
 | 
				
			||||||
 | 
						ShortName string
 | 
				
			||||||
 | 
						// A list of aliases for the command
 | 
				
			||||||
 | 
						Aliases []string
 | 
				
			||||||
 | 
						// A short description of the usage of this command
 | 
				
			||||||
 | 
						Usage string
 | 
				
			||||||
 | 
						// Custom text to show on USAGE section of help
 | 
				
			||||||
 | 
						UsageText string
 | 
				
			||||||
 | 
						// A longer explanation of how the command works
 | 
				
			||||||
 | 
						Description string
 | 
				
			||||||
 | 
						// A short description of the arguments of this command
 | 
				
			||||||
 | 
						ArgsUsage string
 | 
				
			||||||
 | 
						// The category the command is part of
 | 
				
			||||||
 | 
						Category string
 | 
				
			||||||
 | 
						// The function to call when checking for bash command completions
 | 
				
			||||||
 | 
						BashComplete BashCompleteFunc
 | 
				
			||||||
 | 
						// An action to execute before any sub-subcommands are run, but after the context is ready
 | 
				
			||||||
 | 
						// If a non-nil error is returned, no sub-subcommands are run
 | 
				
			||||||
 | 
						Before BeforeFunc
 | 
				
			||||||
 | 
						// An action to execute after any subcommands are run, but after the subcommand has finished
 | 
				
			||||||
 | 
						// It is run even if Action() panics
 | 
				
			||||||
 | 
						After AfterFunc
 | 
				
			||||||
 | 
						// The function to call when this command is invoked
 | 
				
			||||||
 | 
						Action interface{}
 | 
				
			||||||
 | 
						// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
 | 
				
			||||||
 | 
						// of deprecation period has passed, maybe?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Execute this function if a usage error occurs.
 | 
				
			||||||
 | 
						OnUsageError OnUsageErrorFunc
 | 
				
			||||||
 | 
						// List of child commands
 | 
				
			||||||
 | 
						Subcommands Commands
 | 
				
			||||||
 | 
						// List of flags to parse
 | 
				
			||||||
 | 
						Flags []Flag
 | 
				
			||||||
 | 
						// Treat all flags as normal arguments if true
 | 
				
			||||||
 | 
						SkipFlagParsing bool
 | 
				
			||||||
 | 
						// Skip argument reordering which attempts to move flags before arguments,
 | 
				
			||||||
 | 
						// but only works if all flags appear after all arguments. This behavior was
 | 
				
			||||||
 | 
						// removed n version 2 since it only works under specific conditions so we
 | 
				
			||||||
 | 
						// backport here by exposing it as an option for compatibility.
 | 
				
			||||||
 | 
						SkipArgReorder bool
 | 
				
			||||||
 | 
						// Boolean to hide built-in help command
 | 
				
			||||||
 | 
						HideHelp bool
 | 
				
			||||||
 | 
						// Boolean to hide this command from help or completion
 | 
				
			||||||
 | 
						Hidden bool
 | 
				
			||||||
 | 
						// Boolean to enable short-option handling so user can combine several
 | 
				
			||||||
 | 
						// single-character bool arguments into one
 | 
				
			||||||
 | 
						// i.e. foobar -o -v -> foobar -ov
 | 
				
			||||||
 | 
						UseShortOptionHandling bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Full name of command for help, defaults to full command name, including parent commands.
 | 
				
			||||||
 | 
						HelpName        string
 | 
				
			||||||
 | 
						commandNamePath []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// CustomHelpTemplate the text template for the command help topic.
 | 
				
			||||||
 | 
						// cli.go uses text/template to render templates. You can
 | 
				
			||||||
 | 
						// render custom help text by setting this variable.
 | 
				
			||||||
 | 
						CustomHelpTemplate string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CommandsByName []Command
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c CommandsByName) Len() int {
 | 
				
			||||||
 | 
						return len(c)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c CommandsByName) Less(i, j int) bool {
 | 
				
			||||||
 | 
						return lexicographicLess(c[i].Name, c[j].Name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c CommandsByName) Swap(i, j int) {
 | 
				
			||||||
 | 
						c[i], c[j] = c[j], c[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FullName returns the full name of the command.
 | 
				
			||||||
 | 
					// For subcommands this ensures that parent commands are part of the command path
 | 
				
			||||||
 | 
					func (c Command) FullName() string {
 | 
				
			||||||
 | 
						if c.commandNamePath == nil {
 | 
				
			||||||
 | 
							return c.Name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return strings.Join(c.commandNamePath, " ")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Commands is a slice of Command
 | 
				
			||||||
 | 
					type Commands []Command
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
 | 
				
			||||||
 | 
					func (c Command) Run(ctx *Context) (err error) {
 | 
				
			||||||
 | 
						if !c.SkipFlagParsing {
 | 
				
			||||||
 | 
							if len(c.Subcommands) > 0 {
 | 
				
			||||||
 | 
								return c.startApp(ctx)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !c.HideHelp && (HelpFlag != BoolFlag{}) {
 | 
				
			||||||
 | 
							// append help to flags
 | 
				
			||||||
 | 
							c.Flags = append(
 | 
				
			||||||
 | 
								c.Flags,
 | 
				
			||||||
 | 
								HelpFlag,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.App.UseShortOptionHandling {
 | 
				
			||||||
 | 
							c.UseShortOptionHandling = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set, err := c.parseFlags(ctx.Args().Tail(), ctx.shellComplete)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						context := NewContext(ctx.App, set, ctx)
 | 
				
			||||||
 | 
						context.Command = c
 | 
				
			||||||
 | 
						if checkCommandCompletions(context, c.Name) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if c.OnUsageError != nil {
 | 
				
			||||||
 | 
								err := c.OnUsageError(context, err, false)
 | 
				
			||||||
 | 
								context.App.handleExitCoder(context, err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintln(context.App.Writer)
 | 
				
			||||||
 | 
							_ = ShowCommandHelp(context, c.Name)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if checkCommandHelp(context, c.Name) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cerr := checkRequiredFlags(c.Flags, context)
 | 
				
			||||||
 | 
						if cerr != nil {
 | 
				
			||||||
 | 
							_ = ShowCommandHelp(context, c.Name)
 | 
				
			||||||
 | 
							return cerr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.After != nil {
 | 
				
			||||||
 | 
							defer func() {
 | 
				
			||||||
 | 
								afterErr := c.After(context)
 | 
				
			||||||
 | 
								if afterErr != nil {
 | 
				
			||||||
 | 
									context.App.handleExitCoder(context, err)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										err = NewMultiError(err, afterErr)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										err = afterErr
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.Before != nil {
 | 
				
			||||||
 | 
							err = c.Before(context)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								context.App.handleExitCoder(context, err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.Action == nil {
 | 
				
			||||||
 | 
							c.Action = helpSubcommand.Action
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = HandleAction(c.Action, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							context.App.handleExitCoder(context, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
 | 
				
			||||||
 | 
						if c.SkipFlagParsing {
 | 
				
			||||||
 | 
							set, err := c.newFlagSet()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return set, set.Parse(append([]string{"--"}, args...))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !c.SkipArgReorder {
 | 
				
			||||||
 | 
							args = reorderArgs(c.Flags, args)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set, err := c.newFlagSet()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = parseIter(set, c, args, shellComplete)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = normalizeFlags(c.Flags, set)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return set, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Command) newFlagSet() (*flag.FlagSet, error) {
 | 
				
			||||||
 | 
						return flagSet(c.Name, c.Flags)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Command) useShortOptionHandling() bool {
 | 
				
			||||||
 | 
						return c.UseShortOptionHandling
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reorderArgs moves all flags (via reorderedArgs) before the rest of
 | 
				
			||||||
 | 
					// the arguments (remainingArgs) as this is what flag expects.
 | 
				
			||||||
 | 
					func reorderArgs(commandFlags []Flag, args []string) []string {
 | 
				
			||||||
 | 
						var remainingArgs, reorderedArgs []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nextIndexMayContainValue := false
 | 
				
			||||||
 | 
						for i, arg := range args {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// if we're expecting an option-value, check if this arg is a value, in
 | 
				
			||||||
 | 
							// which case it should be re-ordered next to its associated flag
 | 
				
			||||||
 | 
							if nextIndexMayContainValue && !argIsFlag(commandFlags, arg) {
 | 
				
			||||||
 | 
								nextIndexMayContainValue = false
 | 
				
			||||||
 | 
								reorderedArgs = append(reorderedArgs, arg)
 | 
				
			||||||
 | 
							} else if arg == "--" {
 | 
				
			||||||
 | 
								// don't reorder any args after the -- delimiter As described in the POSIX spec:
 | 
				
			||||||
 | 
								// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
 | 
				
			||||||
 | 
								// > Guideline 10:
 | 
				
			||||||
 | 
								// >   The first -- argument that is not an option-argument should be accepted
 | 
				
			||||||
 | 
								// >   as a delimiter indicating the end of options. Any following arguments
 | 
				
			||||||
 | 
								// >   should be treated as operands, even if they begin with the '-' character.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// make sure the "--" delimiter itself is at the start
 | 
				
			||||||
 | 
								remainingArgs = append([]string{"--"}, remainingArgs...)
 | 
				
			||||||
 | 
								remainingArgs = append(remainingArgs, args[i+1:]...)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
								// checks if this is an arg that should be re-ordered
 | 
				
			||||||
 | 
							} else if argIsFlag(commandFlags, arg) {
 | 
				
			||||||
 | 
								// we have determined that this is a flag that we should re-order
 | 
				
			||||||
 | 
								reorderedArgs = append(reorderedArgs, arg)
 | 
				
			||||||
 | 
								// if this arg does not contain a "=", then the next index may contain the value for this flag
 | 
				
			||||||
 | 
								nextIndexMayContainValue = !strings.Contains(arg, "=")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// simply append any remaining args
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								remainingArgs = append(remainingArgs, arg)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return append(reorderedArgs, remainingArgs...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// argIsFlag checks if an arg is one of our command flags
 | 
				
			||||||
 | 
					func argIsFlag(commandFlags []Flag, arg string) bool {
 | 
				
			||||||
 | 
						if arg == "-" || arg == "--" {
 | 
				
			||||||
 | 
							// `-` is never a flag
 | 
				
			||||||
 | 
							// `--` is an option-value when following a flag, and a delimiter indicating the end of options in other cases.
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// flags always start with a -
 | 
				
			||||||
 | 
						if !strings.HasPrefix(arg, "-") {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// this line turns `--flag` into `flag`
 | 
				
			||||||
 | 
						if strings.HasPrefix(arg, "--") {
 | 
				
			||||||
 | 
							arg = strings.Replace(arg, "-", "", 2)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// this line turns `-flag` into `flag`
 | 
				
			||||||
 | 
						if strings.HasPrefix(arg, "-") {
 | 
				
			||||||
 | 
							arg = strings.Replace(arg, "-", "", 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// this line turns `flag=value` into `flag`
 | 
				
			||||||
 | 
						arg = strings.Split(arg, "=")[0]
 | 
				
			||||||
 | 
						// look through all the flags, to see if the `arg` is one of our flags
 | 
				
			||||||
 | 
						for _, flag := range commandFlags {
 | 
				
			||||||
 | 
							for _, key := range strings.Split(flag.GetName(), ",") {
 | 
				
			||||||
 | 
								key := strings.TrimSpace(key)
 | 
				
			||||||
 | 
								if key == arg {
 | 
				
			||||||
 | 
									return true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// return false if this arg was not one of our flags
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Names returns the names including short names and aliases.
 | 
				
			||||||
 | 
					func (c Command) Names() []string {
 | 
				
			||||||
 | 
						names := []string{c.Name}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.ShortName != "" {
 | 
				
			||||||
 | 
							names = append(names, c.ShortName)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return append(names, c.Aliases...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HasName returns true if Command.Name or Command.ShortName matches given name
 | 
				
			||||||
 | 
					func (c Command) HasName(name string) bool {
 | 
				
			||||||
 | 
						for _, n := range c.Names() {
 | 
				
			||||||
 | 
							if n == name {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c Command) startApp(ctx *Context) error {
 | 
				
			||||||
 | 
						app := NewApp()
 | 
				
			||||||
 | 
						app.Metadata = ctx.App.Metadata
 | 
				
			||||||
 | 
						app.ExitErrHandler = ctx.App.ExitErrHandler
 | 
				
			||||||
 | 
						// set the name and usage
 | 
				
			||||||
 | 
						app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
 | 
				
			||||||
 | 
						if c.HelpName == "" {
 | 
				
			||||||
 | 
							app.HelpName = c.HelpName
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							app.HelpName = app.Name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.Usage = c.Usage
 | 
				
			||||||
 | 
						app.Description = c.Description
 | 
				
			||||||
 | 
						app.ArgsUsage = c.ArgsUsage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set CommandNotFound
 | 
				
			||||||
 | 
						app.CommandNotFound = ctx.App.CommandNotFound
 | 
				
			||||||
 | 
						app.CustomAppHelpTemplate = c.CustomHelpTemplate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set the flags and commands
 | 
				
			||||||
 | 
						app.Commands = c.Subcommands
 | 
				
			||||||
 | 
						app.Flags = c.Flags
 | 
				
			||||||
 | 
						app.HideHelp = c.HideHelp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.Version = ctx.App.Version
 | 
				
			||||||
 | 
						app.HideVersion = ctx.App.HideVersion
 | 
				
			||||||
 | 
						app.Compiled = ctx.App.Compiled
 | 
				
			||||||
 | 
						app.Author = ctx.App.Author
 | 
				
			||||||
 | 
						app.Email = ctx.App.Email
 | 
				
			||||||
 | 
						app.Writer = ctx.App.Writer
 | 
				
			||||||
 | 
						app.ErrWriter = ctx.App.ErrWriter
 | 
				
			||||||
 | 
						app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.categories = CommandCategories{}
 | 
				
			||||||
 | 
						for _, command := range c.Subcommands {
 | 
				
			||||||
 | 
							app.categories = app.categories.AddCommand(command.Category, command)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sort.Sort(app.categories)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// bash completion
 | 
				
			||||||
 | 
						app.EnableBashCompletion = ctx.App.EnableBashCompletion
 | 
				
			||||||
 | 
						if c.BashComplete != nil {
 | 
				
			||||||
 | 
							app.BashComplete = c.BashComplete
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set the actions
 | 
				
			||||||
 | 
						app.Before = c.Before
 | 
				
			||||||
 | 
						app.After = c.After
 | 
				
			||||||
 | 
						if c.Action != nil {
 | 
				
			||||||
 | 
							app.Action = c.Action
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							app.Action = helpSubcommand.Action
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						app.OnUsageError = c.OnUsageError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for index, cc := range app.Commands {
 | 
				
			||||||
 | 
							app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return app.RunAsSubcommand(ctx)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VisibleFlags returns a slice of the Flags with Hidden=false
 | 
				
			||||||
 | 
					func (c Command) VisibleFlags() []Flag {
 | 
				
			||||||
 | 
						return visibleFlags(c.Flags)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,348 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Context is a type that is passed through to
 | 
				
			||||||
 | 
					// each Handler action in a cli application. Context
 | 
				
			||||||
 | 
					// can be used to retrieve context-specific Args and
 | 
				
			||||||
 | 
					// parsed command-line options.
 | 
				
			||||||
 | 
					type Context struct {
 | 
				
			||||||
 | 
						App           *App
 | 
				
			||||||
 | 
						Command       Command
 | 
				
			||||||
 | 
						shellComplete bool
 | 
				
			||||||
 | 
						flagSet       *flag.FlagSet
 | 
				
			||||||
 | 
						setFlags      map[string]bool
 | 
				
			||||||
 | 
						parentContext *Context
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewContext creates a new context. For use in when invoking an App or Command action.
 | 
				
			||||||
 | 
					func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
 | 
				
			||||||
 | 
						c := &Context{App: app, flagSet: set, parentContext: parentCtx}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if parentCtx != nil {
 | 
				
			||||||
 | 
							c.shellComplete = parentCtx.shellComplete
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NumFlags returns the number of flags set
 | 
				
			||||||
 | 
					func (c *Context) NumFlags() int {
 | 
				
			||||||
 | 
						return c.flagSet.NFlag()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set sets a context flag to a value.
 | 
				
			||||||
 | 
					func (c *Context) Set(name, value string) error {
 | 
				
			||||||
 | 
						c.setFlags = nil
 | 
				
			||||||
 | 
						return c.flagSet.Set(name, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalSet sets a context flag to a value on the global flagset
 | 
				
			||||||
 | 
					func (c *Context) GlobalSet(name, value string) error {
 | 
				
			||||||
 | 
						globalContext(c).setFlags = nil
 | 
				
			||||||
 | 
						return globalContext(c).flagSet.Set(name, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsSet determines if the flag was actually set
 | 
				
			||||||
 | 
					func (c *Context) IsSet(name string) bool {
 | 
				
			||||||
 | 
						if c.setFlags == nil {
 | 
				
			||||||
 | 
							c.setFlags = make(map[string]bool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c.flagSet.Visit(func(f *flag.Flag) {
 | 
				
			||||||
 | 
								c.setFlags[f.Name] = true
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c.flagSet.VisitAll(func(f *flag.Flag) {
 | 
				
			||||||
 | 
								if _, ok := c.setFlags[f.Name]; ok {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								c.setFlags[f.Name] = false
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// XXX hack to support IsSet for flags with EnvVar
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// There isn't an easy way to do this with the current implementation since
 | 
				
			||||||
 | 
							// whether a flag was set via an environment variable is very difficult to
 | 
				
			||||||
 | 
							// determine here. Instead, we intend to introduce a backwards incompatible
 | 
				
			||||||
 | 
							// change in version 2 to add `IsSet` to the Flag interface to push the
 | 
				
			||||||
 | 
							// responsibility closer to where the information required to determine
 | 
				
			||||||
 | 
							// whether a flag is set by non-standard means such as environment
 | 
				
			||||||
 | 
							// variables is available.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// See https://github.com/urfave/cli/issues/294 for additional discussion
 | 
				
			||||||
 | 
							flags := c.Command.Flags
 | 
				
			||||||
 | 
							if c.Command.Name == "" { // cannot == Command{} since it contains slice types
 | 
				
			||||||
 | 
								if c.App != nil {
 | 
				
			||||||
 | 
									flags = c.App.Flags
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, f := range flags {
 | 
				
			||||||
 | 
								eachName(f.GetName(), func(name string) {
 | 
				
			||||||
 | 
									if isSet, ok := c.setFlags[name]; isSet || !ok {
 | 
				
			||||||
 | 
										// Check if a flag is set
 | 
				
			||||||
 | 
										if isSet {
 | 
				
			||||||
 | 
											// If the flag is set, also set its other aliases
 | 
				
			||||||
 | 
											eachName(f.GetName(), func(name string) {
 | 
				
			||||||
 | 
												c.setFlags[name] = true
 | 
				
			||||||
 | 
											})
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									val := reflect.ValueOf(f)
 | 
				
			||||||
 | 
									if val.Kind() == reflect.Ptr {
 | 
				
			||||||
 | 
										val = val.Elem()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									filePathValue := val.FieldByName("FilePath")
 | 
				
			||||||
 | 
									if filePathValue.IsValid() {
 | 
				
			||||||
 | 
										eachName(filePathValue.String(), func(filePath string) {
 | 
				
			||||||
 | 
											if _, err := os.Stat(filePath); err == nil {
 | 
				
			||||||
 | 
												c.setFlags[name] = true
 | 
				
			||||||
 | 
												return
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									envVarValue := val.FieldByName("EnvVar")
 | 
				
			||||||
 | 
									if envVarValue.IsValid() {
 | 
				
			||||||
 | 
										eachName(envVarValue.String(), func(envVar string) {
 | 
				
			||||||
 | 
											envVar = strings.TrimSpace(envVar)
 | 
				
			||||||
 | 
											if _, ok := syscall.Getenv(envVar); ok {
 | 
				
			||||||
 | 
												c.setFlags[name] = true
 | 
				
			||||||
 | 
												return
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.setFlags[name]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalIsSet determines if the global flag was actually set
 | 
				
			||||||
 | 
					func (c *Context) GlobalIsSet(name string) bool {
 | 
				
			||||||
 | 
						ctx := c
 | 
				
			||||||
 | 
						if ctx.parentContext != nil {
 | 
				
			||||||
 | 
							ctx = ctx.parentContext
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for ; ctx != nil; ctx = ctx.parentContext {
 | 
				
			||||||
 | 
							if ctx.IsSet(name) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagNames returns a slice of flag names used in this context.
 | 
				
			||||||
 | 
					func (c *Context) FlagNames() (names []string) {
 | 
				
			||||||
 | 
						for _, f := range c.Command.Flags {
 | 
				
			||||||
 | 
							name := strings.Split(f.GetName(), ",")[0]
 | 
				
			||||||
 | 
							if name == "help" {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							names = append(names, name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalFlagNames returns a slice of global flag names used by the app.
 | 
				
			||||||
 | 
					func (c *Context) GlobalFlagNames() (names []string) {
 | 
				
			||||||
 | 
						for _, f := range c.App.Flags {
 | 
				
			||||||
 | 
							name := strings.Split(f.GetName(), ",")[0]
 | 
				
			||||||
 | 
							if name == "help" || name == "version" {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							names = append(names, name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parent returns the parent context, if any
 | 
				
			||||||
 | 
					func (c *Context) Parent() *Context {
 | 
				
			||||||
 | 
						return c.parentContext
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// value returns the value of the flag coressponding to `name`
 | 
				
			||||||
 | 
					func (c *Context) value(name string) interface{} {
 | 
				
			||||||
 | 
						return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Args contains apps console arguments
 | 
				
			||||||
 | 
					type Args []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Args returns the command line arguments associated with the context.
 | 
				
			||||||
 | 
					func (c *Context) Args() Args {
 | 
				
			||||||
 | 
						args := Args(c.flagSet.Args())
 | 
				
			||||||
 | 
						return args
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NArg returns the number of the command line arguments.
 | 
				
			||||||
 | 
					func (c *Context) NArg() int {
 | 
				
			||||||
 | 
						return len(c.Args())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get returns the nth argument, or else a blank string
 | 
				
			||||||
 | 
					func (a Args) Get(n int) string {
 | 
				
			||||||
 | 
						if len(a) > n {
 | 
				
			||||||
 | 
							return a[n]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// First returns the first argument, or else a blank string
 | 
				
			||||||
 | 
					func (a Args) First() string {
 | 
				
			||||||
 | 
						return a.Get(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Tail returns the rest of the arguments (not the first one)
 | 
				
			||||||
 | 
					// or else an empty string slice
 | 
				
			||||||
 | 
					func (a Args) Tail() []string {
 | 
				
			||||||
 | 
						if len(a) >= 2 {
 | 
				
			||||||
 | 
							return []string(a)[1:]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []string{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Present checks if there are any arguments present
 | 
				
			||||||
 | 
					func (a Args) Present() bool {
 | 
				
			||||||
 | 
						return len(a) != 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Swap swaps arguments at the given indexes
 | 
				
			||||||
 | 
					func (a Args) Swap(from, to int) error {
 | 
				
			||||||
 | 
						if from >= len(a) || to >= len(a) {
 | 
				
			||||||
 | 
							return errors.New("index out of range")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a[from], a[to] = a[to], a[from]
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func globalContext(ctx *Context) *Context {
 | 
				
			||||||
 | 
						if ctx == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							if ctx.parentContext == nil {
 | 
				
			||||||
 | 
								return ctx
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx = ctx.parentContext
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
 | 
				
			||||||
 | 
						if ctx.parentContext != nil {
 | 
				
			||||||
 | 
							ctx = ctx.parentContext
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for ; ctx != nil; ctx = ctx.parentContext {
 | 
				
			||||||
 | 
							if f := ctx.flagSet.Lookup(name); f != nil {
 | 
				
			||||||
 | 
								return ctx.flagSet
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
 | 
				
			||||||
 | 
						switch ff.Value.(type) {
 | 
				
			||||||
 | 
						case *StringSlice:
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							_ = set.Set(name, ff.Value.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						visited := make(map[string]bool)
 | 
				
			||||||
 | 
						set.Visit(func(f *flag.Flag) {
 | 
				
			||||||
 | 
							visited[f.Name] = true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						for _, f := range flags {
 | 
				
			||||||
 | 
							parts := strings.Split(f.GetName(), ",")
 | 
				
			||||||
 | 
							if len(parts) == 1 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var ff *flag.Flag
 | 
				
			||||||
 | 
							for _, name := range parts {
 | 
				
			||||||
 | 
								name = strings.Trim(name, " ")
 | 
				
			||||||
 | 
								if visited[name] {
 | 
				
			||||||
 | 
									if ff != nil {
 | 
				
			||||||
 | 
										return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									ff = set.Lookup(name)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if ff == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, name := range parts {
 | 
				
			||||||
 | 
								name = strings.Trim(name, " ")
 | 
				
			||||||
 | 
								if !visited[name] {
 | 
				
			||||||
 | 
									copyFlag(name, ff, set)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type requiredFlagsErr interface {
 | 
				
			||||||
 | 
						error
 | 
				
			||||||
 | 
						getMissingFlags() []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type errRequiredFlags struct {
 | 
				
			||||||
 | 
						missingFlags []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *errRequiredFlags) Error() string {
 | 
				
			||||||
 | 
						numberOfMissingFlags := len(e.missingFlags)
 | 
				
			||||||
 | 
						if numberOfMissingFlags == 1 {
 | 
				
			||||||
 | 
							return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						joinedMissingFlags := strings.Join(e.missingFlags, ", ")
 | 
				
			||||||
 | 
						return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *errRequiredFlags) getMissingFlags() []string {
 | 
				
			||||||
 | 
						return e.missingFlags
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
 | 
				
			||||||
 | 
						var missingFlags []string
 | 
				
			||||||
 | 
						for _, f := range flags {
 | 
				
			||||||
 | 
							if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
 | 
				
			||||||
 | 
								var flagPresent bool
 | 
				
			||||||
 | 
								var flagName string
 | 
				
			||||||
 | 
								for _, key := range strings.Split(f.GetName(), ",") {
 | 
				
			||||||
 | 
									key = strings.TrimSpace(key)
 | 
				
			||||||
 | 
									if len(key) > 1 {
 | 
				
			||||||
 | 
										flagName = key
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if context.IsSet(key) {
 | 
				
			||||||
 | 
										flagPresent = true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !flagPresent && flagName != "" {
 | 
				
			||||||
 | 
									missingFlags = append(missingFlags, flagName)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(missingFlags) != 0 {
 | 
				
			||||||
 | 
							return &errRequiredFlags{missingFlags: missingFlags}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,151 @@
 | 
				
			|||||||
 | 
					//go:build !urfave_cli_no_docs
 | 
				
			||||||
 | 
					// +build !urfave_cli_no_docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"text/template"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/cpuguy83/go-md2man/v2/md2man"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToMarkdown creates a markdown string for the `*App`
 | 
				
			||||||
 | 
					// The function errors if either parsing or writing of the string fails.
 | 
				
			||||||
 | 
					func (a *App) ToMarkdown() (string, error) {
 | 
				
			||||||
 | 
						var w bytes.Buffer
 | 
				
			||||||
 | 
						if err := a.writeDocTemplate(&w); err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return w.String(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToMan creates a man page string for the `*App`
 | 
				
			||||||
 | 
					// The function errors if either parsing or writing of the string fails.
 | 
				
			||||||
 | 
					func (a *App) ToMan() (string, error) {
 | 
				
			||||||
 | 
						var w bytes.Buffer
 | 
				
			||||||
 | 
						if err := a.writeDocTemplate(&w); err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						man := md2man.Render(w.Bytes())
 | 
				
			||||||
 | 
						return string(man), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type cliTemplate struct {
 | 
				
			||||||
 | 
						App          *App
 | 
				
			||||||
 | 
						Commands     []string
 | 
				
			||||||
 | 
						GlobalArgs   []string
 | 
				
			||||||
 | 
						SynopsisArgs []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) writeDocTemplate(w io.Writer) error {
 | 
				
			||||||
 | 
						const name = "cli"
 | 
				
			||||||
 | 
						t, err := template.New(name).Parse(MarkdownDocTemplate)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return t.ExecuteTemplate(w, name, &cliTemplate{
 | 
				
			||||||
 | 
							App:          a,
 | 
				
			||||||
 | 
							Commands:     prepareCommands(a.Commands, 0),
 | 
				
			||||||
 | 
							GlobalArgs:   prepareArgsWithValues(a.Flags),
 | 
				
			||||||
 | 
							SynopsisArgs: prepareArgsSynopsis(a.Flags),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func prepareCommands(commands []Command, level int) []string {
 | 
				
			||||||
 | 
						coms := []string{}
 | 
				
			||||||
 | 
						for i := range commands {
 | 
				
			||||||
 | 
							command := &commands[i]
 | 
				
			||||||
 | 
							if command.Hidden {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							usage := ""
 | 
				
			||||||
 | 
							if command.Usage != "" {
 | 
				
			||||||
 | 
								usage = command.Usage
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							prepared := fmt.Sprintf("%s %s\n\n%s\n",
 | 
				
			||||||
 | 
								strings.Repeat("#", level+2),
 | 
				
			||||||
 | 
								strings.Join(command.Names(), ", "),
 | 
				
			||||||
 | 
								usage,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							flags := prepareArgsWithValues(command.Flags)
 | 
				
			||||||
 | 
							if len(flags) > 0 {
 | 
				
			||||||
 | 
								prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							coms = append(coms, prepared)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// recursevly iterate subcommands
 | 
				
			||||||
 | 
							if len(command.Subcommands) > 0 {
 | 
				
			||||||
 | 
								coms = append(
 | 
				
			||||||
 | 
									coms,
 | 
				
			||||||
 | 
									prepareCommands(command.Subcommands, level+1)...,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return coms
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func prepareArgsWithValues(flags []Flag) []string {
 | 
				
			||||||
 | 
						return prepareFlags(flags, ", ", "**", "**", `""`, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func prepareArgsSynopsis(flags []Flag) []string {
 | 
				
			||||||
 | 
						return prepareFlags(flags, "|", "[", "]", "[value]", false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func prepareFlags(
 | 
				
			||||||
 | 
						flags []Flag,
 | 
				
			||||||
 | 
						sep, opener, closer, value string,
 | 
				
			||||||
 | 
						addDetails bool,
 | 
				
			||||||
 | 
					) []string {
 | 
				
			||||||
 | 
						args := []string{}
 | 
				
			||||||
 | 
						for _, f := range flags {
 | 
				
			||||||
 | 
							flag, ok := f.(DocGenerationFlag)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							modifiedArg := opener
 | 
				
			||||||
 | 
							for _, s := range strings.Split(flag.GetName(), ",") {
 | 
				
			||||||
 | 
								trimmed := strings.TrimSpace(s)
 | 
				
			||||||
 | 
								if len(modifiedArg) > len(opener) {
 | 
				
			||||||
 | 
									modifiedArg += sep
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if len(trimmed) > 1 {
 | 
				
			||||||
 | 
									modifiedArg += fmt.Sprintf("--%s", trimmed)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									modifiedArg += fmt.Sprintf("-%s", trimmed)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							modifiedArg += closer
 | 
				
			||||||
 | 
							if flag.TakesValue() {
 | 
				
			||||||
 | 
								modifiedArg += fmt.Sprintf("=%s", value)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if addDetails {
 | 
				
			||||||
 | 
								modifiedArg += flagDetails(flag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							args = append(args, modifiedArg+"\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sort.Strings(args)
 | 
				
			||||||
 | 
						return args
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// flagDetails returns a string containing the flags metadata
 | 
				
			||||||
 | 
					func flagDetails(flag DocGenerationFlag) string {
 | 
				
			||||||
 | 
						description := flag.GetUsage()
 | 
				
			||||||
 | 
						value := flag.GetValue()
 | 
				
			||||||
 | 
						if value != "" {
 | 
				
			||||||
 | 
							description += " (default: " + value + ")"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ": " + description
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,115 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
 | 
				
			||||||
 | 
					var OsExiter = os.Exit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrWriter is used to write errors to the user. This can be anything
 | 
				
			||||||
 | 
					// implementing the io.Writer interface and defaults to os.Stderr.
 | 
				
			||||||
 | 
					var ErrWriter io.Writer = os.Stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MultiError is an error that wraps multiple errors.
 | 
				
			||||||
 | 
					type MultiError struct {
 | 
				
			||||||
 | 
						Errors []error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewMultiError creates a new MultiError. Pass in one or more errors.
 | 
				
			||||||
 | 
					func NewMultiError(err ...error) MultiError {
 | 
				
			||||||
 | 
						return MultiError{Errors: err}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Error implements the error interface.
 | 
				
			||||||
 | 
					func (m MultiError) Error() string {
 | 
				
			||||||
 | 
						errs := make([]string, len(m.Errors))
 | 
				
			||||||
 | 
						for i, err := range m.Errors {
 | 
				
			||||||
 | 
							errs[i] = err.Error()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return strings.Join(errs, "\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ErrorFormatter interface {
 | 
				
			||||||
 | 
						Format(s fmt.State, verb rune)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExitCoder is the interface checked by `App` and `Command` for a custom exit
 | 
				
			||||||
 | 
					// code
 | 
				
			||||||
 | 
					type ExitCoder interface {
 | 
				
			||||||
 | 
						error
 | 
				
			||||||
 | 
						ExitCode() int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExitError fulfills both the builtin `error` interface and `ExitCoder`
 | 
				
			||||||
 | 
					type ExitError struct {
 | 
				
			||||||
 | 
						exitCode int
 | 
				
			||||||
 | 
						message  interface{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewExitError makes a new *ExitError
 | 
				
			||||||
 | 
					func NewExitError(message interface{}, exitCode int) *ExitError {
 | 
				
			||||||
 | 
						return &ExitError{
 | 
				
			||||||
 | 
							exitCode: exitCode,
 | 
				
			||||||
 | 
							message:  message,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Error returns the string message, fulfilling the interface required by
 | 
				
			||||||
 | 
					// `error`
 | 
				
			||||||
 | 
					func (ee *ExitError) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%v", ee.message)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExitCode returns the exit code, fulfilling the interface required by
 | 
				
			||||||
 | 
					// `ExitCoder`
 | 
				
			||||||
 | 
					func (ee *ExitError) ExitCode() int {
 | 
				
			||||||
 | 
						return ee.exitCode
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
 | 
				
			||||||
 | 
					// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
 | 
				
			||||||
 | 
					// given exit code.  If the given error is a MultiError, then this func is
 | 
				
			||||||
 | 
					// called on all members of the Errors slice and calls OsExiter with the last exit code.
 | 
				
			||||||
 | 
					func HandleExitCoder(err error) {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if exitErr, ok := err.(ExitCoder); ok {
 | 
				
			||||||
 | 
							if err.Error() != "" {
 | 
				
			||||||
 | 
								if _, ok := exitErr.(ErrorFormatter); ok {
 | 
				
			||||||
 | 
									fmt.Fprintf(ErrWriter, "%+v\n", err)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									fmt.Fprintln(ErrWriter, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							OsExiter(exitErr.ExitCode())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if multiErr, ok := err.(MultiError); ok {
 | 
				
			||||||
 | 
							code := handleMultiError(multiErr)
 | 
				
			||||||
 | 
							OsExiter(code)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func handleMultiError(multiErr MultiError) int {
 | 
				
			||||||
 | 
						code := 1
 | 
				
			||||||
 | 
						for _, merr := range multiErr.Errors {
 | 
				
			||||||
 | 
							if multiErr2, ok := merr.(MultiError); ok {
 | 
				
			||||||
 | 
								code = handleMultiError(multiErr2)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								fmt.Fprintln(ErrWriter, merr)
 | 
				
			||||||
 | 
								if exitErr, ok := merr.(ExitCoder); ok {
 | 
				
			||||||
 | 
									code = exitErr.ExitCode()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return code
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,194 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"text/template"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToFishCompletion creates a fish completion string for the `*App`
 | 
				
			||||||
 | 
					// The function errors if either parsing or writing of the string fails.
 | 
				
			||||||
 | 
					func (a *App) ToFishCompletion() (string, error) {
 | 
				
			||||||
 | 
						var w bytes.Buffer
 | 
				
			||||||
 | 
						if err := a.writeFishCompletionTemplate(&w); err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return w.String(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type fishCompletionTemplate struct {
 | 
				
			||||||
 | 
						App         *App
 | 
				
			||||||
 | 
						Completions []string
 | 
				
			||||||
 | 
						AllCommands []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) writeFishCompletionTemplate(w io.Writer) error {
 | 
				
			||||||
 | 
						const name = "cli"
 | 
				
			||||||
 | 
						t, err := template.New(name).Parse(FishCompletionTemplate)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						allCommands := []string{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add global flags
 | 
				
			||||||
 | 
						completions := a.prepareFishFlags(a.VisibleFlags(), allCommands)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add help flag
 | 
				
			||||||
 | 
						if !a.HideHelp {
 | 
				
			||||||
 | 
							completions = append(
 | 
				
			||||||
 | 
								completions,
 | 
				
			||||||
 | 
								a.prepareFishFlags([]Flag{HelpFlag}, allCommands)...,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add version flag
 | 
				
			||||||
 | 
						if !a.HideVersion {
 | 
				
			||||||
 | 
							completions = append(
 | 
				
			||||||
 | 
								completions,
 | 
				
			||||||
 | 
								a.prepareFishFlags([]Flag{VersionFlag}, allCommands)...,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add commands and their flags
 | 
				
			||||||
 | 
						completions = append(
 | 
				
			||||||
 | 
							completions,
 | 
				
			||||||
 | 
							a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})...,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return t.ExecuteTemplate(w, name, &fishCompletionTemplate{
 | 
				
			||||||
 | 
							App:         a,
 | 
				
			||||||
 | 
							Completions: completions,
 | 
				
			||||||
 | 
							AllCommands: allCommands,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) prepareFishCommands(commands []Command, allCommands *[]string, previousCommands []string) []string {
 | 
				
			||||||
 | 
						completions := []string{}
 | 
				
			||||||
 | 
						for i := range commands {
 | 
				
			||||||
 | 
							command := &commands[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if command.Hidden {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var completion strings.Builder
 | 
				
			||||||
 | 
							completion.WriteString(fmt.Sprintf(
 | 
				
			||||||
 | 
								"complete -r -c %s -n '%s' -a '%s'",
 | 
				
			||||||
 | 
								a.Name,
 | 
				
			||||||
 | 
								a.fishSubcommandHelper(previousCommands),
 | 
				
			||||||
 | 
								strings.Join(command.Names(), " "),
 | 
				
			||||||
 | 
							))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if command.Usage != "" {
 | 
				
			||||||
 | 
								completion.WriteString(fmt.Sprintf(" -d '%s'",
 | 
				
			||||||
 | 
									escapeSingleQuotes(command.Usage)))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !command.HideHelp {
 | 
				
			||||||
 | 
								completions = append(
 | 
				
			||||||
 | 
									completions,
 | 
				
			||||||
 | 
									a.prepareFishFlags([]Flag{HelpFlag}, command.Names())...,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							*allCommands = append(*allCommands, command.Names()...)
 | 
				
			||||||
 | 
							completions = append(completions, completion.String())
 | 
				
			||||||
 | 
							completions = append(
 | 
				
			||||||
 | 
								completions,
 | 
				
			||||||
 | 
								a.prepareFishFlags(command.Flags, command.Names())...,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// recursevly iterate subcommands
 | 
				
			||||||
 | 
							if len(command.Subcommands) > 0 {
 | 
				
			||||||
 | 
								completions = append(
 | 
				
			||||||
 | 
									completions,
 | 
				
			||||||
 | 
									a.prepareFishCommands(
 | 
				
			||||||
 | 
										command.Subcommands, allCommands, command.Names(),
 | 
				
			||||||
 | 
									)...,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return completions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string {
 | 
				
			||||||
 | 
						completions := []string{}
 | 
				
			||||||
 | 
						for _, f := range flags {
 | 
				
			||||||
 | 
							flag, ok := f.(DocGenerationFlag)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							completion := &strings.Builder{}
 | 
				
			||||||
 | 
							completion.WriteString(fmt.Sprintf(
 | 
				
			||||||
 | 
								"complete -c %s -n '%s'",
 | 
				
			||||||
 | 
								a.Name,
 | 
				
			||||||
 | 
								a.fishSubcommandHelper(previousCommands),
 | 
				
			||||||
 | 
							))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fishAddFileFlag(f, completion)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for idx, opt := range strings.Split(flag.GetName(), ",") {
 | 
				
			||||||
 | 
								if idx == 0 {
 | 
				
			||||||
 | 
									completion.WriteString(fmt.Sprintf(
 | 
				
			||||||
 | 
										" -l %s", strings.TrimSpace(opt),
 | 
				
			||||||
 | 
									))
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									completion.WriteString(fmt.Sprintf(
 | 
				
			||||||
 | 
										" -s %s", strings.TrimSpace(opt),
 | 
				
			||||||
 | 
									))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if flag.TakesValue() {
 | 
				
			||||||
 | 
								completion.WriteString(" -r")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if flag.GetUsage() != "" {
 | 
				
			||||||
 | 
								completion.WriteString(fmt.Sprintf(" -d '%s'",
 | 
				
			||||||
 | 
									escapeSingleQuotes(flag.GetUsage())))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							completions = append(completions, completion.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return completions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fishAddFileFlag(flag Flag, completion *strings.Builder) {
 | 
				
			||||||
 | 
						switch f := flag.(type) {
 | 
				
			||||||
 | 
						case GenericFlag:
 | 
				
			||||||
 | 
							if f.TakesFile {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case StringFlag:
 | 
				
			||||||
 | 
							if f.TakesFile {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case StringSliceFlag:
 | 
				
			||||||
 | 
							if f.TakesFile {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						completion.WriteString(" -f")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) fishSubcommandHelper(allCommands []string) string {
 | 
				
			||||||
 | 
						fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name)
 | 
				
			||||||
 | 
						if len(allCommands) > 0 {
 | 
				
			||||||
 | 
							fishHelper = fmt.Sprintf(
 | 
				
			||||||
 | 
								"__fish_seen_subcommand_from %s",
 | 
				
			||||||
 | 
								strings.Join(allCommands, " "),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fishHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func escapeSingleQuotes(input string) string {
 | 
				
			||||||
 | 
						return strings.Replace(input, `'`, `\'`, -1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,348 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultPlaceholder = "value"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BashCompletionFlag enables bash-completion for all commands and subcommands
 | 
				
			||||||
 | 
					var BashCompletionFlag Flag = BoolFlag{
 | 
				
			||||||
 | 
						Name:   "generate-bash-completion",
 | 
				
			||||||
 | 
						Hidden: true,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VersionFlag prints the version for the application
 | 
				
			||||||
 | 
					var VersionFlag Flag = BoolFlag{
 | 
				
			||||||
 | 
						Name:  "version, v",
 | 
				
			||||||
 | 
						Usage: "print the version",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HelpFlag prints the help for all commands and subcommands
 | 
				
			||||||
 | 
					// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
 | 
				
			||||||
 | 
					// unless HideHelp is set to true)
 | 
				
			||||||
 | 
					var HelpFlag Flag = BoolFlag{
 | 
				
			||||||
 | 
						Name:  "help, h",
 | 
				
			||||||
 | 
						Usage: "show help",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagStringer converts a flag definition to a string. This is used by help
 | 
				
			||||||
 | 
					// to display a flag.
 | 
				
			||||||
 | 
					var FlagStringer FlagStringFunc = stringifyFlag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagNamePrefixer converts a full flag name and its placeholder into the help
 | 
				
			||||||
 | 
					// message flag prefix. This is used by the default FlagStringer.
 | 
				
			||||||
 | 
					var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagEnvHinter annotates flag help message with the environment variable
 | 
				
			||||||
 | 
					// details. This is used by the default FlagStringer.
 | 
				
			||||||
 | 
					var FlagEnvHinter FlagEnvHintFunc = withEnvHint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagFileHinter annotates flag help message with the environment variable
 | 
				
			||||||
 | 
					// details. This is used by the default FlagStringer.
 | 
				
			||||||
 | 
					var FlagFileHinter FlagFileHintFunc = withFileHint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagsByName is a slice of Flag.
 | 
				
			||||||
 | 
					type FlagsByName []Flag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f FlagsByName) Len() int {
 | 
				
			||||||
 | 
						return len(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f FlagsByName) Less(i, j int) bool {
 | 
				
			||||||
 | 
						return lexicographicLess(f[i].GetName(), f[j].GetName())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f FlagsByName) Swap(i, j int) {
 | 
				
			||||||
 | 
						f[i], f[j] = f[j], f[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Flag is a common interface related to parsing flags in cli.
 | 
				
			||||||
 | 
					// For more advanced flag parsing techniques, it is recommended that
 | 
				
			||||||
 | 
					// this interface be implemented.
 | 
				
			||||||
 | 
					type Flag interface {
 | 
				
			||||||
 | 
						fmt.Stringer
 | 
				
			||||||
 | 
						// Apply Flag settings to the given flag set
 | 
				
			||||||
 | 
						Apply(*flag.FlagSet)
 | 
				
			||||||
 | 
						GetName() string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RequiredFlag is an interface that allows us to mark flags as required
 | 
				
			||||||
 | 
					// it allows flags required flags to be backwards compatible with the Flag interface
 | 
				
			||||||
 | 
					type RequiredFlag interface {
 | 
				
			||||||
 | 
						Flag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						IsRequired() bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DocGenerationFlag is an interface that allows documentation generation for the flag
 | 
				
			||||||
 | 
					type DocGenerationFlag interface {
 | 
				
			||||||
 | 
						Flag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TakesValue returns true if the flag takes a value, otherwise false
 | 
				
			||||||
 | 
						TakesValue() bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
						GetUsage() string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
						// string if the flag takes no value at all.
 | 
				
			||||||
 | 
						GetValue() string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// errorableFlag is an interface that allows us to return errors during apply
 | 
				
			||||||
 | 
					// it allows flags defined in this library to return errors in a fashion backwards compatible
 | 
				
			||||||
 | 
					// TODO remove in v2 and modify the existing Flag interface to return errors
 | 
				
			||||||
 | 
					type errorableFlag interface {
 | 
				
			||||||
 | 
						Flag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ApplyWithError(*flag.FlagSet) error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
 | 
				
			||||||
 | 
						set := flag.NewFlagSet(name, flag.ContinueOnError)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, f := range flags {
 | 
				
			||||||
 | 
							//TODO remove in v2 when errorableFlag is removed
 | 
				
			||||||
 | 
							if ef, ok := f.(errorableFlag); ok {
 | 
				
			||||||
 | 
								if err := ef.ApplyWithError(set); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								f.Apply(set)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						set.SetOutput(ioutil.Discard)
 | 
				
			||||||
 | 
						return set, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func eachName(longName string, fn func(string)) {
 | 
				
			||||||
 | 
						parts := strings.Split(longName, ",")
 | 
				
			||||||
 | 
						for _, name := range parts {
 | 
				
			||||||
 | 
							name = strings.Trim(name, " ")
 | 
				
			||||||
 | 
							fn(name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func visibleFlags(fl []Flag) []Flag {
 | 
				
			||||||
 | 
						var visible []Flag
 | 
				
			||||||
 | 
						for _, f := range fl {
 | 
				
			||||||
 | 
							field := flagValue(f).FieldByName("Hidden")
 | 
				
			||||||
 | 
							if !field.IsValid() || !field.Bool() {
 | 
				
			||||||
 | 
								visible = append(visible, f)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return visible
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func prefixFor(name string) (prefix string) {
 | 
				
			||||||
 | 
						if len(name) == 1 {
 | 
				
			||||||
 | 
							prefix = "-"
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							prefix = "--"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns the placeholder, if any, and the unquoted usage string.
 | 
				
			||||||
 | 
					func unquoteUsage(usage string) (string, string) {
 | 
				
			||||||
 | 
						for i := 0; i < len(usage); i++ {
 | 
				
			||||||
 | 
							if usage[i] == '`' {
 | 
				
			||||||
 | 
								for j := i + 1; j < len(usage); j++ {
 | 
				
			||||||
 | 
									if usage[j] == '`' {
 | 
				
			||||||
 | 
										name := usage[i+1 : j]
 | 
				
			||||||
 | 
										usage = usage[:i] + name + usage[j+1:]
 | 
				
			||||||
 | 
										return name, usage
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "", usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func prefixedNames(fullName, placeholder string) string {
 | 
				
			||||||
 | 
						var prefixed string
 | 
				
			||||||
 | 
						parts := strings.Split(fullName, ",")
 | 
				
			||||||
 | 
						for i, name := range parts {
 | 
				
			||||||
 | 
							name = strings.Trim(name, " ")
 | 
				
			||||||
 | 
							prefixed += prefixFor(name) + name
 | 
				
			||||||
 | 
							if placeholder != "" {
 | 
				
			||||||
 | 
								prefixed += " " + placeholder
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if i < len(parts)-1 {
 | 
				
			||||||
 | 
								prefixed += ", "
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return prefixed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func withEnvHint(envVar, str string) string {
 | 
				
			||||||
 | 
						envText := ""
 | 
				
			||||||
 | 
						if envVar != "" {
 | 
				
			||||||
 | 
							prefix := "$"
 | 
				
			||||||
 | 
							suffix := ""
 | 
				
			||||||
 | 
							sep := ", $"
 | 
				
			||||||
 | 
							if runtime.GOOS == "windows" {
 | 
				
			||||||
 | 
								prefix = "%"
 | 
				
			||||||
 | 
								suffix = "%"
 | 
				
			||||||
 | 
								sep = "%, %"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return str + envText
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func withFileHint(filePath, str string) string {
 | 
				
			||||||
 | 
						fileText := ""
 | 
				
			||||||
 | 
						if filePath != "" {
 | 
				
			||||||
 | 
							fileText = fmt.Sprintf(" [%s]", filePath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return str + fileText
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func flagValue(f Flag) reflect.Value {
 | 
				
			||||||
 | 
						fv := reflect.ValueOf(f)
 | 
				
			||||||
 | 
						for fv.Kind() == reflect.Ptr {
 | 
				
			||||||
 | 
							fv = reflect.Indirect(fv)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fv
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func stringifyFlag(f Flag) string {
 | 
				
			||||||
 | 
						fv := flagValue(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch f.(type) {
 | 
				
			||||||
 | 
						case IntSliceFlag:
 | 
				
			||||||
 | 
							return FlagFileHinter(
 | 
				
			||||||
 | 
								fv.FieldByName("FilePath").String(),
 | 
				
			||||||
 | 
								FlagEnvHinter(
 | 
				
			||||||
 | 
									fv.FieldByName("EnvVar").String(),
 | 
				
			||||||
 | 
									stringifyIntSliceFlag(f.(IntSliceFlag)),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						case Int64SliceFlag:
 | 
				
			||||||
 | 
							return FlagFileHinter(
 | 
				
			||||||
 | 
								fv.FieldByName("FilePath").String(),
 | 
				
			||||||
 | 
								FlagEnvHinter(
 | 
				
			||||||
 | 
									fv.FieldByName("EnvVar").String(),
 | 
				
			||||||
 | 
									stringifyInt64SliceFlag(f.(Int64SliceFlag)),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						case StringSliceFlag:
 | 
				
			||||||
 | 
							return FlagFileHinter(
 | 
				
			||||||
 | 
								fv.FieldByName("FilePath").String(),
 | 
				
			||||||
 | 
								FlagEnvHinter(
 | 
				
			||||||
 | 
									fv.FieldByName("EnvVar").String(),
 | 
				
			||||||
 | 
									stringifyStringSliceFlag(f.(StringSliceFlag)),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						needsPlaceholder := false
 | 
				
			||||||
 | 
						defaultValueString := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if val := fv.FieldByName("Value"); val.IsValid() {
 | 
				
			||||||
 | 
							needsPlaceholder = true
 | 
				
			||||||
 | 
							defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if val.Kind() == reflect.String && val.String() != "" {
 | 
				
			||||||
 | 
								defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if defaultValueString == " (default: )" {
 | 
				
			||||||
 | 
							defaultValueString = ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if needsPlaceholder && placeholder == "" {
 | 
				
			||||||
 | 
							placeholder = defaultPlaceholder
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						usageWithDefault := strings.TrimSpace(usage + defaultValueString)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return FlagFileHinter(
 | 
				
			||||||
 | 
							fv.FieldByName("FilePath").String(),
 | 
				
			||||||
 | 
							FlagEnvHinter(
 | 
				
			||||||
 | 
								fv.FieldByName("EnvVar").String(),
 | 
				
			||||||
 | 
								FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault,
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func stringifyIntSliceFlag(f IntSliceFlag) string {
 | 
				
			||||||
 | 
						var defaultVals []string
 | 
				
			||||||
 | 
						if f.Value != nil && len(f.Value.Value()) > 0 {
 | 
				
			||||||
 | 
							for _, i := range f.Value.Value() {
 | 
				
			||||||
 | 
								defaultVals = append(defaultVals, strconv.Itoa(i))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return stringifySliceFlag(f.Usage, f.Name, defaultVals)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func stringifyInt64SliceFlag(f Int64SliceFlag) string {
 | 
				
			||||||
 | 
						var defaultVals []string
 | 
				
			||||||
 | 
						if f.Value != nil && len(f.Value.Value()) > 0 {
 | 
				
			||||||
 | 
							for _, i := range f.Value.Value() {
 | 
				
			||||||
 | 
								defaultVals = append(defaultVals, strconv.FormatInt(i, 10))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return stringifySliceFlag(f.Usage, f.Name, defaultVals)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func stringifyStringSliceFlag(f StringSliceFlag) string {
 | 
				
			||||||
 | 
						var defaultVals []string
 | 
				
			||||||
 | 
						if f.Value != nil && len(f.Value.Value()) > 0 {
 | 
				
			||||||
 | 
							for _, s := range f.Value.Value() {
 | 
				
			||||||
 | 
								if len(s) > 0 {
 | 
				
			||||||
 | 
									defaultVals = append(defaultVals, strconv.Quote(s))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return stringifySliceFlag(f.Usage, f.Name, defaultVals)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func stringifySliceFlag(usage, name string, defaultVals []string) string {
 | 
				
			||||||
 | 
						placeholder, usage := unquoteUsage(usage)
 | 
				
			||||||
 | 
						if placeholder == "" {
 | 
				
			||||||
 | 
							placeholder = defaultPlaceholder
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defaultVal := ""
 | 
				
			||||||
 | 
						if len(defaultVals) > 0 {
 | 
				
			||||||
 | 
							defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						usageWithDefault := strings.TrimSpace(usage + defaultVal)
 | 
				
			||||||
 | 
						return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func flagFromFileEnv(filePath, envName string) (val string, ok bool) {
 | 
				
			||||||
 | 
						for _, envVar := range strings.Split(envName, ",") {
 | 
				
			||||||
 | 
							envVar = strings.TrimSpace(envVar)
 | 
				
			||||||
 | 
							if envVal, ok := syscall.Getenv(envVar); ok {
 | 
				
			||||||
 | 
								return envVal, true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, fileVar := range strings.Split(filePath, ",") {
 | 
				
			||||||
 | 
							if fileVar != "" {
 | 
				
			||||||
 | 
								if data, err := ioutil.ReadFile(fileVar); err == nil {
 | 
				
			||||||
 | 
									return string(data), true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "", false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BoolFlag is a flag with type bool
 | 
				
			||||||
 | 
					type BoolFlag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						Destination *bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f BoolFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f BoolFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f BoolFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f BoolFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f BoolFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f BoolFlag) GetValue() string {
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Bool looks up the value of a local BoolFlag, returns
 | 
				
			||||||
 | 
					// false if not found
 | 
				
			||||||
 | 
					func (c *Context) Bool(name string) bool {
 | 
				
			||||||
 | 
						return lookupBool(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalBool looks up the value of a global BoolFlag, returns
 | 
				
			||||||
 | 
					// false if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalBool(name string) bool {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupBool(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f BoolFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						val := false
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							if envVal == "" {
 | 
				
			||||||
 | 
								val = false
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								envValBool, err := strconv.ParseBool(envVal)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								val = envValBool
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.BoolVar(f.Destination, name, val, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Bool(name, val, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupBool(name string, set *flag.FlagSet) bool {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := strconv.ParseBool(f.Value.String())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BoolTFlag is a flag with type bool that is true by default
 | 
				
			||||||
 | 
					type BoolTFlag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						Destination *bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f BoolTFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f BoolTFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f BoolTFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f BoolTFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f BoolTFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f BoolTFlag) GetValue() string {
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BoolT looks up the value of a local BoolTFlag, returns
 | 
				
			||||||
 | 
					// false if not found
 | 
				
			||||||
 | 
					func (c *Context) BoolT(name string) bool {
 | 
				
			||||||
 | 
						return lookupBoolT(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalBoolT looks up the value of a global BoolTFlag, returns
 | 
				
			||||||
 | 
					// false if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalBoolT(name string) bool {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupBoolT(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f BoolTFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						val := true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							if envVal == "" {
 | 
				
			||||||
 | 
								val = false
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								envValBool, err := strconv.ParseBool(envVal)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								val = envValBool
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.BoolVar(f.Destination, name, val, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Bool(name, val, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupBoolT(name string, set *flag.FlagSet) bool {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := strconv.ParseBool(f.Value.String())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
 | 
				
			||||||
 | 
					type DurationFlag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						Value       time.Duration
 | 
				
			||||||
 | 
						Destination *time.Duration
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f DurationFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f DurationFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f DurationFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f DurationFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f DurationFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f DurationFlag) GetValue() string {
 | 
				
			||||||
 | 
						return f.Value.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Duration looks up the value of a local DurationFlag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) Duration(name string) time.Duration {
 | 
				
			||||||
 | 
						return lookupDuration(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalDuration looks up the value of a global DurationFlag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalDuration(name string) time.Duration {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupDuration(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f DurationFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							envValDuration, err := time.ParseDuration(envVal)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							f.Value = envValDuration
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.DurationVar(f.Destination, name, f.Value, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Duration(name, f.Value, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupDuration(name string, set *flag.FlagSet) time.Duration {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := time.ParseDuration(f.Value.String())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Float64Flag is a flag with type float64
 | 
				
			||||||
 | 
					type Float64Flag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						Value       float64
 | 
				
			||||||
 | 
						Destination *float64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f Float64Flag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f Float64Flag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f Float64Flag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f Float64Flag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f Float64Flag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f Float64Flag) GetValue() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%f", f.Value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Float64 looks up the value of a local Float64Flag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) Float64(name string) float64 {
 | 
				
			||||||
 | 
						return lookupFloat64(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalFloat64 looks up the value of a global Float64Flag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalFloat64(name string) float64 {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupFloat64(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f Float64Flag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							envValFloat, err := strconv.ParseFloat(envVal, 10)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							f.Value = envValFloat
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.Float64Var(f.Destination, name, f.Value, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Float64(name, f.Value, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupFloat64(name string, set *flag.FlagSet) float64 {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := strconv.ParseFloat(f.Value.String(), 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Generic is a generic parseable type identified by a specific flag
 | 
				
			||||||
 | 
					type Generic interface {
 | 
				
			||||||
 | 
						Set(value string) error
 | 
				
			||||||
 | 
						String() string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GenericFlag is a flag with type Generic
 | 
				
			||||||
 | 
					type GenericFlag struct {
 | 
				
			||||||
 | 
						Name      string
 | 
				
			||||||
 | 
						Usage     string
 | 
				
			||||||
 | 
						EnvVar    string
 | 
				
			||||||
 | 
						FilePath  string
 | 
				
			||||||
 | 
						Required  bool
 | 
				
			||||||
 | 
						Hidden    bool
 | 
				
			||||||
 | 
						TakesFile bool
 | 
				
			||||||
 | 
						Value     Generic
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f GenericFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f GenericFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f GenericFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f GenericFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f GenericFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f GenericFlag) GetValue() string {
 | 
				
			||||||
 | 
						if f.Value != nil {
 | 
				
			||||||
 | 
							return f.Value.String()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply takes the flagset and calls Set on the generic flag with the value
 | 
				
			||||||
 | 
					// provided by the user for parsing by the flag
 | 
				
			||||||
 | 
					// Ignores parsing errors
 | 
				
			||||||
 | 
					func (f GenericFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError takes the flagset and calls Set on the generic flag with the value
 | 
				
			||||||
 | 
					// provided by the user for parsing by the flag
 | 
				
			||||||
 | 
					func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						val := f.Value
 | 
				
			||||||
 | 
						if fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							if err := val.Set(fileEnvVal); err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							set.Var(f.Value, name, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Generic looks up the value of a local GenericFlag, returns
 | 
				
			||||||
 | 
					// nil if not found
 | 
				
			||||||
 | 
					func (c *Context) Generic(name string) interface{} {
 | 
				
			||||||
 | 
						return lookupGeneric(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalGeneric looks up the value of a global GenericFlag, returns
 | 
				
			||||||
 | 
					// nil if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalGeneric(name string) interface{} {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupGeneric(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupGeneric(name string, set *flag.FlagSet) interface{} {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := f.Value, error(nil)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IntFlag is a flag with type int
 | 
				
			||||||
 | 
					type IntFlag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						Value       int
 | 
				
			||||||
 | 
						Destination *int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f IntFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f IntFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f IntFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f IntFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f IntFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f IntFlag) GetValue() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%d", f.Value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f IntFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							envValInt, err := strconv.ParseInt(envVal, 0, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							f.Value = int(envValInt)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.IntVar(f.Destination, name, f.Value, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Int(name, f.Value, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Int looks up the value of a local IntFlag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) Int(name string) int {
 | 
				
			||||||
 | 
						return lookupInt(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalInt looks up the value of a global IntFlag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalInt(name string) int {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupInt(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupInt(name string, set *flag.FlagSet) int {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return int(parsed)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Int64Flag is a flag with type int64
 | 
				
			||||||
 | 
					type Int64Flag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						Value       int64
 | 
				
			||||||
 | 
						Destination *int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f Int64Flag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f Int64Flag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f Int64Flag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f Int64Flag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f Int64Flag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f Int64Flag) GetValue() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%d", f.Value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f Int64Flag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							envValInt, err := strconv.ParseInt(envVal, 0, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							f.Value = envValInt
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.Int64Var(f.Destination, name, f.Value, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Int64(name, f.Value, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Int64 looks up the value of a local Int64Flag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) Int64(name string) int64 {
 | 
				
			||||||
 | 
						return lookupInt64(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalInt64 looks up the value of a global Int64Flag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalInt64(name string) int64 {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupInt64(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupInt64(name string, set *flag.FlagSet) int64 {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,199 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
 | 
				
			||||||
 | 
					type Int64Slice []int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set parses the value into an integer and appends it to the list of values
 | 
				
			||||||
 | 
					func (f *Int64Slice) Set(value string) error {
 | 
				
			||||||
 | 
						tmp, err := strconv.ParseInt(value, 10, 64)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						*f = append(*f, tmp)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value (for usage defaults)
 | 
				
			||||||
 | 
					func (f *Int64Slice) String() string {
 | 
				
			||||||
 | 
						slice := make([]string, len(*f))
 | 
				
			||||||
 | 
						for i, v := range *f {
 | 
				
			||||||
 | 
							slice[i] = strconv.FormatInt(v, 10)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return strings.Join(slice, ",")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Value returns the slice of ints set by this flag
 | 
				
			||||||
 | 
					func (f *Int64Slice) Value() []int64 {
 | 
				
			||||||
 | 
						return *f
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get returns the slice of ints set by this flag
 | 
				
			||||||
 | 
					func (f *Int64Slice) Get() interface{} {
 | 
				
			||||||
 | 
						return *f
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Int64SliceFlag is a flag with type *Int64Slice
 | 
				
			||||||
 | 
					type Int64SliceFlag struct {
 | 
				
			||||||
 | 
						Name     string
 | 
				
			||||||
 | 
						Usage    string
 | 
				
			||||||
 | 
						EnvVar   string
 | 
				
			||||||
 | 
						FilePath string
 | 
				
			||||||
 | 
						Required bool
 | 
				
			||||||
 | 
						Hidden   bool
 | 
				
			||||||
 | 
						Value    *Int64Slice
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f Int64SliceFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f Int64SliceFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f Int64SliceFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f Int64SliceFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f Int64SliceFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f Int64SliceFlag) GetValue() string {
 | 
				
			||||||
 | 
						if f.Value != nil {
 | 
				
			||||||
 | 
							return f.Value.String()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							newVal := &Int64Slice{}
 | 
				
			||||||
 | 
							for _, s := range strings.Split(envVal, ",") {
 | 
				
			||||||
 | 
								s = strings.TrimSpace(s)
 | 
				
			||||||
 | 
								if err := newVal.Set(s); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if f.Value == nil {
 | 
				
			||||||
 | 
								f.Value = newVal
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								*f.Value = *newVal
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Value == nil {
 | 
				
			||||||
 | 
								f.Value = &Int64Slice{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Var(f.Value, name, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Int64Slice looks up the value of a local Int64SliceFlag, returns
 | 
				
			||||||
 | 
					// nil if not found
 | 
				
			||||||
 | 
					func (c *Context) Int64Slice(name string) []int64 {
 | 
				
			||||||
 | 
						return lookupInt64Slice(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
 | 
				
			||||||
 | 
					// nil if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalInt64Slice(name string) []int64 {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupInt64Slice(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							value, ok := f.Value.(*Int64Slice)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// extract the slice from asserted value
 | 
				
			||||||
 | 
							parsed := value.Value()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// extract default value from the flag
 | 
				
			||||||
 | 
							var defaultVal []int64
 | 
				
			||||||
 | 
							for _, v := range strings.Split(f.DefValue, ",") {
 | 
				
			||||||
 | 
								if v != "" {
 | 
				
			||||||
 | 
									int64Value, err := strconv.ParseInt(v, 10, 64)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										panic(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									defaultVal = append(defaultVal, int64Value)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// if the current value is not equal to the default value
 | 
				
			||||||
 | 
							// remove the default values from the flag
 | 
				
			||||||
 | 
							if !isInt64SliceEqual(parsed, defaultVal) {
 | 
				
			||||||
 | 
								for _, v := range defaultVal {
 | 
				
			||||||
 | 
									parsed = removeFromInt64Slice(parsed, v)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func removeFromInt64Slice(slice []int64, val int64) []int64 {
 | 
				
			||||||
 | 
						for i, v := range slice {
 | 
				
			||||||
 | 
							if v == val {
 | 
				
			||||||
 | 
								ret := append([]int64{}, slice[:i]...)
 | 
				
			||||||
 | 
								ret = append(ret, slice[i+1:]...)
 | 
				
			||||||
 | 
								return ret
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return slice
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isInt64SliceEqual(newValue, defaultValue []int64) bool {
 | 
				
			||||||
 | 
						// If one is nil, the other must also be nil.
 | 
				
			||||||
 | 
						if (newValue == nil) != (defaultValue == nil) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(newValue) != len(defaultValue) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, v := range newValue {
 | 
				
			||||||
 | 
							if v != defaultValue[i] {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,198 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
 | 
				
			||||||
 | 
					type IntSlice []int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set parses the value into an integer and appends it to the list of values
 | 
				
			||||||
 | 
					func (f *IntSlice) Set(value string) error {
 | 
				
			||||||
 | 
						tmp, err := strconv.Atoi(value)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						*f = append(*f, tmp)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value (for usage defaults)
 | 
				
			||||||
 | 
					func (f *IntSlice) String() string {
 | 
				
			||||||
 | 
						slice := make([]string, len(*f))
 | 
				
			||||||
 | 
						for i, v := range *f {
 | 
				
			||||||
 | 
							slice[i] = strconv.Itoa(v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return strings.Join(slice, ",")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Value returns the slice of ints set by this flag
 | 
				
			||||||
 | 
					func (f *IntSlice) Value() []int {
 | 
				
			||||||
 | 
						return *f
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get returns the slice of ints set by this flag
 | 
				
			||||||
 | 
					func (f *IntSlice) Get() interface{} {
 | 
				
			||||||
 | 
						return *f
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IntSliceFlag is a flag with type *IntSlice
 | 
				
			||||||
 | 
					type IntSliceFlag struct {
 | 
				
			||||||
 | 
						Name     string
 | 
				
			||||||
 | 
						Usage    string
 | 
				
			||||||
 | 
						EnvVar   string
 | 
				
			||||||
 | 
						FilePath string
 | 
				
			||||||
 | 
						Required bool
 | 
				
			||||||
 | 
						Hidden   bool
 | 
				
			||||||
 | 
						Value    *IntSlice
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f IntSliceFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f IntSliceFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f IntSliceFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f IntSliceFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f IntSliceFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f IntSliceFlag) GetValue() string {
 | 
				
			||||||
 | 
						if f.Value != nil {
 | 
				
			||||||
 | 
							return f.Value.String()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f IntSliceFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							newVal := &IntSlice{}
 | 
				
			||||||
 | 
							for _, s := range strings.Split(envVal, ",") {
 | 
				
			||||||
 | 
								s = strings.TrimSpace(s)
 | 
				
			||||||
 | 
								if err := newVal.Set(s); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if f.Value == nil {
 | 
				
			||||||
 | 
								f.Value = newVal
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								*f.Value = *newVal
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Value == nil {
 | 
				
			||||||
 | 
								f.Value = &IntSlice{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Var(f.Value, name, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IntSlice looks up the value of a local IntSliceFlag, returns
 | 
				
			||||||
 | 
					// nil if not found
 | 
				
			||||||
 | 
					func (c *Context) IntSlice(name string) []int {
 | 
				
			||||||
 | 
						return lookupIntSlice(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
 | 
				
			||||||
 | 
					// nil if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalIntSlice(name string) []int {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupIntSlice(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupIntSlice(name string, set *flag.FlagSet) []int {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							value, ok := f.Value.(*IntSlice)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// extract the slice from asserted value
 | 
				
			||||||
 | 
							slice := value.Value()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// extract default value from the flag
 | 
				
			||||||
 | 
							var defaultVal []int
 | 
				
			||||||
 | 
							for _, v := range strings.Split(f.DefValue, ",") {
 | 
				
			||||||
 | 
								if v != "" {
 | 
				
			||||||
 | 
									intValue, err := strconv.Atoi(v)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										panic(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									defaultVal = append(defaultVal, intValue)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// if the current value is not equal to the default value
 | 
				
			||||||
 | 
							// remove the default values from the flag
 | 
				
			||||||
 | 
							if !isIntSliceEqual(slice, defaultVal) {
 | 
				
			||||||
 | 
								for _, v := range defaultVal {
 | 
				
			||||||
 | 
									slice = removeFromIntSlice(slice, v)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return slice
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func removeFromIntSlice(slice []int, val int) []int {
 | 
				
			||||||
 | 
						for i, v := range slice {
 | 
				
			||||||
 | 
							if v == val {
 | 
				
			||||||
 | 
								ret := append([]int{}, slice[:i]...)
 | 
				
			||||||
 | 
								ret = append(ret, slice[i+1:]...)
 | 
				
			||||||
 | 
								return ret
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return slice
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isIntSliceEqual(newValue, defaultValue []int) bool {
 | 
				
			||||||
 | 
						// If one is nil, the other must also be nil.
 | 
				
			||||||
 | 
						if (newValue == nil) != (defaultValue == nil) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(newValue) != len(defaultValue) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, v := range newValue {
 | 
				
			||||||
 | 
							if v != defaultValue[i] {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "flag"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StringFlag is a flag with type string
 | 
				
			||||||
 | 
					type StringFlag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						TakesFile   bool
 | 
				
			||||||
 | 
						Value       string
 | 
				
			||||||
 | 
						Destination *string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f StringFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f StringFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f StringFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f StringFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f StringFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f StringFlag) GetValue() string {
 | 
				
			||||||
 | 
						return f.Value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f StringFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							f.Value = envVal
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.StringVar(f.Destination, name, f.Value, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.String(name, f.Value, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String looks up the value of a local StringFlag, returns
 | 
				
			||||||
 | 
					// "" if not found
 | 
				
			||||||
 | 
					func (c *Context) String(name string) string {
 | 
				
			||||||
 | 
						return lookupString(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalString looks up the value of a global StringFlag, returns
 | 
				
			||||||
 | 
					// "" if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalString(name string) string {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupString(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupString(name string, set *flag.FlagSet) string {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := f.Value.String(), error(nil)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return ""
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,184 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
 | 
				
			||||||
 | 
					type StringSlice []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set appends the string value to the list of values
 | 
				
			||||||
 | 
					func (f *StringSlice) Set(value string) error {
 | 
				
			||||||
 | 
						*f = append(*f, value)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value (for usage defaults)
 | 
				
			||||||
 | 
					func (f *StringSlice) String() string {
 | 
				
			||||||
 | 
						return strings.Join(*f, ",")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Value returns the slice of strings set by this flag
 | 
				
			||||||
 | 
					func (f *StringSlice) Value() []string {
 | 
				
			||||||
 | 
						return *f
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get returns the slice of strings set by this flag
 | 
				
			||||||
 | 
					func (f *StringSlice) Get() interface{} {
 | 
				
			||||||
 | 
						return *f
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StringSliceFlag is a flag with type *StringSlice
 | 
				
			||||||
 | 
					type StringSliceFlag struct {
 | 
				
			||||||
 | 
						Name      string
 | 
				
			||||||
 | 
						Usage     string
 | 
				
			||||||
 | 
						EnvVar    string
 | 
				
			||||||
 | 
						FilePath  string
 | 
				
			||||||
 | 
						Required  bool
 | 
				
			||||||
 | 
						Hidden    bool
 | 
				
			||||||
 | 
						TakesFile bool
 | 
				
			||||||
 | 
						Value     *StringSlice
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f StringSliceFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f StringSliceFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f StringSliceFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f StringSliceFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f StringSliceFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f StringSliceFlag) GetValue() string {
 | 
				
			||||||
 | 
						if f.Value != nil {
 | 
				
			||||||
 | 
							return f.Value.String()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f StringSliceFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							newVal := &StringSlice{}
 | 
				
			||||||
 | 
							for _, s := range strings.Split(envVal, ",") {
 | 
				
			||||||
 | 
								s = strings.TrimSpace(s)
 | 
				
			||||||
 | 
								if err := newVal.Set(s); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if f.Value == nil {
 | 
				
			||||||
 | 
								f.Value = newVal
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								*f.Value = *newVal
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Value == nil {
 | 
				
			||||||
 | 
								f.Value = &StringSlice{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Var(f.Value, name, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StringSlice looks up the value of a local StringSliceFlag, returns
 | 
				
			||||||
 | 
					// nil if not found
 | 
				
			||||||
 | 
					func (c *Context) StringSlice(name string) []string {
 | 
				
			||||||
 | 
						return lookupStringSlice(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
 | 
				
			||||||
 | 
					// nil if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalStringSlice(name string) []string {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupStringSlice(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupStringSlice(name string, set *flag.FlagSet) []string {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							value, ok := f.Value.(*StringSlice)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// extract the slice from asserted value
 | 
				
			||||||
 | 
							slice := value.Value()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// extract default value from the flag
 | 
				
			||||||
 | 
							var defaultVal []string
 | 
				
			||||||
 | 
							for _, v := range strings.Split(f.DefValue, ",") {
 | 
				
			||||||
 | 
								defaultVal = append(defaultVal, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// if the current value is not equal to the default value
 | 
				
			||||||
 | 
							// remove the default values from the flag
 | 
				
			||||||
 | 
							if !isStringSliceEqual(slice, defaultVal) {
 | 
				
			||||||
 | 
								for _, v := range defaultVal {
 | 
				
			||||||
 | 
									slice = removeFromStringSlice(slice, v)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return slice
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func removeFromStringSlice(slice []string, val string) []string {
 | 
				
			||||||
 | 
						for i, v := range slice {
 | 
				
			||||||
 | 
							if v == val {
 | 
				
			||||||
 | 
								ret := append([]string{}, slice[:i]...)
 | 
				
			||||||
 | 
								ret = append(ret, slice[i+1:]...)
 | 
				
			||||||
 | 
								return ret
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return slice
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isStringSliceEqual(newValue, defaultValue []string) bool {
 | 
				
			||||||
 | 
						// If one is nil, the other must also be nil.
 | 
				
			||||||
 | 
						if (newValue == nil) != (defaultValue == nil) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(newValue) != len(defaultValue) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, v := range newValue {
 | 
				
			||||||
 | 
							if v != defaultValue[i] {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UintFlag is a flag with type uint
 | 
				
			||||||
 | 
					type UintFlag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						Value       uint
 | 
				
			||||||
 | 
						Destination *uint
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f UintFlag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f UintFlag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f UintFlag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f UintFlag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f UintFlag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f UintFlag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							envValInt, err := strconv.ParseUint(envVal, 0, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							f.Value = uint(envValInt)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.UintVar(f.Destination, name, f.Value, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Uint(name, f.Value, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f UintFlag) GetValue() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%d", f.Value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Uint looks up the value of a local UintFlag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) Uint(name string) uint {
 | 
				
			||||||
 | 
						return lookupUint(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalUint looks up the value of a global UintFlag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalUint(name string) uint {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupUint(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupUint(name string, set *flag.FlagSet) uint {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return uint(parsed)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Uint64Flag is a flag with type uint64
 | 
				
			||||||
 | 
					type Uint64Flag struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						Usage       string
 | 
				
			||||||
 | 
						EnvVar      string
 | 
				
			||||||
 | 
						FilePath    string
 | 
				
			||||||
 | 
						Required    bool
 | 
				
			||||||
 | 
						Hidden      bool
 | 
				
			||||||
 | 
						Value       uint64
 | 
				
			||||||
 | 
						Destination *uint64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a readable representation of this value
 | 
				
			||||||
 | 
					// (for usage defaults)
 | 
				
			||||||
 | 
					func (f Uint64Flag) String() string {
 | 
				
			||||||
 | 
						return FlagStringer(f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the name of the flag
 | 
				
			||||||
 | 
					func (f Uint64Flag) GetName() string {
 | 
				
			||||||
 | 
						return f.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRequired returns whether or not the flag is required
 | 
				
			||||||
 | 
					func (f Uint64Flag) IsRequired() bool {
 | 
				
			||||||
 | 
						return f.Required
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakesValue returns true of the flag takes a value, otherwise false
 | 
				
			||||||
 | 
					func (f Uint64Flag) TakesValue() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUsage returns the usage string for the flag
 | 
				
			||||||
 | 
					func (f Uint64Flag) GetUsage() string {
 | 
				
			||||||
 | 
						return f.Usage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetValue returns the flags value as string representation and an empty
 | 
				
			||||||
 | 
					// string if the flag takes no value at all.
 | 
				
			||||||
 | 
					func (f Uint64Flag) GetValue() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%d", f.Value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					// Ignores errors
 | 
				
			||||||
 | 
					func (f Uint64Flag) Apply(set *flag.FlagSet) {
 | 
				
			||||||
 | 
						_ = f.ApplyWithError(set)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyWithError populates the flag given the flag set and environment
 | 
				
			||||||
 | 
					func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
 | 
				
			||||||
 | 
						if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
 | 
				
			||||||
 | 
							envValInt, err := strconv.ParseUint(envVal, 0, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							f.Value = envValInt
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eachName(f.Name, func(name string) {
 | 
				
			||||||
 | 
							if f.Destination != nil {
 | 
				
			||||||
 | 
								set.Uint64Var(f.Destination, name, f.Value, f.Usage)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set.Uint64(name, f.Value, f.Usage)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Uint64 looks up the value of a local Uint64Flag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) Uint64(name string) uint64 {
 | 
				
			||||||
 | 
						return lookupUint64(name, c.flagSet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GlobalUint64 looks up the value of a global Uint64Flag, returns
 | 
				
			||||||
 | 
					// 0 if not found
 | 
				
			||||||
 | 
					func (c *Context) GlobalUint64(name string) uint64 {
 | 
				
			||||||
 | 
						if fs := lookupGlobalFlagSet(name, c); fs != nil {
 | 
				
			||||||
 | 
							return lookupUint64(name, fs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lookupUint64(name string, set *flag.FlagSet) uint64 {
 | 
				
			||||||
 | 
						f := set.Lookup(name)
 | 
				
			||||||
 | 
						if f != nil {
 | 
				
			||||||
 | 
							parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return parsed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BashCompleteFunc is an action to execute when the bash-completion flag is set
 | 
				
			||||||
 | 
					type BashCompleteFunc func(*Context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BeforeFunc is an action to execute before any subcommands are run, but after
 | 
				
			||||||
 | 
					// the context is ready if a non-nil error is returned, no subcommands are run
 | 
				
			||||||
 | 
					type BeforeFunc func(*Context) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AfterFunc is an action to execute after any subcommands are run, but after the
 | 
				
			||||||
 | 
					// subcommand has finished it is run even if Action() panics
 | 
				
			||||||
 | 
					type AfterFunc func(*Context) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ActionFunc is the action to execute when no subcommands are specified
 | 
				
			||||||
 | 
					type ActionFunc func(*Context) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CommandNotFoundFunc is executed if the proper command cannot be found
 | 
				
			||||||
 | 
					type CommandNotFoundFunc func(*Context, string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
 | 
				
			||||||
 | 
					// customized usage error messages.  This function is able to replace the
 | 
				
			||||||
 | 
					// original error messages.  If this function is not set, the "Incorrect usage"
 | 
				
			||||||
 | 
					// is displayed and the execution is interrupted.
 | 
				
			||||||
 | 
					type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExitErrHandlerFunc is executed if provided in order to handle ExitError values
 | 
				
			||||||
 | 
					// returned by Actions and Before/After functions.
 | 
				
			||||||
 | 
					type ExitErrHandlerFunc func(context *Context, err error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagStringFunc is used by the help generation to display a flag, which is
 | 
				
			||||||
 | 
					// expected to be a single line.
 | 
				
			||||||
 | 
					type FlagStringFunc func(Flag) string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
 | 
				
			||||||
 | 
					// text for a flag's full name.
 | 
				
			||||||
 | 
					type FlagNamePrefixFunc func(fullName, placeholder string) string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
 | 
				
			||||||
 | 
					// with the environment variable details.
 | 
				
			||||||
 | 
					type FlagEnvHintFunc func(envVar, str string) string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
 | 
				
			||||||
 | 
					// with the file path details.
 | 
				
			||||||
 | 
					type FlagFileHintFunc func(filePath, str string) string
 | 
				
			||||||
@ -0,0 +1,363 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"text/tabwriter"
 | 
				
			||||||
 | 
						"text/template"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var helpCommand = Command{
 | 
				
			||||||
 | 
						Name:      "help",
 | 
				
			||||||
 | 
						Aliases:   []string{"h"},
 | 
				
			||||||
 | 
						Usage:     "Shows a list of commands or help for one command",
 | 
				
			||||||
 | 
						ArgsUsage: "[command]",
 | 
				
			||||||
 | 
						Action: func(c *Context) error {
 | 
				
			||||||
 | 
							args := c.Args()
 | 
				
			||||||
 | 
							if args.Present() {
 | 
				
			||||||
 | 
								return ShowCommandHelp(c, args.First())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_ = ShowAppHelp(c)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var helpSubcommand = Command{
 | 
				
			||||||
 | 
						Name:      "help",
 | 
				
			||||||
 | 
						Aliases:   []string{"h"},
 | 
				
			||||||
 | 
						Usage:     "Shows a list of commands or help for one command",
 | 
				
			||||||
 | 
						ArgsUsage: "[command]",
 | 
				
			||||||
 | 
						Action: func(c *Context) error {
 | 
				
			||||||
 | 
							args := c.Args()
 | 
				
			||||||
 | 
							if args.Present() {
 | 
				
			||||||
 | 
								return ShowCommandHelp(c, args.First())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return ShowSubcommandHelp(c)
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Prints help for the App or Command
 | 
				
			||||||
 | 
					type helpPrinter func(w io.Writer, templ string, data interface{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Prints help for the App or Command with custom template function.
 | 
				
			||||||
 | 
					type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HelpPrinter is a function that writes the help output. If not set explicitly,
 | 
				
			||||||
 | 
					// this calls HelpPrinterCustom using only the default template functions.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If custom logic for printing help is required, this function can be
 | 
				
			||||||
 | 
					// overridden. If the ExtraInfo field is defined on an App, this function
 | 
				
			||||||
 | 
					// should not be modified, as HelpPrinterCustom will be used directly in order
 | 
				
			||||||
 | 
					// to capture the extra information.
 | 
				
			||||||
 | 
					var HelpPrinter helpPrinter = printHelp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HelpPrinterCustom is a function that writes the help output. It is used as
 | 
				
			||||||
 | 
					// the default implementation of HelpPrinter, and may be called directly if
 | 
				
			||||||
 | 
					// the ExtraInfo field is set on an App.
 | 
				
			||||||
 | 
					var HelpPrinterCustom helpPrinterCustom = printHelpCustom
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VersionPrinter prints the version for the App
 | 
				
			||||||
 | 
					var VersionPrinter = printVersion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
 | 
				
			||||||
 | 
					func ShowAppHelpAndExit(c *Context, exitCode int) {
 | 
				
			||||||
 | 
						_ = ShowAppHelp(c)
 | 
				
			||||||
 | 
						os.Exit(exitCode)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShowAppHelp is an action that displays the help.
 | 
				
			||||||
 | 
					func ShowAppHelp(c *Context) error {
 | 
				
			||||||
 | 
						template := c.App.CustomAppHelpTemplate
 | 
				
			||||||
 | 
						if template == "" {
 | 
				
			||||||
 | 
							template = AppHelpTemplate
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.App.ExtraInfo == nil {
 | 
				
			||||||
 | 
							HelpPrinter(c.App.Writer, template, c.App)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						customAppData := func() map[string]interface{} {
 | 
				
			||||||
 | 
							return map[string]interface{}{
 | 
				
			||||||
 | 
								"ExtraInfo": c.App.ExtraInfo,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						HelpPrinterCustom(c.App.Writer, template, c.App, customAppData())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DefaultAppComplete prints the list of subcommands as the default app completion method
 | 
				
			||||||
 | 
					func DefaultAppComplete(c *Context) {
 | 
				
			||||||
 | 
						DefaultCompleteWithFlags(nil)(c)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func printCommandSuggestions(commands []Command, writer io.Writer) {
 | 
				
			||||||
 | 
						for _, command := range commands {
 | 
				
			||||||
 | 
							if command.Hidden {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" {
 | 
				
			||||||
 | 
								for _, name := range command.Names() {
 | 
				
			||||||
 | 
									_, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								for _, name := range command.Names() {
 | 
				
			||||||
 | 
									_, _ = fmt.Fprintf(writer, "%s\n", name)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func cliArgContains(flagName string) bool {
 | 
				
			||||||
 | 
						for _, name := range strings.Split(flagName, ",") {
 | 
				
			||||||
 | 
							name = strings.TrimSpace(name)
 | 
				
			||||||
 | 
							count := utf8.RuneCountInString(name)
 | 
				
			||||||
 | 
							if count > 2 {
 | 
				
			||||||
 | 
								count = 2
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
 | 
				
			||||||
 | 
							for _, a := range os.Args {
 | 
				
			||||||
 | 
								if a == flag {
 | 
				
			||||||
 | 
									return true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
 | 
				
			||||||
 | 
						cur := strings.TrimPrefix(lastArg, "-")
 | 
				
			||||||
 | 
						cur = strings.TrimPrefix(cur, "-")
 | 
				
			||||||
 | 
						for _, flag := range flags {
 | 
				
			||||||
 | 
							if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, name := range strings.Split(flag.GetName(), ",") {
 | 
				
			||||||
 | 
								name = strings.TrimSpace(name)
 | 
				
			||||||
 | 
								// this will get total count utf8 letters in flag name
 | 
				
			||||||
 | 
								count := utf8.RuneCountInString(name)
 | 
				
			||||||
 | 
								if count > 2 {
 | 
				
			||||||
 | 
									count = 2 // resuse this count to generate single - or -- in flag completion
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// if flag name has more than one utf8 letter and last argument in cli has -- prefix then
 | 
				
			||||||
 | 
								// skip flag completion for short flags example -v or -x
 | 
				
			||||||
 | 
								if strings.HasPrefix(lastArg, "--") && count == 1 {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// match if last argument matches this flag and it is not repeated
 | 
				
			||||||
 | 
								if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) {
 | 
				
			||||||
 | 
									flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
 | 
				
			||||||
 | 
									_, _ = fmt.Fprintln(writer, flagCompletion)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DefaultCompleteWithFlags(cmd *Command) func(c *Context) {
 | 
				
			||||||
 | 
						return func(c *Context) {
 | 
				
			||||||
 | 
							if len(os.Args) > 2 {
 | 
				
			||||||
 | 
								lastArg := os.Args[len(os.Args)-2]
 | 
				
			||||||
 | 
								if strings.HasPrefix(lastArg, "-") {
 | 
				
			||||||
 | 
									printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
 | 
				
			||||||
 | 
									if cmd != nil {
 | 
				
			||||||
 | 
										printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if cmd != nil {
 | 
				
			||||||
 | 
								printCommandSuggestions(cmd.Subcommands, c.App.Writer)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								printCommandSuggestions(c.App.Commands, c.App.Writer)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShowCommandHelpAndExit - exits with code after showing help
 | 
				
			||||||
 | 
					func ShowCommandHelpAndExit(c *Context, command string, code int) {
 | 
				
			||||||
 | 
						_ = ShowCommandHelp(c, command)
 | 
				
			||||||
 | 
						os.Exit(code)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShowCommandHelp prints help for the given command
 | 
				
			||||||
 | 
					func ShowCommandHelp(ctx *Context, command string) error {
 | 
				
			||||||
 | 
						// show the subcommand help for a command with subcommands
 | 
				
			||||||
 | 
						if command == "" {
 | 
				
			||||||
 | 
							HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, c := range ctx.App.Commands {
 | 
				
			||||||
 | 
							if c.HasName(command) {
 | 
				
			||||||
 | 
								templ := c.CustomHelpTemplate
 | 
				
			||||||
 | 
								if templ == "" {
 | 
				
			||||||
 | 
									templ = CommandHelpTemplate
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								HelpPrinter(ctx.App.Writer, templ, c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.App.CommandNotFound == nil {
 | 
				
			||||||
 | 
							return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.App.CommandNotFound(ctx, command)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShowSubcommandHelp prints help for the given subcommand
 | 
				
			||||||
 | 
					func ShowSubcommandHelp(c *Context) error {
 | 
				
			||||||
 | 
						return ShowCommandHelp(c, c.Command.Name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShowVersion prints the version number of the App
 | 
				
			||||||
 | 
					func ShowVersion(c *Context) {
 | 
				
			||||||
 | 
						VersionPrinter(c)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func printVersion(c *Context) {
 | 
				
			||||||
 | 
						_, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShowCompletions prints the lists of commands within a given context
 | 
				
			||||||
 | 
					func ShowCompletions(c *Context) {
 | 
				
			||||||
 | 
						a := c.App
 | 
				
			||||||
 | 
						if a != nil && a.BashComplete != nil {
 | 
				
			||||||
 | 
							a.BashComplete(c)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShowCommandCompletions prints the custom completions for a given command
 | 
				
			||||||
 | 
					func ShowCommandCompletions(ctx *Context, command string) {
 | 
				
			||||||
 | 
						c := ctx.App.Command(command)
 | 
				
			||||||
 | 
						if c != nil {
 | 
				
			||||||
 | 
							if c.BashComplete != nil {
 | 
				
			||||||
 | 
								c.BashComplete(ctx)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								DefaultCompleteWithFlags(c)(ctx)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// printHelpCustom is the default implementation of HelpPrinterCustom.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The customFuncs map will be combined with a default template.FuncMap to
 | 
				
			||||||
 | 
					// allow using arbitrary functions in template rendering.
 | 
				
			||||||
 | 
					func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
 | 
				
			||||||
 | 
						funcMap := template.FuncMap{
 | 
				
			||||||
 | 
							"join": strings.Join,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for key, value := range customFuncs {
 | 
				
			||||||
 | 
							funcMap[key] = value
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
 | 
				
			||||||
 | 
						t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
 | 
				
			||||||
 | 
						err := t.Execute(w, data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// If the writer is closed, t.Execute will fail, and there's nothing
 | 
				
			||||||
 | 
							// we can do to recover.
 | 
				
			||||||
 | 
							if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
 | 
				
			||||||
 | 
								_, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_ = w.Flush()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func printHelp(out io.Writer, templ string, data interface{}) {
 | 
				
			||||||
 | 
						HelpPrinterCustom(out, templ, data, nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkVersion(c *Context) bool {
 | 
				
			||||||
 | 
						found := false
 | 
				
			||||||
 | 
						if VersionFlag.GetName() != "" {
 | 
				
			||||||
 | 
							eachName(VersionFlag.GetName(), func(name string) {
 | 
				
			||||||
 | 
								if c.GlobalBool(name) || c.Bool(name) {
 | 
				
			||||||
 | 
									found = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return found
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkHelp(c *Context) bool {
 | 
				
			||||||
 | 
						found := false
 | 
				
			||||||
 | 
						if HelpFlag.GetName() != "" {
 | 
				
			||||||
 | 
							eachName(HelpFlag.GetName(), func(name string) {
 | 
				
			||||||
 | 
								if c.GlobalBool(name) || c.Bool(name) {
 | 
				
			||||||
 | 
									found = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return found
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkCommandHelp(c *Context, name string) bool {
 | 
				
			||||||
 | 
						if c.Bool("h") || c.Bool("help") {
 | 
				
			||||||
 | 
							_ = ShowCommandHelp(c, name)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkSubcommandHelp(c *Context) bool {
 | 
				
			||||||
 | 
						if c.Bool("h") || c.Bool("help") {
 | 
				
			||||||
 | 
							_ = ShowSubcommandHelp(c)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
 | 
				
			||||||
 | 
						if !a.EnableBashCompletion {
 | 
				
			||||||
 | 
							return false, arguments
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pos := len(arguments) - 1
 | 
				
			||||||
 | 
						lastArg := arguments[pos]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if lastArg != "--"+BashCompletionFlag.GetName() {
 | 
				
			||||||
 | 
							return false, arguments
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true, arguments[:pos]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkCompletions(c *Context) bool {
 | 
				
			||||||
 | 
						if !c.shellComplete {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if args := c.Args(); args.Present() {
 | 
				
			||||||
 | 
							name := args.First()
 | 
				
			||||||
 | 
							if cmd := c.App.Command(name); cmd != nil {
 | 
				
			||||||
 | 
								// let the command handle the completion
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ShowCompletions(c)
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkCommandCompletions(c *Context, name string) bool {
 | 
				
			||||||
 | 
						if !c.shellComplete {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ShowCommandCompletions(c, name)
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type iterativeParser interface {
 | 
				
			||||||
 | 
						newFlagSet() (*flag.FlagSet, error)
 | 
				
			||||||
 | 
						useShortOptionHandling() bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
 | 
				
			||||||
 | 
					// iteratively catch parsing errors. This way we achieve LR parsing without
 | 
				
			||||||
 | 
					// transforming any arguments. Otherwise, there is no way we can discriminate
 | 
				
			||||||
 | 
					// combined short options from common arguments that should be left untouched.
 | 
				
			||||||
 | 
					// Pass `shellComplete` to continue parsing options on failure during shell
 | 
				
			||||||
 | 
					// completion when, the user-supplied options may be incomplete.
 | 
				
			||||||
 | 
					func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							err := set.Parse(args)
 | 
				
			||||||
 | 
							if !ip.useShortOptionHandling() || err == nil {
 | 
				
			||||||
 | 
								if shellComplete {
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							errStr := err.Error()
 | 
				
			||||||
 | 
							trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -")
 | 
				
			||||||
 | 
							if errStr == trimmed {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// regenerate the initial args with the split short opts
 | 
				
			||||||
 | 
							argsWereSplit := false
 | 
				
			||||||
 | 
							for i, arg := range args {
 | 
				
			||||||
 | 
								// skip args that are not part of the error message
 | 
				
			||||||
 | 
								if name := strings.TrimLeft(arg, "-"); name != trimmed {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// if we can't split, the error was accurate
 | 
				
			||||||
 | 
								shortOpts := splitShortOptions(set, arg)
 | 
				
			||||||
 | 
								if len(shortOpts) == 1 {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// swap current argument with the split version
 | 
				
			||||||
 | 
								args = append(args[:i], append(shortOpts, args[i+1:]...)...)
 | 
				
			||||||
 | 
								argsWereSplit = true
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// This should be an impossible to reach code path, but in case the arg
 | 
				
			||||||
 | 
							// splitting failed to happen, this will prevent infinite loops
 | 
				
			||||||
 | 
							if !argsWereSplit {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Since custom parsing failed, replace the flag set before retrying
 | 
				
			||||||
 | 
							newSet, err := ip.newFlagSet()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							*set = *newSet
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func splitShortOptions(set *flag.FlagSet, arg string) []string {
 | 
				
			||||||
 | 
						shortFlagsExist := func(s string) bool {
 | 
				
			||||||
 | 
							for _, c := range s[1:] {
 | 
				
			||||||
 | 
								if f := set.Lookup(string(c)); f == nil {
 | 
				
			||||||
 | 
									return false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !isSplittable(arg) || !shortFlagsExist(arg) {
 | 
				
			||||||
 | 
							return []string{arg}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						separated := make([]string, 0, len(arg)-1)
 | 
				
			||||||
 | 
						for _, flagChar := range arg[1:] {
 | 
				
			||||||
 | 
							separated = append(separated, "-"+string(flagChar))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return separated
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isSplittable(flagArg string) bool {
 | 
				
			||||||
 | 
						return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "unicode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// lexicographicLess compares strings alphabetically considering case.
 | 
				
			||||||
 | 
					func lexicographicLess(i, j string) bool {
 | 
				
			||||||
 | 
						iRunes := []rune(i)
 | 
				
			||||||
 | 
						jRunes := []rune(j)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lenShared := len(iRunes)
 | 
				
			||||||
 | 
						if lenShared > len(jRunes) {
 | 
				
			||||||
 | 
							lenShared = len(jRunes)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for index := 0; index < lenShared; index++ {
 | 
				
			||||||
 | 
							ir := iRunes[index]
 | 
				
			||||||
 | 
							jr := jRunes[index]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr {
 | 
				
			||||||
 | 
								return lir < ljr
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if ir != jr {
 | 
				
			||||||
 | 
								return ir < jr
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return i < j
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AppHelpTemplate is the text template for the Default help topic.
 | 
				
			||||||
 | 
					// cli.go uses text/template to render templates. You can
 | 
				
			||||||
 | 
					// render custom help text by setting this variable.
 | 
				
			||||||
 | 
					var AppHelpTemplate = `NAME:
 | 
				
			||||||
 | 
					   {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USAGE:
 | 
				
			||||||
 | 
					   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VERSION:
 | 
				
			||||||
 | 
					   {{.Version}}{{end}}{{end}}{{if .Description}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DESCRIPTION:
 | 
				
			||||||
 | 
					   {{.Description}}{{end}}{{if len .Authors}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
 | 
				
			||||||
 | 
					   {{range $index, $author := .Authors}}{{if $index}}
 | 
				
			||||||
 | 
					   {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COMMANDS:{{range .VisibleCategories}}{{if .Name}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   {{.Name}}:{{range .VisibleCommands}}
 | 
				
			||||||
 | 
					     {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
 | 
				
			||||||
 | 
					   {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GLOBAL OPTIONS:
 | 
				
			||||||
 | 
					   {{range $index, $option := .VisibleFlags}}{{if $index}}
 | 
				
			||||||
 | 
					   {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPYRIGHT:
 | 
				
			||||||
 | 
					   {{.Copyright}}{{end}}
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CommandHelpTemplate is the text template for the command help topic.
 | 
				
			||||||
 | 
					// cli.go uses text/template to render templates. You can
 | 
				
			||||||
 | 
					// render custom help text by setting this variable.
 | 
				
			||||||
 | 
					var CommandHelpTemplate = `NAME:
 | 
				
			||||||
 | 
					   {{.HelpName}} - {{.Usage}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USAGE:
 | 
				
			||||||
 | 
					   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CATEGORY:
 | 
				
			||||||
 | 
					   {{.Category}}{{end}}{{if .Description}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DESCRIPTION:
 | 
				
			||||||
 | 
					   {{.Description}}{{end}}{{if .VisibleFlags}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OPTIONS:
 | 
				
			||||||
 | 
					   {{range .VisibleFlags}}{{.}}
 | 
				
			||||||
 | 
					   {{end}}{{end}}
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SubcommandHelpTemplate is the text template for the subcommand help topic.
 | 
				
			||||||
 | 
					// cli.go uses text/template to render templates. You can
 | 
				
			||||||
 | 
					// render custom help text by setting this variable.
 | 
				
			||||||
 | 
					var SubcommandHelpTemplate = `NAME:
 | 
				
			||||||
 | 
					   {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USAGE:
 | 
				
			||||||
 | 
					   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COMMANDS:{{range .VisibleCategories}}{{if .Name}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   {{.Name}}:{{range .VisibleCommands}}
 | 
				
			||||||
 | 
					     {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
 | 
				
			||||||
 | 
					   {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OPTIONS:
 | 
				
			||||||
 | 
					   {{range .VisibleFlags}}{{.}}
 | 
				
			||||||
 | 
					   {{end}}{{end}}
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					% {{ .App.Author }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NAME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SYNOPSIS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{ .App.Name }}
 | 
				
			||||||
 | 
					{{ if .SynopsisArgs }}
 | 
				
			||||||
 | 
					` + "```" + `
 | 
				
			||||||
 | 
					{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
 | 
				
			||||||
 | 
					{{ end }}{{ if .App.UsageText }}
 | 
				
			||||||
 | 
					# DESCRIPTION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{ .App.UsageText }}
 | 
				
			||||||
 | 
					{{ end }}
 | 
				
			||||||
 | 
					**Usage**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					` + "```" + `
 | 
				
			||||||
 | 
					{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
 | 
				
			||||||
 | 
					` + "```" + `
 | 
				
			||||||
 | 
					{{ if .GlobalArgs }}
 | 
				
			||||||
 | 
					# GLOBAL OPTIONS
 | 
				
			||||||
 | 
					{{ range $v := .GlobalArgs }}
 | 
				
			||||||
 | 
					{{ $v }}{{ end }}
 | 
				
			||||||
 | 
					{{ end }}{{ if .Commands }}
 | 
				
			||||||
 | 
					# COMMANDS
 | 
				
			||||||
 | 
					{{ range $v := .Commands }}
 | 
				
			||||||
 | 
					{{ $v }}{{ end }}{{ end }}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet'
 | 
				
			||||||
 | 
					    for i in (commandline -opc)
 | 
				
			||||||
 | 
					        if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }}
 | 
				
			||||||
 | 
					            return 1
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return 0
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{ range $v := .Completions }}{{ $v }}
 | 
				
			||||||
 | 
					{{ end }}`
 | 
				
			||||||
					Loading…
					
					
				
		Reference in New Issue