diff --git a/docs/pipeline-api.md b/docs/pipeline-api.md new file mode 100644 index 00000000000..e160a627d99 --- /dev/null +++ b/docs/pipeline-api.md @@ -0,0 +1,8550 @@ +

Packages:

+ +

tekton.dev/v1alpha1

+
+

Package v1alpha1 contains API Schema definitions for the pipeline v1alpha1 API group

+
+Resource Types: + +

ClusterTask +

+
+

ClusterTask is a Task with a cluster scope. ClusterTasks are used to +represent Tasks that should be publicly addressable from any namespace in the +cluster.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1alpha1 + +
+kind
+string +
ClusterTask
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +TaskSpec + + +
+(Optional) +

Spec holds the desired state of the Task from the client

+
+
+ + + + + + + + + + + + + +
+TaskSpec
+ + +TaskSpec + + +
+

+(Members of TaskSpec are embedded into this type.) +

+
+inputs
+ + +Inputs + + +
+(Optional) +

Inputs is an optional set of parameters and resources which must be +supplied by the user when a Task is executed by a TaskRun.

+
+outputs
+ + +Outputs + + +
+(Optional) +

Outputs is an optional set of resources and results produced when this +Task is run.

+
+
+

Condition +

+
+

Condition declares a step that is used to gate the execution of a Task in a Pipeline. +A condition execution (ConditionCheck) evaluates to either true or false

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1alpha1 + +
+kind
+string +
Condition
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +ConditionSpec + + +
+(Optional) +

Spec holds the desired state of the Condition from the client

+
+
+ + + + + + + + + + + + + + + + + +
+check
+ + +Step + + +
+

Check declares container whose exit code determines where a condition is true or false

+
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the condition that may be +used to populate a UI.

+
+params
+ + +[]ParamSpec + + +
+(Optional) +

Params is an optional set of parameters which must be supplied by the user when a Condition +is evaluated

+
+resources
+ + +[]ResourceDeclaration + + +
+(Optional) +

Resources is a list of the ConditionResources required to run the condition.

+
+
+

Pipeline +

+
+

Pipeline describes a list of Tasks to execute. It expresses how outputs +of tasks feed into inputs of subsequent tasks.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1alpha1 + +
+kind
+string +
Pipeline
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +PipelineSpec + + +
+(Optional) +

Spec holds the desired state of the Pipeline from the client

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the pipeline that may be +used to populate a UI.

+
+resources
+ + +[]PipelineDeclaredResource + + +
+

Resources declares the names and types of the resources given to the +Pipeline’s tasks as inputs and outputs.

+
+tasks
+ + +[]PipelineTask + + +
+

Tasks declares the graph of Tasks that execute when this Pipeline is run.

+
+params
+ + +[]ParamSpec + + +
+

Params declares a list of input parameters that must be supplied when +this Pipeline is run.

+
+workspaces
+ + +[]PipelineWorkspaceDeclaration + + +
+(Optional) +

Workspaces declares a set of named workspaces that are expected to be +provided by a PipelineRun.

+
+results
+ + +[]PipelineResult + + +
+(Optional) +

Results are values that this pipeline can output once run

+
+
+status
+ + +PipelineStatus + + +
+(Optional) +

Status is deprecated. +It usually is used to communicate the observed state of the Pipeline from +the controller, but was unused as there is no controller for Pipeline.

+
+

PipelineRun +

+
+

PipelineRun represents a single execution of a Pipeline. PipelineRuns are how +the graph of Tasks declared in a Pipeline are executed; they specify inputs +to Pipelines such as parameter values and capture operational aspects of the +Tasks execution such as service account and tolerations. Creating a +PipelineRun creates TaskRuns for Tasks in the referenced Pipeline.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1alpha1 + +
+kind
+string +
PipelineRun
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +PipelineRunSpec + + +
+(Optional) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+pipelineRef
+ + +PipelineRef + + +
+(Optional) +
+pipelineSpec
+ + +PipelineSpec + + +
+(Optional) +
+resources
+ + +[]PipelineResourceBinding + + +
+

Resources is a list of bindings specifying which actual instances of +PipelineResources to use for the resources the Pipeline has declared +it needs.

+
+params
+ + +[]Param + + +
+

Params is a list of parameter names and values.

+
+serviceAccountName
+ +string + +
+(Optional) +
+serviceAccountNames
+ + +[]PipelineRunSpecServiceAccountName + + +
+(Optional) +
+status
+ + +PipelineRunSpecStatus + + +
+(Optional) +

Used for cancelling a pipelinerun (and maybe more later on)

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the Pipeline times out. Defaults to never. +Refer to Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces holds a set of workspace bindings that must match names +with those declared in the pipeline.

+
+taskRunSpecs
+ + +[]PipelineTaskRunSpec + + +
+(Optional) +

TaskRunSpecs holds a set of task specific specs

+
+
+status
+ + +PipelineRunStatus + + +
+(Optional) +
+

Run +

+
+

Run represents a single execution of a Custom Task.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1alpha1 + +
+kind
+string +
Run
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +RunSpec + + +
+(Optional) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ref
+ + +TaskRef + + +
+(Optional) +
+spec
+ + +EmbeddedRunSpec + + +
+(Optional) +

Spec is a specification of a custom task

+
+
+ +
+
+params
+ + +[]Param + + +
+(Optional) +
+status
+ + +RunSpecStatus + + +
+(Optional) +

Used for cancelling a run (and maybe more later on)

+
+retries
+ +int + +
+(Optional) +

Used for propagating retries count to custom tasks

+
+serviceAccountName
+ +string + +
+(Optional) +
+podTemplate
+ + +Template + + +
+(Optional) +

PodTemplate holds pod specific configuration

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the custom-task times out. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces is a list of WorkspaceBindings from volumes to workspaces.

+
+
+status
+ + +RunStatus + + +
+(Optional) +
+

Task +

+
+

Task represents a collection of sequential steps that are run as part of a +Pipeline using a set of inputs and producing a set of outputs. Tasks execute +when TaskRuns are created that provide the input parameters and resources and +output resources the Task requires.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1alpha1 + +
+kind
+string +
Task
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +TaskSpec + + +
+(Optional) +

Spec holds the desired state of the Task from the client

+
+
+ + + + + + + + + + + + + +
+TaskSpec
+ + +TaskSpec + + +
+

+(Members of TaskSpec are embedded into this type.) +

+
+inputs
+ + +Inputs + + +
+(Optional) +

Inputs is an optional set of parameters and resources which must be +supplied by the user when a Task is executed by a TaskRun.

+
+outputs
+ + +Outputs + + +
+(Optional) +

Outputs is an optional set of resources and results produced when this +Task is run.

+
+
+

TaskRun +

+
+

TaskRun represents a single execution of a Task. TaskRuns are how the steps +specified in a Task are executed; they specify the parameters and resources +used to run the steps in a Task.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1alpha1 + +
+kind
+string +
TaskRun
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +TaskRunSpec + + +
+(Optional) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+serviceAccountName
+ +string + +
+(Optional) +
+taskRef
+ + +TaskRef + + +
+(Optional) +

no more than one of the TaskRef and TaskSpec may be specified.

+
+taskSpec
+ + +TaskSpec + + +
+(Optional) +
+status
+ + +TaskRunSpecStatus + + +
+(Optional) +

Used for cancelling a taskrun (and maybe more later on)

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the build times out. Defaults to 10 minutes. +Specified build timeout should be less than 24h. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+(Optional) +

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces is a list of WorkspaceBindings from volumes to workspaces.

+
+params
+ + +[]Param + + +
+(Optional) +

From v1beta1

+
+resources
+ + +TaskRunResources + + +
+(Optional) +
+inputs
+ + +TaskRunInputs + + +
+(Optional) +

Deprecated

+
+outputs
+ + +TaskRunOutputs + + +
+(Optional) +
+
+status
+ + +TaskRunStatus + + +
+(Optional) +
+

PipelineResource +

+
+

PipelineResource describes a resource that is an input to or output from a +Task.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1alpha1 + +
+kind
+string +
PipelineResource
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +PipelineResourceSpec + + +
+

Spec holds the desired state of the PipelineResource from the client

+
+
+ + + + + + + + + + + + + + + + + +
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the resource that may be +used to populate a UI.

+
+type
+ +string + +
+
+params
+ + +[]ResourceParam + + +
+
+secrets
+ + +[]SecretParam + + +
+(Optional) +

Secrets to fetch to populate some of resource fields

+
+
+status
+ + +PipelineResourceStatus + + +
+(Optional) +

Status is deprecated. +It usually is used to communicate the observed state of the PipelineResource from +the controller, but was unused as there is no controller for PipelineResource.

+
+

ConditionCheck +

+
+

ConditionCheck represents a single evaluation of a Condition step.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +TaskRunSpec + + +
+(Optional) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+serviceAccountName
+ +string + +
+(Optional) +
+taskRef
+ + +TaskRef + + +
+(Optional) +

no more than one of the TaskRef and TaskSpec may be specified.

+
+taskSpec
+ + +TaskSpec + + +
+(Optional) +
+status
+ + +TaskRunSpecStatus + + +
+(Optional) +

Used for cancelling a taskrun (and maybe more later on)

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the build times out. Defaults to 10 minutes. +Specified build timeout should be less than 24h. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+(Optional) +

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces is a list of WorkspaceBindings from volumes to workspaces.

+
+params
+ + +[]Param + + +
+(Optional) +

From v1beta1

+
+resources
+ + +TaskRunResources + + +
+(Optional) +
+inputs
+ + +TaskRunInputs + + +
+(Optional) +

Deprecated

+
+outputs
+ + +TaskRunOutputs + + +
+(Optional) +
+
+status
+ + +TaskRunStatus + + +
+(Optional) +
+

ConditionSpec +

+

+(Appears on:Condition) +

+
+

ConditionSpec defines the desired state of the Condition

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+check
+ + +Step + + +
+

Check declares container whose exit code determines where a condition is true or false

+
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the condition that may be +used to populate a UI.

+
+params
+ + +[]ParamSpec + + +
+(Optional) +

Params is an optional set of parameters which must be supplied by the user when a Condition +is evaluated

+
+resources
+ + +[]ResourceDeclaration + + +
+(Optional) +

Resources is a list of the ConditionResources required to run the condition.

+
+

EmbeddedRunSpec +

+

+(Appears on:RunSpec) +

+
+

EmbeddedRunSpec allows custom task definitions to be embedded

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+metadata
+ + +PipelineTaskMetadata + + +
+(Optional) +
+spec
+ +k8s.io/apimachinery/pkg/runtime.RawExtension + +
+(Optional) +

Spec is a specification of a custom task

+
+
+ + + + + + + + + +
+-
+ +[]byte + +
+

Raw is the underlying serialization of this object.

+

TODO: Determine how to detect ContentType and ContentEncoding of ‘Raw’ data.

+
+-
+ +k8s.io/apimachinery/pkg/runtime.Object + +
+

Object can hold a representation of this extension - useful for working with versioned +structs.

+
+
+

Inputs +

+

+(Appears on:TaskSpec) +

+
+

Inputs are the requirements that a task needs to run a Build.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+resources
+ + +[]TaskResource + + +
+(Optional) +

Resources is a list of the input resources required to run the task. +Resources are represented in TaskRuns as bindings to instances of +PipelineResources.

+
+params
+ + +[]ParamSpec + + +
+(Optional) +

Params is a list of input parameters required to run the task. Params +must be supplied as inputs in TaskRuns unless they declare a default +value.

+
+

Outputs +

+

+(Appears on:TaskSpec) +

+
+

Outputs allow a task to declare what data the Build/Task will be producing, +i.e. results such as logs and artifacts such as images.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+results
+ + +[]TestResult + + +
+(Optional) +
+resources
+ + +[]TaskResource + + +
+(Optional) +
+

PipelineObject +

+
+

PipelineObject is implemented by Pipeline and ClusterPipeline

+
+

PipelineResourceInterface +

+
+

PipelineResourceInterface interface to be implemented by different PipelineResource types

+
+

PipelineRunSpec +

+

+(Appears on:PipelineRun) +

+
+

PipelineRunSpec defines the desired state of PipelineRun

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pipelineRef
+ + +PipelineRef + + +
+(Optional) +
+pipelineSpec
+ + +PipelineSpec + + +
+(Optional) +
+resources
+ + +[]PipelineResourceBinding + + +
+

Resources is a list of bindings specifying which actual instances of +PipelineResources to use for the resources the Pipeline has declared +it needs.

+
+params
+ + +[]Param + + +
+

Params is a list of parameter names and values.

+
+serviceAccountName
+ +string + +
+(Optional) +
+serviceAccountNames
+ + +[]PipelineRunSpecServiceAccountName + + +
+(Optional) +
+status
+ + +PipelineRunSpecStatus + + +
+(Optional) +

Used for cancelling a pipelinerun (and maybe more later on)

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the Pipeline times out. Defaults to never. +Refer to Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces holds a set of workspace bindings that must match names +with those declared in the pipeline.

+
+taskRunSpecs
+ + +[]PipelineTaskRunSpec + + +
+(Optional) +

TaskRunSpecs holds a set of task specific specs

+
+

PipelineSpec +

+

+(Appears on:Pipeline, PipelineRunSpec) +

+
+

PipelineSpec defines the desired state of Pipeline.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the pipeline that may be +used to populate a UI.

+
+resources
+ + +[]PipelineDeclaredResource + + +
+

Resources declares the names and types of the resources given to the +Pipeline’s tasks as inputs and outputs.

+
+tasks
+ + +[]PipelineTask + + +
+

Tasks declares the graph of Tasks that execute when this Pipeline is run.

+
+params
+ + +[]ParamSpec + + +
+

Params declares a list of input parameters that must be supplied when +this Pipeline is run.

+
+workspaces
+ + +[]PipelineWorkspaceDeclaration + + +
+(Optional) +

Workspaces declares a set of named workspaces that are expected to be +provided by a PipelineRun.

+
+results
+ + +[]PipelineResult + + +
+(Optional) +

Results are values that this pipeline can output once run

+
+

PipelineStatus +

+

+(Appears on:Pipeline) +

+
+

PipelineStatus does not contain anything because Pipelines on their own +do not have a status, they just hold data which is later used by a +PipelineRun. +Deprecated

+
+

PipelineTask +

+

+(Appears on:PipelineSpec) +

+
+

PipelineTask defines a task in a Pipeline, passing inputs from both +Params and from the output of previous tasks.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of this task within the context of a Pipeline. Name is +used as a coordinate with the from and runAfter fields to establish +the execution order of tasks relative to one another.

+
+taskRef
+ + +TaskRef + + +
+(Optional) +

TaskRef is a reference to a task definition.

+
+taskSpec
+ + +TaskSpec + + +
+(Optional) +

TaskSpec is specification of a task

+
+conditions
+ + +[]PipelineTaskCondition + + +
+(Optional) +

Conditions is a list of conditions that need to be true for the task to run

+
+retries
+ +int + +
+(Optional) +

Retries represents how many times this task should be retried in case of task failure: ConditionSucceeded set to False

+
+runAfter
+ +[]string + +
+(Optional) +

RunAfter is the list of PipelineTask names that should be executed before +this Task executes. (Used to force a specific ordering in graph execution.)

+
+resources
+ + +PipelineTaskResources + + +
+(Optional) +

Resources declares the resources given to this task as inputs and +outputs.

+
+params
+ + +[]Param + + +
+(Optional) +

Parameters declares parameters passed to this task.

+
+workspaces
+ + +[]WorkspacePipelineTaskBinding + + +
+(Optional) +

Workspaces maps workspaces from the pipeline spec to the workspaces +declared in the Task.

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the TaskRun times out. Defaults to 1 hour. +Specified TaskRun timeout should be less than 24h. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+

PipelineTaskRunSpec +

+

+(Appears on:PipelineRunSpec) +

+
+

PipelineTaskRunSpec holds task specific specs

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pipelineTaskName
+ +string + +
+
+taskServiceAccountName
+ +string + +
+
+taskPodTemplate
+ + +Template + + +
+
+

RunSpec +

+

+(Appears on:Run) +

+
+

RunSpec defines the desired state of Run

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ref
+ + +TaskRef + + +
+(Optional) +
+spec
+ + +EmbeddedRunSpec + + +
+(Optional) +

Spec is a specification of a custom task

+
+
+ +
+
+params
+ + +[]Param + + +
+(Optional) +
+status
+ + +RunSpecStatus + + +
+(Optional) +

Used for cancelling a run (and maybe more later on)

+
+retries
+ +int + +
+(Optional) +

Used for propagating retries count to custom tasks

+
+serviceAccountName
+ +string + +
+(Optional) +
+podTemplate
+ + +Template + + +
+(Optional) +

PodTemplate holds pod specific configuration

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the custom-task times out. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces is a list of WorkspaceBindings from volumes to workspaces.

+
+

RunSpecStatus +(string alias)

+

+(Appears on:RunSpec) +

+
+

RunSpecStatus defines the taskrun spec status the user can provide

+
+

TaskObject +

+
+

TaskObject is implemented by Task and ClusterTask

+
+

TaskRunInputs +

+

+(Appears on:TaskRunSpec) +

+
+

TaskRunInputs holds the input values that this task was invoked with.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+resources
+ + +[]TaskResourceBinding + + +
+(Optional) +
+params
+ + +[]Param + + +
+(Optional) +
+

TaskRunOutputs +

+

+(Appears on:TaskRunSpec) +

+
+

TaskRunOutputs holds the output values that this task was invoked with.

+
+ + + + + + + + + + + + + +
FieldDescription
+resources
+ + +[]TaskResourceBinding + + +
+(Optional) +
+

TaskRunSpec +

+

+(Appears on:TaskRun, ConditionCheck) +

+
+

TaskRunSpec defines the desired state of TaskRun

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+serviceAccountName
+ +string + +
+(Optional) +
+taskRef
+ + +TaskRef + + +
+(Optional) +

no more than one of the TaskRef and TaskSpec may be specified.

+
+taskSpec
+ + +TaskSpec + + +
+(Optional) +
+status
+ + +TaskRunSpecStatus + + +
+(Optional) +

Used for cancelling a taskrun (and maybe more later on)

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the build times out. Defaults to 10 minutes. +Specified build timeout should be less than 24h. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+(Optional) +

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces is a list of WorkspaceBindings from volumes to workspaces.

+
+params
+ + +[]Param + + +
+(Optional) +

From v1beta1

+
+resources
+ + +TaskRunResources + + +
+(Optional) +
+inputs
+ + +TaskRunInputs + + +
+(Optional) +

Deprecated

+
+outputs
+ + +TaskRunOutputs + + +
+(Optional) +
+

TaskSpec +

+

+(Appears on:ClusterTask, Task, PipelineTask, TaskRunSpec) +

+
+

TaskSpec defines the desired state of Task.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+TaskSpec
+ + +TaskSpec + + +
+

+(Members of TaskSpec are embedded into this type.) +

+
+inputs
+ + +Inputs + + +
+(Optional) +

Inputs is an optional set of parameters and resources which must be +supplied by the user when a Task is executed by a TaskRun.

+
+outputs
+ + +Outputs + + +
+(Optional) +

Outputs is an optional set of resources and results produced when this +Task is run.

+
+

TestResult +

+

+(Appears on:Outputs) +

+
+

TestResult allows a task to specify the location where test logs +can be found and what format they will be in.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name declares the name by which a result is referenced in the Task’s +definition. Results may be referenced by name in the definition of a +Task’s steps.

+
+format
+ +string + +
+

TODO: maybe this is an enum with types like “go test”, “junit”, etc.

+
+path
+ +string + +
+
+

PipelineResourceSpec +

+

+(Appears on:PipelineResource, PipelineResourceBinding) +

+
+

PipelineResourceSpec defines an individual resources used in the pipeline.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the resource that may be +used to populate a UI.

+
+type
+ +string + +
+
+params
+ + +[]ResourceParam + + +
+
+secrets
+ + +[]SecretParam + + +
+(Optional) +

Secrets to fetch to populate some of resource fields

+
+

PipelineResourceStatus +

+

+(Appears on:PipelineResource) +

+
+

PipelineResourceStatus does not contain anything because PipelineResources on their own +do not have a status +Deprecated

+
+

ResourceDeclaration +

+

+(Appears on:ConditionSpec, TaskResource) +

+
+

ResourceDeclaration defines an input or output PipelineResource declared as a requirement +by another type such as a Task or Condition. The Name field will be used to refer to these +PipelineResources within the type’s definition, and when provided as an Input, the Name will be the +path to the volume mounted containing this PipelineResource as an input (e.g. +an input Resource named workspace will be mounted at /workspace).

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name declares the name by which a resource is referenced in the +definition. Resources may be referenced by name in the definition of a +Task’s steps.

+
+type
+ +string + +
+

Type is the type of this resource;

+
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the declared resource that may be +used to populate a UI.

+
+targetPath
+ +string + +
+(Optional) +

TargetPath is the path in workspace directory where the resource +will be copied.

+
+optional
+ +bool + +
+

Optional declares the resource as optional. +By default optional is set to false which makes a resource required. +optional: true - the resource is considered optional +optional: false - the resource is considered required (equivalent of not specifying it)

+
+

ResourceParam +

+

+(Appears on:PipelineResourceSpec) +

+
+

ResourceParam declares a string value to use for the parameter called Name, and is used in +the specific context of PipelineResources.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+
+value
+ +string + +
+
+

SecretParam +

+

+(Appears on:PipelineResourceSpec) +

+
+

SecretParam indicates which secret can be used to populate a field of the resource

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+fieldName
+ +string + +
+
+secretKey
+ +string + +
+
+secretName
+ +string + +
+
+

RunResult +

+

+(Appears on:RunStatusFields) +

+
+

RunResult used to describe the results of a task

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name the given name

+
+value
+ +string + +
+

Value the given value of the result

+
+

RunStatus +

+

+(Appears on:Run, PipelineRunRunStatus, RunStatusFields) +

+
+

RunStatus defines the observed state of Run

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+Status
+ + +knative.dev/pkg/apis/duck/v1.Status + + +
+

+(Members of Status are embedded into this type.) +

+
+RunStatusFields
+ + +RunStatusFields + + +
+

+(Members of RunStatusFields are embedded into this type.) +

+

RunStatusFields inlines the status fields.

+
+

RunStatusFields +

+

+(Appears on:RunStatus) +

+
+

RunStatusFields holds the fields of Run’s status. This is defined +separately and inlined so that other types can readily consume these fields +via duck typing.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+startTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

StartTime is the time the build is actually started.

+
+completionTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

CompletionTime is the time the build completed.

+
+results
+ + +[]RunResult + + +
+(Optional) +

Results reports any output result values to be consumed by later +tasks in a pipeline.

+
+retriesStatus
+ + +[]RunStatus + + +
+(Optional) +

RetriesStatus contains the history of RunStatus, in case of a retry.

+
+extraFields
+ +k8s.io/apimachinery/pkg/runtime.RawExtension + +
+

ExtraFields holds arbitrary fields provided by the custom task +controller.

+
+
+

tekton.dev/v1beta1

+
+

Package v1beta1 contains API Schema definitions for the pipeline v1beta1 API group

+
+Resource Types: + +

ClusterTask +

+
+

ClusterTask is a Task with a cluster scope. ClusterTasks are used to +represent Tasks that should be publicly addressable from any namespace in the +cluster.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1beta1 + +
+kind
+string +
ClusterTask
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +TaskSpec + + +
+(Optional) +

Spec holds the desired state of the Task from the client

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+resources
+ + +TaskResources + + +
+(Optional) +

Resources is a list input and output resource to run the task +Resources are represented in TaskRuns as bindings to instances of +PipelineResources.

+
+params
+ + +[]ParamSpec + + +
+(Optional) +

Params is a list of input parameters required to run the task. Params +must be supplied as inputs in TaskRuns unless they declare a default +value.

+
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the task that may be +used to populate a UI.

+
+steps
+ + +[]Step + + +
+

Steps are the steps of the build; each step is run sequentially with the +source mounted into /workspace.

+
+volumes
+ + +[]Kubernetes core/v1.Volume + + +
+

Volumes is a collection of volumes that are available to mount into the +steps of the build.

+
+stepTemplate
+ + +Kubernetes core/v1.Container + + +
+

StepTemplate can be used as the basis for all step containers within the +Task, so that the steps inherit settings on the base container.

+
+sidecars
+ + +[]Sidecar + + +
+

Sidecars are run alongside the Task’s step containers. They begin before +the steps start and end after the steps complete.

+
+workspaces
+ + +[]WorkspaceDeclaration + + +
+

Workspaces are the volumes that this Task requires.

+
+results
+ + +[]TaskResult + + +
+

Results are values that this Task can output

+
+
+

Pipeline +

+
+

Pipeline describes a list of Tasks to execute. It expresses how outputs +of tasks feed into inputs of subsequent tasks.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1beta1 + +
+kind
+string +
Pipeline
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +PipelineSpec + + +
+(Optional) +

Spec holds the desired state of the Pipeline from the client

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the pipeline that may be +used to populate a UI.

+
+resources
+ + +[]PipelineDeclaredResource + + +
+

Resources declares the names and types of the resources given to the +Pipeline’s tasks as inputs and outputs.

+
+tasks
+ + +[]PipelineTask + + +
+

Tasks declares the graph of Tasks that execute when this Pipeline is run.

+
+params
+ + +[]ParamSpec + + +
+

Params declares a list of input parameters that must be supplied when +this Pipeline is run.

+
+workspaces
+ + +[]PipelineWorkspaceDeclaration + + +
+(Optional) +

Workspaces declares a set of named workspaces that are expected to be +provided by a PipelineRun.

+
+results
+ + +[]PipelineResult + + +
+(Optional) +

Results are values that this pipeline can output once run

+
+finally
+ + +[]PipelineTask + + +
+

Finally declares the list of Tasks that execute just before leaving the Pipeline +i.e. either after all Tasks are finished executing successfully +or after a failure which would result in ending the Pipeline

+
+
+

PipelineRun +

+
+

PipelineRun represents a single execution of a Pipeline. PipelineRuns are how +the graph of Tasks declared in a Pipeline are executed; they specify inputs +to Pipelines such as parameter values and capture operational aspects of the +Tasks execution such as service account and tolerations. Creating a +PipelineRun creates TaskRuns for Tasks in the referenced Pipeline.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1beta1 + +
+kind
+string +
PipelineRun
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +PipelineRunSpec + + +
+(Optional) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+pipelineRef
+ + +PipelineRef + + +
+(Optional) +
+pipelineSpec
+ + +PipelineSpec + + +
+(Optional) +
+resources
+ + +[]PipelineResourceBinding + + +
+

Resources is a list of bindings specifying which actual instances of +PipelineResources to use for the resources the Pipeline has declared +it needs.

+
+params
+ + +[]Param + + +
+

Params is a list of parameter names and values.

+
+serviceAccountName
+ +string + +
+(Optional) +
+serviceAccountNames
+ + +[]PipelineRunSpecServiceAccountName + + +
+(Optional) +

Deprecated: use taskRunSpecs.ServiceAccountName instead

+
+status
+ + +PipelineRunSpecStatus + + +
+(Optional) +

Used for cancelling a pipelinerun (and maybe more later on)

+
+timeouts
+ + +TimeoutFields + + +
+(Optional) +

This is an alpha field. You must set the “enable-api-fields” feature flag to “alpha” +for this field to be supported.

+

Time after which the Pipeline times out. +Currently three keys are accepted in the map +pipeline, tasks and finally +with Timeouts.pipeline >= Timeouts.tasks + Timeouts.finally

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the Pipeline times out. Defaults to never. +Refer to Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces holds a set of workspace bindings that must match names +with those declared in the pipeline.

+
+taskRunSpecs
+ + +[]PipelineTaskRunSpec + + +
+(Optional) +

TaskRunSpecs holds a set of runtime specs

+
+
+status
+ + +PipelineRunStatus + + +
+(Optional) +
+

Task +

+
+

Task represents a collection of sequential steps that are run as part of a +Pipeline using a set of inputs and producing a set of outputs. Tasks execute +when TaskRuns are created that provide the input parameters and resources and +output resources the Task requires.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1beta1 + +
+kind
+string +
Task
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +TaskSpec + + +
+(Optional) +

Spec holds the desired state of the Task from the client

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+resources
+ + +TaskResources + + +
+(Optional) +

Resources is a list input and output resource to run the task +Resources are represented in TaskRuns as bindings to instances of +PipelineResources.

+
+params
+ + +[]ParamSpec + + +
+(Optional) +

Params is a list of input parameters required to run the task. Params +must be supplied as inputs in TaskRuns unless they declare a default +value.

+
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the task that may be +used to populate a UI.

+
+steps
+ + +[]Step + + +
+

Steps are the steps of the build; each step is run sequentially with the +source mounted into /workspace.

+
+volumes
+ + +[]Kubernetes core/v1.Volume + + +
+

Volumes is a collection of volumes that are available to mount into the +steps of the build.

+
+stepTemplate
+ + +Kubernetes core/v1.Container + + +
+

StepTemplate can be used as the basis for all step containers within the +Task, so that the steps inherit settings on the base container.

+
+sidecars
+ + +[]Sidecar + + +
+

Sidecars are run alongside the Task’s step containers. They begin before +the steps start and end after the steps complete.

+
+workspaces
+ + +[]WorkspaceDeclaration + + +
+

Workspaces are the volumes that this Task requires.

+
+results
+ + +[]TaskResult + + +
+

Results are values that this Task can output

+
+
+

TaskRun +

+
+

TaskRun represents a single execution of a Task. TaskRuns are how the steps +specified in a Task are executed; they specify the parameters and resources +used to run the steps in a Task.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +tekton.dev/v1beta1 + +
+kind
+string +
TaskRun
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +TaskRunSpec + + +
+(Optional) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+debug
+ + +TaskRunDebug + + +
+(Optional) +
+params
+ + +[]Param + + +
+(Optional) +
+resources
+ + +TaskRunResources + + +
+(Optional) +
+serviceAccountName
+ +string + +
+(Optional) +
+taskRef
+ + +TaskRef + + +
+(Optional) +

no more than one of the TaskRef and TaskSpec may be specified.

+
+taskSpec
+ + +TaskSpec + + +
+(Optional) +
+status
+ + +TaskRunSpecStatus + + +
+(Optional) +

Used for cancelling a taskrun (and maybe more later on)

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the build times out. Defaults to 1 hour. +Specified build timeout should be less than 24h. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces is a list of WorkspaceBindings from volumes to workspaces.

+
+
+status
+ + +TaskRunStatus + + +
+(Optional) +
+

ArrayOrString +

+

+(Appears on:Param, ParamSpec) +

+
+

ArrayOrString is a type that can hold a single string or string array. +Used in JSON unmarshalling so that a single JSON field can accept +either an individual string or an array of strings.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+type
+ + +ParamType + + +
+
+stringVal
+ +string + +
+

Represents the stored type of ArrayOrString.

+
+arrayVal
+ +[]string + +
+
+

CloudEventCondition +(string alias)

+

+(Appears on:CloudEventDeliveryState) +

+
+

CloudEventCondition is a string that represents the condition of the event.

+
+ + + + + + + + + + + + + + +
ValueDescription

"Failed"

CloudEventConditionFailed means that there was one or more attempts to +send the event, and none was successful so far.

+

"Sent"

CloudEventConditionSent means that the event was sent successfully

+

"Unknown"

CloudEventConditionUnknown means that the condition for the event to be +triggered was not met yet, or we don’t know the state yet.

+
+

CloudEventDelivery +

+

+(Appears on:TaskRunStatusFields) +

+
+

CloudEventDelivery is the target of a cloud event along with the state of +delivery.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+target
+ +string + +
+

Target points to an addressable

+
+status
+ + +CloudEventDeliveryState + + +
+
+

CloudEventDeliveryState +

+

+(Appears on:CloudEventDelivery) +

+
+

CloudEventDeliveryState reports the state of a cloud event to be sent.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+condition
+ + +CloudEventCondition + + +
+

Current status

+
+sentAt
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

SentAt is the time at which the last attempt to send the event was made

+
+message
+ +string + +
+

Error is the text of error (if any)

+
+retryCount
+ +int32 + +
+

RetryCount is the number of attempts of sending the cloud event

+
+

ConditionCheck +

+
+

ConditionCheck represents a single evaluation of a Condition step.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +TaskRunSpec + + +
+(Optional) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+debug
+ + +TaskRunDebug + + +
+(Optional) +
+params
+ + +[]Param + + +
+(Optional) +
+resources
+ + +TaskRunResources + + +
+(Optional) +
+serviceAccountName
+ +string + +
+(Optional) +
+taskRef
+ + +TaskRef + + +
+(Optional) +

no more than one of the TaskRef and TaskSpec may be specified.

+
+taskSpec
+ + +TaskSpec + + +
+(Optional) +
+status
+ + +TaskRunSpecStatus + + +
+(Optional) +

Used for cancelling a taskrun (and maybe more later on)

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the build times out. Defaults to 1 hour. +Specified build timeout should be less than 24h. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces is a list of WorkspaceBindings from volumes to workspaces.

+
+
+status
+ + +TaskRunStatus + + +
+(Optional) +
+

ConditionCheckStatus +

+

+(Appears on:PipelineRunConditionCheckStatus) +

+
+

ConditionCheckStatus defines the observed state of ConditionCheck

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+Status
+ + +knative.dev/pkg/apis/duck/v1beta1.Status + + +
+

+(Members of Status are embedded into this type.) +

+
+ConditionCheckStatusFields
+ + +ConditionCheckStatusFields + + +
+

+(Members of ConditionCheckStatusFields are embedded into this type.) +

+

ConditionCheckStatusFields inlines the status fields.

+
+

ConditionCheckStatusFields +

+

+(Appears on:ConditionCheckStatus) +

+
+

ConditionCheckStatusFields holds the fields of ConfigurationCheck’s status. +This is defined separately and inlined so that other types can readily +consume these fields via duck typing.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+podName
+ +string + +
+

PodName is the name of the pod responsible for executing this condition check.

+
+startTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

StartTime is the time the check is actually started.

+
+completionTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

CompletionTime is the time the check pod completed.

+
+check
+ + +Kubernetes core/v1.ContainerState + + +
+(Optional) +

Check describes the state of the check container.

+
+

EmbeddedTask +

+

+(Appears on:PipelineTask) +

+
+

EmbeddedTask is used to define a Task inline within a Pipeline’s PipelineTasks.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+spec
+ +k8s.io/apimachinery/pkg/runtime.RawExtension + +
+(Optional) +

Spec is a specification of a custom task

+
+
+ + + + + + + + + +
+-
+ +[]byte + +
+

Raw is the underlying serialization of this object.

+

TODO: Determine how to detect ContentType and ContentEncoding of ‘Raw’ data.

+
+-
+ +k8s.io/apimachinery/pkg/runtime.Object + +
+

Object can hold a representation of this extension - useful for working with versioned +structs.

+
+
+metadata
+ + +PipelineTaskMetadata + + +
+(Optional) +
+TaskSpec
+ + +TaskSpec + + +
+

+(Members of TaskSpec are embedded into this type.) +

+(Optional) +

TaskSpec is a specification of a task

+
+

InternalTaskModifier +

+
+

InternalTaskModifier implements TaskModifier for resources that are built-in to Tekton Pipelines.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+StepsToPrepend
+ + +[]Step + + +
+
+StepsToAppend
+ + +[]Step + + +
+
+Volumes
+ + +[]Kubernetes core/v1.Volume + + +
+
+

Param +

+

+(Appears on:PipelineRunSpec, PipelineTask, RunSpec, TaskRunInputs, TaskRunSpec, PipelineRunSpec, PipelineTask, PipelineTaskCondition, TaskRunInputs, TaskRunSpec) +

+
+

Param declares an ArrayOrString to use for the parameter called name.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+
+value
+ + +ArrayOrString + + +
+
+

ParamSpec +

+

+(Appears on:ConditionSpec, Inputs, PipelineSpec, PipelineSpec, TaskSpec) +

+
+

ParamSpec defines arbitrary parameters needed beyond typed inputs (such as +resources). Parameter values are provided by users as inputs on a TaskRun +or PipelineRun.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name declares the name by which a parameter is referenced.

+
+type
+ + +ParamType + + +
+(Optional) +

Type is the user-specified type of the parameter. The possible types +are currently “string” and “array”, and “string” is the default.

+
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the parameter that may be +used to populate a UI.

+
+default
+ + +ArrayOrString + + +
+(Optional) +

Default is the value a parameter takes if no input value is supplied. If +default is set, a Task may be executed without a supplied value for the +parameter.

+
+

ParamType +(string alias)

+

+(Appears on:ArrayOrString, ParamSpec) +

+
+

ParamType indicates the type of an input parameter; +Used to distinguish between a single string and an array of strings.

+
+ + + + + + + + + + + + +
ValueDescription

"array"

"string"

+

PipelineDeclaredResource +

+

+(Appears on:PipelineSpec, PipelineSpec) +

+
+

PipelineDeclaredResource is used by a Pipeline to declare the types of the +PipelineResources that it will required to run and names which can be used to +refer to these PipelineResources in PipelineTaskResourceBindings.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name that will be used by the Pipeline to refer to this resource. +It does not directly correspond to the name of any PipelineResources Task +inputs or outputs, and it does not correspond to the actual names of the +PipelineResources that will be bound in the PipelineRun.

+
+type
+ +string + +
+

Type is the type of the PipelineResource.

+
+optional
+ +bool + +
+

Optional declares the resource as optional. +optional: true - the resource is considered optional +optional: false - the resource is considered required (default/equivalent of not specifying it)

+
+

PipelineObject +

+
+

PipelineObject is implemented by Pipeline and ClusterPipeline

+
+

PipelineRef +

+

+(Appears on:PipelineRunSpec, PipelineRunSpec) +

+
+

PipelineRef can be used to refer to a specific instance of a Pipeline. +Copied from CrossVersionObjectReference: https://github.com/kubernetes/kubernetes/blob/169df7434155cbbc22f1532cba8e0a9588e29ad8/pkg/apis/autoscaling/types.go#L64

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names

+
+apiVersion
+ +string + +
+(Optional) +

API version of the referent

+
+bundle
+ +string + +
+(Optional) +

Bundle url reference to a Tekton Bundle.

+
+

PipelineResourceBinding +

+

+(Appears on:PipelineRunSpec, PipelineRunSpec, TaskResourceBinding) +

+
+

PipelineResourceBinding connects a reference to an instance of a PipelineResource +with a PipelineResource dependency that the Pipeline has declared

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of the PipelineResource in the Pipeline’s declaration

+
+resourceRef
+ + +PipelineResourceRef + + +
+(Optional) +

ResourceRef is a reference to the instance of the actual PipelineResource +that should be used

+
+resourceSpec
+ + +PipelineResourceSpec + + +
+(Optional) +

ResourceSpec is specification of a resource that should be created and +consumed by the task

+
+

PipelineResourceInterface +

+
+

PipelineResourceInterface interface to be implemented by different PipelineResource types

+
+

PipelineResourceRef +

+

+(Appears on:PipelineResourceBinding, PipelineResourceResult) +

+
+

PipelineResourceRef can be used to refer to a specific instance of a Resource

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names

+
+apiVersion
+ +string + +
+(Optional) +

API version of the referent

+
+

PipelineResourceResult +

+

+(Appears on:TaskRunStatusFields) +

+
+

PipelineResourceResult used to export the image name and digest as json

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+key
+ +string + +
+
+value
+ +string + +
+
+resourceName
+ +string + +
+
+resourceRef
+ + +PipelineResourceRef + + +
+

The field ResourceRef should be deprecated and removed in the next API version. +See https://github.com/tektoncd/pipeline/issues/2694 for more information.

+
+type
+ + +ResultType + + +
+
+

PipelineResult +

+

+(Appears on:PipelineSpec, PipelineSpec) +

+
+

PipelineResult used to describe the results of a pipeline

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name the given name

+
+description
+ +string + +
+(Optional) +

Description is a human-readable description of the result

+
+value
+ +string + +
+

Value the expression used to retrieve the value

+
+

PipelineRunConditionCheckStatus +

+

+(Appears on:PipelineRunTaskRunStatus) +

+
+

PipelineRunConditionCheckStatus returns the condition check status

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+conditionName
+ +string + +
+

ConditionName is the name of the Condition

+
+status
+ + +ConditionCheckStatus + + +
+(Optional) +

Status is the ConditionCheckStatus for the corresponding ConditionCheck

+
+

PipelineRunReason +(string alias)

+
+

PipelineRunReason represents a reason for the pipeline run “Succeeded” condition

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueDescription

"Cancelled"

PipelineRunReasonCancelled is the reason set when the PipelineRun cancelled by the user +This reason may be found with a corev1.ConditionFalse status, if the cancellation was processed successfully +This reason may be found with a corev1.ConditionUnknown status, if the cancellation is being processed or failed

+

"CancelledRunningFinally"

PipelineRunReasonCancelledRunningFinally indicates that pipeline has been gracefully cancelled +and no new Tasks will be scheduled by the controller, but final tasks are now running

+

"Completed"

PipelineRunReasonCompleted is the reason set when the PipelineRun completed successfully with one or more skipped Tasks

+

"Failed"

PipelineRunReasonFailed is the reason set when the PipelineRun completed with a failure

+

"PipelineRunPending"

PipelineRunReasonPending is the reason set when the PipelineRun is in the pending state

+

"Running"

PipelineRunReasonRunning is the reason set when the PipelineRun is running

+

"Started"

PipelineRunReasonStarted is the reason set when the PipelineRun has just started

+

"StoppedRunningFinally"

PipelineRunReasonStoppedRunningFinally indicates that pipeline has been gracefully stopped +and no new Tasks will be scheduled by the controller, but final tasks are now running

+

"PipelineRunStopping"

PipelineRunReasonStopping indicates that no new Tasks will be scheduled by the controller, and the +pipeline will stop once all running tasks complete their work

+

"Succeeded"

PipelineRunReasonSuccessful is the reason set when the PipelineRun completed successfully

+

"PipelineRunTimeout"

PipelineRunReasonTimedOut is the reason set when the PipelineRun has timed out

+
+

PipelineRunResult +

+

+(Appears on:PipelineRunStatusFields) +

+
+

PipelineRunResult used to describe the results of a pipeline

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the result’s name as declared by the Pipeline

+
+value
+ +string + +
+

Value is the result returned from the execution of this PipelineRun

+
+

PipelineRunRunStatus +

+

+(Appears on:PipelineRunStatusFields) +

+
+

PipelineRunRunStatus contains the name of the PipelineTask for this Run and the Run’s Status

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pipelineTaskName
+ +string + +
+

PipelineTaskName is the name of the PipelineTask.

+
+status
+ + +RunStatus + + +
+(Optional) +

Status is the RunStatus for the corresponding Run

+
+whenExpressions
+ + +[]WhenExpression + + +
+(Optional) +

WhenExpressions is the list of checks guarding the execution of the PipelineTask

+
+

PipelineRunSpec +

+

+(Appears on:PipelineRun) +

+
+

PipelineRunSpec defines the desired state of PipelineRun

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pipelineRef
+ + +PipelineRef + + +
+(Optional) +
+pipelineSpec
+ + +PipelineSpec + + +
+(Optional) +
+resources
+ + +[]PipelineResourceBinding + + +
+

Resources is a list of bindings specifying which actual instances of +PipelineResources to use for the resources the Pipeline has declared +it needs.

+
+params
+ + +[]Param + + +
+

Params is a list of parameter names and values.

+
+serviceAccountName
+ +string + +
+(Optional) +
+serviceAccountNames
+ + +[]PipelineRunSpecServiceAccountName + + +
+(Optional) +

Deprecated: use taskRunSpecs.ServiceAccountName instead

+
+status
+ + +PipelineRunSpecStatus + + +
+(Optional) +

Used for cancelling a pipelinerun (and maybe more later on)

+
+timeouts
+ + +TimeoutFields + + +
+(Optional) +

This is an alpha field. You must set the “enable-api-fields” feature flag to “alpha” +for this field to be supported.

+

Time after which the Pipeline times out. +Currently three keys are accepted in the map +pipeline, tasks and finally +with Timeouts.pipeline >= Timeouts.tasks + Timeouts.finally

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the Pipeline times out. Defaults to never. +Refer to Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces holds a set of workspace bindings that must match names +with those declared in the pipeline.

+
+taskRunSpecs
+ + +[]PipelineTaskRunSpec + + +
+(Optional) +

TaskRunSpecs holds a set of runtime specs

+
+

PipelineRunSpecServiceAccountName +

+

+(Appears on:PipelineRunSpec, PipelineRunSpec) +

+
+

PipelineRunSpecServiceAccountName can be used to configure specific +ServiceAccountName for a concrete Task

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+taskName
+ +string + +
+
+serviceAccountName
+ +string + +
+
+

PipelineRunSpecStatus +(string alias)

+

+(Appears on:PipelineRunSpec, PipelineRunSpec) +

+
+

PipelineRunSpecStatus defines the pipelinerun spec status the user can provide

+
+

PipelineRunStatus +

+

+(Appears on:PipelineRun, PipelineRun) +

+
+

PipelineRunStatus defines the observed state of PipelineRun

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+Status
+ + +knative.dev/pkg/apis/duck/v1beta1.Status + + +
+

+(Members of Status are embedded into this type.) +

+
+PipelineRunStatusFields
+ + +PipelineRunStatusFields + + +
+

+(Members of PipelineRunStatusFields are embedded into this type.) +

+

PipelineRunStatusFields inlines the status fields.

+
+

PipelineRunStatusFields +

+

+(Appears on:PipelineRunStatus) +

+
+

PipelineRunStatusFields holds the fields of PipelineRunStatus’ status. +This is defined separately and inlined so that other types can readily +consume these fields via duck typing.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+startTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

StartTime is the time the PipelineRun is actually started.

+
+completionTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

CompletionTime is the time the PipelineRun completed.

+
+taskRuns
+ + +map[string]*github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.PipelineRunTaskRunStatus + + +
+(Optional) +

map of PipelineRunTaskRunStatus with the taskRun name as the key

+
+runs
+ + +map[string]*github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.PipelineRunRunStatus + + +
+(Optional) +

map of PipelineRunRunStatus with the run name as the key

+
+pipelineResults
+ + +[]PipelineRunResult + + +
+(Optional) +

PipelineResults are the list of results written out by the pipeline task’s containers

+
+pipelineSpec
+ + +PipelineSpec + + +
+

PipelineRunSpec contains the exact spec used to instantiate the run

+
+skippedTasks
+ + +[]SkippedTask + + +
+(Optional) +

list of tasks that were skipped due to when expressions evaluating to false

+
+

PipelineRunTaskRunStatus +

+

+(Appears on:PipelineRunStatusFields) +

+
+

PipelineRunTaskRunStatus contains the name of the PipelineTask for this TaskRun and the TaskRun’s Status

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pipelineTaskName
+ +string + +
+

PipelineTaskName is the name of the PipelineTask.

+
+status
+ + +TaskRunStatus + + +
+(Optional) +

Status is the TaskRunStatus for the corresponding TaskRun

+
+conditionChecks
+ + +map[string]*github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.PipelineRunConditionCheckStatus + + +
+(Optional) +

ConditionChecks maps the name of a condition check to its Status

+
+whenExpressions
+ + +[]WhenExpression + + +
+(Optional) +

WhenExpressions is the list of checks guarding the execution of the PipelineTask

+
+

PipelineSpec +

+

+(Appears on:Pipeline, PipelineRunSpec, PipelineRunStatusFields) +

+
+

PipelineSpec defines the desired state of Pipeline.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the pipeline that may be +used to populate a UI.

+
+resources
+ + +[]PipelineDeclaredResource + + +
+

Resources declares the names and types of the resources given to the +Pipeline’s tasks as inputs and outputs.

+
+tasks
+ + +[]PipelineTask + + +
+

Tasks declares the graph of Tasks that execute when this Pipeline is run.

+
+params
+ + +[]ParamSpec + + +
+

Params declares a list of input parameters that must be supplied when +this Pipeline is run.

+
+workspaces
+ + +[]PipelineWorkspaceDeclaration + + +
+(Optional) +

Workspaces declares a set of named workspaces that are expected to be +provided by a PipelineRun.

+
+results
+ + +[]PipelineResult + + +
+(Optional) +

Results are values that this pipeline can output once run

+
+finally
+ + +[]PipelineTask + + +
+

Finally declares the list of Tasks that execute just before leaving the Pipeline +i.e. either after all Tasks are finished executing successfully +or after a failure which would result in ending the Pipeline

+
+

PipelineTask +

+

+(Appears on:PipelineSpec) +

+
+

PipelineTask defines a task in a Pipeline, passing inputs from both +Params and from the output of previous tasks.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of this task within the context of a Pipeline. Name is +used as a coordinate with the from and runAfter fields to establish +the execution order of tasks relative to one another.

+
+taskRef
+ + +TaskRef + + +
+(Optional) +

TaskRef is a reference to a task definition.

+
+taskSpec
+ + +EmbeddedTask + + +
+(Optional) +

TaskSpec is a specification of a task

+
+conditions
+ + +[]PipelineTaskCondition + + +
+(Optional) +

Conditions is a list of conditions that need to be true for the task to run +Conditions are deprecated, use WhenExpressions instead

+
+when
+ + +WhenExpressions + + +
+(Optional) +

WhenExpressions is a list of when expressions that need to be true for the task to run

+
+retries
+ +int + +
+(Optional) +

Retries represents how many times this task should be retried in case of task failure: ConditionSucceeded set to False

+
+runAfter
+ +[]string + +
+(Optional) +

RunAfter is the list of PipelineTask names that should be executed before +this Task executes. (Used to force a specific ordering in graph execution.)

+
+resources
+ + +PipelineTaskResources + + +
+(Optional) +

Resources declares the resources given to this task as inputs and +outputs.

+
+params
+ + +[]Param + + +
+(Optional) +

Parameters declares parameters passed to this task.

+
+workspaces
+ + +[]WorkspacePipelineTaskBinding + + +
+(Optional) +

Workspaces maps workspaces from the pipeline spec to the workspaces +declared in the Task.

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the TaskRun times out. Defaults to 1 hour. +Specified TaskRun timeout should be less than 24h. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+

PipelineTaskCondition +

+

+(Appears on:PipelineTask, PipelineTask) +

+
+

PipelineTaskCondition allows a PipelineTask to declare a Condition to be evaluated before +the Task is run.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+conditionRef
+ +string + +
+

ConditionRef is the name of the Condition to use for the conditionCheck

+
+params
+ + +[]Param + + +
+(Optional) +

Params declare parameters passed to this Condition

+
+resources
+ + +[]PipelineTaskInputResource + + +
+

Resources declare the resources provided to this Condition as input

+
+

PipelineTaskInputResource +

+

+(Appears on:PipelineTaskCondition, PipelineTaskResources) +

+
+

PipelineTaskInputResource maps the name of a declared PipelineResource input +dependency in a Task to the resource in the Pipeline’s DeclaredPipelineResources +that should be used. This input may come from a previous task.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of the PipelineResource as declared by the Task.

+
+resource
+ +string + +
+

Resource is the name of the DeclaredPipelineResource to use.

+
+from
+ +[]string + +
+(Optional) +

From is the list of PipelineTask names that the resource has to come from. +(Implies an ordering in the execution graph.)

+
+

PipelineTaskMetadata +

+

+(Appears on:EmbeddedRunSpec, EmbeddedTask) +

+
+

PipelineTaskMetadata contains the labels or annotations for an EmbeddedTask

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+labels
+ +map[string]string + +
+(Optional) +
+annotations
+ +map[string]string + +
+(Optional) +
+

PipelineTaskOutputResource +

+

+(Appears on:PipelineTaskResources) +

+
+

PipelineTaskOutputResource maps the name of a declared PipelineResource output +dependency in a Task to the resource in the Pipeline’s DeclaredPipelineResources +that should be used.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of the PipelineResource as declared by the Task.

+
+resource
+ +string + +
+

Resource is the name of the DeclaredPipelineResource to use.

+
+

PipelineTaskParam +

+
+

PipelineTaskParam is used to provide arbitrary string parameters to a Task.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+
+value
+ +string + +
+
+

PipelineTaskResources +

+

+(Appears on:PipelineTask, PipelineTask) +

+
+

PipelineTaskResources allows a Pipeline to declare how its DeclaredPipelineResources +should be provided to a Task as its inputs and outputs.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+inputs
+ + +[]PipelineTaskInputResource + + +
+

Inputs holds the mapping from the PipelineResources declared in +DeclaredPipelineResources to the input PipelineResources required by the Task.

+
+outputs
+ + +[]PipelineTaskOutputResource + + +
+

Outputs holds the mapping from the PipelineResources declared in +DeclaredPipelineResources to the input PipelineResources required by the Task.

+
+

PipelineTaskRun +

+
+

PipelineTaskRun reports the results of running a step in the Task. Each +task has the potential to succeed or fail (based on the exit code) +and produces logs.

+
+ + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+
+

PipelineTaskRunSpec +

+

+(Appears on:PipelineRunSpec) +

+
+

PipelineTaskRunSpec can be used to configure specific +specs for a concrete Task

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pipelineTaskName
+ +string + +
+
+taskServiceAccountName
+ +string + +
+
+taskPodTemplate
+ + +Template + + +
+
+

PipelineWorkspaceDeclaration +

+

+(Appears on:PipelineSpec, PipelineSpec) +

+
+

WorkspacePipelineDeclaration creates a named slot in a Pipeline that a PipelineRun +is expected to populate with a workspace binding. +Deprecated: use PipelineWorkspaceDeclaration type instead

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of a workspace to be provided by a PipelineRun.

+
+description
+ +string + +
+(Optional) +

Description is a human readable string describing how the workspace will be +used in the Pipeline. It can be useful to include a bit of detail about which +tasks are intended to have access to the data on the workspace.

+
+optional
+ +bool + +
+

Optional marks a Workspace as not being required in PipelineRuns. By default +this field is false and so declared workspaces are required.

+
+

ResultRef +

+
+

ResultRef is a type that represents a reference to a task run result

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+PipelineTask
+ +string + +
+
+Result
+ +string + +
+
+

ResultType +(int alias)

+

+(Appears on:PipelineResourceResult) +

+
+

ResultType used to find out whether a PipelineResourceResult is from a task result or not

+
+ + + + + + + + + + +
ValueDescription

1

TaskRunResultType default task run result value

+
+

Sidecar +

+

+(Appears on:TaskSpec) +

+
+

Sidecar has nearly the same data structure as Step, consisting of a Container and an optional Script, but does not have the ability to timeout.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+Container
+ + +Kubernetes core/v1.Container + + +
+

+(Members of Container are embedded into this type.) +

+
+script
+ +string + +
+(Optional) +

Script is the contents of an executable file to execute.

+

If Script is not empty, the Step cannot have an Command or Args.

+
+workspaces
+ + +[]WorkspaceUsage + + +
+(Optional) +

This is an alpha field. You must set the “enable-api-fields” feature flag to “alpha” +for this field to be supported.

+

Workspaces is a list of workspaces from the Task that this Sidecar wants +exclusive access to. Adding a workspace to this list means that any +other Step or Sidecar that does not also request this Workspace will +not have access to it.

+
+

SidecarState +

+

+(Appears on:TaskRunStatusFields) +

+
+

SidecarState reports the results of running a sidecar in a Task.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ContainerState
+ + +Kubernetes core/v1.ContainerState + + +
+

+(Members of ContainerState are embedded into this type.) +

+
+name
+ +string + +
+
+container
+ +string + +
+
+imageID
+ +string + +
+
+

SkippedTask +

+

+(Appears on:PipelineRunStatusFields) +

+
+

SkippedTask is used to describe the Tasks that were skipped due to their When Expressions +evaluating to False. This is a struct because we are looking into including more details +about the When Expressions that caused this Task to be skipped.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the Pipeline Task name

+
+whenExpressions
+ + +[]WhenExpression + + +
+(Optional) +

WhenExpressions is the list of checks guarding the execution of the PipelineTask

+
+

Step +

+

+(Appears on:ConditionSpec, InternalTaskModifier, TaskSpec) +

+
+

Step embeds the Container type, which allows it to include fields not +provided by Container.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+Container
+ + +Kubernetes core/v1.Container + + +
+

+(Members of Container are embedded into this type.) +

+
+script
+ +string + +
+(Optional) +

Script is the contents of an executable file to execute.

+

If Script is not empty, the Step cannot have an Command and the Args will be passed to the Script.

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Timeout is the time after which the step times out. Defaults to never. +Refer to Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+workspaces
+ + +[]WorkspaceUsage + + +
+(Optional) +

This is an alpha field. You must set the “enable-api-fields” feature flag to “alpha” +for this field to be supported.

+

Workspaces is a list of workspaces from the Task that this Step wants +exclusive access to. Adding a workspace to this list means that any +other Step or Sidecar that does not also request this Workspace will +not have access to it.

+
+onError
+ +string + +
+

OnError defines the exiting behavior of a container on error +can be set to [ continue | stopAndFail ] +stopAndFail indicates exit the taskRun if the container exits with non-zero exit code +continue indicates continue executing the rest of the steps irrespective of the container exit code

+
+

StepState +

+

+(Appears on:TaskRunStatusFields) +

+
+

StepState reports the results of running a step in a Task.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ContainerState
+ + +Kubernetes core/v1.ContainerState + + +
+

+(Members of ContainerState are embedded into this type.) +

+
+name
+ +string + +
+
+container
+ +string + +
+
+imageID
+ +string + +
+
+

TaskKind +(string alias)

+

+(Appears on:TaskRef) +

+
+

TaskKind defines the type of Task used by the pipeline.

+
+ + + + + + + + + + + + +
ValueDescription

"ClusterTask"

ClusterTaskKind indicates that task type has a cluster scope.

+

"Task"

NamespacedTaskKind indicates that the task type has a namespaced scope.

+
+

TaskModifier +

+
+

TaskModifier is an interface to be implemented by different PipelineResources

+
+

TaskObject +

+
+

TaskObject is implemented by Task and ClusterTask

+
+

TaskRef +

+

+(Appears on:PipelineTask, RunSpec, TaskRunSpec, PipelineTask, TaskRunSpec) +

+
+

TaskRef can be used to refer to a specific instance of a task. +Copied from CrossVersionObjectReference: https://github.com/kubernetes/kubernetes/blob/169df7434155cbbc22f1532cba8e0a9588e29ad8/pkg/apis/autoscaling/types.go#L64

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names

+
+kind
+ + +TaskKind + + +
+

TaskKind indicates the kind of the task, namespaced or cluster scoped.

+
+apiVersion
+ +string + +
+(Optional) +

API version of the referent

+
+bundle
+ +string + +
+(Optional) +

Bundle url reference to a Tekton Bundle.

+
+

TaskResource +

+

+(Appears on:Inputs, Outputs, TaskResources) +

+
+

TaskResource defines an input or output Resource declared as a requirement +by a Task. The Name field will be used to refer to these Resources within +the Task definition, and when provided as an Input, the Name will be the +path to the volume mounted containing this Resource as an input (e.g. +an input Resource named workspace will be mounted at /workspace).

+
+ + + + + + + + + + + + + +
FieldDescription
+ResourceDeclaration
+ + +ResourceDeclaration + + +
+

+(Members of ResourceDeclaration are embedded into this type.) +

+
+

TaskResourceBinding +

+

+(Appears on:TaskRunInputs, TaskRunOutputs, TaskRunInputs, TaskRunOutputs, TaskRunResources) +

+
+

TaskResourceBinding points to the PipelineResource that +will be used for the Task input or output called Name.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+PipelineResourceBinding
+ + +PipelineResourceBinding + + +
+

+(Members of PipelineResourceBinding are embedded into this type.) +

+
+paths
+ +[]string + +
+(Optional) +

Paths will probably be removed in #1284, and then PipelineResourceBinding can be used instead. +The optional Path field corresponds to a path on disk at which the Resource can be found +(used when providing the resource via mounted volume, overriding the default logic to fetch the Resource).

+
+

TaskResources +

+

+(Appears on:TaskSpec) +

+
+

TaskResources allows a Pipeline to declare how its DeclaredPipelineResources +should be provided to a Task as its inputs and outputs.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+inputs
+ + +[]TaskResource + + +
+

Inputs holds the mapping from the PipelineResources declared in +DeclaredPipelineResources to the input PipelineResources required by the Task.

+
+outputs
+ + +[]TaskResource + + +
+

Outputs holds the mapping from the PipelineResources declared in +DeclaredPipelineResources to the input PipelineResources required by the Task.

+
+

TaskResult +

+

+(Appears on:TaskSpec) +

+
+

TaskResult used to describe the results of a task

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name the given name

+
+description
+ +string + +
+(Optional) +

Description is a human-readable description of the result

+
+

TaskRunDebug +

+

+(Appears on:TaskRunSpec) +

+
+

TaskRunDebug defines the breakpoint config for a particular TaskRun

+
+ + + + + + + + + + + + + +
FieldDescription
+breakpoint
+ +[]string + +
+(Optional) +
+

TaskRunInputs +

+
+

TaskRunInputs holds the input values that this task was invoked with.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+resources
+ + +[]TaskResourceBinding + + +
+(Optional) +
+params
+ + +[]Param + + +
+(Optional) +
+

TaskRunOutputs +

+
+

TaskRunOutputs holds the output values that this task was invoked with.

+
+ + + + + + + + + + + + + +
FieldDescription
+resources
+ + +[]TaskResourceBinding + + +
+(Optional) +
+

TaskRunReason +(string alias)

+
+

TaskRunReason is an enum used to store all TaskRun reason for +the Succeeded condition that are controlled by the TaskRun itself. Failure +reasons that emerge from underlying resources are not included here

+
+ + + + + + + + + + + + + + + + + + + + +
ValueDescription

"TaskRunCancelled"

TaskRunReasonCancelled is the reason set when the Taskrun is cancelled by the user

+

"Failed"

TaskRunReasonFailed is the reason set when the TaskRun completed with a failure

+

"Running"

TaskRunReasonRunning is the reason set when the TaskRun is running

+

"Started"

TaskRunReasonStarted is the reason set when the TaskRun has just started

+

"Succeeded"

TaskRunReasonSuccessful is the reason set when the TaskRun completed successfully

+

"TaskRunTimeout"

TaskRunReasonTimedOut is the reason set when the Taskrun has timed out

+
+

TaskRunResources +

+

+(Appears on:TaskRunSpec, TaskRunSpec) +

+
+

TaskRunResources allows a TaskRun to declare inputs and outputs TaskResourceBinding

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+inputs
+ + +[]TaskResourceBinding + + +
+

Inputs holds the inputs resources this task was invoked with

+
+outputs
+ + +[]TaskResourceBinding + + +
+

Outputs holds the inputs resources this task was invoked with

+
+

TaskRunResult +

+

+(Appears on:TaskRunStatusFields) +

+
+

TaskRunResult used to describe the results of a task

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name the given name

+
+value
+ +string + +
+

Value the given value of the result

+
+

TaskRunSpec +

+

+(Appears on:TaskRun, ConditionCheck) +

+
+

TaskRunSpec defines the desired state of TaskRun

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+debug
+ + +TaskRunDebug + + +
+(Optional) +
+params
+ + +[]Param + + +
+(Optional) +
+resources
+ + +TaskRunResources + + +
+(Optional) +
+serviceAccountName
+ +string + +
+(Optional) +
+taskRef
+ + +TaskRef + + +
+(Optional) +

no more than one of the TaskRef and TaskSpec may be specified.

+
+taskSpec
+ + +TaskSpec + + +
+(Optional) +
+status
+ + +TaskRunSpecStatus + + +
+(Optional) +

Used for cancelling a taskrun (and maybe more later on)

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Time after which the build times out. Defaults to 1 hour. +Specified build timeout should be less than 24h. +Refer Go’s ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration

+
+podTemplate
+ + +Template + + +
+

PodTemplate holds pod specific configuration

+
+workspaces
+ + +[]WorkspaceBinding + + +
+(Optional) +

Workspaces is a list of WorkspaceBindings from volumes to workspaces.

+
+

TaskRunSpecStatus +(string alias)

+

+(Appears on:TaskRunSpec, TaskRunSpec) +

+
+

TaskRunSpecStatus defines the taskrun spec status the user can provide

+
+

TaskRunStatus +

+

+(Appears on:TaskRun, TaskRun, ConditionCheck, ConditionCheck, PipelineRunTaskRunStatus, TaskRunStatusFields) +

+
+

TaskRunStatus defines the observed state of TaskRun

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+Status
+ + +knative.dev/pkg/apis/duck/v1beta1.Status + + +
+

+(Members of Status are embedded into this type.) +

+
+TaskRunStatusFields
+ + +TaskRunStatusFields + + +
+

+(Members of TaskRunStatusFields are embedded into this type.) +

+

TaskRunStatusFields inlines the status fields.

+
+

TaskRunStatusFields +

+

+(Appears on:TaskRunStatus) +

+
+

TaskRunStatusFields holds the fields of TaskRun’s status. This is defined +separately and inlined so that other types can readily consume these fields +via duck typing.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+podName
+ +string + +
+

PodName is the name of the pod responsible for executing this task’s steps.

+
+startTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

StartTime is the time the build is actually started.

+
+completionTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

CompletionTime is the time the build completed.

+
+steps
+ + +[]StepState + + +
+(Optional) +

Steps describes the state of each build step container.

+
+cloudEvents
+ + +[]CloudEventDelivery + + +
+(Optional) +

CloudEvents describe the state of each cloud event requested via a +CloudEventResource.

+
+retriesStatus
+ + +[]TaskRunStatus + + +
+(Optional) +

RetriesStatus contains the history of TaskRunStatus in case of a retry in order to keep record of failures. +All TaskRunStatus stored in RetriesStatus will have no date within the RetriesStatus as is redundant.

+
+resourcesResult
+ + +[]PipelineResourceResult + + +
+(Optional) +

Results from Resources built during the taskRun. currently includes +the digest of build container images

+
+taskResults
+ + +[]TaskRunResult + + +
+(Optional) +

TaskRunResults are the list of results written out by the task’s containers

+
+sidecars
+ + +[]SidecarState + + +
+

The list has one entry per sidecar in the manifest. Each entry is +represents the imageid of the corresponding sidecar.

+
+taskSpec
+ + +TaskSpec + + +
+

TaskSpec contains the Spec from the dereferenced Task definition used to instantiate this TaskRun.

+
+

TaskSpec +

+

+(Appears on:ClusterTask, Task, TaskSpec, EmbeddedTask, TaskRunSpec, TaskRunStatusFields) +

+
+

TaskSpec defines the desired state of Task.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+resources
+ + +TaskResources + + +
+(Optional) +

Resources is a list input and output resource to run the task +Resources are represented in TaskRuns as bindings to instances of +PipelineResources.

+
+params
+ + +[]ParamSpec + + +
+(Optional) +

Params is a list of input parameters required to run the task. Params +must be supplied as inputs in TaskRuns unless they declare a default +value.

+
+description
+ +string + +
+(Optional) +

Description is a user-facing description of the task that may be +used to populate a UI.

+
+steps
+ + +[]Step + + +
+

Steps are the steps of the build; each step is run sequentially with the +source mounted into /workspace.

+
+volumes
+ + +[]Kubernetes core/v1.Volume + + +
+

Volumes is a collection of volumes that are available to mount into the +steps of the build.

+
+stepTemplate
+ + +Kubernetes core/v1.Container + + +
+

StepTemplate can be used as the basis for all step containers within the +Task, so that the steps inherit settings on the base container.

+
+sidecars
+ + +[]Sidecar + + +
+

Sidecars are run alongside the Task’s step containers. They begin before +the steps start and end after the steps complete.

+
+workspaces
+ + +[]WorkspaceDeclaration + + +
+

Workspaces are the volumes that this Task requires.

+
+results
+ + +[]TaskResult + + +
+

Results are values that this Task can output

+
+

TimeoutFields +

+

+(Appears on:PipelineRunSpec) +

+
+

TimeoutFields allows granular specification of pipeline, task, and finally timeouts

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pipeline
+ + +Kubernetes meta/v1.Duration + + +
+

Pipeline sets the maximum allowed duration for execution of the entire pipeline. The sum of individual timeouts for tasks and finally must not exceed this value.

+
+tasks
+ + +Kubernetes meta/v1.Duration + + +
+

Tasks sets the maximum allowed duration of this pipeline’s tasks

+
+finally
+ + +Kubernetes meta/v1.Duration + + +
+

Finally sets the maximum allowed duration of this pipeline’s finally

+
+

WhenExpression +

+

+(Appears on:PipelineRunRunStatus, PipelineRunTaskRunStatus, SkippedTask) +

+
+

WhenExpression allows a PipelineTask to declare expressions to be evaluated before the Task is run +to determine whether the Task should be executed or skipped

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+input
+ +string + +
+

Input is the string for guard checking which can be a static input or an output from a parent Task

+
+operator
+ +k8s.io/apimachinery/pkg/selection.Operator + +
+

Operator that represents an Input’s relationship to the values

+
+values
+ +[]string + +
+

Values is an array of strings, which is compared against the input, for guard checking +It must be non-empty

+
+

WhenExpressions +([]github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.WhenExpression alias)

+

+(Appears on:PipelineTask) +

+
+

WhenExpressions are used to specify whether a Task should be executed or skipped +All of them need to evaluate to True for a guarded Task to be executed.

+
+

WorkspaceBinding +

+

+(Appears on:PipelineRunSpec, RunSpec, TaskRunSpec, PipelineRunSpec, TaskRunSpec) +

+
+

WorkspaceBinding maps a Task’s declared workspace to a Volume.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of the workspace populated by the volume.

+
+subPath
+ +string + +
+(Optional) +

SubPath is optionally a directory on the volume which should be used +for this binding (i.e. the volume will be mounted at this sub directory).

+
+volumeClaimTemplate
+ + +Kubernetes core/v1.PersistentVolumeClaim + + +
+(Optional) +

VolumeClaimTemplate is a template for a claim that will be created in the same namespace. +The PipelineRun controller is responsible for creating a unique claim for each instance of PipelineRun.

+
+persistentVolumeClaim
+ + +Kubernetes core/v1.PersistentVolumeClaimVolumeSource + + +
+(Optional) +

PersistentVolumeClaimVolumeSource represents a reference to a +PersistentVolumeClaim in the same namespace. Either this OR EmptyDir can be used.

+
+emptyDir
+ + +Kubernetes core/v1.EmptyDirVolumeSource + + +
+(Optional) +

EmptyDir represents a temporary directory that shares a Task’s lifetime. +More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir +Either this OR PersistentVolumeClaim can be used.

+
+configMap
+ + +Kubernetes core/v1.ConfigMapVolumeSource + + +
+(Optional) +

ConfigMap represents a configMap that should populate this workspace.

+
+secret
+ + +Kubernetes core/v1.SecretVolumeSource + + +
+(Optional) +

Secret represents a secret that should populate this workspace.

+
+

WorkspaceDeclaration +

+

+(Appears on:TaskSpec) +

+
+

WorkspaceDeclaration is a declaration of a volume that a Task requires.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name by which you can bind the volume at runtime.

+
+description
+ +string + +
+(Optional) +

Description is an optional human readable description of this volume.

+
+mountPath
+ +string + +
+(Optional) +

MountPath overrides the directory that the volume will be made available at.

+
+readOnly
+ +bool + +
+

ReadOnly dictates whether a mounted volume is writable. By default this +field is false and so mounted volumes are writable.

+
+optional
+ +bool + +
+

Optional marks a Workspace as not being required in TaskRuns. By default +this field is false and so declared workspaces are required.

+
+

WorkspacePipelineTaskBinding +

+

+(Appears on:PipelineTask, PipelineTask) +

+
+

WorkspacePipelineTaskBinding describes how a workspace passed into the pipeline should be +mapped to a task’s declared workspace.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of the workspace as declared by the task

+
+workspace
+ +string + +
+

Workspace is the name of the workspace declared by the pipeline

+
+subPath
+ +string + +
+(Optional) +

SubPath is optionally a directory on the volume which should be used +for this binding (i.e. the volume will be mounted at this sub directory).

+
+

WorkspaceUsage +

+

+(Appears on:Sidecar, Step) +

+
+

WorkspaceUsage is used by a Step or Sidecar to declare that it wants isolated access +to a Workspace defined in a Task.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of the workspace this Step or Sidecar wants access to.

+
+mountPath
+ +string + +
+

MountPath is the path that the workspace should be mounted to inside the Step or Sidecar, +overriding any MountPath specified in the Task’s WorkspaceDeclaration.

+
+
+

+Generated with gen-crd-api-reference-docs +. +

diff --git a/go.mod b/go.mod index eb97d254181..63982f65d81 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/tektoncd/pipeline go 1.16 require ( + github.com/abayer/gen-crd-api-reference-docs v0.999.0 github.com/cloudevents/sdk-go/v2 v2.5.0 github.com/containerd/containerd v1.5.2 github.com/docker/cli v20.10.8+incompatible // indirect diff --git a/go.sum b/go.sum index 3dd58642848..55bae79855f 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,8 @@ github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fT github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/abayer/gen-crd-api-reference-docs v0.999.0 h1:vWWWZtgG+te5YDpim37+zehuFDX0o/Sc2Mh953kTnhU= +github.com/abayer/gen-crd-api-reference-docs v0.999.0/go.mod h1:hCZxQ/b99HVLLuijnYy7xY+z2P53IQ5A2dgGKJblO4Q= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/jsonschema v0.0.0-20180308105923-f2c93856175a/go.mod h1:qpebaTNSsyUn5rPSJMsfqEtDw71TTggXM6stUDI16HA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -1080,8 +1082,10 @@ github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg= github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= @@ -2000,9 +2004,11 @@ k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/csi-translation-lib v0.21.0/go.mod h1:edq+UMpgqEx3roTuGF/03uIuSOsI986jtu65+ytLlkA= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 h1:TT1WdmqqXareKxZ/oNXEUSwKlLiHzPMyB0t8BaFeBYI= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= diff --git a/hack/reference-docs-gen-config.json b/hack/reference-docs-gen-config.json new file mode 100644 index 00000000000..1cf8db3503c --- /dev/null +++ b/hack/reference-docs-gen-config.json @@ -0,0 +1,42 @@ +{ + "hideMemberFields": [ + "TypeMeta" + ], + "hideTypePatterns": [ + "ParseError$", + "List$" + ], + "externalPackages": [ + { + "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$", + "docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration" + }, + { + "typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/", + "docsURLTemplate": "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}" + }, + { + "typeMatchPrefix": "^knative\\.dev/pkg/apis/duck", + "docsURLTemplate": "https://pkg.go.dev/knative.dev/pkg/apis/duck/{{arrIndex .PackageSegments -1}}#{{.TypeIdentifier}}" + }, + { + "typeMatchPrefix": "^knative\\.dev/pkg/apis\\.URL$", + "docsURLTemplate": "https://pkg.go.dev/knative.dev/pkg/apis#URL" + }, + { + "typeMatchPrefix": "^knative\\.dev/networking/pkg/apis/networking", + "docsURLTemplate": "https://pkg.go.dev/knative.dev/networking/pkg/apis/networking#{{.TypeIdentifier}}" + }, + { + "typeMatchPrefix": "^time\\.Duration$", + "docsURLTemplate": "https://golang.org/pkg/time/#Duration" + } + ], + "typeDisplayNamePrefixOverrides": { + "k8s.io/api/": "Kubernetes ", + "k8s.io/apimachinery/pkg/apis/": "Kubernetes " + }, + "markdownDisabled": false, + "gitCommitDisabled": true +} + diff --git a/hack/reference-docs-template/members.tpl b/hack/reference-docs-template/members.tpl new file mode 100644 index 00000000000..a529c671647 --- /dev/null +++ b/hack/reference-docs-template/members.tpl @@ -0,0 +1,48 @@ +{{ define "members" }} + +{{ range .Members }} +{{ if not (hiddenMember .)}} + + + {{ fieldName . }}
+ + {{ if linkForType .Type }} + + {{ typeDisplayName .Type }} + + {{ else }} + {{ typeDisplayName .Type }} + {{ end }} + + + + {{ if fieldEmbedded . }} +

+ (Members of {{ fieldName . }} are embedded into this type.) +

+ {{ end}} + + {{ if isOptionalMember .}} + (Optional) + {{ end }} + + {{ safe (renderComments .CommentLines) }} + + {{ if and (eq (.Type.Name.Name) "ObjectMeta") }} + Refer to the Kubernetes API documentation for the fields of the + metadata field. + {{ end }} + + {{ if or (eq (fieldName .) "spec") }} +
+
+ + {{ template "members" .Type }} +
+ {{ end }} + + +{{ end }} +{{ end }} + +{{ end }} diff --git a/hack/reference-docs-template/pkg.tpl b/hack/reference-docs-template/pkg.tpl new file mode 100644 index 00000000000..842ec93d493 --- /dev/null +++ b/hack/reference-docs-template/pkg.tpl @@ -0,0 +1,49 @@ +{{ define "packages" }} + +{{ with .packages}} +

Packages:

+ +{{ end}} + +{{ range .packages }} +

+ {{- packageDisplayName . -}} +

+ + {{ with (index .GoPackages 0 )}} + {{ with .DocComments }} +
+ {{ safe (renderComments .) }} +
+ {{ end }} + {{ end }} + + Resource Types: + + + {{ range (visibleTypes (sortedTypes .Types))}} + {{ template "type" . }} + {{ end }} +
+{{ end }} + +

+ Generated with gen-crd-api-reference-docs + {{ with .gitCommit }} on git commit {{ . }}{{end}}. +

+ +{{ end }} diff --git a/hack/reference-docs-template/placeholder.go b/hack/reference-docs-template/placeholder.go new file mode 100644 index 00000000000..cc8f1453aca --- /dev/null +++ b/hack/reference-docs-template/placeholder.go @@ -0,0 +1,2 @@ +// Placeholder file to make Go vendor this directory properly. +package template diff --git a/hack/reference-docs-template/type.tpl b/hack/reference-docs-template/type.tpl new file mode 100644 index 00000000000..8f0d66b8bab --- /dev/null +++ b/hack/reference-docs-template/type.tpl @@ -0,0 +1,81 @@ +{{ define "type" }} + +

+ {{- .Name.Name }} + {{ if eq .Kind "Alias" }}({{.Underlying}} alias){{ end -}} +

+{{ with (typeReferences .) }} +

+ (Appears on: + {{- $prev := "" -}} + {{- range . -}} + {{- if $prev -}}, {{ end -}} + {{- $prev = . -}} + {{ typeDisplayName . }} + {{- end -}} + ) +

+{{ end }} + +
+ {{ safe (renderComments .CommentLines) }} +
+ +{{ with (constantsOfType .) }} + + + + + + + + + {{- range . -}} + + {{- /* + renderComments implicitly creates a

element, so we + add one to the display name as well to make the contents + of the two cells align evenly. + */ -}} +

+ + + {{- end -}} + +
ValueDescription

{{ typeDisplayName . }}

{{ safe (renderComments .CommentLines) }}
+{{ end }} + +{{ if .Members }} + + + + + + + + + {{ if isExportedType . }} + + + + + + + + + {{ end }} + {{ template "members" .}} + +
FieldDescription
+ apiVersion
+ string
+ + {{apiGroup .}} + +
+ kind
+ string +
{{.Name.Name}}
+{{ end }} + +{{ end }} diff --git a/hack/tools.go b/hack/tools.go index c0b8564c060..478aa8b0c68 100644 --- a/hack/tools.go +++ b/hack/tools.go @@ -15,4 +15,7 @@ import ( _ "k8s.io/kube-openapi/cmd/openapi-gen" _ "knative.dev/pkg/codegen/cmd/injection-gen" + + // TODO(abayer): Switch to github.com/ahmetb/gen-crd-api-reference-docs once https://github.com/ahmetb/gen-crd-api-reference-docs/pull/43 is merged/released + _ "github.com/abayer/gen-crd-api-reference-docs" ) diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index ce40324fc08..2e0b92a719f 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -86,3 +86,6 @@ ${REPO_ROOT_DIR}/hack/update-deps.sh # Make sure the OpenAPI specification and Swagger file are up-to-date ${REPO_ROOT_DIR}/hack/update-openapigen.sh + +# Make sure the generated API reference docs are up-to-date +${REPO_ROOT_DIR}/hack/update-reference-docs.sh diff --git a/hack/update-reference-docs.sh b/hack/update-reference-docs.sh new file mode 100755 index 00000000000..e7c6305f70a --- /dev/null +++ b/hack/update-reference-docs.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Tekton 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. + +set -o errexit +set -o nounset + +echo "Generating API reference docs ..." +# TODO(abayer): Switch to github.com/ahmetb/gen-crd-api-reference-docs when https://github.com/ahmetb/gen-crd-api-reference-docs/pull/43 is merged/vendor is updated +go run github.com/abayer/gen-crd-api-reference-docs \ + -config "./hack/reference-docs-gen-config.json" \ + -api-dir "github.com/tektoncd/pipeline/pkg/apis" \ + -template-dir "./hack/reference-docs-template" \ + -out-file "./docs/pipeline-api.md" diff --git a/pkg/apis/doc.go b/pkg/apis/doc.go new file mode 100644 index 00000000000..45a32127ac8 --- /dev/null +++ b/pkg/apis/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2019 The Tekton 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 apis contains API Schema definitions for the various API groups +package apis diff --git a/pkg/apis/pipeline/pod/doc.go b/pkg/apis/pipeline/pod/doc.go index 2187544efad..9e7c8c3352c 100644 --- a/pkg/apis/pipeline/pod/doc.go +++ b/pkg/apis/pipeline/pod/doc.go @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:openapi-gen=true - // Package pod contains non-versioned pod configuration +// +k8s:openapi-gen=true +// +gencrdrefdocs:unversionedTypes +// +groupName=tekton.dev package pod diff --git a/pkg/apis/run/v1alpha1/doc.go b/pkg/apis/run/v1alpha1/doc.go new file mode 100644 index 00000000000..dde2f9120d9 --- /dev/null +++ b/pkg/apis/run/v1alpha1/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2019 The Tekton 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 v1alpha1 contains API Schema definitions for the run v1alpha1 API group +// +groupName=tekton.dev +package v1alpha1 diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/.gitignore b/vendor/github.com/abayer/gen-crd-api-reference-docs/.gitignore new file mode 100644 index 00000000000..a4d184e80a8 --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +refdocs +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# goreleaser output +dist diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/.goreleaser.yml b/vendor/github.com/abayer/gen-crd-api-reference-docs/.goreleaser.yml new file mode 100644 index 00000000000..f1caccad8b0 --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/.goreleaser.yml @@ -0,0 +1,24 @@ +builds: + - env: + - CGO_ENABLED=0 + # travis ci currently sets GOPATH even with go1.11. + # force-setting GO111MODULE=on to use vgo + - GO111MODULE=on + goos: + - linux + - darwin + goarch: + - amd64 +archive: + name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" + files: + - LICENSE + - template/** + - example-config.json +checksum: + name_template: "checksums.txt" +changelog: + skip: true +release: + # releases are uploaded to github by .travis.yml + disable: true diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/.travis.yml b/vendor/github.com/abayer/gen-crd-api-reference-docs/.travis.yml new file mode 100644 index 00000000000..d6845bab15e --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/.travis.yml @@ -0,0 +1,32 @@ +language: go +go: + - 1.11.x +install: + - echo noop +before_script: + # travis ci currently sets GOPATH even with go1.11. + # force-setting GO111MODULE=on to use vgo + - env GO111MODULE=on go mod download +script: + # travis ci currently sets GOPATH even with go1.11. + # force-setting GO111MODULE=on to use vgo + - env GO111MODULE=on go build -v -o /dev/null +deploy: + # use goreleaser to prepare dist/ + - provider: script + skip_cleanup: true + on: + tags: true + script: curl -sL https://git.io/goreleaser | bash + # use github release feature to upload dist/ + - provider: releases + skip_cleanup: true + on: + tags: true + file_glob: true + file: + - dist/*.tar.gz + - dist/*.zip + - dist/checksums.txt + api_key: + secure: r1GMgbVDnZTUcny/PbIATW9dXGOTpm2U9iEGaWvpprMO2AGo7ju7SWEJWtjcap3pc0YasyR2/eon9LC0scWY0Xlpeb+g0pRCQ39FABk1Vo3DpmIPRUCFFkaescWmrWDj3ImzjJgZjCewwK6Fo8s8ngnqIlZnE1Hq6ls2xDp6jNVf+Pn7LyqxkK4axFFSPQM9zFX3N1PVUH5RT03bIJfojJZguqnhNfyTvKvHJidoeWU/Ie+fXc4AdPHyP85xrmGHYl68O0HziU6JCLXira8r1FjUgVeYFYC5nnNuylszO6JWqWh1nXYDxs5FGPnZd9N8bEi/2ahiqms8eV7S+/DGzhSoEdHikcBxTgJpZP2VOmvRSITyv3RleJzCeMULTGFQodoxRgA/Q8qZySvInNjstiBjV2Pyucrnn990XQbN8rIV4RmNggJvbAwJNCGjCwS2eB42EKNCODTuzHPbIV0ap4EjvfBBo0cZ2J9M2Q6VzdpNErdntpM1hZl9yymv3MNN4hOiLQKkofoo/QI3cffB8Y0PBPAL8Cs9Mx1bbx+Dr8iitTHBUAt4a5DHFen4MS8znrZ+Cr4kLDD9QPJ8G0oh4tDKq8CJ73Gt+xqkLZEuka0W1awz9essqE7MH20kRJbKa5woTIs0v9njHMpbeqd7KrNV+1e5F5aPRQyiCzaom7c= diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/LICENSE b/vendor/github.com/abayer/gen-crd-api-reference-docs/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/LICENSE @@ -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. diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/README.md b/vendor/github.com/abayer/gen-crd-api-reference-docs/README.md new file mode 100644 index 00000000000..d481b8f57d2 --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/README.md @@ -0,0 +1,80 @@ +# Kubernetes Custom Resource API Reference Docs generator + +If you have a project that is Custom Resource Definitions and wanted to generate +API Reference Docs [like this][ar] this tool is for you. + +[ar]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/ + +## Current Users + +- [**Knative** API reference docs](https://knative.dev/docs/reference/api/serving-api/) +- [**Kubeflow** API reference docs](https://www.kubeflow.org/docs/reference/overview/) +- [**Agones** API reference docs](https://agones.dev/site/docs/reference/agones_crd_api_reference/) +- [**cert-manager** API reference docs](https://cert-manager.io/docs/reference/api-docs/) +- [**Gardener** API reference docs](https://gardener.cloud/api-reference/) +- [**New Relic Alert Manager** API reference docs](https://github.com/fpetkovski/newrelic-alert-manager/tree/master/docs) +- _[[ADD YOUR PROJECT HERE]]_ + +## Why + +Normally you would want to use the same [docs generator][dg] as [Kubernetes API +reference][ar], but here's why I wrote a different parser/generator: + +1. Today, Kubernetes API [does not][pr] provide OpenAPI specs for CRDs (e.g. + Knative), therefore the [gen-apidocs][ga] + generator used by Kubernetes won't work. + +2. Even when Kubernetes API starts providing OpenAPI specs for CRDs, your CRD + must have a validation schema (e.g. Knative API doesn't!) + +3. Kubernetes [gen-apidocs][ga] parser relies on running a `kube-apiserver` and + calling `/apis` endpoint to get OpenAPI specs to generate docs. **This tool + doesn't need that!** + +[dg]: https://github.com/kubernetes-incubator/reference-docs/ +[ga]: https://github.com/kubernetes-incubator/reference-docs/tree/master/gen-apidocs/generators +[pr]: https://github.com/kubernetes/kubernetes/pull/71192 + +## How + +This is a custom API reference docs generator that uses the +[k8s.io/gengo](https://godoc.org/k8s.io/gengo) project to parse types and +generate API documentation from it. + +Capabilities of this tool include: + +- Doesn't depend on OpenAPI specs, or kube-apiserver, or a running cluster. +- Relies only on the Go source code (pkg/apis/**/*.go) to parse API types. +- Can link to other sites for external APIs. For example, if your types have a + reference to Kubernetes core/v1.PodSpec, you can link to it. +- [Configurable](./example-config.json) settings to hide certain fields or types + entirely from the generated output. +- Either output to a file or start a live http-server (for rapid iteration). +- Supports markdown rendering from godoc type, package and field comments. + +## Try it out + +1. Clone this repository. + +2. Make sure you have go1.11+ instaled. Then run `go build`, you should get a + `gen-crd-api-reference-docs` binary executable in the current directory. + +3. Clone a Knative repository, set GOPATH correctly, + and call the compiled binary within that directory. + + ```sh + # go into a repository root with GOPATH set. (I use my own script + # goclone(1) to have a separate GOPATH for each repo I clone.) + $ goclone knative/build + + $ /path/to/gen-crd-api-reference-docs \ + -config "/path/to/example-config.json" \ + -api-dir "github.com/knative/build/pkg/apis/build/v1alpha1" \ + -out-file docs.html + ``` + +4. Visit `docs.html` to view the results. + +----- + +This is not an official Google project. See [LICENSE](./LICENSE). diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/example-config.json b/vendor/github.com/abayer/gen-crd-api-reference-docs/example-config.json new file mode 100644 index 00000000000..298f00b64cc --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/example-config.json @@ -0,0 +1,28 @@ +{ + "hideMemberFields": [ + "TypeMeta" + ], + "hideTypePatterns": [ + "ParseError$", + "List$" + ], + "externalPackages": [ + { + "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$", + "docsURLTemplate": "https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration" + }, + { + "typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/", + "docsURLTemplate": "https://v1-18.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}" + }, + { + "typeMatchPrefix": "^github\\.com/knative/pkg/apis/duck/", + "docsURLTemplate": "https://pkg.go.dev/github.com/knative/pkg/apis/duck/{{arrIndex .PackageSegments -1}}#{{.TypeIdentifier}}" + } + ], + "typeDisplayNamePrefixOverrides": { + "k8s.io/api/": "Kubernetes ", + "k8s.io/apimachinery/pkg/apis/": "Kubernetes " + }, + "markdownDisabled": false +} diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/go.mod b/vendor/github.com/abayer/gen-crd-api-reference-docs/go.mod new file mode 100644 index 00000000000..28a5e3b0dde --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/go.mod @@ -0,0 +1,12 @@ +module github.com/abayer/gen-crd-api-reference-docs + +go 1.15 + +require ( + github.com/pkg/errors v0.8.1 + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/russross/blackfriday/v2 v2.0.1 + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9 + k8s.io/klog v0.2.0 +) diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/go.sum b/vendor/github.com/abayer/gen-crd-api-reference-docs/go.sum new file mode 100644 index 00000000000..fe95f42d82b --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/go.sum @@ -0,0 +1,50 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 h1:BMFHd4OFnFtWX46Xj4DN6vvT1btiBxyq+s0orYBqcQY= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9 h1:1bLA4Agvs1DILmc+q2Bbcqjx6jOHO7YEFA+G+0aTZoc= +k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/vendor/github.com/abayer/gen-crd-api-reference-docs/main.go b/vendor/github.com/abayer/gen-crd-api-reference-docs/main.go new file mode 100644 index 00000000000..8832321b849 --- /dev/null +++ b/vendor/github.com/abayer/gen-crd-api-reference-docs/main.go @@ -0,0 +1,722 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "html/template" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path/filepath" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + texttemplate "text/template" + "time" + "unicode" + + "github.com/pkg/errors" + "github.com/russross/blackfriday/v2" + "k8s.io/gengo/parser" + "k8s.io/gengo/types" + "k8s.io/klog" +) + +var ( + flConfig = flag.String("config", "", "path to config file") + flAPIDir = flag.String("api-dir", "", "api directory (or import path), point this to pkg/apis") + flTemplateDir = flag.String("template-dir", "template", "path to template/ dir") + + flHTTPAddr = flag.String("http-addr", "", "start an HTTP server on specified addr to view the result (e.g. :8080)") + flOutFile = flag.String("out-file", "", "path to output file to save the result") +) + +const ( + docCommentForceIncludes = "+gencrdrefdocs:force" + docCommentIncludeUnversionedTypes = "+gencrdrefdocs:unversionedTypes" +) + +type generatorConfig struct { + // HiddenMemberFields hides fields with specified names on all types. + HiddenMemberFields []string `json:"hideMemberFields"` + + // HideTypePatterns hides types matching the specified patterns from the + // output. + HideTypePatterns []string `json:"hideTypePatterns"` + + // ExternalPackages lists recognized external package references and how to + // link to them. + ExternalPackages []externalPackage `json:"externalPackages"` + + // TypeDisplayNamePrefixOverrides is a mapping of how to override displayed + // name for types with certain prefixes with what value. + TypeDisplayNamePrefixOverrides map[string]string `json:"typeDisplayNamePrefixOverrides"` + + // MarkdownDisabled controls markdown rendering for comment lines. + MarkdownDisabled bool `json:"markdownDisabled"` + + // GitCommitDisabled causes the git commit information to be excluded from the output. + GitCommitDisabled bool `json:"gitCommitDisabled"` +} + +type externalPackage struct { + TypeMatchPrefix string `json:"typeMatchPrefix"` + DocsURLTemplate string `json:"docsURLTemplate"` +} + +type apiPackage struct { + apiGroup string + apiVersion string + GoPackages []*types.Package + Types []*types.Type // because multiple 'types.Package's can add types to an apiVersion + Constants []*types.Type +} + +func (v *apiPackage) identifier() string { return fmt.Sprintf("%s/%s", v.apiGroup, v.apiVersion) } + +func init() { + klog.InitFlags(nil) + flag.Set("alsologtostderr", "true") // for klog + flag.Parse() + + if *flConfig == "" { + panic("-config not specified") + } + if *flAPIDir == "" { + panic("-api-dir not specified") + } + if *flHTTPAddr == "" && *flOutFile == "" { + panic("-out-file or -http-addr must be specified") + } + if *flHTTPAddr != "" && *flOutFile != "" { + panic("only -out-file or -http-addr can be specified") + } + if err := resolveTemplateDir(*flTemplateDir); err != nil { + panic(err) + } + +} + +func resolveTemplateDir(dir string) error { + path, err := filepath.Abs(dir) + if err != nil { + return err + } + if fi, err := os.Stat(path); err != nil { + return errors.Wrapf(err, "cannot read the %s directory", path) + } else if !fi.IsDir() { + return errors.Errorf("%s path is not a directory", path) + } + return nil +} + +func main() { + defer klog.Flush() + + f, err := os.Open(*flConfig) + if err != nil { + klog.Fatalf("failed to open config file: %+v", err) + } + d := json.NewDecoder(f) + d.DisallowUnknownFields() + var config generatorConfig + if err := d.Decode(&config); err != nil { + klog.Fatalf("failed to parse config file: %+v", err) + } + + klog.Infof("parsing go packages in directory %s", *flAPIDir) + pkgs, unversionedPkgs, err := parseAPIPackages(*flAPIDir) + if err != nil { + klog.Fatal(err) + } + if len(pkgs) == 0 { + klog.Fatalf("no API packages found in %s", *flAPIDir) + } + + var unversionedPkgNames []string + for _, uvp := range unversionedPkgs { + unversionedPkgNames = append(unversionedPkgNames, uvp.Path) + } + apiPackages, err := combineAPIPackages(pkgs, unversionedPkgNames) + if err != nil { + klog.Fatal(err) + } + + unversionedAPIPackages, err := combineAPIPackages(unversionedPkgs, unversionedPkgNames) + if err != nil { + klog.Fatal(err) + } + + mkOutput := func() (string, error) { + var b bytes.Buffer + err := render(&b, apiPackages, unversionedAPIPackages, config) + if err != nil { + return "", errors.Wrap(err, "failed to render the result") + } + + // remove trailing whitespace from each html line for markdown renderers + s := regexp.MustCompile(`(?m)^\s+`).ReplaceAllString(b.String(), "") + return s, nil + } + + if *flOutFile != "" { + dir := filepath.Dir(*flOutFile) + if err := os.MkdirAll(dir, 0755); err != nil { + klog.Fatalf("failed to create dir %s: %v", dir, err) + } + s, err := mkOutput() + if err != nil { + klog.Fatalf("failed: %+v", err) + } + if err := ioutil.WriteFile(*flOutFile, []byte(s), 0644); err != nil { + klog.Fatalf("failed to write to out file: %v", err) + } + klog.Infof("written to %s", *flOutFile) + } + + if *flHTTPAddr != "" { + h := func(w http.ResponseWriter, r *http.Request) { + now := time.Now() + defer func() { klog.Infof("request took %v", time.Since(now)) }() + s, err := mkOutput() + if err != nil { + fmt.Fprintf(w, "error: %+v", err) + klog.Warningf("failed: %+v", err) + } + if _, err := fmt.Fprint(w, s); err != nil { + klog.Warningf("response write error: %v", err) + } + } + http.HandleFunc("/", h) + klog.Infof("server listening at %s", *flHTTPAddr) + klog.Fatal(http.ListenAndServe(*flHTTPAddr, nil)) + } +} + +// groupName extracts the "//+groupName" meta-comment from the specified +// package's comments, or returns empty string if it cannot be found. +func groupName(pkg *types.Package) string { + m := types.ExtractCommentTags("+", pkg.Comments) + v := m["groupName"] + if len(v) == 1 { + return v[0] + } + return "" +} + +func parseAPIPackages(dir string) ([]*types.Package, []*types.Package, error) { + b := parser.New() + // the following will silently fail (turn on -v=4 to see logs) + if err := b.AddDirRecursive(*flAPIDir); err != nil { + return nil, nil, err + } + scan, err := b.FindTypes() + if err != nil { + return nil, nil, errors.Wrap(err, "failed to parse pkgs and types") + } + var unversionedPkgs []*types.Package + var pkgNames []string + for p := range scan { + pkg := scan[p] + klog.V(3).Infof("trying package=%v groupName=%s", p, groupName(pkg)) + + // Do not pick up packages that are in vendor/ as API packages. (This + // happened in knative/eventing-sources/vendor/..., where a package + // matched the pattern, but it didn't have a compatible import path). + if isVendorPackage(pkg) { + klog.V(3).Infof("package=%v coming from vendor/, ignoring.", p) + continue + } + + if len(pkg.Types) > 0 && containsString(pkg.DocComments, docCommentIncludeUnversionedTypes) { + klog.Infof("including package=%s as an additional unversioned include", p) + unversionedPkgs = append(unversionedPkgs, pkg) + } else if groupName(pkg) != "" && len(pkg.Types) > 0 || containsString(pkg.DocComments, docCommentForceIncludes) { + klog.V(3).Infof("package=%v has groupName and has types", p) + pkgNames = append(pkgNames, p) + } + } + sort.Strings(pkgNames) + var pkgs []*types.Package + for _, p := range pkgNames { + klog.Infof("using package=%s", p) + pkgs = append(pkgs, scan[p]) + } + return pkgs, unversionedPkgs, nil +} + +func containsString(sl []string, str string) bool { + for _, s := range sl { + if str == s { + return true + } + } + return false +} + +// combineAPIPackages groups the Go packages by the they +// offer, and combines the types in them. +func combineAPIPackages(pkgs []*types.Package, unversionedPkgNames []string) ([]*apiPackage, error) { + pkgMap := make(map[string]*apiPackage) + var pkgIds []string + + flattenTypes := func(typeMap map[string]*types.Type) []*types.Type { + typeList := make([]*types.Type, 0, len(typeMap)) + + for _, t := range typeMap { + typeList = append(typeList, t) + } + + return typeList + } + + for _, pkg := range pkgs { + apiGroup, apiVersion, err := apiVersionForPackage(pkg, unversionedPkgNames) + if err != nil { + return nil, errors.Wrapf(err, "could not get apiVersion for package %s", pkg.Path) + } + + typeList := make([]*types.Type, 0, len(pkg.Types)) + for _, t := range pkg.Types { + typeList = append(typeList, t) + } + + id := fmt.Sprintf("%s/%s", apiGroup, apiVersion) + v, ok := pkgMap[id] + if !ok { + pkgMap[id] = &apiPackage{ + apiGroup: apiGroup, + apiVersion: apiVersion, + Types: flattenTypes(pkg.Types), + Constants: flattenTypes(pkg.Constants), + GoPackages: []*types.Package{pkg}, + } + pkgIds = append(pkgIds, id) + } else { + v.Types = append(v.Types, flattenTypes(pkg.Types)...) + v.Constants = append(v.Types, flattenTypes(pkg.Constants)...) + v.GoPackages = append(v.GoPackages, pkg) + } + } + + sort.Sort(sort.StringSlice(pkgIds)) + + out := make([]*apiPackage, 0, len(pkgMap)) + for _, id := range pkgIds { + out = append(out, pkgMap[id]) + } + return out, nil +} + +// isVendorPackage determines if package is coming from vendor/ dir. +func isVendorPackage(pkg *types.Package) bool { + vendorPattern := string(os.PathSeparator) + "vendor" + string(os.PathSeparator) + return strings.Contains(pkg.SourcePath, vendorPattern) +} + +func findTypeReferences(pkgs []*apiPackage) map[*types.Type][]*types.Type { + m := make(map[*types.Type][]*types.Type) + for _, pkg := range pkgs { + for _, typ := range pkg.Types { + for _, member := range typ.Members { + t := member.Type + t = tryDereference(t) + m[t] = append(m[t], typ) + } + } + } + return m +} + +func isExportedType(t *types.Type) bool { + // TODO(ahmetb) use types.ExtractSingleBoolCommentTag() to parse +genclient + // https://godoc.org/k8s.io/gengo/types#ExtractCommentTags + return strings.Contains(strings.Join(t.SecondClosestCommentLines, "\n"), "+genclient") +} + +func fieldName(m types.Member) string { + v := reflect.StructTag(m.Tags).Get("json") + v = strings.TrimSuffix(v, ",omitempty") + v = strings.TrimSuffix(v, ",inline") + if v != "" { + return v + } + return m.Name +} + +func fieldEmbedded(m types.Member) bool { + return strings.Contains(reflect.StructTag(m.Tags).Get("json"), ",inline") +} + +func isLocalType(t *types.Type, typePkgMap map[*types.Type]*apiPackage) bool { + t = tryDereference(t) + _, ok := typePkgMap[t] + return ok +} + +func renderComments(s []string, markdown bool) string { + s = filterCommentTags(s) + doc := strings.Join(s, "\n") + + if markdown { + // TODO(ahmetb): when a comment includes stuff like "http://" + // we treat this as a HTML tag with markdown renderer below. solve this. + return string(blackfriday.Run([]byte(doc))) + } + return nl2br(doc) +} + +func safe(s string) template.HTML { return template.HTML(s) } + +func nl2br(s string) string { + return strings.Replace(s, "\n\n", string(template.HTML("

")), -1) +} + +func hiddenMember(m types.Member, c generatorConfig) bool { + for _, v := range c.HiddenMemberFields { + if m.Name == v { + return true + } + } + return false +} + +func typeIdentifier(t *types.Type) string { + t = tryDereference(t) + return t.Name.String() // {PackagePath.Name} +} + +// apiGroupForType looks up apiGroup for the given type +func apiGroupForType(t *types.Type, typePkgMap map[*types.Type]*apiPackage) string { + t = tryDereference(t) + + v := typePkgMap[t] + if v == nil { + klog.Warningf("WARNING: cannot read apiVersion for %s from type=>pkg map", t.Name.String()) + return "" + } + + return v.identifier() +} + +// anchorIDForLocalType returns the #anchor string for the local type +func anchorIDForLocalType(t *types.Type, typePkgMap map[*types.Type]*apiPackage) string { + return fmt.Sprintf("%s.%s", apiGroupForType(t, typePkgMap), t.Name.Name) +} + +// linkForType returns an anchor to the type if it can be generated. returns +// empty string if it is not a local type or unrecognized external type. +func linkForType(t *types.Type, c generatorConfig, typePkgMap map[*types.Type]*apiPackage) (string, error) { + t = tryDereference(t) // dereference kind=Pointer + + if isLocalType(t, typePkgMap) { + return "#" + anchorIDForLocalType(t, typePkgMap), nil + } + + var arrIndex = func(a []string, i int) string { + return a[(len(a)+i)%len(a)] + } + + // types like k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta, + // k8s.io/api/core/v1.Container, k8s.io/api/autoscaling/v1.CrossVersionObjectReference, + // github.com/knative/build/pkg/apis/build/v1alpha1.BuildSpec + if t.Kind == types.Struct || t.Kind == types.Pointer || t.Kind == types.Interface || t.Kind == types.Alias { + id := typeIdentifier(t) // gives {{ImportPath.Identifier}} for type + segments := strings.Split(t.Name.Package, "/") // to parse [meta, v1] from "k8s.io/apimachinery/pkg/apis/meta/v1" + + for _, v := range c.ExternalPackages { + r, err := regexp.Compile(v.TypeMatchPrefix) + if err != nil { + return "", errors.Wrapf(err, "pattern %q failed to compile", v.TypeMatchPrefix) + } + if r.MatchString(id) { + tpl, err := texttemplate.New("").Funcs(map[string]interface{}{ + "lower": strings.ToLower, + "arrIndex": arrIndex, + }).Parse(v.DocsURLTemplate) + if err != nil { + return "", errors.Wrap(err, "docs URL template failed to parse") + } + + var b bytes.Buffer + if err := tpl. + Execute(&b, map[string]interface{}{ + "TypeIdentifier": t.Name.Name, + "PackagePath": t.Name.Package, + "PackageSegments": segments, + }); err != nil { + return "", errors.Wrap(err, "docs url template execution error") + } + return b.String(), nil + } + } + klog.Warningf("not found external link source for type %v", t.Name) + } + return "", nil +} + +// tryDereference returns the underlying type when t is a pointer, map, or slice. +func tryDereference(t *types.Type) *types.Type { + for t.Elem != nil { + t = t.Elem + } + return t +} + +// finalUnderlyingTypeOf walks the type hierarchy for t and returns +// its base type (i.e. the type that has no further underlying type). +func finalUnderlyingTypeOf(t *types.Type) *types.Type { + for { + if t.Underlying == nil { + return t + } + + t = t.Underlying + } +} + +func typeDisplayName(t *types.Type, c generatorConfig, typePkgMap map[*types.Type]*apiPackage) string { + s := typeIdentifier(t) + + if isLocalType(t, typePkgMap) { + s = tryDereference(t).Name.Name + } + + if t.Kind == types.Pointer { + s = strings.TrimLeft(s, "*") + } + + switch t.Kind { + case types.Struct, + types.Interface, + types.Alias, + types.Pointer, + types.Slice, + types.Builtin: + // noop + case types.Map: + // return original name + return t.Name.Name + case types.DeclarationOf: + // For constants, we want to display the value + // rather than the name of the constant, since the + // value is what users will need to write into YAML + // specs. + if t.ConstValue != nil { + u := finalUnderlyingTypeOf(t) + // Quote string constants to make it clear to the documentation reader. + if u.Kind == types.Builtin && u.Name.Name == "string" { + return strconv.Quote(*t.ConstValue) + } + + return *t.ConstValue + } + klog.Fatalf("type %s is a non-const declaration, which is unhandled", t.Name) + default: + klog.Fatalf("type %s has kind=%v which is unhandled", t.Name, t.Kind) + } + + // substitute prefix, if registered + for prefix, replacement := range c.TypeDisplayNamePrefixOverrides { + if strings.HasPrefix(s, prefix) { + s = strings.Replace(s, prefix, replacement, 1) + } + } + + if t.Kind == types.Slice { + s = "[]" + s + } + + return s +} + +func hideType(t *types.Type, c generatorConfig) bool { + for _, pattern := range c.HideTypePatterns { + if regexp.MustCompile(pattern).MatchString(t.Name.String()) { + return true + } + } + if !isExportedType(t) && unicode.IsLower(rune(t.Name.Name[0])) { + // types that start with lowercase + return true + } + return false +} + +func typeReferences(t *types.Type, c generatorConfig, references map[*types.Type][]*types.Type) []*types.Type { + var out []*types.Type + m := make(map[*types.Type]struct{}) + for _, ref := range references[t] { + if !hideType(ref, c) { + m[ref] = struct{}{} + } + } + for k := range m { + out = append(out, k) + } + sortTypes(out) + return out +} + +func sortTypes(typs []*types.Type) []*types.Type { + sort.Slice(typs, func(i, j int) bool { + t1, t2 := typs[i], typs[j] + if isExportedType(t1) && !isExportedType(t2) { + return true + } else if !isExportedType(t1) && isExportedType(t2) { + return false + } + return t1.Name.String() < t2.Name.String() + }) + return typs +} + +func visibleTypes(in []*types.Type, c generatorConfig) []*types.Type { + var out []*types.Type + for _, t := range in { + if !hideType(t, c) { + out = append(out, t) + } + } + return out +} + +func packageDisplayName(pkg *types.Package, apiVersions map[string]string) string { + apiGroupVersion, ok := apiVersions[pkg.Path] + if ok { + return apiGroupVersion + } + return pkg.Path // go import path +} + +func filterCommentTags(comments []string) []string { + var out []string + for _, v := range comments { + if !strings.HasPrefix(strings.TrimSpace(v), "+") { + out = append(out, v) + } + } + return out +} + +func isOptionalMember(m types.Member) bool { + tags := types.ExtractCommentTags("+", m.CommentLines) + _, ok := tags["optional"] + return ok +} + +func apiVersionForPackage(pkg *types.Package, unversionedPkgNames []string) (string, string, error) { + group := groupName(pkg) + for _, upn := range unversionedPkgNames { + if upn == pkg.Path { + return group, "unversioned", nil + } + } + version := pkg.Name // assumes basename (i.e. "v1" in "core/v1") is apiVersion + r := `^v\d+((alpha|beta)\d+)?$` + if !regexp.MustCompile(r).MatchString(version) { + return "", "", errors.Errorf("cannot infer kubernetes apiVersion of go package %s (basename %q doesn't match expected pattern %s that's used to determine apiVersion)", pkg.Path, version, r) + } + return group, version, nil +} + +// extractTypeToPackageMap creates a *types.Type map to apiPackage +func extractTypeToPackageMap(pkgs []*apiPackage) map[*types.Type]*apiPackage { + out := make(map[*types.Type]*apiPackage) + for _, ap := range pkgs { + for _, t := range ap.Types { + out[t] = ap + } + for _, t := range ap.Constants { + out[t] = ap + } + } + return out +} + +// packageMapToList flattens the map. +func packageMapToList(pkgs map[string]*apiPackage) []*apiPackage { + // TODO(ahmetb): we should probably not deal with maps, this type can be + // a list everywhere. + out := make([]*apiPackage, 0, len(pkgs)) + for _, v := range pkgs { + out = append(out, v) + } + return out +} + +// constantsOfType finds all the constants in pkg that have the +// same underlying type as t. This is intended for use by enum +// type validation, where users need to specify one of a specific +// set of constant values for a field. +func constantsOfType(t *types.Type, pkg *apiPackage) []*types.Type { + constants := []*types.Type{} + + for _, c := range pkg.Constants { + if c.Underlying == t { + constants = append(constants, c) + } + } + + return sortTypes(constants) +} + +func render(w io.Writer, pkgs []*apiPackage, unversionedPkgs []*apiPackage, config generatorConfig) error { + references := findTypeReferences(append(pkgs, unversionedPkgs...)) + typePkgMap := extractTypeToPackageMap(append(pkgs, unversionedPkgs...)) + + t, err := template.New("").Funcs(map[string]interface{}{ + "isExportedType": isExportedType, + "fieldName": fieldName, + "fieldEmbedded": fieldEmbedded, + "typeIdentifier": func(t *types.Type) string { return typeIdentifier(t) }, + "typeDisplayName": func(t *types.Type) string { return typeDisplayName(t, config, typePkgMap) }, + "visibleTypes": func(t []*types.Type) []*types.Type { return visibleTypes(t, config) }, + "renderComments": func(s []string) string { return renderComments(s, !config.MarkdownDisabled) }, + "packageDisplayName": func(p *apiPackage) string { return p.identifier() }, + "apiGroup": func(t *types.Type) string { return apiGroupForType(t, typePkgMap) }, + "packageAnchorID": func(p *apiPackage) string { + // TODO(ahmetb): currently this is the same as packageDisplayName + // func, and it's fine since it retuns valid DOM id strings like + // 'serving.knative.dev/v1alpha1' which is valid per HTML5, except + // spaces, so just trim those. + return strings.Replace(p.identifier(), " ", "", -1) + }, + "linkForType": func(t *types.Type) string { + v, err := linkForType(t, config, typePkgMap) + if err != nil { + klog.Fatal(errors.Wrapf(err, "error getting link for type=%s", t.Name)) + return "" + } + return v + }, + "anchorIDForType": func(t *types.Type) string { return anchorIDForLocalType(t, typePkgMap) }, + "safe": safe, + "sortedTypes": sortTypes, + "typeReferences": func(t *types.Type) []*types.Type { return typeReferences(t, config, references) }, + "hiddenMember": func(m types.Member) bool { return hiddenMember(m, config) }, + "isLocalType": isLocalType, + "isOptionalMember": isOptionalMember, + "constantsOfType": func(t *types.Type) []*types.Type { return constantsOfType(t, typePkgMap[t]) }, + }).ParseGlob(filepath.Join(*flTemplateDir, "*.tpl")) + if err != nil { + return errors.Wrap(err, "parse error") + } + + var gitCommit []byte + if !config.GitCommitDisabled { + gitCommit, _ = exec.Command("git", "rev-parse", "--short", "HEAD").Output() + } + + return errors.Wrap(t.ExecuteTemplate(w, "packages", map[string]interface{}{ + "packages": pkgs, + "config": config, + "gitCommit": strings.TrimSpace(string(gitCommit)), + }), "template execution error") +} diff --git a/vendor/github.com/russross/blackfriday/v2/.gitignore b/vendor/github.com/russross/blackfriday/v2/.gitignore new file mode 100644 index 00000000000..75623dcccbb --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/.gitignore @@ -0,0 +1,8 @@ +*.out +*.swp +*.8 +*.6 +_obj +_test* +markdown +tags diff --git a/vendor/github.com/russross/blackfriday/v2/.travis.yml b/vendor/github.com/russross/blackfriday/v2/.travis.yml new file mode 100644 index 00000000000..b0b525a5a8e --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/.travis.yml @@ -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 ./... diff --git a/vendor/github.com/russross/blackfriday/v2/LICENSE.txt b/vendor/github.com/russross/blackfriday/v2/LICENSE.txt new file mode 100644 index 00000000000..2885af3602d --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/LICENSE.txt @@ -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. diff --git a/vendor/github.com/russross/blackfriday/v2/README.md b/vendor/github.com/russross/blackfriday/v2/README.md new file mode 100644 index 00000000000..d9c08a22fc5 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/README.md @@ -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: + +* + +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 `45`, which renders as + 45. + + +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 diff --git a/vendor/github.com/russross/blackfriday/v2/block.go b/vendor/github.com/russross/blackfriday/v2/block.go new file mode 100644 index 00000000000..dcd61e6e35b --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/block.go @@ -0,0 +1,1612 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// Functions to parse block-level elements. +// + +package blackfriday + +import ( + "bytes" + "html" + "regexp" + "strings" + "unicode" +) + +const ( + charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});" + escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]" +) + +var ( + reBackslashOrAmp = regexp.MustCompile("[\\&]") + reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity) +) + +// Parse block-level data. +// Note: this function and many that it calls assume that +// the input buffer ends with a newline. +func (p *Markdown) block(data []byte) { + // this is called recursively: enforce a maximum depth + if p.nesting >= p.maxNesting { + return + } + p.nesting++ + + // parse out one block-level construct at a time + for len(data) > 0 { + // prefixed heading: + // + // # Heading 1 + // ## Heading 2 + // ... + // ###### Heading 6 + if p.isPrefixHeading(data) { + data = data[p.prefixHeading(data):] + continue + } + + // block of preformatted HTML: + // + //
+ // ... + //
+ if data[0] == '<' { + if i := p.html(data, true); i > 0 { + data = data[i:] + continue + } + } + + // title block + // + // % stuff + // % more stuff + // % even more stuff + if p.extensions&Titleblock != 0 { + if data[0] == '%' { + if i := p.titleBlock(data, true); i > 0 { + data = data[i:] + continue + } + } + } + + // blank lines. note: returns the # of bytes to skip + if i := p.isEmpty(data); i > 0 { + data = data[i:] + continue + } + + // indented code block: + // + // func max(a, b int) int { + // if a > b { + // return a + // } + // return b + // } + if p.codePrefix(data) > 0 { + data = data[p.code(data):] + continue + } + + // fenced code block: + // + // ``` go + // func fact(n int) int { + // if n <= 1 { + // return n + // } + // return n * fact(n-1) + // } + // ``` + if p.extensions&FencedCode != 0 { + if i := p.fencedCodeBlock(data, true); i > 0 { + data = data[i:] + continue + } + } + + // horizontal rule: + // + // ------ + // or + // ****** + // or + // ______ + if p.isHRule(data) { + p.addBlock(HorizontalRule, nil) + var i int + for i = 0; i < len(data) && data[i] != '\n'; i++ { + } + data = data[i:] + continue + } + + // block quote: + // + // > A big quote I found somewhere + // > on the web + if p.quotePrefix(data) > 0 { + data = data[p.quote(data):] + continue + } + + // table: + // + // Name | Age | Phone + // ------|-----|--------- + // Bob | 31 | 555-1234 + // Alice | 27 | 555-4321 + if p.extensions&Tables != 0 { + if i := p.table(data); i > 0 { + data = data[i:] + continue + } + } + + // an itemized/unordered list: + // + // * Item 1 + // * Item 2 + // + // also works with + or - + if p.uliPrefix(data) > 0 { + data = data[p.list(data, 0):] + continue + } + + // a numbered/ordered list: + // + // 1. Item 1 + // 2. Item 2 + if p.oliPrefix(data) > 0 { + data = data[p.list(data, ListTypeOrdered):] + continue + } + + // definition lists: + // + // Term 1 + // : Definition a + // : Definition b + // + // Term 2 + // : Definition c + if p.extensions&DefinitionLists != 0 { + if p.dliPrefix(data) > 0 { + data = data[p.list(data, ListTypeDefinition):] + continue + } + } + + // anything else must look like a normal paragraph + // note: this finds underlined headings, too + data = data[p.paragraph(data):] + } + + p.nesting-- +} + +func (p *Markdown) addBlock(typ NodeType, content []byte) *Node { + p.closeUnmatchedBlocks() + container := p.addChild(typ, 0) + container.content = content + return container +} + +func (p *Markdown) isPrefixHeading(data []byte) bool { + if data[0] != '#' { + return false + } + + if p.extensions&SpaceHeadings != 0 { + level := 0 + for level < 6 && level < len(data) && data[level] == '#' { + level++ + } + if level == len(data) || data[level] != ' ' { + return false + } + } + return true +} + +func (p *Markdown) prefixHeading(data []byte) int { + level := 0 + for level < 6 && level < len(data) && data[level] == '#' { + level++ + } + i := skipChar(data, level, ' ') + end := skipUntilChar(data, i, '\n') + skip := end + id := "" + if p.extensions&HeadingIDs != 0 { + j, k := 0, 0 + // find start/end of heading id + for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ { + } + for k = j + 1; k < end && data[k] != '}'; k++ { + } + // extract heading id iff found + if j < end && k < end { + id = string(data[j+2 : k]) + end = j + skip = k + 1 + for end > 0 && data[end-1] == ' ' { + end-- + } + } + } + for end > 0 && data[end-1] == '#' { + if isBackslashEscaped(data, end-1) { + break + } + end-- + } + for end > 0 && data[end-1] == ' ' { + end-- + } + if end > i { + if id == "" && p.extensions&AutoHeadingIDs != 0 { + id = SanitizedAnchorName(string(data[i:end])) + } + block := p.addBlock(Heading, data[i:end]) + block.HeadingID = id + block.Level = level + } + return skip +} + +func (p *Markdown) isUnderlinedHeading(data []byte) int { + // test of level 1 heading + if data[0] == '=' { + i := skipChar(data, 1, '=') + i = skipChar(data, i, ' ') + if i < len(data) && data[i] == '\n' { + return 1 + } + return 0 + } + + // test of level 2 heading + if data[0] == '-' { + i := skipChar(data, 1, '-') + i = skipChar(data, i, ' ') + if i < len(data) && data[i] == '\n' { + return 2 + } + return 0 + } + + return 0 +} + +func (p *Markdown) titleBlock(data []byte, doRender bool) int { + if data[0] != '%' { + return 0 + } + splitData := bytes.Split(data, []byte("\n")) + var i int + for idx, b := range splitData { + if !bytes.HasPrefix(b, []byte("%")) { + i = idx // - 1 + break + } + } + + data = bytes.Join(splitData[0:i], []byte("\n")) + consumed := len(data) + data = bytes.TrimPrefix(data, []byte("% ")) + data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1) + block := p.addBlock(Heading, data) + block.Level = 1 + block.IsTitleblock = true + + return consumed +} + +func (p *Markdown) html(data []byte, doRender bool) int { + var i, j int + + // identify the opening tag + if data[0] != '<' { + return 0 + } + curtag, tagfound := p.htmlFindTag(data[1:]) + + // handle special cases + if !tagfound { + // check for an HTML comment + if size := p.htmlComment(data, doRender); size > 0 { + return size + } + + // check for an
tag + if size := p.htmlHr(data, doRender); size > 0 { + return size + } + + // no special case recognized + return 0 + } + + // look for an unindented matching closing tag + // followed by a blank line + found := false + /* + closetag := []byte("\n") + j = len(curtag) + 1 + for !found { + // scan for a closing tag at the beginning of a line + if skip := bytes.Index(data[j:], closetag); skip >= 0 { + j += skip + len(closetag) + } else { + break + } + + // see if it is the only thing on the line + if skip := p.isEmpty(data[j:]); skip > 0 { + // see if it is followed by a blank line/eof + j += skip + if j >= len(data) { + found = true + i = j + } else { + if skip := p.isEmpty(data[j:]); skip > 0 { + j += skip + found = true + i = j + } + } + } + } + */ + + // if not found, try a second pass looking for indented match + // but not if tag is "ins" or "del" (following original Markdown.pl) + if !found && curtag != "ins" && curtag != "del" { + i = 1 + for i < len(data) { + i++ + for i < len(data) && !(data[i-1] == '<' && data[i] == '/') { + i++ + } + + if i+2+len(curtag) >= len(data) { + break + } + + j = p.htmlFindEnd(curtag, data[i-1:]) + + if j > 0 { + i += j - 1 + found = true + break + } + } + } + + if !found { + return 0 + } + + // the end of the block has been found + if doRender { + // trim newlines + end := i + for end > 0 && data[end-1] == '\n' { + end-- + } + finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end])) + } + + return i +} + +func finalizeHTMLBlock(block *Node) { + block.Literal = block.content + block.content = nil +} + +// HTML comment, lax form +func (p *Markdown) htmlComment(data []byte, doRender bool) int { + i := p.inlineHTMLComment(data) + // needs to end with a blank line + if j := p.isEmpty(data[i:]); j > 0 { + size := i + j + if doRender { + // trim trailing newlines + end := size + for end > 0 && data[end-1] == '\n' { + end-- + } + block := p.addBlock(HTMLBlock, data[:end]) + finalizeHTMLBlock(block) + } + return size + } + return 0 +} + +// HR, which is the only self-closing block tag considered +func (p *Markdown) htmlHr(data []byte, doRender bool) int { + if len(data) < 4 { + return 0 + } + if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') { + return 0 + } + if data[3] != ' ' && data[3] != '/' && data[3] != '>' { + // not an
tag after all; at least not a valid one + return 0 + } + i := 3 + for i < len(data) && data[i] != '>' && data[i] != '\n' { + i++ + } + if i < len(data) && data[i] == '>' { + i++ + if j := p.isEmpty(data[i:]); j > 0 { + size := i + j + if doRender { + // trim newlines + end := size + for end > 0 && data[end-1] == '\n' { + end-- + } + finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end])) + } + return size + } + } + return 0 +} + +func (p *Markdown) htmlFindTag(data []byte) (string, bool) { + i := 0 + for i < len(data) && isalnum(data[i]) { + i++ + } + key := string(data[:i]) + if _, ok := blockTags[key]; ok { + return key, true + } + return "", false +} + +func (p *Markdown) htmlFindEnd(tag string, data []byte) int { + // assume data[0] == '<' && data[1] == '/' already tested + if tag == "hr" { + return 2 + } + // check if tag is a match + closetag := []byte("") + if !bytes.HasPrefix(data, closetag) { + return 0 + } + i := len(closetag) + + // check that the rest of the line is blank + skip := 0 + if skip = p.isEmpty(data[i:]); skip == 0 { + return 0 + } + i += skip + skip = 0 + + if i >= len(data) { + return i + } + + if p.extensions&LaxHTMLBlocks != 0 { + return i + } + if skip = p.isEmpty(data[i:]); skip == 0 { + // following line must be blank + return 0 + } + + return i + skip +} + +func (*Markdown) isEmpty(data []byte) int { + // it is okay to call isEmpty on an empty buffer + if len(data) == 0 { + return 0 + } + + var i int + for i = 0; i < len(data) && data[i] != '\n'; i++ { + if data[i] != ' ' && data[i] != '\t' { + return 0 + } + } + if i < len(data) && data[i] == '\n' { + i++ + } + return i +} + +func (*Markdown) isHRule(data []byte) bool { + i := 0 + + // skip up to three spaces + for i < 3 && data[i] == ' ' { + i++ + } + + // look at the hrule char + if data[i] != '*' && data[i] != '-' && data[i] != '_' { + return false + } + c := data[i] + + // the whole line must be the char or whitespace + n := 0 + for i < len(data) && data[i] != '\n' { + switch { + case data[i] == c: + n++ + case data[i] != ' ': + return false + } + i++ + } + + return n >= 3 +} + +// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data, +// and returns the end index if so, or 0 otherwise. It also returns the marker found. +// If info is not nil, it gets set to the syntax specified in the fence line. +func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) { + i, size := 0, 0 + + // skip up to three spaces + for i < len(data) && i < 3 && data[i] == ' ' { + i++ + } + + // check for the marker characters: ~ or ` + if i >= len(data) { + return 0, "" + } + if data[i] != '~' && data[i] != '`' { + return 0, "" + } + + c := data[i] + + // the whole line must be the same char or whitespace + for i < len(data) && data[i] == c { + size++ + i++ + } + + // the marker char must occur at least 3 times + if size < 3 { + return 0, "" + } + marker = string(data[i-size : i]) + + // if this is the end marker, it must match the beginning marker + if oldmarker != "" && marker != oldmarker { + return 0, "" + } + + // TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here + // into one, always get the info string, and discard it if the caller doesn't care. + if info != nil { + infoLength := 0 + i = skipChar(data, i, ' ') + + if i >= len(data) { + if i == len(data) { + return i, marker + } + return 0, "" + } + + infoStart := i + + if data[i] == '{' { + i++ + infoStart++ + + for i < len(data) && data[i] != '}' && data[i] != '\n' { + infoLength++ + i++ + } + + if i >= len(data) || data[i] != '}' { + return 0, "" + } + + // strip all whitespace at the beginning and the end + // of the {} block + for infoLength > 0 && isspace(data[infoStart]) { + infoStart++ + infoLength-- + } + + for infoLength > 0 && isspace(data[infoStart+infoLength-1]) { + infoLength-- + } + i++ + i = skipChar(data, i, ' ') + } else { + for i < len(data) && !isverticalspace(data[i]) { + infoLength++ + i++ + } + } + + *info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength])) + } + + if i == len(data) { + return i, marker + } + if i > len(data) || data[i] != '\n' { + return 0, "" + } + return i + 1, marker // Take newline into account. +} + +// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, +// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. +// If doRender is true, a final newline is mandatory to recognize the fenced code block. +func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int { + var info string + beg, marker := isFenceLine(data, &info, "") + if beg == 0 || beg >= len(data) { + return 0 + } + fenceLength := beg - 1 + + var work bytes.Buffer + work.Write([]byte(info)) + work.WriteByte('\n') + + for { + // safe to assume beg < len(data) + + // check for the end of the code block + fenceEnd, _ := isFenceLine(data[beg:], nil, marker) + if fenceEnd != 0 { + beg += fenceEnd + break + } + + // copy the current line + end := skipUntilChar(data, beg, '\n') + 1 + + // did we reach the end of the buffer without a closing marker? + if end >= len(data) { + return 0 + } + + // verbatim copy to the working buffer + if doRender { + work.Write(data[beg:end]) + } + beg = end + } + + if doRender { + block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer + block.IsFenced = true + block.FenceLength = fenceLength + finalizeCodeBlock(block) + } + + return beg +} + +func unescapeChar(str []byte) []byte { + if str[0] == '\\' { + return []byte{str[1]} + } + return []byte(html.UnescapeString(string(str))) +} + +func unescapeString(str []byte) []byte { + if reBackslashOrAmp.Match(str) { + return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar) + } + return str +} + +func finalizeCodeBlock(block *Node) { + if block.IsFenced { + newlinePos := bytes.IndexByte(block.content, '\n') + firstLine := block.content[:newlinePos] + rest := block.content[newlinePos+1:] + block.Info = unescapeString(bytes.Trim(firstLine, "\n")) + block.Literal = rest + } else { + block.Literal = block.content + } + block.content = nil +} + +func (p *Markdown) table(data []byte) int { + table := p.addBlock(Table, nil) + i, columns := p.tableHeader(data) + if i == 0 { + p.tip = table.Parent + table.Unlink() + return 0 + } + + p.addBlock(TableBody, nil) + + for i < len(data) { + pipes, rowStart := 0, i + for ; i < len(data) && data[i] != '\n'; i++ { + if data[i] == '|' { + pipes++ + } + } + + if pipes == 0 { + i = rowStart + break + } + + // include the newline in data sent to tableRow + if i < len(data) && data[i] == '\n' { + i++ + } + p.tableRow(data[rowStart:i], columns, false) + } + + return i +} + +// check if the specified position is preceded by an odd number of backslashes +func isBackslashEscaped(data []byte, i int) bool { + backslashes := 0 + for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' { + backslashes++ + } + return backslashes&1 == 1 +} + +func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) { + i := 0 + colCount := 1 + for i = 0; i < len(data) && data[i] != '\n'; i++ { + if data[i] == '|' && !isBackslashEscaped(data, i) { + colCount++ + } + } + + // doesn't look like a table header + if colCount == 1 { + return + } + + // include the newline in the data sent to tableRow + j := i + if j < len(data) && data[j] == '\n' { + j++ + } + header := data[:j] + + // column count ignores pipes at beginning or end of line + if data[0] == '|' { + colCount-- + } + if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) { + colCount-- + } + + columns = make([]CellAlignFlags, colCount) + + // move on to the header underline + i++ + if i >= len(data) { + return + } + + if data[i] == '|' && !isBackslashEscaped(data, i) { + i++ + } + i = skipChar(data, i, ' ') + + // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3 + // and trailing | optional on last column + col := 0 + for i < len(data) && data[i] != '\n' { + dashes := 0 + + if data[i] == ':' { + i++ + columns[col] |= TableAlignmentLeft + dashes++ + } + for i < len(data) && data[i] == '-' { + i++ + dashes++ + } + if i < len(data) && data[i] == ':' { + i++ + columns[col] |= TableAlignmentRight + dashes++ + } + for i < len(data) && data[i] == ' ' { + i++ + } + if i == len(data) { + return + } + // end of column test is messy + switch { + case dashes < 3: + // not a valid column + return + + case data[i] == '|' && !isBackslashEscaped(data, i): + // marker found, now skip past trailing whitespace + col++ + i++ + for i < len(data) && data[i] == ' ' { + i++ + } + + // trailing junk found after last column + if col >= colCount && i < len(data) && data[i] != '\n' { + return + } + + case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount: + // something else found where marker was required + return + + case data[i] == '\n': + // marker is optional for the last column + col++ + + default: + // trailing junk found after last column + return + } + } + if col != colCount { + return + } + + p.addBlock(TableHead, nil) + p.tableRow(header, columns, true) + size = i + if size < len(data) && data[size] == '\n' { + size++ + } + return +} + +func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) { + p.addBlock(TableRow, nil) + i, col := 0, 0 + + if data[i] == '|' && !isBackslashEscaped(data, i) { + i++ + } + + for col = 0; col < len(columns) && i < len(data); col++ { + for i < len(data) && data[i] == ' ' { + i++ + } + + cellStart := i + + for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { + i++ + } + + cellEnd := i + + // skip the end-of-cell marker, possibly taking us past end of buffer + i++ + + for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' { + cellEnd-- + } + + cell := p.addBlock(TableCell, data[cellStart:cellEnd]) + cell.IsHeader = header + cell.Align = columns[col] + } + + // pad it out with empty columns to get the right number + for ; col < len(columns); col++ { + cell := p.addBlock(TableCell, nil) + cell.IsHeader = header + cell.Align = columns[col] + } + + // silently ignore rows with too many cells +} + +// returns blockquote prefix length +func (p *Markdown) quotePrefix(data []byte) int { + i := 0 + for i < 3 && i < len(data) && data[i] == ' ' { + i++ + } + if i < len(data) && data[i] == '>' { + if i+1 < len(data) && data[i+1] == ' ' { + return i + 2 + } + return i + 1 + } + return 0 +} + +// blockquote ends with at least one blank line +// followed by something without a blockquote prefix +func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool { + if p.isEmpty(data[beg:]) <= 0 { + return false + } + if end >= len(data) { + return true + } + return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0 +} + +// parse a blockquote fragment +func (p *Markdown) quote(data []byte) int { + block := p.addBlock(BlockQuote, nil) + var raw bytes.Buffer + beg, end := 0, 0 + for beg < len(data) { + end = beg + // Step over whole lines, collecting them. While doing that, check for + // fenced code and if one's found, incorporate it altogether, + // irregardless of any contents inside it + for end < len(data) && data[end] != '\n' { + if p.extensions&FencedCode != 0 { + if i := p.fencedCodeBlock(data[end:], false); i > 0 { + // -1 to compensate for the extra end++ after the loop: + end += i - 1 + break + } + } + end++ + } + if end < len(data) && data[end] == '\n' { + end++ + } + if pre := p.quotePrefix(data[beg:]); pre > 0 { + // skip the prefix + beg += pre + } else if p.terminateBlockquote(data, beg, end) { + break + } + // this line is part of the blockquote + raw.Write(data[beg:end]) + beg = end + } + p.block(raw.Bytes()) + p.finalize(block) + return end +} + +// returns prefix length for block code +func (p *Markdown) codePrefix(data []byte) int { + if len(data) >= 1 && data[0] == '\t' { + return 1 + } + if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { + return 4 + } + return 0 +} + +func (p *Markdown) code(data []byte) int { + var work bytes.Buffer + + i := 0 + for i < len(data) { + beg := i + for i < len(data) && data[i] != '\n' { + i++ + } + if i < len(data) && data[i] == '\n' { + i++ + } + + blankline := p.isEmpty(data[beg:i]) > 0 + if pre := p.codePrefix(data[beg:i]); pre > 0 { + beg += pre + } else if !blankline { + // non-empty, non-prefixed line breaks the pre + i = beg + break + } + + // verbatim copy to the working buffer + if blankline { + work.WriteByte('\n') + } else { + work.Write(data[beg:i]) + } + } + + // trim all the \n off the end of work + workbytes := work.Bytes() + eol := len(workbytes) + for eol > 0 && workbytes[eol-1] == '\n' { + eol-- + } + if eol != len(workbytes) { + work.Truncate(eol) + } + + work.WriteByte('\n') + + block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer + block.IsFenced = false + finalizeCodeBlock(block) + + return i +} + +// returns unordered list item prefix +func (p *Markdown) uliPrefix(data []byte) int { + i := 0 + // start with up to 3 spaces + for i < len(data) && i < 3 && data[i] == ' ' { + i++ + } + if i >= len(data)-1 { + return 0 + } + // need one of {'*', '+', '-'} followed by a space or a tab + if (data[i] != '*' && data[i] != '+' && data[i] != '-') || + (data[i+1] != ' ' && data[i+1] != '\t') { + return 0 + } + return i + 2 +} + +// returns ordered list item prefix +func (p *Markdown) oliPrefix(data []byte) int { + i := 0 + + // start with up to 3 spaces + for i < 3 && i < len(data) && data[i] == ' ' { + i++ + } + + // count the digits + start := i + for i < len(data) && data[i] >= '0' && data[i] <= '9' { + i++ + } + if start == i || i >= len(data)-1 { + return 0 + } + + // we need >= 1 digits followed by a dot and a space or a tab + if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') { + return 0 + } + return i + 2 +} + +// returns definition list item prefix +func (p *Markdown) dliPrefix(data []byte) int { + if len(data) < 2 { + return 0 + } + i := 0 + // need a ':' followed by a space or a tab + if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') { + return 0 + } + for i < len(data) && data[i] == ' ' { + i++ + } + return i + 2 +} + +// parse ordered or unordered list block +func (p *Markdown) list(data []byte, flags ListType) int { + i := 0 + flags |= ListItemBeginningOfList + block := p.addBlock(List, nil) + block.ListFlags = flags + block.Tight = true + + for i < len(data) { + skip := p.listItem(data[i:], &flags) + if flags&ListItemContainsBlock != 0 { + block.ListData.Tight = false + } + i += skip + if skip == 0 || flags&ListItemEndOfList != 0 { + break + } + flags &= ^ListItemBeginningOfList + } + + above := block.Parent + finalizeList(block) + p.tip = above + return i +} + +// Returns true if the list item is not the same type as its parent list +func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool { + if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 { + return true + } else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 { + return true + } else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) { + return true + } + return false +} + +// Returns true if block ends with a blank line, descending if needed +// into lists and sublists. +func endsWithBlankLine(block *Node) bool { + // TODO: figure this out. Always false now. + for block != nil { + //if block.lastLineBlank { + //return true + //} + t := block.Type + if t == List || t == Item { + block = block.LastChild + } else { + break + } + } + return false +} + +func finalizeList(block *Node) { + block.open = false + item := block.FirstChild + for item != nil { + // check for non-final list item ending with blank line: + if endsWithBlankLine(item) && item.Next != nil { + block.ListData.Tight = false + break + } + // recurse into children of list item, to see if there are spaces + // between any of them: + subItem := item.FirstChild + for subItem != nil { + if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) { + block.ListData.Tight = false + break + } + subItem = subItem.Next + } + item = item.Next + } +} + +// Parse a single list item. +// Assumes initial prefix is already removed if this is a sublist. +func (p *Markdown) listItem(data []byte, flags *ListType) int { + // keep track of the indentation of the first line + itemIndent := 0 + if data[0] == '\t' { + itemIndent += 4 + } else { + for itemIndent < 3 && data[itemIndent] == ' ' { + itemIndent++ + } + } + + var bulletChar byte = '*' + i := p.uliPrefix(data) + if i == 0 { + i = p.oliPrefix(data) + } else { + bulletChar = data[i-2] + } + if i == 0 { + i = p.dliPrefix(data) + // reset definition term flag + if i > 0 { + *flags &= ^ListTypeTerm + } + } + if i == 0 { + // if in definition list, set term flag and continue + if *flags&ListTypeDefinition != 0 { + *flags |= ListTypeTerm + } else { + return 0 + } + } + + // skip leading whitespace on first line + for i < len(data) && data[i] == ' ' { + i++ + } + + // find the end of the line + line := i + for i > 0 && 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[line:i]) + line = i + + // process the following lines + containsBlankLine := false + sublist := 0 + codeBlockMarker := "" + +gatherlines: + for line < 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[line:i]) > 0 { + containsBlankLine = true + line = i + continue + } + + // calculate the indentation + indent := 0 + indentIndex := 0 + if data[line] == '\t' { + indentIndex++ + indent += 4 + } else { + for indent < 4 && line+indent < i && data[line+indent] == ' ' { + indent++ + indentIndex++ + } + } + + chunk := data[line+indentIndex : i] + + if p.extensions&FencedCode != 0 { + // determine if in or out of codeblock + // if in codeblock, ignore normal list processing + _, marker := isFenceLine(chunk, nil, codeBlockMarker) + if marker != "" { + if codeBlockMarker == "" { + // start of codeblock + codeBlockMarker = marker + } else { + // end of codeblock. + codeBlockMarker = "" + } + } + // we are in a codeblock, write line, and continue + if codeBlockMarker != "" || marker != "" { + raw.Write(data[line+indentIndex : i]) + line = i + continue gatherlines + } + } + + // evaluate how this line fits in + switch { + // is this a nested list item? + case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) || + p.oliPrefix(chunk) > 0 || + p.dliPrefix(chunk) > 0: + + // to be a nested list, it must be indented more + // if not, it is either a different kind of list + // or the next item in the same list + if indent <= itemIndent { + if p.listTypeChanged(chunk, flags) { + *flags |= ListItemEndOfList + } else if containsBlankLine { + *flags |= ListItemContainsBlock + } + + break gatherlines + } + + if containsBlankLine { + *flags |= ListItemContainsBlock + } + + // is this the first item in the nested list? + if sublist == 0 { + sublist = raw.Len() + } + + // is this a nested prefix heading? + case p.isPrefixHeading(chunk): + // if the heading is not indented, it is not nested in the list + // and thus ends the list + if containsBlankLine && indent < 4 { + *flags |= ListItemEndOfList + break gatherlines + } + *flags |= ListItemContainsBlock + + // anything following an empty line is only part + // of this item if it is indented 4 spaces + // (regardless of the indentation of the beginning of the item) + case containsBlankLine && indent < 4: + if *flags&ListTypeDefinition != 0 && i < len(data)-1 { + // is the next item still a part of this list? + next := i + for next < len(data) && data[next] != '\n' { + next++ + } + for next < len(data)-1 && data[next] == '\n' { + next++ + } + if i < len(data)-1 && data[i] != ':' && data[next] != ':' { + *flags |= ListItemEndOfList + } + } else { + *flags |= ListItemEndOfList + } + break gatherlines + + // a blank line means this should be parsed as a block + case containsBlankLine: + raw.WriteByte('\n') + *flags |= ListItemContainsBlock + } + + // if this line was preceded by one or more blanks, + // re-introduce the blank into the buffer + if containsBlankLine { + containsBlankLine = false + raw.WriteByte('\n') + } + + // add the line into the working buffer without prefix + raw.Write(data[line+indentIndex : i]) + + line = i + } + + rawBytes := raw.Bytes() + + block := p.addBlock(Item, nil) + block.ListFlags = *flags + block.Tight = false + block.BulletChar = bulletChar + block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark + + // render the contents of the list item + if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 { + // intermediate render of block item, except for definition term + if sublist > 0 { + p.block(rawBytes[:sublist]) + p.block(rawBytes[sublist:]) + } else { + p.block(rawBytes) + } + } else { + // intermediate render of inline item + if sublist > 0 { + child := p.addChild(Paragraph, 0) + child.content = rawBytes[:sublist] + p.block(rawBytes[sublist:]) + } else { + child := p.addChild(Paragraph, 0) + child.content = rawBytes + } + } + return line +} + +// render a single paragraph that has already been parsed out +func (p *Markdown) renderParagraph(data []byte) { + if len(data) == 0 { + return + } + + // trim leading spaces + beg := 0 + for data[beg] == ' ' { + beg++ + } + + end := len(data) + // trim trailing newline + if data[len(data)-1] == '\n' { + end-- + } + + // trim trailing spaces + for end > beg && data[end-1] == ' ' { + end-- + } + + p.addBlock(Paragraph, data[beg:end]) +} + +func (p *Markdown) paragraph(data []byte) int { + // prev: index of 1st char of previous line + // line: index of 1st char of current line + // i: index of cursor/end of current line + var prev, line, i int + tabSize := TabSizeDefault + if p.extensions&TabSizeEight != 0 { + tabSize = TabSizeDouble + } + // keep going until we find something to mark the end of the paragraph + for i < len(data) { + // mark the beginning of the current line + prev = line + current := data[i:] + line = i + + // did we find a reference or a footnote? If so, end a paragraph + // preceding it and report that we have consumed up to the end of that + // reference: + if refEnd := isReference(p, current, tabSize); refEnd > 0 { + p.renderParagraph(data[:i]) + return i + refEnd + } + + // did we find a blank line marking the end of the paragraph? + if n := p.isEmpty(current); n > 0 { + // did this blank line followed by a definition list item? + if p.extensions&DefinitionLists != 0 { + if i < len(data)-1 && data[i+1] == ':' { + return p.list(data[prev:], ListTypeDefinition) + } + } + + p.renderParagraph(data[:i]) + return i + n + } + + // an underline under some text marks a heading, so our paragraph ended on prev line + if i > 0 { + if level := p.isUnderlinedHeading(current); level > 0 { + // render the paragraph + p.renderParagraph(data[:prev]) + + // ignore leading and trailing whitespace + eol := i - 1 + for prev < eol && data[prev] == ' ' { + prev++ + } + for eol > prev && data[eol-1] == ' ' { + eol-- + } + + id := "" + if p.extensions&AutoHeadingIDs != 0 { + id = SanitizedAnchorName(string(data[prev:eol])) + } + + block := p.addBlock(Heading, data[prev:eol]) + block.Level = level + block.HeadingID = id + + // find the end of the underline + for i < len(data) && data[i] != '\n' { + i++ + } + return i + } + } + + // if the next line starts a block of HTML, then the paragraph ends here + if p.extensions&LaxHTMLBlocks != 0 { + if data[i] == '<' && p.html(current, false) > 0 { + // rewind to before the HTML block + p.renderParagraph(data[:i]) + return i + } + } + + // if there's a prefixed heading or a horizontal rule after this, paragraph is over + if p.isPrefixHeading(current) || p.isHRule(current) { + p.renderParagraph(data[:i]) + return i + } + + // if there's a fenced code block, paragraph is over + if p.extensions&FencedCode != 0 { + if p.fencedCodeBlock(current, false) > 0 { + p.renderParagraph(data[:i]) + return i + } + } + + // if there's a definition list item, prev line is a definition term + if p.extensions&DefinitionLists != 0 { + if p.dliPrefix(current) != 0 { + ret := p.list(data[prev:], ListTypeDefinition) + return ret + } + } + + // if there's a list after this, paragraph is over + if p.extensions&NoEmptyLineBeforeBlock != 0 { + if p.uliPrefix(current) != 0 || + p.oliPrefix(current) != 0 || + p.quotePrefix(current) != 0 || + p.codePrefix(current) != 0 { + p.renderParagraph(data[:i]) + return i + } + } + + // otherwise, scan to the beginning of the next line + nl := bytes.IndexByte(data[i:], '\n') + if nl >= 0 { + i += nl + 1 + } else { + i += len(data[i:]) + } + } + + p.renderParagraph(data[:i]) + return i +} + +func skipChar(data []byte, start int, char byte) int { + i := start + for i < len(data) && data[i] == char { + i++ + } + return i +} + +func skipUntilChar(text []byte, start int, char byte) int { + i := start + for i < len(text) && text[i] != char { + i++ + } + return i +} + +// SanitizedAnchorName returns a sanitized anchor name for the given text. +// +// It implements the algorithm specified in the package comment. +func SanitizedAnchorName(text string) string { + var anchorName []rune + futureDash := false + for _, r := range text { + switch { + case unicode.IsLetter(r) || unicode.IsNumber(r): + if futureDash && len(anchorName) > 0 { + anchorName = append(anchorName, '-') + } + futureDash = false + anchorName = append(anchorName, unicode.ToLower(r)) + default: + futureDash = true + } + } + return string(anchorName) +} diff --git a/vendor/github.com/russross/blackfriday/v2/doc.go b/vendor/github.com/russross/blackfriday/v2/doc.go new file mode 100644 index 00000000000..57ff152a056 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/doc.go @@ -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. diff --git a/vendor/github.com/russross/blackfriday/v2/entities.go b/vendor/github.com/russross/blackfriday/v2/entities.go new file mode 100644 index 00000000000..a2c3edb691c --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/entities.go @@ -0,0 +1,2236 @@ +package blackfriday + +// Extracted from https://html.spec.whatwg.org/multipage/entities.json +var entities = map[string]bool{ + "Æ": true, + "Æ": true, + "&": true, + "&": true, + "Á": true, + "Á": true, + "Ă": true, + "Â": true, + "Â": true, + "А": true, + "𝔄": true, + "À": true, + "À": true, + "Α": true, + "Ā": true, + "⩓": true, + "Ą": true, + "𝔸": true, + "⁡": true, + "Å": true, + "Å": true, + "𝒜": true, + "≔": true, + "Ã": true, + "Ã": true, + "Ä": true, + "Ä": true, + "∖": true, + "⫧": true, + "⌆": true, + "Б": true, + "∵": true, + "ℬ": true, + "Β": true, + "𝔅": true, + "𝔹": true, + "˘": true, + "ℬ": true, + "≎": true, + "Ч": true, + "©": true, + "©": true, + "Ć": true, + "⋒": true, + "ⅅ": true, + "ℭ": true, + "Č": true, + "Ç": true, + "Ç": true, + "Ĉ": true, + "∰": true, + "Ċ": true, + "¸": true, + "·": true, + "ℭ": true, + "Χ": true, + "⊙": true, + "⊖": true, + "⊕": true, + "⊗": true, + "∲": true, + "”": true, + "’": true, + "∷": true, + "⩴": true, + "≡": true, + "∯": true, + "∮": true, + "ℂ": true, + "∐": true, + "∳": true, + "⨯": true, + "𝒞": true, + "⋓": true, + "≍": true, + "ⅅ": true, + "⤑": true, + "Ђ": true, + "Ѕ": true, + "Џ": true, + "‡": true, + "↡": true, + "⫤": true, + "Ď": true, + "Д": true, + "∇": true, + "Δ": true, + "𝔇": true, + "´": true, + "˙": true, + "˝": true, + "`": true, + "˜": true, + "⋄": true, + "ⅆ": true, + "𝔻": true, + "¨": true, + "⃜": true, + "≐": true, + "∯": true, + "¨": true, + "⇓": true, + "⇐": true, + "⇔": true, + "⫤": true, + "⟸": true, + "⟺": true, + "⟹": true, + "⇒": true, + "⊨": true, + "⇑": true, + "⇕": true, + "∥": true, + "↓": true, + "⤓": true, + "⇵": true, + "̑": true, + "⥐": true, + "⥞": true, + "↽": true, + "⥖": true, + "⥟": true, + "⇁": true, + "⥗": true, + "⊤": true, + "↧": true, + "⇓": true, + "𝒟": true, + "Đ": true, + "Ŋ": true, + "Ð": true, + "Ð": true, + "É": true, + "É": true, + "Ě": true, + "Ê": true, + "Ê": true, + "Э": true, + "Ė": true, + "𝔈": true, + "È": true, + "È": true, + "∈": true, + "Ē": true, + "◻": true, + "▫": true, + "Ę": true, + "𝔼": true, + "Ε": true, + "⩵": true, + "≂": true, + "⇌": true, + "ℰ": true, + "⩳": true, + "Η": true, + "Ë": true, + "Ë": true, + "∃": true, + "ⅇ": true, + "Ф": true, + "𝔉": true, + "◼": true, + "▪": true, + "𝔽": true, + "∀": true, + "ℱ": true, + "ℱ": true, + "Ѓ": true, + ">": true, + ">": true, + "Γ": true, + "Ϝ": true, + "Ğ": true, + "Ģ": true, + "Ĝ": true, + "Г": true, + "Ġ": true, + "𝔊": true, + "⋙": true, + "𝔾": true, + "≥": true, + "⋛": true, + "≧": true, + "⪢": true, + "≷": true, + "⩾": true, + "≳": true, + "𝒢": true, + "≫": true, + "Ъ": true, + "ˇ": true, + "^": true, + "Ĥ": true, + "ℌ": true, + "ℋ": true, + "ℍ": true, + "─": true, + "ℋ": true, + "Ħ": true, + "≎": true, + "≏": true, + "Е": true, + "IJ": true, + "Ё": true, + "Í": true, + "Í": true, + "Î": true, + "Î": true, + "И": true, + "İ": true, + "ℑ": true, + "Ì": true, + "Ì": true, + "ℑ": true, + "Ī": true, + "ⅈ": true, + "⇒": true, + "∬": true, + "∫": true, + "⋂": true, + "⁣": true, + "⁢": true, + "Į": true, + "𝕀": true, + "Ι": true, + "ℐ": true, + "Ĩ": true, + "І": true, + "Ï": true, + "Ï": true, + "Ĵ": true, + "Й": true, + "𝔍": true, + "𝕁": true, + "𝒥": true, + "Ј": true, + "Є": true, + "Х": true, + "Ќ": true, + "Κ": true, + "Ķ": true, + "К": true, + "𝔎": true, + "𝕂": true, + "𝒦": true, + "Љ": true, + "<": true, + "<": true, + "Ĺ": true, + "Λ": true, + "⟪": true, + "ℒ": true, + "↞": true, + "Ľ": true, + "Ļ": true, + "Л": true, + "⟨": true, + "←": true, + "⇤": true, + "⇆": true, + "⌈": true, + "⟦": true, + "⥡": true, + "⇃": true, + "⥙": true, + "⌊": true, + "↔": true, + "⥎": true, + "⊣": true, + "↤": true, + "⥚": true, + "⊲": true, + "⧏": true, + "⊴": true, + "⥑": true, + "⥠": true, + "↿": true, + "⥘": true, + "↼": true, + "⥒": true, + "⇐": true, + "⇔": true, + "⋚": true, + "≦": true, + "≶": true, + "⪡": true, + "⩽": true, + "≲": true, + "𝔏": true, + "⋘": true, + "⇚": true, + "Ŀ": true, + "⟵": true, + "⟷": true, + "⟶": true, + "⟸": true, + "⟺": true, + "⟹": true, + "𝕃": true, + "↙": true, + "↘": true, + "ℒ": true, + "↰": true, + "Ł": true, + "≪": true, + "⤅": true, + "М": true, + " ": true, + "ℳ": true, + "𝔐": true, + "∓": true, + "𝕄": true, + "ℳ": true, + "Μ": true, + "Њ": true, + "Ń": true, + "Ň": true, + "Ņ": true, + "Н": true, + "​": true, + "​": true, + "​": true, + "​": true, + "≫": true, + "≪": true, + " ": true, + "𝔑": true, + "⁠": true, + " ": true, + "ℕ": true, + "⫬": true, + "≢": true, + "≭": true, + "∦": true, + "∉": true, + "≠": true, + "≂̸": true, + "∄": true, + "≯": true, + "≱": true, + "≧̸": true, + "≫̸": true, + "≹": true, + "⩾̸": true, + "≵": true, + "≎̸": true, + "≏̸": true, + "⋪": true, + "⧏̸": true, + "⋬": true, + "≮": true, + "≰": true, + "≸": true, + "≪̸": true, + "⩽̸": true, + "≴": true, + "⪢̸": true, + "⪡̸": true, + "⊀": true, + "⪯̸": true, + "⋠": true, + "∌": true, + "⋫": true, + "⧐̸": true, + "⋭": true, + "⊏̸": true, + "⋢": true, + "⊐̸": true, + "⋣": true, + "⊂⃒": true, + "⊈": true, + "⊁": true, + "⪰̸": true, + "⋡": true, + "≿̸": true, + "⊃⃒": true, + "⊉": true, + "≁": true, + "≄": true, + "≇": true, + "≉": true, + "∤": true, + "𝒩": true, + "Ñ": true, + "Ñ": true, + "Ν": true, + "Œ": true, + "Ó": true, + "Ó": true, + "Ô": true, + "Ô": true, + "О": true, + "Ő": true, + "𝔒": true, + "Ò": true, + "Ò": true, + "Ō": true, + "Ω": true, + "Ο": true, + "𝕆": true, + "“": true, + "‘": true, + "⩔": true, + "𝒪": true, + "Ø": true, + "Ø": true, + "Õ": true, + "Õ": true, + "⨷": true, + "Ö": true, + "Ö": true, + "‾": true, + "⏞": true, + "⎴": true, + "⏜": true, + "∂": true, + "П": true, + "𝔓": true, + "Φ": true, + "Π": true, + "±": true, + "ℌ": true, + "ℙ": true, + "⪻": true, + "≺": true, + "⪯": true, + "≼": true, + "≾": true, + "″": true, + "∏": true, + "∷": true, + "∝": true, + "𝒫": true, + "Ψ": true, + """: true, + """: true, + "𝔔": true, + "ℚ": true, + "𝒬": true, + "⤐": true, + "®": true, + "®": true, + "Ŕ": true, + "⟫": true, + "↠": true, + "⤖": true, + "Ř": true, + "Ŗ": true, + "Р": true, + "ℜ": true, + "∋": true, + "⇋": true, + "⥯": true, + "ℜ": true, + "Ρ": true, + "⟩": true, + "→": true, + "⇥": true, + "⇄": true, + "⌉": true, + "⟧": true, + "⥝": true, + "⇂": true, + "⥕": true, + "⌋": true, + "⊢": true, + "↦": true, + "⥛": true, + "⊳": true, + "⧐": true, + "⊵": true, + "⥏": true, + "⥜": true, + "↾": true, + "⥔": true, + "⇀": true, + "⥓": true, + "⇒": true, + "ℝ": true, + "⥰": true, + "⇛": true, + "ℛ": true, + "↱": true, + "⧴": true, + "Щ": true, + "Ш": true, + "Ь": true, + "Ś": true, + "⪼": true, + "Š": true, + "Ş": true, + "Ŝ": true, + "С": true, + "𝔖": true, + "↓": true, + "←": true, + "→": true, + "↑": true, + "Σ": true, + "∘": true, + "𝕊": true, + "√": true, + "□": true, + "⊓": true, + "⊏": true, + "⊑": true, + "⊐": true, + "⊒": true, + "⊔": true, + "𝒮": true, + "⋆": true, + "⋐": true, + "⋐": true, + "⊆": true, + "≻": true, + "⪰": true, + "≽": true, + "≿": true, + "∋": true, + "∑": true, + "⋑": true, + "⊃": true, + "⊇": true, + "⋑": true, + "Þ": true, + "Þ": true, + "™": true, + "Ћ": true, + "Ц": true, + " ": true, + "Τ": true, + "Ť": true, + "Ţ": true, + "Т": true, + "𝔗": true, + "∴": true, + "Θ": true, + "  ": true, + " ": true, + "∼": true, + "≃": true, + "≅": true, + "≈": true, + "𝕋": true, + "⃛": true, + "𝒯": true, + "Ŧ": true, + "Ú": true, + "Ú": true, + "↟": true, + "⥉": true, + "Ў": true, + "Ŭ": true, + "Û": true, + "Û": true, + "У": true, + "Ű": true, + "𝔘": true, + "Ù": true, + "Ù": true, + "Ū": true, + "_": true, + "⏟": true, + "⎵": true, + "⏝": true, + "⋃": true, + "⊎": true, + "Ų": true, + "𝕌": true, + "↑": true, + "⤒": true, + "⇅": true, + "↕": true, + "⥮": true, + "⊥": true, + "↥": true, + "⇑": true, + "⇕": true, + "↖": true, + "↗": true, + "ϒ": true, + "Υ": true, + "Ů": true, + "𝒰": true, + "Ũ": true, + "Ü": true, + "Ü": true, + "⊫": true, + "⫫": true, + "В": true, + "⊩": true, + "⫦": true, + "⋁": true, + "‖": true, + "‖": true, + "∣": true, + "|": true, + "❘": true, + "≀": true, + " ": true, + "𝔙": true, + "𝕍": true, + "𝒱": true, + "⊪": true, + "Ŵ": true, + "⋀": true, + "𝔚": true, + "𝕎": true, + "𝒲": true, + "𝔛": true, + "Ξ": true, + "𝕏": true, + "𝒳": true, + "Я": true, + "Ї": true, + "Ю": true, + "Ý": true, + "Ý": true, + "Ŷ": true, + "Ы": true, + "𝔜": true, + "𝕐": true, + "𝒴": true, + "Ÿ": true, + "Ж": true, + "Ź": true, + "Ž": true, + "З": true, + "Ż": true, + "​": true, + "Ζ": true, + "ℨ": true, + "ℤ": true, + "𝒵": true, + "á": true, + "á": true, + "ă": true, + "∾": true, + "∾̳": true, + "∿": true, + "â": true, + "â": true, + "´": true, + "´": true, + "а": true, + "æ": true, + "æ": true, + "⁡": true, + "𝔞": true, + "à": true, + "à": true, + "ℵ": true, + "ℵ": true, + "α": true, + "ā": true, + "⨿": true, + "&": true, + "&": true, + "∧": true, + "⩕": true, + "⩜": true, + "⩘": true, + "⩚": true, + "∠": true, + "⦤": true, + "∠": true, + "∡": true, + "⦨": true, + "⦩": true, + "⦪": true, + "⦫": true, + "⦬": true, + "⦭": true, + "⦮": true, + "⦯": true, + "∟": true, + "⊾": true, + "⦝": true, + "∢": true, + "Å": true, + "⍼": true, + "ą": true, + "𝕒": true, + "≈": true, + "⩰": true, + "⩯": true, + "≊": true, + "≋": true, + "'": true, + "≈": true, + "≊": true, + "å": true, + "å": true, + "𝒶": true, + "*": true, + "≈": true, + "≍": true, + "ã": true, + "ã": true, + "ä": true, + "ä": true, + "∳": true, + "⨑": true, + "⫭": true, + "≌": true, + "϶": true, + "‵": true, + "∽": true, + "⋍": true, + "⊽": true, + "⌅": true, + "⌅": true, + "⎵": true, + "⎶": true, + "≌": true, + "б": true, + "„": true, + "∵": true, + "∵": true, + "⦰": true, + "϶": true, + "ℬ": true, + "β": true, + "ℶ": true, + "≬": true, + "𝔟": true, + "⋂": true, + "◯": true, + "⋃": true, + "⨀": true, + "⨁": true, + "⨂": true, + "⨆": true, + "★": true, + "▽": true, + "△": true, + "⨄": true, + "⋁": true, + "⋀": true, + "⤍": true, + "⧫": true, + "▪": true, + "▴": true, + "▾": true, + "◂": true, + "▸": true, + "␣": true, + "▒": true, + "░": true, + "▓": true, + "█": true, + "=⃥": true, + "≡⃥": true, + "⌐": true, + "𝕓": true, + "⊥": true, + "⊥": true, + "⋈": true, + "╗": true, + "╔": true, + "╖": true, + "╓": true, + "═": true, + "╦": true, + "╩": true, + "╤": true, + "╧": true, + "╝": true, + "╚": true, + "╜": true, + "╙": true, + "║": true, + "╬": true, + "╣": true, + "╠": true, + "╫": true, + "╢": true, + "╟": true, + "⧉": true, + "╕": true, + "╒": true, + "┐": true, + "┌": true, + "─": true, + "╥": true, + "╨": true, + "┬": true, + "┴": true, + "⊟": true, + "⊞": true, + "⊠": true, + "╛": true, + "╘": true, + "┘": true, + "└": true, + "│": true, + "╪": true, + "╡": true, + "╞": true, + "┼": true, + "┤": true, + "├": true, + "‵": true, + "˘": true, + "¦": true, + "¦": true, + "𝒷": true, + "⁏": true, + "∽": true, + "⋍": true, + "\": true, + "⧅": true, + "⟈": true, + "•": true, + "•": true, + "≎": true, + "⪮": true, + "≏": true, + "≏": true, + "ć": true, + "∩": true, + "⩄": true, + "⩉": true, + "⩋": true, + "⩇": true, + "⩀": true, + "∩︀": true, + "⁁": true, + "ˇ": true, + "⩍": true, + "č": true, + "ç": true, + "ç": true, + "ĉ": true, + "⩌": true, + "⩐": true, + "ċ": true, + "¸": true, + "¸": true, + "⦲": true, + "¢": true, + "¢": true, + "·": true, + "𝔠": true, + "ч": true, + "✓": true, + "✓": true, + "χ": true, + "○": true, + "⧃": true, + "ˆ": true, + "≗": true, + "↺": true, + "↻": true, + "®": true, + "Ⓢ": true, + "⊛": true, + "⊚": true, + "⊝": true, + "≗": true, + "⨐": true, + "⫯": true, + "⧂": true, + "♣": true, + "♣": true, + ":": true, + "≔": true, + "≔": true, + ",": true, + "@": true, + "∁": true, + "∘": true, + "∁": true, + "ℂ": true, + "≅": true, + "⩭": true, + "∮": true, + "𝕔": true, + "∐": true, + "©": true, + "©": true, + "℗": true, + "↵": true, + "✗": true, + "𝒸": true, + "⫏": true, + "⫑": true, + "⫐": true, + "⫒": true, + "⋯": true, + "⤸": true, + "⤵": true, + "⋞": true, + "⋟": true, + "↶": true, + "⤽": true, + "∪": true, + "⩈": true, + "⩆": true, + "⩊": true, + "⊍": true, + "⩅": true, + "∪︀": true, + "↷": true, + "⤼": true, + "⋞": true, + "⋟": true, + "⋎": true, + "⋏": true, + "¤": true, + "¤": true, + "↶": true, + "↷": true, + "⋎": true, + "⋏": true, + "∲": true, + "∱": true, + "⌭": true, + "⇓": true, + "⥥": true, + "†": true, + "ℸ": true, + "↓": true, + "‐": true, + "⊣": true, + "⤏": true, + "˝": true, + "ď": true, + "д": true, + "ⅆ": true, + "‡": true, + "⇊": true, + "⩷": true, + "°": true, + "°": true, + "δ": true, + "⦱": true, + "⥿": true, + "𝔡": true, + "⇃": true, + "⇂": true, + "⋄": true, + "⋄": true, + "♦": true, + "♦": true, + "¨": true, + "ϝ": true, + "⋲": true, + "÷": true, + "÷": true, + "÷": true, + "⋇": true, + "⋇": true, + "ђ": true, + "⌞": true, + "⌍": true, + "$": true, + "𝕕": true, + "˙": true, + "≐": true, + "≑": true, + "∸": true, + "∔": true, + "⊡": true, + "⌆": true, + "↓": true, + "⇊": true, + "⇃": true, + "⇂": true, + "⤐": true, + "⌟": true, + "⌌": true, + "𝒹": true, + "ѕ": true, + "⧶": true, + "đ": true, + "⋱": true, + "▿": true, + "▾": true, + "⇵": true, + "⥯": true, + "⦦": true, + "џ": true, + "⟿": true, + "⩷": true, + "≑": true, + "é": true, + "é": true, + "⩮": true, + "ě": true, + "≖": true, + "ê": true, + "ê": true, + "≕": true, + "э": true, + "ė": true, + "ⅇ": true, + "≒": true, + "𝔢": true, + "⪚": true, + "è": true, + "è": true, + "⪖": true, + "⪘": true, + "⪙": true, + "⏧": true, + "ℓ": true, + "⪕": true, + "⪗": true, + "ē": true, + "∅": true, + "∅": true, + "∅": true, + " ": true, + " ": true, + " ": true, + "ŋ": true, + " ": true, + "ę": true, + "𝕖": true, + "⋕": true, + "⧣": true, + "⩱": true, + "ε": true, + "ε": true, + "ϵ": true, + "≖": true, + "≕": true, + "≂": true, + "⪖": true, + "⪕": true, + "=": true, + "≟": true, + "≡": true, + "⩸": true, + "⧥": true, + "≓": true, + "⥱": true, + "ℯ": true, + "≐": true, + "≂": true, + "η": true, + "ð": true, + "ð": true, + "ë": true, + "ë": true, + "€": true, + "!": true, + "∃": true, + "ℰ": true, + "ⅇ": true, + "≒": true, + "ф": true, + "♀": true, + "ffi": true, + "ff": true, + "ffl": true, + "𝔣": true, + "fi": true, + "fj": true, + "♭": true, + "fl": true, + "▱": true, + "ƒ": true, + "𝕗": true, + "∀": true, + "⋔": true, + "⫙": true, + "⨍": true, + "½": true, + "½": true, + "⅓": true, + "¼": true, + "¼": true, + "⅕": true, + "⅙": true, + "⅛": true, + "⅔": true, + "⅖": true, + "¾": true, + "¾": true, + "⅗": true, + "⅜": true, + "⅘": true, + "⅚": true, + "⅝": true, + "⅞": true, + "⁄": true, + "⌢": true, + "𝒻": true, + "≧": true, + "⪌": true, + "ǵ": true, + "γ": true, + "ϝ": true, + "⪆": true, + "ğ": true, + "ĝ": true, + "г": true, + "ġ": true, + "≥": true, + "⋛": true, + "≥": true, + "≧": true, + "⩾": true, + "⩾": true, + "⪩": true, + "⪀": true, + "⪂": true, + "⪄": true, + "⋛︀": true, + "⪔": true, + "𝔤": true, + "≫": true, + "⋙": true, + "ℷ": true, + "ѓ": true, + "≷": true, + "⪒": true, + "⪥": true, + "⪤": true, + "≩": true, + "⪊": true, + "⪊": true, + "⪈": true, + "⪈": true, + "≩": true, + "⋧": true, + "𝕘": true, + "`": true, + "ℊ": true, + "≳": true, + "⪎": true, + "⪐": true, + ">": true, + ">": true, + "⪧": true, + "⩺": true, + "⋗": true, + "⦕": true, + "⩼": true, + "⪆": true, + "⥸": true, + "⋗": true, + "⋛": true, + "⪌": true, + "≷": true, + "≳": true, + "≩︀": true, + "≩︀": true, + "⇔": true, + " ": true, + "½": true, + "ℋ": true, + "ъ": true, + "↔": true, + "⥈": true, + "↭": true, + "ℏ": true, + "ĥ": true, + "♥": true, + "♥": true, + "…": true, + "⊹": true, + "𝔥": true, + "⤥": true, + "⤦": true, + "⇿": true, + "∻": true, + "↩": true, + "↪": true, + "𝕙": true, + "―": true, + "𝒽": true, + "ℏ": true, + "ħ": true, + "⁃": true, + "‐": true, + "í": true, + "í": true, + "⁣": true, + "î": true, + "î": true, + "и": true, + "е": true, + "¡": true, + "¡": true, + "⇔": true, + "𝔦": true, + "ì": true, + "ì": true, + "ⅈ": true, + "⨌": true, + "∭": true, + "⧜": true, + "℩": true, + "ij": true, + "ī": true, + "ℑ": true, + "ℐ": true, + "ℑ": true, + "ı": true, + "⊷": true, + "Ƶ": true, + "∈": true, + "℅": true, + "∞": true, + "⧝": true, + "ı": true, + "∫": true, + "⊺": true, + "ℤ": true, + "⊺": true, + "⨗": true, + "⨼": true, + "ё": true, + "į": true, + "𝕚": true, + "ι": true, + "⨼": true, + "¿": true, + "¿": true, + "𝒾": true, + "∈": true, + "⋹": true, + "⋵": true, + "⋴": true, + "⋳": true, + "∈": true, + "⁢": true, + "ĩ": true, + "і": true, + "ï": true, + "ï": true, + "ĵ": true, + "й": true, + "𝔧": true, + "ȷ": true, + "𝕛": true, + "𝒿": true, + "ј": true, + "є": true, + "κ": true, + "ϰ": true, + "ķ": true, + "к": true, + "𝔨": true, + "ĸ": true, + "х": true, + "ќ": true, + "𝕜": true, + "𝓀": true, + "⇚": true, + "⇐": true, + "⤛": true, + "⤎": true, + "≦": true, + "⪋": true, + "⥢": true, + "ĺ": true, + "⦴": true, + "ℒ": true, + "λ": true, + "⟨": true, + "⦑": true, + "⟨": true, + "⪅": true, + "«": true, + "«": true, + "←": true, + "⇤": true, + "⤟": true, + "⤝": true, + "↩": true, + "↫": true, + "⤹": true, + "⥳": true, + "↢": true, + "⪫": true, + "⤙": true, + "⪭": true, + "⪭︀": true, + "⤌": true, + "❲": true, + "{": true, + "[": true, + "⦋": true, + "⦏": true, + "⦍": true, + "ľ": true, + "ļ": true, + "⌈": true, + "{": true, + "л": true, + "⤶": true, + "“": true, + "„": true, + "⥧": true, + "⥋": true, + "↲": true, + "≤": true, + "←": true, + "↢": true, + "↽": true, + "↼": true, + "⇇": true, + "↔": true, + "⇆": true, + "⇋": true, + "↭": true, + "⋋": true, + "⋚": true, + "≤": true, + "≦": true, + "⩽": true, + "⩽": true, + "⪨": true, + "⩿": true, + "⪁": true, + "⪃": true, + "⋚︀": true, + "⪓": true, + "⪅": true, + "⋖": true, + "⋚": true, + "⪋": true, + "≶": true, + "≲": true, + "⥼": true, + "⌊": true, + "𝔩": true, + "≶": true, + "⪑": true, + "↽": true, + "↼": true, + "⥪": true, + "▄": true, + "љ": true, + "≪": true, + "⇇": true, + "⌞": true, + "⥫": true, + "◺": true, + "ŀ": true, + "⎰": true, + "⎰": true, + "≨": true, + "⪉": true, + "⪉": true, + "⪇": true, + "⪇": true, + "≨": true, + "⋦": true, + "⟬": true, + "⇽": true, + "⟦": true, + "⟵": true, + "⟷": true, + "⟼": true, + "⟶": true, + "↫": true, + "↬": true, + "⦅": true, + "𝕝": true, + "⨭": true, + "⨴": true, + "∗": true, + "_": true, + "◊": true, + "◊": true, + "⧫": true, + "(": true, + "⦓": true, + "⇆": true, + "⌟": true, + "⇋": true, + "⥭": true, + "‎": true, + "⊿": true, + "‹": true, + "𝓁": true, + "↰": true, + "≲": true, + "⪍": true, + "⪏": true, + "[": true, + "‘": true, + "‚": true, + "ł": true, + "<": true, + "<": true, + "⪦": true, + "⩹": true, + "⋖": true, + "⋋": true, + "⋉": true, + "⥶": true, + "⩻": true, + "⦖": true, + "◃": true, + "⊴": true, + "◂": true, + "⥊": true, + "⥦": true, + "≨︀": true, + "≨︀": true, + "∺": true, + "¯": true, + "¯": true, + "♂": true, + "✠": true, + "✠": true, + "↦": true, + "↦": true, + "↧": true, + "↤": true, + "↥": true, + "▮": true, + "⨩": true, + "м": true, + "—": true, + "∡": true, + "𝔪": true, + "℧": true, + "µ": true, + "µ": true, + "∣": true, + "*": true, + "⫰": true, + "·": true, + "·": true, + "−": true, + "⊟": true, + "∸": true, + "⨪": true, + "⫛": true, + "…": true, + "∓": true, + "⊧": true, + "𝕞": true, + "∓": true, + "𝓂": true, + "∾": true, + "μ": true, + "⊸": true, + "⊸": true, + "⋙̸": true, + "≫⃒": true, + "≫̸": true, + "⇍": true, + "⇎": true, + "⋘̸": true, + "≪⃒": true, + "≪̸": true, + "⇏": true, + "⊯": true, + "⊮": true, + "∇": true, + "ń": true, + "∠⃒": true, + "≉": true, + "⩰̸": true, + "≋̸": true, + "ʼn": true, + "≉": true, + "♮": true, + "♮": true, + "ℕ": true, + " ": true, + " ": true, + "≎̸": true, + "≏̸": true, + "⩃": true, + "ň": true, + "ņ": true, + "≇": true, + "⩭̸": true, + "⩂": true, + "н": true, + "–": true, + "≠": true, + "⇗": true, + "⤤": true, + "↗": true, + "↗": true, + "≐̸": true, + "≢": true, + "⤨": true, + "≂̸": true, + "∄": true, + "∄": true, + "𝔫": true, + "≧̸": true, + "≱": true, + "≱": true, + "≧̸": true, + "⩾̸": true, + "⩾̸": true, + "≵": true, + "≯": true, + "≯": true, + "⇎": true, + "↮": true, + "⫲": true, + "∋": true, + "⋼": true, + "⋺": true, + "∋": true, + "њ": true, + "⇍": true, + "≦̸": true, + "↚": true, + "‥": true, + "≰": true, + "↚": true, + "↮": true, + "≰": true, + "≦̸": true, + "⩽̸": true, + "⩽̸": true, + "≮": true, + "≴": true, + "≮": true, + "⋪": true, + "⋬": true, + "∤": true, + "𝕟": true, + "¬": true, + "¬": true, + "∉": true, + "⋹̸": true, + "⋵̸": true, + "∉": true, + "⋷": true, + "⋶": true, + "∌": true, + "∌": true, + "⋾": true, + "⋽": true, + "∦": true, + "∦": true, + "⫽⃥": true, + "∂̸": true, + "⨔": true, + "⊀": true, + "⋠": true, + "⪯̸": true, + "⊀": true, + "⪯̸": true, + "⇏": true, + "↛": true, + "⤳̸": true, + "↝̸": true, + "↛": true, + "⋫": true, + "⋭": true, + "⊁": true, + "⋡": true, + "⪰̸": true, + "𝓃": true, + "∤": true, + "∦": true, + "≁": true, + "≄": true, + "≄": true, + "∤": true, + "∦": true, + "⋢": true, + "⋣": true, + "⊄": true, + "⫅̸": true, + "⊈": true, + "⊂⃒": true, + "⊈": true, + "⫅̸": true, + "⊁": true, + "⪰̸": true, + "⊅": true, + "⫆̸": true, + "⊉": true, + "⊃⃒": true, + "⊉": true, + "⫆̸": true, + "≹": true, + "ñ": true, + "ñ": true, + "≸": true, + "⋪": true, + "⋬": true, + "⋫": true, + "⋭": true, + "ν": true, + "#": true, + "№": true, + " ": true, + "⊭": true, + "⤄": true, + "≍⃒": true, + "⊬": true, + "≥⃒": true, + ">⃒": true, + "⧞": true, + "⤂": true, + "≤⃒": true, + "<⃒": true, + "⊴⃒": true, + "⤃": true, + "⊵⃒": true, + "∼⃒": true, + "⇖": true, + "⤣": true, + "↖": true, + "↖": true, + "⤧": true, + "Ⓢ": true, + "ó": true, + "ó": true, + "⊛": true, + "⊚": true, + "ô": true, + "ô": true, + "о": true, + "⊝": true, + "ő": true, + "⨸": true, + "⊙": true, + "⦼": true, + "œ": true, + "⦿": true, + "𝔬": true, + "˛": true, + "ò": true, + "ò": true, + "⧁": true, + "⦵": true, + "Ω": true, + "∮": true, + "↺": true, + "⦾": true, + "⦻": true, + "‾": true, + "⧀": true, + "ō": true, + "ω": true, + "ο": true, + "⦶": true, + "⊖": true, + "𝕠": true, + "⦷": true, + "⦹": true, + "⊕": true, + "∨": true, + "↻": true, + "⩝": true, + "ℴ": true, + "ℴ": true, + "ª": true, + "ª": true, + "º": true, + "º": true, + "⊶": true, + "⩖": true, + "⩗": true, + "⩛": true, + "ℴ": true, + "ø": true, + "ø": true, + "⊘": true, + "õ": true, + "õ": true, + "⊗": true, + "⨶": true, + "ö": true, + "ö": true, + "⌽": true, + "∥": true, + "¶": true, + "¶": true, + "∥": true, + "⫳": true, + "⫽": true, + "∂": true, + "п": true, + "%": true, + ".": true, + "‰": true, + "⊥": true, + "‱": true, + "𝔭": true, + "φ": true, + "ϕ": true, + "ℳ": true, + "☎": true, + "π": true, + "⋔": true, + "ϖ": true, + "ℏ": true, + "ℎ": true, + "ℏ": true, + "+": true, + "⨣": true, + "⊞": true, + "⨢": true, + "∔": true, + "⨥": true, + "⩲": true, + "±": true, + "±": true, + "⨦": true, + "⨧": true, + "±": true, + "⨕": true, + "𝕡": true, + "£": true, + "£": true, + "≺": true, + "⪳": true, + "⪷": true, + "≼": true, + "⪯": true, + "≺": true, + "⪷": true, + "≼": true, + "⪯": true, + "⪹": true, + "⪵": true, + "⋨": true, + "≾": true, + "′": true, + "ℙ": true, + "⪵": true, + "⪹": true, + "⋨": true, + "∏": true, + "⌮": true, + "⌒": true, + "⌓": true, + "∝": true, + "∝": true, + "≾": true, + "⊰": true, + "𝓅": true, + "ψ": true, + " ": true, + "𝔮": true, + "⨌": true, + "𝕢": true, + "⁗": true, + "𝓆": true, + "ℍ": true, + "⨖": true, + "?": true, + "≟": true, + """: true, + """: true, + "⇛": true, + "⇒": true, + "⤜": true, + "⤏": true, + "⥤": true, + "∽̱": true, + "ŕ": true, + "√": true, + "⦳": true, + "⟩": true, + "⦒": true, + "⦥": true, + "⟩": true, + "»": true, + "»": true, + "→": true, + "⥵": true, + "⇥": true, + "⤠": true, + "⤳": true, + "⤞": true, + "↪": true, + "↬": true, + "⥅": true, + "⥴": true, + "↣": true, + "↝": true, + "⤚": true, + "∶": true, + "ℚ": true, + "⤍": true, + "❳": true, + "}": true, + "]": true, + "⦌": true, + "⦎": true, + "⦐": true, + "ř": true, + "ŗ": true, + "⌉": true, + "}": true, + "р": true, + "⤷": true, + "⥩": true, + "”": true, + "”": true, + "↳": true, + "ℜ": true, + "ℛ": true, + "ℜ": true, + "ℝ": true, + "▭": true, + "®": true, + "®": true, + "⥽": true, + "⌋": true, + "𝔯": true, + "⇁": true, + "⇀": true, + "⥬": true, + "ρ": true, + "ϱ": true, + "→": true, + "↣": true, + "⇁": true, + "⇀": true, + "⇄": true, + "⇌": true, + "⇉": true, + "↝": true, + "⋌": true, + "˚": true, + "≓": true, + "⇄": true, + "⇌": true, + "‏": true, + "⎱": true, + "⎱": true, + "⫮": true, + "⟭": true, + "⇾": true, + "⟧": true, + "⦆": true, + "𝕣": true, + "⨮": true, + "⨵": true, + ")": true, + "⦔": true, + "⨒": true, + "⇉": true, + "›": true, + "𝓇": true, + "↱": true, + "]": true, + "’": true, + "’": true, + "⋌": true, + "⋊": true, + "▹": true, + "⊵": true, + "▸": true, + "⧎": true, + "⥨": true, + "℞": true, + "ś": true, + "‚": true, + "≻": true, + "⪴": true, + "⪸": true, + "š": true, + "≽": true, + "⪰": true, + "ş": true, + "ŝ": true, + "⪶": true, + "⪺": true, + "⋩": true, + "⨓": true, + "≿": true, + "с": true, + "⋅": true, + "⊡": true, + "⩦": true, + "⇘": true, + "⤥": true, + "↘": true, + "↘": true, + "§": true, + "§": true, + ";": true, + "⤩": true, + "∖": true, + "∖": true, + "✶": true, + "𝔰": true, + "⌢": true, + "♯": true, + "щ": true, + "ш": true, + "∣": true, + "∥": true, + "­": true, + "­": true, + "σ": true, + "ς": true, + "ς": true, + "∼": true, + "⩪": true, + "≃": true, + "≃": true, + "⪞": true, + "⪠": true, + "⪝": true, + "⪟": true, + "≆": true, + "⨤": true, + "⥲": true, + "←": true, + "∖": true, + "⨳": true, + "⧤": true, + "∣": true, + "⌣": true, + "⪪": true, + "⪬": true, + "⪬︀": true, + "ь": true, + "/": true, + "⧄": true, + "⌿": true, + "𝕤": true, + "♠": true, + "♠": true, + "∥": true, + "⊓": true, + "⊓︀": true, + "⊔": true, + "⊔︀": true, + "⊏": true, + "⊑": true, + "⊏": true, + "⊑": true, + "⊐": true, + "⊒": true, + "⊐": true, + "⊒": true, + "□": true, + "□": true, + "▪": true, + "▪": true, + "→": true, + "𝓈": true, + "∖": true, + "⌣": true, + "⋆": true, + "☆": true, + "★": true, + "ϵ": true, + "ϕ": true, + "¯": true, + "⊂": true, + "⫅": true, + "⪽": true, + "⊆": true, + "⫃": true, + "⫁": true, + "⫋": true, + "⊊": true, + "⪿": true, + "⥹": true, + "⊂": true, + "⊆": true, + "⫅": true, + "⊊": true, + "⫋": true, + "⫇": true, + "⫕": true, + "⫓": true, + "≻": true, + "⪸": true, + "≽": true, + "⪰": true, + "⪺": true, + "⪶": true, + "⋩": true, + "≿": true, + "∑": true, + "♪": true, + "¹": true, + "¹": true, + "²": true, + "²": true, + "³": true, + "³": true, + "⊃": true, + "⫆": true, + "⪾": true, + "⫘": true, + "⊇": true, + "⫄": true, + "⟉": true, + "⫗": true, + "⥻": true, + "⫂": true, + "⫌": true, + "⊋": true, + "⫀": true, + "⊃": true, + "⊇": true, + "⫆": true, + "⊋": true, + "⫌": true, + "⫈": true, + "⫔": true, + "⫖": true, + "⇙": true, + "⤦": true, + "↙": true, + "↙": true, + "⤪": true, + "ß": true, + "ß": true, + "⌖": true, + "τ": true, + "⎴": true, + "ť": true, + "ţ": true, + "т": true, + "⃛": true, + "⌕": true, + "𝔱": true, + "∴": true, + "∴": true, + "θ": true, + "ϑ": true, + "ϑ": true, + "≈": true, + "∼": true, + " ": true, + "≈": true, + "∼": true, + "þ": true, + "þ": true, + "˜": true, + "×": true, + "×": true, + "⊠": true, + "⨱": true, + "⨰": true, + "∭": true, + "⤨": true, + "⊤": true, + "⌶": true, + "⫱": true, + "𝕥": true, + "⫚": true, + "⤩": true, + "‴": true, + "™": true, + "▵": true, + "▿": true, + "◃": true, + "⊴": true, + "≜": true, + "▹": true, + "⊵": true, + "◬": true, + "≜": true, + "⨺": true, + "⨹": true, + "⧍": true, + "⨻": true, + "⏢": true, + "𝓉": true, + "ц": true, + "ћ": true, + "ŧ": true, + "≬": true, + "↞": true, + "↠": true, + "⇑": true, + "⥣": true, + "ú": true, + "ú": true, + "↑": true, + "ў": true, + "ŭ": true, + "û": true, + "û": true, + "у": true, + "⇅": true, + "ű": true, + "⥮": true, + "⥾": true, + "𝔲": true, + "ù": true, + "ù": true, + "↿": true, + "↾": true, + "▀": true, + "⌜": true, + "⌜": true, + "⌏": true, + "◸": true, + "ū": true, + "¨": true, + "¨": true, + "ų": true, + "𝕦": true, + "↑": true, + "↕": true, + "↿": true, + "↾": true, + "⊎": true, + "υ": true, + "ϒ": true, + "υ": true, + "⇈": true, + "⌝": true, + "⌝": true, + "⌎": true, + "ů": true, + "◹": true, + "𝓊": true, + "⋰": true, + "ũ": true, + "▵": true, + "▴": true, + "⇈": true, + "ü": true, + "ü": true, + "⦧": true, + "⇕": true, + "⫨": true, + "⫩": true, + "⊨": true, + "⦜": true, + "ϵ": true, + "ϰ": true, + "∅": true, + "ϕ": true, + "ϖ": true, + "∝": true, + "↕": true, + "ϱ": true, + "ς": true, + "⊊︀": true, + "⫋︀": true, + "⊋︀": true, + "⫌︀": true, + "ϑ": true, + "⊲": true, + "⊳": true, + "в": true, + "⊢": true, + "∨": true, + "⊻": true, + "≚": true, + "⋮": true, + "|": true, + "|": true, + "𝔳": true, + "⊲": true, + "⊂⃒": true, + "⊃⃒": true, + "𝕧": true, + "∝": true, + "⊳": true, + "𝓋": true, + "⫋︀": true, + "⊊︀": true, + "⫌︀": true, + "⊋︀": true, + "⦚": true, + "ŵ": true, + "⩟": true, + "∧": true, + "≙": true, + "℘": true, + "𝔴": true, + "𝕨": true, + "℘": true, + "≀": true, + "≀": true, + "𝓌": true, + "⋂": true, + "◯": true, + "⋃": true, + "▽": true, + "𝔵": true, + "⟺": true, + "⟷": true, + "ξ": true, + "⟸": true, + "⟵": true, + "⟼": true, + "⋻": true, + "⨀": true, + "𝕩": true, + "⨁": true, + "⨂": true, + "⟹": true, + "⟶": true, + "𝓍": true, + "⨆": true, + "⨄": true, + "△": true, + "⋁": true, + "⋀": true, + "ý": true, + "ý": true, + "я": true, + "ŷ": true, + "ы": true, + "¥": true, + "¥": true, + "𝔶": true, + "ї": true, + "𝕪": true, + "𝓎": true, + "ю": true, + "ÿ": true, + "ÿ": true, + "ź": true, + "ž": true, + "з": true, + "ż": true, + "ℨ": true, + "ζ": true, + "𝔷": true, + "ж": true, + "⇝": true, + "𝕫": true, + "𝓏": true, + "‍": true, + "‌": true, +} diff --git a/vendor/github.com/russross/blackfriday/v2/esc.go b/vendor/github.com/russross/blackfriday/v2/esc.go new file mode 100644 index 00000000000..6ab60102c9b --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/esc.go @@ -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)) +} diff --git a/vendor/github.com/russross/blackfriday/v2/go.mod b/vendor/github.com/russross/blackfriday/v2/go.mod new file mode 100644 index 00000000000..620b74e0ac2 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/go.mod @@ -0,0 +1 @@ +module github.com/russross/blackfriday/v2 diff --git a/vendor/github.com/russross/blackfriday/v2/html.go b/vendor/github.com/russross/blackfriday/v2/html.go new file mode 100644 index 00000000000..cb4f26e30fd --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/html.go @@ -0,0 +1,952 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// 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 = "]" + 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 = "" + declaration = "]*>" + 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 tag for a footnote return link, if the + // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string + // [return] 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,

becomes

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 = "↩\ufe0e" + } + + 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(`%d`, urlFrag, node.NoteID) + return []byte(fmt.Sprintf(`%s`, urlFrag, anchor)) +} + +func footnoteItem(prefix string, slug []byte) []byte { + return []byte(fmt.Sprintf(`
  • `, prefix, slug)) +} + +func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte { + const format = ` %s` + 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("
    ") + brXHTMLTag = []byte("
    ") + emTag = []byte("") + emCloseTag = []byte("") + strongTag = []byte("") + strongCloseTag = []byte("") + delTag = []byte("") + delCloseTag = []byte("") + ttTag = []byte("") + ttCloseTag = []byte("") + aTag = []byte("") + preTag = []byte("
    ")
    +	preCloseTag        = []byte("
    ") + codeTag = []byte("") + codeCloseTag = []byte("") + pTag = []byte("

    ") + pCloseTag = []byte("

    ") + blockquoteTag = []byte("
    ") + blockquoteCloseTag = []byte("
    ") + hrTag = []byte("
    ") + hrXHTMLTag = []byte("
    ") + ulTag = []byte("
      ") + ulCloseTag = []byte("
    ") + olTag = []byte("
      ") + olCloseTag = []byte("
    ") + dlTag = []byte("
    ") + dlCloseTag = []byte("
    ") + liTag = []byte("
  • ") + liCloseTag = []byte("
  • ") + ddTag = []byte("
    ") + ddCloseTag = []byte("
    ") + dtTag = []byte("
    ") + dtCloseTag = []byte("
    ") + tableTag = []byte("") + tableCloseTag = []byte("
    ") + tdTag = []byte("") + thTag = []byte("") + theadTag = []byte("") + theadCloseTag = []byte("") + tbodyTag = []byte("") + tbodyCloseTag = []byte("") + trTag = []byte("") + trCloseTag = []byte("") + h1Tag = []byte("") + h2Tag = []byte("") + h3Tag = []byte("") + h4Tag = []byte("") + h5Tag = []byte("") + h6Tag = []byte("") + + footnotesDivBytes = []byte("\n
    \n\n") + footnotesCloseDivBytes = []byte("\n
    \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, ``)
+				//} else {
+				r.out(w, []byte(`<img src=`)) + } + } + 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\n\n") +} + +func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) { + if r.Flags&CompletePage == 0 { + return + } + ending := "" + if r.Flags&UseXHTML != 0 { + io.WriteString(w, "\n") + io.WriteString(w, "\n") + ending = " /" + } else { + io.WriteString(w, "\n") + io.WriteString(w, "\n") + } + io.WriteString(w, "\n") + io.WriteString(w, " ") + if r.Flags&Smartypants != 0 { + r.sr.Process(w, []byte(r.Title)) + } else { + escapeHTML(w, []byte(r.Title)) + } + io.WriteString(w, "\n") + io.WriteString(w, " \n") + io.WriteString(w, " \n") + if r.CSS != "" { + io.WriteString(w, " \n") + } + if r.Icon != "" { + io.WriteString(w, " \n") + } + io.WriteString(w, "\n") + io.WriteString(w, "\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("\n\n
  • ") + } else if node.Level < tocLevel { + for node.Level < tocLevel { + tocLevel-- + buf.WriteString("
  • \n") + } + buf.WriteString("\n\n
  • ") + } else { + for node.Level > tocLevel { + tocLevel++ + buf.WriteString("\n") + } + + if buf.Len() > 0 { + io.WriteString(w, "\n") + } + r.lastOutputLen = buf.Len() +} diff --git a/vendor/github.com/russross/blackfriday/v2/inline.go b/vendor/github.com/russross/blackfriday/v2/inline.go new file mode 100644 index 00000000000..d45bd941726 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/inline.go @@ -0,0 +1,1228 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// Functions to parse inline elements. +// + +package blackfriday + +import ( + "bytes" + "regexp" + "strconv" +) + +var ( + urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+` + anchorRe = regexp.MustCompile(`^(]+")?\s?>` + urlRe + `<\/a>)`) + + // https://www.w3.org/TR/html5/syntax.html#character-references + // highest unicode code point in 17 planes (2^20): 1,114,112d = + // 7 dec digits or 6 hex digits + // named entity references can be 2-31 characters with stuff like < + // at one end and ∳ at the other. There + // are also sometimes numbers at the end, although this isn't inherent + // in the specification; there are never numbers anywhere else in + // current character references, though; see ¾ and ▒, etc. + // https://www.w3.org/TR/html5/syntax.html#named-character-references + // + // entity := "&" (named group | number ref) ";" + // named group := [a-zA-Z]{2,31}[0-9]{0,2} + // number ref := "#" (dec ref | hex ref) + // dec ref := [0-9]{1,7} + // hex ref := ("x" | "X") [0-9a-fA-F]{1,6} + htmlEntityRe = regexp.MustCompile(`&([a-zA-Z]{2,31}[0-9]{0,2}|#([0-9]{1,7}|[xX][0-9a-fA-F]{1,6}));`) +) + +// Functions to parse text within a block +// Each function returns the number of chars taken care of +// data is the complete block being rendered +// offset is the number of valid chars before the current cursor + +func (p *Markdown) inline(currBlock *Node, data []byte) { + // handlers might call us recursively: enforce a maximum depth + if p.nesting >= p.maxNesting || len(data) == 0 { + return + } + p.nesting++ + beg, end := 0, 0 + for end < len(data) { + handler := p.inlineCallback[data[end]] + if handler != nil { + if consumed, node := handler(p, data, end); consumed == 0 { + // No action from the callback. + end++ + } else { + // Copy inactive chars into the output. + currBlock.AppendChild(text(data[beg:end])) + if node != nil { + currBlock.AppendChild(node) + } + // Skip past whatever the callback used. + beg = end + consumed + end = beg + } + } else { + end++ + } + } + if beg < len(data) { + if data[end-1] == '\n' { + end-- + } + currBlock.AppendChild(text(data[beg:end])) + } + p.nesting-- +} + +// single and double emphasis parsing +func emphasis(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + c := data[0] + + if len(data) > 2 && data[1] != c { + // whitespace cannot follow an opening emphasis; + // strikethrough only takes two characters '~~' + if c == '~' || isspace(data[1]) { + return 0, nil + } + ret, node := helperEmphasis(p, data[1:], c) + if ret == 0 { + return 0, nil + } + + return ret + 1, node + } + + if len(data) > 3 && data[1] == c && data[2] != c { + if isspace(data[2]) { + return 0, nil + } + ret, node := helperDoubleEmphasis(p, data[2:], c) + if ret == 0 { + return 0, nil + } + + return ret + 2, node + } + + if len(data) > 4 && data[1] == c && data[2] == c && data[3] != c { + if c == '~' || isspace(data[3]) { + return 0, nil + } + ret, node := helperTripleEmphasis(p, data, 3, c) + if ret == 0 { + return 0, nil + } + + return ret + 3, node + } + + return 0, nil +} + +func codeSpan(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + + nb := 0 + + // count the number of backticks in the delimiter + for nb < len(data) && data[nb] == '`' { + nb++ + } + + // find the next delimiter + i, end := 0, 0 + for end = nb; end < len(data) && i < nb; end++ { + if data[end] == '`' { + i++ + } else { + i = 0 + } + } + + // no matching delimiter? + if i < nb && end >= len(data) { + return 0, nil + } + + // trim outside whitespace + fBegin := nb + for fBegin < end && data[fBegin] == ' ' { + fBegin++ + } + + fEnd := end - nb + for fEnd > fBegin && data[fEnd-1] == ' ' { + fEnd-- + } + + // render the code span + if fBegin != fEnd { + code := NewNode(Code) + code.Literal = data[fBegin:fEnd] + return end, code + } + + return end, nil +} + +// newline preceded by two spaces becomes
    +func maybeLineBreak(p *Markdown, data []byte, offset int) (int, *Node) { + origOffset := offset + for offset < len(data) && data[offset] == ' ' { + offset++ + } + + if offset < len(data) && data[offset] == '\n' { + if offset-origOffset >= 2 { + return offset - origOffset + 1, NewNode(Hardbreak) + } + return offset - origOffset, nil + } + return 0, nil +} + +// newline without two spaces works when HardLineBreak is enabled +func lineBreak(p *Markdown, data []byte, offset int) (int, *Node) { + if p.extensions&HardLineBreak != 0 { + return 1, NewNode(Hardbreak) + } + return 0, nil +} + +type linkType int + +const ( + linkNormal linkType = iota + linkImg + linkDeferredFootnote + linkInlineFootnote +) + +func isReferenceStyleLink(data []byte, pos int, t linkType) bool { + if t == linkDeferredFootnote { + return false + } + return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^' +} + +func maybeImage(p *Markdown, data []byte, offset int) (int, *Node) { + if offset < len(data)-1 && data[offset+1] == '[' { + return link(p, data, offset) + } + return 0, nil +} + +func maybeInlineFootnote(p *Markdown, data []byte, offset int) (int, *Node) { + if offset < len(data)-1 && data[offset+1] == '[' { + return link(p, data, offset) + } + return 0, nil +} + +// '[': parse a link or an image or a footnote +func link(p *Markdown, data []byte, offset int) (int, *Node) { + // no links allowed inside regular links, footnote, and deferred footnotes + if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') { + return 0, nil + } + + var t linkType + switch { + // special case: ![^text] == deferred footnote (that follows something with + // an exclamation point) + case p.extensions&Footnotes != 0 && len(data)-1 > offset && data[offset+1] == '^': + t = linkDeferredFootnote + // ![alt] == image + case offset >= 0 && data[offset] == '!': + t = linkImg + offset++ + // ^[text] == inline footnote + // [^refId] == deferred footnote + case p.extensions&Footnotes != 0: + if offset >= 0 && data[offset] == '^' { + t = linkInlineFootnote + offset++ + } else if len(data)-1 > offset && data[offset+1] == '^' { + t = linkDeferredFootnote + } + // [text] == regular link + default: + t = linkNormal + } + + data = data[offset:] + + var ( + i = 1 + noteID int + title, link, altContent []byte + textHasNl = false + ) + + if t == linkDeferredFootnote { + i++ + } + + // look for the matching closing bracket + for level := 1; level > 0 && i < len(data); i++ { + switch { + case data[i] == '\n': + textHasNl = true + + case isBackslashEscaped(data, i): + continue + + case data[i] == '[': + level++ + + case data[i] == ']': + level-- + if level <= 0 { + i-- // compensate for extra i++ in for loop + } + } + } + + if i >= len(data) { + return 0, nil + } + + txtE := i + i++ + var footnoteNode *Node + + // skip any amount of whitespace or newline + // (this is much more lax than original markdown syntax) + for i < len(data) && isspace(data[i]) { + i++ + } + + // inline style link + switch { + case i < len(data) && data[i] == '(': + // skip initial whitespace + i++ + + for i < len(data) && isspace(data[i]) { + i++ + } + + linkB := i + + // look for link end: ' " ) + findlinkend: + for i < len(data) { + switch { + case data[i] == '\\': + i += 2 + + case data[i] == ')' || data[i] == '\'' || data[i] == '"': + break findlinkend + + default: + i++ + } + } + + if i >= len(data) { + return 0, nil + } + linkE := i + + // look for title end if present + titleB, titleE := 0, 0 + if data[i] == '\'' || data[i] == '"' { + i++ + titleB = i + + findtitleend: + for i < len(data) { + switch { + case data[i] == '\\': + i += 2 + + case data[i] == ')': + break findtitleend + + default: + i++ + } + } + + if i >= len(data) { + return 0, nil + } + + // skip whitespace after title + titleE = i - 1 + for titleE > titleB && isspace(data[titleE]) { + titleE-- + } + + // check for closing quote presence + if data[titleE] != '\'' && data[titleE] != '"' { + titleB, titleE = 0, 0 + linkE = i + } + } + + // remove whitespace at the end of the link + for linkE > linkB && isspace(data[linkE-1]) { + linkE-- + } + + // remove optional angle brackets around the link + if data[linkB] == '<' { + linkB++ + } + if data[linkE-1] == '>' { + linkE-- + } + + // build escaped link and title + if linkE > linkB { + link = data[linkB:linkE] + } + + if titleE > titleB { + title = data[titleB:titleE] + } + + i++ + + // reference style link + case isReferenceStyleLink(data, i, t): + var id []byte + altContentConsidered := false + + // look for the id + i++ + linkB := i + for i < len(data) && data[i] != ']' { + i++ + } + if i >= len(data) { + return 0, nil + } + linkE := i + + // find the reference + if linkB == linkE { + if textHasNl { + var b bytes.Buffer + + for j := 1; j < txtE; j++ { + switch { + case data[j] != '\n': + b.WriteByte(data[j]) + case data[j-1] != ' ': + b.WriteByte(' ') + } + } + + id = b.Bytes() + } else { + id = data[1:txtE] + altContentConsidered = true + } + } else { + id = data[linkB:linkE] + } + + // find the reference with matching id + lr, ok := p.getRef(string(id)) + if !ok { + return 0, nil + } + + // keep link and title from reference + link = lr.link + title = lr.title + if altContentConsidered { + altContent = lr.text + } + i++ + + // shortcut reference style link or reference or inline footnote + default: + var id []byte + + // craft the id + if textHasNl { + var b bytes.Buffer + + for j := 1; j < txtE; j++ { + switch { + case data[j] != '\n': + b.WriteByte(data[j]) + case data[j-1] != ' ': + b.WriteByte(' ') + } + } + + id = b.Bytes() + } else { + if t == linkDeferredFootnote { + id = data[2:txtE] // get rid of the ^ + } else { + id = data[1:txtE] + } + } + + footnoteNode = NewNode(Item) + if t == linkInlineFootnote { + // create a new reference + noteID = len(p.notes) + 1 + + var fragment []byte + if len(id) > 0 { + if len(id) < 16 { + fragment = make([]byte, len(id)) + } else { + fragment = make([]byte, 16) + } + copy(fragment, slugify(id)) + } else { + fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteID))...) + } + + ref := &reference{ + noteID: noteID, + hasBlock: false, + link: fragment, + title: id, + footnote: footnoteNode, + } + + p.notes = append(p.notes, ref) + + link = ref.link + title = ref.title + } else { + // find the reference with matching id + lr, ok := p.getRef(string(id)) + if !ok { + return 0, nil + } + + if t == linkDeferredFootnote { + lr.noteID = len(p.notes) + 1 + lr.footnote = footnoteNode + p.notes = append(p.notes, lr) + } + + // keep link and title from reference + link = lr.link + // if inline footnote, title == footnote contents + title = lr.title + noteID = lr.noteID + } + + // rewind the whitespace + i = txtE + 1 + } + + var uLink []byte + if t == linkNormal || t == linkImg { + if len(link) > 0 { + var uLinkBuf bytes.Buffer + unescapeText(&uLinkBuf, link) + uLink = uLinkBuf.Bytes() + } + + // links need something to click on and somewhere to go + if len(uLink) == 0 || (t == linkNormal && txtE <= 1) { + return 0, nil + } + } + + // call the relevant rendering function + var linkNode *Node + switch t { + case linkNormal: + linkNode = NewNode(Link) + linkNode.Destination = normalizeURI(uLink) + linkNode.Title = title + if len(altContent) > 0 { + linkNode.AppendChild(text(altContent)) + } else { + // links cannot contain other links, so turn off link parsing + // temporarily and recurse + insideLink := p.insideLink + p.insideLink = true + p.inline(linkNode, data[1:txtE]) + p.insideLink = insideLink + } + + case linkImg: + linkNode = NewNode(Image) + linkNode.Destination = uLink + linkNode.Title = title + linkNode.AppendChild(text(data[1:txtE])) + i++ + + case linkInlineFootnote, linkDeferredFootnote: + linkNode = NewNode(Link) + linkNode.Destination = link + linkNode.Title = title + linkNode.NoteID = noteID + linkNode.Footnote = footnoteNode + if t == linkInlineFootnote { + i++ + } + + default: + return 0, nil + } + + return i, linkNode +} + +func (p *Markdown) inlineHTMLComment(data []byte) int { + if len(data) < 5 { + return 0 + } + if data[0] != '<' || data[1] != '!' || data[2] != '-' || data[3] != '-' { + return 0 + } + i := 5 + // scan for an end-of-comment marker, across lines if necessary + for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') { + i++ + } + // no end-of-comment marker + if i >= len(data) { + return 0 + } + return i + 1 +} + +func stripMailto(link []byte) []byte { + if bytes.HasPrefix(link, []byte("mailto://")) { + return link[9:] + } else if bytes.HasPrefix(link, []byte("mailto:")) { + return link[7:] + } else { + return link + } +} + +// autolinkType specifies a kind of autolink that gets detected. +type autolinkType int + +// These are the possible flag values for the autolink renderer. +const ( + notAutolink autolinkType = iota + normalAutolink + emailAutolink +) + +// '<' when tags or autolinks are allowed +func leftAngle(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + altype, end := tagLength(data) + if size := p.inlineHTMLComment(data); size > 0 { + end = size + } + if end > 2 { + if altype != notAutolink { + var uLink bytes.Buffer + unescapeText(&uLink, data[1:end+1-2]) + if uLink.Len() > 0 { + link := uLink.Bytes() + node := NewNode(Link) + node.Destination = link + if altype == emailAutolink { + node.Destination = append([]byte("mailto:"), link...) + } + node.AppendChild(text(stripMailto(link))) + return end, node + } + } else { + htmlTag := NewNode(HTMLSpan) + htmlTag.Literal = data[:end] + return end, htmlTag + } + } + + return end, nil +} + +// '\\' backslash escape +var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~") + +func escape(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + + if len(data) > 1 { + if p.extensions&BackslashLineBreak != 0 && data[1] == '\n' { + return 2, NewNode(Hardbreak) + } + if bytes.IndexByte(escapeChars, data[1]) < 0 { + return 0, nil + } + + return 2, text(data[1:2]) + } + + return 2, nil +} + +func unescapeText(ob *bytes.Buffer, src []byte) { + i := 0 + for i < len(src) { + org := i + for i < len(src) && src[i] != '\\' { + i++ + } + + if i > org { + ob.Write(src[org:i]) + } + + if i+1 >= len(src) { + break + } + + ob.WriteByte(src[i+1]) + i += 2 + } +} + +// '&' escaped when it doesn't belong to an entity +// valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; +func entity(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + + end := 1 + + if end < len(data) && data[end] == '#' { + end++ + } + + for end < len(data) && isalnum(data[end]) { + end++ + } + + if end < len(data) && data[end] == ';' { + end++ // real entity + } else { + return 0, nil // lone '&' + } + + ent := data[:end] + // undo & escaping or it will be converted to &amp; by another + // escaper in the renderer + if bytes.Equal(ent, []byte("&")) { + ent = []byte{'&'} + } + + return end, text(ent) +} + +func linkEndsWithEntity(data []byte, linkEnd int) bool { + entityRanges := htmlEntityRe.FindAllIndex(data[:linkEnd], -1) + return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd +} + +// hasPrefixCaseInsensitive is a custom implementation of +// strings.HasPrefix(strings.ToLower(s), prefix) +// we rolled our own because ToLower pulls in a huge machinery of lowercasing +// anything from Unicode and that's very slow. Since this func will only be +// used on ASCII protocol prefixes, we can take shortcuts. +func hasPrefixCaseInsensitive(s, prefix []byte) bool { + if len(s) < len(prefix) { + return false + } + delta := byte('a' - 'A') + for i, b := range prefix { + if b != s[i] && b != s[i]+delta { + return false + } + } + return true +} + +var protocolPrefixes = [][]byte{ + []byte("http://"), + []byte("https://"), + []byte("ftp://"), + []byte("file://"), + []byte("mailto:"), +} + +const shortestPrefix = 6 // len("ftp://"), the shortest of the above + +func maybeAutoLink(p *Markdown, data []byte, offset int) (int, *Node) { + // quick check to rule out most false hits + if p.insideLink || len(data) < offset+shortestPrefix { + return 0, nil + } + for _, prefix := range protocolPrefixes { + endOfHead := offset + 8 // 8 is the len() of the longest prefix + if endOfHead > len(data) { + endOfHead = len(data) + } + if hasPrefixCaseInsensitive(data[offset:endOfHead], prefix) { + return autoLink(p, data, offset) + } + } + return 0, nil +} + +func autoLink(p *Markdown, data []byte, offset int) (int, *Node) { + // Now a more expensive check to see if we're not inside an anchor element + anchorStart := offset + offsetFromAnchor := 0 + for anchorStart > 0 && data[anchorStart] != '<' { + anchorStart-- + offsetFromAnchor++ + } + + anchorStr := anchorRe.Find(data[anchorStart:]) + if anchorStr != nil { + anchorClose := NewNode(HTMLSpan) + anchorClose.Literal = anchorStr[offsetFromAnchor:] + return len(anchorStr) - offsetFromAnchor, anchorClose + } + + // scan backward for a word boundary + rewind := 0 + for offset-rewind > 0 && rewind <= 7 && isletter(data[offset-rewind-1]) { + rewind++ + } + if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters + return 0, nil + } + + origData := data + data = data[offset-rewind:] + + if !isSafeLink(data) { + return 0, nil + } + + linkEnd := 0 + for linkEnd < len(data) && !isEndOfLink(data[linkEnd]) { + linkEnd++ + } + + // Skip punctuation at the end of the link + if (data[linkEnd-1] == '.' || data[linkEnd-1] == ',') && data[linkEnd-2] != '\\' { + linkEnd-- + } + + // But don't skip semicolon if it's a part of escaped entity: + if data[linkEnd-1] == ';' && data[linkEnd-2] != '\\' && !linkEndsWithEntity(data, linkEnd) { + linkEnd-- + } + + // See if the link finishes with a punctuation sign that can be closed. + var copen byte + switch data[linkEnd-1] { + case '"': + copen = '"' + case '\'': + copen = '\'' + case ')': + copen = '(' + case ']': + copen = '[' + case '}': + copen = '{' + default: + copen = 0 + } + + if copen != 0 { + bufEnd := offset - rewind + linkEnd - 2 + + openDelim := 1 + + /* Try to close the final punctuation sign in this same line; + * if we managed to close it outside of the URL, that means that it's + * not part of the URL. If it closes inside the URL, that means it + * is part of the URL. + * + * Examples: + * + * foo http://www.pokemon.com/Pikachu_(Electric) bar + * => http://www.pokemon.com/Pikachu_(Electric) + * + * foo (http://www.pokemon.com/Pikachu_(Electric)) bar + * => http://www.pokemon.com/Pikachu_(Electric) + * + * foo http://www.pokemon.com/Pikachu_(Electric)) bar + * => http://www.pokemon.com/Pikachu_(Electric)) + * + * (foo http://www.pokemon.com/Pikachu_(Electric)) bar + * => foo http://www.pokemon.com/Pikachu_(Electric) + */ + + for bufEnd >= 0 && origData[bufEnd] != '\n' && openDelim != 0 { + if origData[bufEnd] == data[linkEnd-1] { + openDelim++ + } + + if origData[bufEnd] == copen { + openDelim-- + } + + bufEnd-- + } + + if openDelim == 0 { + linkEnd-- + } + } + + var uLink bytes.Buffer + unescapeText(&uLink, data[:linkEnd]) + + if uLink.Len() > 0 { + node := NewNode(Link) + node.Destination = uLink.Bytes() + node.AppendChild(text(uLink.Bytes())) + return linkEnd, node + } + + return linkEnd, nil +} + +func isEndOfLink(char byte) bool { + return isspace(char) || char == '<' +} + +var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")} +var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")} + +func isSafeLink(link []byte) bool { + for _, path := range validPaths { + if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) { + if len(link) == len(path) { + return true + } else if isalnum(link[len(path)]) { + return true + } + } + } + + for _, prefix := range validUris { + // TODO: handle unicode here + // case-insensitive prefix test + if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) { + return true + } + } + + return false +} + +// return the length of the given tag, or 0 is it's not valid +func tagLength(data []byte) (autolink autolinkType, end int) { + var i, j int + + // a valid tag can't be shorter than 3 chars + if len(data) < 3 { + return notAutolink, 0 + } + + // begins with a '<' optionally followed by '/', followed by letter or number + if data[0] != '<' { + return notAutolink, 0 + } + if data[1] == '/' { + i = 2 + } else { + i = 1 + } + + if !isalnum(data[i]) { + return notAutolink, 0 + } + + // scheme test + autolink = notAutolink + + // try to find the beginning of an URI + for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') { + i++ + } + + if i > 1 && i < len(data) && data[i] == '@' { + if j = isMailtoAutoLink(data[i:]); j != 0 { + return emailAutolink, i + j + } + } + + if i > 2 && i < len(data) && data[i] == ':' { + autolink = normalAutolink + i++ + } + + // complete autolink test: no whitespace or ' or " + switch { + case i >= len(data): + autolink = notAutolink + case autolink != notAutolink: + j = i + + for i < len(data) { + if data[i] == '\\' { + i += 2 + } else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) { + break + } else { + i++ + } + + } + + if i >= len(data) { + return autolink, 0 + } + if i > j && data[i] == '>' { + return autolink, i + 1 + } + + // one of the forbidden chars has been found + autolink = notAutolink + } + i += bytes.IndexByte(data[i:], '>') + if i < 0 { + return autolink, 0 + } + return autolink, i + 1 +} + +// look for the address part of a mail autolink and '>' +// this is less strict than the original markdown e-mail address matching +func isMailtoAutoLink(data []byte) int { + nb := 0 + + // address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' + for i := 0; i < len(data); i++ { + if isalnum(data[i]) { + continue + } + + switch data[i] { + case '@': + nb++ + + case '-', '.', '_': + break + + case '>': + if nb == 1 { + return i + 1 + } + return 0 + default: + return 0 + } + } + + return 0 +} + +// look for the next emph char, skipping other constructs +func helperFindEmphChar(data []byte, c byte) int { + i := 0 + + for i < len(data) { + for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' { + i++ + } + if i >= len(data) { + return 0 + } + // do not count escaped chars + if i != 0 && data[i-1] == '\\' { + i++ + continue + } + if data[i] == c { + return i + } + + if data[i] == '`' { + // skip a code span + tmpI := 0 + i++ + for i < len(data) && data[i] != '`' { + if tmpI == 0 && data[i] == c { + tmpI = i + } + i++ + } + if i >= len(data) { + return tmpI + } + i++ + } else if data[i] == '[' { + // skip a link + tmpI := 0 + i++ + for i < len(data) && data[i] != ']' { + if tmpI == 0 && data[i] == c { + tmpI = i + } + i++ + } + i++ + for i < len(data) && (data[i] == ' ' || data[i] == '\n') { + i++ + } + if i >= len(data) { + return tmpI + } + if data[i] != '[' && data[i] != '(' { // not a link + if tmpI > 0 { + return tmpI + } + continue + } + cc := data[i] + i++ + for i < len(data) && data[i] != cc { + if tmpI == 0 && data[i] == c { + return i + } + i++ + } + if i >= len(data) { + return tmpI + } + i++ + } + } + return 0 +} + +func helperEmphasis(p *Markdown, data []byte, c byte) (int, *Node) { + i := 0 + + // skip one symbol if coming from emph3 + if len(data) > 1 && data[0] == c && data[1] == c { + i = 1 + } + + for i < len(data) { + length := helperFindEmphChar(data[i:], c) + if length == 0 { + return 0, nil + } + i += length + if i >= len(data) { + return 0, nil + } + + if i+1 < len(data) && data[i+1] == c { + i++ + continue + } + + if data[i] == c && !isspace(data[i-1]) { + + if p.extensions&NoIntraEmphasis != 0 { + if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) { + continue + } + } + + emph := NewNode(Emph) + p.inline(emph, data[:i]) + return i + 1, emph + } + } + + return 0, nil +} + +func helperDoubleEmphasis(p *Markdown, data []byte, c byte) (int, *Node) { + i := 0 + + for i < len(data) { + length := helperFindEmphChar(data[i:], c) + if length == 0 { + return 0, nil + } + i += length + + if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) { + nodeType := Strong + if c == '~' { + nodeType = Del + } + node := NewNode(nodeType) + p.inline(node, data[:i]) + return i + 2, node + } + i++ + } + return 0, nil +} + +func helperTripleEmphasis(p *Markdown, data []byte, offset int, c byte) (int, *Node) { + i := 0 + origData := data + data = data[offset:] + + for i < len(data) { + length := helperFindEmphChar(data[i:], c) + if length == 0 { + return 0, nil + } + i += length + + // skip whitespace preceded symbols + if data[i] != c || isspace(data[i-1]) { + continue + } + + switch { + case i+2 < len(data) && data[i+1] == c && data[i+2] == c: + // triple symbol found + strong := NewNode(Strong) + em := NewNode(Emph) + strong.AppendChild(em) + p.inline(em, data[:i]) + return i + 3, strong + case (i+1 < len(data) && data[i+1] == c): + // double symbol found, hand over to emph1 + length, node := helperEmphasis(p, origData[offset-2:], c) + if length == 0 { + return 0, nil + } + return length - 2, node + default: + // single symbol found, hand over to emph2 + length, node := helperDoubleEmphasis(p, origData[offset-1:], c) + if length == 0 { + return 0, nil + } + return length - 1, node + } + } + return 0, nil +} + +func text(s []byte) *Node { + node := NewNode(Text) + node.Literal = s + return node +} + +func normalizeURI(s []byte) []byte { + return s // TODO: implement +} diff --git a/vendor/github.com/russross/blackfriday/v2/markdown.go b/vendor/github.com/russross/blackfriday/v2/markdown.go new file mode 100644 index 00000000000..58d2e4538c6 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/markdown.go @@ -0,0 +1,950 @@ +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// 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: +// +//

    link

    +// +// 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: , +// } +// +// 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] +} diff --git a/vendor/github.com/russross/blackfriday/v2/node.go b/vendor/github.com/russross/blackfriday/v2/node.go new file mode 100644 index 00000000000..04e6050ceea --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/node.go @@ -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

    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) +} diff --git a/vendor/github.com/russross/blackfriday/v2/smartypants.go b/vendor/github.com/russross/blackfriday/v2/smartypants.go new file mode 100644 index 00000000000..3a220e94247 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/smartypants.go @@ -0,0 +1,457 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// +// SmartyPants rendering +// +// + +package blackfriday + +import ( + "bytes" + "io" +) + +// SPRenderer is a struct containing state of a Smartypants renderer. +type SPRenderer struct { + inSingleQuote bool + inDoubleQuote bool + callbacks [256]smartCallback +} + +func wordBoundary(c byte) bool { + return c == 0 || isspace(c) || ispunct(c) +} + +func tolower(c byte) byte { + if c >= 'A' && c <= 'Z' { + return c - 'A' + 'a' + } + return c +} + +func isdigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool { + // edge of the buffer is likely to be a tag that we don't get to see, + // so we treat it like text sometimes + + // enumerate all sixteen possibilities for (previousChar, nextChar) + // each can be one of {0, space, punct, other} + switch { + case previousChar == 0 && nextChar == 0: + // context is not any help here, so toggle + *isOpen = !*isOpen + case isspace(previousChar) && nextChar == 0: + // [ "] might be [ "foo...] + *isOpen = true + case ispunct(previousChar) && nextChar == 0: + // [!"] hmm... could be [Run!"] or [("...] + *isOpen = false + case /* isnormal(previousChar) && */ nextChar == 0: + // [a"] is probably a close + *isOpen = false + case previousChar == 0 && isspace(nextChar): + // [" ] might be [...foo" ] + *isOpen = false + case isspace(previousChar) && isspace(nextChar): + // [ " ] context is not any help here, so toggle + *isOpen = !*isOpen + case ispunct(previousChar) && isspace(nextChar): + // [!" ] is probably a close + *isOpen = false + case /* isnormal(previousChar) && */ isspace(nextChar): + // [a" ] this is one of the easy cases + *isOpen = false + case previousChar == 0 && ispunct(nextChar): + // ["!] hmm... could be ["$1.95] or ["!...] + *isOpen = false + case isspace(previousChar) && ispunct(nextChar): + // [ "!] looks more like [ "$1.95] + *isOpen = true + case ispunct(previousChar) && ispunct(nextChar): + // [!"!] context is not any help here, so toggle + *isOpen = !*isOpen + case /* isnormal(previousChar) && */ ispunct(nextChar): + // [a"!] is probably a close + *isOpen = false + case previousChar == 0 /* && isnormal(nextChar) */ : + // ["a] is probably an open + *isOpen = true + case isspace(previousChar) /* && isnormal(nextChar) */ : + // [ "a] this is one of the easy cases + *isOpen = true + case ispunct(previousChar) /* && isnormal(nextChar) */ : + // [!"a] is probably an open + *isOpen = true + default: + // [a'b] maybe a contraction? + *isOpen = false + } + + // Note that with the limited lookahead, this non-breaking + // space will also be appended to single double quotes. + if addNBSP && !*isOpen { + out.WriteString(" ") + } + + out.WriteByte('&') + if *isOpen { + out.WriteByte('l') + } else { + out.WriteByte('r') + } + out.WriteByte(quote) + out.WriteString("quo;") + + if addNBSP && *isOpen { + out.WriteString(" ") + } + + return true +} + +func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 2 { + t1 := tolower(text[1]) + + if t1 == '\'' { + nextChar := byte(0) + if len(text) >= 3 { + nextChar = text[2] + } + if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { + return 1 + } + } + + if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) { + out.WriteString("’") + return 0 + } + + if len(text) >= 3 { + t2 := tolower(text[2]) + + if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) && + (len(text) < 4 || wordBoundary(text[3])) { + out.WriteString("’") + return 0 + } + } + } + + nextChar := byte(0) + if len(text) > 1 { + nextChar = text[1] + } + if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) { + return 0 + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 3 { + t1 := tolower(text[1]) + t2 := tolower(text[2]) + + if t1 == 'c' && t2 == ')' { + out.WriteString("©") + return 2 + } + + if t1 == 'r' && t2 == ')' { + out.WriteString("®") + return 2 + } + + if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' { + out.WriteString("™") + return 3 + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 2 { + if text[1] == '-' { + out.WriteString("—") + return 1 + } + + if wordBoundary(previousChar) && wordBoundary(text[1]) { + out.WriteString("–") + return 0 + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 3 && text[1] == '-' && text[2] == '-' { + out.WriteString("—") + return 2 + } + if len(text) >= 2 && text[1] == '-' { + out.WriteString("–") + return 1 + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int { + if bytes.HasPrefix(text, []byte(""")) { + nextChar := byte(0) + if len(text) >= 7 { + nextChar = text[6] + } + if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) { + return 5 + } + } + + if bytes.HasPrefix(text, []byte("�")) { + return 3 + } + + out.WriteByte('&') + return 0 +} + +func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int { + var quote byte = 'd' + if angledQuotes { + quote = 'a' + } + + return func(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartAmpVariant(out, previousChar, text, quote, addNBSP) + } +} + +func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 3 && text[1] == '.' && text[2] == '.' { + out.WriteString("…") + return 2 + } + + if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' { + out.WriteString("…") + return 4 + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 2 && text[1] == '`' { + nextChar := byte(0) + if len(text) >= 3 { + nextChar = text[2] + } + if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { + return 1 + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int { + if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { + // is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b + // note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8) + // and avoid changing dates like 1/23/2005 into fractions. + numEnd := 0 + for len(text) > numEnd && isdigit(text[numEnd]) { + numEnd++ + } + if numEnd == 0 { + out.WriteByte(text[0]) + return 0 + } + denStart := numEnd + 1 + if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 { + denStart = numEnd + 3 + } else if len(text) < numEnd+2 || text[numEnd] != '/' { + out.WriteByte(text[0]) + return 0 + } + denEnd := denStart + for len(text) > denEnd && isdigit(text[denEnd]) { + denEnd++ + } + if denEnd == denStart { + out.WriteByte(text[0]) + return 0 + } + if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' { + out.WriteString("") + out.Write(text[:numEnd]) + out.WriteString("") + out.Write(text[denStart:denEnd]) + out.WriteString("") + return denEnd - 1 + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int { + if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { + if text[0] == '1' && text[1] == '/' && text[2] == '2' { + if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { + out.WriteString("½") + return 2 + } + } + + if text[0] == '1' && text[1] == '/' && text[2] == '4' { + if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') { + out.WriteString("¼") + return 2 + } + } + + if text[0] == '3' && text[1] == '/' && text[2] == '4' { + if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') { + out.WriteString("¾") + return 2 + } + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int { + nextChar := byte(0) + if len(text) > 1 { + nextChar = text[1] + } + if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) { + out.WriteString(""") + } + + return 0 +} + +func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartDoubleQuoteVariant(out, previousChar, text, 'd') +} + +func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartDoubleQuoteVariant(out, previousChar, text, 'a') +} + +func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int { + i := 0 + + for i < len(text) && text[i] != '>' { + i++ + } + + out.Write(text[:i+1]) + return i +} + +type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int + +// NewSmartypantsRenderer constructs a Smartypants renderer object. +func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer { + var ( + r SPRenderer + + smartAmpAngled = r.smartAmp(true, false) + smartAmpAngledNBSP = r.smartAmp(true, true) + smartAmpRegular = r.smartAmp(false, false) + smartAmpRegularNBSP = r.smartAmp(false, true) + + addNBSP = flags&SmartypantsQuotesNBSP != 0 + ) + + if flags&SmartypantsAngledQuotes == 0 { + r.callbacks['"'] = r.smartDoubleQuote + if !addNBSP { + r.callbacks['&'] = smartAmpRegular + } else { + r.callbacks['&'] = smartAmpRegularNBSP + } + } else { + r.callbacks['"'] = r.smartAngledDoubleQuote + if !addNBSP { + r.callbacks['&'] = smartAmpAngled + } else { + r.callbacks['&'] = smartAmpAngledNBSP + } + } + r.callbacks['\''] = r.smartSingleQuote + r.callbacks['('] = r.smartParens + if flags&SmartypantsDashes != 0 { + if flags&SmartypantsLatexDashes == 0 { + r.callbacks['-'] = r.smartDash + } else { + r.callbacks['-'] = r.smartDashLatex + } + } + r.callbacks['.'] = r.smartPeriod + if flags&SmartypantsFractions == 0 { + r.callbacks['1'] = r.smartNumber + r.callbacks['3'] = r.smartNumber + } else { + for ch := '1'; ch <= '9'; ch++ { + r.callbacks[ch] = r.smartNumberGeneric + } + } + r.callbacks['<'] = r.smartLeftAngle + r.callbacks['`'] = r.smartBacktick + return &r +} + +// Process is the entry point of the Smartypants renderer. +func (r *SPRenderer) Process(w io.Writer, text []byte) { + mark := 0 + for i := 0; i < len(text); i++ { + if action := r.callbacks[text[i]]; action != nil { + if i > mark { + w.Write(text[mark:i]) + } + previousChar := byte(0) + if i > 0 { + previousChar = text[i-1] + } + var tmp bytes.Buffer + i += action(&tmp, previousChar, text[i:]) + w.Write(tmp.Bytes()) + mark = i + 1 + } + } + if mark < len(text) { + w.Write(text[mark:]) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 21a7c9bebb5..bd1bccd970e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -28,6 +28,8 @@ github.com/Azure/go-autorest/tracing github.com/PuerkitoBio/purell # github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 github.com/PuerkitoBio/urlesc +# github.com/abayer/gen-crd-api-reference-docs v0.999.0 +github.com/abayer/gen-crd-api-reference-docs # github.com/aws/aws-sdk-go v1.36.30 github.com/aws/aws-sdk-go/aws github.com/aws/aws-sdk-go/aws/awserr @@ -297,6 +299,8 @@ github.com/prometheus/procfs/internal/util # github.com/prometheus/statsd_exporter v0.21.0 github.com/prometheus/statsd_exporter/pkg/mapper github.com/prometheus/statsd_exporter/pkg/mapper/fsm +# github.com/russross/blackfriday/v2 v2.1.0 +github.com/russross/blackfriday/v2 # github.com/shurcooL/githubv4 v0.0.0-20190718010115-4ba037080260 github.com/shurcooL/githubv4 # github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f