Skip to content

Commit

Permalink
Fix #2 and add stdin
Browse files Browse the repository at this point in the history
* Fixes #2. The issue is
that the "autofix" command line flag was overriding the Config object.
So even if the config had autofix: true, later in the main function the
flag value would override this configuration. It didn't help that the
default bool value for the flag was 'false'.

* Refactor the main code at linelint.go, allowing for input from STDIN.
This took some inspiration from autopep8, in which to read from STDIN
requires the "-" positional argument (any other positional argument
is interpreted as a list of files/dirs to lint, and an "empty" list is
interpreted as "lint the current directory tree"). This also makes it
much easier to test the linting from the main entrypoint.

* Add a couple tests as Example in linelint_test.go, using the new
"read from stdin" functionality.
  • Loading branch information
fernandrone committed Oct 9, 2020
1 parent 0a5d225 commit 5f775f4
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 47 deletions.
40 changes: 32 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ See the **[#GitHub Actions](#GitHub-Actions)** and the **[#Docker](#Docker)** fo

> This is a project in development. Use it at your own risk!
To run it locally, execute the binary and pass a list of file or directories as argument.
Executing the binary will automatically search the local directory tree for linting errors.

```console
$ linelint .
Expand All @@ -41,7 +41,7 @@ $ linelint .
Total of 2 lint errors!
```

Or:
Pass a list of files or directories to limit your search.

```console
$ linelint README.md LICENSE linter/config.go
Expand All @@ -52,7 +52,9 @@ Total of 1 lint errors!

After checking all files, in case any rule has failed, Linelint will finish with an error (exit code 1).

If the `autofix` option is set to `true` (it is `false` by default, activate it with the `-a` flag), Linelint will attempt to fix any file with error by rewriting it.
### AutoFix

If the `autofix` option is set to `true` (it is `false` by default, activate it with the `-a` flag or set it in the configuration file), Linelint will attempt to fix any linting error by rewriting the file.

```console
$ linelint -a .
Expand All @@ -62,21 +64,43 @@ $ linelint -a .
[EOF Rule] File "linter/eof.go" lint errors fixed
```

When all files are fixed successfully, Linelint terminates with with a success as well (exit code 0).
If all files are fixed successfully, Linelint terminates with exit code 0.

### Stdin

Pass "-" as an argument to read data from standard input instead of a list of files.

```console
$ cat hello.txt
Hello World


```

```console
$ cat hello.txt | linelint -
Hello World
```

When reading from stdin, linelint behavior changes and it won't report lint errors. Instead when autofix is on, it will fix them and output the result to `/dev/stdout`. When autofix is off, it will terminate the program with an error code in case there are any linting violations, but won't output anything.

### Help

At any time run `linenlint --help` for a list of available command line arguments.

## Configuration

Create a `.linelint.yml` file in the same working directory you run `linelint` to adjust your settings. See [.linelint.yml](.linelint.yml) for an up-to-date example:
Create a `.linelint.yml` file in the same working directory you run `linelint` to adjust your settings. See [.linelint.yml](.linelint.yml) for an up-to-date example.

## Rules

Right now it only supports a single rule, "End of File", which is enabled by default.
Right now it supports only a single rule, "End of File", which is enabled by default.

### EndOfFile

The _End of File_ rule checks if the file ends in a newline character, or `\n`. You may find this rule useful if you dislike seeing these 🚫 symbols at the end of files on GitHub Pull Requests.
The _End of File_ rule checks if the file ends in a newline character, or `\n`. You may find it useful if you dislike seeing these 🚫 symbols at the end of files on GitHub Pull Requests.

By default it also checks if it ends strictly in a single newline character. This behavior can be disabled by setting the `single-new-line` parameter to `false`.
By default it also checks if it strictly ends in a single newline character. This behavior can be disabled by setting the `single-new-line` parameter to `false`.

```yaml
rules:
Expand Down
165 changes: 126 additions & 39 deletions linelint.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package main

import (
"bufio"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -16,13 +18,20 @@ const helpMsg = `usage of %s [-a] [FILE_OR_DIR [FILE_OR_DIR ...]]
Validates simple newline and whitespace rules in all sorts of files.
positional arguments:
FILE_OR_DIR files to format
FILE_OR_DIR files to format or '-' for stdin
optional arguments:
`

// Input is the main input structure to the program
type Input struct {
Paths []string
Stdin io.Reader
Config linter.Config
}

func main() {
var flagAutofix, flagVerbose bool
var flagAutofix bool
flag.BoolVar(&flagAutofix, "a", false, "(autofix) will automatically fix files with errors in place")

flag.Usage = func() {
Expand All @@ -31,12 +40,12 @@ func main() {
}
flag.Parse()

var args, paths []string
var paths []string

if flag.NArg() == 0 {
args = []string{"."}
paths = []string{"."}
} else {
args = flag.Args()
paths = flag.Args()
}

config := linter.NewConfig()
Expand All @@ -45,15 +54,102 @@ func main() {
config.AutoFix = true
}

// get paths to ignore
ignore := linter.MustCompileIgnoreLines(config.Ignore...)
input := Input{
Paths: paths,
Stdin: os.Stdin,
Config: config,
}

if err := run(input); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
}

func run(in Input) error {
var linters []linter.Linter

if in.Config.Rules.EndOfFile.Enable {
linters = append(linters, linter.NewEndOfFileRule(in.Config))
}

if len(linters) == 0 {
return errors.New("No valid rule enabled")
}

// read from stdin
if len(in.Paths) == 1 && in.Paths[0] == "-" {
return processSTDIN(in, linters)
}

for _, path := range args {
return processDirectoryTree(in, linters)
}

func processSTDIN(in Input, linters []linter.Linter) error {
var lintErrors int

b, err := ioutil.ReadAll(in.Stdin)

if err != nil {
return fmt.Errorf("Error reading from Stdin: %v", err)
}

if !linter.IsText(b) {
return errors.New("Stdin is not a valid UFT-8 input")
}

for _, rule := range linters {

valid, fix := rule.Lint(b)

if !valid {
lintErrors++
}

if fix != nil {

if err != nil {
return fmt.Errorf("[%s] Failed to fix Stdin: %v\n", rule.GetName(), err)
}

w := bufio.NewWriter(os.Stdout)
defer w.Flush()

_, err = w.Write(fix)

if err != nil {
return fmt.Errorf("[%s] Failed to print fixed input to Stdout: %v\n", rule.GetName(), err)
}

err = w.Flush()

if err != nil {
return fmt.Errorf("[%s] Failed to flush fixed input to Stdout: %v\n", rule.GetName(), err)
}

lintErrors--
}
}

if lintErrors != 0 {
// call exit directly to disable the error message
os.Exit(1)
}

return nil
}

func processDirectoryTree(in Input, linters []linter.Linter) error {
var files []string

// get patterns to ignore
ignore := linter.MustCompileIgnoreLines(in.Config.Ignore...)

for _, path := range in.Paths {
f, err := os.Stat(path)

if os.IsNotExist(err) {
fmt.Printf("File %q does not exist", path)
os.Exit(1)
return fmt.Errorf("File %q does not exist", path)
}

// if dir, walk and append only files
Expand All @@ -73,86 +169,74 @@ func main() {
return nil
}

paths = append(paths, p)
files = append(files, p)
return nil
})
if err != nil {
fmt.Printf("Error walking the path %q: %v\n", path, err)
return
return fmt.Errorf("Error walking the path %q: %v\n", path, err)
}
} else {
// if not dir, append
paths = append(paths, path)
files = append(files, path)
}
}

var fileErrors, lintErrors int
var linters []linter.Linter

// TODO a better code for selecting rules
if config.Rules.EndOfFile.Enable {
linters = append(linters, linter.NewEndOfFileRule(config))
}
for _, f := range files {

if len(linters) == 0 {
fmt.Printf("Fatal: no valid rule enabled\n")
os.Exit(1)
}

for _, path := range paths {

fr, err := os.Open(path)
fr, err := os.Open(f)

if err != nil {
fmt.Printf("Error opening file %q: %v\n", path, err)
fmt.Printf("Error opening file %q: %v\n", f, err)
fileErrors++
continue
}

defer fr.Close()

if err != nil {
fmt.Printf("Skipping file %q: %v\n", path, err)
fmt.Printf("Skipping file %q: %v\n", f, err)
continue
}

b, err := ioutil.ReadAll(fr)

if err != nil {
fmt.Printf("Error reading file %q: %v\n", path, err)
fmt.Printf("Error reading file %q: %v\n", f, err)
fileErrors++
continue
}

if !linter.IsText(b) {
// TODO add log levels
// fmt.Printf("Ignoring file %q: not text file\n", path)
continue
}

for _, rule := range linters {

if rule.ShouldIgnore(path) {
fmt.Printf("[%s] Ignoring file %q: in rule ignore path\n", rule.GetName(), path)
if rule.ShouldIgnore(f) {
fmt.Printf("[%s] Ignoring file %q: in rule ignore path\n", rule.GetName(), f)
continue
}

valid, fix := rule.Lint(b)

if !valid {
fmt.Printf("[%s] File %q has lint errors\n", rule.GetName(), path)
fmt.Printf("[%s] File %q has lint errors\n", rule.GetName(), f)
lintErrors++
}

// ignore errors
fr.Close()

if fix != nil {

// will erase the file
fw, err := os.Create(path)
fw, err := os.Create(f)

if err != nil {
fmt.Printf("[%s] Failed to fix file %q: %v\n", rule.GetName(), path, err)
fmt.Printf("[%s] Failed to fix file %q: %v\n", rule.GetName(), f, err)
break
}

Expand All @@ -164,18 +248,18 @@ func main() {
_, err = w.Write(fix)

if err != nil {
fmt.Printf("[%s] Failed to fix file %q: %v\n", rule.GetName(), path, err)
fmt.Printf("[%s] Failed to fix file %q: %v\n", rule.GetName(), f, err)
break
}

err = w.Flush()

if err != nil {
fmt.Printf("[%s] Failed to flush file %q: %v\n", rule.GetName(), path, err)
fmt.Printf("[%s] Failed to flush file %q: %v\n", rule.GetName(), f, err)
break
}

fmt.Printf("[%s] File %q lint errors fixed\n", rule.GetName(), path)
fmt.Printf("[%s] File %q lint errors fixed\n", rule.GetName(), f)
lintErrors--

// ignore errors
Expand All @@ -193,6 +277,9 @@ func main() {
}

if fileErrors != 0 || lintErrors != 0 {
// call exit directly to disable the error message
os.Exit(1)
}

return nil
}
Loading

0 comments on commit 5f775f4

Please sign in to comment.