Skip to content

Commit

Permalink
feat: Add .RUNFILE.DIR, .SELF, .SELF.DIR (#55)
Browse files Browse the repository at this point in the history
Adds new attributes:
- .RUNFILE.DIR
- .SELF.RUNFILE
- .SELF.DIR

chore: Adds config properties:
- config.RunfileAbsDir
- config.CurrentRunfile
- config.CurrentRunfileAbs
- config.CurrentRunfileAbsDir

docs: Adds minimum viable docs for existing attributes
bug: Fixes defer in for loop
  • Loading branch information
TekWizely authored Jul 25, 2022
1 parent 1fbfde0 commit e28963e
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 23 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ In run, the entire script is executed within a single sub-shell.
- [Referencing Other Variables](#referencing-other-variables)
- [Shell Substitution](#shell-substitution)
- [Conditional Assignment](#conditional-assignment)
- [Runfile Attributes](#runfile-attributes)
- [`.SHELL`](#runfile-attributes)
- [`.RUN`](#runfile-attributes)
- [`.RUNFILE`](#runfile-attributes)
- [`.RUNFILE.DIR`](#runfile-attributes)
- [`.SELF`](#runfile-attributes)
- [`.SELF.DIR`](#runfile-attributes)
- [Assertions](#assertions)
- [Includes](#includes)
- [File Globbing](#file-globbing)
Expand Down Expand Up @@ -762,6 +769,24 @@ NAME="Newman" run hello
Hello, Newman
```

----------------------
### Runfile Attributes

Attributes are special variables used by the Run engine.

Their names start with `.` to avoid colliding with [runfile variables](#runfile-variables) and environment variables.<br/>

Following is the list of Run's attributes:

| Attribute | Description
|----------------|------------
| `.SHELL` | Contains the shell command that will be used to execute command scripts. See [Script Shells](#script-shells) for more details.
| `.RUN` | Contains the absolute path of the run binary currently in use. Useful for [Invoking Other Commands & Runfiles](#invoking-other-commands--runfiles).
| `.RUNFILE` | Contains the absolute path of the **primary** Runfile.
| `.RUNFILE.DIR` | Contains the absolute path of the parent folder of the **primary** runfile.
| `.SELF` | Contains the absolute path of the **current** (primary or included) runfile.
| `.SELF.DIR` | Contains the absolute path of the parent folder of the **current** runfile.

--------------
### Assertions

Expand Down
63 changes: 45 additions & 18 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ var ParseBytes func(runfile []byte) *Ast = nil
//
func ProcessAST(ast *Ast) *runfile.Runfile {
rf := runfile.NewRunfile()
// Seed defaults
// Seed attributes
//
rf.Scope.PutAttr(".SHELL", config.DefaultShell)
rf.Scope.PutAttr(".RUN", config.RunBin)
rf.Scope.PutAttr(".RUNFILE", config.RunfileAbs)
rf.Scope.PutAttr(".RUNFILE.DIR", config.RunfileAbsDir)
rf.Scope.PutAttr(".SELF", config.CurrentRunfileAbs)
rf.Scope.PutAttr(".SELF.DIR", config.CurrentRunfileAbsDir)
for _, n := range ast.nodes {
n.Apply(rf)
}
Expand All @@ -38,6 +41,17 @@ func ProcessAST(ast *Ast) *runfile.Runfile {
// Assumes Runfile created via ProcessAST
//
func ProcessAstRunfile(ast *Ast, rf *runfile.Runfile) {
// Seed attributes
// Save current values, restore before leaving
//
selfRunfileBak, _ := rf.Scope.GetAttr(".SELF")
selfRunfileDirBak, _ := rf.Scope.GetAttr(".SELF.DIR")
defer func() {
rf.Scope.PutAttr(".SELF", selfRunfileBak)
rf.Scope.PutAttr(".SELF.DIR", selfRunfileDirBak)
}()
rf.Scope.PutAttr(".SELF", config.CurrentRunfileAbs)
rf.Scope.PutAttr(".SELF.DIR", config.CurrentRunfileAbsDir)
for _, n := range ast.nodes {
n.Apply(rf)
}
Expand Down Expand Up @@ -183,10 +197,10 @@ func (a *ScopeInclude) Apply(r *runfile.Runfile) {
err error
)
// We want the absolute file paths for include tracking
// If pattern is not absolute, assume its relative to dir(Runfile)
// If pattern is not absolute, assume its relative to config.RunfileAbsDir
//
if !filepath.IsAbs(filePattern) {
filePattern = filepath.Join(filepath.Dir(config.RunfileAbs), filePattern)
filePattern = filepath.Join(config.RunfileAbsDir, filePattern)
}
// Skip fileglob if pattern does not look like a glob.
// By checking this ourselves, we hope to gain more control over error reporting,
Expand All @@ -205,6 +219,18 @@ func (a *ScopeInclude) Apply(r *runfile.Runfile) {
//
files = []string{filePattern}
}
// Save log prefix and current runfile values, restore before leaving
//
logPrefixBak := log.Prefix()
currentRunfileBak := config.CurrentRunfile
currentRunfileAbsBak := config.CurrentRunfileAbs
currentRunfileAbsDirBak := config.CurrentRunfileAbsDir
defer func() {
log.SetPrefix(logPrefixBak)
config.CurrentRunfile = currentRunfileBak
config.CurrentRunfileAbs = currentRunfileAbsBak
config.CurrentRunfileAbsDir = currentRunfileAbsDirBak
}()
// NOTE: filenames assumed to be absolute
// TODO Sort list (path aware) ?
//
Expand All @@ -223,35 +249,36 @@ func (a *ScopeInclude) Apply(r *runfile.Runfile) {
// Mark file included
//
config.IncludedFiles[filename] = struct{}{}
// Save prefix and runfile, restore before leaving
//
logPrefix := log.Prefix()
configRunfile := config.Runfile
configRunfileAbs := config.RunfileAbs
defer func() {
log.SetPrefix(logPrefix)
config.Runfile = configRunfile
config.RunfileAbs = configRunfileAbs
}()
// Set new prefix so parse errors/line numbers will be relative to the correct file
// For brevity, use path relative to dir(Runfile) if possible
// For brevity, use path relative to config.RunfileAbsDir if possible
//
var filenameRel string
if filenameRel, err = filepath.Rel(filepath.Dir(config.RunfileAbs), filename); err == nil && len(filenameRel) > 0 && !strings.HasPrefix(filenameRel, ".") {
if filenameRel, err = filepath.Rel(config.RunfileAbsDir, filename); err == nil && len(filenameRel) > 0 && !strings.HasPrefix(filenameRel, ".") {
log.SetPrefix(filenameRel + ": ")
config.Runfile = filenameRel
config.CurrentRunfile = filenameRel
} else {
log.SetPrefix(filename + ": ")
config.Runfile = filename
config.CurrentRunfile = filename
}
config.RunfileAbs = filename
config.CurrentRunfileAbs = filename
config.CurrentRunfileAbsDir = filepath.Dir(config.CurrentRunfileAbs)
// Parse the file
//
rfAst := ParseBytes(fileBytes)
// Process the AST
//
ProcessAstRunfile(rfAst, r)
// Restore values here too for consistency
//
log.SetPrefix(logPrefixBak)
config.CurrentRunfile = currentRunfileBak
config.CurrentRunfileAbs = currentRunfileAbsBak
config.CurrentRunfileAbsDir = currentRunfileAbsDirBak
} else {
//
// We're about to panic, assume prior defer will restore values before exiting
//

if err == nil {
panic(fmt.Errorf("include runfile not found: '%s'", filename))
} else {
Expand Down
20 changes: 18 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,18 @@ var CommandMap = make(map[string]*Command)
//
var RunBin string

// Runfile holds the (possibly relative) path to the current Runfile.
// Runfile holds the (possibly relative) path to the primary Runfile.
//
var Runfile string

// RunfileAbs holds the absolute path to the current Runfile.
// RunfileAbs holds the absolute path to the primary Runfile.
//
var RunfileAbs string

// RunfileAbsDir holds the absolute path to the containing folder of the primary Runfile.
//
var RunfileAbsDir string

// RunfileIsLoaded is true if the runfile has been successfully loaded
//
var RunfileIsLoaded bool
Expand All @@ -71,6 +75,18 @@ var RunfileIsLoaded bool
//
var RunfileIsDefault bool

// CurrentRunfile holds the (possibly relative) path to the current (primary or otherwise) Runfile.
//
var CurrentRunfile string

// CurrentRunfileAbs holds the absolute path to the current (primary or otherwise) Runfile.
//
var CurrentRunfileAbs string

// CurrentRunfileAbsDir holds the absolute path to the containing folder of the current (primary or otherwise) Runfile.
//
var CurrentRunfileAbsDir string

// IncludedFiles contains a set of abs file paths to included Runfiles
//
var IncludedFiles = map[string]struct{}{}
Expand Down
6 changes: 3 additions & 3 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func parseMain(ctx *parseContext, p *parser.Parser) parseFn {
ctx.l.PushFn(lexer.LexExpectNewline)
ctx.setLexFn(lexer.LexAssert)
assert := &ast.ScopeAssert{}
assert.Runfile = config.Runfile // Assumed to be current file
assert.Runfile = config.CurrentRunfile
assert.Line = t.Line()
assert.Test = expectTestString(ctx, p)
assert.Message = expectAssertMessage(ctx, p)
Expand Down Expand Up @@ -267,7 +267,7 @@ func tryMatchCmd(ctx *parseContext, p *parser.Parser, cmdConfig *ast.CmdConfig)
Name: name,
Config: cmdConfig,
Script: script,
Runfile: config.Runfile, // Assumed to be current file
Runfile: config.CurrentRunfile,
Line: line,
})
return true
Expand Down Expand Up @@ -369,7 +369,7 @@ func tryMatchDocBlock(ctx *parseContext, p *parser.Parser) (*ast.CmdConfig, bool
ctx.l.PushFn(lexer.LexExpectNewline)
ctx.setLexFn(lexer.LexAssert)
assert := &ast.CmdAssert{}
assert.Runfile = config.Runfile // Assumed to be current file
assert.Runfile = config.CurrentRunfile
assert.Line = t.Line()
assert.Test = expectTestString(ctx, p)
assert.Message = expectAssertMessage(ctx, p)
Expand Down
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ func main() {
exitCode = 2
return
}
// Runfile parent directory used for .RUNFILE.DIR attribute
//
config.RunfileAbsDir = filepath.Dir(config.RunfileAbs)
// Current runfile/dir values, also used for .SELF / .SELF.DIR attributes
//
config.CurrentRunfileAbs = config.RunfileAbs
config.CurrentRunfileAbsDir = config.RunfileAbsDir
// Read the file (will re-check exists/stat, but that's the cost of the abstraction)
//
bytes, exists, err = util.ReadFileIfExists(config.RunfileAbs)
Expand Down

0 comments on commit e28963e

Please sign in to comment.