gfmrun

package module
v0.0.0-...-279a764 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2016 License: MIT Imports: 21 Imported by: 0

README

gfmrun

A (GitHub-Flavored Markdown Runner). Runs stuff inside code gates (maybe!).

This project is not intended to fully replace the example code running capabilities of any given language's tooling, but instead to fill a gap. In the case of a Go repository, for example, it can be handy to have some verifiable code examples in the README.md and example functions in *_test.go files.

Explicitly Supported Languages

Bash

If a code example has a declared language of bash, then gfxmr will write the source to a temporary file and run it via whatever executable is first in line to respond to bash.

for i in {97..99} ; do
  echo "$i problems" >&2
done

echo "Begot by all the supernova (${0})"
exit 0
Go

If a code example has a declared language of go and the first line is package main, then gfmrun will write the source to a temporary file, build it, and run it. It is worth noting that go run is not used, as this executes a child process of its own, thus making process management and exit success detection all the more complex.

package main

import (
  "fmt"
  "os"
)

func main() {
  fmt.Printf("---> %v\n", os.Args[0])
  fmt.Println("we could make an entire album out of this one sound")
}
Java

If a code example has a declared language of java and a line matching ^public class ([^ ]+), then gfmrun will write the source to a temporary file, build it, and run the class by name.

public class GalacticPerimeter {
  public static void main(String[] args) {
    System.out.println(System.getenv("FILE"));
    System.out.println("Awaken the hive");
  }
}
JavaScript (assumed node.js compatible)

If a code example has a declared language of javascript, then gfmrun will write the source to a temporary file and run it via whatever executable is first in line to respond to node.

var main = function() {
  console.log("they won't stop at dancin, no");
  console.log("they won't stop at dancin");
};

if (require.main == module) {
  main();
}
JSON

If a code example has a declared language of json, then gfmrun will write the source to a temporary file and "run" it via the node executable for validation.

{
  "no": "output",
  "levels": [
    8000,
    9000,
    9001
  ]
}
Python

If a code example has a declared language of python, then gfxmr will write the source to a temporary file and run it via whatever executable is first in line to respond to python.

from __future__ import print_function

import sys


def main():
    print('lipstick ringo dance all night {!r}!'.format(sys.argv))
    return 0


if __name__ == '__main__':
    sys.exit(main())
Ruby

If a code example has a declared language of ruby, then gfxmr will write the source to a temporary file and run it via whatever executable is first in line to respond to ruby.


def main
  puts $0
  puts "get out of the path of the king"
  return 0
end

if $0 == __FILE__
  exit main
end
Shell

If a code example has a declared language that can be mapped to the linguist definition of shell, then gfmrun will write the source to a temporary file and run it via whatever executable is first in line to respond to bash.

if [ 0 -eq 0 ] ; then
  echo "Just the way you like it, yes"
  echo "just the way you like it. (${0})"
fi
exit 0
Sh

If a code example has a declared language of sh, then gfxmr will write the source to a temporary file and run it via whatever executable is first in line to respond to sh.

if [ 5 -eq 3 ] ; then
  echo "YOU FOUND THE MARBLE IN THE OATMEAL"
else
  echo "Saddle up preacher, don't look back. (${0})"
fi
exit 0
Zsh

If a code example has a declared language of zsh, then gfxmr will write the source to a temporary file and run it via whatever executable is first in line to respond to zsh.

printf "Kiss me.\nJust kiss me.\n(${0})\n"

bye 0
Implicitly Supported Languages

If a code example's declared language can be matched to one of the explicitly supported languages listed above via the linguist languages definition, then the matched language's runner will be used.

This example declares node, but will be run via the javascript frob, which happens to use the node executable anyway:

if (1 / 1 == 1) {
  console.log("kiss me, Nefertiti");
}
Disabling automatic download of languages.yml

By default, the linguist languages definition is downloaded if not present. This behavior can be disabled by passing the --no-auto-pull / -N flag or setting a GFMRUN_NO_AUTO_PULL=true environment variable.

Tag annotation comments

gfmrun supports the use of JSON tags embedded in comments preceding code blocks, e.g. (just pretend ^ are backticks):

<!-- { "awesome": "maybe", "all on one line": "yep" } -->
^^^ go
package lolmain
// ... stuff
^^^
<!-- {
  "wow": "cool",
  "multiple": "lines"
} -->
^^^ go
package lolmain
// ... stuff
^^^
<!-- {
  "super": "advanced",
  "whitespace after the comment": "mindblown"
} -->


^^^ go
package lolmain
// ... stuff
^^^
"output" tag

Given a regular expression string value, asserts that the program output (stdout) matches.

"args" tag

Given a string array, run the program with the value as command line arguments.

"interrupt" tag

Given either a truthy or duration string value, interrupts the program via increasingly serious signals (INT, HUP, TERM, and finally KILL). If the value is truthy, then the default duration is used (3s). If the value is a string duration, then the parsed duration is used. This tag is intended for use with long-lived example programs such as HTTP servers.

"os" tag

Given either a string or array of strings, skips the program if the current OS does not match. When absent, no filter is applied.

Examples

No tag annotations, expected to be short-lived and exit successfully:

var _ = 1 / 1;

Annotated with an "output" JSON tag that informs gfxmr to verify the example program's output:

console.log("Ohai from the land of GitHub-Flavored Markdown :wave:");

Annotated with an "interrupt" JSON tag that informs gfmrun to interrupt the example program after a specified duration, which implies that the exit code is ignored (not Windows-compatible):

package main

import (
  "fmt"
  "net/http"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Why Hello From Your Friendly Server Example :bomb:\n")
  })
  http.ListenAndServe(":8990", nil)
}

Similar to the above example, but the "interrupt" tag is truthy rather than a specific interval, which will result in the default interrupt duration being used (3s). It is also annotated with an "output" tag that takes precedence over the "interrupt" tag's behavior of ignoring the exit code:

package main

import (
  "fmt"
  "net/http"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Hello Again From Your Friendly Server Example :bomb:\n")
  })
  fmt.Println(":bomb:")
  http.ListenAndServe(":8989", nil)
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	DefaultLanguagesYml    = filepath.Join(getCacheDir(), "languages.yml")
	DefaultLanguagesYmlURL = "https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml"
)
View Source
var (
	VersionString   = ""
	RevisionString  = ""
	GeneratedString = ""
	CopyrightString = ""
)
View Source
var (
	DefaultFrobs = map[string]Frob{
		"bash":       NewSimpleInterpretedFrob("bash", "bash"),
		"go":         &GoFrob{},
		"java":       &JavaFrob{},
		"javascript": NewSimpleInterpretedFrob("js", "node"),
		"json":       NewSimpleInterpretedFrob("json", "node"),
		"python":     NewSimpleInterpretedFrob("py", "python"),
		"ruby":       NewSimpleInterpretedFrob("rb", "ruby"),
		"shell":      NewSimpleInterpretedFrob("bash", "bash"),
		"sh":         NewSimpleInterpretedFrob("sh", "sh"),
		"zsh":        NewSimpleInterpretedFrob("zsh", "zsh"),
	}
)

Functions

func ExtractExamples

func ExtractExamples(sources []string, outDir, languagesFile string, autoPull bool, log *logrus.Logger) error

func NewCLI

func NewCLI() *cli.App

func PullLanguagesYml

func PullLanguagesYml(srcURL, destFile string) error

func RunExamples

func RunExamples(sources []string, expectedCount int, languagesFile string, autoPull bool, log *logrus.Logger) error

Types

type Frob

type Frob interface {
	Extension() string
	CanExecute(*Runnable) error
	TempFileName(*Runnable) string
	Environ(*Runnable) []string
	Commands(*Runnable) []*command
}

func NewSimpleInterpretedFrob

func NewSimpleInterpretedFrob(ext, interpreter string) Frob

type GoFrob

type GoFrob struct{}

func (*GoFrob) CanExecute

func (e *GoFrob) CanExecute(rn *Runnable) error

func (*GoFrob) Commands

func (e *GoFrob) Commands(_ *Runnable) []*command

func (*GoFrob) Environ

func (e *GoFrob) Environ(_ *Runnable) []string

func (*GoFrob) Extension

func (e *GoFrob) Extension() string

func (*GoFrob) TempFileName

func (e *GoFrob) TempFileName(rn *Runnable) string

type InterpretedFrob

type InterpretedFrob struct {
	// contains filtered or unexported fields
}

func (*InterpretedFrob) CanExecute

func (e *InterpretedFrob) CanExecute(rn *Runnable) error

func (*InterpretedFrob) Commands

func (e *InterpretedFrob) Commands(_ *Runnable) []*command

func (*InterpretedFrob) Environ

func (e *InterpretedFrob) Environ(_ *Runnable) []string

func (*InterpretedFrob) Extension

func (e *InterpretedFrob) Extension() string

func (*InterpretedFrob) TempFileName

func (e *InterpretedFrob) TempFileName(rn *Runnable) string

type JavaFrob

type JavaFrob struct{}

func (*JavaFrob) CanExecute

func (e *JavaFrob) CanExecute(rn *Runnable) error

func (*JavaFrob) Commands

func (e *JavaFrob) Commands(rn *Runnable) []*command

func (*JavaFrob) Environ

func (e *JavaFrob) Environ(_ *Runnable) []string

func (*JavaFrob) Extension

func (e *JavaFrob) Extension() string

func (*JavaFrob) TempFileName

func (e *JavaFrob) TempFileName(rn *Runnable) string

type LanguageDefinition

type LanguageDefinition struct {
	Name         string              `json:"-"`
	Type         string              `json:"type,omitempty" yaml:"type"`
	Aliases      []string            `json:"aliases,omitempty" yaml:"aliases"`
	Interpreters []string            `json:"interpreters,omitempty" yaml:"interpreters"`
	AceMode      string              `json:"ace_mode,omitempty" yaml:"ace_mode"`
	Group        string              `json:"group,omitempty" yaml:"group"`
	Canonical    *LanguageDefinition `json:"-"`
}

type Languages

type Languages struct {
	Map map[string]*LanguageDefinition
}

func LoadLanguages

func LoadLanguages(languagesYml string) (*Languages, error)

func (*Languages) Lookup

func (l *Languages) Lookup(identifier string) *LanguageDefinition

type Runnable

type Runnable struct {
	Frob       Frob
	RawTags    string
	Tags       map[string]interface{}
	SourceFile string
	BlockStart string
	Lang       string
	LineOffset int
	Lines      []string
	// contains filtered or unexported fields
}

func NewRunnable

func NewRunnable(sourceName string, log *logrus.Logger) *Runnable

func (*Runnable) Args

func (rn *Runnable) Args() []string

func (*Runnable) Begin

func (rn *Runnable) Begin(lineno int, line string)

func (*Runnable) ExpectedOutput

func (rn *Runnable) ExpectedOutput() *regexp.Regexp

func (*Runnable) Extract

func (rn *Runnable) Extract(i int, dir string) *runResult

func (*Runnable) GoString

func (rn *Runnable) GoString() string

func (*Runnable) Interruptable

func (rn *Runnable) Interruptable() (bool, time.Duration)

func (*Runnable) IsValidOS

func (rn *Runnable) IsValidOS() bool

func (*Runnable) Run

func (rn *Runnable) Run(i int) *runResult

func (*Runnable) String

func (rn *Runnable) String() string

type Runner

type Runner struct {
	Sources   []string
	Count     int
	Frobs     map[string]Frob
	Languages *Languages
	// contains filtered or unexported fields
}

Runner is the top level of execution for running examples in sources

func NewRunner

func NewRunner(sources []string, count int, languagesYml string, autoPull bool, log *logrus.Logger) (*Runner, error)

NewRunner makes a *Runner from a slice of sources, optional expected example count, optional languages.yml location, and a log

func (*Runner) Run

func (r *Runner) Run() []error

Run scans all sources for runnable examples, runs them, and returns a slice of errors encountered

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL