Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,49 @@ goreportcard.com: https://goreportcard.com/report/github.com/org/project
Coverage: https://app.codecov.io/gh/org/project
```

## Project Tagging System

To help users distinguish between different types of projects and their maintenance status, you can optionally add tags to project descriptions.

### Available Tags

#### Type Tags
- `[lib]` - For libraries, packages, and frameworks
- `[app]` - For standalone applications, tools, and command-line utilities

#### Status Tags
- `[active]` - Projects with recent commits (within the last year)
- `[stalled]` - Projects with no commits for more than 1 year but still functional
- `[unmaintained]` - Deprecated, archived, or no longer maintained projects

### Tag Usage Examples

**Good Examples:**
```md
- [cobra](https://github.com/spf13/cobra) [lib] [active] - A Commander for modern Go CLI interactions.
- [hugo](https://github.com/gohugoio/hugo) [app] [active] - Fast and Modern Static Website Engine.
- [gox](https://github.com/mitchellh/gox) [app] [stalled] - Dead simple, no frills Go cross compile tool.
```

### Guidelines

- Tags are optional but encouraged for new entries
- Place tags immediately after the project link, before the description
- Use square brackets with lowercase text
- Tags will be visually displayed as colored badges on the website
- Only use approved tag values (validation enforced in CI)
- Contributors should verify project status before adding status tags

### Tag Validation

- All tags are validated during continuous integration
- Invalid tags will cause build failures
- Maintainers may update status tags during periodic reviews

### Backwards Compatibility

Existing entries without tags will continue to work normally. The tagging system is additive and non-breaking.

## Congrats, your project got accepted - what now

You are an outstanding project now! Feel encouraged to tell others about it by adding one of these badges:
Expand Down
93 changes: 92 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"

"github.com/avelino/awesome-go/pkg/markdown"
Expand All @@ -19,11 +21,19 @@ import (
"github.com/avelino/awesome-go/pkg/slug"
)

// Tag represents a project tag with type, status, and display properties
type Tag struct {
Type string `json:"type"` // "lib", "app"
Status string `json:"status"` // "active", "stalled", "unmaintained"
Color string `json:"color"` // CSS class for styling
}

// Link contains info about awesome url
type Link struct {
Title string
URL string
Description string
Tags []Tag // Add tag support
}

// Category describe link category
Expand All @@ -49,6 +59,79 @@ var tplFs embed.FS

var tpl = template.Must(template.ParseFS(tplFs, "tmpl/*.tmpl.html", "tmpl/*.tmpl.xml"))

// parseTagsFromDescription extracts tags from description and returns (tags, cleanDescription)
func parseTagsFromDescription(description string) ([]Tag, string) {
var tags []Tag
cleanDescription := description

// Parse type tags
typePattern := regexp.MustCompile(`\[(lib|app)\]`)
typeMatches := typePattern.FindAllStringSubmatch(description, -1)
for _, match := range typeMatches {
tags = append(tags, Tag{
Type: match[1],
Color: getTagColor("type", match[1]),
})
cleanDescription = strings.ReplaceAll(cleanDescription, match[0], "")
}

// Parse status tags
statusPattern := regexp.MustCompile(`\[(active|stalled|unmaintained)\]`)
statusMatches := statusPattern.FindAllStringSubmatch(description, -1)
for _, match := range statusMatches {
tags = append(tags, Tag{
Status: match[1],
Color: getTagColor("status", match[1]),
})
cleanDescription = strings.ReplaceAll(cleanDescription, match[0], "")
}

// Clean up the description after tag removal
cleanDescription = cleanupDescription(cleanDescription)

return tags, cleanDescription
}

// cleanupDescription normalizes whitespace and removes stray separators after tag removal
func cleanupDescription(s string) string {
// Remove any leading hyphen with surrounding spaces that may remain after tag removal
leadingHyphenPattern := regexp.MustCompile(`^\s*-\s*`)
s = leadingHyphenPattern.ReplaceAllString(s, "")

// Collapse consecutive spaces into single spaces
spacePattern := regexp.MustCompile(`\s+`)
s = spacePattern.ReplaceAllString(s, " ")

// Remove spaces before punctuation
punctPattern := regexp.MustCompile(`\s+([.,:;!?])`)
s = punctPattern.ReplaceAllString(s, "$1")

// Trim leading and trailing spaces
return strings.TrimSpace(s)
}

// getTagColor returns CSS class for tag styling
func getTagColor(category, value string) string {
colorMap := map[string]map[string]string{
"type": {
"lib": "tag-lib",
"app": "tag-app",
},
"status": {
"active": "tag-active",
"stalled": "tag-stalled",
"unmaintained": "tag-unmaintained",
},
}

if colors, exists := colorMap[category]; exists {
if color, exists := colors[value]; exists {
return color
}
}
return "tag-default"
}

// Output files
const outDir = "out/" // NOTE: trailing slash is required

Expand Down Expand Up @@ -251,12 +334,20 @@ func extractCategory(doc *goquery.Document, selector string) (*Category, error)
ul.Find("li").Each(func(_ int, selLi *goquery.Selection) {
selLink := selLi.Find("a")
url, _ := selLink.Attr("href")

// Parse tags from description
fullText := selLi.Text()
titleAndDesc := strings.TrimPrefix(fullText, selLink.Text())

tags, cleanDescription := parseTagsFromDescription(titleAndDesc)

link := Link{
Title: selLink.Text(),
// FIXME(kazhuravlev): Title contains only title but
// description contains Title + description
Description: selLi.Text(),
Description: cleanDescription,
URL: url,
Tags: tags, // Add parsed tags
}
links = append(links, link)
})
Expand Down
Loading
Loading