Skip to content

Commit

Permalink
Renamed FindLabels* to ParseLabels* in graffiti
Browse files Browse the repository at this point in the history
Renamed Label struct to ParsedLabel.

Added `graffiti match` command.
  • Loading branch information
zomglings committed Jul 17, 2024
1 parent e56867c commit 312453a
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test-web3:
npx hardhat test

test-graffiti:
go test ./cmd/graffiti
go test ./cmd/graffiti -v

test: test-web3 test-graffiti

Expand Down
82 changes: 80 additions & 2 deletions cmd/graffiti/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"strings"

"github.com/spf13/cobra"
)
Expand All @@ -29,7 +31,8 @@ func CreateRootCommand() *cobra.Command {

tagsCmd := CreateTagsCommand()
numberCmd := CreateNumberCommand()
rootCmd.AddCommand(tagsCmd, numberCmd)
matchCmd := CreateMatchCommand()
rootCmd.AddCommand(tagsCmd, numberCmd, matchCmd)

// By default, cobra Command objects write to stderr. We have to forcibly set them to output to
// stdout.
Expand Down Expand Up @@ -67,7 +70,7 @@ func CreateTagsCommand() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
tags := FindTags(content, true)
tags := ParseTags(content, true)
linesNeeded := map[int]bool{}
lines := map[int]string{}
if showLines {
Expand Down Expand Up @@ -167,6 +170,81 @@ func CreateNumberCommand() *cobra.Command {
return numberCmd
}

func CreateMatchCommand() *cobra.Command {
var sourceFileRaw, tag, targetFileRaw string
var source, target []byte
var missing bool

matchCmd := &cobra.Command{
Use: "match",
Short: "Match the labels in a file with a given tag",
PreRunE: func(cmd *cobra.Command, args []string) error {
if tag == "" {
return errors.New("-t/--tag must be provided")
}

if sourceFileRaw == "" {
return errors.New("-s/--source must be provided")
}

if targetFileRaw == "" {
return errors.New("-T/--target must be provided")
}

sourceFile, sourceFileErr := os.Open(sourceFileRaw)
if sourceFileErr != nil {
return sourceFileErr
}
defer sourceFile.Close()
var readErr error
source, readErr = io.ReadAll(sourceFile)
if readErr != nil {
return readErr
}

targetFile, targetFileErr := os.Open(targetFileRaw)
if targetFileErr != nil {
return targetFileErr
}
defer targetFile.Close()
target, readErr = io.ReadAll(targetFile)
return readErr
},
RunE: func(cmd *cobra.Command, args []string) error {
sourceMatches := Match(source, tag, true)
targetMatches := Match(target, tag, true)

for label, parsedLabels := range sourceMatches {
sourceLines := make([]string, len(parsedLabels))
for i, parsedLabel := range parsedLabels {
sourceLines[i] = fmt.Sprintf("%d", parsedLabel.LineNumber)
}
parsedLabelsFromTarget, existsInTarget := targetMatches[label]
if existsInTarget {
if !missing {
targetLines := make([]string, len(parsedLabelsFromTarget))
for i, parsedLabel := range parsedLabelsFromTarget {
targetLines[i] = fmt.Sprintf("%d", parsedLabel.LineNumber)
}
cmd.Printf("- - -\nSource label: %s\nOccurs in source on lines: %s\nOccurs in target on lines: %s\n", label, strings.Join(sourceLines, ", "), strings.Join(targetLines, ", "))
}
} else {
cmd.Printf("- - -\nSource label: %s\nOccurs in source on lines: %s\nDoes not occur in target\n", label, strings.Join(sourceLines, ", "))
}
}

return nil
},
}

matchCmd.Flags().StringVarP(&sourceFileRaw, "source", "s", "", "The file containing the labels to be matched against.")
matchCmd.Flags().StringVarP(&targetFileRaw, "target", "T", "", "The file containing the labels to be matched.")
matchCmd.Flags().StringVarP(&tag, "tag", "t", "", "The tag to filter labels on.")
matchCmd.Flags().BoolVarP(&missing, "missing", "m", false, "Only show labels that are in the source file but not in the target file.")

return matchCmd
}

func CreateCompletionCommand(rootCmd *cobra.Command) *cobra.Command {
completionCmd := &cobra.Command{
Use: "completion",
Expand Down
63 changes: 45 additions & 18 deletions cmd/graffiti/graffiti.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

// Represents a parsed label.
// A label has the form TAG-modifier - see LabelRegexp for full details.
type Label struct {
type ParsedLabel struct {
TagStartPosition int
TagEndPosition int
ModifierStartPosition int
Expand All @@ -35,13 +35,13 @@ type Label struct {
var LabelRegexp *regexp.Regexp = regexp.MustCompile(`([A-Z]+[A-Z0-9]+)-([\*a-z0-9]+)`)

// Finds all labels (as defined by LabelRegexp) in the given content.
func FindLabels(content []byte) []Label {
func ParseLabels(content []byte) []ParsedLabel {
matches := LabelRegexp.FindAllSubmatchIndex(content, -1)

labels := make([]Label, len(matches))
labels := make([]ParsedLabel, len(matches))

for i, match := range matches {
labels[i] = Label{
labels[i] = ParsedLabel{
TagStartPosition: match[2],
TagEndPosition: match[3],
ModifierStartPosition: match[4],
Expand All @@ -62,17 +62,22 @@ func FindLabels(content []byte) []Label {
return labels
}

// Returns the string representation of a parsed label.
func Label(p ParsedLabel) string {
return fmt.Sprintf("%s-%s", p.Tag, p.Modifier)
}

// Finds all the labels in the given content, and also marks their line numbers.
func FindLabelsWithLineNumbers(content []byte) []Label {
func ParseLabelsWithLineNumbers(content []byte) []ParsedLabel {
contentReader := bytes.NewReader(content)
scanner := bufio.NewScanner(contentReader)

labels := []Label{}
labels := []ParsedLabel{}

currentLine := 0
for scanner.Scan() {
currentLine++
lineLabels := FindLabels(scanner.Bytes())
lineLabels := ParseLabels(scanner.Bytes())
for _, lineLabel := range lineLabels {
lineLabel.IncludesLineNumbers = true
lineLabel.LineNumber = currentLine
Expand All @@ -85,19 +90,19 @@ func FindLabelsWithLineNumbers(content []byte) []Label {

// Groups the labels in the given content by their tags.
// If lines = true, parses labels relative to its line in the content, otherwise parses absolutely.
func FindTags(content []byte, lines bool) map[string][]Label {
var labels []Label
func ParseTags(content []byte, lines bool) map[string][]ParsedLabel {
var labels []ParsedLabel
if lines {
labels = FindLabelsWithLineNumbers(content)
labels = ParseLabelsWithLineNumbers(content)
} else {
labels = FindLabels(content)
labels = ParseLabels(content)
}

tagIndex := make(map[string][]Label)
tagIndex := make(map[string][]ParsedLabel)

for _, label := range labels {
if _, ok := tagIndex[label.Tag]; !ok {
tagIndex[label.Tag] = []Label{}
tagIndex[label.Tag] = []ParsedLabel{}
}
tagIndex[label.Tag] = append(tagIndex[label.Tag], label)
}
Expand All @@ -113,10 +118,10 @@ func FindTags(content []byte, lines bool) map[string][]Label {
// If a label with the given tag has a modifier which is larger than the total number of labels with
// the given tag, then that could create a gap. graffiti does not renumber already numbered labels.
// Note that the modifier end positions in the resulting labels reflect the pre-numbering positions.
func Number(labels []Label) []Label {
func Number(labels []ParsedLabel) []ParsedLabel {
existingNumbers := map[int]bool{}

numberedLabels := make([]Label, len(labels))
numberedLabels := make([]ParsedLabel, len(labels))

for _, label := range labels {
if label.ModifierIsInt {
Expand All @@ -127,7 +132,7 @@ func Number(labels []Label) []Label {
currentNumber := 1
for i, label := range labels {
if label.ModifierIsInt {
numberedLabels[i] = Label{
numberedLabels[i] = ParsedLabel{
TagStartPosition: label.TagStartPosition,
TagEndPosition: label.TagEndPosition,
ModifierStartPosition: label.ModifierStartPosition,
Expand All @@ -143,7 +148,7 @@ func Number(labels []Label) []Label {
for existingNumbers[currentNumber] {
currentNumber++
}
numberedLabels[i] = Label{
numberedLabels[i] = ParsedLabel{
TagStartPosition: label.TagStartPosition,
TagEndPosition: label.TagEndPosition,
ModifierStartPosition: label.ModifierStartPosition,
Expand All @@ -170,7 +175,7 @@ func ApplyNumbering(content []byte, tag string) []byte {
updatedContent := make([]byte, len(content))
copy(updatedContent, content)

tagLabels := FindTags(content, false)
tagLabels := ParseTags(content, false)
labels, ok := tagLabels[tag]
if !ok {
return content
Expand All @@ -185,3 +190,25 @@ func ApplyNumbering(content []byte, tag string) []byte {

return updatedContent
}

// Indexes all labels parsed from the given content by their string representations.
func Match(content []byte, tag string, lines bool) map[string][]ParsedLabel {
var contentLabels []ParsedLabel
if lines {
contentLabels = ParseLabelsWithLineNumbers(content)
} else {
contentLabels = ParseLabels(content)
}

contentMatches := map[string][]ParsedLabel{}
for _, label := range contentLabels {
if label.Tag == tag {
if _, ok := contentMatches[Label(label)]; !ok {
contentMatches[Label(label)] = []ParsedLabel{}
}
contentMatches[Label(label)] = append(contentMatches[Label(label)], label)
}
}

return contentMatches
}
28 changes: 14 additions & 14 deletions cmd/graffiti/graffiti_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"testing"
)

func TestFindLabels(t *testing.T) {
func TestParseLabels(t *testing.T) {
text := `
This is a test file containing some labels, like LABEL-123.
Expand All @@ -15,13 +15,13 @@ WILDCARD-*
Labels need to have a sequence of lowercase characters and digits after the hyphen. Strings like INCOMPLETE- do not match.
`

expected := []Label{
expected := []ParsedLabel{
{TagStartPosition: 50, TagEndPosition: 55, ModifierStartPosition: 56, ModifierEndPosition: 59, Tag: "LABEL", Modifier: "123", ModifierIsInt: true, ModifierAsInt: 123},
{TagStartPosition: 62, TagEndPosition: 74, ModifierStartPosition: 75, ModifierEndPosition: 76, Tag: "ANOTHERLABEL", Modifier: "x", ModifierIsInt: false},
{TagStartPosition: 78, TagEndPosition: 86, ModifierStartPosition: 87, ModifierEndPosition: 88, Tag: "WILDCARD", Modifier: "*", ModifierIsInt: false},
}

labels := FindLabels([]byte(text))
labels := ParseLabels([]byte(text))

if len(labels) != len(expected) {
t.Fatalf("Expected %d labels, got %d", len(expected), len(labels))
Expand Down Expand Up @@ -61,12 +61,12 @@ Labels need to have a sequence of lowercase characters and digits after the hyph
}
}

func TestFindLabelsWithBackticks(t *testing.T) {
func TestParseLabelsWithBackticks(t *testing.T) {
text := "### `TAG-5`: lol"

expected := Label{TagStartPosition: 5, TagEndPosition: 8, ModifierStartPosition: 9, ModifierEndPosition: 10, Tag: "TAG", Modifier: "5", ModifierIsInt: true, ModifierAsInt: 5}
expected := ParsedLabel{TagStartPosition: 5, TagEndPosition: 8, ModifierStartPosition: 9, ModifierEndPosition: 10, Tag: "TAG", Modifier: "5", ModifierIsInt: true, ModifierAsInt: 5}

labels := FindLabels([]byte(text))
labels := ParseLabels([]byte(text))

if len(labels) != 1 {
t.Fatalf("Expected 1 label, got %d", len(labels))
Expand Down Expand Up @@ -104,12 +104,12 @@ func TestFindLabelsWithBackticks(t *testing.T) {
}
}

func TestFindLabelsDoesNotRecognizeModifiersWithUppercaseCharacters(t *testing.T) {
func TestParseLabelsDoesNotRecognizeModifiersWithUppercaseCharacters(t *testing.T) {
text := "### `TAG-ANOTHERTAG-5`: lol"

expected := Label{TagStartPosition: 9, TagEndPosition: 19, ModifierStartPosition: 20, ModifierEndPosition: 21, Tag: "ANOTHERTAG", Modifier: "5", ModifierIsInt: true, ModifierAsInt: 5}
expected := ParsedLabel{TagStartPosition: 9, TagEndPosition: 19, ModifierStartPosition: 20, ModifierEndPosition: 21, Tag: "ANOTHERTAG", Modifier: "5", ModifierIsInt: true, ModifierAsInt: 5}

labels := FindLabels([]byte(text))
labels := ParseLabels([]byte(text))

if len(labels) != 1 {
t.Fatalf("Expected 1 label, got %d", len(labels))
Expand Down Expand Up @@ -147,7 +147,7 @@ func TestFindLabelsDoesNotRecognizeModifiersWithUppercaseCharacters(t *testing.T
}
}

func TestFindLabelsWithLineNumbers(t *testing.T) {
func TestParseLabelsWithLineNumbers(t *testing.T) {
text := `
This is a test file containing some labels, like LABEL-123.
Expand All @@ -158,13 +158,13 @@ WILDCARD-*
Labels need to have a sequence of lowercase characters and digits after the hyphen. Strings like INCOMPLETE- do not match.
`

expected := []Label{
expected := []ParsedLabel{
{TagStartPosition: 49, TagEndPosition: 54, ModifierStartPosition: 55, ModifierEndPosition: 58, Tag: "LABEL", Modifier: "123", ModifierIsInt: true, ModifierAsInt: 123, IncludesLineNumbers: true, LineNumber: 2},
{TagStartPosition: 0, TagEndPosition: 12, ModifierStartPosition: 13, ModifierEndPosition: 14, Tag: "ANOTHERLABEL", Modifier: "x", ModifierIsInt: false, IncludesLineNumbers: true, LineNumber: 4},
{TagStartPosition: 0, TagEndPosition: 8, ModifierStartPosition: 9, ModifierEndPosition: 10, Tag: "WILDCARD", Modifier: "*", ModifierIsInt: false, IncludesLineNumbers: true, LineNumber: 6},
}

labels := FindLabelsWithLineNumbers([]byte(text))
labels := ParseLabelsWithLineNumbers([]byte(text))

if len(labels) != len(expected) {
t.Fatalf("Expected %d labels, got %d", len(expected), len(labels))
Expand Down Expand Up @@ -211,9 +211,9 @@ TAG-51
TAG-x
`

labels := FindLabels([]byte(text))
labels := ParseLabels([]byte(text))

expectedLabels := []Label{
expectedLabels := []ParsedLabel{
{TagStartPosition: 1, TagEndPosition: 4, ModifierStartPosition: 5, ModifierEndPosition: 6, Tag: "TAG", Modifier: "1", ModifierIsInt: true, ModifierAsInt: 1},
{TagStartPosition: 7, TagEndPosition: 10, ModifierStartPosition: 11, ModifierEndPosition: 13, Tag: "TAG", Modifier: "51", ModifierIsInt: true, ModifierAsInt: 51},
{TagStartPosition: 14, TagEndPosition: 17, ModifierStartPosition: 18, ModifierEndPosition: 19, Tag: "TAG", Modifier: "2", ModifierIsInt: true, ModifierAsInt: 2},
Expand Down

0 comments on commit 312453a

Please sign in to comment.