-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support for file headers (#183)
- Loading branch information
Showing
15 changed files
with
1,109 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
Oops, something went wrong.