2022-11-17 19:00:43 +08:00
|
|
|
<!--* toc_depth: 3 *-->
|
|
|
|
|
|
|
|
# Go Style Guide
|
|
|
|
|
|
|
|
https://google.github.io/styleguide/go/guide
|
|
|
|
|
|
|
|
[Overview](index) | [Guide](guide) | [Decisions](decisions) |
|
|
|
|
[Best practices](best-practices)
|
|
|
|
|
|
|
|
<!--
|
|
|
|
|
|
|
|
-->
|
|
|
|
|
|
|
|
{% raw %}
|
|
|
|
|
|
|
|
**Note:** This is part of a series of documents that outline [Go Style](index)
|
|
|
|
at Google. This document is **[normative](index#normative) and
|
|
|
|
[canonical](index#canonical)**. See [the overview](index#about) for more
|
|
|
|
information.
|
|
|
|
|
|
|
|
<a id="principles"></a>
|
|
|
|
|
|
|
|
## Style principles
|
|
|
|
|
|
|
|
There are a few overarching principles that summarize how to think about writing
|
|
|
|
readable Go code. The following are attributes of readable code, in order of
|
|
|
|
importance:
|
|
|
|
|
|
|
|
1. **[Clarity]**: The code's purpose and rationale is clear to the reader.
|
|
|
|
1. **[Simplicity]**: The code accomplishes its goal in the simplest way
|
|
|
|
possible.
|
|
|
|
1. **[Concision]**: The code has a high signal-to-noise ratio.
|
|
|
|
1. **[Maintainability]**: The code is written such that it can be easily
|
|
|
|
maintained.
|
|
|
|
1. **[Consistency]**: The code is consistent with the broader Google codebase.
|
|
|
|
|
|
|
|
[Clarity]: #clarity
|
|
|
|
[Simplicity]: #simplicity
|
|
|
|
[Concision]: #concision
|
|
|
|
[Maintainability]: #maintainability
|
|
|
|
[Consistency]: #consistency
|
|
|
|
|
|
|
|
<a id="clarity"></a>
|
|
|
|
|
|
|
|
### Clarity
|
|
|
|
|
|
|
|
The core goal of readability is to produce code that is clear to the reader.
|
|
|
|
|
|
|
|
Clarity is primarily achieved with effective naming, helpful commentary, and
|
|
|
|
efficient code organization.
|
|
|
|
|
|
|
|
Clarity is to be viewed through the lens of the reader, not the author of the
|
|
|
|
code. It is more important that code be easy to read than easy to write. Clarity
|
|
|
|
in code has two distinct facets:
|
|
|
|
|
|
|
|
* [What is the code actually doing?](#clarity-purpose)
|
|
|
|
* [Why is the code doing what it does?](#clarity-rationale)
|
|
|
|
|
|
|
|
<a id="clarity-purpose"></a>
|
|
|
|
|
|
|
|
#### What is the code actually doing?
|
|
|
|
|
|
|
|
Go is designed such that it should be relatively straightforward to see what the
|
|
|
|
code is doing. In cases of uncertainty or where a reader may require prior
|
|
|
|
knowledge in order to understand the code, it is worth investing time in order
|
|
|
|
to make the code's purpose clearer for future readers. For example, it may help
|
|
|
|
to:
|
|
|
|
|
|
|
|
* Use more descriptive variable names
|
|
|
|
* Add additional commentary
|
|
|
|
* Break up the code with whitespace and comments
|
|
|
|
* Refactor the code into separate functions/methods to make it more modular
|
|
|
|
|
|
|
|
There is no one-size-fits-all approach here, but it is important to prioritize
|
|
|
|
clarity when developing Go code.
|
|
|
|
|
|
|
|
<a id="clarity-rationale"></a>
|
|
|
|
|
|
|
|
#### Why is the code doing what it does?
|
|
|
|
|
|
|
|
The code's rationale is often sufficiently communicated by the names of
|
|
|
|
variables, functions, methods, or packages. Where it is not, it is important to
|
|
|
|
add commentary. The "Why?" is especially important when the code contains
|
|
|
|
nuances that a reader may not be familiar with, such as:
|
|
|
|
|
|
|
|
* A nuance in the language, e.g., a closure will be capturing a loop variable,
|
|
|
|
but the closure is many lines away
|
|
|
|
* A nuance of the business logic, e.g., an access control check that needs to
|
|
|
|
distinguish between the actual user and someone impersonating a user
|
|
|
|
|
|
|
|
An API might require care to use correctly. For example, a piece of code may be
|
|
|
|
intricate and difficult to follow for performance reasons, or a complex sequence
|
|
|
|
of mathematical operations may use type conversions in an unexpected way. In
|
|
|
|
these cases and many more, it is important that accompanying commentary and
|
|
|
|
documentation explain these aspects so that future maintainers don't make a
|
|
|
|
mistake and so that readers can understand the code without needing to
|
|
|
|
reverse-engineer it.
|
|
|
|
|
|
|
|
It is also important to be aware that some attempts to provide clarity (such as
|
|
|
|
adding extra commentary) can actually obscure the code's purpose by adding
|
|
|
|
clutter, restating what the code already says, contradicting the code, or adding
|
|
|
|
maintenance burden to keep the comments up-to-date. Allow the code to speak for
|
|
|
|
itself (e.g., by making the symbol names themselves self-describing) rather than
|
|
|
|
adding redundant comments. It is often better for comments to explain why
|
|
|
|
something is done, not what the code is doing.
|
|
|
|
|
|
|
|
The Google codebase is largely uniform and consistent. It is often the case that
|
|
|
|
code that stands out (e.g., by using an unfamiliar pattern) is doing so for a
|
|
|
|
good reason, typically for performance. Maintaining this property is important
|
|
|
|
to make it clear to readers where they should focus their attention when reading
|
|
|
|
a new piece of code.
|
|
|
|
|
|
|
|
The standard library contains many examples of this principle in action. Among
|
|
|
|
them:
|
|
|
|
|
|
|
|
* Maintainer comments in
|
|
|
|
[`package sort`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/sort/sort.go).
|
|
|
|
* Good
|
|
|
|
[runnable examples in the same package](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/sort/example_search_test.go),
|
|
|
|
which benefit both users (they
|
|
|
|
[show up in godoc](https://pkg.go.dev/sort#pkg-examples)) and maintainers
|
|
|
|
(they [run as part of tests](decisions#examples)).
|
|
|
|
* [`strings.Cut`](https://pkg.go.dev/strings#Cut) is only four lines of code,
|
|
|
|
but they improve the
|
|
|
|
[clarity and correctness of callsites](https://github.com/golang/go/issues/46336).
|
|
|
|
|
|
|
|
<a id="simplicity"></a>
|
|
|
|
|
|
|
|
### Simplicity
|
|
|
|
|
|
|
|
Your Go code should be simple for those using, reading, and maintaining it.
|
|
|
|
|
|
|
|
Go code should be written in the simplest way that accomplishes its goals, both
|
|
|
|
in terms of behavior and performance. Within the Google Go codebase, simple
|
|
|
|
code:
|
|
|
|
|
|
|
|
* Is easy to read from top to bottom
|
|
|
|
* Does not assume that you already know what it is doing
|
|
|
|
* Does not assume that you can memorize all of the preceding code
|
|
|
|
* Does not have unnecessary levels of abstraction
|
|
|
|
* Does not have names that call attention to something mundane
|
|
|
|
* Makes the propagation of values and decisions clear to the reader
|
|
|
|
* Has comments that explain why, not what, the code is doing to avoid future
|
|
|
|
deviation
|
|
|
|
* Has documentation that stands on its own
|
|
|
|
* Has useful errors and useful test failures
|
|
|
|
* May often be mutually exclusive with "clever" code
|
|
|
|
|
|
|
|
Tradeoffs can arise between code simplicity and API usage simplicity. For
|
|
|
|
example, it may be worthwhile to have the code be more complex so that the end
|
|
|
|
user of the API may more easily call the API correctly. In contrast, it may also
|
|
|
|
be worthwhile to leave a bit of extra work to the end user of the API so that
|
|
|
|
the code remains simple and easy to understand.
|
|
|
|
|
|
|
|
When code needs complexity, the complexity should be added deliberately. This is
|
|
|
|
typically necessary if additional performance is required or where there are
|
|
|
|
multiple disparate customers of a particular library or service. Complexity may
|
|
|
|
be justified, but it should come with accompanying documentation so that clients
|
|
|
|
and future maintainers are able to understand and navigate the complexity. This
|
|
|
|
should be supplemented with tests and examples that demonstrate its correct
|
|
|
|
usage, especially if there is both a "simple" and a "complex" way to use the
|
|
|
|
code.
|
|
|
|
|
|
|
|
This principle does not imply that complex code cannot or should not be written
|
|
|
|
in Go or that Go code is not allowed to be complex. We strive for a codebase
|
|
|
|
that avoids unnecessary complexity so that when complexity does appear, it
|
|
|
|
indicates that the code in question requires care to understand and maintain.
|
|
|
|
Ideally, there should be accompanying commentary that explains the rationale and
|
|
|
|
identifies the care that should be taken. This often arises when optimizing code
|
|
|
|
for performance; doing so often requires a more complex approach, like
|
|
|
|
preallocating a buffer and reusing it throughout a goroutine lifetime. When a
|
|
|
|
maintainer sees this, it should be a clue that the code in question is
|
|
|
|
performance-critical, and that should influence the care that is taken when
|
|
|
|
making future changes. If employed unnecessarily, on the other hand, this
|
|
|
|
complexity is a burden on those who need to read or change the code in the
|
|
|
|
future.
|
|
|
|
|
|
|
|
If code turns out to be very complex when its purpose should be simple, this is
|
|
|
|
often a signal to revisit the implementation to see if there is a simpler way to
|
|
|
|
accomplish the same thing.
|
|
|
|
|
|
|
|
<a id="least-mechanism"></a>
|
|
|
|
|
|
|
|
#### Least mechanism
|
|
|
|
|
|
|
|
Where there are several ways to express the same idea, prefer the one that uses
|
|
|
|
the most standard tools. Sophisticated machinery often exists, but should not be
|
|
|
|
employed without reason. It is easy to add complexity to code as needed, whereas
|
|
|
|
it is much harder to remove existing complexity after it has been found to be
|
|
|
|
unnecessary.
|
|
|
|
|
|
|
|
1. Aim to use a core language construct (for example a channel, slice, map,
|
|
|
|
loop, or struct) when sufficient for your use case.
|
|
|
|
2. If there isn't one, look for a tool within the standard library (like an
|
|
|
|
HTTP client or a template engine).
|
|
|
|
3. Finally, consider whether there is a core library in the Google codebase
|
|
|
|
that is sufficient before introducing a new dependency or creating your own.
|
|
|
|
|
|
|
|
As an example, consider production code that contains a flag bound to a variable
|
|
|
|
with a default value which must be overridden in tests. Unless intending to test
|
|
|
|
the program's command-line interface itself (say, with `os/exec`), it is simpler
|
|
|
|
and therefore preferable to override the bound value directly rather than by
|
|
|
|
using `flag.Set`.
|
|
|
|
|
|
|
|
Similarly, if a piece of code requires a set membership check, a boolean-valued
|
|
|
|
map (e.g., `map[string]bool`) often suffices. Libraries that provide set-like
|
|
|
|
types and functionality should only be used if more complicated operations are
|
|
|
|
required that are impossible or overly complicated with a map.
|
|
|
|
|
|
|
|
<a id="concision"></a>
|
|
|
|
|
|
|
|
### Concision
|
|
|
|
|
|
|
|
Concise Go code has a high signal-to-noise ratio. It is easy to discern the
|
|
|
|
relevant details, and the naming and structure guide the reader through these
|
|
|
|
details.
|
|
|
|
|
|
|
|
There are many things that can get in the way of surfacing the most salient
|
|
|
|
details at any given time:
|
|
|
|
|
|
|
|
* Repetitive code
|
|
|
|
* Extraneous syntax
|
|
|
|
* [Opaque names](#naming)
|
|
|
|
* Unnecessary abstraction
|
|
|
|
* Whitespace
|
|
|
|
|
|
|
|
Repetitive code especially obscures the differences between each
|
|
|
|
nearly-identical section, and requires a reader to visually compare similar
|
|
|
|
lines of code to find the changes. [Table-driven testing] is a good example of a
|
|
|
|
mechanism that can concisely factor out the common code from the important
|
|
|
|
details of each repetition, but the choice of which pieces to include in the
|
|
|
|
table will have an impact on how easy the table is to understand.
|
|
|
|
|
|
|
|
When considering multiple ways to structure code, it is worth considering which
|
|
|
|
way makes important details the most apparent.
|
|
|
|
|
|
|
|
Understanding and using common code constructions and idioms are also important
|
|
|
|
for maintaining a high signal-to-noise ratio. For example, the following code
|
|
|
|
block is very common in [error handling], and the reader can quickly understand
|
|
|
|
the purpose of this block.
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Good:
|
|
|
|
if err := doSomething(); err != nil {
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If code looks very similar to this but is subtly different, a reader may not
|
|
|
|
notice the change. In cases like this, it is worth intentionally ["boosting"]
|
|
|
|
the signal of the error check by adding a comment to call attention to it.
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Good:
|
|
|
|
if err := doSomething(); err == nil { // if NO error
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
[Table-driven testing]: https://github.com/golang/go/wiki/TableDrivenTests
|
|
|
|
[error handling]: https://go.dev/blog/errors-are-values
|
|
|
|
["boosting"]: best-practices#signal-boost
|
|
|
|
|
|
|
|
<a id="maintainability"></a>
|
|
|
|
|
|
|
|
### Maintainability
|
|
|
|
|
|
|
|
Code is edited many more times than it is written. Readable code not only makes
|
|
|
|
sense to a reader who is trying to understand how it works, but also to the
|
|
|
|
programmer who needs to change it. Clarity is key.
|
|
|
|
|
|
|
|
Maintainable code:
|
|
|
|
|
|
|
|
* Is easy for a future programmer to modify correctly
|
|
|
|
* Has APIs that are structured so that they can grow gracefully
|
|
|
|
* Is clear about the assumptions that it makes and chooses abstractions that
|
|
|
|
map to the structure of the problem, not to the structure of the code
|
|
|
|
* Avoids unnecessary coupling and doesn't include features that are not used
|
|
|
|
* Has a comprehensive test suite to ensure promised behaviors are maintained
|
|
|
|
and important logic is correct, and the tests provide clear, actionable
|
|
|
|
diagnostics in case of failure
|
|
|
|
|
|
|
|
When using abstractions like interfaces and types which by definition remove
|
|
|
|
information from the context in which they are used, it is important to ensure
|
|
|
|
that they provide sufficient benefit. Editors and IDEs can connect directly to a
|
|
|
|
method definition and show the corresponding documentation when a concrete type
|
|
|
|
is used, but can only refer to an interface definition otherwise. Interfaces are
|
|
|
|
a powerful tool, but come with a cost, since the maintainer may need to
|
|
|
|
understand the specifics of the underlying implementation in order to correctly
|
|
|
|
use the interface, which must be explained within the interface documentation or
|
|
|
|
at the call-site.
|
|
|
|
|
|
|
|
Maintainable code also avoids hiding important details in places that are easy
|
|
|
|
to overlook. For example, in each of the following lines of code, the presence
|
|
|
|
or lack of a single character is critical to understand the line:
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Bad:
|
|
|
|
// The use of = instead of := can change this line completely.
|
|
|
|
if user, err = db.UserByID(userID); err != nil {
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Bad:
|
|
|
|
// The ! in the middle of this line is very easy to miss.
|
|
|
|
leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))
|
|
|
|
```
|
|
|
|
|
|
|
|
Neither of these are incorrect, but both could be written in a more explicit
|
|
|
|
fashion, or could have an accompanying comment that calls attention to the
|
|
|
|
important behavior:
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Good:
|
|
|
|
u, err := db.UserByID(userID)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid origin user: %s", err)
|
|
|
|
}
|
|
|
|
user = u
|
|
|
|
```
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Good:
|
|
|
|
// Gregorian leap years aren't just year%4 == 0.
|
|
|
|
// See https://en.wikipedia.org/wiki/Leap_year#Algorithm.
|
|
|
|
var (
|
|
|
|
leap4 = year%4 == 0
|
|
|
|
leap100 = year%100 == 0
|
|
|
|
leap400 = year%400 == 0
|
|
|
|
)
|
|
|
|
leap := leap4 && (!leap100 || leap400)
|
|
|
|
```
|
|
|
|
|
|
|
|
In the same way, a helper function that hides critical logic or an important
|
|
|
|
edge-case could make it easy for a future change to fail to account for it
|
|
|
|
properly.
|
|
|
|
|
|
|
|
Predictable names are another feature of maintainable code. A user of a package
|
|
|
|
or a maintainer of a piece of code should be able to predict the name of a
|
|
|
|
variable, method, or function in a given context. Function parameters and
|
|
|
|
receiver names for identical concepts should typically share the same name, both
|
|
|
|
to keep documentation understandable and to facilitate refactoring code with
|
|
|
|
minimal overhead.
|
|
|
|
|
|
|
|
Maintainable code minimizes its dependencies (both implicit and explicit).
|
|
|
|
Depending on fewer packages means fewer lines of code that can affect behavior.
|
|
|
|
Avoiding dependencies on internal or undocumented behavior makes code less
|
|
|
|
likely to impose a maintenance burden when those behaviors change in the future.
|
|
|
|
|
|
|
|
When considering how to structure or write code, it is worth taking the time to
|
|
|
|
think through ways in which the code may evolve over time. If a given approach
|
|
|
|
is more conducive to easier and safer future changes, that is often a good
|
|
|
|
trade-off, even if it means a slightly more complicated design.
|
|
|
|
|
|
|
|
<a id="consistency"></a>
|
|
|
|
|
|
|
|
### Consistency
|
|
|
|
|
|
|
|
Consistent code is code that looks, feels, and behaves like similar code
|
|
|
|
throughout the broader codebase, within the context of a team or package, and
|
|
|
|
even within a single file.
|
|
|
|
|
|
|
|
Consistency concerns do not override any of the principles above, but if a tie
|
|
|
|
must be broken, it is often beneficial to break it in favor of consistency.
|
|
|
|
|
|
|
|
Consistency within a package is often the most immediately important level of
|
|
|
|
consistency. It can be very jarring if the same problem is approached in
|
|
|
|
multiple ways throughout a package, or if the same concept has many names within
|
|
|
|
a file. However, even this should not override documented style principles or
|
|
|
|
global consistency.
|
|
|
|
|
|
|
|
<a id="core"></a>
|
|
|
|
|
|
|
|
## Core guidelines
|
|
|
|
|
|
|
|
These guidelines collect the most important aspects of Go style that all Go code
|
|
|
|
is expected to follow. We expect that these principles be learned and followed
|
|
|
|
by the time readability is granted. These are not expected to change frequently,
|
|
|
|
and new additions will have to clear a high bar.
|
|
|
|
|
|
|
|
The guidelines below expand on the recommendations in [Effective Go], which
|
|
|
|
provide a common baseline for Go code across the entire community.
|
|
|
|
|
|
|
|
[Effective Go]: https://go.dev/doc/effective_go
|
|
|
|
|
|
|
|
<a id="formatting"></a>
|
|
|
|
|
|
|
|
### Formatting
|
|
|
|
|
|
|
|
All Go source files must conform to the format outputted by the `gofmt` tool.
|
|
|
|
This format is enforced by a presubmit check in the Google codebase.
|
|
|
|
[Generated code] should generally also be formatted (e.g., by using
|
|
|
|
[`format.Source`]), as it is also browsable in Code Search.
|
|
|
|
|
|
|
|
[Generated code]: https://docs.bazel.build/versions/main/be/general.html#genrule
|
|
|
|
[`format.Source`]: https://pkg.go.dev/go/format#Source
|
|
|
|
|
|
|
|
<a id="mixed-caps"></a>
|
|
|
|
|
|
|
|
### MixedCaps
|
|
|
|
|
|
|
|
Go source code uses `MixedCaps` or `mixedCaps` (camel case) rather than
|
|
|
|
underscores (snake case) when writing multi-word names.
|
|
|
|
|
|
|
|
This applies even when it breaks conventions in other languages. For example, a
|
|
|
|
constant is `MaxLength` (not `MAX_LENGTH`) if exported and `maxLength` (not
|
|
|
|
`max_length`) if unexported.
|
|
|
|
|
|
|
|
Local variables are considered [unexported] for the purpose of choosing the
|
|
|
|
initial capitalization.
|
|
|
|
|
|
|
|
<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
|
|
|
|
|
|
|
|
[unexported]: https://go.dev/ref/spec#Exported_identifiers
|
|
|
|
|
|
|
|
<a id="line-length"></a>
|
|
|
|
|
|
|
|
### Line length
|
|
|
|
|
2024-01-31 00:19:28 +08:00
|
|
|
There is no fixed line length for Go source code. If a line feels too long,
|
|
|
|
prefer refactoring instead of splitting it. If it is already as short as it is
|
2022-11-17 19:00:43 +08:00
|
|
|
practical for it to be, the line should be allowed to remain long.
|
|
|
|
|
|
|
|
Do not split a line:
|
|
|
|
|
|
|
|
* Before an [indentation change](decisions#indentation-confusion) (e.g.,
|
|
|
|
function declaration, conditional)
|
|
|
|
* To make a long string (e.g., a URL) fit into multiple shorter lines
|
|
|
|
|
|
|
|
<a id="naming"></a>
|
|
|
|
|
|
|
|
### Naming
|
|
|
|
|
|
|
|
Naming is more art than science. In Go, names tend to be somewhat shorter than
|
|
|
|
in many other languages, but the same [general guidelines] apply. Names should:
|
|
|
|
|
|
|
|
* Not feel [repetitive](decisions#repetition) when they are used
|
|
|
|
* Take the context into consideration
|
|
|
|
* Not repeat concepts that are already clear
|
|
|
|
|
|
|
|
You can find more specific guidance on naming in [decisions](decisions#naming).
|
|
|
|
|
|
|
|
[general guidelines]: https://testing.googleblog.com/2017/10/code-health-identifiernamingpostforworl.html
|
|
|
|
|
|
|
|
<a id="local-consistency"></a>
|
|
|
|
|
|
|
|
### Local consistency
|
|
|
|
|
|
|
|
Where the style guide has nothing to say about a particular point of style,
|
|
|
|
authors are free to choose the style that they prefer, unless the code in close
|
|
|
|
proximity (usually within the same file or package, but sometimes within a team
|
|
|
|
or project directory) has taken a consistent stance on the issue.
|
|
|
|
|
|
|
|
Examples of **valid** local style considerations:
|
|
|
|
|
|
|
|
* Use of `%s` or `%v` for formatted printing of errors
|
|
|
|
* Usage of buffered channels in lieu of mutexes
|
|
|
|
|
|
|
|
Examples of **invalid** local style considerations:
|
|
|
|
|
|
|
|
* Line length restrictions for code
|
|
|
|
* Use of assertion-based testing libraries
|
|
|
|
|
|
|
|
If the local style disagrees with the style guide but the readability impact is
|
|
|
|
limited to one file, it will generally be surfaced in a code review for which a
|
|
|
|
consistent fix would be outside the scope of the CL in question. At that point,
|
|
|
|
it is appropriate to file a bug to track the fix.
|
|
|
|
|
|
|
|
If a change would worsen an existing style deviation, expose it in more API
|
|
|
|
surfaces, expand the number of files in which the deviation is present, or
|
|
|
|
introduce an actual bug, then local consistency is no longer a valid
|
|
|
|
justification for violating the style guide for new code. In these cases, it is
|
|
|
|
appropriate for the author to clean up the existing codebase in the same CL,
|
|
|
|
perform a refactor in advance of the current CL, or find an alternative that at
|
|
|
|
least does not make the local problem worse.
|
|
|
|
|
|
|
|
<!--
|
|
|
|
|
|
|
|
-->
|
|
|
|
|
|
|
|
{% endraw %}
|