<commit_before>// Package geoJSON converts geoJSON TO s2
// 2016 giulio <giulioungaretti@me.com>
package geoJSON

import (
	"fmt"
	"sort"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/giulioungaretti/geo/s2"
	"github.com/giulioungaretti/globo/point"
)

//Coordinate is a [longitude, latitude]
type Coordinate [2]float64

// Lon returns the longitude
func (c Coordinate) Lon() float64 {
	return c[0]
}

// Lat returns the latitude
func (c Coordinate) Lat() float64 {
	return c[1]
}

//Coordinates is a set of coordinate
type Coordinates []Coordinate

// IsCW checks if the Coordinates are CW
// it can return true also in edge cases
func (cc Coordinates) IsCW() bool {
	sum := 0.0
	for i, c := range cc[:len(cc)-1] {
		n := cc[i+1]
		sum += (n.Lon() - c.Lon()) * (n.Lat() + c.Lat())
	}
	return sum > 0
}

// Point rapresent a geoJSON point geometry object
type Point struct {
	Type       string `json:"type"`
	Coordinate `json:"coordinates"`
}

// Polygon rapresent a geoJSON polygon geometry object
type Polygon struct {
	Type string        `json:"type"`
	C    []Coordinates `json:"coordinates"`
}

// MultiPolygon rapresent a geoJSON mulitpolygon  geometry object
type MultiPolygon struct {
	Type string          `json:"type"`
	C    [][]Coordinates `json:"coordinates"`
}

// Prop is a geoJSON property
type Prop map[string]string

// Feature is a geoJSON feature
type Feature struct {
	Type       string      `json:"type"`
	Geometry   interface{} `json:"geometry"`
	Properties Prop        `json:"properties"`
}

//FeatureCollection is a geoJSON Feature colelction
type FeatureCollection struct {
	Type string    `json:"type"`
	Feat []Feature `json:"features"`
}

// GeoJSON is the interface that allows any geojson to be unmarshaled
// converted to s2, and marhsaled back to be visualized
type GeoJSON interface {
	ToS2(int) ([][]uint64, []s2.Loop, error)
	ToGeoJSON(int) (FeatureCollection, error)
}

func (c Coordinate) tos2point() s2.Point {
	ll := s2.LatLngFromDegrees(c[1], c[0])
	p := s2.PointFromLatLng(ll)
	return p
}

// IsValid vaildates coordinates (approximate)
func (c Coordinate) IsValid() bool {
	if c[0] == 0 || c[1] == 0 {
		return false
	}
	return true
}

// tos2 transforms Coordinates to a s2 loop
// return error if coordinates are not in CCW order
func (cc Coordinates) tos2() (s2.Loop, error) {
	pts := []s2.Point{}
	if cc.IsCW() {
		err := fmt.Errorf("Coordinates are not CCW winded")
		return *s2.LoopFromPoints(pts), err
	}
	for _, c := range cc {
		p := c.tos2point()
		pts = append(pts, p)
	}
	//origin := s2.OriginPoint()
	//for i := range pts {
	//j := 1 + i
	//k := 2 + i
	//if i == len(pts)-2 {
	//k = 0
	//}
	//if i == len(pts)-1 {
	//j = 0
	//k = 1
	//}
	//if !s2.OrderedCCW(pts[i], pts[j], pts[k], origin) {
	//err := fmt.Errorf("Polygon not ordered")
	//log.Errorf("%v,%v,%v, %v", i, j, k, err)
	////return *s2.LoopFromPoints(pts), err
	//}
	//}
	return *s2.LoopFromPoints(pts), nil
}

// ToS2 converts a Point to S2Cellid.
// If precision is different than 30 (max) then the
// parent cellid at specified level is returned.
// levels go from 0 to 30:
// for reference :
// 30 covers 0.48cm2
// 12 covers 3.31km2
// 0 covers 85,011,012 km2
func (p Point) ToS2(precision int) (ids [][]uint64, loops []s2.Loop, err error) {
	c := p.Coordinate
	ll := s2.LatLngFromDegrees(c[1], c[0])
	cellID := s2.CellIDFromLatLng(ll)
	// approximate returns
	if precision != 30 {
		log.Debug("Rounding down")
		return [][]uint64{[]uint64{uint64(cellID.Parent(precision))}}, loops, nil
	}
	return [][]uint64{[]uint64{uint64(cellID)}}, loops, nil
}

// ToGeoJSON converts point to geoJSON
func (p Point) ToGeoJSON(precision int) (ff FeatureCollection, err error) {
	// ingore loops, as a point has obviously no loops
	in, _, err := p.ToS2(precision)
	if err != nil {
		return
	}
	ff = FeatureCollection{}
	ff.Type = "FeatureCollection"
	var features []Feature
	for _, id := range in[0] {
		// add center point
		feature := cellIDToCenterPoint(id)
		features = append(features, feature)
		// add bounding box
		feature = cellIDToPolygon(id)
		features = append(features, feature)
	}
	ff.Feat = features
	return
}

// innerLoop(s) return an s2.loop representation of the inner loop of the
// geoJSON polygon or multi polygon. We do not support holes in polygons, so the inner ring of the geojson is discarded.
// At this point winding order of the loops is not specified but it **must** be
// counterclockwise, else we return an error.

func (p Polygon) innerLoops() (loops []s2.Loop, err error) {
	cds := p.C[0]
	loop, err := cds.tos2()
	loops = append(loops, loop)
	return loops, err
}

func (pp MultiPolygon) innerLoops() ([]s2.Loop, error) {
	var loops []s2.Loop
	var err error
	for _, c := range pp.C {
		cds := c[0]
		loop, err := cds.tos2()
		if err != nil {
			return loops, err
		}
		loops = append(loops, loop)
	}
	return loops, err
}

// Contains check if the polygon contains the  point
// TODO needs polish
func (p Polygon) Contains(point point.Point) bool {
	// TODO this has a bug
	// maybe is the point to s2 conversion
	var contains bool
	ll, cell := point.ToCell()
	loop, err := p.innerLoops()
	if err != nil {
		log.Error(err)
		return false
	}

	contains = loop[0].RectBound().ContainsCell(cell)
	if !contains {
		return false
	}
	contains = loop[0].ContainsPoint(s2.PointFromLatLng(ll))
	return contains
}

func cellIDToPolygon(id uint64) (f Feature) {
	cellid := s2.CellID(id)
	cell := s2.CellFromCellID(cellid)
	rect := cell.RectBound()
	var coordinates Coordinates
	var coordinates2 []Coordinates
	for i := 0; i < 4; i++ {
		ll := rect.Vertex(i)
		// ll is the vertex of the  cellid
		var ld Coordinate
		ld = [2]float64{ll.Lng.Degrees(), ll.Lat.Degrees()}
		coordinates = append(coordinates, ld)
	}
	// add first point as last point to close polygon
	coordinates = append(coordinates, coordinates[0])
	coordinates2 = append(coordinates2, coordinates)
	polygon := Polygon{}
	polygon.Type = "Polygon"
	polygon.C = coordinates2
	f.Type = "Feature"
	prop := make(Prop)
	prop["cellid"] = fmt.Sprintf("%v", cellid)
	f.Properties = prop
	f.Geometry = polygon
	return f
}

func cellIDToCenterPoint(id uint64) (f Feature) {
	cellid := s2.CellID(id)
	// ll is the center of the  cellid
	ll := cellid.LatLng()
	var ld Coordinate
	ld = [2]float64{ll.Lng.Degrees(), ll.Lat.Degrees()}
	point := Point{}
	point.Type = "Point"
	point.Coordinate = ld
	f.Type = "Feature"
	prop := make(Prop)
	prop["is_center"] = "true"
	f.Properties = prop
	f.Geometry = point
	return f
}

func boundingbox(loop s2.Loop, id int) (f Feature) {
	rect := loop.RectBound()
	var coordinates Coordinates
	var coordinates2 []Coordinates
	for i := 0; i < 4; i++ {
		ll := rect.Vertex(i)
		// ll is the vertex of the  cellid
		var ld Coordinate
		ld = [2]float64{ll.Lng.Degrees(), ll.Lat.Degrees()}
		coordinates = append(coordinates, ld)
	}
	// add last vertex
	coordinates = append(coordinates, coordinates[0])
	coordinates2 = append(coordinates2, coordinates)
	polygon := Polygon{}
	polygon.Type = "Polygon"
	polygon.C = coordinates2
	f = Feature{}
	f.Type = "Feature"
	prop := make(Prop)
	prop["boundingbox"] = fmt.Sprint(id)
	f.Properties = prop
	f.Geometry = polygon
	return
}

// transforms a loop to a sorted cellID collection
func loopCoverer(loop s2.Loop, precision int) []uint64 {
	debug := false
	// now for bounds
	rc := &s2.RegionCoverer{MinLevel: 0, MaxLevel: precision, MaxCells: 100}
	r := s2.Region(loop.RectBound())
	covering := rc.Covering(r)
	// now approximate the bounding rect
	var polygon []uint64
	for _, val := range covering {
		polygon = append(polygon, uint64(val))
	}
	// NOTE ++ TODO
	// is this 100% correct ?
	// in theory it is.
	sort.Sort(intArray(polygon))
	// NOTE this will be the actual polygonsponse
	// when I/google finish updating the s2 library
	// right now the region coverer for loops
	// is buggy in the sense that it reutns
	// just the boundary of the loop.
	// by setting debug to true, one can
	// visualize the json
	//min := polygon[0]
	//max := polygon[len(polygon)-1]
	//if len(polygon) > 1 {
	//log.Infof("min: %v", min)
	//log.Infof("max: %v", max)
	//}
	if debug {
		min := polygon[0]
		max := polygon[len(polygon)-1]
		rc := &s2.RegionCoverer{MinLevel: 15, MaxLevel: 30, MaxCells: 50}
		covering := rc.Covering(s2.Region(loop))
		var boundary []uint64
		// now approximate the polygon
		for _, val := range covering {
			boundary = append(boundary, uint64(val))
		}
		for _, val := range boundary {
			if val > max {
				log.Printf("strange %v", val)
			}
			if val < min {
				log.Printf("strange %v", val)
			}
		}
		return boundary
	}
	return polygon
}

// ToS2 converts a geoJSON polygon to a set of cellUnions
func (p Polygon) ToS2(precision int) (ids [][]uint64, loops []s2.Loop, err error) {
	var polygons [][]uint64
	loops, err = p.innerLoops()
	if err != nil {
		return polygons, loops, err
	}
	polygon := loopCoverer(loops[0], precision)
	polygons = append(polygons, polygon)
	return polygons, loops, err
}

// ToS2 converts a geoJSON multi polygon to a set of cellUnions
func (pp MultiPolygon) ToS2(precision int) (ids [][]uint64, loops []s2.Loop, err error) {
	var polygons [][]uint64
	loops, err = pp.innerLoops()
	if err != nil {
		return polygons, loops, err
	}
	for _, loop := range loops {
		polygon := loopCoverer(loop, precision)
		polygons = append(polygons, polygon)
	}
	return polygons, loops, err
}

// ToGeoJSON converts polygon to geoJSON
func (p Polygon) ToGeoJSON(precision int) (ff FeatureCollection, err error) {
	in, loops, err := p.ToS2(precision)
	if err != nil {
		return
	}
	// NOTE  we expect one polygon/ one loop
	polygon := in[0]
	inner := loops[0]
	ff = FeatureCollection{}
	ff.Type = "FeatureCollection"
	var features []Feature
	// add bbox
	for _, id := range polygon {
		feature := cellIDToPolygon(id)
		features = append(features, feature)
	}
	bbox := boundingbox(inner, 0)
	features = append(features, bbox)
	ff.Feat = features
	return
}

// ToGeoJSON converts back a multipolygon to geoJSON
func (pp MultiPolygon) ToGeoJSON(precision int) (ff FeatureCollection, err error) {
	in, loops, err := pp.ToS2(precision)
	if err != nil {
		return
	}
	ff = FeatureCollection{}
	ff.Type = "FeatureCollection"
	var features []Feature
	for _, polygon := range in {
		for _, id := range polygon {
			feature := cellIDToPolygon(id)
			features = append(features, feature)
		}
	}
	// add bbox
	for i, inner := range loops {
		bbox := boundingbox(inner, i)
		features = append(features, bbox)
	}
	ff.Feat = features
	return
}

// HELPERS
// sortable array
type intArray []uint64

func (s intArray) Len() int           { return len(s) }
func (s intArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s intArray) Less(i, j int) bool { return s[i] < s[j] }

// convert to string token
func toToken(ci uint64) string {
	s := strings.TrimRight(fmt.Sprintf("%016x", uint64(ci)), "0")
	if len(s) == 0 {
		return "X"
	}
	return s
}
<commit_msg>Use proper s2 loop to ids error if covering can't be done.<commit_after>// Package geoJSON converts geoJSON TO s2
// 2016 giulio <giulioungaretti@me.com>
package geoJSON

import (
	"fmt"
	"sort"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/giulioungaretti/geo/s2"
	"github.com/giulioungaretti/globo/point"
)

//Coordinate is a [longitude, latitude]
type Coordinate [2]float64

// Lon returns the longitude
func (c Coordinate) Lon() float64 {
	return c[0]
}

// Lat returns the latitude
func (c Coordinate) Lat() float64 {
	return c[1]
}

//Coordinates is a set of coordinate
type Coordinates []Coordinate

// IsCW checks if the Coordinates are CW
// it can return true also in edge cases
func (cc Coordinates) IsCW() bool {
	sum := 0.0
	for i, c := range cc[:len(cc)-1] {
		n := cc[i+1]
		sum += (n.Lon() - c.Lon()) * (n.Lat() + c.Lat())
	}
	return sum > 0
}

// Point rapresent a geoJSON point geometry object
type Point struct {
	Type       string `json:"type"`
	Coordinate `json:"coordinates"`
}

// Polygon rapresent a geoJSON polygon geometry object
type Polygon struct {
	Type string        `json:"type"`
	C    []Coordinates `json:"coordinates"`
}

// MultiPolygon rapresent a geoJSON mulitpolygon  geometry object
type MultiPolygon struct {
	Type string          `json:"type"`
	C    [][]Coordinates `json:"coordinates"`
}

// Prop is a geoJSON property
type Prop map[string]string

// Feature is a geoJSON feature
type Feature struct {
	Type       string      `json:"type"`
	Geometry   interface{} `json:"geometry"`
	Properties Prop        `json:"properties"`
}

//FeatureCollection is a geoJSON Feature colelction
type FeatureCollection struct {
	Type string    `json:"type"`
	Feat []Feature `json:"features"`
}

// GeoJSON is the interface that allows any geojson to be unmarshaled
// converted to s2, and marhsaled back to be visualized
type GeoJSON interface {
	ToS2(int) ([][]uint64, []s2.Loop, error)
	ToGeoJSON(int) (FeatureCollection, error)
}

func (c Coordinate) tos2point() s2.Point {
	ll := s2.LatLngFromDegrees(c[1], c[0])
	p := s2.PointFromLatLng(ll)
	return p
}

// IsValid vaildates coordinates (approximate)
func (c Coordinate) IsValid() bool {
	if c[0] == 0 || c[1] == 0 {
		return false
	}
	return true
}

// tos2 transforms Coordinates to a s2 loop
// return error if coordinates are not in CCW order
func (cc Coordinates) tos2() (s2.Loop, error) {
	pts := []s2.Point{}
	if cc.IsCW() {
		err := fmt.Errorf("Coordinates are not CCW winded")
		return *s2.LoopFromPoints(pts), err
	}
	for _, c := range cc {
		p := c.tos2point()
		pts = append(pts, p)
	}
	//origin := s2.OriginPoint()
	//for i := range pts {
	//j := 1 + i
	//k := 2 + i
	//if i == len(pts)-2 {
	//k = 0
	//}
	//if i == len(pts)-1 {
	//j = 0
	//k = 1
	//}
	//if !s2.OrderedCCW(pts[i], pts[j], pts[k], origin) {
	//err := fmt.Errorf("Polygon not ordered")
	//log.Errorf("%v,%v,%v, %v", i, j, k, err)
	////return *s2.LoopFromPoints(pts), err
	//}
	//}
	return *s2.LoopFromPoints(pts), nil
}

// ToS2 converts a Point to S2Cellid.
// If precision is different than 30 (max) then the
// parent cellid at specified level is returned.
// levels go from 0 to 30:
// for reference :
// 30 covers 0.48cm2
// 12 covers 3.31km2
// 0 covers 85,011,012 km2
func (p Point) ToS2(precision int) (ids [][]uint64, loops []s2.Loop, err error) {
	c := p.Coordinate
	ll := s2.LatLngFromDegrees(c[1], c[0])
	cellID := s2.CellIDFromLatLng(ll)
	// approximate returns
	if precision != 30 {
		log.Debug("Rounding down")
		return [][]uint64{[]uint64{uint64(cellID.Parent(precision))}}, loops, nil
	}
	return [][]uint64{[]uint64{uint64(cellID)}}, loops, nil
}

// ToGeoJSON converts point to geoJSON
func (p Point) ToGeoJSON(precision int) (ff FeatureCollection, err error) {
	// ingore loops, as a point has obviously no loops
	in, _, err := p.ToS2(precision)
	if err != nil {
		return
	}
	ff = FeatureCollection{}
	ff.Type = "FeatureCollection"
	var features []Feature
	for _, id := range in[0] {
		// add center point
		feature := cellIDToCenterPoint(id)
		features = append(features, feature)
		// add bounding box
		feature = cellIDToPolygon(id)
		features = append(features, feature)
	}
	ff.Feat = features
	return
}

// innerLoop(s) return an s2.loop representation of the inner loop of the
// geoJSON polygon or multi polygon. We do not support holes in polygons, so the inner ring of the geojson is discarded.
// At this point winding order of the loops is not specified but it **must** be
// counterclockwise, else we return an error.

func (p Polygon) innerLoops() (loops []s2.Loop, err error) {
	cds := p.C[0]
	loop, err := cds.tos2()
	loops = append(loops, loop)
	return loops, err
}

func (pp MultiPolygon) innerLoops() ([]s2.Loop, error) {
	var loops []s2.Loop
	var err error
	for _, c := range pp.C {
		cds := c[0]
		loop, err := cds.tos2()
		if err != nil {
			return loops, err
		}
		loops = append(loops, loop)
	}
	return loops, err
}

// Contains check if the polygon contains the  point
// TODO needs polish
func (p Polygon) Contains(point point.Point) bool {
	// TODO this has a bug
	// maybe is the point to s2 conversion
	var contains bool
	ll, cell := point.ToCell()
	loop, err := p.innerLoops()
	if err != nil {
		log.Error(err)
		return false
	}

	contains = loop[0].RectBound().ContainsCell(cell)
	if !contains {
		return false
	}
	contains = loop[0].ContainsPoint(s2.PointFromLatLng(ll))
	return contains
}

func cellIDToPolygon(id uint64) (f Feature) {
	cellid := s2.CellID(id)
	cell := s2.CellFromCellID(cellid)
	rect := cell.RectBound()
	var coordinates Coordinates
	var coordinates2 []Coordinates
	for i := 0; i < 4; i++ {
		ll := rect.Vertex(i)
		// ll is the vertex of the  cellid
		var ld Coordinate
		ld = [2]float64{ll.Lng.Degrees(), ll.Lat.Degrees()}
		coordinates = append(coordinates, ld)
	}
	// add first point as last point to close polygon
	coordinates = append(coordinates, coordinates[0])
	coordinates2 = append(coordinates2, coordinates)
	polygon := Polygon{}
	polygon.Type = "Polygon"
	polygon.C = coordinates2
	f.Type = "Feature"
	prop := make(Prop)
	prop["cellid"] = fmt.Sprintf("%v", cellid)
	f.Properties = prop
	f.Geometry = polygon
	return f
}

func cellIDToCenterPoint(id uint64) (f Feature) {
	cellid := s2.CellID(id)
	// ll is the center of the  cellid
	ll := cellid.LatLng()
	var ld Coordinate
	ld = [2]float64{ll.Lng.Degrees(), ll.Lat.Degrees()}
	point := Point{}
	point.Type = "Point"
	point.Coordinate = ld
	f.Type = "Feature"
	prop := make(Prop)
	prop["is_center"] = "true"
	f.Properties = prop
	f.Geometry = point
	return f
}

func boundingbox(loop s2.Loop, id int) (f Feature) {
	rect := loop.RectBound()
	var coordinates Coordinates
	var coordinates2 []Coordinates
	for i := 0; i < 4; i++ {
		ll := rect.Vertex(i)
		// ll is the vertex of the  cellid
		var ld Coordinate
		ld = [2]float64{ll.Lng.Degrees(), ll.Lat.Degrees()}
		coordinates = append(coordinates, ld)
	}
	// add last vertex
	coordinates = append(coordinates, coordinates[0])
	coordinates2 = append(coordinates2, coordinates)
	polygon := Polygon{}
	polygon.Type = "Polygon"
	polygon.C = coordinates2
	f = Feature{}
	f.Type = "Feature"
	prop := make(Prop)
	prop["boundingbox"] = fmt.Sprint(id)
	f.Properties = prop
	f.Geometry = polygon
	return
}

func loopCoverer(loop s2.Loop, precision int) ([]uint64, error) {
	var boundary []uint64
	for i := precision; i < 30; i++ {
		rc := &s2.RegionCoverer{MinLevel: 0, MaxLevel: i, MaxCells: 500000}
		covering := rc.InteriorCovering(s2.Region(loop))
		log.Debug("done creating cover")
		// now approximate the polygon
		for _, val := range covering {
			boundary = append(boundary, uint64(val))
		}
		if len(boundary) > 0 {
			return boundary, nil
		}
		log.Warnf("Need to upscale precision to %v", i+1)
	}
	return boundary, fmt.Errorf("Can't cover region.")
}

// ToS2 converts a geoJSON polygon to a set of cellUnions
func (p Polygon) ToS2(precision int) (ids [][]uint64, loops []s2.Loop, err error) {
	var polygons [][]uint64
	loops, err = p.innerLoops()
	if err != nil {
		return polygons, loops, err
	}
	polygon, err := loopCoverer(loops[0], precision)
	polygons = append(polygons, polygon)
	return polygons, loops, err
}

// ToS2 converts a geoJSON multi polygon to a set of cellUnions
func (pp MultiPolygon) ToS2(precision int) (ids [][]uint64, loops []s2.Loop, err error) {
	var polygons [][]uint64
	loops, err = pp.innerLoops()
	if err != nil {
		return polygons, loops, err
	}
	for _, loop := range loops {
		polygon, err := loopCoverer(loop, precision)
		if err != nil {
			return polygons, loops, err
		}
		polygons = append(polygons, polygon)
	}
	return polygons, loops, err
}

// ToGeoJSON converts polygon to geoJSON
func (p Polygon) ToGeoJSON(precision int) (ff FeatureCollection, err error) {
	in, loops, err := p.ToS2(precision)
	if err != nil {
		return
	}
	// NOTE  we expect one polygon/ one loop
	polygon := in[0]
	inner := loops[0]
	ff = FeatureCollection{}
	ff.Type = "FeatureCollection"
	var features []Feature
	// add bbox
	for _, id := range polygon {
		feature := cellIDToPolygon(id)
		features = append(features, feature)
	}
	bbox := boundingbox(inner, 0)
	features = append(features, bbox)
	ff.Feat = features
	return
}

// ToGeoJSON converts back a multipolygon to geoJSON
func (pp MultiPolygon) ToGeoJSON(precision int) (ff FeatureCollection, err error) {
	in, loops, err := pp.ToS2(precision)
	if err != nil {
		return
	}
	ff = FeatureCollection{}
	ff.Type = "FeatureCollection"
	var features []Feature
	for _, polygon := range in {
		for _, id := range polygon {
			feature := cellIDToPolygon(id)
			features = append(features, feature)
		}
	}
	// add bbox
	for i, inner := range loops {
		bbox := boundingbox(inner, i)
		features = append(features, bbox)
	}
	ff.Feat = features
	return
}

// HELPERS
// sortable array
type intArray []uint64

func (s intArray) Len() int           { return len(s) }
func (s intArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s intArray) Less(i, j int) bool { return s[i] < s[j] }

// convert to string token
func toToken(ci uint64) string {
	s := strings.TrimRight(fmt.Sprintf("%016x", uint64(ci)), "0")
	if len(s) == 0 {
		return "X"
	}
	return s
}
<|endoftext|>


<commit_before>package geometry

import (
	dax "github.com/dlespiau/dax/lib"
	"github.com/dlespiau/dax/math"
)

type Box struct {
	Width, Height, Depth                                  float32
	NumWidthSegments, NumHeightSegments, NumDepthSegments int
}

type BoxOptions struct {
	NumWidthSegments, NumHeightSegments, NumDepthSegments int
}

var defaultBox = Box{
	Width:             1.0,
	Height:            1.0,
	Depth:             1.0,
	NumWidthSegments:  1,
	NumHeightSegments: 1,
	NumDepthSegments:  1,
}

// NewBox creates a new box geometry.
func NewBox(width, height, depth float32, options ...BoxOptions) *Box {
	box := defaultBox
	box.Width = width
	box.Height = height
	box.Depth = depth

	if len(options) == 0 {
		return &box
	}

	if options[0].NumWidthSegments > 0 {
		box.NumWidthSegments = options[0].NumWidthSegments
	}
	if options[0].NumHeightSegments > 0 {
		box.NumHeightSegments = options[0].NumHeightSegments
	}
	if options[0].NumDepthSegments > 0 {
		box.NumDepthSegments = options[0].NumDepthSegments
	}

	return &box
}

type boxContext struct {
	nVertices int
	positions []float32
	normals   []float32
	uvs       []float32
	indices   []uint
}

func buildPlane(ctx *boxContext,
	u, v, w int,
	udir, vdir float32,
	width, height, depth float32,
	gridX, gridY int) {

	segmentWidth := width / float32(gridX)
	segmentHeight := height / float32(gridY)

	widthHalf := width / 2
	heightHalf := height / 2
	depthHalf := depth / 2

	gridX1 := gridX + 1
	gridY1 := gridY + 1

	vertexCounter := 0

	vector := math.Vec3{}

	// generate vertices, normals and uvs

	for iy := 0; iy < gridY1; iy++ {
		y := float32(iy)*segmentHeight - heightHalf

		for ix := 0; ix < gridX1; ix++ {
			x := float32(ix)*segmentWidth - widthHalf

			// position
			vector[u] = x * udir
			vector[v] = y * vdir
			vector[w] = depthHalf

			ctx.positions = append(ctx.positions, vector[0], vector[1], vector[2])

			// normal
			vector[u] = 0
			vector[v] = 0
			if depth > 0 {
				vector[w] = 1
			} else {
				vector[w] = -1
			}

			ctx.normals = append(ctx.normals, vector[0], vector[1], vector[2])

			// uvs
			ctx.uvs = append(ctx.uvs, float32(ix)/float32(gridX), 1-(float32(iy)/float32(gridY)))

			// counters
			vertexCounter += 1

		}

	}

	// indices

	// 1. you need three indices to draw a single face
	// 2. a single segment consists of two faces
	// 3. so we need to generate six (2*3) indices per segment

	for iy := 0; iy < gridY; iy++ {
		for ix := 0; ix < gridX; ix++ {
			a := uint(ctx.nVertices + ix + gridX1*iy)
			b := uint(ctx.nVertices + ix + gridX1*(iy+1))
			c := uint(ctx.nVertices + (ix + 1) + gridX1*(iy+1))
			d := uint(ctx.nVertices + (ix + 1) + gridX1*iy)

			// faces
			ctx.indices = append(ctx.indices, a, b, d, b, c, d)
		}

	}

	// update total number of vertices
	ctx.nVertices += vertexCounter
}

func (b *Box) GetMesh() *dax.Mesh {

	m := dax.NewMesh()

	ctx := &boxContext{}

	width := b.Width
	height := b.Height
	depth := b.Depth
	widthSegments := b.NumWidthSegments
	heightSegments := b.NumHeightSegments
	depthSegments := b.NumDepthSegments

	buildPlane(ctx, 2, 1, 0, -1, -1, depth, height, width, depthSegments, heightSegments)  // px
	buildPlane(ctx, 2, 1, 0, 1, -1, depth, height, -width, depthSegments, heightSegments)  // nx
	buildPlane(ctx, 0, 2, 1, 1, 1, width, depth, height, widthSegments, depthSegments)     // py
	buildPlane(ctx, 0, 2, 1, 1, -1, width, depth, -height, widthSegments, depthSegments)   // ny
	buildPlane(ctx, 0, 1, 2, 1, -1, width, height, depth, widthSegments, heightSegments)   // pz
	buildPlane(ctx, 0, 1, 2, -1, -1, width, height, -depth, widthSegments, heightSegments) // nz

	m.AddAttribute("position", ctx.positions, 3)
	m.AddAttribute("normal", ctx.normals, 3)
	m.AddAttribute("uv", ctx.uvs, 2)

	return m
}
<commit_msg>box: Don't forget to add the indices to the box mesh<commit_after>package geometry

import (
	dax "github.com/dlespiau/dax/lib"
	"github.com/dlespiau/dax/math"
)

type Box struct {
	Width, Height, Depth                                  float32
	NumWidthSegments, NumHeightSegments, NumDepthSegments int
}

type BoxOptions struct {
	NumWidthSegments, NumHeightSegments, NumDepthSegments int
}

var defaultBox = Box{
	Width:             1.0,
	Height:            1.0,
	Depth:             1.0,
	NumWidthSegments:  1,
	NumHeightSegments: 1,
	NumDepthSegments:  1,
}

// NewBox creates a new box geometry.
func NewBox(width, height, depth float32, options ...BoxOptions) *Box {
	box := defaultBox
	box.Width = width
	box.Height = height
	box.Depth = depth

	if len(options) == 0 {
		return &box
	}

	if options[0].NumWidthSegments > 0 {
		box.NumWidthSegments = options[0].NumWidthSegments
	}
	if options[0].NumHeightSegments > 0 {
		box.NumHeightSegments = options[0].NumHeightSegments
	}
	if options[0].NumDepthSegments > 0 {
		box.NumDepthSegments = options[0].NumDepthSegments
	}

	return &box
}

type boxContext struct {
	nVertices int
	positions []float32
	normals   []float32
	uvs       []float32
	indices   []uint
}

func buildPlane(ctx *boxContext,
	u, v, w int,
	udir, vdir float32,
	width, height, depth float32,
	gridX, gridY int) {

	segmentWidth := width / float32(gridX)
	segmentHeight := height / float32(gridY)

	widthHalf := width / 2
	heightHalf := height / 2
	depthHalf := depth / 2

	gridX1 := gridX + 1
	gridY1 := gridY + 1

	vertexCounter := 0

	vector := math.Vec3{}

	// generate vertices, normals and uvs

	for iy := 0; iy < gridY1; iy++ {
		y := float32(iy)*segmentHeight - heightHalf

		for ix := 0; ix < gridX1; ix++ {
			x := float32(ix)*segmentWidth - widthHalf

			// position
			vector[u] = x * udir
			vector[v] = y * vdir
			vector[w] = depthHalf

			ctx.positions = append(ctx.positions, vector[0], vector[1], vector[2])

			// normal
			vector[u] = 0
			vector[v] = 0
			if depth > 0 {
				vector[w] = 1
			} else {
				vector[w] = -1
			}

			ctx.normals = append(ctx.normals, vector[0], vector[1], vector[2])

			// uvs
			ctx.uvs = append(ctx.uvs, float32(ix)/float32(gridX), 1-(float32(iy)/float32(gridY)))

			// counters
			vertexCounter += 1

		}

	}

	// indices

	// 1. you need three indices to draw a single face
	// 2. a single segment consists of two faces
	// 3. so we need to generate six (2*3) indices per segment

	for iy := 0; iy < gridY; iy++ {
		for ix := 0; ix < gridX; ix++ {
			a := uint(ctx.nVertices + ix + gridX1*iy)
			b := uint(ctx.nVertices + ix + gridX1*(iy+1))
			c := uint(ctx.nVertices + (ix + 1) + gridX1*(iy+1))
			d := uint(ctx.nVertices + (ix + 1) + gridX1*iy)

			// faces
			ctx.indices = append(ctx.indices, a, b, d, b, c, d)
		}

	}

	// update total number of vertices
	ctx.nVertices += vertexCounter
}

func (b *Box) GetMesh() *dax.Mesh {

	m := dax.NewMesh()

	ctx := &boxContext{}

	width := b.Width
	height := b.Height
	depth := b.Depth
	widthSegments := b.NumWidthSegments
	heightSegments := b.NumHeightSegments
	depthSegments := b.NumDepthSegments

	buildPlane(ctx, 2, 1, 0, -1, -1, depth, height, width, depthSegments, heightSegments)  // px
	buildPlane(ctx, 2, 1, 0, 1, -1, depth, height, -width, depthSegments, heightSegments)  // nx
	buildPlane(ctx, 0, 2, 1, 1, 1, width, depth, height, widthSegments, depthSegments)     // py
	buildPlane(ctx, 0, 2, 1, 1, -1, width, depth, -height, widthSegments, depthSegments)   // ny
	buildPlane(ctx, 0, 1, 2, 1, -1, width, height, depth, widthSegments, heightSegments)   // pz
	buildPlane(ctx, 0, 1, 2, -1, -1, width, height, -depth, widthSegments, heightSegments) // nz

	m.AddAttribute("position", ctx.positions, 3)
	m.AddAttribute("normal", ctx.normals, 3)
	m.AddAttribute("uv", ctx.uvs, 2)
	m.AddIndices(ctx.indices)

	return m
}
<|endoftext|>


<commit_before>package testflight_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"syscall"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"

	"github.com/concourse/atc/redisrunner"
	"github.com/concourse/testflight/gitserver"
	"github.com/concourse/testflight/guidserver"
	"github.com/concourse/testflight/runner"
)

var _ = FDescribe("A job with a git resource", func() {
	var redisRunner *redisrunner.Runner

	var atcConfigFilePath string

	var atcProcess ifrit.Process

	BeforeEach(func() {
		var err error

		redisRunner = redisrunner.NewRunner()
		redisRunner.Start()

		guidserver.Start(wardenClient)
		gitserver.Start(wardenClient)

		gitserver.Commit()

		atcConfigFile, err := ioutil.TempFile("", "atc-config")
		Ω(err).ShouldNot(HaveOccurred())

		atcConfigFilePath = atcConfigFile.Name()

		_, err = fmt.Fprintf(atcConfigFile, `---
resources:
  - name: some-git-resource
    type: git
    source:
      uri: %s

jobs:
  - name: some-job
    inputs:
      - resource: some-git-resource
    image: concourse/testflight-helper
    script: tail -1 some-git-resource/guids | %s
`, gitserver.URI(), guidserver.CurlCommand())
		Ω(err).ShouldNot(HaveOccurred())

		err = atcConfigFile.Close()
		Ω(err).ShouldNot(HaveOccurred())

		atcProcess = ifrit.Envoke(runner.NewRunner(
			builtComponents["atc"],
			"-peerAddr", externalAddr+":8081",
			"-config", atcConfigFilePath,
			"-templates", filepath.Join(atcDir, "server", "templates"),
			"-public", filepath.Join(atcDir, "server", "public"),
			"-redisAddr", fmt.Sprintf("127.0.0.1:%d", redisRunner.Port()),
		))

		Consistently(atcProcess.Wait(), 1*time.Second).ShouldNot(Receive())
	})

	AfterEach(func() {
		atcProcess.Signal(syscall.SIGINT)
		Eventually(atcProcess.Wait(), 10*time.Second).Should(Receive())

		gitserver.Stop(wardenClient)
		guidserver.Stop(wardenClient)

		redisRunner.Stop()

		err := os.Remove(atcConfigFilePath)
		Ω(err).ShouldNot(HaveOccurred())
	})

	It("builds when the git repo is initialized", func() {
		Eventually(guidserver.ReportingGuids, 5*time.Minute, 10*time.Second).Should(HaveLen(1))
		Ω(guidServer.ReportingGuids()).Should(Equal(gitserver.CommittedGuids()))
	})
})
<commit_msg>fix typo<commit_after>package testflight_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"syscall"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"

	"github.com/concourse/atc/redisrunner"
	"github.com/concourse/testflight/gitserver"
	"github.com/concourse/testflight/guidserver"
	"github.com/concourse/testflight/runner"
)

var _ = FDescribe("A job with a git resource", func() {
	var redisRunner *redisrunner.Runner

	var atcConfigFilePath string

	var atcProcess ifrit.Process

	BeforeEach(func() {
		var err error

		redisRunner = redisrunner.NewRunner()
		redisRunner.Start()

		guidserver.Start(wardenClient)
		gitserver.Start(wardenClient)

		gitserver.Commit()

		atcConfigFile, err := ioutil.TempFile("", "atc-config")
		Ω(err).ShouldNot(HaveOccurred())

		atcConfigFilePath = atcConfigFile.Name()

		_, err = fmt.Fprintf(atcConfigFile, `---
resources:
  - name: some-git-resource
    type: git
    source:
      uri: %s

jobs:
  - name: some-job
    inputs:
      - resource: some-git-resource
    image: concourse/testflight-helper
    script: tail -1 some-git-resource/guids | %s
`, gitserver.URI(), guidserver.CurlCommand())
		Ω(err).ShouldNot(HaveOccurred())

		err = atcConfigFile.Close()
		Ω(err).ShouldNot(HaveOccurred())

		atcProcess = ifrit.Envoke(runner.NewRunner(
			builtComponents["atc"],
			"-peerAddr", externalAddr+":8081",
			"-config", atcConfigFilePath,
			"-templates", filepath.Join(atcDir, "server", "templates"),
			"-public", filepath.Join(atcDir, "server", "public"),
			"-redisAddr", fmt.Sprintf("127.0.0.1:%d", redisRunner.Port()),
		))

		Consistently(atcProcess.Wait(), 1*time.Second).ShouldNot(Receive())
	})

	AfterEach(func() {
		atcProcess.Signal(syscall.SIGINT)
		Eventually(atcProcess.Wait(), 10*time.Second).Should(Receive())

		gitserver.Stop(wardenClient)
		guidserver.Stop(wardenClient)

		redisRunner.Stop()

		err := os.Remove(atcConfigFilePath)
		Ω(err).ShouldNot(HaveOccurred())
	})

	It("builds when the git repo is initialized", func() {
		Eventually(guidserver.ReportingGuids, 5*time.Minute, 10*time.Second).Should(HaveLen(1))
		Ω(guidserver.ReportingGuids()).Should(Equal(gitserver.CommittedGuids()))
	})
})
<|endoftext|>


<commit_before>//
// store.go
//

package srnd

import (
  "bytes"
  "crypto/sha512"
  "encoding/base32"
  "fmt"
  "io"
  //"io/ioutil"
  "log"
  "mime"
  "mime/multipart"
  "net/mail"
  "os"
  "path/filepath"
  "strings"
)

type ArticleStore interface {
  MessageReader
  MessageWriter
  MessageVerifier
  AttachmentReader

  // get the filepath for an attachment
  AttachmentFilepath(fname string) string
  // get the filepath for an attachment's thumbnail
  ThumbnailFilepath(fname string) string
  // do we have this article?
  HasArticle(msgid string) bool
  // create a file for a message
  CreateFile(msgid string) io.WriteCloser
  // create a file for a temp message
  CreateTempFile(msgid string) io.WriteCloser
  // get the filename of a message
  GetFilename(msgid string) string
  // get the filename of a temp message
  GetTempFilename(msgid string) string
  // Get a message given its messageid
  GetMessage(msgid string) NNTPMessage
  // get a temp message given its messageid
  // temp message is deleted once read
  ReadTempMessage(msgid string) NNTPMessage
  // store a post
  StorePost(nntp NNTPMessage) error
  // get article headers only
  GetHeaders(msgid string) ArticleHeaders
}
type articleStore struct {
  directory string
  temp string
  attachments string
  thumbs string
  database Database
}

func createArticleStore(config map[string]string, database Database) ArticleStore {
  store := articleStore{
    directory: config["store_dir"],
    temp: config["incoming_dir"],
    attachments: config["attachments_dir"],
    thumbs: config["thumbs_dir"],
    database: database,
  }
  store.Init()
  return store
}

// initialize article store
func (self articleStore) Init() {
  EnsureDir(self.directory)
  EnsureDir(self.temp)
  EnsureDir(self.attachments)
  EnsureDir(self.thumbs)
}

func (self articleStore) StorePost(nntp NNTPMessage) (err error) {
  f := self.CreateFile(nntp.MessageID())
  if f != nil {
    err = self.WriteMessage(nntp, f)
    f.Close()
  }
  for _, att := range nntp.Attachments() {
    log.Println("save attachment", att.Filename(), "to", att.Filepath())
    fpath := att.Filepath()
    f, err = os.Create(fpath)
    if err == nil {
      err = att.WriteTo(f)
      f.Close()
    }
    if err != nil {
      return
    }
  }
  return
}

func (self articleStore) ReadMessage(r io.Reader) (NNTPMessage, error) {

  msg, err := mail.ReadMessage(r)
  nntp := nntpArticle{}

  if err == nil {
    nntp.headers = ArticleHeaders(msg.Header)
    log.Println("reading message")
    content_type := msg.Header.Get("Content-Type")
    _, params, err := mime.ParseMediaType(content_type)
    if err != nil {
      log.Println("failed to parse media type", err)
      return nil, err
    }
    boundary, ok := params["boundary"]
    if ok {
      partReader := multipart.NewReader(msg.Body, boundary)
      for {
        part, err := partReader.NextPart()
        if err == io.EOF {
          return nntp, nil
        } else if err == nil {
          hdr := part.Header
          // get content type of part
          part_type := hdr.Get("Content-Type")
          // parse content type
          media_type, _, err := mime.ParseMediaType(part_type)
          if err == nil {
            if media_type == "text/plain" {
              // plaintext gets added to message part
              nntp.message.body.ReadFrom(part)
            } else {
              // non plaintext gets added to attachments
              att := self.ReadAttachmentFromMimePart(part)
              nntp.Attach(att)
            }
          } else {
            log.Println("part has no content type", err)
          }
          part.Close()
        } else {
          log.Println("failed to load part! ", err)
          return nntp, err
        }
      }
    } else {
      _, err = nntp.message.body.ReadFrom(msg.Body)
    }
  } else {
    log.Println("failed to read message", err)
  }
  return nntp, err
}

func (self articleStore) WriteMessage(nntp NNTPMessage, wr io.Writer) (err error) {
  // write headers
  for hdr, hdr_vals := range(nntp.Headers()) {
    for _ , hdr_val := range hdr_vals {
      _, err = io.WriteString(wr, fmt.Sprintf("%s: %s\n", hdr, hdr_val))
      if err != nil {
        return
      }
    }
  }
  // done headers
  _, err = io.WriteString(wr, "\n")
  if err != nil {
    return
  }
  // write body
  err = nntp.WriteBody(wr)
  return
}

func (self articleStore) VerifyMessage(nntp NNTPMessage) (NNTPMessage, error) {
  // TODO: implement
  return nntp, nil
}

// get the filepath for an attachment
func (self articleStore) AttachmentFilepath(fname string) string {
  return filepath.Join(self.attachments, fname)
}

// get the filepath for a thumbanil
func (self articleStore) ThumbnailFilepath(fname string) string {
  return filepath.Join(self.thumbs, fname)
}

// create a file for this article
func (self articleStore) CreateFile(messageID string) io.WriteCloser {
  fname := self.GetFilename(messageID)
  file, err := os.Create(fname)
  if err != nil {
    log.Println("cannot open file", fname)
    return nil
  }
  return file
}

// create a temp file for inboud articles
func (self articleStore) CreateTempFile(messageID string) io.WriteCloser {
  fname := self.GetTempFilename(messageID)
  file, err := os.Create(fname)
  if err != nil {
    log.Println("cannot open file", fname)
    return nil
  }
  return file
}

// return true if we have an article
func (self articleStore) HasArticle(messageID string) bool {
  return self.database.HasArticle(messageID)
}

// get the filename for this article
func (self articleStore) GetFilename(messageID string) string {
  return filepath.Join(self.directory, messageID)
}

// get the filename for this article
func (self articleStore) GetTempFilename(messageID string) string {
  return filepath.Join(self.temp, messageID)
}

// loads temp message and deletes old article
func (self articleStore) ReadTempMessage(messageID string) NNTPMessage {
  fname := self.GetTempFilename(messageID)
  nntp := self.readfile(fname)
  //DelFile(fname)
  return nntp
}

// read a file give filepath
func (self articleStore) readfile(fname string) NNTPMessage {

  file, err := os.Open(fname)
  if err != nil {
    log.Println("store cannot open file",fname)
    return nil
  }
  message, err := self.ReadMessage(file)
  file.Close()
  if err == nil {
    return message
  }

  log.Println("failed to load file", fname)
  return nil
}

// get the replies for a thread
func (self articleStore) GetThreadReplies(messageID string, last int) []NNTPMessage {
  var repls []NNTPMessage
  if self.database.ThreadHasReplies(messageID) {
    rpls := self.database.GetThreadReplies(messageID, last)
    if rpls == nil {
      return repls
    }
    for _, rpl := range rpls {
      msg := self.GetMessage(rpl)
      if msg == nil {
        log.Println("cannot get message", rpl)
      } else {
        repls = append(repls, msg)
      }
    }
  }
  return repls
}

// load an article
// return nil on failure
func (self articleStore) GetMessage(messageID string) NNTPMessage {
  return self.readfile(self.GetFilename(messageID))
}

// get article with headers only
func (self articleStore) GetHeaders(messageID string) ArticleHeaders {
  // TODO: don't load the entire body
  nntp := self.readfile(self.GetFilename(messageID))
  if nntp == nil {
    return nil
  }
  return nntp.Headers()
}

func (self articleStore) ReadAttachmentFromMimePart(part *multipart.Part) NNTPAttachment {
  var buff bytes.Buffer
  content_type := part.Header.Get("Content-Type")
  if content_type == "" {
    content_type = "unknown"
  }
  fname := part.FileName()
  idx := strings.LastIndex(fname, ".")
  ext := ".txt"
  if idx > 0 {
    ext = fname[idx:]
  }

  n, err := io.Copy(&buff, part)
  log.Printf("read %d bytes from mime part", n)
  if err != nil {
    log.Println("failed to read attachment from mimepart", err)
    return nil
  }
  sha := sha512.Sum512(buff.Bytes())
  hashstr := base32.StdEncoding.EncodeToString(sha[:])
  fpath_fname := hashstr+ext
  fpath := filepath.Join(self.attachments, fpath_fname)

  hdr := part.Header
  return nntpAttachment{
    header: hdr,
    body: buff,
    mime: content_type,
    filename: fname,
    filepath: fpath,
    ext: ext,
    hash: sha[:],
  }
}
<commit_msg>add logging<commit_after>//
// store.go
//

package srnd

import (
  "bytes"
  "crypto/sha512"
  "encoding/base32"
  "fmt"
  "io"
  //"io/ioutil"
  "log"
  "mime"
  "mime/multipart"
  "net/mail"
  "os"
  "path/filepath"
  "strings"
)

type ArticleStore interface {
  MessageReader
  MessageWriter
  MessageVerifier
  AttachmentReader

  // get the filepath for an attachment
  AttachmentFilepath(fname string) string
  // get the filepath for an attachment's thumbnail
  ThumbnailFilepath(fname string) string
  // do we have this article?
  HasArticle(msgid string) bool
  // create a file for a message
  CreateFile(msgid string) io.WriteCloser
  // create a file for a temp message
  CreateTempFile(msgid string) io.WriteCloser
  // get the filename of a message
  GetFilename(msgid string) string
  // get the filename of a temp message
  GetTempFilename(msgid string) string
  // Get a message given its messageid
  GetMessage(msgid string) NNTPMessage
  // get a temp message given its messageid
  // temp message is deleted once read
  ReadTempMessage(msgid string) NNTPMessage
  // store a post
  StorePost(nntp NNTPMessage) error
  // get article headers only
  GetHeaders(msgid string) ArticleHeaders
}
type articleStore struct {
  directory string
  temp string
  attachments string
  thumbs string
  database Database
}

func createArticleStore(config map[string]string, database Database) ArticleStore {
  store := articleStore{
    directory: config["store_dir"],
    temp: config["incoming_dir"],
    attachments: config["attachments_dir"],
    thumbs: config["thumbs_dir"],
    database: database,
  }
  store.Init()
  return store
}

// initialize article store
func (self articleStore) Init() {
  EnsureDir(self.directory)
  EnsureDir(self.temp)
  EnsureDir(self.attachments)
  EnsureDir(self.thumbs)
}

func (self articleStore) StorePost(nntp NNTPMessage) (err error) {
  f := self.CreateFile(nntp.MessageID())
  if f != nil {
    err = self.WriteMessage(nntp, f)
    f.Close()
  }
  for _, att := range nntp.Attachments() {
    log.Println("save attachment", att.Filename(), "to", att.Filepath())
    fpath := att.Filepath()
    f, err = os.Create(fpath)
    if err == nil {
      err = att.WriteTo(f)
      f.Close()
    }
    if err != nil {
      return
    }
  }
  return
}

func (self articleStore) ReadMessage(r io.Reader) (NNTPMessage, error) {

  msg, err := mail.ReadMessage(r)
  nntp := nntpArticle{}

  if err == nil {
    nntp.headers = ArticleHeaders(msg.Header)
    log.Println("reading message")
    content_type := msg.Header.Get("Content-Type")
    _, params, err := mime.ParseMediaType(content_type)
    if err != nil {
      log.Println("failed to parse media type", err)
      return nil, err
    }
    boundary, ok := params["boundary"]
    if ok {
      partReader := multipart.NewReader(msg.Body, boundary)
      for {
        part, err := partReader.NextPart()
        if err == io.EOF {
          return nntp, nil
        } else if err == nil {
          hdr := part.Header
          // get content type of part
          part_type := hdr.Get("Content-Type")
          log.Println("part has content type", part_type)
          // parse content type
          media_type, _, err := mime.ParseMediaType(part_type)
          if err == nil {
            if media_type == "text/plain" {
              // plaintext gets added to message part
              nntp.message.body.ReadFrom(part)
            } else {
              // non plaintext gets added to attachments
              att := self.ReadAttachmentFromMimePart(part)
              nntp = nntp.Attach(att).(nntpArticle)
            }
          } else {
            log.Println("part has no content type", err)
          }
          part.Close()
        } else {
          log.Println("failed to load part! ", err)
          return nntp, err
        }
      }
    } else {
      _, err = nntp.message.body.ReadFrom(msg.Body)
    }
  } else {
    log.Println("failed to read message", err)
  }
  return nntp, err
}

func (self articleStore) WriteMessage(nntp NNTPMessage, wr io.Writer) (err error) {
  // write headers
  for hdr, hdr_vals := range(nntp.Headers()) {
    for _ , hdr_val := range hdr_vals {
      _, err = io.WriteString(wr, fmt.Sprintf("%s: %s\n", hdr, hdr_val))
      if err != nil {
        return
      }
    }
  }
  // done headers
  _, err = io.WriteString(wr, "\n")
  if err != nil {
    return
  }
  // write body
  err = nntp.WriteBody(wr)
  return
}

func (self articleStore) VerifyMessage(nntp NNTPMessage) (NNTPMessage, error) {
  // TODO: implement
  return nntp, nil
}

// get the filepath for an attachment
func (self articleStore) AttachmentFilepath(fname string) string {
  return filepath.Join(self.attachments, fname)
}

// get the filepath for a thumbanil
func (self articleStore) ThumbnailFilepath(fname string) string {
  return filepath.Join(self.thumbs, fname)
}

// create a file for this article
func (self articleStore) CreateFile(messageID string) io.WriteCloser {
  fname := self.GetFilename(messageID)
  file, err := os.Create(fname)
  if err != nil {
    log.Println("cannot open file", fname)
    return nil
  }
  return file
}

// create a temp file for inboud articles
func (self articleStore) CreateTempFile(messageID string) io.WriteCloser {
  fname := self.GetTempFilename(messageID)
  file, err := os.Create(fname)
  if err != nil {
    log.Println("cannot open file", fname)
    return nil
  }
  return file
}

// return true if we have an article
func (self articleStore) HasArticle(messageID string) bool {
  return self.database.HasArticle(messageID)
}

// get the filename for this article
func (self articleStore) GetFilename(messageID string) string {
  return filepath.Join(self.directory, messageID)
}

// get the filename for this article
func (self articleStore) GetTempFilename(messageID string) string {
  return filepath.Join(self.temp, messageID)
}

// loads temp message and deletes old article
func (self articleStore) ReadTempMessage(messageID string) NNTPMessage {
  fname := self.GetTempFilename(messageID)
  nntp := self.readfile(fname)
  //DelFile(fname)
  return nntp
}

// read a file give filepath
func (self articleStore) readfile(fname string) NNTPMessage {

  file, err := os.Open(fname)
  if err != nil {
    log.Println("store cannot open file",fname)
    return nil
  }
  message, err := self.ReadMessage(file)
  file.Close()
  if err == nil {
    return message
  }

  log.Println("failed to load file", fname)
  return nil
}

// get the replies for a thread
func (self articleStore) GetThreadReplies(messageID string, last int) []NNTPMessage {
  var repls []NNTPMessage
  if self.database.ThreadHasReplies(messageID) {
    rpls := self.database.GetThreadReplies(messageID, last)
    if rpls == nil {
      return repls
    }
    for _, rpl := range rpls {
      msg := self.GetMessage(rpl)
      if msg == nil {
        log.Println("cannot get message", rpl)
      } else {
        repls = append(repls, msg)
      }
    }
  }
  return repls
}

// load an article
// return nil on failure
func (self articleStore) GetMessage(messageID string) NNTPMessage {
  return self.readfile(self.GetFilename(messageID))
}

// get article with headers only
func (self articleStore) GetHeaders(messageID string) ArticleHeaders {
  // TODO: don't load the entire body
  nntp := self.readfile(self.GetFilename(messageID))
  if nntp == nil {
    return nil
  }
  return nntp.Headers()
}

func (self articleStore) ReadAttachmentFromMimePart(part *multipart.Part) NNTPAttachment {
  var buff bytes.Buffer
  content_type := part.Header.Get("Content-Type")
  if content_type == "" {
    content_type = "unknown"
  }
  fname := part.FileName()
  idx := strings.LastIndex(fname, ".")
  ext := ".txt"
  if idx > 0 {
    ext = fname[idx:]
  }

  n, err := io.Copy(&buff, part)
  log.Printf("read %d bytes from mime part", n)
  if err != nil {
    log.Println("failed to read attachment from mimepart", err)
    return nil
  }
  sha := sha512.Sum512(buff.Bytes())
  hashstr := base32.StdEncoding.EncodeToString(sha[:])
  fpath_fname := hashstr+ext
  fpath := filepath.Join(self.attachments, fpath_fname)

  hdr := part.Header
  return nntpAttachment{
    header: hdr,
    body: buff,
    mime: content_type,
    filename: fname,
    filepath: fpath,
    ext: ext,
    hash: sha[:],
  }
}
<|endoftext|>


<commit_before>package deploy

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/pkg/errors"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
)

type RemoteDeployer struct {
	ssh             *ssh.Client
	targetDirectory string
}

func NewRemoteDeployer(s *ssh.Client, target string) (*RemoteDeployer, error) {
	d := &RemoteDeployer{
		ssh:             s,
		targetDirectory: target,
	}

	client, err := sftp.NewClient(d.ssh)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to create new sftp client")
	}
	defer client.Close()

	if err := client.MkdirAll(d.targetDirectory); err != nil {
		return nil, errors.Wrap(err, "Failed to make target directory")
	}

	return d, nil
}

// targetDirectory にファイルを転送し、 path にシンボリックリンクを貼る
func (d RemoteDeployer) SendFile(body []byte, path string, permission os.FileMode) error {
	client, err := sftp.NewClient(d.ssh)
	if err != nil {
		return errors.Wrap(err, "Failed to create new sftp client")
	}
	defer client.Close()

	var target string
	if !filepath.IsAbs(path) {
		target = filepath.Join(d.targetDirectory, path)
	} else {
		filename := filepath.Base(path)
		target = filepath.Join(d.targetDirectory, filename)

		if _, err := client.Lstat(path); err == nil {
			if err := client.Remove(path); err != nil {
				return errors.Wrap(err, "Failed to remove old symbolic link")
			}
		}

		if err := client.Symlink(target, path); err != nil {
			return errors.Wrap(err, "Failed to create symbolic link")
		}
	}

	if _, err := client.Stat(target); err == nil {
		if err := client.Remove(target); err != nil {
			return errors.Wrap(err, "Failed to delete target")
		}
	}

	file, err := client.Create(target)
	if err != nil {
		return errors.Wrap(err, "Failed to create new file")
	}
	defer file.Close()

	if err := file.Chmod(permission); err != nil {
		return errors.Wrap(err, "Failed to change permission")
	}

	if _, err := file.Write(body); err != nil {
		return errors.Wrap(err, "Failed to write to file")
	}

	return nil
}

func (d RemoteDeployer) ReadSelf() ([]byte, error) {
	if !filepath.IsAbs(os.Args[0]) {
		return nil, fmt.Errorf("Use binary with absolute path to read self")
	}

	path, err := filepath.Abs(os.Args[0])
	if err != nil {
		return nil, errors.Wrap(err, "Failed to get absolute path")
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to open self")
	}
	defer file.Close()

	buf := new(bytes.Buffer)
	buf.ReadFrom(file)
	return buf.Bytes(), nil
}

func (d RemoteDeployer) Command(command string, stdout, stderr io.Writer) error {
	sess, err := d.ssh.NewSession()
	if err != nil {
		return errors.Wrap(err, "Failed to create new session")
	}
	defer sess.Close()

	sess.Stdout = stdout
	sess.Stderr = stderr

	if err := sess.Run(command); err != nil {
		if ee, ok := err.(*ssh.ExitError); ok {
			return fmt.Errorf("'%s' exit status is not 0: code=%d", command, ee.ExitStatus())
		}

		return errors.Wrapf(err, "Failed to command '%s'", command)
	}

	return nil
}
<commit_msg>get abspath on deploying<commit_after>package deploy

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/pkg/errors"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
)

type RemoteDeployer struct {
	ssh             *ssh.Client
	targetDirectory string
}

func NewRemoteDeployer(s *ssh.Client, target string) (*RemoteDeployer, error) {
	d := &RemoteDeployer{
		ssh:             s,
		targetDirectory: target,
	}

	client, err := sftp.NewClient(d.ssh)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to create new sftp client")
	}
	defer client.Close()

	if err := client.MkdirAll(d.targetDirectory); err != nil {
		return nil, errors.Wrap(err, "Failed to make target directory")
	}

	return d, nil
}

// targetDirectory にファイルを転送し、 path にシンボリックリンクを貼る
func (d RemoteDeployer) SendFile(body []byte, path string, permission os.FileMode) error {
	client, err := sftp.NewClient(d.ssh)
	if err != nil {
		return errors.Wrap(err, "Failed to create new sftp client")
	}
	defer client.Close()

	var target string
	if !filepath.IsAbs(path) {
		target = filepath.Join(d.targetDirectory, path)
	} else {
		filename := filepath.Base(path)
		target = filepath.Join(d.targetDirectory, filename)

		if _, err := client.Lstat(path); err == nil {
			if err := client.Remove(path); err != nil {
				return errors.Wrap(err, "Failed to remove old symbolic link")
			}
		}

		if err := client.Symlink(target, path); err != nil {
			return errors.Wrap(err, "Failed to create symbolic link")
		}
	}

	if _, err := client.Stat(target); err == nil {
		if err := client.Remove(target); err != nil {
			return errors.Wrap(err, "Failed to delete target")
		}
	}

	file, err := client.Create(target)
	if err != nil {
		return errors.Wrap(err, "Failed to create new file")
	}
	defer file.Close()

	if err := file.Chmod(permission); err != nil {
		return errors.Wrap(err, "Failed to change permission")
	}

	if _, err := file.Write(body); err != nil {
		return errors.Wrap(err, "Failed to write to file")
	}

	return nil
}

func (d RemoteDeployer) ReadSelf() ([]byte, error) {
	path, err := filepath.Abs(os.Args[0])
	if err != nil {
		return nil, errors.Wrap(err, "Failed to get absolute path")
	}

	if !filepath.IsAbs(path) {
		return nil, fmt.Errorf("Use binary with absolute path to read self")
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to open self")
	}
	defer file.Close()

	buf := new(bytes.Buffer)
	buf.ReadFrom(file)
	return buf.Bytes(), nil
}

func (d RemoteDeployer) Command(command string, stdout, stderr io.Writer) error {
	sess, err := d.ssh.NewSession()
	if err != nil {
		return errors.Wrap(err, "Failed to create new session")
	}
	defer sess.Close()

	sess.Stdout = stdout
	sess.Stderr = stderr

	if err := sess.Run(command); err != nil {
		if ee, ok := err.(*ssh.ExitError); ok {
			return fmt.Errorf("'%s' exit status is not 0: code=%d", command, ee.ExitStatus())
		}

		return errors.Wrapf(err, "Failed to command '%s'", command)
	}

	return nil
}
<|endoftext|>


<commit_before>package fuse

import (
	"fmt"
	"os"
	"strings"
	"syscall"
)

var initFlagNames map[int64]string
var releaseFlagNames map[int64]string
var OpenFlagNames map[int64]string
var FuseOpenFlagNames map[int64]string
var accessFlagName map[int64]string
var writeFlagNames map[int64]string
var readFlagNames map[int64]string

func init() {
	writeFlagNames = map[int64]string{
		WRITE_CACHE:     "CACHE",
		WRITE_LOCKOWNER: "LOCKOWNER",
	}
	readFlagNames = map[int64]string{
		READ_LOCKOWNER: "LOCKOWNER",
	}
	initFlagNames = map[int64]string{
		CAP_ASYNC_READ:       "ASYNC_READ",
		CAP_POSIX_LOCKS:      "POSIX_LOCKS",
		CAP_FILE_OPS:         "FILE_OPS",
		CAP_ATOMIC_O_TRUNC:   "ATOMIC_O_TRUNC",
		CAP_EXPORT_SUPPORT:   "EXPORT_SUPPORT",
		CAP_BIG_WRITES:       "BIG_WRITES",
		CAP_DONT_MASK:        "DONT_MASK",
		CAP_SPLICE_WRITE:     "SPLICE_WRITE",
		CAP_SPLICE_MOVE:      "SPLICE_MOVE",
		CAP_SPLICE_READ:      "SPLICE_READ",
		CAP_FLOCK_LOCKS:      "FLOCK_LOCKS",
		CAP_IOCTL_DIR:        "IOCTL_DIR",
		CAP_AUTO_INVAL_DATA:  "AUTO_INVAL_DATA",
		CAP_READDIRPLUS:      "READDIRPLUS",
		CAP_READDIRPLUS_AUTO: "READDIRPLUS_AUTO",
	}
	releaseFlagNames = map[int64]string{
		RELEASE_FLUSH: "FLUSH",
	}
	OpenFlagNames = map[int64]string{
		int64(os.O_WRONLY):        "WRONLY",
		int64(os.O_RDWR):          "RDWR",
		int64(os.O_APPEND):        "APPEND",
		int64(syscall.O_ASYNC):    "ASYNC",
		int64(os.O_CREATE):        "CREAT",
		int64(os.O_EXCL):          "EXCL",
		int64(syscall.O_NOCTTY):   "NOCTTY",
		int64(syscall.O_NONBLOCK): "NONBLOCK",
		int64(os.O_SYNC):          "SYNC",
		int64(os.O_TRUNC):         "TRUNC",

		int64(syscall.O_CLOEXEC):   "CLOEXEC",
		int64(syscall.O_DIRECTORY): "DIRECTORY",
	}
	FuseOpenFlagNames = map[int64]string{
		FOPEN_DIRECT_IO:   "DIRECT",
		FOPEN_KEEP_CACHE:  "CACHE",
		FOPEN_NONSEEKABLE: "NONSEEK",
	}
	accessFlagName = map[int64]string{
		X_OK: "x",
		W_OK: "w",
		R_OK: "r",
	}

}

func FlagString(names map[int64]string, fl int64, def string) string {
	s := []string{}
	for k, v := range names {
		if fl&k != 0 {
			s = append(s, v)
			fl ^= k
		}
	}
	if len(s) == 0 && def != "" {
		s = []string{def}
	}
	if fl != 0 {
		s = append(s, fmt.Sprintf("0x%x", fl))
	}

	return strings.Join(s, ",")
}

func (me *ForgetIn) string() string {
	return fmt.Sprintf("{%d}", me.Nlookup)
}

func (me *_BatchForgetIn) string() string {
	return fmt.Sprintf("{%d}", me.Count)
}

func (me *MkdirIn) string() string {
	return fmt.Sprintf("{0%o (0%o)}", me.Mode, me.Umask)
}

func (me *RenameIn) string() string {
	return fmt.Sprintf("{%d}", me.Newdir)
}

func (me *SetAttrIn) string() string {
	s := []string{}
	if me.Valid&FATTR_MODE != 0 {
		s = append(s, fmt.Sprintf("mode 0%o", me.Mode))
	}
	if me.Valid&FATTR_UID != 0 {
		s = append(s, fmt.Sprintf("uid %d", me.Uid))
	}
	if me.Valid&FATTR_GID != 0 {
		s = append(s, fmt.Sprintf("uid %d", me.Gid))
	}
	if me.Valid&FATTR_SIZE != 0 {
		s = append(s, fmt.Sprintf("size %d", me.Size))
	}
	if me.Valid&FATTR_ATIME != 0 {
		s = append(s, fmt.Sprintf("atime %d.%09d", me.Atime, me.Atimensec))
	}
	if me.Valid&FATTR_MTIME != 0 {
		s = append(s, fmt.Sprintf("mtime %d.%09d", me.Mtime, me.Mtimensec))
	}
	if me.Valid&FATTR_MTIME != 0 {
		s = append(s, fmt.Sprintf("fh %d", me.Fh))
	}
	// TODO - FATTR_ATIME_NOW = (1 << 7), FATTR_MTIME_NOW = (1 << 8), FATTR_LOCKOWNER = (1 << 9)
	return fmt.Sprintf("{%s}", strings.Join(s, ", "))
}

func (me *ReleaseIn) string() string {
	return fmt.Sprintf("{Fh %d %s %s L%d}",
		me.Fh, FlagString(OpenFlagNames, int64(me.Flags), ""),
		FlagString(releaseFlagNames, int64(me.ReleaseFlags), ""),
		me.LockOwner)
}

func (me *OpenIn) string() string {
	return fmt.Sprintf("{%s}", FlagString(OpenFlagNames, int64(me.Flags), "O_RDONLY"))
}

func (me *OpenOut) string() string {
	return fmt.Sprintf("{Fh %d %s}", me.Fh,
		FlagString(FuseOpenFlagNames, int64(me.OpenFlags), ""))
}

func (me *InitIn) String() string {
	return fmt.Sprintf("{%d.%d Ra 0x%x %s}",
		me.Major, me.Minor, me.MaxReadAhead,
		FlagString(initFlagNames, int64(me.Flags), ""))
}

func (me *InitOut) string() string {
	return fmt.Sprintf("{%d.%d Ra 0x%x %s %d/%d Wr 0x%x}",
		me.Major, me.Minor, me.MaxReadAhead,
		FlagString(initFlagNames, int64(me.Flags), ""),
		me.CongestionThreshold, me.MaxBackground, me.MaxWrite)
}

func (me *SetXAttrIn) string() string {
	return fmt.Sprintf("{sz %d f%o}", me.Size, me.Flags)
}

func (me *GetXAttrIn) string() string {
	return fmt.Sprintf("{sz %d}", me.Size)
}

func (me *GetXAttrOut) string() string {
	return fmt.Sprintf("{sz %d}", me.Size)
}

func (me *AccessIn) string() string {
	return fmt.Sprintf("{%s}", FlagString(accessFlagName, int64(me.Mask), ""))
}

func (me *FlushIn) string() string {
	return fmt.Sprintf("{Fh %d}", me.Fh)
}

func (me *AttrOut) string() string {
	return fmt.Sprintf(
		"{A%d.%09d %v}",
		me.AttrValid, me.AttrValidNsec, &me.Attr)
}

func (me *EntryOut) string() string {
	return fmt.Sprintf("{%d E%d.%09d A%d.%09d %v}",
		me.NodeId, me.EntryValid, me.EntryValidNsec,
		me.AttrValid, me.AttrValidNsec, &me.Attr)
}

func (me *CreateOut) string() string {
	return fmt.Sprintf("{%v %v}", &me.EntryOut, &me.OpenOut)
}

func (me *StatfsOut) string() string {
	return fmt.Sprintf(
		"{blocks (%d,%d)/%d files %d/%d bs%d nl%d frs%d}",
		me.Bfree, me.Bavail, me.Blocks, me.Ffree, me.Files,
		me.Bsize, me.NameLen, me.Frsize)
}

func (o *NotifyInvalEntryOut) string() string {
	return fmt.Sprintf("{parent %d sz %d}", o.Parent, o.NameLen)
}

func (o *NotifyInvalInodeOut) string() string {
	return fmt.Sprintf("{ino %d off %d sz %d}", o.Ino, o.Off, o.Length)
}

func (o *NotifyInvalDeleteOut) string() string {
	return fmt.Sprintf("{parent %d ch %d sz %d}", o.Parent, o.Child, o.NameLen)
}

func (f *FallocateIn) string() string {
	return fmt.Sprintf("{Fh %d off %d sz %d mod 0%o}",
		f.Fh, f.Offset, f.Length, f.Mode)
}

// Print pretty prints FUSE data types for kernel communication
func Print(obj interface{}) string {
	t, ok := obj.(interface {
		string() string
	})
	if ok {
		return t.string()
	}
	return fmt.Sprintf("%T: %v", obj, obj)
}
<commit_msg>Include Generation field in EntryOut.string()<commit_after>package fuse

import (
	"fmt"
	"os"
	"strings"
	"syscall"
)

var initFlagNames map[int64]string
var releaseFlagNames map[int64]string
var OpenFlagNames map[int64]string
var FuseOpenFlagNames map[int64]string
var accessFlagName map[int64]string
var writeFlagNames map[int64]string
var readFlagNames map[int64]string

func init() {
	writeFlagNames = map[int64]string{
		WRITE_CACHE:     "CACHE",
		WRITE_LOCKOWNER: "LOCKOWNER",
	}
	readFlagNames = map[int64]string{
		READ_LOCKOWNER: "LOCKOWNER",
	}
	initFlagNames = map[int64]string{
		CAP_ASYNC_READ:       "ASYNC_READ",
		CAP_POSIX_LOCKS:      "POSIX_LOCKS",
		CAP_FILE_OPS:         "FILE_OPS",
		CAP_ATOMIC_O_TRUNC:   "ATOMIC_O_TRUNC",
		CAP_EXPORT_SUPPORT:   "EXPORT_SUPPORT",
		CAP_BIG_WRITES:       "BIG_WRITES",
		CAP_DONT_MASK:        "DONT_MASK",
		CAP_SPLICE_WRITE:     "SPLICE_WRITE",
		CAP_SPLICE_MOVE:      "SPLICE_MOVE",
		CAP_SPLICE_READ:      "SPLICE_READ",
		CAP_FLOCK_LOCKS:      "FLOCK_LOCKS",
		CAP_IOCTL_DIR:        "IOCTL_DIR",
		CAP_AUTO_INVAL_DATA:  "AUTO_INVAL_DATA",
		CAP_READDIRPLUS:      "READDIRPLUS",
		CAP_READDIRPLUS_AUTO: "READDIRPLUS_AUTO",
	}
	releaseFlagNames = map[int64]string{
		RELEASE_FLUSH: "FLUSH",
	}
	OpenFlagNames = map[int64]string{
		int64(os.O_WRONLY):        "WRONLY",
		int64(os.O_RDWR):          "RDWR",
		int64(os.O_APPEND):        "APPEND",
		int64(syscall.O_ASYNC):    "ASYNC",
		int64(os.O_CREATE):        "CREAT",
		int64(os.O_EXCL):          "EXCL",
		int64(syscall.O_NOCTTY):   "NOCTTY",
		int64(syscall.O_NONBLOCK): "NONBLOCK",
		int64(os.O_SYNC):          "SYNC",
		int64(os.O_TRUNC):         "TRUNC",

		int64(syscall.O_CLOEXEC):   "CLOEXEC",
		int64(syscall.O_DIRECTORY): "DIRECTORY",
	}
	FuseOpenFlagNames = map[int64]string{
		FOPEN_DIRECT_IO:   "DIRECT",
		FOPEN_KEEP_CACHE:  "CACHE",
		FOPEN_NONSEEKABLE: "NONSEEK",
	}
	accessFlagName = map[int64]string{
		X_OK: "x",
		W_OK: "w",
		R_OK: "r",
	}

}

func FlagString(names map[int64]string, fl int64, def string) string {
	s := []string{}
	for k, v := range names {
		if fl&k != 0 {
			s = append(s, v)
			fl ^= k
		}
	}
	if len(s) == 0 && def != "" {
		s = []string{def}
	}
	if fl != 0 {
		s = append(s, fmt.Sprintf("0x%x", fl))
	}

	return strings.Join(s, ",")
}

func (me *ForgetIn) string() string {
	return fmt.Sprintf("{%d}", me.Nlookup)
}

func (me *_BatchForgetIn) string() string {
	return fmt.Sprintf("{%d}", me.Count)
}

func (me *MkdirIn) string() string {
	return fmt.Sprintf("{0%o (0%o)}", me.Mode, me.Umask)
}

func (me *RenameIn) string() string {
	return fmt.Sprintf("{%d}", me.Newdir)
}

func (me *SetAttrIn) string() string {
	s := []string{}
	if me.Valid&FATTR_MODE != 0 {
		s = append(s, fmt.Sprintf("mode 0%o", me.Mode))
	}
	if me.Valid&FATTR_UID != 0 {
		s = append(s, fmt.Sprintf("uid %d", me.Uid))
	}
	if me.Valid&FATTR_GID != 0 {
		s = append(s, fmt.Sprintf("uid %d", me.Gid))
	}
	if me.Valid&FATTR_SIZE != 0 {
		s = append(s, fmt.Sprintf("size %d", me.Size))
	}
	if me.Valid&FATTR_ATIME != 0 {
		s = append(s, fmt.Sprintf("atime %d.%09d", me.Atime, me.Atimensec))
	}
	if me.Valid&FATTR_MTIME != 0 {
		s = append(s, fmt.Sprintf("mtime %d.%09d", me.Mtime, me.Mtimensec))
	}
	if me.Valid&FATTR_MTIME != 0 {
		s = append(s, fmt.Sprintf("fh %d", me.Fh))
	}
	// TODO - FATTR_ATIME_NOW = (1 << 7), FATTR_MTIME_NOW = (1 << 8), FATTR_LOCKOWNER = (1 << 9)
	return fmt.Sprintf("{%s}", strings.Join(s, ", "))
}

func (me *ReleaseIn) string() string {
	return fmt.Sprintf("{Fh %d %s %s L%d}",
		me.Fh, FlagString(OpenFlagNames, int64(me.Flags), ""),
		FlagString(releaseFlagNames, int64(me.ReleaseFlags), ""),
		me.LockOwner)
}

func (me *OpenIn) string() string {
	return fmt.Sprintf("{%s}", FlagString(OpenFlagNames, int64(me.Flags), "O_RDONLY"))
}

func (me *OpenOut) string() string {
	return fmt.Sprintf("{Fh %d %s}", me.Fh,
		FlagString(FuseOpenFlagNames, int64(me.OpenFlags), ""))
}

func (me *InitIn) String() string {
	return fmt.Sprintf("{%d.%d Ra 0x%x %s}",
		me.Major, me.Minor, me.MaxReadAhead,
		FlagString(initFlagNames, int64(me.Flags), ""))
}

func (me *InitOut) string() string {
	return fmt.Sprintf("{%d.%d Ra 0x%x %s %d/%d Wr 0x%x}",
		me.Major, me.Minor, me.MaxReadAhead,
		FlagString(initFlagNames, int64(me.Flags), ""),
		me.CongestionThreshold, me.MaxBackground, me.MaxWrite)
}

func (me *SetXAttrIn) string() string {
	return fmt.Sprintf("{sz %d f%o}", me.Size, me.Flags)
}

func (me *GetXAttrIn) string() string {
	return fmt.Sprintf("{sz %d}", me.Size)
}

func (me *GetXAttrOut) string() string {
	return fmt.Sprintf("{sz %d}", me.Size)
}

func (me *AccessIn) string() string {
	return fmt.Sprintf("{%s}", FlagString(accessFlagName, int64(me.Mask), ""))
}

func (me *FlushIn) string() string {
	return fmt.Sprintf("{Fh %d}", me.Fh)
}

func (me *AttrOut) string() string {
	return fmt.Sprintf(
		"{A%d.%09d %v}",
		me.AttrValid, me.AttrValidNsec, &me.Attr)
}

func (me *EntryOut) string() string {
	return fmt.Sprintf("{%d G%d E%d.%09d A%d.%09d %v}",
		me.NodeId, me.Generation, me.EntryValid, me.EntryValidNsec,
		me.AttrValid, me.AttrValidNsec, &me.Attr)
}

func (me *CreateOut) string() string {
	return fmt.Sprintf("{%v %v}", &me.EntryOut, &me.OpenOut)
}

func (me *StatfsOut) string() string {
	return fmt.Sprintf(
		"{blocks (%d,%d)/%d files %d/%d bs%d nl%d frs%d}",
		me.Bfree, me.Bavail, me.Blocks, me.Ffree, me.Files,
		me.Bsize, me.NameLen, me.Frsize)
}

func (o *NotifyInvalEntryOut) string() string {
	return fmt.Sprintf("{parent %d sz %d}", o.Parent, o.NameLen)
}

func (o *NotifyInvalInodeOut) string() string {
	return fmt.Sprintf("{ino %d off %d sz %d}", o.Ino, o.Off, o.Length)
}

func (o *NotifyInvalDeleteOut) string() string {
	return fmt.Sprintf("{parent %d ch %d sz %d}", o.Parent, o.Child, o.NameLen)
}

func (f *FallocateIn) string() string {
	return fmt.Sprintf("{Fh %d off %d sz %d mod 0%o}",
		f.Fh, f.Offset, f.Length, f.Mode)
}

// Print pretty prints FUSE data types for kernel communication
func Print(obj interface{}) string {
	t, ok := obj.(interface {
		string() string
	})
	if ok {
		return t.string()
	}
	return fmt.Sprintf("%T: %v", obj, obj)
}
<|endoftext|>


<commit_before>package main

/*
 Go interfaces support a subtyping relation. However, the Go Language Reference
 never explicitly mentions subtyping, just 'subsets'. So lets play around.
*/

import "fmt"

type A interface {
  a() int
}

// B is a subtype of A
type B interface {
  A
  b() int
}

// C is a subtype of B
type C interface {
  B
  c() int
}

// A2 is a new top level type
type A2 interface {
  a2() int
}

// B2 <: A && B2 <: A2
type B2 interface {
  A
  A2
  b() int
}

type TA struct {}
func (this TA) a() int { return 0 }
func (this TA) b() int { return 1 }

func main() {
  t := TA{}
  var a A
  var b B

  // 'TA' is a subtype of 'B'
  // TA <: B
  // 't::TA' can be used anywhere that 'b::B' is expected
  b = t
  // effectively means that the method set of 'TA' is a superset of 'B'. (i.e.,
  // kind of confusing that we say 'TA' is a subtype of 'A' when its method set
  // is a superset. *However* if we think in terms of value sets defining
  // members of a type, then the set of values of 'B' is a superset of the set
  // of values of 'TA' since all 'TA' are in 'B'). So subtying relates to method
  // sets in terms of the reverse direction.

  // So if the method set of type 'X' is a subset of method set of type 'Y' then
  // 'Y' is a subtype of 'X', Y <: X (i.e., the requirements on X are less than
  // Y, so X is more relaxed and contains more values...).

  // TA <: A
  a = t
  // B <: A
  a = b

  // A </: B
  //b = a

  // TA <: B <: A
  fmt.Printf("%d\n", a.a())
  print("Hello") // using bootstrap builtin print
  return
}

<commit_msg>update subtyping test<commit_after>package main

/*
 Go interfaces support a subtyping relation. However, the Go Language Reference
 never explicitly mentions subtyping, just 'subsets'. So lets play around.

 Seems to be open, structural subtyping.
*/

import "fmt"

type A interface {
  a() int
}

// Interface subtyping is done through structural equivalence (i.e., just the
// method sets). A := Aequiv
type Aequiv interface {
  a() int
}

type Aequiv2 interface {
  A
}

// B is a subtype of A
type B interface {
  A
  b() int
}

// C is a subtype of B
type C interface {
  B
  c() int
}

// A2 is a new top level type
type A2 interface {
  a2() int
}

// B2 <: A && B2 <: A2
type B2 interface {
  A
  A2
  b() int
}

type TA struct {}
func (this TA) a() int { return 0 }
func (this TA) b() int { return 1 }

func main() {
  t := TA{}
  var a A
  var ae Aequiv
  var ae2 Aequiv2
  var b B

  // 'TA' is a subtype of 'B'
  // TA <: B
  // 't::TA' can be used anywhere that 'b::B' is expected
  b = t
  // effectively means that the method set of 'TA' is a superset of 'B'. (i.e.,
  // kind of confusing that we say 'TA' is a subtype of 'A' when its method set
  // is a superset. *However* if we think in terms of value sets defining
  // members of a type, then the set of values of 'B' is a superset of the set
  // of values of 'TA' since all 'TA' are in 'B'). So subtying relates to method
  // sets in terms of the reverse direction.

  // So if the method set of type 'X' is a subset of method set of type 'Y' then
  // 'Y' is a subtype of 'X', Y <: X (i.e., the requirements on X are less than
  // Y, so X is more relaxed and contains more values...).

  // TA <: A
  a = t
  // B <: A
  a = b

  // A := Aequiv := Aequiv2
  ae = a
  a = ae
  ae2 = ae
  ae = ae2

  // A </: B
  //b = a

  // TA <: B <: A
  fmt.Printf("%d\n", a.a())
  print("Hello") // using bootstrap builtin print
  return
}

<|endoftext|>


<commit_before>package grep

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strings"
)

type line struct {
	number   int
	contents string
	filename string
}

type configuration struct {
	// -n Print the line numbers of each matching line.
	prefixLineNumbers bool
	// -l Print only the names of files that contain at least one matching line.
	printFileNames bool
	// -i Match line using a case-insensitive comparison.
	matchCaseInsensitive bool
	// -v Invert the program -- collect all lines that fail to match the pattern.
	invertMatch bool
	// -x Only match entire lines, instead of lines that contain a match.
	matchEntireLine bool

	prefixFileName bool
}

func NewConfiguration(flags []string, files []string) (c configuration) {
	c = configuration{
		prefixLineNumbers:    false,
		printFileNames:       false,
		matchCaseInsensitive: false,
		invertMatch:          false,
		matchEntireLine:      false,
		prefixFileName:       false,
	}

	for _, flag := range flags {
		switch flag {
		case "-n":
			c.prefixLineNumbers = true
		case "-l":
			c.printFileNames = true
		case "-i":
			c.matchCaseInsensitive = true
		case "-v":
			c.invertMatch = true
		case "-x":
			c.matchEntireLine = true
		default:
			log.Fatalf("unrecognized flag %v", flag)
		}
	}

	if len(files) > 1 {
		c.prefixFileName = true
	}

	return c
}

func Search(pattern string, flags, files []string) (result []string) {
	lines := readFiles(files)
	config := NewConfiguration(flags, files)
	matches := search(pattern, config, lines)

	temp := format(matches, config)
	fmt.Printf("result %v\n", temp)
	return temp
}

func search(pattern string, config configuration, lines []line) (matches []line) {
	for _, line := range lines {
		if config.matchCaseInsensitive {
			lowerContents := strings.ToLower(line.contents)
			lowerPattern := strings.ToLower(pattern)
			if strings.Contains(lowerContents, lowerPattern) {
				matches = append(matches, line)
			}
		} else if config.matchEntireLine && config.invertMatch {
			if pattern != line.contents {
				matches = append(matches, line)
			}
		} else if config.matchEntireLine {
			if pattern == line.contents {
				matches = append(matches, line)
			}
		} else if config.invertMatch {
			if !strings.Contains(line.contents, pattern) {
				matches = append(matches, line)
			}
		} else {
			if strings.Contains(line.contents, pattern) {
				matches = append(matches, line)
			}
		}
	}
	return matches
}

func format(matches []line, config configuration) (result []string) {
	result = []string{}
	for _, match := range matches {
		if config.printFileNames {
			result = append(result, match.filename)
		} else if config.prefixFileName && config.prefixLineNumbers {
			result = append(result, fmt.Sprintf("%s:%d:%s", match.filename, match.number, match.contents))
		} else if config.prefixFileName {
			result = append(result, fmt.Sprintf("%s:%s", match.filename, match.contents))
		} else if config.prefixLineNumbers {
			result = append(result, fmt.Sprintf("%d:%s", match.number, match.contents))
		} else {
			result = append(result, match.contents)
		}
	}
	return result
}

func readFiles(files []string) (lines []line) {
	for _, file := range files {
		lines = append(lines, readLines(file)...)
	}
	return lines
}

func readLines(filename string) (lines []line) {
	file, err := os.Open(filename)

	if err != nil {
		log.Fatal(err)
	}

	scanner := bufio.NewScanner(file)
	lineNumber := 1
	for scanner.Scan() {
		contents := scanner.Text()
		line := line{lineNumber, contents, filename}

		lines = append(lines, line)
		lineNumber += 1
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	return lines
}
<commit_msg>Solve grep<commit_after>package grep

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strings"
)

type line struct {
	number   int
	contents string
	filename string
}

type configuration struct {
	// -n Print the line numbers of each matching line.
	prefixLineNumbers bool
	// -l Print only the names of files that contain at least one matching line.
	printFileNames bool
	// -i Match line using a case-insensitive comparison.
	matchCaseInsensitive bool
	// -v Invert the program -- collect all lines that fail to match the pattern.
	invertMatch bool
	// -x Only match entire lines, instead of lines that contain a match.
	matchEntireLine bool

	prefixFileName bool
}

func NewConfiguration(flags []string, files []string) (c configuration) {
	c = configuration{
		prefixLineNumbers:    false,
		printFileNames:       false,
		matchCaseInsensitive: false,
		invertMatch:          false,
		matchEntireLine:      false,
		prefixFileName:       false,
	}

	for _, flag := range flags {
		switch flag {
		case "-n":
			c.prefixLineNumbers = true
		case "-l":
			c.printFileNames = true
		case "-i":
			c.matchCaseInsensitive = true
		case "-v":
			c.invertMatch = true
		case "-x":
			c.matchEntireLine = true
		default:
			log.Fatalf("unrecognized flag %v", flag)
		}
	}

	if len(files) > 1 {
		c.prefixFileName = true
	}

	return c
}

func Search(pattern string, flags, files []string) (result []string) {
	lines := readFiles(files)
	config := NewConfiguration(flags, files)
	matches := search(pattern, config, lines)

	temp := format(matches, config)
	fmt.Printf("result %v\n", temp)
	return temp
}

func search(pattern string, config configuration, lines []line) (matches []line) {
	for _, line := range lines {
		if config.matchCaseInsensitive {
			lowerContents := strings.ToLower(line.contents)
			lowerPattern := strings.ToLower(pattern)
			if strings.Contains(lowerContents, lowerPattern) {
				matches = append(matches, line)
			}
		} else if config.matchEntireLine && config.invertMatch {
			if pattern != line.contents {
				matches = append(matches, line)
			}
		} else if config.matchEntireLine {
			if pattern == line.contents {
				matches = append(matches, line)
			}
		} else if config.invertMatch {
			if !strings.Contains(line.contents, pattern) {
				matches = append(matches, line)
			}
		} else {
			if strings.Contains(line.contents, pattern) {
				matches = append(matches, line)
			}
		}
	}
	return matches
}

func format(matches []line, config configuration) (result []string) {
	result = []string{}
	for _, match := range matches {
		if config.printFileNames {
			result = append(result, match.filename)
		} else if config.prefixFileName && config.prefixLineNumbers {
			result = append(result, fmt.Sprintf("%s:%d:%s", match.filename, match.number, match.contents))
		} else if config.prefixFileName {
			result = append(result, fmt.Sprintf("%s:%s", match.filename, match.contents))
		} else if config.prefixLineNumbers {
			result = append(result, fmt.Sprintf("%d:%s", match.number, match.contents))
		} else {
			result = append(result, match.contents)
		}
	}

	if config.printFileNames {
		return uniq(result)
	}
	return result
}

func readFiles(files []string) (lines []line) {
	for _, file := range files {
		lines = append(lines, readLines(file)...)
	}
	return lines
}

func readLines(filename string) (lines []line) {
	file, err := os.Open(filename)

	if err != nil {
		log.Fatal(err)
	}

	scanner := bufio.NewScanner(file)
	lineNumber := 1
	for scanner.Scan() {
		contents := scanner.Text()
		line := line{lineNumber, contents, filename}

		lines = append(lines, line)
		lineNumber += 1
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	return lines
}

func uniq(items []string) (result []string) {
	result = []string{}
	for _, item := range items {
		if !contains(result, item) {
			result = append(result, item)
		}
	}
	return result
}

func contains(list []string, value string) bool {
	for _, item := range list {
		if item == value {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package goasync

import (
	"errors"
	"testing"
)

type MyStruct struct {
	name string
}

func TestAuto(t *testing.T) {
	graph := map[string]*Task{
		"b": &Task{
			Dep: []string{"a"},
			Handler: func(cb Cb, ar ResultSet) {
				t.Log("task b")
				cb("b string", nil)
			},
		},
		"e": &Task{
			Dep: []string{"a", "b", "c"},
			Handler: func(cb Cb, ar ResultSet) {
				var b string
				ar["b"].Data(&b)
				if b != "b string" {
					t.Error("should be 'b string'")
				}
				var c MyStruct
				ar["c"].Data(&c)
				if c.name != "from c" {
					t.Error("should be 'from c'")
				}
				tbl := map[string]MyStruct{
					"first": MyStruct{name: "inner"},
				}
				cb(tbl, nil)
			},
		},
		"f": &Task{
			Dep: []string{"e"},
			Handler: func(cb Cb, ar ResultSet) {
				var tbl map[string]MyStruct
				ar["e"].Data(&tbl)
				if tbl["first"].name != "inner" {
					t.Error("should be 'inner'")
				}
				cb(nil, nil)
			},
		},
		"c": &Task{
			Dep: []string{"a"},
			Handler: func(cb Cb, ar ResultSet) {
				var data []string
				ar["a"].Data(&data)
				t.Log("task c get a's data:", data)
				ms := &MyStruct{name: "from c"}
				cb(ms, nil)
			},
		},
		"a": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				t.Log("task a")
				d := []string{"bob", "foo"}
				cb(d, nil)
			},
		},
	}
	asy, _ := Auto(graph)
	asy.Run()
}

func TestParallel(t *testing.T) {
	asy, _ := Parallel(
		func(cb Cb, ar ResultSet) {
			t.Log("aaa")
			cb(0, nil)
		},
		func(cb Cb, ar ResultSet) {
			t.Log("bbb")
			cb("", nil)
		},
	)
	err := asy.Run()
	if err != nil {
		t.Error("should no error")
	}
	var s int = 2
	err = asy.GetResult("1").Data(&s)
	if err == nil {
		t.Error("should an error when get data")
	}
	var str string = "xyz"
	asy.GetResult("1").Data(&str)
	if str != "" {
		t.Error("should be empty")
	}
}
func TestAutoErr(t *testing.T) {
	graph := map[string]*Task{
		"b": &Task{
			Dep: []string{"a"},
			Handler: func(cb Cb, ar ResultSet) {
				t.Log("task b")
				cb("b string", nil)
			},
		},
		"a": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				t.Log("task a")
				d := []string{"bob", "foo"}
				cb(d, errors.New("error happens in a"))
			},
		},
	}
	asy, _ := Auto(graph)
	err := asy.Run()
	if err == nil {
		t.Error("should get an error")
	}
	ar := asy.GetResult("a")
	if ar.err == nil {
		t.Error("should be error")
	}
}

func TestAutoSequence(t *testing.T) {
	graph := map[string]*Task{
		"a": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				cb(1, nil)
			},
		},
		"b": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				cb(2, nil)
			},
		},
		"c": &Task{
			Dep: []string{"a", "b"},
			Handler: func(cb Cb, ar ResultSet) {
				var a int
				ar.Get("a").Data(&a)
				var b int
				ar.Get("b").Data(&b)
				if a != 1 {
					t.Error("task a result should be 1")
				}
				if b != 2 {
					t.Error("task b result should be 2")
				}
				cb(a+b, nil)
			},
		},
		"d": &Task{
			Dep: []string{"c"},
			Handler: func(cb Cb, ar ResultSet) {
				var c int
				ar.Get("c").Data(&c)
				if c != 3 {
					t.Error("task c result should be 3")
				}
				cb(nil, nil)
			},
		},
		"e": &Task{
			Dep: []string{"c"},
			Handler: func(cb Cb, ar ResultSet) {
				var c int
				ar.Get("c").Data(&c)
				if c != 3 {
					t.Error("task c result should be 3")
				}
				cb(nil, nil)
			},
		},
	}
	asy, _ := Auto(graph)
	err := asy.Run()
	if err != nil {
		t.Error("should get no error")
	}
}

func TestAutoErr1(t *testing.T) {
	graph := map[string]*Task{
		"a": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				cb("text", nil)
			},
		},
		"b": &Task{
			Dep: []string{"a"},
			Handler: func(cb Cb, ar ResultSet) {
				cb(nil, nil)
			},
		},
	}
	asy, _ := Auto(graph)
	err := asy.Run()
	if err != nil {
		t.Error("should get no error")
	}
	ar := asy.GetResult("a")
	var g int
	if err = ar.Data(g); err == nil {
		t.Error("should be an error")
	}
}
<commit_msg>add test<commit_after>package goasync

import (
	"errors"
	"testing"
)

type MyStruct struct {
	name string
}

func TestAuto(t *testing.T) {
	graph := map[string]*Task{
		"b": &Task{
			Dep: []string{"a"},
			Handler: func(cb Cb, ar ResultSet) {
				t.Log("task b")
				cb("b string", nil)
			},
		},
		"e": &Task{
			Dep: []string{"a", "b", "c"},
			Handler: func(cb Cb, ar ResultSet) {
				var b string
				ar["b"].Data(&b)
				if b != "b string" {
					t.Error("should be 'b string'")
				}
				var c MyStruct
				ar["c"].Data(&c)
				if c.name != "from c" {
					t.Error("should be 'from c'")
				}
				tbl := map[string]MyStruct{
					"first": MyStruct{name: "inner"},
				}
				cb(tbl, nil)
			},
		},
		"f": &Task{
			Dep: []string{"e"},
			Handler: func(cb Cb, ar ResultSet) {
				var tbl map[string]MyStruct
				err := ar["e"].Data(tbl)
				if err == nil {
					t.Error("should be error")
				}
				err = ar["e"].Data(&tbl)
				if tbl["first"].name != "inner" {
					t.Error("should be 'inner'")
				}
				cb(nil, nil)
			},
		},
		"c": &Task{
			Dep: []string{"a"},
			Handler: func(cb Cb, ar ResultSet) {
				var data []string
				err := ar["a"].Data(data)
				if err == nil {
					t.Error("should be err")
				}
				ar["a"].Data(&data)
				if len(data) != 2 || data[0] != "bob" {
					t.Error("error in fetch a's data")
				}
				t.Log("task c get a's data:", data)
				ms := &MyStruct{name: "from c"}
				cb(ms, nil)
			},
		},
		"a": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				t.Log("task a")
				d := []string{"bob", "foo"}
				cb(d, nil)
			},
		},
	}
	asy, _ := Auto(graph)
	asy.Run()
}

func TestParallel(t *testing.T) {
	asy, _ := Parallel(
		func(cb Cb, ar ResultSet) {
			t.Log("aaa")
			cb(0, nil)
		},
		func(cb Cb, ar ResultSet) {
			t.Log("bbb")
			cb("", nil)
		},
	)
	err := asy.Run()
	if err != nil {
		t.Error("should no error")
	}
	var s int = 2
	err = asy.GetResult("1").Data(&s)
	if err == nil {
		t.Error("should an error when get data")
	}
	var str string = "xyz"
	asy.GetResult("1").Data(&str)
	if str != "" {
		t.Error("should be empty")
	}
}
func TestAutoErr(t *testing.T) {
	graph := map[string]*Task{
		"b": &Task{
			Dep: []string{"a"},
			Handler: func(cb Cb, ar ResultSet) {
				t.Log("task b")
				cb("b string", nil)
			},
		},
		"a": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				t.Log("task a")
				d := []string{"bob", "foo"}
				cb(d, errors.New("error happens in a"))
			},
		},
	}
	asy, _ := Auto(graph)
	err := asy.Run()
	if err == nil {
		t.Error("should get an error")
	}
	ar := asy.GetResult("a")
	if ar.err == nil {
		t.Error("should be error")
	}
}

func TestAutoSequence(t *testing.T) {
	graph := map[string]*Task{
		"a": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				cb(1, nil)
			},
		},
		"b": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				cb(2, nil)
			},
		},
		"c": &Task{
			Dep: []string{"a", "b"},
			Handler: func(cb Cb, ar ResultSet) {
				var a int
				ar.Get("a").Data(&a)
				var b int
				ar.Get("b").Data(&b)
				if a != 1 {
					t.Error("task a result should be 1")
				}
				if b != 2 {
					t.Error("task b result should be 2")
				}
				cb(a+b, nil)
			},
		},
		"d": &Task{
			Dep: []string{"c"},
			Handler: func(cb Cb, ar ResultSet) {
				var c int
				ar.Get("c").Data(&c)
				if c != 3 {
					t.Error("task c result should be 3")
				}
				cb(nil, nil)
			},
		},
		"e": &Task{
			Dep: []string{"c"},
			Handler: func(cb Cb, ar ResultSet) {
				var c int
				ar.Get("c").Data(&c)
				if c != 3 {
					t.Error("task c result should be 3")
				}
				cb(nil, nil)
			},
		},
	}
	asy, _ := Auto(graph)
	err := asy.Run()
	if err != nil {
		t.Error("should get no error")
	}
}

func TestAutoErr1(t *testing.T) {
	graph := map[string]*Task{
		"a": &Task{
			Handler: func(cb Cb, ar ResultSet) {
				cb("text", nil)
			},
		},
		"b": &Task{
			Dep: []string{"a"},
			Handler: func(cb Cb, ar ResultSet) {
				cb(nil, nil)
			},
		},
	}
	asy, _ := Auto(graph)
	err := asy.Run()
	if err != nil {
		t.Error("should get no error")
	}
	ar := asy.GetResult("a")
	var g int
	if err = ar.Data(g); err == nil {
		t.Error("should be an error")
	}
}
<|endoftext|>