Documentation
¶
Overview ¶
Package ccme implements a parser for Conventional Commits: Monorepo Extension (CCME) 1.0.0, a strict superset of Conventional Commits 1.0.0.
The parser is a single left-to-right index scan with one byte of lookahead: no regular-expression engine, no backtracking, no recursion, and therefore no input that can trigger superlinear behaviour (§20). Every diagnostic is raised at a known position, so a caller can render a caret under the offending character.
Usage ¶
p := ccme.DefaultParser()
res, err := p.Parse(message)
if err != nil {
// err lists the error-severity diagnostics; res is still populated
// with the units that parsed cleanly.
}
for _, u := range res.ValidUnits() {
fmt.Println(u.Header.Type, u.Scopes(), u.Bump)
}
All configuration lives in a single Config struct whose zero value is the specification default, so only the fields you care about need setting:
p, err := ccme.NewParser(ccme.Config{
Separator: "%%%",
StrictTypes: true,
Propagation: ccme.PropagationConfig{Depth: ccme.DepthAll},
})
A Parser is immutable after construction and safe for concurrent use, so a single package-level parser can serve every goroutine in a service.
Propagation has two axes ¶
§5.3 gives propagation two independent axes, each with a value and a depth, and each expressible inline or as a footer:
axis value depth footers
bump "^", "^^" "+N" Propagate, Propagate-Depth, Propagate-Scope
channel "%%" "++N" Propagate-Channel, Propagate-Channel-Depth,
Propagate-Channel-Scope
"%" is on neither axis: it sets the unit's own channel. Both depths default to 0, so a unit reaches nobody until it opts in. The doubled sigils are fixed two-character tokens rather than repetition counts, so "^^^", "%%%" and "+++" are all E110.
A channel value may also be a transition, "from>to", where "*" on the left matches any prerelease (§11.2).
Performance ¶
The package is built for sweeping large histories. Parsing is O(n) in message length with no backtracking, and the hot path is allocation-lean: Header.Raw, Unit.Raw, Unit.Body, Header.Description and every scope term are substrings of the input rather than copies, and a message that is already normalised is not rewritten at all. A clean single-unit message costs a handful of small allocations, none of them proportional to the body size.
Two consequences are worth knowing. Result and its Units retain a reference to the message string, so holding a Result alive holds the message alive. And Directives.Kinds always aliases the parser's configuration, since §8.4 has no per-unit override, so it must be treated as read-only.
Scope ¶
This package parses messages. It does not read git, load a workspace, walk a dependency graph, or compute versions, so the diagnostics that require any of those are out of scope and are never emitted: E001 is raised only for invalid UTF-8 in the message itself, and E130, E153, E156, E157, E182, E185, E191, E195, E196, E210, E211, E212, E213, W130, W131, W134, W135, W153, W154, W158, W160, W170, W171, W172, W185, W186, W190, W192, W209, W210, W211, W212, W213 and W215 belong to the release engine. E154 is enforced for the cases that are decidable from the message alone, and the correction footers of §7.4 are shape-validated here (E151, E173, W214) while their targets are resolved by the engine (§13.4b).
The parser bounds of §14.1 are always enforced, because a commit message is untrusted input (§18): exceeding limits.unitsPerMessage, limits.scopeTermsPerUnit or limits.messageBytes is E158, which is message-scoped: the commit contributes nothing.
The hold machinery of §8.6.1 is split the same way: this package parses and classifies Release-As values, all three of which are package-level since §8.6 has no bump form, but resolving which directive wins over a window, and what that means for a release plan, belongs to the engine.
Section references in the documentation are to the CCME specification.
Index ¶
- Constants
- Variables
- func DefaultIssueTrailers() []string
- func DefaultMessageLevelTrailers() []string
- func DefaultTypes() map[string]Bump
- func IsDiagnosticCode(code string) bool
- func Normalize(message string) string
- func SilentFailureCodes() []string
- type Bump
- type ChannelValue
- type Config
- type CorrectionTarget
- type DependencyKind
- type Depth
- type Diagnostic
- type Directives
- type Footer
- type Header
- type InlineDirectives
- type Limits
- type ParseError
- type Parser
- type Position
- type Propagate
- type PropagationConfig
- type ReleaseAs
- type ReleaseAsKind
- type Result
- type ScopeSet
- type ScopeTerm
- type Severity
- type Unit
- type Version
Examples ¶
Constants ¶
const ( DefaultSeparator = "---" DefaultMaxDescriptionLength = 100 DefaultPropagate = PropagatePatch DefaultDepth = Depth(0) DefaultChannelDepth = Depth(0) DefaultPropagateChannel = ChannelInherit )
Default configuration values (§14).
const ( DefaultUnitsPerMessage = 64 DefaultScopeTermsPerUnit = 256 DefaultMessageBytes = 1 << 20 )
Default parser bounds (§14.1). Unlike the rest of §14's safety limits, which gate a release run and are opt-in, these are always enforced: a hostile commit message is untrusted input, and exceeding a bound must be a diagnostic rather than unbounded work (§18.3).
const ( CodeE001 = "E001" // message is not valid UTF-8 CodeE002 = "E002" // message is empty CodeE100 = "E100" // unit header does not match the grammar CodeE101 = "E101" // type contains uppercase or illegal characters CodeE102 = "E102" // whitespace inside a scope-set other than after a comma CodeE103 = "E103" // unbalanced or nested parentheses CodeE104 = "E104" // empty scope-set CodeE110 = "E110" // duplicate inline directive sigil CodeE111 = "E111" // unknown inline directive value, or an illegally empty value CodeE112 = "E112" // inline and footer set the same key to different values CodeE113 = "E113" // "^^" combined with an explicit "+N" where N is not all CodeE120 = "E120" // missing or malformed ": " separator CodeE121 = "E121" // empty description CodeE140 = "E140" // unknown type under strictTypes CodeE141 = "E141" // release unit with "!" CodeE151 = "E151" // footer value is not valid for its key CodeE154 = "E154" // exact Release-As on a multi-package scope-set CodeE158 = "E158" // a limits.* cap was exceeded; message-scoped CodeE170 = "E170" // cancel unit with "!" CodeE171 = "E171" // cancel unit with directives or footers CodeE173 = "E173" // correction footer on a release unit CodeE180 = "E180" // reserved channel name "latest" CodeE181 = "E181" // channel name contains uppercase or illegal characters CodeW001 = "W001" // empty unit discarded CodeW101 = "W101" // type lowercased under lenient mode CodeW110 = "W110" // redundant restatement of a directive CodeW112 = "W112" // footer overrode inline under lenient mode CodeW120 = "W120" // description exceeds maxDescriptionLength CodeW121 = "W121" // missing space after ": " accepted under lenient mode CodeW132 = "W132" // multi-unit commit with unscoped units CodeW133 = "W133" // package both included and excluded CodeW140 = "W140" // unknown type mapped to none CodeW141 = "W141" // release unit with no directives CodeW150 = "W150" // unknown footer key ignored CodeW151 = "W151" // trailing paragraph nearly footer-shaped but treated as body CodeW152 = "W152" // redundant no-op propagation pairing CodeW155 = "W155" // footer key matches BREAKING CHANGE only case-insensitively CodeW156 = "W156" // a BREAKING CHANGE line sits in the body, not the footer block CodeW157 = "W157" // BREAKING CHANGE with an empty value CodeW201 = "W201" // a propagation value was supplied while its axis's depth is 0 CodeW207 = "W207" // a channel transition whose from equals its to; inert CodeW214 = "W214" // Reverts value is not a commit sha; footer is informational )
Diagnostic codes from §16 (Diagnostics registry).
Codes that require a workspace, a dependency graph or git history to detect are deliberately absent: this package parses messages, it does not compute releases. See the package documentation for the exact list.
const ( // FooterPropagateChannelDepth and FooterPropagateChannelScope are the // channel axis's counterparts of Propagate-Depth and Propagate-Scope // (§8.3a, §9.3). // FooterEdits and FooterDeletes are the correction footers of §7.4: an // ordinary unit carrying one restates or discards the pending records it // names. )
Canonical footer keys from the registry in §8.1.
const ( // ChannelStable is the non-prerelease line. Legal on either side of a // transition, and as a whole value. ChannelStable = "stable" // ChannelInherit means "the origin's channel". A Propagate-Channel value // only, never a side of a transition. ChannelInherit = "inherit" // ChannelNone disables channel propagation. A Propagate-Channel value // only, never a side of a transition. ChannelNone = "none" // ChannelAnyPrerelease is "*", legal only as a transition's from-side, // where it matches any prerelease channel and never matches stable. ChannelAnyPrerelease = "*" )
Reserved channel values (§11.2). None of them may be used as a channel name, and a package may not be named after them.
const ( TypeCancel = "cancel" TypeRelease = "release" )
Reserved type names that carry control semantics rather than a bump (§7).
Variables ¶
var ErrInvalidVersion = errors.New("ccme: invalid semantic version")
ErrInvalidVersion is returned by ParseVersion for any malformed input.
Functions ¶
func DefaultIssueTrailers ¶
func DefaultIssueTrailers() []string
DefaultIssueTrailers returns the issue-reference trailer keys, which are ignored for versioning but may be surfaced in a changelog (§4.5).
func DefaultMessageLevelTrailers ¶
func DefaultMessageLevelTrailers() []string
DefaultMessageLevelTrailers returns the trailer keys that describe authorship or review rather than release intent (§4.5). They are ignored wherever they appear and never prevent a paragraph from being a footer block.
func DefaultTypes ¶
DefaultTypes returns a fresh copy of the type-to-bump table of §7.1.
func IsDiagnosticCode ¶
IsDiagnosticCode reports whether code is one this package emits: a finding about a commit message itself, as opposed to one about the workspace, the graph or the git history, which are the caller's to produce. A tool presenting both kinds together uses it to tell them apart: to group them, to count them, or to let an operator silence the authoring findings of a history that predates the convention.
func Normalize ¶
Normalize applies the input normalisation of §4.1 to a cleaned commit message, i.e. the output of `git log --format=%B`:
- strip a leading UTF-8 BOM;
- normalise CRLF and CR line terminators to LF;
- strip trailing spaces and tabs from the end of each line, preserving leading whitespace, which is significant for footer continuations;
- strip trailing blank lines from the end of the message.
Nothing else is altered. Normalize is idempotent, and returns the input string unchanged, with no allocation, when it is already normalised, which is the common case for messages read straight from git.
func SilentFailureCodes ¶
func SilentFailureCodes() []string
SilentFailureCodes returns the warnings §16 singles out as silent-wrong-answer warnings rather than style notes: each one means the message says something different from what its author meant, with no error to stop it. The returned slice is a copy; callers may modify it freely.
Commit-lint implementations SHOULD reject a commit carrying any of them. W172 belongs to this set too but requires git history, so it is not emitted by this package.
Types ¶
type Bump ¶
type Bump int
Bump is a semantic-version increment level, ordered none < patch < minor < major (§2).
type ChannelValue ¶
type ChannelValue struct {
// Word is ChannelInherit or ChannelNone when the entire value is one of
// those. From and To are then empty.
Word string
// From is the transition's source, or empty when the value names only a
// target. ChannelAnyPrerelease matches any prerelease channel.
From string
// To is the target: a channel name or ChannelStable. Empty when Word is
// set.
To string
}
ChannelValue is a parsed channel directive value (§11.2):
channel-value = [ from ">" ] to from = channel-name / "stable" / "*" to = channel-name / "stable"
Propagate-Channel additionally accepts the whole-value words "inherit" and "none", which are reported in Word.
func (ChannelValue) IsTransition ¶
func (c ChannelValue) IsTransition() bool
IsTransition reports whether the value is a <from>><to> form.
func (ChannelValue) IsWord ¶
func (c ChannelValue) IsWord() bool
IsWord reports whether the whole value was inherit or none.
func (ChannelValue) IsZero ¶
func (c ChannelValue) IsZero() bool
IsZero reports whether no channel value was given at all.
func (ChannelValue) String ¶
func (c ChannelValue) String() string
String renders the value as it would be written.
type Config ¶
type Config struct {
// Separator is the unit separator line (§4.2, §4.3). It must be at least
// three ASCII-printable characters, must contain no whitespace, and must
// not begin with a character that can begin a type. Zero value: "---".
// Repositories that exchange patches by mail should set "%%%".
Separator string
// Types maps a type to its default direct bump (§7.1). A nil map selects
// DefaultTypes; a non-nil map replaces the table wholesale, so add to a
// copy of DefaultTypes rather than starting from scratch unless you mean
// to drop the standard types. Type names must consist of a-z only.
Types map[string]Bump
// StrictTypes turns an unknown type into E140 instead of W140.
StrictTypes bool
// Lenient downgrades selected errors to warnings (§16): an uppercase type
// is lowercased with W101, a missing space after ':' is accepted with
// W121, and a footer contradicting an inline directive wins with W112
// instead of raising E112. Two or more spaces after ':' remain E120 even
// here, because the extra space is indistinguishable from a description
// that begins with one (§5.5).
Lenient bool
// MaxDescriptionLength is the W120 threshold, counted in Unicode scalar
// values. Zero value: 100. A negative value disables the check.
MaxDescriptionLength int
// Propagation holds the propagation defaults.
Propagation PropagationConfig
// Limits are the always-enforced parser bounds of §14.1.
Limits Limits
// AllowedChannels restricts channel names (channels.allowed in §14). A nil
// slice is unrestricted; a non-nil slice rejects any channel name outside
// it with E181. "stable" is always accepted, because it is a graduation
// directive rather than a channel name (§11.5).
AllowedChannels []string
// MessageLevelTrailers are the authorship and review trailers ignored
// wherever they appear (§4.5). A nil slice selects
// DefaultMessageLevelTrailers; a non-nil empty slice disables the
// behaviour. Matching is case-insensitive.
MessageLevelTrailers []string
// IssueTrailers are the issue-reference trailers, ignored for versioning
// but surfaced on Footer.IssueReference for changelog use (§4.5). A nil
// slice selects DefaultIssueTrailers. Matching is case-insensitive.
IssueTrailers []string
}
Config is the complete parser configuration: every option in one struct.
The zero value is valid and means "the specification defaults", so Config{Lenient: true} changes exactly one thing. Any field left at its zero value is filled in from §14, which is why DefaultConfig is only needed when you want to read the defaults rather than set them.
Keys of §14 that affect release computation rather than parsing (tagFormat, initialVersion, preserveMajorZero, rangeStrategy, rootPathMap, ignoredPaths, branchChannels, propagation.respectRanges) are deliberately absent: this package parses messages.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns the specification defaults, fully populated. It is equivalent to Config{} once NewParser has filled in the zero values, and is the convenient starting point when you want to adjust one field:
cfg := ccme.DefaultConfig() cfg.Types["deps"] = ccme.BumpPatch p, err := ccme.NewParser(cfg)
func (Config) Clone ¶
Clone returns a deep copy, so that mutating the result cannot affect the original or any parser built from it.
The nil-versus-empty distinction is preserved exactly, because it is load-bearing: a nil slice selects the default, a non-nil empty one means "none".
type CorrectionTarget ¶
type CorrectionTarget struct {
// SHA is the full or abbreviated commit id, 7 to 64 lowercase hexadecimal
// characters. Empty when All is set.
SHA string
// UnitSelector is the 1-based unit index into the target commit, or 0 when
// no selector was written. A bare SHA is legal only for single-unit
// targets; the engine enforces that (E211).
UnitSelector int
// All reports the wildcard "*": every record pending for the carrying
// unit's resolved scope-set (§7.4.2).
All bool
// Raw is the value as written.
Raw string
}
CorrectionTarget is a parsed Edits or Deletes footer value (§7.4.1):
correction-value = "*" / sha [ "#" unit-no ]
Resolving the sha, the selector, and the correction's effect requires git history and is the release engine's §13.4b; the parser validates shape.
func (CorrectionTarget) IsWildcard ¶
func (t CorrectionTarget) IsWildcard() bool
IsWildcard reports whether the target is the "*" form.
func (CorrectionTarget) String ¶
func (t CorrectionTarget) String() string
String implements fmt.Stringer.
type DependencyKind ¶
type DependencyKind string
DependencyKind is a manifest dependency field traversed as a graph edge (§8.4).
const ( KindDependencies DependencyKind = "dependencies" KindDevDependencies DependencyKind = "devDependencies" KindPeerDependencies DependencyKind = "peerDependencies" KindOptionalDependencies DependencyKind = "optionalDependencies" KindAll DependencyKind = "*" )
Dependency kinds. KindAll is the wildcard "*", reusing the scope-set selector of §5.2: every kind is traversed, devDependencies included.
func DefaultPropagateKinds ¶
func DefaultPropagateKinds() []DependencyKind
DefaultPropagateKinds returns the manifest fields traversed by default (§8.4). devDependencies is deliberately absent.
func ParseDependencyKind ¶
func ParseDependencyKind(s string) (DependencyKind, bool)
ParseDependencyKind validates a single dependency-edge kind (§8.4).
type Depth ¶
type Depth int
Depth is a Propagate-Depth value (§8.3): the number of graph edges a propagation travels. DepthAll denotes the full transitive closure.
const DepthAll Depth = -1
DepthAll is the "+*" / "all" depth.
type Diagnostic ¶
type Diagnostic struct {
Code string
Severity Severity
Message string
Position Position
UnitIndex int // index into Result.Units, or -1 for message-level diagnostics
}
Diagnostic is a single entry produced by a parse. Every diagnostic carries the exact position at which it was raised, so callers can render a caret under the offending character (§20.7).
func (Diagnostic) IsError ¶
func (d Diagnostic) IsError() bool
IsError reports whether the diagnostic invalidates its unit.
type Directives ¶
type Directives struct {
Propagate Propagate
PropagateSet bool
Depth Depth
DepthSet bool
PropagateScope ScopeSet
PropagateScopeSet bool
PropagateChannel ChannelValue
PropagateChannelSet bool
ChannelDepth Depth
ChannelDepthSet bool
PropagateChannelScope ScopeSet
PropagateChannelScopeSet bool
Channel ChannelValue
ChannelSet bool
ReleaseAs *ReleaseAs
// Edits holds the targets this unit restates: each named record is
// discarded and this unit's type, breaking marker and description stand in
// its place. Applying them is the engine's §13.4b.
Edits []CorrectionTarget
// Deletes holds the targets this unit discards without restatement.
Deletes []CorrectionTarget
// Kinds are the manifest fields traversed as propagation edges. They come
// from configuration alone: §8.4 defines no per-unit override, because
// which dependency fields imply "must be republished" is a fact about the
// repository, not about any one commit.
//
// The slice aliases the parser's configuration and must be treated as
// read-only.
Kinds []DependencyKind
// Reverts holds the values of any Reverts footers, which are informational
// (§8.1).
Reverts []string
// BreakingChange is the text of a BREAKING CHANGE footer, if present.
BreakingChange string
}
Directives is the reconciled directive state of a unit: inline sigils and footers merged, then filled in from configuration and the spec defaults. The *Set fields report whether the value was stated by the author rather than inherited from a default.
type Footer ¶
type Footer struct {
Key string
CanonicalKey string
Value string
Separator string
Position Position
Known bool
// BREAKING-CHANGE case-insensitively but not exactly. Such a footer is
// NOT breaking (it is an unknown key) and carries W155 (§8.1.1).
MiscasedBreaking bool
// engine ignores wherever it appears (§4.5).
MessageLevel bool
// versioning but available to a changelog (§4.5).
IssueReference bool
}
Footer is one git trailer in a unit's final paragraph (§8.1).
func (Footer) IsBreakingChange ¶
IsBreakingChange reports whether this footer is a BREAKING CHANGE trailer.
type Header ¶
type Header struct {
// Raw is the header line exactly as it appeared after normalisation.
Raw string
// Type is the lowercase type.
Type string
// HasScopeSet reports whether parentheses were written at all. It is what
// distinguishes "feat: x" (derived scope, §6.2) from "feat(*): x".
HasScopeSet bool
// Scopes holds the scope terms in written order.
Scopes ScopeSet
// Inline holds the directives written with sigils.
Inline InlineDirectives
// Breaking reports whether "!" preceded the colon.
Breaking bool
// Description is the remainder of the line after ": ".
Description string
// Position is the start of the header in the message.
Position Position
}
Header is a parsed unit header (§5):
<type>[(<scope-set>)][<inline-directives>][!]: <description>
type InlineDirectives ¶
type InlineDirectives struct {
// Propagate is set by "^x" or a non-empty "^^x".
Propagate *Propagate
// Depth is set by "+N", by a bare or valued "^" (as 1), or by "^^" (as all).
Depth *Depth
// Channel is set by "%x": the unit's own channel.
Channel *ChannelValue
// PropagateChannel is set by "%%x": the channel given to dependents.
PropagateChannel *ChannelValue
// ChannelDepth is set by "++N", or implied as 1 by "%%".
ChannelDepth *Depth
// contains filtered or unexported fields
}
InlineDirectives holds the directives written in a header with sigils (§5.3). A nil field means the sigil was absent; it does not mean "default".
func (InlineDirectives) IsEmpty ¶
func (d InlineDirectives) IsEmpty() bool
IsEmpty reports whether the header carried no inline directive at all.
type Limits ¶
type Limits struct {
// UnitsPerMessage caps the number of units in one message. Zero value: 64.
UnitsPerMessage int
// ScopeTermsPerUnit caps the terms in one scope-set. Zero value: 256.
ScopeTermsPerUnit int
// MessageBytes caps the message length. Zero value: 1 MiB.
MessageBytes int
}
Limits are the parser bounds of §14.1. Exceeding any of them is E158, which is message-scoped: the commit contributes nothing.
The zero value of each field selects the default. The bounds cannot be disabled: a commit message is untrusted input (§18.3), so a negative value is rejected by Validate. Raise the numbers instead.
type ParseError ¶
type ParseError struct {
Diagnostics []Diagnostic
}
ParseError aggregates every error-severity diagnostic produced by a parse. A non-nil ParseError does not mean the Result is unusable: units without errors are still populated and still apply (§16).
func (*ParseError) Codes ¶
func (e *ParseError) Codes() []string
Codes returns the diagnostic codes in order, which is convenient in tests.
type Parser ¶
type Parser struct {
// contains filtered or unexported fields
}
Parser parses CCME commit messages. Construct one with NewParser, MustNewParser or DefaultParser; the zero value is not usable.
A Parser holds no mutable state and is safe for concurrent use.
func DefaultParser ¶
func DefaultParser() *Parser
DefaultParser returns a parser configured entirely from the specification defaults. It is shorthand for MustNewParser(Config{}).
func MustNewParser ¶
MustNewParser is NewParser for configurations known to be valid, such as package-level defaults. It panics on an invalid configuration.
func NewParser ¶
NewParser builds a Parser from a single configuration struct. Every zero-valued field is filled in from the specification defaults (§14), so NewParser(ccme.Config{}) is the fully default parser and NewParser(ccme.Config{Lenient: true}) changes exactly one thing.
The configuration is copied, so the caller may reuse or mutate the struct afterwards. An invalid configuration is returned as an error.
Example ¶
package main
import (
"fmt"
"github.com/yohimik/dispat/pkg/ccme"
)
func main() {
types := ccme.DefaultTypes()
types["deps"] = ccme.BumpPatch
p, err := ccme.NewParser(ccme.Config{
Separator: "%%%",
StrictTypes: true,
Types: types,
Propagation: ccme.PropagationConfig{Depth: ccme.DepthAll},
})
if err != nil {
fmt.Println("config error:", err)
return
}
res, err := p.Parse("deps(core): bump lockfile\n%%%\nfix(cli): guard nil")
fmt.Println("units:", len(res.Units), "err:", err)
fmt.Println("unit 0 bump:", res.Units[0].Bump)
fmt.Println("unit 0 depth:", res.Units[0].Directives.Depth)
}
Output: units: 2 err: <nil> unit 0 bump: patch unit 0 depth: all
func (*Parser) Config ¶
Config returns a deep copy of the parser's effective configuration, with every default already filled in.
func (*Parser) Parse ¶
Parse parses a complete commit message: normalisation (§4.1), splitting into units (§4.2), then header, body, footers and semantics for each unit.
The returned Result is always non-nil. A non-nil error means at least one error-severity diagnostic was raised; the units that parsed cleanly are still present and still apply, because an error invalidates only the offending unit (§16).
Example ¶
package main
import (
"fmt"
"github.com/yohimik/dispat/pkg/ccme"
)
func main() {
const message = `feat(@acme/api): add cursor pagination
---
fix(@acme/api): reject negative page sizes
---
docs(docs-site): document pagination`
p := ccme.DefaultParser()
res, _ := p.Parse(message)
for _, u := range res.ValidUnits() {
fmt.Printf("%d: %-5s %-12s %s\n", u.Index, u.Header.Type, u.Scopes(), u.Bump)
}
fmt.Println("message bump:", res.Bump())
}
Output: 0: feat @acme/api minor 1: fix @acme/api patch 2: docs docs-site none message bump: minor
Example (Diagnostics) ¶
package main
import (
"fmt"
"github.com/yohimik/dispat/pkg/ccme"
)
func main() {
p := ccme.DefaultParser()
res, err := p.Parse("feat(core)^^minor+2: broken directive")
fmt.Println("err != nil:", err != nil)
for _, d := range res.Diagnostics {
fmt.Printf("%s %s at %s\n", d.Severity, d.Code, d.Position)
}
}
Output: err != nil: true error E113 at 1:18
func (*Parser) ParseSubject ¶
ParseSubject parses a single commit subject, the header line on its own. It is the narrow entry point for commit-lint style checks; the message is normalised first, and a subject containing a line break is rejected.
Example ¶
package main
import (
"fmt"
"github.com/yohimik/dispat/pkg/ccme"
)
func main() {
p := ccme.DefaultParser()
res, err := p.ParseSubject("feat(@acme/core)^^minor%beta!: streaming reader")
if err != nil {
fmt.Println("error:", err)
return
}
u := res.Units[0]
fmt.Println("type: ", u.Header.Type)
fmt.Println("scopes: ", u.Scopes())
fmt.Println("breaking: ", u.Breaking)
fmt.Println("bump: ", u.Bump)
fmt.Println("propagate: ", u.Directives.Propagate)
fmt.Println("depth: ", u.Directives.Depth)
fmt.Println("channel: ", u.Directives.Channel)
fmt.Println("description:", u.Header.Description)
}
Output: type: feat scopes: @acme/core breaking: true bump: major propagate: minor depth: all channel: beta description: streaming reader
type Position ¶
Position is a location inside the normalised message. Both fields are 1-based; Column counts bytes, which is what a caret-pointing renderer needs.
type Propagate ¶
type Propagate string
Propagate is the bump handed to dependents of a unit's packages (§8.2).
const ( PropagateNone Propagate = "none" PropagatePatch Propagate = "patch" PropagateMinor Propagate = "minor" PropagateMajor Propagate = "major" PropagateInherit Propagate = "inherit" )
Propagate values.
func ParsePropagate ¶
ParsePropagate validates a Propagate value byte-for-byte. Abbreviations are rejected (§5.3).
type PropagationConfig ¶
type PropagationConfig struct {
// Bump is the default Propagate value. Zero value: PropagatePatch.
Bump Propagate
// Depth is the default Propagate-Depth (§8.3). Zero value: 0, which is
// also the specification default: a unit does not propagate unless it
// says so, so there is no ambiguity between "unset" and "no propagation".
//
// Repositories that bundle rather than declare their dependencies should
// set 1; use DepthAll for the full transitive closure.
Depth Depth
// ChannelDepth is the default Propagate-Channel-Depth (§8.3a). Zero value:
// 0: a unit moves nobody else's channel unless it says so. Set 1 or
// DepthAll to carry release trains along by default.
ChannelDepth Depth
// Kinds is the set of dependency edges propagation follows (§8.4). It is
// configuration only, since the spec has no per-unit override, so every unit
// of every message sees this list. A nil slice selects
// DefaultPropagateKinds; a non-nil empty slice traverses no edges at all.
Kinds []DependencyKind
// Channel is the default Propagate-Channel value. Zero value:
// ChannelInherit.
Channel string
}
PropagationConfig holds the configurable propagation defaults (§14). Every field is overridden by a directive written on the unit itself; the precedence chain is footer → inline sigil → this configuration → the specification default (§8.3). A footer wins over the sigil it contradicts, with E112, or with W112 under Config.Lenient.
Propagation has two independent axes (§5.3). The bump axis is Bump plus Depth, written "^", "^^" and "+N"; the channel axis is Channel plus ChannelDepth, written "%%" and "++N". Both depths default to 0, so neither axis reaches anybody until a unit or this configuration opts in.
type ReleaseAs ¶
type ReleaseAs struct {
Kind ReleaseAsKind
Version Version // valid when Kind is ReleaseAsExact
Raw string
}
ReleaseAs is a parsed Release-As footer value (§8.6). It never carries a bump: Release-As acts on the release, not on the size of the change.
type ReleaseAsKind ¶
type ReleaseAsKind int
ReleaseAsKind discriminates the three forms of a Release-As value (§8.6).
const ( // ReleaseAsExact pins a specific version. ReleaseAsExact ReleaseAsKind = iota // ReleaseAsNone holds the package: it is not released in this window, but // its pending units are retained rather than discarded, and accumulate // until the hold is lifted (§8.6.1). This is what distinguishes it from // cancel, which erases. ReleaseAsNone // ReleaseAsAuto lifts an active hold and returns to normal computation, // releasing everything that accumulated at the max() of all of it. ReleaseAsAuto )
Release-As forms. All three operate at the same level, the package for the current window, because Release-As decides whether and at what version a package is released, never how large a change is (§8.6).
There is deliberately no bump form: how large a change is, is a property of the change, and the type already declares it. Release-As: minor is E151.
func (ReleaseAsKind) IsHold ¶
func (k ReleaseAsKind) IsHold() bool
IsHold reports whether the directive pauses publishing (§8.6.1).
func (ReleaseAsKind) String ¶
func (k ReleaseAsKind) String() string
String implements fmt.Stringer.
type Result ¶
type Result struct {
// Message is the normalised message (§4.1).
Message string
// Units are every unit in written order, including units that failed to
// parse. Check Unit.Valid, or use ValidUnits.
Units []*Unit
// Diagnostics are every diagnostic raised.
//
// The order is deterministic and is a pure function of the input, as §17.2
// requires: unit-level diagnostics are grouped by unit in unit order, and
// within a unit they follow the order the parser raises them. Nothing here
// depends on map-iteration order.
Diagnostics []Diagnostic
}
Result is the outcome of a parse.
A Result keeps a reference to the normalised message: Units, bodies and descriptions are substrings of it, so retaining a Result retains the message.
func (*Result) Bump ¶
Bump returns the highest direct bump over the valid units. It is the message's contribution before scope resolution and propagation (§13.6).
func (*Result) Errors ¶
func (r *Result) Errors() []Diagnostic
Errors returns the error-severity diagnostics.
func (*Result) ValidUnits ¶
ValidUnits returns the units with no error-severity diagnostic.
When every unit is valid, the overwhelmingly common case, it returns Units itself rather than a copy, so treat the result as read-only.
func (*Result) Warnings ¶
func (r *Result) Warnings() []Diagnostic
Warnings returns the warning-severity diagnostics.
type ScopeSet ¶
type ScopeSet []ScopeTerm
ScopeSet is an ordered list of scope terms.
type ScopeTerm ¶
type ScopeTerm struct {
// Raw is the term as written, including any leading "-".
Raw string
// Name is the term without its exclusion marker.
Name string
// Exclude reports whether the term was written with a leading "-".
Exclude bool
// Position is where the term begins in the message.
Position Position
}
ScopeTerm is one comma-separated term of a scope-set (§5.2). Resolution of a term to concrete packages requires a workspace and is out of scope for this package; the term is reported exactly as written, with the leading "-" of an exclusion stripped from Name.
func (ScopeTerm) IsDerived ¶
IsDerived reports whether the term is ".", the file-derived set (§6.2).
type Severity ¶
type Severity int
Severity classifies a Diagnostic. Errors make the offending unit's contribution undefined; warnings never block a release (§16).
type Unit ¶
type Unit struct {
// Index is the unit's position in the message, starting at 0.
Index int
// Start is where the unit's header begins in the normalised message.
Start Position
// Raw is the unit's text.
Raw string
// Header is the parsed header. It is zero-valued when the header itself
// failed to parse.
Header Header
// Body is the free-form body, without the trailing footer block.
Body string
Footers []Footer
// Directives is the reconciled directive state: inline sigils and footers
// merged, then filled in from configuration and the spec defaults.
Directives Directives
// Breaking reports "!" on the header or a BREAKING CHANGE footer (§5.4).
Breaking bool
// TypeBump is the bump the type alone maps to (§7.1).
TypeBump Bump
// Bump is the unit's direct bump: TypeBump, raised to major by a breaking
// marker, then overridden by Release-As (§13.6).
Bump Bump
// Valid reports that no error-severity diagnostic is attached to the unit.
// An invalid unit contributes nothing, but its siblings still apply (§16).
Valid bool
// Diagnostics are the diagnostics raised for this unit.
Diagnostics []Diagnostic
}
Unit is one <header>[body][footers] block of a commit message (§2, §4.4).
func (*Unit) BreakingDescription ¶
BreakingDescription returns the text of a BREAKING CHANGE footer, if any.
func (*Unit) HasExplicitScope ¶
HasExplicitScope reports whether the header carried a scope-set. When it did not, the unit's packages are derived from the commit's changed files (§6.2).
func (*Unit) IsControl ¶
IsControl reports whether the unit is a control unit, which never produces a bump of its own (§7.1).
type Version ¶
type Version struct {
Major uint64
Minor uint64
Patch uint64
Prerelease []string
Build []string
Raw string
}
Version is a parsed SemVer 2.0.0 version. It exists so that an exact Release-As value can be validated at parse time; the release engine needs the same type when it compares against a baseline.
func ParseVersion ¶
ParseVersion parses a SemVer 2.0.0 version with a single left-to-right scan and no regular expression (§20.6). A leading "v" is rejected, as are leading zeros in numeric identifiers.
func (Version) Bumped ¶
Bumped returns the version incremented by b: the next major, minor or patch core above v's, with prerelease and build dropped. Bumping a *prerelease* baseline therefore both graduates it and moves the core: 1.2.0-beta.1 bumped minor is 1.3.0, not 1.2.0; whether the train should instead graduate to its own target is the release engine's §11 decision, made against the stable baseline, never through this helper. BumpNone returns v unchanged, Raw and build metadata included.
func (Version) Compare ¶
Compare orders two versions by SemVer precedence. Build metadata is ignored. It returns -1, 0 or 1.
func (Version) Core ¶
Core strips the prerelease and build components, leaving the MAJOR.MINOR.PATCH triple: 1.0.1-beta.4 -> 1.0.1.
func (Version) IsPrerelease ¶
IsPrerelease reports whether the version carries a prerelease component.
func (Version) MarshalText ¶
MarshalText implements encoding.TextMarshaler, rendering the version the way String does (build metadata dropped), so a Version crossing a module boundary, say a config model's initial versions, serialises as "1.2.3" rather than as a struct.
func (Version) String ¶
String renders the version without its build metadata, which is never carried into a computed version (§12.1).
func (*Version) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler via ParseVersion.