Skip to content

Commit

Permalink
feat: support for file headers (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevgo authored Aug 4, 2022
1 parent 5f7e7e3 commit 04d0d5b
Show file tree
Hide file tree
Showing 15 changed files with 1,109 additions and 4 deletions.
2 changes: 2 additions & 0 deletions cmd/dev/headers/comments/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package comments provides functionality to add and remove comments to the top of files.
package comments
35 changes: 35 additions & 0 deletions cmd/dev/headers/comments/file_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package comments

import "path/filepath"

// a file format that we know about, represented as its file extension
type FileType string

// indicates whether the given list of FileTypes contains the given FileType
func ContainsFileType(fileTypes []FileType, fileType FileType) bool {
for _, ft := range fileTypes {
if ft == fileType {
return true
}
}
return false
}

// provides the extension of the given filepath
func GetFileType(filePath string) FileType {
ext := filepath.Ext(filePath)
if len(ext) > 0 {
ext = ext[1:]
}
if ext == "yaml" {
ext = "yml"
}
return FileType(ext)
}

// indicates whether it is possible to add comments to the file with the given name
func SupportsFile(filePath string) bool {
filetype := GetFileType(filePath)
_, ok := commentFormats[filetype]
return ok
}
41 changes: 41 additions & 0 deletions cmd/dev/headers/comments/file_type_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package comments_test

import (
"fmt"
"testing"

"github.com/ory/cli/cmd/dev/headers/comments"
"github.com/stretchr/testify/assert"
)

func TestContainsFileType(t *testing.T) {
t.Parallel()
fileTypes := []comments.FileType{"ts", "md", "go"}
assert.True(t, comments.ContainsFileType(fileTypes, "ts"))
assert.True(t, comments.ContainsFileType(fileTypes, "go"))
assert.False(t, comments.ContainsFileType(fileTypes, "rs"))
}

func TestGetFileType(t *testing.T) {
t.Parallel()
tests := map[string]comments.FileType{
"foo.yml": "yml",
"foo.yaml": "yml",
"foo.md": "md",
"foo.xxx": "xxx",
"foo": "",
}
for give, want := range tests {
t.Run(fmt.Sprintf("%s -> %s", give, want), func(t *testing.T) {
have := comments.GetFileType(give)
assert.Equal(t, want, have)
})
}
}

func TestSupports(t *testing.T) {
t.Parallel()
assert.True(t, comments.SupportsFile("foo.ts"))
assert.True(t, comments.SupportsFile("foo.md"))
assert.False(t, comments.SupportsFile("foo.xxx"))
}
45 changes: 45 additions & 0 deletions cmd/dev/headers/comments/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package comments

import (
"fmt"
"os"
)

// FileContentWithoutHeader provides the content of the file with the given path,
// without the comment block identified by the given token.
func FileContentWithoutHeader(path, token string) (string, error) {
buffer, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("cannot open file %q: %w", path, err)
}
text := string(buffer)
format, knowsFormat := commentFormats[GetFileType(path)]
if !knowsFormat {
return text, nil
}
return format.remove(text, token), nil
}

// WriteFileWithHeader creates a file at the given path containing the given file content (header + body).
// The header argument should contain only text. This method will transform it into the correct comment format.
func WriteFileWithHeader(path, header, body string) error {
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("cannot write file %q: %w", path, err)
}
defer file.Close()
format, knowsFormat := commentFormats[GetFileType(path)]
if !knowsFormat {
return os.WriteFile(path, []byte(body), 0744)
}
headerComment := format.renderBlock(header)
content := fmt.Sprintf("%s\n\n%s", headerComment, body)
count, err := file.WriteString(content)
if err != nil {
return fmt.Errorf("cannot write into file %q: %w", path, err)
}
if count != len(content) {
return fmt.Errorf("did not write the full %d bytes of header into %q: %w", len(headerComment), path, err)
}
return nil
}
60 changes: 60 additions & 0 deletions cmd/dev/headers/comments/files_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package comments_test

import (
"os"
"strings"
"testing"

"github.com/ory/cli/cmd/dev/headers/comments"
"github.com/stretchr/testify/assert"
)

func TestFileContentWithoutHeader_knownFile(t *testing.T) {
give := strings.Trim(`
// copyright Ory
// all rights reserved
file content`, "\n")
want := strings.Trim(`
file content`, "\n")
createTestFile(t, "testfile.go", give)
defer os.Remove("testfile.go")
have, err := comments.FileContentWithoutHeader("testfile.go", "copyright")
assert.NoError(t, err)
assert.Equal(t, want, have)
}

func TestFileContentWithoutHeader_otherCommentFirst(t *testing.T) {
give := strings.Trim(`
// another comment block
// copyright Ory
// all rights reserved
file content`, "\n")
want := strings.Trim(`
// another comment block
file content`, "\n")
createTestFile(t, "testfile.go", give)
defer os.Remove("testfile.go")
have, err := comments.FileContentWithoutHeader("testfile.go", "copyright")
assert.NoError(t, err)
assert.Equal(t, want, have)
}

func TestFileContentWithoutHeader_unknownFile(t *testing.T) {
give := "file content"
want := "file content"
createTestFile(t, "testfile.txt", give)
defer os.Remove("testfile.txt")
have, err := comments.FileContentWithoutHeader("testfile.txt", "copyright")
assert.NoError(t, err)
assert.Equal(t, want, have)
}

func createTestFile(t *testing.T, name, content string) {
t.Helper()
err := os.WriteFile(name, []byte(content), 0744)
assert.NoError(t, err)
}
90 changes: 90 additions & 0 deletions cmd/dev/headers/comments/formats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package comments

import "strings"

// a comment format known to this app
type Format struct {
// converts the given text into a comment in this format
startToken string
// converts the given beginning of a text line into the beginning of a comment line
endToken string
}

// removes the comment block in the given format containing the given token from the given text
func (f Format) remove(text string, token string) string {
commentWithToken := f.renderLineStart(token)
inComment := false
result := []string{}
for _, line := range strings.Split(text, "\n") {
if strings.HasPrefix(line, commentWithToken) {
inComment = true
}
if inComment && line == "" {
// the type of comment blocks we remove here is separated by an empty line
// --> empty line marks the end of our comment block
inComment = false
continue
}
if !inComment {
result = append(result, line)
}
}
return strings.Join(result, "\n")
}

// renders the given text block (consisting of many text lines) into a comment block
func (f Format) renderBlock(text string) string {
result := []string{}
for _, line := range strings.Split(text, "\n") {
if line != "" {
line = f.renderLine(line)
}
result = append(result, line)
}
return strings.Join(result, "\n")
}

// renders the given text line into a comment line of this format
func (f Format) renderLine(text string) string {
return f.startToken + text + f.endToken
}

// renders the given text line part into the beginning of a comment line of this format
func (f Format) renderLineStart(text string) string {
return f.startToken + text
}

// comment format that starts with a doubleslash
var doubleSlashComments = Format{
startToken: "// ",
endToken: "",
}

// comment format that starts with pound symbols
var poundComments = Format{
startToken: "# ",
endToken: "",
}

// HTML comment format
var htmlComments = Format{
startToken: "<!-- ",
endToken: " -->",
}

// all file formats that we can create comments for, and how to do it
var commentFormats = map[FileType]Format{
"cs": doubleSlashComments,
"dart": doubleSlashComments,
"go": doubleSlashComments,
"java": doubleSlashComments,
"js": doubleSlashComments,
"md": htmlComments,
"php": doubleSlashComments,
"py": poundComments,
"rb": poundComments,
"rs": doubleSlashComments,
"ts": doubleSlashComments,
"vue": htmlComments,
"yml": poundComments,
}
Loading

0 comments on commit 04d0d5b

Please sign in to comment.