-
Notifications
You must be signed in to change notification settings - Fork 11
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add links.validate.config
flag
#33
Changes from 6 commits
2a7a529
2cfb2e9
88790de
412bcbd
0e2b12a
c46b6e4
7536052
3282c03
d1c885d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,4 +55,5 @@ jobs: | |
- name: Run unit tests. | ||
env: | ||
GOBIN: /tmp/.bin | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
run: make test |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright (c) Bartłomiej Płotka @bwplotka | ||
// Licensed under the Apache License 2.0. | ||
|
||
// Taken from Thanos project. | ||
// | ||
// Copyright (c) The Thanos Authors. | ||
// Licensed under the Apache License 2.0. | ||
package extflag | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
|
||
"github.com/pkg/errors" | ||
"gopkg.in/alecthomas/kingpin.v2" | ||
) | ||
|
||
// PathOrContent is a flag type that defines two flags to fetch bytes. Either from file (*-file flag) or content (* flag). | ||
type PathOrContent struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add env substitution here, and then we can think about proposing this upstream. So the idea would be to add env substitution using e.g e.g our config could be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then we can upstream it e.g through this: efficientgo/tools#9 |
||
flagName string | ||
|
||
required bool | ||
|
||
path *string | ||
content *string | ||
} | ||
|
||
type FlagClause interface { | ||
Flag(name, help string) *kingpin.FlagClause | ||
} | ||
|
||
// RegisterPathOrContent registers PathOrContent flag in kingpinCmdClause. | ||
func RegisterPathOrContent(cmd FlagClause, flagName string, help string, required bool) *PathOrContent { | ||
fileFlagName := fmt.Sprintf("%s-file", flagName) | ||
contentFlagName := flagName | ||
|
||
fileHelp := fmt.Sprintf("Path to %s", help) | ||
fileFlag := cmd.Flag(fileFlagName, fileHelp).PlaceHolder("<file-path>").String() | ||
|
||
contentHelp := fmt.Sprintf("Alternative to '%s' flag (mutually exclusive). Content of %s", fileFlagName, help) | ||
contentFlag := cmd.Flag(contentFlagName, contentHelp).PlaceHolder("<content>").String() | ||
|
||
return &PathOrContent{ | ||
flagName: flagName, | ||
required: required, | ||
path: fileFlag, | ||
content: contentFlag, | ||
} | ||
} | ||
|
||
// Content returns the content of the file when given or directly the content that has been passed to the flag. | ||
// It returns an error when: | ||
// * The file and content flags are both not empty. | ||
// * The file flag is not empty but the file can't be read. | ||
// * The content is empty and the flag has been defined as required. | ||
func (p *PathOrContent) Content() ([]byte, error) { | ||
fileFlagName := fmt.Sprintf("%s-file", p.flagName) | ||
|
||
if len(*p.path) > 0 && len(*p.content) > 0 { | ||
return nil, errors.Errorf("both %s and %s flags set.", fileFlagName, p.flagName) | ||
} | ||
|
||
var content []byte | ||
if len(*p.path) > 0 { | ||
c, err := ioutil.ReadFile(*p.path) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "loading file %s for %s", *p.path, fileFlagName) | ||
} | ||
content = c | ||
} else { | ||
content = []byte(*p.content) | ||
} | ||
|
||
if len(content) == 0 && p.required { | ||
return nil, errors.Errorf("flag %s or %s is required for running this command and content cannot be empty.", fileFlagName, p.flagName) | ||
} | ||
|
||
return content, nil | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,51 @@ | ||||||
// Copyright (c) Bartłomiej Płotka @bwplotka | ||||||
// Licensed under the Apache License 2.0. | ||||||
|
||||||
package linktransformer | ||||||
|
||||||
import ( | ||||||
"bytes" | ||||||
"regexp" | ||||||
|
||||||
"github.com/pkg/errors" | ||||||
"gopkg.in/yaml.v3" | ||||||
) | ||||||
|
||||||
const roundtrip ValidatorType = "roundtrip" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's common to put this just below the
Suggested change
|
||||||
|
||||||
// const github ValidatorType = "github" | ||||||
|
||||||
type Config struct { | ||||||
Version int | ||||||
|
||||||
Validators []Validator `yaml:"validators"` | ||||||
} | ||||||
|
||||||
type Validator struct { | ||||||
_regex *regexp.Regexp | ||||||
_maxNum int | ||||||
// Regex for type github is reponame matcher, like `bwplotka\/mdox`. | ||||||
Regex string `yaml:"regex"` | ||||||
// By default type is `roundtrip`. Could be `github`. | ||||||
Type ValidatorType `yaml:"type"` | ||||||
} | ||||||
|
||||||
type ValidatorType string | ||||||
|
||||||
func ParseConfig(c []byte) (Config, error) { | ||||||
cfg := Config{} | ||||||
dec := yaml.NewDecoder(bytes.NewReader(c)) | ||||||
dec.KnownFields(true) | ||||||
if err := dec.Decode(&cfg); err != nil { | ||||||
return Config{}, errors.Wrapf(err, "parsing YAML content %q", string(c)) | ||||||
} | ||||||
|
||||||
if len(cfg.Validators) <= 0 { | ||||||
return Config{}, errors.New("No validator provided") | ||||||
} | ||||||
|
||||||
for i := range cfg.Validators { | ||||||
cfg.Validators[i]._regex = regexp.MustCompile(cfg.Validators[i].Regex) | ||||||
} | ||||||
bwplotka marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
return cfg, nil | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -110,9 +110,9 @@ func (l *localizer) TransformDestination(ctx mdformatter.SourceContext, destinat | |||||||||||
func (l *localizer) Close(mdformatter.SourceContext) error { return nil } | ||||||||||||
|
||||||||||||
type validator struct { | ||||||||||||
logger log.Logger | ||||||||||||
anchorDir string | ||||||||||||
except *regexp.Regexp | ||||||||||||
logger log.Logger | ||||||||||||
anchorDir string | ||||||||||||
validateConfig Config | ||||||||||||
|
||||||||||||
localLinks localLinksCache | ||||||||||||
rMu sync.RWMutex | ||||||||||||
|
@@ -135,15 +135,23 @@ type futureResult struct { | |||||||||||
|
||||||||||||
// NewValidator returns mdformatter.LinkTransformer that crawls all links. | ||||||||||||
// TODO(bwplotka): Add optimization and debug modes - this is the main source of latency and pain. | ||||||||||||
func NewValidator(logger log.Logger, except *regexp.Regexp, anchorDir string) (mdformatter.LinkTransformer, error) { | ||||||||||||
func NewValidator(logger log.Logger, linksValidateConfig []byte, anchorDir string) (mdformatter.LinkTransformer, error) { | ||||||||||||
var config Config | ||||||||||||
var err error | ||||||||||||
if string(linksValidateConfig) != "" { | ||||||||||||
config, err = ParseConfig(linksValidateConfig) | ||||||||||||
if err != nil { | ||||||||||||
return nil, err | ||||||||||||
} | ||||||||||||
} | ||||||||||||
v := &validator{ | ||||||||||||
logger: logger, | ||||||||||||
anchorDir: anchorDir, | ||||||||||||
except: except, | ||||||||||||
localLinks: map[string]*[]string{}, | ||||||||||||
remoteLinks: map[string]error{}, | ||||||||||||
c: colly.NewCollector(colly.Async()), | ||||||||||||
destFutures: map[futureKey]*futureResult{}, | ||||||||||||
logger: logger, | ||||||||||||
anchorDir: anchorDir, | ||||||||||||
validateConfig: config, | ||||||||||||
localLinks: map[string]*[]string{}, | ||||||||||||
remoteLinks: map[string]error{}, | ||||||||||||
c: colly.NewCollector(colly.Async()), | ||||||||||||
destFutures: map[futureKey]*futureResult{}, | ||||||||||||
} | ||||||||||||
// Set very soft limits. | ||||||||||||
// E.g github has 50-5000 https://docs.github.com/en/free-pro-team@latest/rest/reference/rate-limit limit depending | ||||||||||||
|
@@ -169,12 +177,16 @@ func NewValidator(logger log.Logger, except *regexp.Regexp, anchorDir string) (m | |||||||||||
defer v.rMu.Unlock() | ||||||||||||
v.remoteLinks[response.Ctx.Get(originalURLKey)] = errors.Wrapf(err, "%q not accessible; status code %v", response.Request.URL.String(), response.StatusCode) | ||||||||||||
}) | ||||||||||||
err = v.validateConfig.validateGH() | ||||||||||||
if err != nil { | ||||||||||||
return nil, err | ||||||||||||
} | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
return v, nil | ||||||||||||
} | ||||||||||||
|
||||||||||||
// MustNewValidator returns mdformatter.LinkTransformer that crawls all links. | ||||||||||||
func MustNewValidator(logger log.Logger, except *regexp.Regexp, anchorDir string) mdformatter.LinkTransformer { | ||||||||||||
v, err := NewValidator(logger, except, anchorDir) | ||||||||||||
func MustNewValidator(logger log.Logger, linksValidateConfig []byte, anchorDir string) mdformatter.LinkTransformer { | ||||||||||||
v, err := NewValidator(logger, linksValidateConfig, anchorDir) | ||||||||||||
if err != nil { | ||||||||||||
panic(err) | ||||||||||||
} | ||||||||||||
|
@@ -232,7 +244,8 @@ func (v *validator) visit(filepath string, dest string) { | |||||||||||
return | ||||||||||||
} | ||||||||||||
v.destFutures[k] = &futureResult{cases: 1, resultFn: func() error { return nil }} | ||||||||||||
if v.except.MatchString(dest) { | ||||||||||||
validator := v.validateConfig.GetValidatorForURL(dest) | ||||||||||||
if validator.Matched { | ||||||||||||
return | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💪🏽