Francisco J Ballesteroswrap line ink text select fix0b2a36110 years agoHistory
443 lines · 9.4 KB · Go
RawDownload
package cmd import (	"bytes"	"clive/u"	"clive/zx"	"errors"	"fmt"	fpath "path"	"strings"	"unicode/utf8") func Stat(path string) (zx.Dir, error) {	upath := path	path = AbsPath(path)	rc := NS().Stat(path)	d := <-rc	if d != nil {		d["Upath"] = upath		d["Rpath"] = "/"	}	return d, cerror(rc)} func Get(path string, off, count int64) <-chan []byte {	path = AbsPath(path)	return NS().Get(path, off, count)} func GetAll(path string) ([]byte, error) {	path = AbsPath(path)	return zx.GetAll(NS(), path)} // Unlike zx.GetDir(), this updates the paths in dirs to reflect user paths,// like Dirs() and Files() do.func GetDir(path string) ([]zx.Dir, error) {	apath := AbsPath(path)	ds, err := zx.GetDir(NS(), apath)	if err != nil {		return nil, err	}	for _, d := range ds {		d["Rpath"] = d["path"]		d["Upath"] = fpath.Join(d["path"], d["name"])		d["path"] = fpath.Join(apath, d["name"])	}	return ds, nil} func Put(path string, ud zx.Dir, off int64, dc <-chan []byte) <-chan zx.Dir {	upath := path	apath := AbsPath(path)	rc := make(chan zx.Dir)	go func() {		pc := NS().Put(apath, ud, off, dc)		d := <-pc		if d != nil {			d["Rpath"] = "/"			d["Upath"] = upath			rc <- d		}		close(rc, cerror(pc))	}()	return rc} func PutAll(path string, data []byte, mode ...string) error {	path = AbsPath(path)	return zx.PutAll(NS(), path, data, mode...)} func Wstat(path string, ud zx.Dir) (zx.Dir, error) {	upath := path	apath := AbsPath(path)	rc := NS().Wstat(apath, ud)	d := <-rc	if d != nil {		d["Rpath"] = "/"		d["Upath"] = upath	}	return d, cerror(rc)} func Remove(path string) error {	path = AbsPath(path)	return <-NS().Remove(path)} func RemoveAll(path string) error {	path = AbsPath(path)	return <-NS().RemoveAll(path)} func Move(from, to string) error {	from = AbsPath(from)	to = AbsPath(to)	return <-NS().Move(from, to)} // Clean a name according to conventions so that it has both a path and// a predicate and return both things.// In the predicate, both '&' and ',' can be used as the and operator.// (which is & in clive/zx/pred)// The name is returned as given by the user, it's not an absolute path.// A missing name is taken as ".".func CleanName(name string) (string, string) {	toks := strings.SplitN(name, ",", 2)	if toks[0] == "" {		toks[0] = "."	}	if len(toks) == 1 {		toks = append(toks, "0")	} else {		toks[1] = strings.Replace(toks[1], ",", "&", -1)	}	toks[0] = fpath.Clean(toks[0])	return toks[0], toks[1]} // Issue a find for these names ("filename,predicate")// In the predicate, both '&' and ',' can be used as the and operator.// (which is & in clive/zx/pred)// If no predicate is given, then "depth<1" is used.// eg. /a -> just /a; /a, -> subtree at /a// Errors are reported by sending an error.// The chan error status is not nil if there's an error.// The Upath attribute in the dir entries returned mimics the paths given// in the names.// The Rpath attribute in the dir entries provide a path relative to the one// specified by the user.// If one argument is "|...", it names an IO chan and a dir entry is sent for it, type "c",// With the path (and Upath/Rpath) set to the namefunc Dirs(names ...string) chan face{} {	ns := NS()	rc := make(chan face{})	go func() {		var err error		for _, name := range names {			name = strings.TrimSpace(name)			if len(name) > 0 && name[0] == '|' {				d := zx.Dir{"path": name, "name": name,					"Upath": name, "Rpath": name, "type": "c"}				rc <- d				continue			}			tok0, tok1 := CleanName(name)			name = AbsPath(tok0)			Dprintf("getdirs: find %s %s\n", name, tok1)			dc := ns.Find(name, tok1, "/", "/", 0)			for d := range dc {				if d == nil {					break				}				d["Upath"] = d["path"]				if tok0 != name && zx.HasPrefix(d["path"], name) {					u := fpath.Join(tok0, zx.Suffix(d["path"], name))					d["Upath"] = u				}				d["Rpath"] = zx.Suffix(d["path"], name)				if d["err"] != "" {					if d["err"] != "pruned" {						err = errors.New(d["err"])						rc <- err					}					continue				}				if ok := rc <- d; !ok {					close(dc, cerror(rc))					return				}			}			if derr := cerror(dc); derr != nil {				err = derr				if ok := rc <- err; !ok {					return				}			} else {				close(dc) // in case a null was sent but no error			}		}		close(rc, err)	}()	return rc} // Like Dirs(), but return a single dir.func Dir(name string) (zx.Dir, error) {	tok0, tok1 := CleanName(name)	if tok1 == "0" {		return Stat(tok0)	}	rc := Dirs(name)	for x := range rc {		switch x := x.(type) {		case zx.Dir:			close(rc)			if err := x["err"]; err != "" {				return nil, errors.New(err)			}			return x, nil		default:			Dprintf("Dir: ignored %T\n", x)		}	}	if err := cerror(rc); err != nil {		return nil, err	}	return nil, fmt.Errorf("%s: %s", name, zx.ErrNotExist)} // Like Dirs(), but sends also file contents// If one argument is "|...", it names an IO chan and a dir entry is sent for it, type "c",// With the path (and Upath/Rpath) set to the name, then its contents.func Files(names ...string) chan face{} {	ns := NS()	rc := make(chan face{})	go func() {		var err error		for _, name := range names {			name = strings.TrimSpace(name)			if len(name) > 2 && name[0] == '|' {				d := zx.Dir{"path": name, "name": name,					"Upath": name, "type": "c"}				rc <- d				cc := In(name[2:])				if cc != nil {					for m := range cc {						if ok := rc <- m; !ok {							close(cc, cerror(rc))							return						}					}					err = cerror(cc)				} else {					err = fmt.Errorf("no I/O chan %s", name[1:])				}				if err != nil {					rc <- err				}				continue			}			tok0, tok1 := CleanName(name)			name = AbsPath(tok0)			Dprintf("getfiles: findget %s %s\n", name, tok1)			dc := ns.FindGet(name, tok1, "/", "/", 0)			for m := range dc {				d, ok := m.(zx.Dir)				if !ok {					if ok := rc <- m; !ok {						close(dc, cerror(rc))						return					}					continue				}				d["Upath"] = d["path"]				if tok0 != name && zx.HasPrefix(d["path"], name) {					u := fpath.Join(tok0, zx.Suffix(d["path"], name))					d["Upath"] = u				}				d["Rpath"] = zx.Suffix(d["path"], name)				if d["err"] != "" {					if d["err"] != "pruned" {						err = errors.New(d["err"])						rc <- err					}					continue				}				if ok := rc <- d; !ok {					close(dc, cerror(rc))					return				}			}			if derr := cerror(dc); derr != nil {				err = derr				if ok := rc <- err; !ok {					return				}			} else {				close(dc) // in case a null was sent but no error			}		}		close(rc, err)	}()	return rc} // Process a stream of input []byte data and send one line at a timefunc ByteLines(c <-chan []byte) <-chan []byte {	sep := '\n'	rc := make(chan []byte)	go func() {		var buf bytes.Buffer		saved := []byte{}		for d := range c {			if len(saved) > 0 {				nb := []byte{}				nb = append(nb, saved...)				nb = append(nb, d...)				d = nb				saved = nil			}			for len(d) > 0 && utf8.FullRune(d) {				r, n := utf8.DecodeRune(d)				d = d[n:]				buf.WriteRune(r)				if r == sep {					nb := make([]byte, buf.Len())					copy(nb, buf.Bytes())					if ok := rc <- nb; !ok {						close(c, cerror(rc))						return					}					buf.Reset()				}			}			saved = d		}		if len(saved) > 0 {			buf.Write(saved)		}		if buf.Len() > 0 {			rc <- buf.Bytes()		}		close(rc, cerror(c))	}()	return rc} // Process a stream of input file data and send one line at a time.func Lines(c <-chan face{}) <-chan face{} {	sep := '\n'	rc := make(chan face{})	go func() {		var buf bytes.Buffer		saved := []byte{}		for m := range c {			d, ok := m.([]byte)			if !ok {				if len(saved) > 0 {					rc <- saved					saved = nil				}				if ok := rc <- m; !ok {					close(c, cerror(rc))					return				}				continue			}			if len(saved) > 0 {				nb := []byte{}				nb = append(nb, saved...)				nb = append(nb, d...)				d = nb				saved = nil			}			for len(d) > 0 && utf8.FullRune(d) {				r, n := utf8.DecodeRune(d)				d = d[n:]				buf.WriteRune(r)				if r == sep {					nb := make([]byte, buf.Len())					copy(nb, buf.Bytes())					if ok := rc <- nb; !ok {						close(c, cerror(rc))						return					}					buf.Reset()				}			}			saved = d		}		if len(saved) > 0 {			buf.Write(saved)		}		if buf.Len() > 0 {			rc <- buf.Bytes()		}		close(rc, cerror(c))	}()	return rc} // pipe an input chan and make sure the output// issues one message per file in the input containing all data.// non []byte messages forwarded as-is.func FullFiles(c <-chan face{}) <-chan face{} {	rc := make(chan face{})	go func() {		var b *bytes.Buffer		for m := range c {			switch d := m.(type) {			case []byte:				if b == nil {					b = &bytes.Buffer{}				}				b.Write(d)			default:				if b != nil {					if ok := rc <- b.Bytes(); !ok {						close(c, cerror(rc))						break					}					b = nil				}				if ok := rc <- m; !ok {					close(c, cerror(rc))					break				}			}		}		if b != nil {			if ok := rc <- b.Bytes(); !ok {				close(c, cerror(rc))			}		}		close(rc, cerror(c))	}()	return rc} // Return the content of a `dotfile' for the given name.// if $name is defined, the data is its value.// Otherwise, if $home/lib/name or $home/.name exists,// The empty string is returned if there's no configuration for name.func DotFile(name string) string {	s := GetEnv(name)	if s != "" {		return s	}	dat, err := GetAll(fpath.Join(u.Home, "lib", name))	if err == nil {		s := string(dat)		return s	}	dat, err = GetAll(fpath.Join(u.Home, "."+name))	if err == nil {		s := string(dat)		return s	}	return ""}