gomarkdoc

package module
v0.4.1-8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 15, 2023 License: MIT Imports: 6 Imported by: 0

README

Cloudogu gomarkdoc

  • Used for internal code documentation where field comments from structs should be extracted.
  • Added option include-files
    • used to generation markdown only for specified files in a package

Update templates

  • modify template in templates/
  • run make generate

Installation

  • go install github.com/cloudogu/gomarkdoc/cmd/gomarkdoc
    • requires go 1.19+

Usage

  • gomarkdoc ./core ./registry --output doc.md --include-files dogu_v2.go --include-files Node.go

Example output

type HealthCheck

HealthCheck struct will be used to do readiness and health checks for the final container

type HealthCheck struct {
    Type string

    State string

    Port int

    Path string

    Parameters map[string]string
}
Type

Type specifies the nature of the health check. It can be either tcp, http or state.

For Type tcp the given Port needs to be open to be healthy.

For Type http the service needs to return a status code between >= 200 and < 300 to be healthy. Port and Path are used to reach the service.

For Type state the /state/<dogu> key in etcd gets checked. This key is written by the installation process. A 'ready' value in etcd means that the dogu is healthy.

State

State contains the expected health check state of Type state, default is ready

Port

Port is the tcp-port for health checks of Type tcp and http.

Path

Path is the Http-Path for health checks of Type http, default is '/health'.

Parameters

key value pairs for check specific parameters. Deprecated: is not in use.


gomarkdoc

import "github.com/cloudogu/gomarkdoc"

Package gomarkdoc formats documentation for one or more packages as markdown for usage outside of the main https://pkg.go.dev site. It supports custom templates for tweaking representation of documentation at fine-grained levels, exporting both exported and unexported symbols, and custom formatters for different backends.

Command Line Usage

If you want to use this package as a command-line tool, you can install the command by running the following on go 1.16+:

go install github.com/cloudogu/gomarkdoc/cmd/gomarkdoc@latest

For older versions of go, you can install using the following method instead:

GO111MODULE=on go get -u github.com/cloudogu/gomarkdoc/cmd/gomarkdoc

The command line tool supports configuration for all of the features of the importable package:

$ gomarkdoc --help
generate markdown documentation for golang code

Usage:
  gomarkdoc [flags] [package ...]

Flags:
  -c, --check                              Check the output to see if it matches the generated documentation. --output must be specified to use this.
      --config string                      File from which to load configuration (default: .gomarkdoc.yml)
  -e, --embed                              Embed documentation into existing markdown files if available, otherwise append to file.
      --footer string                      Additional content to inject at the end of each output file.
      --footer-file string                 File containing additional content to inject at the end of each output file.
  -f, --format string                      Format to use for writing output data. Valid options: github (default), azure-devops, plain (default "github")
      --header string                      Additional content to inject at the beginning of each output file.
      --header-file string                 File containing additional content to inject at the beginning of each output file.
  -h, --help                               help for gomarkdoc
  -u, --include-unexported                 Output documentation for unexported symbols, methods and fields in addition to exported ones.
  -o, --output string                      File or pattern specifying where to write documentation output. Defaults to printing to stdout.
      --repository.default-branch string   Manual override for the git repository URL used in place of automatic detection.
      --repository.path string             Manual override for the path from the root of the git repository used in place of automatic detection.
      --repository.url string              Manual override for the git repository URL used in place of automatic detection.
      --tags strings                       Set of build tags to apply when choosing which files to include for documentation generation.
  -t, --template stringToString            Custom template string to use for the provided template name instead of the default template. (default [])
      --template-file stringToString       Custom template file to use for the provided template name instead of the default template. (default [])
  -v, --verbose count                      Log additional output from the execution of the command. Can be chained for additional verbosity.
      --version                            Print the version.

The gomarkdoc command processes each of the provided packages, generating documentation for the package in markdown format and writing it to console. For example, if you have a package in your current directory and want to send it to a documentation markdown file, you might do something like this:

gomarkdoc --output doc.md .
Package Specifiers

The gomarkdoc tool supports generating documentation for both local packages and remote ones. To specify a local package, start the name of the package with a period (.) or specify an absolute path on the filesystem. All other package signifiers are assumed to be remote packages. You may specify both local and remote packages in the same command invocation as separate arguments.

Output Redirection

By default, the documentation generated by the gomarkdoc command is sent to standard output, where it can be redirected to a file. This can be useful if you want to perform additional modifications to the documentation or send it somewhere other than a file. However, keep in mind that there are some inconsistencies in how various shells/platforms handle redirected command output (for example, Powershell encodes in UTF-16, not UTF-8). As a result, the --output option described below is recommended for most use cases.

gomarkdoc . > doc.md

If you want to redirect output for each processed package to a file, you can provide the --output/-o option, which accepts a template specifying how to generate the path of the output file. A common usage of this option is when generating README documentation for a package with subpackages (which are supported via the ... signifier as in other parts of the golang toolchain). In addition, this option provides consistent behavior across platforms and shells:

gomarkdoc --output '{{.Dir}}/README.md' ./...

You can see all of the data available to the output template in the PackageSpec struct in the github.com/cloudogu/gomarkdoc/cmd/gomarkdoc package.

Template Overrides

The documentation information that is output is formatted using a series of text templates for the various components of the overall documentation which get generated. Higher level templates contain lower level templates, but any template may be replaced with an override template using the --template/-t option. The full list of templates that may be overridden are:

  • file: generates documentation for a file containing one or more packages, depending on how the tool is configured. This is the root template for documentation generation.

  • package: generates documentation for an entire package.

  • type: generates documentation for a single type declaration, as well as any related functions/methods.

  • func: generates documentation for a single function or method. It may be referenced from within a type, or directly in the package, depending on nesting.

  • value: generates documentation for a single variable or constant declaration block within a package.

  • index: generates an index of symbols within a package, similar to what is seen for godoc.org. The index links to types, funcs, variables, and constants generated by other templates, so it may need to be overridden as well if any of those templates are changed in a material way.

  • example: generates documentation for a single example for a package or one of its symbols. The example is generated alongside whichever symbol it represents, based on the standard naming conventions outlined in https://blog.golang.org/examples#TOC_4 .

  • doc: generates the freeform documentation block for any of the above structures that can contain a documentation section.

  • import: generates the import code used to pull in a package.

Overriding with the -t option uses a key-vaule pair mapping a template name to the file containing the contents of the override template to use. Specified template files must exist:

gomarkdoc -t package=custom-package.gotxt -t doc=custom-doc.gotxt .
Additional Options

As with the godoc tool itself, only exported symbols will be shown in documentation. This can be expanded to include all symbols in a package by adding the --include-unexported/-u flag.

gomarkdoc -u -o README.md .

If you want to blend the documentation generated by gomarkdoc with your own hand-written markdown, you can use the --embed/-e flag to change the gomarkdoc tool into an append/embed mode. When documentation is generated, gomarkdoc looks for a file in the location where the documentation is to be written and embeds the documentation if present. Otherwise, the documentation is appended to the end of the file.

gomarkdoc -o README.md -e .

When running with embed mode enabled, gomarkdoc will look for either this single comment:

<!-- gomarkdoc:embed -->

Or the following pair of comments (in which case all content in between is replaced):

<!-- gomarkdoc:embed:start -->

This content is replaced with the embedded documentation

<!-- gomarkdoc:embed:end -->

If you would like to include files that are part of a build tag, you can specify build tags with the --tags flag. Tags are also supported through GOFLAGS, though command line and configuration file definitions override tags specified through GOFLAGS.

gomarkdoc --tags sometag .

You can also run gomarkdoc in a verification mode with the --check/-c flag. This is particularly useful for continuous integration when you want to make sure that a commit correctly updated the generated documentation. This flag is only supported when the --output/-o flag is specified, as the file provided there is what the tool is checking:

gomarkdoc -o README.md -c .

If you're experiencing difficulty with gomarkdoc or just want to get more information about how it's executing underneath, you can add -v to show more logs. This can be chained a second time to show even more verbose logs:

gomarkdoc -vv -o README.md .

Some features of gomarkdoc rely on being able to detect information from the git repository containing the project. Since individual local git repositories may be configured differently from person to person, you may want to manually specify the information for the repository to remove any inconsistencies. This can be achieved with the --repository.url, --repository.default-branch and --repository.path options. For example, this repository would be configured with:

gomarkdoc --repository.url "https://github.com/cloudogu/gomarkdoc" --repository.default-branch master --repository.path / -o README.md .
Configuring via File

If you want to reuse configuration options across multiple invocations, you can specify a file in the folder where you invoke gomarkdoc containing configuration information that you would otherwise provide on the command line. This file may be a JSON, TOML, YAML, HCL, env, or Java properties file, but the name is expected to start with .gomarkdoc (e.g. .gomarkdoc.yml).

All configuration options are available with the camel-cased form of their long name (e.g. --include-unexported becomes includeUnexported). Template overrides are specified as a map, rather than a set of key-value pairs separated by =. Options provided on the command line override those provided in the configuration file if an option is present in both.

Programmatic Usage

While most users will find the command line utility sufficient for their needs, this package may also be used programmatically by installing it directly, rather than its command subpackage. The programmatic usage provides more flexibility when selecting what packages to work with and what components to generate documentation for.

A common usage will look something like this:

package main

import (
	"go/build"
	"fmt"
	"os"

	"github.com/cloudogu/gomarkdoc"
	"github.com/cloudogu/gomarkdoc/lang"
	"github.com/cloudogu/gomarkdoc/logger"
)

func main() {
	// Create a renderer to output data
	out, err := gomarkdoc.NewRenderer()
	if err != nil {
		// handle error
	}

	wd, err := os.Getwd()
	if err != nil {
		// handle error
	}

	buildPkg, err := build.ImportDir(wd, build.ImportComment)
	if err != nil {
		// handle error
	}

	// Create a documentation package from the build representation of our
	// package.
	log := logger.New(logger.DebugLevel)
	pkg, err := lang.NewPackageFromBuild(log, buildPkg)
	if err != nil {
		// handle error
	}

	// Write the documentation out to console.
	fmt.Println(out.Package(pkg))
}
Examples

This project uses itself to generate the README files in github.com/cloudogu/gomarkdoc and its subdirectories. To see the commands that are run to generate documentation for this repository, take a look at the Doc() and DocVerify() functions in magefile.go and the .gomarkdoc.yml file in the root of this repository. To run these commands in your own project, simply replace `go run ./cmd/gomarkdoc` with `gomarkdoc`.

Know of another project that is using gomarkdoc? Open an issue with a description of the project and link to the repository and it might be featured here!

Index

type Renderer

Renderer provides capabilities for rendering various types of documentation with the configured format and templates.

type Renderer struct {
    // contains filtered or unexported fields
}
func NewRenderer
func NewRenderer(opts ...RendererOption) (*Renderer, error)

NewRenderer initializes a Renderer configured using the provided options. If nothing special is provided, the created renderer will use the default set of templates and the GitHubFlavoredMarkdown.

func (*Renderer) Example
func (out *Renderer) Example(ex *lang.Example) (string, error)

Example renders an example's documentation to a string. You can change the rendering of the example by overriding the "example" template or one of the templates it references.

func (*Renderer) File
func (out *Renderer) File(file *lang.File) (string, error)

File renders a file containing one or more packages to document to a string. You can change the rendering of the file by overriding the "file" template or one of the templates it references.

func (*Renderer) Func
func (out *Renderer) Func(fn *lang.Func) (string, error)

Func renders a function's documentation to a string. You can change the rendering of the package by overriding the "func" template or one of the templates it references.

func (*Renderer) Package
func (out *Renderer) Package(pkg *lang.Package) (string, error)

Package renders a package's documentation to a string. You can change the rendering of the package by overriding the "package" template or one of the templates it references.

func (*Renderer) Type
func (out *Renderer) Type(typ *lang.Type) (string, error)

Type renders a type's documentation to a string. You can change the rendering of the type by overriding the "type" template or one of the templates it references.

type RendererOption

RendererOption configures the renderer's behavior.

type RendererOption func(renderer *Renderer) error
func WithFormat
func WithFormat(format format.Format) RendererOption

WithFormat changes the renderer to use the format provided instead of the default format.

func WithTemplateOverride
func WithTemplateOverride(name, tmpl string) RendererOption

WithTemplateOverride adds a template that overrides the template with the provided name using the value provided in the tmpl parameter.

Generated by gomarkdoc

Documentation

Overview

Package gomarkdoc formats documentation for one or more packages as markdown for usage outside of the main https://pkg.go.dev site. It supports custom templates for tweaking representation of documentation at fine-grained levels, exporting both exported and unexported symbols, and custom formatters for different backends.

Command Line Usage

If you want to use this package as a command-line tool, you can install the command by running the following on go 1.16+:

go install github.com/cloudogu/gomarkdoc/cmd/gomarkdoc@latest

For older versions of go, you can install using the following method instead:

GO111MODULE=on go get -u github.com/cloudogu/gomarkdoc/cmd/gomarkdoc

The command line tool supports configuration for all of the features of the importable package:

$ gomarkdoc --help
generate markdown documentation for golang code

Usage:
  gomarkdoc [flags] [package ...]

Flags:
  -c, --check                              Check the output to see if it matches the generated documentation. --output must be specified to use this.
      --config string                      File from which to load configuration (default: .gomarkdoc.yml)
  -e, --embed                              Embed documentation into existing markdown files if available, otherwise append to file.
      --footer string                      Additional content to inject at the end of each output file.
      --footer-file string                 File containing additional content to inject at the end of each output file.
  -f, --format string                      Format to use for writing output data. Valid options: github (default), azure-devops, plain (default "github")
      --header string                      Additional content to inject at the beginning of each output file.
      --header-file string                 File containing additional content to inject at the beginning of each output file.
  -h, --help                               help for gomarkdoc
  -u, --include-unexported                 Output documentation for unexported symbols, methods and fields in addition to exported ones.
  -o, --output string                      File or pattern specifying where to write documentation output. Defaults to printing to stdout.
      --repository.default-branch string   Manual override for the git repository URL used in place of automatic detection.
      --repository.path string             Manual override for the path from the root of the git repository used in place of automatic detection.
      --repository.url string              Manual override for the git repository URL used in place of automatic detection.
      --tags strings                       Set of build tags to apply when choosing which files to include for documentation generation.
  -t, --template stringToString            Custom template string to use for the provided template name instead of the default template. (default [])
      --template-file stringToString       Custom template file to use for the provided template name instead of the default template. (default [])
  -v, --verbose count                      Log additional output from the execution of the command. Can be chained for additional verbosity.
      --version                            Print the version.

The gomarkdoc command processes each of the provided packages, generating documentation for the package in markdown format and writing it to console. For example, if you have a package in your current directory and want to send it to a documentation markdown file, you might do something like this:

gomarkdoc --output doc.md .

Package Specifiers

The gomarkdoc tool supports generating documentation for both local packages and remote ones. To specify a local package, start the name of the package with a period (.) or specify an absolute path on the filesystem. All other package signifiers are assumed to be remote packages. You may specify both local and remote packages in the same command invocation as separate arguments.

Output Redirection

By default, the documentation generated by the gomarkdoc command is sent to standard output, where it can be redirected to a file. This can be useful if you want to perform additional modifications to the documentation or send it somewhere other than a file. However, keep in mind that there are some inconsistencies in how various shells/platforms handle redirected command output (for example, Powershell encodes in UTF-16, not UTF-8). As a result, the --output option described below is recommended for most use cases.

gomarkdoc . > doc.md

If you want to redirect output for each processed package to a file, you can provide the --output/-o option, which accepts a template specifying how to generate the path of the output file. A common usage of this option is when generating README documentation for a package with subpackages (which are supported via the ... signifier as in other parts of the golang toolchain). In addition, this option provides consistent behavior across platforms and shells:

gomarkdoc --output '{{.Dir}}/README.md' ./...

You can see all of the data available to the output template in the PackageSpec struct in the github.com/cloudogu/gomarkdoc/cmd/gomarkdoc package.

Template Overrides

The documentation information that is output is formatted using a series of text templates for the various components of the overall documentation which get generated. Higher level templates contain lower level templates, but any template may be replaced with an override template using the --template/-t option. The full list of templates that may be overridden are:

  • file: generates documentation for a file containing one or more packages, depending on how the tool is configured. This is the root template for documentation generation.

  • package: generates documentation for an entire package.

  • type: generates documentation for a single type declaration, as well as any related functions/methods.

  • func: generates documentation for a single function or method. It may be referenced from within a type, or directly in the package, depending on nesting.

  • value: generates documentation for a single variable or constant declaration block within a package.

  • index: generates an index of symbols within a package, similar to what is seen for godoc.org. The index links to types, funcs, variables, and constants generated by other templates, so it may need to be overridden as well if any of those templates are changed in a material way.

  • example: generates documentation for a single example for a package or one of its symbols. The example is generated alongside whichever symbol it represents, based on the standard naming conventions outlined in https://blog.golang.org/examples#TOC_4.

  • doc: generates the freeform documentation block for any of the above structures that can contain a documentation section.

  • import: generates the import code used to pull in a package.

Overriding with the -t option uses a key-vaule pair mapping a template name to the file containing the contents of the override template to use. Specified template files must exist:

gomarkdoc -t package=custom-package.gotxt -t doc=custom-doc.gotxt .

Additional Options

As with the godoc tool itself, only exported symbols will be shown in documentation. This can be expanded to include all symbols in a package by adding the --include-unexported/-u flag.

gomarkdoc -u -o README.md .

If you want to blend the documentation generated by gomarkdoc with your own hand-written markdown, you can use the --embed/-e flag to change the gomarkdoc tool into an append/embed mode. When documentation is generated, gomarkdoc looks for a file in the location where the documentation is to be written and embeds the documentation if present. Otherwise, the documentation is appended to the end of the file.

gomarkdoc -o README.md -e .

When running with embed mode enabled, gomarkdoc will look for either this single comment:

<!-- gomarkdoc:embed -->

Or the following pair of comments (in which case all content in between is replaced):

<!-- gomarkdoc:embed:start -->

This content is replaced with the embedded documentation

<!-- gomarkdoc:embed:end -->

If you would like to include files that are part of a build tag, you can specify build tags with the --tags flag. Tags are also supported through GOFLAGS, though command line and configuration file definitions override tags specified through GOFLAGS.

gomarkdoc --tags sometag .

You can also run gomarkdoc in a verification mode with the --check/-c flag. This is particularly useful for continuous integration when you want to make sure that a commit correctly updated the generated documentation. This flag is only supported when the --output/-o flag is specified, as the file provided there is what the tool is checking:

gomarkdoc -o README.md -c .

If you're experiencing difficulty with gomarkdoc or just want to get more information about how it's executing underneath, you can add -v to show more logs. This can be chained a second time to show even more verbose logs:

gomarkdoc -vv -o README.md .

Some features of gomarkdoc rely on being able to detect information from the git repository containing the project. Since individual local git repositories may be configured differently from person to person, you may want to manually specify the information for the repository to remove any inconsistencies. This can be achieved with the --repository.url, --repository.default-branch and --repository.path options. For example, this repository would be configured with:

gomarkdoc --repository.url "https://github.com/cloudogu/gomarkdoc" --repository.default-branch master --repository.path / -o README.md .

Configuring via File

If you want to reuse configuration options across multiple invocations, you can specify a file in the folder where you invoke gomarkdoc containing configuration information that you would otherwise provide on the command line. This file may be a JSON, TOML, YAML, HCL, env, or Java properties file, but the name is expected to start with .gomarkdoc (e.g. .gomarkdoc.yml).

All configuration options are available with the camel-cased form of their long name (e.g. --include-unexported becomes includeUnexported). Template overrides are specified as a map, rather than a set of key-value pairs separated by =. Options provided on the command line override those provided in the configuration file if an option is present in both.

Programmatic Usage

While most users will find the command line utility sufficient for their needs, this package may also be used programmatically by installing it directly, rather than its command subpackage. The programmatic usage provides more flexibility when selecting what packages to work with and what components to generate documentation for.

A common usage will look something like this:

package main

import (
	"go/build"
	"fmt"
	"os"

	"github.com/cloudogu/gomarkdoc"
	"github.com/cloudogu/gomarkdoc/lang"
	"github.com/cloudogu/gomarkdoc/logger"
)

func main() {
	// Create a renderer to output data
	out, err := gomarkdoc.NewRenderer()
	if err != nil {
		// handle error
	}

	wd, err := os.Getwd()
	if err != nil {
		// handle error
	}

	buildPkg, err := build.ImportDir(wd, build.ImportComment)
	if err != nil {
		// handle error
	}

	// Create a documentation package from the build representation of our
	// package.
	log := logger.New(logger.DebugLevel)
	pkg, err := lang.NewPackageFromBuild(log, buildPkg)
	if err != nil {
		// handle error
	}

	// Write the documentation out to console.
	fmt.Println(out.Package(pkg))
}

Examples

This project uses itself to generate the README files in github.com/cloudogu/gomarkdoc and its subdirectories. To see the commands that are run to generate documentation for this repository, take a look at the Doc() and DocVerify() functions in magefile.go and the .gomarkdoc.yml file in the root of this repository. To run these commands in your own project, simply replace `go run ./cmd/gomarkdoc` with `gomarkdoc`.

Know of another project that is using gomarkdoc? Open an issue with a description of the project and link to the repository and it might be featured here!

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer provides capabilities for rendering various types of documentation with the configured format and templates.

func NewRenderer

func NewRenderer(opts ...RendererOption) (*Renderer, error)

NewRenderer initializes a Renderer configured using the provided options. If nothing special is provided, the created renderer will use the default set of templates and the GitHubFlavoredMarkdown.

func (*Renderer) Example

func (out *Renderer) Example(ex *lang.Example) (string, error)

Example renders an example's documentation to a string. You can change the rendering of the example by overriding the "example" template or one of the templates it references.

func (*Renderer) File

func (out *Renderer) File(file *lang.File) (string, error)

File renders a file containing one or more packages to document to a string. You can change the rendering of the file by overriding the "file" template or one of the templates it references.

func (*Renderer) Func

func (out *Renderer) Func(fn *lang.Func) (string, error)

Func renders a function's documentation to a string. You can change the rendering of the package by overriding the "func" template or one of the templates it references.

func (*Renderer) Package

func (out *Renderer) Package(pkg *lang.Package) (string, error)

Package renders a package's documentation to a string. You can change the rendering of the package by overriding the "package" template or one of the templates it references.

func (*Renderer) Type

func (out *Renderer) Type(typ *lang.Type) (string, error)

Type renders a type's documentation to a string. You can change the rendering of the type by overriding the "type" template or one of the templates it references.

type RendererOption

type RendererOption func(renderer *Renderer) error

RendererOption configures the renderer's behavior.

func WithFormat

func WithFormat(format format.Format) RendererOption

WithFormat changes the renderer to use the format provided instead of the default format.

func WithTemplateOverride

func WithTemplateOverride(name, tmpl string) RendererOption

WithTemplateOverride adds a template that overrides the template with the provided name using the value provided in the tmpl parameter.

Directories

Path Synopsis
cmd
gomarkdoc
Package gomarkdoc provides a command line interface for writing golang documentation in markdown format.
Package gomarkdoc provides a command line interface for writing golang documentation in markdown format.
Package format defines output formats for emitting documentation information.
Package format defines output formats for emitting documentation information.
formatcore
Package formatcore provides utilities for creating formatters like those found in the format package.
Package formatcore provides utilities for creating formatters like those found in the format package.
Package lang provides constructs for defining golang language constructs and extracting information from them for documentation purposes.
Package lang provides constructs for defining golang language constructs and extracting information from them for documentation purposes.
Package logger provides a simple console logger for reporting information about execution to stderr.
Package logger provides a simple console logger for reporting information about execution to stderr.
testData
docs
Package docs exercises the documentation features of golang 1.19 and above at the package documentation level.
Package docs exercises the documentation features of golang 1.19 and above at the package documentation level.
embed
Package embed tests out embedding of documentation in an existing readme.
Package embed tests out embedding of documentation in an existing readme.
simple
Package simple contains, some simple code to exercise basic scenarios for documentation purposes.
Package simple contains, some simple code to exercise basic scenarios for documentation purposes.
tags
Package tags contains code to demonstrate usage of build tags.
Package tags contains code to demonstrate usage of build tags.
unexported
Package unexported contains some simple code to exercise basic scenarios for documentation purposes.
Package unexported contains some simple code to exercise basic scenarios for documentation purposes.
untagged
Package untagged contains code to demonstrate usage of build tags.
Package untagged contains code to demonstrate usage of build tags.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL