aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile1
-rw-r--r--Dockerfile.updater1
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--pkg/app/handler/packages/show.go1
-rw-r--r--pkg/app/handler/packages/utils.go48
-rw-r--r--pkg/database/connection.go2
-rw-r--r--pkg/models/masks.go14
-rw-r--r--pkg/models/version.go1
-rw-r--r--pkg/portage/repository/mask.go255
-rw-r--r--pkg/portage/update.go2
-rw-r--r--web/templates/packages/packageheader.tmpl2
-rw-r--r--web/templates/packages/show.tmpl57
-rw-r--r--web/templates/packages/versionrow.tmpl42
14 files changed, 404 insertions, 25 deletions
diff --git a/Dockerfile b/Dockerfile
index 3eec982..be77b76 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,6 +2,7 @@ FROM golang:1.14.0 AS builder
WORKDIR /go/src/soko
COPY . /go/src/soko
RUN go get github.com/go-pg/pg/v9
+RUN go get github.com/mcuadros/go-version
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o bin .
FROM node:13 AS assetsbuilder
diff --git a/Dockerfile.updater b/Dockerfile.updater
index cdecdad..4129dad 100644
--- a/Dockerfile.updater
+++ b/Dockerfile.updater
@@ -2,6 +2,7 @@ FROM golang:1.14.0 AS builder
WORKDIR /go/src/soko
COPY . /go/src/soko
RUN go get github.com/go-pg/pg/v9
+RUN go get github.com/mcuadros/go-version
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o bin .
diff --git a/go.mod b/go.mod
index dfc455b..9971026 100644
--- a/go.mod
+++ b/go.mod
@@ -5,4 +5,5 @@ go 1.13
require (
github.com/go-pg/pg v8.0.6+incompatible
github.com/go-pg/pg/v9 v9.1.3
+ github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
)
diff --git a/go.sum b/go.sum
index 905df60..09b71af 100644
--- a/go.sum
+++ b/go.sum
@@ -24,6 +24,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEdvY3iDK6jfWXvEaM5OCKkjxPKoJRdB3Gg=
+github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
diff --git a/pkg/app/handler/packages/show.go b/pkg/app/handler/packages/show.go
index 8e77018..4a7a252 100644
--- a/pkg/app/handler/packages/show.go
+++ b/pkg/app/handler/packages/show.go
@@ -26,6 +26,7 @@ func Show(w http.ResponseWriter, r *http.Request) {
err := database.DBCon.Model(gpackage).
Where("atom = ?", atom).
Relation("Versions").
+ Relation("Versions.Masks").
Select()
if err != nil {
diff --git a/pkg/app/handler/packages/utils.go b/pkg/app/handler/packages/utils.go
index f2dd97d..a08a035 100644
--- a/pkg/app/handler/packages/utils.go
+++ b/pkg/app/handler/packages/utils.go
@@ -182,13 +182,16 @@ func getChangelogData(commits []*models.Commit, atom string) interface{} {
// GetFuncMap returns the FuncMap used in templates
func GetFuncMap() template.FuncMap {
return template.FuncMap{
- "contains": strings.Contains,
- "replaceall": strings.ReplaceAll,
- "gravatar": gravatar,
- "mkSlice": mkSlice,
- "getReverse": getReverse,
- "tolower": strings.ToLower,
- "formatRestricts": FormatRestricts,
+ "contains": strings.Contains,
+ "replaceall": strings.ReplaceAll,
+ "gravatar": gravatar,
+ "mkSlice": mkSlice,
+ "getReverse": getReverse,
+ "tolower": strings.ToLower,
+ "formatRestricts": FormatRestricts,
+ "isMasked": isMasked,
+ "getMask": getMask,
+ "showRemovalNotice": showRemovalNotice,
}
}
@@ -295,3 +298,34 @@ func FormatRestricts(restricts []string) string {
result = utils2.Deduplicate(result)
return strings.Join(result, ", ")
}
+
+// isMasked returns true if any version is masked
+func isMasked(versions []*models.Version) bool {
+ for _, version := range versions {
+ if len(version.Masks) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// getMask returns the mask entry of the first version that is masked
+func getMask(versions []*models.Version) *models.Mask {
+ for _, version := range versions {
+ if len(version.Masks) > 0 {
+ return version.Masks[0]
+ }
+ }
+ return nil
+}
+
+// showRemovalNotice if all versions of the package are masked
+func showRemovalNotice(versions []*models.Version) bool {
+ showNotice := false
+ for _, version := range versions {
+ if len(version.Masks) > 0 && version.Masks[0].Versions == version.Atom {
+ showNotice = true
+ }
+ }
+ return showNotice
+}
diff --git a/pkg/database/connection.go b/pkg/database/connection.go
index d481731..12e9174 100644
--- a/pkg/database/connection.go
+++ b/pkg/database/connection.go
@@ -29,6 +29,8 @@ func CreateSchema() error {
(*models.CommitToPackage)(nil),
(*models.CommitToVersion)(nil),
(*models.Useflag)(nil),
+ (*models.Mask)(nil),
+ (*models.MaskToVersion)(nil),
(*models.Application)(nil)} {
err := DBCon.CreateTable(model, &orm.CreateTableOptions{
diff --git a/pkg/models/masks.go b/pkg/models/masks.go
index d7adca0..623d282 100644
--- a/pkg/models/masks.go
+++ b/pkg/models/masks.go
@@ -2,6 +2,18 @@
package models
+import "time"
+
type Mask struct {
- Description string
+ Versions string `pg:",pk"`
+ Author string
+ AuthorEmail string
+ Date time.Time
+ Reason string
+}
+
+type MaskToVersion struct {
+ Id string `pg:",pk"`
+ MaskVersions string
+ VersionId string
}
diff --git a/pkg/models/version.go b/pkg/models/version.go
index c9a5130..4ac908e 100644
--- a/pkg/models/version.go
+++ b/pkg/models/version.go
@@ -19,4 +19,5 @@ type Version struct {
License string
Description string
Commits []*Commit `pg:"many2many:commit_to_versions,joinFK:commit_id"`
+ Masks []*Mask `pg:"many2many:mask_to_versions,joinFK:mask_versions"`
}
diff --git a/pkg/portage/repository/mask.go b/pkg/portage/repository/mask.go
index 723aa83..b5cc6da 100644
--- a/pkg/portage/repository/mask.go
+++ b/pkg/portage/repository/mask.go
@@ -1,13 +1,264 @@
// Contains functions to import package mask entries into the database
+//
+// Example
+//
+// ## # Dev E. Loper <developer@gentoo.org> (2019-07-01)
+// ## # Masking these versions until we can get the
+// ## # v4l stuff to work properly again
+// ## =media-video/mplayer-0.90_pre5
+// ## =media-video/mplayer-0.90_pre5-r1
+//
package repository
+import (
+ "github.com/go-pg/pg/v9"
+ "github.com/mcuadros/go-version"
+ "regexp"
+ "soko/pkg/database"
+ "soko/pkg/logger"
+ "soko/pkg/models"
+ "soko/pkg/portage/utils"
+ "strings"
+ "time"
+)
+
// isMask checks whether the path
// points to a package.mask file
func isMask(path string) bool {
return path == "profiles/package.mask"
}
-func UpdateMask(line string) {
- //TODO
+// UpdateMask updates all entries in
+// the Mask table in the database
+func UpdateMask(path string) {
+
+ splittedLine := strings.Split(path, "\t")
+
+ if len(splittedLine) == 2 {
+ status := splittedLine[0]
+ changedFile := splittedLine[1]
+
+ if status != "D" && isMask(changedFile) {
+
+ logger.Info.Println("Updating Masks")
+
+ for _, packageMask := range getMasks(changedFile) {
+ parsePackageMask(packageMask)
+ }
+ }
+ }
+}
+
+// versionSpecifierToPackageAtom returns the package atom from a given version specifier
+func versionSpecifierToPackageAtom(versionSpecifier string) string {
+ gpackage := strings.ReplaceAll(versionSpecifier, ">", "")
+ gpackage = strings.ReplaceAll(gpackage, "<", "")
+ gpackage = strings.ReplaceAll(gpackage, "=", "")
+ gpackage = strings.ReplaceAll(gpackage, "~", "")
+
+ gpackage = strings.Split(gpackage, ":")[0]
+
+ versionnumber := regexp.MustCompile(`-[0-9]`)
+ gpackage = versionnumber.Split(gpackage, 2)[0]
+
+ return gpackage
+}
+
+// parseAuthorLine parses the first line in the package.mask file
+// and returns the author name, author email and the date
+func parseAuthorLine(authorLine string) (string, string, time.Time) {
+ author := strings.TrimSpace(strings.Split(authorLine, "<")[0])
+ author = strings.ReplaceAll(author, "#", "")
+ authorEmail := strings.TrimSpace(strings.Split(strings.Split(authorLine, "<")[1], ">")[0])
+ date := strings.TrimSpace(strings.Split(authorLine, ">")[1])
+ date = strings.ReplaceAll(date, "(", "")
+ date = strings.ReplaceAll(date, ")", "")
+ parsedDate, err := time.Parse("2006-01-02", date)
+ if err != nil {
+ logger.Error.Println("Error while parsing package mask date: " + date)
+ logger.Error.Println(err)
+ }
+ return author, authorEmail, parsedDate
+}
+
+// parse the package.mask entries and
+// update the Mask table in the database
+func parsePackageMask(packageMask string) {
+ packageMaskLines := strings.Split(packageMask, "\n")
+ if len(packageMaskLines) >= 3 {
+ packageMaskLine, packageMaskLines := packageMaskLines[0], packageMaskLines[1:]
+ author, authorEmail, date := parseAuthorLine(packageMaskLine)
+
+ reason := ""
+ packageMaskLine, packageMaskLines = packageMaskLines[0], packageMaskLines[1:]
+ for strings.HasPrefix(packageMaskLine, "#") {
+ reason = reason + " " + strings.Replace(packageMaskLine, "# ", "", 1)
+ packageMaskLine, packageMaskLines = packageMaskLines[0], packageMaskLines[1:]
+ }
+
+ packageMaskLines = append(packageMaskLines, packageMaskLine)
+
+ for _, version := range packageMaskLines {
+ useflag := &models.Mask{
+ Author: author,
+ AuthorEmail: authorEmail,
+ Date: date,
+ Reason: reason,
+ Versions: version,
+ }
+
+ _, err := database.DBCon.Model(useflag).OnConflict("(versions) DO UPDATE").Insert()
+
+ if err != nil {
+ logger.Error.Println("Error while inserting/updating package mask entry")
+ logger.Error.Println(err)
+ }
+ }
+ }
+
+}
+
+// get all mask entries from the package.mask file
+func getMasks(path string) []string {
+ var masks []string
+ lines, err := utils.ReadLines(path)
+
+ if err != nil {
+ logger.Error.Println("Could not read Masks file. Abort masks import")
+ logger.Error.Println(err)
+ return masks
+ }
+
+ line, lines := lines[0], lines[1:]
+ for !strings.Contains(line, "#--- END OF EXAMPLES ---") {
+ line, lines = lines[0], lines[1:]
+ }
+ lines = lines[1:]
+
+ return strings.Split(strings.Join(lines, "\n"), "\n\n")
+}
+
+// Calculate all versions that are currently
+// masked and update the MaskToVersion Table
+func CalculateMaskedVersions() {
+
+ // TODO clear whole table before starting
+
+ var masks []*models.Mask
+ err := database.DBCon.Model(&masks).Select()
+ if err != nil && err != pg.ErrNoRows {
+ logger.Error.Println("Failed to retrieve package masks. Aborting update")
+ logger.Error.Println(err)
+ }
+
+ for _, mask := range masks {
+ versionSpecifier := mask.Versions
+ packageAtom := versionSpecifierToPackageAtom(versionSpecifier)
+ var versions []*models.Version
+
+ if strings.HasPrefix(versionSpecifier, "=") {
+ versions = exaktVersion(versionSpecifier, packageAtom)
+ } else if strings.HasPrefix(versionSpecifier, "<=") {
+ versions = comparedVersions("<=", versionSpecifier, packageAtom)
+ } else if strings.HasPrefix(versionSpecifier, "<") {
+ versions = comparedVersions("<", versionSpecifier, packageAtom)
+ } else if strings.HasPrefix(versionSpecifier, ">=") {
+ versions = comparedVersions(">=", versionSpecifier, packageAtom)
+ } else if strings.HasPrefix(versionSpecifier, ">") {
+ versions = comparedVersions(">", versionSpecifier, packageAtom)
+ } else if strings.HasPrefix(versionSpecifier, "~") {
+ versions = allRevisions(versionSpecifier, packageAtom)
+ } else if strings.Contains(versionSpecifier, ":") {
+ versions = versionsWithSlot(versionSpecifier, packageAtom)
+ } else {
+ versions = allVersions(versionSpecifier, packageAtom)
+ }
+
+ maskVersions(versionSpecifier, versions)
+ }
+}
+
+// comparedVersions computes and returns all versions that are >=, >, <= or < than then given version
+func comparedVersions(operator string, versionSpecifier string, packageAtom string) []*models.Version {
+ var results []*models.Version
+ var versions []*models.Version
+ versionSpecifier = strings.ReplaceAll(versionSpecifier, operator, "")
+ versionSpecifier = strings.ReplaceAll(versionSpecifier, packageAtom+"-", "")
+ versionSpecifier = strings.Split(versionSpecifier, ":")[0]
+
+ database.DBCon.Model(&versions).
+ Where("atom = ?", packageAtom).
+ Select()
+
+ for _, v := range versions {
+ if version.Compare(version.Normalize(v.Version), version.Normalize(versionSpecifier), operator) {
+ results = append(results, v)
+ }
+ }
+ return results
+}
+
+// allRevisions returns all revisions of the given version
+func allRevisions(versionSpecifier string, packageAtom string) []*models.Version {
+ var versions []*models.Version
+ revision := regexp.MustCompile(`-r[0-9]*$`)
+ versionWithoutRevision := revision.Split(versionSpecifier, 1)[0]
+ versionWithoutRevision = strings.ReplaceAll(versionWithoutRevision, "~", "")
+ logger.Info.Println("id LIKE " + versionWithoutRevision + "%")
+ database.DBCon.Model(&versions).
+ Where("id LIKE ?", versionWithoutRevision+"%").
+ Select()
+
+ return versions
+}
+
+// exaktVersion returns the exact version specified in the versionSpecifier
+func exaktVersion(versionSpecifier string, packageAtom string) []*models.Version {
+ var versions []*models.Version
+ database.DBCon.Model(&versions).
+ Where("id = ?", versionSpecifier).
+ Select()
+
+ return versions
+}
+
+// TODO include subslot
+// versionsWithSlot returns all versions with the given slot
+func versionsWithSlot(versionSpecifier string, packageAtom string) []*models.Version {
+ var versions []*models.Version
+ slot := strings.Split(versionSpecifier, ":")[1]
+
+ database.DBCon.Model(&versions).
+ Where("atom = ?", packageAtom).
+ Where("slot = ?", slot).
+ Select()
+
+ return versions
+}
+
+// allVersions returns all versions of the given package
+func allVersions(versionSpecifier string, packageAtom string) []*models.Version {
+ var versions []*models.Version
+ database.DBCon.Model(&versions).
+ Where("atom = ?", packageAtom).
+ Select()
+ return versions
+}
+
+// maskVersions updates the MaskToVersion table using the given versions
+func maskVersions(versionSpecifier string, versions []*models.Version) {
+
+ for _, version := range versions {
+ maskToVersion := &models.MaskToVersion{
+ Id: versionSpecifier + "-" + version.Id,
+ MaskVersions: versionSpecifier,
+ VersionId: version.Id,
+ }
+
+ _, err := database.DBCon.Model(maskToVersion).OnConflict("(id) DO UPDATE").Insert()
+
+ logger.Error.Println("Error while inserting mask to version entry")
+ logger.Error.Println(err)
+ }
}
diff --git a/pkg/portage/update.go b/pkg/portage/update.go
index 17804ca..bf08590 100644
--- a/pkg/portage/update.go
+++ b/pkg/portage/update.go
@@ -33,6 +33,8 @@ func Update() {
updatePackageData()
updateHistory()
+ repository.CalculateMaskedVersions()
+
}
// updateMetadata updates all USE flags, package masks and arches in the database
diff --git a/web/templates/packages/packageheader.tmpl b/web/templates/packages/packageheader.tmpl
index 34d916b..b9197ed 100644
--- a/web/templates/packages/packageheader.tmpl
+++ b/web/templates/packages/packageheader.tmpl
@@ -27,7 +27,7 @@
<!-- unless package.homepage.nil? || package.homepage.first.nil? || package.homepage.first.empty? -->
<p class="kk-package-homepage">
- <a href="{{(index .Versions 0).Homepage}}">{{ ( index (index .Versions 0).Homepage 0) }}</a>
+ <a href="{{ ( index (index .Versions 0).Homepage 0) }}">{{ ( index (index .Versions 0).Homepage 0) }}</a>
</p>
</div>
diff --git a/web/templates/packages/show.tmpl b/web/templates/packages/show.tmpl
index 2aee1c5..a952a47 100644
--- a/web/templates/packages/show.tmpl
+++ b/web/templates/packages/show.tmpl
@@ -17,16 +17,59 @@
{{template "versions" .}}
- <!-- if @package.removal_pending? -->
- <!-- render partial: 'removal_notice', object: @package, as: 'package' -->
- <!-- end -->
-
- <!-- render partial: 'maintainer_needed_notice', object: @package, as: 'package' -->
+ {{ if isMasked .Versions }}
+ <div class="card kk-mask mb-3">
+ <div class="card-body">
+ <h4 class="card-title mb-0">
+ Masks
+ </h4>
+ </div>
+ <ul class="list-group list-group-flush kk-mask">
+ <li class="list-group-item kk-mask">
+ {{ if showRemovalNotice .Versions }}
+ <p style="color:#721c24;">
+ <strong><span class="fa fa-fw fa-warning"></span> This package is masked and could be removed soon!</strong><br>
+ The mask comment indicates that this package is scheduled for removal from our package repository.<br>
+ Please review the mask information below for more details.
+ </p>
+ {{end}}
+
+ <strong class="kk-mask-reason">{{(getMask .Versions).Reason}}</strong>
+
+ <div class="kk-mask-details">
+
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ Affected packages
+ </div>
+ <div class="col-xs-12 col-md-9 kk-mask-atoms overflow-hidden">
+ {{(getMask .Versions).Versions}}
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ Author/Date
+ </div>
+ <div class="col-xs-12 col-md-9">
+ {{(getMask .Versions).Author}} &lt;{{(getMask .Versions).AuthorEmail}}&gt; <span class="text-muted">({{(getMask .Versions).Date}})</span>
+ </div>
+ </div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ {{end}}
+
+ {{ if not .Package.Maintainers }}
+ <div class="alert alert-info">
+ <strong><span class="fa fa-fw fa-wrench"></span> This package needs a new maintainer!</strong><br>
+ If you are interested in helping with the maintenance of opencsg, please get in touch with our
+ <a href="https://wiki.gentoo.org/wiki/Project:Proxy_Maintainers" class="alert-link">Proxy Maintainers team</a>.
+ </div>
+ {{end}}
{{template "metadata" .}}
- <!-- render partial: 'masks', object: @package.versions, as: 'versions' -->
-
{{template "changelogwrapper" .}}
</div>
diff --git a/web/templates/packages/versionrow.tmpl b/web/templates/packages/versionrow.tmpl
index 939e95c..f6a93f8 100644
--- a/web/templates/packages/versionrow.tmpl
+++ b/web/templates/packages/versionrow.tmpl
@@ -15,21 +15,49 @@
{{ if contains $.Keywords (print "~" .) }}
- <td class="kk-keyword kk-keyword-testing" title="{{$.Version}} is testing on amd64">
- <svg height="16" class="octicon octicon-diff-modified" viewBox="0 0 14 16" version="1.1" width="14" aria-hidden="true"><path fill-rule="evenodd" d="M13 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z"></path></svg>
- <span class="sr-only">~{{.}}</span>
+ {{ if ge (len $.Masks) 1 }}
+ <td class="kk-keyword kk-keyword-masked" title="{{$.Version}} is masked (testing) on {{.}}">
+ <svg height="16" class="octicon octicon-diff-modified" viewBox="0 0 14 16" version="1.1" width="14" aria-hidden="true"><path fill-rule="evenodd" d="M13 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 13H1V2h12v12zm-8.5-2H3v-1.5L9.5 4H11v1.5L4.5 12z"></path></svg>
+ <span class="sr-only">~{{.}}</span>
+ </td>
+ {{else}}
+ <td class="kk-keyword kk-keyword-testing" title="{{$.Version}} is testing on {{.}}">
+ <svg height="16" class="octicon octicon-diff-modified" viewBox="0 0 14 16" version="1.1" width="14" aria-hidden="true"><path fill-rule="evenodd" d="M13 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z"></path></svg>
+ <span class="sr-only">~{{.}}</span>
+ </td>
+ {{end}}
+
+ {{ else if contains $.Keywords (print "-" .) }}
+
+ <td class="kk-keyword kk-keyword-unavailable" title="{{$.Version}} is unavailable on {{.}}">
+ <svg height="16" class="octicon octicon-diff-removed" viewBox="0 0 14 16" version="1.1" width="14" aria-hidden="true"><path fill-rule="evenodd" d="M13 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 13H1V2h12v12zm-2-5H3V7h8v2z"></path></svg>
+ <span class="sr-only">-{{.}}</span>
</td>
{{ else if contains $.Keywords . }}
- <td class="kk-keyword kk-keyword-stable" title="{{$.Version}} is stable on amd64">
- <svg height="16" class="octicon octicon-diff-added" viewBox="0 0 14 16" version="1.1" width="14" aria-hidden="true"><path fill-rule="evenodd" d="M13 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z"></path></svg>
- <span class="sr-only">{{.}}</span>
+ {{ if ge (len $.Masks) 1 }}
+ <td class="kk-keyword kk-keyword-masked" title="{{$.Version}} is masked (stable) on {{.}}">
+ <svg height="16" class="octicon octicon-diff-added" viewBox="0 0 14 16" version="1.1" width="14" aria-hidden="true"><path fill-rule="evenodd" d="M13 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 13H1V2h12v12zm-8.5-2H3v-1.5L9.5 4H11v1.5L4.5 12z"></path></svg>
+ <span class="sr-only">{{.}}</span>
+ </td>
+ {{else}}
+ <td class="kk-keyword kk-keyword-stable" title="{{$.Version}} is stable on {{.}}">
+ <svg height="16" class="octicon octicon-diff-added" viewBox="0 0 14 16" version="1.1" width="14" aria-hidden="true"><path fill-rule="evenodd" d="M13 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z"></path></svg>
+ <span class="sr-only">{{.}}</span>
+ </td>
+ {{end}}
+
+ {{ else if contains $.Keywords "-*" }}
+
+ <td class="kk-keyword kk-keyword-unavailable" title="{{$.Version}} is unavailable on {{.}}">
+ <svg height="16" class="octicon octicon-diff-removed" viewBox="0 0 14 16" version="1.1" width="14" aria-hidden="true"><path fill-rule="evenodd" d="M13 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 13H1V2h12v12zm-2-5H3V7h8v2z"></path></svg>
+ <span class="sr-only">-{{.}}</span>
</td>
{{else}}
- <td class="kk-keyword kk-keyword-unkown" title="{{$.Version}} is unknown on amd64">
+ <td class="kk-keyword kk-keyword-unkown" title="{{$.Version}} is unknown on {{.}}">
<span class="sr-only">{{.}}</span>
</td>