Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(examples): add linting to all examples#12595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
mafredri merged 2 commits intomainfrommafredri/feat-lint-examples
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes fromall 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
6 changes: 5 additions & 1 deletionMakefile
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -428,7 +428,7 @@ else
endif
.PHONY: fmt/shfmt

lint: lint/shellcheck lint/go lint/ts lint/helm lint/site-icons
lint: lint/shellcheck lint/go lint/ts lint/examples lint/helm lint/site-icons
.PHONY: lint

lint/site-icons:
Expand All@@ -447,6 +447,10 @@ lint/go:
golangci-lint run
.PHONY: lint/go

lint/examples:
go run ./scripts/examplegen/main.go -lint
.PHONY: lint/examples

# Use shfmt to determine the shell files, takes editorconfig into consideration.
lint/shellcheck:$(SHELL_SRC_FILES)
echo"--- shellcheck"
Expand Down
2 changes: 1 addition & 1 deletionexamples/templates/incus/README.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
---
display_name:Incus System Container with Docker
description:Develop in an Incus System Container with Docker using incus
icon:/icon/lxc.svg
icon:../../../site/static/icon/lxc.svg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

😅

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

To be fair, both forms work. But all our otherexamples/templates use this relative form, and it's handy for confirming that the exact path exists.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yep, +1 for enforced consistency!

maintainer_github:coder
verified:true
tags:[local, incus, lxc, lxd]
Expand Down
225 changes: 151 additions & 74 deletionsscripts/examplegen/main.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,12 @@ package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"go/parser"
"go/token"
"io"
"io/fs"
"os"
"path"
Expand All@@ -24,122 +27,196 @@ const (
)

funcmain() {
iferr:=run();err!=nil {
panic(err)
lint:=flag.Bool("lint",false,"Lint **all** the examples instead of generating the examples.gen.json file")
flag.Parse()

iferr:=run(*lint);err!=nil {
_,_=fmt.Fprintf(os.Stderr,"error: %+v\n",err)
os.Exit(1)
}
}

funcrun()error {
//nolint:revive // This is a script, not a library.
funcrun(lintbool)error {
fset:=token.NewFileSet()
src,err:=parser.ParseFile(fset,filepath.Join(examplesDir,examplesSrc),nil,parser.ParseComments)
iferr!=nil {
returnerr
}

projectFS:=os.DirFS(".")
examplesFS:=os.DirFS(examplesDir)

varpaths []string
for_,comment:=rangesrc.Comments {
for_,line:=rangecomment.List {
ifs,ok:=parseEmbedTag(line.Text);ok&&!strings.HasSuffix(s,".json") {
paths=append(paths,s)
iflint {
files,err:=fs.ReadDir(examplesFS,"templates")
iferr!=nil {
returnerr
}

for_,f:=rangefiles {
if!f.IsDir() {
continue
}
paths=append(paths,filepath.Join("templates",f.Name()))
}
}else {
for_,comment:=rangesrc.Comments {
for_,line:=rangecomment.List {
ifs,ok:=parseEmbedTag(line.Text);ok&&!strings.HasSuffix(s,".json") {
paths=append(paths,s)
}
}
}
}

varexamples []codersdk.TemplateExample
files:=os.DirFS(examplesDir)
varerrs []error
for_,name:=rangepaths {
dir,err:=fs.Stat(files,name)
te,err:=parseTemplateExample(projectFS,examplesFS,name)
iferr!=nil {
returnerr
}
if!dir.IsDir() {
errs=append(errs,err)
continue
}
exampleID:=dir.Name()
// Each one of these is a example!
readme,err:=fs.ReadFile(files,path.Join(name,"README.md"))
iferr!=nil {
returnxerrors.Errorf("example %q does not contain README.md",exampleID)
ifte!=nil {
examples=append(examples,*te)
}
}

frontMatter,err:=pageparser.ParseFrontMatterAndContent(bytes.NewReader(readme))
iflen(errs)>0 {
returnxerrors.Errorf("parse failed: %w",errors.Join(errs...))
}

varw io.Writer=os.Stdout
iflint {
w=io.Discard
}

_,err=fmt.Fprint(w,"// Code generated by examplegen. DO NOT EDIT.\n")
iferr!=nil {
returnerr
}

enc:=json.NewEncoder(w)
enc.SetIndent(""," ")
returnenc.Encode(examples)
}

funcparseTemplateExample(projectFS,examplesFS fs.FS,namestring) (te*codersdk.TemplateExample,errerror) {
varerrs []error
deferfunc() {
iferr!=nil {
returnxerrors.Errorf("parse example %q front matter: %w",exampleID,err)
errs=append([]error{err},errs...)
}

nameRaw,exists:=frontMatter.FrontMatter["display_name"]
if!exists {
returnxerrors.Errorf("example %q front matter does not contain name",exampleID)
iflen(errs)>0 {
err=xerrors.Errorf("example %q has errors",name)
for_,e:=rangeerrs {
err=errors.Join(err,e)
}
}
}()

name,valid:=nameRaw.(string)
if!valid {
returnxerrors.Errorf("example %q name isn't a string",exampleID)
}
dir,err:=fs.Stat(examplesFS,name)
iferr!=nil {
returnnil,err
}
if!dir.IsDir() {
//nolint:nilnil // This is a script, not a library.
returnnil,nil
}

descriptionRaw,exists:=frontMatter.FrontMatter["description"]
if!exists {
returnxerrors.Errorf("example %q front matter does not contain name",exampleID)
}
exampleID:=dir.Name()
// Each one of these is a example!
readme,err:=fs.ReadFile(examplesFS,path.Join(name,"README.md"))
iferr!=nil {
returnnil,xerrors.New("missing README.md")
}

description,valid:=descriptionRaw.(string)
if!valid {
returnxerrors.Errorf("example %q description isn't a string",exampleID)
}
frontMatter,err:=pageparser.ParseFrontMatterAndContent(bytes.NewReader(readme))
iferr!=nil {
returnnil,xerrors.Errorf("parse front matter: %w",err)
}

tags:= []string{}
tagsRaw,exists:=frontMatter.FrontMatter["tags"]
ifexists {
tagsI,valid:=tagsRaw.([]interface{})
if!valid {
returnxerrors.Errorf("example %q tags isn't a slice: type %T",exampleID,tagsRaw)
}
// Make sure validation here is in sync with requirements for
// coder/registry.
displayName,err:=getString(frontMatter.FrontMatter,"display_name")
iferr!=nil {
errs=append(errs,err)
}

description,err:=getString(frontMatter.FrontMatter,"description")
iferr!=nil {
errs=append(errs,err)
}

_,err=getString(frontMatter.FrontMatter,"maintainer_github")
iferr!=nil {
errs=append(errs,err)
}

tags:= []string{}
tagsRaw,exists:=frontMatter.FrontMatter["tags"]
ifexists {
tagsI,valid:=tagsRaw.([]interface{})
if!valid {
errs=append(errs,xerrors.Errorf("tags isn't a slice: type %T",tagsRaw))
}else {
for_,tagI:=rangetagsI {
tag,valid:=tagI.(string)
if!valid {
returnxerrors.Errorf("example %q tag isn't a string: type %T",exampleID,tagI)
errs=append(errs,xerrors.Errorf("tag isn't a string: type %T",tagI))
continue
}
tags=append(tags,tag)
}
}
}

variconstring
iconRaw,exists:=frontMatter.FrontMatter["icon"]
ifexists {
icon,valid=iconRaw.(string)
if!valid {
returnxerrors.Errorf("example %q icon isn't a string",exampleID)
}
icon,err=filepath.Rel("../site/static/",filepath.Join(examplesDir,name,icon))
iferr!=nil {
returnxerrors.Errorf("example %q icon is not in site/static: %w",exampleID,err)
}
// The FE needs a static path!
icon="/"+icon
variconstring
icon,err=getString(frontMatter.FrontMatter,"icon")
iferr!=nil {
errs=append(errs,err)
}else {
cleanPath:=filepath.Clean(filepath.Join(examplesDir,name,icon))
_,err:=fs.Stat(projectFS,cleanPath)
iferr!=nil {
errs=append(errs,xerrors.Errorf("icon does not exist: %w",err))
}
if!strings.HasPrefix(cleanPath,filepath.Join("site","static")) {
errs=append(errs,xerrors.Errorf("icon is not in site/static/: %q",icon))
}
icon,err=filepath.Rel(filepath.Join("site","static"),cleanPath)
iferr!=nil {
errs=append(errs,xerrors.Errorf("cannot make icon relative to site/static: %w",err))
}
}

examples=append(examples, codersdk.TemplateExample{
ID:exampleID,
Name:name,
Description:description,
Icon:icon,
Tags:tags,
Markdown:string(frontMatter.Content),

// URL is set by examples/examples.go.
})
iflen(errs)>0 {
returnnil,xerrors.New("front matter validation failed")
}

w:=os.Stdout
return&codersdk.TemplateExample{
ID:exampleID,
Name:displayName,
Description:description,
Icon:"/"+icon,// The FE needs a static path!
Tags:tags,
Markdown:string(frontMatter.Content),

_,err=fmt.Fprint(w,"// Code generated by examplegen. DO NOT EDIT.\n")
iferr!=nil {
returnerr
}
// URL is set by examples/examples.go.
},nil
}

enc:=json.NewEncoder(os.Stdout)
enc.SetIndent(""," ")
returnenc.Encode(examples)
funcgetString(mmap[string]any,keystring) (string,error) {
v,ok:=m[key]
if!ok {
return"",xerrors.Errorf("front matter does not contain %q",key)
}
vv,ok:=v.(string)
if!ok {
return"",xerrors.Errorf("%q isn't a string",key)
}
returnvv,nil
}

funcparseEmbedTag(sstring) (string,bool) {
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp