mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 01:30:22 +00:00
compiler: Add error output to the compiler
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "optparse"
|
||||
require "open3"
|
||||
|
||||
ROOT = File.expand_path("../../", __FILE__)
|
||||
|
||||
@@ -42,6 +43,17 @@ def log(msg)
|
||||
puts msg if $verbose
|
||||
end
|
||||
|
||||
def command(*args)
|
||||
log "$ #{args.join(' ')}"
|
||||
output, status = Open3.capture2e(*args)
|
||||
if !status.success?
|
||||
output.each_line do |line|
|
||||
log " > #{line}"
|
||||
end
|
||||
warn "Command failed. Aborting."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
usage = """Usage:
|
||||
#{$0} [-v|--verbose] [--replace grammar] url
|
||||
@@ -51,12 +63,12 @@ Examples:
|
||||
"""
|
||||
|
||||
$replace = nil
|
||||
$verbose = false
|
||||
$verbose = true
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = usage
|
||||
opts.on("-v", "--verbose", "Print verbose feedback to STDOUT") do
|
||||
$verbose = true
|
||||
opts.on("-q", "--quiet", "Do not print output unless there's a failure") do
|
||||
$verbose = false
|
||||
end
|
||||
opts.on("-rSUBMODULE", "--replace=SUBMODDULE", "Replace an existing grammar submodule.") do |name|
|
||||
$replace = name
|
||||
@@ -82,23 +94,22 @@ Dir.chdir(ROOT)
|
||||
|
||||
if repo_old
|
||||
log "Deregistering: #{repo_old}"
|
||||
`git submodule deinit #{repo_old}`
|
||||
`git rm -rf #{repo_old}`
|
||||
`script/grammar-compiler -update`
|
||||
command('git', 'submodule', 'deinit', repo_old)
|
||||
command('git', 'rm', '-rf', repo_old)
|
||||
command('script/grammar-compiler', 'update', '-f')
|
||||
end
|
||||
|
||||
log "Registering new submodule: #{repo_new}"
|
||||
`git submodule add -f #{https} #{repo_new}`
|
||||
exit 1 if $?.exitstatus > 0
|
||||
`script/grammar-compiler -add #{repo_new}`
|
||||
command('git', 'submodule', 'add', '-f', https, repo_new)
|
||||
command('script/grammar-compiler', 'add', repo_new)
|
||||
|
||||
log "Confirming license"
|
||||
if repo_old
|
||||
`script/licensed`
|
||||
command('script/licensed')
|
||||
else
|
||||
`script/licensed --module "#{repo_new}"`
|
||||
command('script/licensed', '--module', repo_new)
|
||||
end
|
||||
|
||||
log "Updating grammar documentation in vendor/README.md"
|
||||
`bundle exec rake samples`
|
||||
`script/list-grammars`
|
||||
command('bundle', 'exec', 'rake', 'samples')
|
||||
command('script/list-grammars')
|
||||
|
||||
@@ -9,4 +9,4 @@ mkdir -p grammars
|
||||
exec docker run --rm \
|
||||
-u $(id -u $USER):$(id -g $USER) \
|
||||
-v $PWD:/src/linguist \
|
||||
-w /src/linguist -ti $image "$@"
|
||||
-w /src/linguist $image "$@"
|
||||
|
||||
8
tools/grammars/Gopkg.lock
generated
8
tools/grammars/Gopkg.lock
generated
@@ -25,6 +25,12 @@
|
||||
packages = ["."]
|
||||
revision = "06020f85339e21b2478f756a78e295255ffa4d6a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/urfave/cli"
|
||||
packages = ["."]
|
||||
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
|
||||
version = "v1.20.0"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/cheggaaa/pb.v1"
|
||||
packages = ["."]
|
||||
@@ -40,6 +46,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "eb10157687c05a542025c119a5280abe429e29141bde70dd437d48668f181861"
|
||||
inputs-digest = "ba2e3150d728692b49e3e2d652b6ea23db82777c340e0c432cd4af6f0eef9f55"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@@ -17,3 +17,7 @@
|
||||
[[constraint]]
|
||||
name = "gopkg.in/cheggaaa/pb.v1"
|
||||
version = "1.0.18"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/urfave/cli"
|
||||
version = "1.20.0"
|
||||
|
||||
@@ -1,80 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/github/linguist/tools/grammars/compiler"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var linguistRoot = flag.String("linguist", "", "path to Linguist installation")
|
||||
var protoOut = flag.String("proto", "", "dump Protobuf library")
|
||||
var jsonOut = flag.String("json", "", "dump JSON output")
|
||||
var addGrammar = flag.String("add", "", "add a new grammar source")
|
||||
var updateList = flag.Bool("update", false, "update grammars.yml instead of verifying its contents")
|
||||
var report = flag.String("report", "", "write report to file")
|
||||
func cwd() string {
|
||||
cwd, _ := os.Getwd()
|
||||
return cwd
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: %s\n", err)
|
||||
os.Exit(1)
|
||||
func wrap(err error) error {
|
||||
return cli.NewExitError(err, 255)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
app := cli.NewApp()
|
||||
app.Name = "Linguist Grammars Compiler"
|
||||
app.Usage = "Compile user-submitted grammars and check them for errors"
|
||||
|
||||
if _, err := exec.LookPath("csonc"); err != nil {
|
||||
fatal(err)
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "linguist-path",
|
||||
Value: cwd(),
|
||||
Usage: "path to Linguist root",
|
||||
},
|
||||
}
|
||||
|
||||
if *linguistRoot == "" {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
*linguistRoot = cwd
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Usage: "add a new grammar source",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "ignore compilation errors",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
conv, err := compiler.NewConverter(c.String("linguist-path"))
|
||||
if err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
if err := conv.AddGrammar(c.Args().First()); err != nil {
|
||||
if !c.Bool("force") {
|
||||
return wrap(err)
|
||||
}
|
||||
}
|
||||
if err := conv.WriteGrammarList(); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Usage: "update grammars.yml with the contents of the grammars library",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "write grammars.yml even if grammars fail to compile",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
conv, err := compiler.NewConverter(c.String("linguist-path"))
|
||||
if err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
if err := conv.ConvertGrammars(true); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
if err := conv.Report(); err != nil {
|
||||
if !c.Bool("force") {
|
||||
return wrap(err)
|
||||
}
|
||||
}
|
||||
if err := conv.WriteGrammarList(); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "compile",
|
||||
Usage: "convert the grammars from the library",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "proto-out, P"},
|
||||
cli.StringFlag{Name: "out, o"},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
conv, err := compiler.NewConverter(c.String("linguist-path"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if err := conv.ConvertGrammars(false); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if out := c.String("proto-out"); out != "" {
|
||||
if err := conv.WriteProto(out); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
if out := c.String("out"); out != "" {
|
||||
if err := conv.WriteJSON(out); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
if err := conv.Report(); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
conv, err := compiler.NewConverter(*linguistRoot)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if *addGrammar != "" {
|
||||
if err := conv.AddGrammar(*addGrammar); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := conv.ConvertGrammars(*updateList); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := conv.WriteGrammarList(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if *protoOut != "" {
|
||||
if err := conv.WriteProto(*protoOut); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if *jsonOut != "" {
|
||||
if err := conv.WriteJSON(*jsonOut); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if *report == "" {
|
||||
conv.Report(os.Stderr)
|
||||
} else {
|
||||
f, err := os.Create(*report)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
conv.Report(f)
|
||||
f.Close()
|
||||
}
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package compiler
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
@@ -52,6 +51,16 @@ func (conv *Converter) work() {
|
||||
conv.wg.Done()
|
||||
}
|
||||
|
||||
func (conv *Converter) tmpScopes() map[string]bool {
|
||||
scopes := make(map[string]bool)
|
||||
for _, ary := range conv.grammars {
|
||||
for _, s := range ary {
|
||||
scopes[s] = true
|
||||
}
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
|
||||
func (conv *Converter) AddGrammar(source string) error {
|
||||
repo := conv.Load(source)
|
||||
if len(repo.Files) == 0 {
|
||||
@@ -61,17 +70,30 @@ func (conv *Converter) AddGrammar(source string) error {
|
||||
conv.grammars[source] = repo.Scopes()
|
||||
conv.modified = true
|
||||
|
||||
knownScopes := conv.tmpScopes()
|
||||
repo.FixRules(knownScopes)
|
||||
|
||||
if len(repo.Errors) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "The new grammar %s contains %d errors:\n",
|
||||
repo, len(repo.Errors))
|
||||
for _, err := range repo.Errors {
|
||||
fmt.Fprintf(os.Stderr, " - %s\n", err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
return fmt.Errorf("failed to compile the given grammar")
|
||||
}
|
||||
|
||||
fmt.Printf("OK! added grammar source '%s'\n", source)
|
||||
for scope := range repo.Files {
|
||||
fmt.Printf("\tnew scope: %s\n", scope)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conv *Converter) ScopeMap() map[string]*Repository {
|
||||
func (conv *Converter) AllScopes() map[string]bool {
|
||||
// Map from scope -> Repository first to error check
|
||||
// possible duplicates
|
||||
allScopes := make(map[string]*Repository)
|
||||
|
||||
for _, repo := range conv.Loaded {
|
||||
for scope := range repo.Files {
|
||||
if original := allScopes[scope]; original != nil {
|
||||
@@ -82,7 +104,12 @@ func (conv *Converter) ScopeMap() map[string]*Repository {
|
||||
}
|
||||
}
|
||||
|
||||
return allScopes
|
||||
// Convert to scope -> bool
|
||||
scopes := make(map[string]bool)
|
||||
for s := range allScopes {
|
||||
scopes[s] = true
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
|
||||
func (conv *Converter) ConvertGrammars(update bool) error {
|
||||
@@ -112,7 +139,7 @@ func (conv *Converter) ConvertGrammars(update bool) error {
|
||||
conv.modified = true
|
||||
}
|
||||
|
||||
knownScopes := conv.ScopeMap()
|
||||
knownScopes := conv.AllScopes()
|
||||
|
||||
for source, repo := range conv.Loaded {
|
||||
repo.FixRules(knownScopes)
|
||||
@@ -190,7 +217,7 @@ func (conv *Converter) WriteGrammarList() error {
|
||||
return ioutil.WriteFile(ymlpath, outyml, 0666)
|
||||
}
|
||||
|
||||
func (conv *Converter) Report(w io.Writer) {
|
||||
func (conv *Converter) Report() error {
|
||||
var failed []*Repository
|
||||
for _, repo := range conv.Loaded {
|
||||
if len(repo.Errors) > 0 {
|
||||
@@ -202,13 +229,20 @@ func (conv *Converter) Report(w io.Writer) {
|
||||
return failed[i].Source < failed[j].Source
|
||||
})
|
||||
|
||||
total := 0
|
||||
for _, repo := range failed {
|
||||
fmt.Fprintf(w, "- [ ] %s (%d errors)\n", repo, len(repo.Errors))
|
||||
fmt.Fprintf(os.Stderr, "- [ ] %s (%d errors)\n", repo, len(repo.Errors))
|
||||
for _, err := range repo.Errors {
|
||||
fmt.Fprintf(w, " - [ ] %s\n", err)
|
||||
fmt.Fprintf(os.Stderr, " - [ ] %s\n", err)
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
total += len(repo.Errors)
|
||||
}
|
||||
|
||||
if total > 0 {
|
||||
return fmt.Errorf("the grammar library contains %d errors", total)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewConverter(root string) (*Converter, error) {
|
||||
|
||||
@@ -81,7 +81,7 @@ func (repo *Repository) CompareScopes(scopes []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *Repository) FixRules(knownScopes map[string]*Repository) {
|
||||
func (repo *Repository) FixRules(knownScopes map[string]bool) {
|
||||
for _, file := range repo.Files {
|
||||
w := walker{
|
||||
File: file,
|
||||
|
||||
@@ -19,7 +19,7 @@ func (w *walker) checkInclude(rule *grammar.Rule) {
|
||||
}
|
||||
|
||||
include = strings.Split(include, "#")[0]
|
||||
_, ok := w.Known[include]
|
||||
ok := w.Known[include]
|
||||
if !ok {
|
||||
if !w.Missing[include] {
|
||||
w.Missing[include] = true
|
||||
@@ -73,7 +73,7 @@ func (w *walker) walk(rule *grammar.Rule) {
|
||||
|
||||
type walker struct {
|
||||
File *LoadedFile
|
||||
Known map[string]*Repository
|
||||
Known map[string]bool
|
||||
Missing map[string]bool
|
||||
Errors []error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user