Skip to content

Commit

Permalink
feat: search upward to find xc compatable README files (#36)
Browse files Browse the repository at this point in the history
* feat: search upward to find xc compatable README files

* chore: document parent dir behaviour
  • Loading branch information
joerdav authored Feb 20, 2023
1 parent 3f6f702 commit 17d6bb0
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 40 deletions.
82 changes: 60 additions & 22 deletions cmd/xc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package main
import (
"context"
_ "embed"
"errors"
"flag"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"runtime/debug"
"strings"

Expand All @@ -16,18 +19,29 @@ import (
"github.com/posener/complete"
)

var version = ""
// ErrNoMarkdownFile will be returned if no markdown file is found in the cwd or any parent directories.
var ErrNoMarkdownFile = errors.New("no xc compatable mardown file found")

type config struct {
version, help, short, md bool
filename string
}

var cfg = config{}

//go:embed usage.txt
var usage string

var (
version = ""
cfg = config{}
)

func main() {
if err := runMain(); err != nil {
fmt.Println("xc error: ", err.Error())
os.Exit(1)
}
}

func flags() {
log.SetFlags(0)
log.SetOutput(os.Stderr)
Expand All @@ -37,16 +51,52 @@ func flags() {
flag.BoolVar(&cfg.version, "version", false, "show xc version")
flag.BoolVar(&cfg.help, "help", false, "shows xc usage")
flag.BoolVar(&cfg.help, "h", false, "shows xc usage")
flag.StringVar(&cfg.filename, "file", "README.md", "specify markdown file that contains tasks")
flag.StringVar(&cfg.filename, "f", "README.md", "specify markdown file that contains tasks")
flag.StringVar(&cfg.filename, "file", "", "specify markdown file that contains tasks")
flag.StringVar(&cfg.filename, "f", "", "specify markdown file that contains tasks")
flag.BoolVar(&cfg.short, "short", false, "list task names in a short format")
flag.BoolVar(&cfg.short, "s", false, "list task names in a short format")
flag.BoolVar(&cfg.md, "md", false, "print the markdown for a task rather than running it")
flag.Parse()
}

func parse() (t models.Tasks, err error) {
b, err := os.Open(cfg.filename)
func parse() (t models.Tasks, directory string, err error) {
if cfg.filename != "" {
return tryParse(cfg.filename)
}
curr, err := filepath.Abs(filepath.Dir("."))
if err != nil {
return nil, "", err
}
return searchUpForFile(curr)
}

func searchUpForFile(curr string) (t models.Tasks, directory string, err error) {
rm := filepath.Join(curr, "README.md")
t, directory, err = tryParse(rm)
if err == nil {
return t, directory, err
}
if err != nil && !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, parser.ErrNoTasksTitle) {
return nil, "", err
}
git := filepath.Join(curr, ".git")
_, err = os.Stat(git)
if err == nil {
return nil, "", ErrNoMarkdownFile
}
next := filepath.Dir(curr)
if strings.HasSuffix(next, string([]rune{filepath.Separator})) {
return nil, "", ErrNoMarkdownFile
}
return searchUpForFile(next)
}

func tryParse(path string) (t models.Tasks, directory string, err error) {
directory = filepath.Dir(path)
if err != nil {
return
}
b, err := os.Open(path)
if err != nil {
return
}
Expand Down Expand Up @@ -91,16 +141,9 @@ func printTask(t models.Task, maxLen int) {
}
}

func main() {
if err := runMain(); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}

func runMain() error {
flags()
t, err := parse()
t, dir, err := parse()
if completion(t) {
return nil
}
Expand All @@ -117,7 +160,7 @@ func runMain() error {
if err != nil {
return err
}
tav := getArgs()
tav := flag.Args()
// xc
if len(tav) == 0 {
printTasks(t)
Expand All @@ -139,7 +182,7 @@ func runMain() error {

}
// xc task1
runner, err := run.NewRunner(t)
runner, err := run.NewRunner(t, dir)
if err != nil {
return err
}
Expand All @@ -151,11 +194,6 @@ func runMain() error {
return nil
}

func getArgs() []string {
args := flag.Args()
return args
}

func getVersion() string {
if version != "" {
return version
Expand Down
13 changes: 10 additions & 3 deletions cmd/xc/usage.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
xc [task] [inputs...] - run tasks
xc [task] [inputs...]
Run a task from an xc compatible markdown file.
If -file is not specified and no README.md is found in the current directory,
xc will search in parent files for convenience.
-f -file string
Specify a markdown file that contains tasks (default "README.md").
Specify a markdown file that contains tasks.
Default: "README.md"
-md
Print the markdown for a task rather than running it.

xc - list tasks
xc
List tasks from an xc compatible markdown file.
If -file is not specified and no README.md is found in the current directory,
xc will search in parent files for convenience.
-s -short
List task names in a short format.
-h -help
Expand Down
5 changes: 4 additions & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"github.com/joerdav/xc/models"
)

// ErrNoTaskTitle is returned if the markdown contains no title named Tasks
var ErrNoTasksTitle = errors.New("no tasks title found")

// TRIM_VALUES are the characters that should be ignored in titles and attributes
const TRIM_VALUES = "_*` "

Expand Down Expand Up @@ -268,6 +271,6 @@ func NewParser(r io.Reader) (p parser, err error) {
p.tasksLevel = level
return
}
err = errors.New("no Tasks section found")
err = ErrNoTasksTitle
return
}
3 changes: 2 additions & 1 deletion parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package parser

import (
_ "embed"
"errors"
"strings"
"testing"

Expand Down Expand Up @@ -55,7 +56,7 @@ echo "Hello, world2!"

func TestParseFileNoTasks(t *testing.T) {
_, err := NewParser(strings.NewReader(e))
if err.Error() != "no Tasks section found" {
if !errors.Is(err, ErrNoTasksTitle) {
t.Fatalf("expected error %v got: %v", "no Tasks section found", err)
}
}
Expand Down
24 changes: 15 additions & 9 deletions run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/joerdav/xc/models"
Expand All @@ -21,6 +22,7 @@ type ScriptRunner func(ctx context.Context, runner *interp.Runner, node syntax.N
type Runner struct {
scriptRunner ScriptRunner
tasks models.Tasks
dir string
}

// NewRunner takes Tasks and returns a Runner.
Expand All @@ -31,12 +33,13 @@ type Runner struct {
//
// NewRunner will return an error in the case that Dependent tasks are cyclical,
// invalid or at a larger depth than 50.
func NewRunner(ts models.Tasks) (runner Runner, err error) {
func NewRunner(ts models.Tasks, dir string) (runner Runner, err error) {
runner = Runner{
scriptRunner: func(ctx context.Context, runner *interp.Runner, node syntax.Node) error {
return runner.Run(ctx, node)
},
tasks: ts,
dir: dir,
}
for _, t := range ts {
err = runner.ValidateDependencies(t.Name, []string{})
Expand Down Expand Up @@ -127,17 +130,10 @@ func (r *Runner) Run(ctx context.Context, name string, inputs []string) error {
if err != nil {
return err
}
path, err := os.Getwd()
if err != nil {
return err
}
if task.Dir != "" {
path = task.Dir
}
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
interp.Dir(path),
interp.Dir(r.getExecutionPath(task)),
interp.Params(inputs...),
)
if err != nil {
Expand All @@ -146,6 +142,16 @@ func (r *Runner) Run(ctx context.Context, name string, inputs []string) error {
return r.scriptRunner(ctx, runner, file)
}

func (r *Runner) getExecutionPath(task models.Task) string {
if task.Dir == "" {
return r.dir
}
if filepath.IsAbs(task.Dir) {
return task.Dir
}
return filepath.Join(r.dir, task.Dir)
}

// ValidateDependencies checks that task dependencies follow these rules:
// - No deeper dependency trees than maxDeps.
// - Dependencies must exist as tasks.
Expand Down
8 changes: 4 additions & 4 deletions run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestRun(t *testing.T) {
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
runner, err := NewRunner(tt.tasks)
runner, err := NewRunner(tt.tasks, "")
fmt.Println("results", err, tt.expectedParseError)
if (err != nil) != tt.expectedParseError {
t.Fatalf("expected error %v, got %v", tt.expectedParseError, err)
Expand Down Expand Up @@ -156,7 +156,7 @@ func TestRunWithInputs(t *testing.T) {
Script: "somecmd",
Inputs: []string{"FOO"},
},
})
}, "")
if err != nil {
t.Fatal(err)
}
Expand All @@ -172,7 +172,7 @@ func TestRunWithInputs(t *testing.T) {
Script: "somecmd",
Inputs: []string{"FOO"},
},
})
}, "")
if err != nil {
t.Fatal(err)
}
Expand All @@ -196,7 +196,7 @@ func TestRunWithInputs(t *testing.T) {
Script: "somecmd",
Inputs: []string{"FOO"},
},
})
}, "")
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit 17d6bb0

Please sign in to comment.