package main
//go:generate goyacc -o parse.go parse.y
import (
"errors"
"fmt"
"os"
"reflect"
"sync"
"unsafe"
"github.com/google/shlex"
"golang.org/x/net/html"
)
// A Datum is a message passed through an hq pipeline. Invariant: all but one of
// the elements must be nil
type Datum struct {
HTML *html.Node
Text *string
End bool
}
// A Filter performs a permutation on a stream of `Datum`s
type Filter interface {
Execute(args []string, ch []<-chan Datum, out chan Datum) error
}
// Filters is the list of built-in filters
var Filters map[string]Filter = make(map[string]Filter)
// Select returns the first channel which sends a value, along with the value
// sent and whether or not the channel is closed
func Select(chs []<-chan Datum) (<-chan Datum, *Datum) {
var (
cases []reflect.SelectCase
d Datum
)
for _, ch := range chs {
cases = append(cases, reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ch),
})
}
chosen, v, ok := reflect.Select(cases)
if !ok {
return nil, nil
}
d.HTML = (*html.Node)(unsafe.Pointer(v.Field(0).Pointer()))
d.Text = (*string)(unsafe.Pointer(v.Field(1).Pointer()))
d.End = v.Field(2).Bool()
return chs[chosen], &d
}
// Exec executes an expression
func Exec(e Expr, ins []<-chan Datum, out chan Datum) error {
if e.Pipe != nil {
mid := make(chan Datum)
errs := make(chan error)
defer close(errs)
go func() {
errs <- Exec(e.Pipe.First, ins, mid)
}()
go func() {
errs <- Exec(e.Pipe.Second, []<-chan Datum{mid}, out)
}()
if err := <-errs; err != nil {
for range mid {
}
<-errs
return err
}
return <-errs
}
if e.Multi != nil {
var chs []chan Datum
var mids []chan Datum
errs := make(chan error)
defer close(errs)
for _, exp := range e.Multi.Cmds {
chs = append(chs, make(chan Datum))
mids = append(mids, make(chan Datum))
go func(exp Expr, in <-chan Datum, out chan Datum) {
errs <- Exec(exp, []<-chan Datum{in}, out)
}(exp, chs[len(chs)-1], mids[len(mids)-1])
}
go func() {
var wg sync.WaitGroup
for _, d := Select(ins); d != nil; _, d = Select(ins) {
for _, ch := range chs {
wg.Add(1)
go func(ch chan Datum, d *Datum) {
ch <- *d
wg.Done()
}(ch, d)
}
}
wg.Wait()
for _, ch := range chs {
close(ch)
}
}()
go func() {
// Go's type system is fucking dumb
var inputs []<-chan Datum
for _, mid := range mids {
inputs = append(inputs, mid)
}
errs <- Exec(e.Multi.Out, inputs, out)
}()
err := <-errs
for range e.Multi.Cmds {
if e := <-errs; e != nil {
err = e
}
}
return err
}
if len(e.Cmd) == 0 {
close(out)
return errors.New("no filter")
}
if filter, ok := Filters[e.Cmd[0]]; ok {
return filter.Execute(e.Cmd, ins, out)
}
close(out)
return errors.New(e.Cmd[0] + ": not found")
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "usage: "+os.Args[0]+" <filter>")
os.Exit(1)
}
cmd, err := shlex.Split(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, "shlex:", err)
os.Exit(1)
}
yyParse(&yyLex{Toks: cmd, Err: os.Stderr})
root, err := html.Parse(os.Stdin)
if err != nil {
fmt.Fprintln(os.Stderr, "parse:", err)
os.Exit(1)
}
in := make(chan Datum)
out := make(chan Datum)
errs := make(chan error)
go func() {
errs <- Exec(Exp, []<-chan Datum{in}, out)
}()
go func() {
in <- Datum{HTML: root}
close(in)
}()
go func() {
for d := range out {
if d.HTML != nil {
html.Render(os.Stdout, d.HTML)
fmt.Println("")
}
if d.Text != nil {
fmt.Println(*d.Text)
}
}
}()
if err := <-errs; err != nil {
fmt.Fprintln(os.Stderr, "exec:", err)
os.Exit(1)
}
}