<commit_before>package stein

import (
	"bufio"
	"encoding/json"
	"fmt"
	"gopkg.in/v1/yaml"
	"io"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var (
	docSep      = regexp.MustCompile(`(?m)^---$|^\.\.\.$`)
	tapTestLine = regexp.MustCompile(`(?m)^(ok|not ok)(?: (\d+))?( [^#]+)?(?:\s*#(.*))?$`)
	tapPlan     = regexp.MustCompile(`(?m)^\d..(\d+|N).*$`)
	tapVersion  = regexp.MustCompile(`(?m)^TAP version \d+$`)
	tapBailOut  = regexp.MustCompile(`(?m)^Bail out!\s*(.*)$`)
)

func Parse(r io.Reader) (*Suite, error) {
	parser := Parser{&DefaultHandler{}}
	return parser.Parse(r)
}

type Parser struct {
	Handler Handler
}

func (p *Parser) Parse(r io.Reader) (*Suite, error) {
	rd := bufio.NewReader(r)
	b, err := rd.Peek(1)
	if err != nil {
		panic(err)
	}

	if p.Handler == nil {
		p.Handler = &DefaultHandler{}
	}

	s := new(Suite)
	switch b[0] {
	case '{':
		return s, s.parse(newDecoder("json", rd), p.Handler)
	case '-':
		return s, s.parse(newDecoder("yaml", rd), p.Handler)
	default:
		return ParseTap(rd, p.Handler)
	}
}

type Time struct {
	time.Time
}

func (t *Time) parse(val string) (err error) {
	var ti time.Time
	if ti, err = time.Parse("2006-01-02 15:04:05", val); err != nil {
		if ti, err = time.Parse(time.RFC3339, val); err != nil {
			return
		}
	}
	(*t).Time = ti
	return
}

func (t *Time) UnmarshalJSON(data []byte) (err error) {
	// parse time, stripping the quotes
	return t.parse(string(data[1 : len(data)-1]))
}
func (t *Time) GetYAML() (tag string, value interface{}) {
	return "", t.String()
}
func (t *Time) SetYAML(tag string, value interface{}) bool {
	if v, ok := value.(string); ok {
		return t.parse(strings.Trim(v, `'"`)) == nil
	}
	return false
}

type Suite struct {
	Type  string
	Start *Time
	Count int
	Seed  int
	Rev   int
	Extra interface{} `yaml:",omitempty" json:",omitempty"`

	Tests []*Test
	Cases []*Case
	Notes []Note
	Final Tally
}

func (s *Suite) parse(dec decoder, handler Handler) (err error) {
	handler.HandleBeforeStream()
	var lastCase *Case
	for dec.Scan() {
		doc := dec.Scanner.Text()
		handler.HandleDoc(doc, dec.format)

		if strings.TrimSpace(doc) == "" {
			continue
		}

		if s.Start == nil {
			if err = dec.Unmarshal([]byte(doc), &s); err != nil {
				return
			}
			handler.HandleSuite(s)
			continue
		}

		var typ string
		if typ, err = dec.Type([]byte(doc)); err != nil {
			return
		}

		switch typ {
		case "case":
			var c Case
			if err = dec.Unmarshal([]byte(doc), &c); err != nil {
				return
			}

			if c.Level == 0 {
				s.Cases = append(s.Cases, &c)
				lastCase = &c
			} else if lastCase == nil {
				// TODO: possibly just add this case to the suite
				return fmt.Errorf("No parent found for test case: %v", c)
			} else {
				// find the parent
				for c.Level <= lastCase.Level {
					lastCase = lastCase.parent
				}
				// TODO: strict check to ensure c.Level == lastCase.Level + 1
				c.parent = lastCase
				lastCase.Subcases = append(lastCase.Subcases, &c)
			}
			handler.HandleCase(&c)
		case "test":
			var t Test
			if err = dec.Unmarshal([]byte(doc), &t); err != nil {
				return
			}
			if lastCase != nil {
				lastCase.Tests = append(lastCase.Tests, &t)
			} else {
				s.Tests = append(s.Tests, &t)
			}
			handler.HandleTest(&t)
		case "note":
			var n Note
			if err = dec.Unmarshal([]byte(doc), &n); err != nil {
				return
			}
			s.Notes = append(s.Notes, n)
			handler.HandleNote(&n)
		case "tally":
			var t Tally
			if err = dec.Unmarshal([]byte(doc), &t); err != nil {
				return
			}
			handler.HandleTally(&t)
		case "final":
			var t Tally
			if err = dec.Unmarshal([]byte(doc), &t); err != nil {
				return
			}
			s.Final = t
			handler.HandleFinal(&t)
		default:
			err = fmt.Errorf("Invalid type: %s", typ)
			return
		}
	}
	handler.HandleAfterStream()
	return
}

func (s Suite) Docs() (ret []interface{}) {
	ret = append(ret, s)
	for _, t := range s.Tests {
		ret = append(ret, t)
	}

	for _, c := range s.Cases {
		ret = append(ret, c.Docs()...)
	}

	for _, n := range s.Notes {
		ret = append(ret, n)
	}

	ret = append(ret, s.Final)
	return
}

func (s Suite) ToTapY() ([]byte, error) {
	var ret []byte
	for _, doc := range s.Docs() {
		if b, err := yaml.Marshal(doc); err != nil {
			return nil, err
		} else {
			ret = append(ret, []byte("---\n")...)
			ret = append(ret, b...)
			ret = append(ret, '\n')
		}
	}
	return append(ret, []byte("...")...), nil
}

type Case struct {
	Type    string
	Subtype string
	Label   string
	Level   int
	Extra   interface{} `yaml:",omitempty" json:",omitempty"`

	Tests    []*Test
	Subcases []*Case

	parent *Case
}

func (c Case) Docs() (ret []interface{}) {
	ret = append(ret, c)
	for _, t := range c.Tests {
		ret = append(ret, t)
	}
	for _, c := range c.Subcases {
		ret = append(ret, c.Docs()...)
	}
	return
}

// TODO: allow strings via UnmarshalJSON/YAML
type Snippet []map[string]string

type Test struct {
	Type     string
	Subtype  string `yaml:",omitempty" json:",omitempty"`
	Status   string
	Setup    string `yaml:",omitempty" json:",omitempty"`
	Label    string
	Expected interface{} `yaml:",omitempty" json:",omitempty"`
	Returned interface{} `yaml:",omitempty" json:",omitempty"`
	File     string      `yaml:",omitempty" json:",omitempty"`
	Line     int         `yaml:",omitempty" json:",omitempty"`
	Source   string      `yaml:",omitempty" json:",omitempty"`
	Snippet  Snippet     `yaml:",omitempty" json:",omitempty"`
	Coverage struct {
		File string
		Line interface{}
		Code string
	} `yaml:",omitempty" json:",omitempty"`
	Exception struct {
		Message   string
		File      string
		Line      int
		Source    string
		Snippet   Snippet
		Backtrace interface{}
	} `yaml:",omitempty" json:",omitempty"`
	Stdout string `yaml:",omitempty" json:",omitempty"`
	Stderr string `yaml:",omitempty" json:",omitempty"`
	Time   float64
	Extra  interface{} `yaml:",omitempty" json:",omitempty"`
}

type Note struct {
	Type  string
	Text  string
	Extra interface{} `yaml:",omitempty" json:",omitempty"`
}

type Tally struct {
	Type   string
	Time   float64
	Counts struct {
		Total int
		Pass  int
		Fail  int
		Error int
		Omit  int
		Todo  int
	}
	Extra interface{} `yaml:",omitempty" json:",omitempty"`
}

type decoder struct {
	*bufio.Scanner
	format    string
	Unmarshal func([]byte, interface{}) error
}

func scanYAMLDoc(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if loc := docSep.FindIndex(data); loc != nil {
		if loc[0] == 0 {
			// just a start document marker
			return loc[1], nil, nil
		}
		// We found a complete document
		return loc[1], data[0:loc[0]], nil
	}
	if atEOF {
		// if we're at EOF without having matched docSep, there's nothing else to get
		return len(data), nil, nil
	}
	// Request more data.
	return 0, nil, nil
}

func newDecoder(typ string, r io.Reader) decoder {
	dec := decoder{Scanner: bufio.NewScanner(r)}
	// TODO: handle plain TAP with a decoder as well
	switch typ {
	case "json":
		dec.format = "json"
		dec.Unmarshal = json.Unmarshal
	case "yaml":
		dec.format = "yaml"
		dec.Unmarshal = yaml.Unmarshal
		dec.Scanner.Split(scanYAMLDoc)
	default:
		panic("Unsupported decoder")
	}
	return dec
}

func (dec decoder) Type(b []byte) (string, error) {
	m := make(map[string]interface{})
	if err := dec.Unmarshal(b, &m); err != nil {
		return "", fmt.Errorf("Invalid document: %s", err)
	}
	if typ, ok := m["type"].(string); !ok {
		return "", fmt.Errorf("Missing 'type' key in document")
	} else {
		return typ, nil
	}
}

func ParseTap(r io.Reader, handler Handler) (*Suite, error) {
	if handler == nil {
		handler = &DefaultHandler{}
	}
	handler.HandleBeforeStream()
	rd := bufio.NewReader(r)
	var s Suite
	s.Start = &Time{time.Now()}
	first := true
	var totalTests int
	for {
		line, err := rd.ReadString('\n')
		if err == io.EOF {
			break
		} else if err != nil {
			return nil, err
		}
		handler.HandleDoc(line, "tap")

		if line[0] == '#' {
			// TODO: do something with diagnostic lines
			continue
		}

		if matches := tapBailOut.FindStringSubmatch(line); matches != nil {
			// TODO: do something with the bail reason
			break
		}

		if first {
			if matches := tapVersion.FindStringSubmatch(line); matches != nil {
				// TAP 13+
				return nil, fmt.Errorf("TAP13 not supported yet")
			}
			first = false
			if matches := tapPlan.FindStringSubmatch(line); matches != nil {
				totalTests, _ = strconv.Atoi(matches[1])
				s.Count = totalTests
				// assuming plan line at beginning of output
				handler.HandleSuite(&s)
				continue
			}
		}

		matches := tapTestLine.FindStringSubmatch(line)
		if matches == nil {
			// TODO: handle extra data
			continue
		}

		t := Test{Type: "test"}
		switch matches[1] {
		case "ok":
			t.Status = "pass"
		case "not ok":
			// TODO: error?
			t.Status = "fail"
		}
		// ignore number
		t.Label = strings.TrimSpace(matches[3])
		directive := strings.TrimSpace(matches[4])
		switch {
		case strings.HasPrefix(strings.ToUpper(directive), "TODO"):
			s.Final.Counts.Todo++
			t.Label = directive
			t.Status = "todo"
		case strings.HasPrefix(strings.ToUpper(directive), "SKIP"):
			s.Final.Counts.Omit++
			t.Label = directive
			t.Status = "omit"
		case t.Status == "pass":
			s.Final.Counts.Pass++
		default:
			s.Final.Counts.Fail++
		}
		handler.HandleTest(&t)
		s.Final.Counts.Total++
		s.Tests = append(s.Tests, &t)
	}

	// fixup missing tests
	if totalTests > 0 && totalTests > s.Final.Counts.Total {
		s.Final.Counts.Fail += (totalTests - s.Final.Counts.Total)
		s.Final.Counts.Total = totalTests
	}

	handler.HandleFinal(&s.Final)
	handler.HandleAfterStream()
	return &s, nil
}
<commit_msg>Mark optional fields as omitempty<commit_after>package stein

import (
	"bufio"
	"encoding/json"
	"fmt"
	"gopkg.in/v1/yaml"
	"io"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var (
	docSep      = regexp.MustCompile(`(?m)^---$|^\.\.\.$`)
	tapTestLine = regexp.MustCompile(`(?m)^(ok|not ok)(?: (\d+))?( [^#]+)?(?:\s*#(.*))?$`)
	tapPlan     = regexp.MustCompile(`(?m)^\d..(\d+|N).*$`)
	tapVersion  = regexp.MustCompile(`(?m)^TAP version \d+$`)
	tapBailOut  = regexp.MustCompile(`(?m)^Bail out!\s*(.*)$`)
)

func Parse(r io.Reader) (*Suite, error) {
	parser := Parser{&DefaultHandler{}}
	return parser.Parse(r)
}

type Parser struct {
	Handler Handler
}

func (p *Parser) Parse(r io.Reader) (*Suite, error) {
	rd := bufio.NewReader(r)
	b, err := rd.Peek(1)
	if err != nil {
		panic(err)
	}

	if p.Handler == nil {
		p.Handler = &DefaultHandler{}
	}

	s := new(Suite)
	switch b[0] {
	case '{':
		return s, s.parse(newDecoder("json", rd), p.Handler)
	case '-':
		return s, s.parse(newDecoder("yaml", rd), p.Handler)
	default:
		return ParseTap(rd, p.Handler)
	}
}

type Time struct {
	time.Time
}

func (t *Time) parse(val string) (err error) {
	var ti time.Time
	if ti, err = time.Parse("2006-01-02 15:04:05", val); err != nil {
		if ti, err = time.Parse(time.RFC3339, val); err != nil {
			return
		}
	}
	(*t).Time = ti
	return
}

func (t *Time) UnmarshalJSON(data []byte) (err error) {
	// parse time, stripping the quotes
	return t.parse(string(data[1 : len(data)-1]))
}
func (t *Time) GetYAML() (tag string, value interface{}) {
	return "", t.String()
}
func (t *Time) SetYAML(tag string, value interface{}) bool {
	if v, ok := value.(string); ok {
		return t.parse(strings.Trim(v, `'"`)) == nil
	}
	return false
}

type Suite struct {
	Type  string
	Start *Time
	Count int
	Seed  int
	Rev   int
	Extra interface{} `yaml:",omitempty" json:",omitempty"`

	Tests []*Test
	Cases []*Case
	Notes []Note
	Final Tally
}

func (s *Suite) parse(dec decoder, handler Handler) (err error) {
	handler.HandleBeforeStream()
	var lastCase *Case
	for dec.Scan() {
		doc := dec.Scanner.Text()
		handler.HandleDoc(doc, dec.format)

		if strings.TrimSpace(doc) == "" {
			continue
		}

		if s.Start == nil {
			if err = dec.Unmarshal([]byte(doc), &s); err != nil {
				return
			}
			handler.HandleSuite(s)
			continue
		}

		var typ string
		if typ, err = dec.Type([]byte(doc)); err != nil {
			return
		}

		switch typ {
		case "case":
			var c Case
			if err = dec.Unmarshal([]byte(doc), &c); err != nil {
				return
			}

			if c.Level == 0 {
				s.Cases = append(s.Cases, &c)
				lastCase = &c
			} else if lastCase == nil {
				// TODO: possibly just add this case to the suite
				return fmt.Errorf("No parent found for test case: %v", c)
			} else {
				// find the parent
				for c.Level <= lastCase.Level {
					lastCase = lastCase.parent
				}
				// TODO: strict check to ensure c.Level == lastCase.Level + 1
				c.parent = lastCase
				lastCase.Subcases = append(lastCase.Subcases, &c)
			}
			handler.HandleCase(&c)
		case "test":
			var t Test
			if err = dec.Unmarshal([]byte(doc), &t); err != nil {
				return
			}
			if lastCase != nil {
				lastCase.Tests = append(lastCase.Tests, &t)
			} else {
				s.Tests = append(s.Tests, &t)
			}
			handler.HandleTest(&t)
		case "note":
			var n Note
			if err = dec.Unmarshal([]byte(doc), &n); err != nil {
				return
			}
			s.Notes = append(s.Notes, n)
			handler.HandleNote(&n)
		case "tally":
			var t Tally
			if err = dec.Unmarshal([]byte(doc), &t); err != nil {
				return
			}
			handler.HandleTally(&t)
		case "final":
			var t Tally
			if err = dec.Unmarshal([]byte(doc), &t); err != nil {
				return
			}
			s.Final = t
			handler.HandleFinal(&t)
		default:
			err = fmt.Errorf("Invalid type: %s", typ)
			return
		}
	}
	handler.HandleAfterStream()
	return
}

func (s Suite) Docs() (ret []interface{}) {
	ret = append(ret, s)
	for _, t := range s.Tests {
		ret = append(ret, t)
	}

	for _, c := range s.Cases {
		ret = append(ret, c.Docs()...)
	}

	for _, n := range s.Notes {
		ret = append(ret, n)
	}

	ret = append(ret, s.Final)
	return
}

func (s Suite) ToTapY() ([]byte, error) {
	var ret []byte
	for _, doc := range s.Docs() {
		if b, err := yaml.Marshal(doc); err != nil {
			return nil, err
		} else {
			ret = append(ret, []byte("---\n")...)
			ret = append(ret, b...)
			ret = append(ret, '\n')
		}
	}
	return append(ret, []byte("...")...), nil
}

type Case struct {
	Type    string
	Label   string
	Level   int

	Subtype string `yaml:",omitempty" json:",omitempty"`
	Extra   interface{} `yaml:",omitempty" json:",omitempty"`

    // Not part of any TAP protocol
	Tests    []*Test `yaml:",omitempty" json:",omitempty"`
    // Not part of any TAP protocol
	Subcases []*Case `yaml:",omitempty" json:",omitempty"`

	parent *Case
}

func (c Case) Docs() (ret []interface{}) {
	ret = append(ret, c)
	for _, t := range c.Tests {
		ret = append(ret, t)
	}
	for _, c := range c.Subcases {
		ret = append(ret, c.Docs()...)
	}
	return
}

// TODO: allow strings via UnmarshalJSON/YAML
type Snippet []map[string]string

type TestCoverage struct {
	File string `yaml:",omitempty" json:",omitempty"`
	// TODO: limit this to a number
	// according to tap-y/j, can be:
	// - line: 11
	// - range: 11..13
	// - list of lines: [11,12,13]
	Line interface{} `yaml:",omitempty" json:",omitempty"`
	Code string      `yaml:",omitempty" json:",omitempty"`
}

type TestException struct {
	Message   string
	File      string      `yaml:",omitempty" json:",omitempty"`
	Line      int         `yaml:",omitempty" json:",omitempty"`
	Source    string      `yaml:",omitempty" json:",omitempty"`
	Snippet   Snippet     `yaml:",omitempty" json:",omitempty"`
	Backtrace interface{} `yaml:",omitempty" json:",omitempty"`
}

type Test struct {
	Type   string
	Status string
	Label  string
	Time   float64

	Subtype   string        `yaml:",omitempty" json:",omitempty"`
	Setup     string        `yaml:",omitempty" json:",omitempty"`
	Expected  interface{}   `yaml:",omitempty" json:",omitempty"`
	Returned  interface{}   `yaml:",omitempty" json:",omitempty"`
	File      string        `yaml:",omitempty" json:",omitempty"`
	Line      int           `yaml:",omitempty" json:",omitempty"`
	Source    string        `yaml:",omitempty" json:",omitempty"`
	Snippet   Snippet       `yaml:",omitempty" json:",omitempty"`
	Coverage  *TestCoverage  `yaml:",omitempty" json:",omitempty"`
	Exception *TestException `yaml:",omitempty" json:",omitempty"`
	Stdout    string        `yaml:",omitempty" json:",omitempty"`
	Stderr    string        `yaml:",omitempty" json:",omitempty"`
	Extra     interface{}   `yaml:",omitempty" json:",omitempty"`
}

type Note struct {
	Type  string
	Text  string
	Extra interface{} `yaml:",omitempty" json:",omitempty"`
}

type Tally struct {
	Type   string
	Time   float64 `yaml:",omitempty" json:",omitempty"`
	Counts struct {
		Total int
		Pass  int
		Fail  int
		Error int
		Omit  int
		Todo  int
	}
	Extra interface{} `yaml:",omitempty" json:",omitempty"`
}

type decoder struct {
	*bufio.Scanner
	format    string
	Unmarshal func([]byte, interface{}) error
}

func scanYAMLDoc(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if loc := docSep.FindIndex(data); loc != nil {
		if loc[0] == 0 {
			// just a start document marker
			return loc[1], nil, nil
		}
		// We found a complete document
		return loc[1], data[0:loc[0]], nil
	}
	if atEOF {
		// if we're at EOF without having matched docSep, there's nothing else to get
		return len(data), nil, nil
	}
	// Request more data.
	return 0, nil, nil
}

func newDecoder(typ string, r io.Reader) decoder {
	dec := decoder{Scanner: bufio.NewScanner(r)}
	// TODO: handle plain TAP with a decoder as well
	switch typ {
	case "json":
		dec.format = "json"
		dec.Unmarshal = json.Unmarshal
	case "yaml":
		dec.format = "yaml"
		dec.Unmarshal = yaml.Unmarshal
		dec.Scanner.Split(scanYAMLDoc)
	default:
		panic("Unsupported decoder")
	}
	return dec
}

func (dec decoder) Type(b []byte) (string, error) {
	m := make(map[string]interface{})
	if err := dec.Unmarshal(b, &m); err != nil {
		return "", fmt.Errorf("Invalid document: %s", err)
	}
	if typ, ok := m["type"].(string); !ok {
		return "", fmt.Errorf("Missing 'type' key in document")
	} else {
		return typ, nil
	}
}

func ParseTap(r io.Reader, handler Handler) (*Suite, error) {
	if handler == nil {
		handler = &DefaultHandler{}
	}
	handler.HandleBeforeStream()
	rd := bufio.NewReader(r)
	var s Suite
	s.Start = &Time{time.Now()}
	first := true
	var totalTests int
	for {
		line, err := rd.ReadString('\n')
		if err == io.EOF {
			break
		} else if err != nil {
			return nil, err
		}
		handler.HandleDoc(line, "tap")

		if line[0] == '#' {
			// TODO: do something with diagnostic lines
			continue
		}

		if matches := tapBailOut.FindStringSubmatch(line); matches != nil {
			// TODO: do something with the bail reason
			break
		}

		if first {
			if matches := tapVersion.FindStringSubmatch(line); matches != nil {
				// TAP 13+
				return nil, fmt.Errorf("TAP13 not supported yet")
			}
			first = false
			if matches := tapPlan.FindStringSubmatch(line); matches != nil {
				totalTests, _ = strconv.Atoi(matches[1])
				s.Count = totalTests
				// assuming plan line at beginning of output
				handler.HandleSuite(&s)
				continue
			}
		}

		matches := tapTestLine.FindStringSubmatch(line)
		if matches == nil {
			// TODO: handle extra data
			continue
		}

		t := Test{Type: "test"}
		switch matches[1] {
		case "ok":
			t.Status = "pass"
		case "not ok":
			// TODO: error?
			t.Status = "fail"
		}
		// ignore number
		t.Label = strings.TrimSpace(matches[3])
		directive := strings.TrimSpace(matches[4])
		switch {
		case strings.HasPrefix(strings.ToUpper(directive), "TODO"):
			s.Final.Counts.Todo++
			t.Label = directive
			t.Status = "todo"
		case strings.HasPrefix(strings.ToUpper(directive), "SKIP"):
			s.Final.Counts.Omit++
			t.Label = directive
			t.Status = "omit"
		case t.Status == "pass":
			s.Final.Counts.Pass++
		default:
			s.Final.Counts.Fail++
		}
		handler.HandleTest(&t)
		s.Final.Counts.Total++
		s.Tests = append(s.Tests, &t)
	}

	// fixup missing tests
	if totalTests > 0 && totalTests > s.Final.Counts.Total {
		s.Final.Counts.Fail += (totalTests - s.Final.Counts.Total)
		s.Final.Counts.Total = totalTests
	}

	handler.HandleFinal(&s.Final)
	handler.HandleAfterStream()
	return &s, nil
}
<|endoftext|>


<commit_before>//
// concurrency-safe file-backed element store
//
//   - Asynchronous writes (errors reported on next action)
//   - Optional cache with LRU eviction on either
//     store insertion or store retrieval
//
// To avoid a lot of casting from store.Element on Store#Get, you should probably
// write a wrapper around this element store for each type you intend
// to use it for.
//
// Pull requests welcome
package store

import (
	"container/list"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"sync"
)

var (
	ErrAlreadyExists = errors.New("element already exists in store")
	ErrDoesNotExist  = errors.New("element not present in store")
)

type ElementID uint64

func (id ElementID) String() string {
	return strconv.FormatUint(uint64(id), 36)
}

func (id *ElementID) FromString(str string) error {
	n, err := strconv.ParseUint(str, 36, 64)
	if err != nil {
		return err
	}
	*id = ElementID(n)
	return nil
}

type Element interface {
	// load an element from a reader
	Load(io.Reader) error

	// store an element to a writer
	Store(io.Writer) error

	// must return a file-system safe ID
	// the two first letters of ID will be
	// used to create directories and the
	// complete ID string will be used as
	// a file name
	ID() ElementID
}

type Cache interface {
	// update (insert, promote) an element in the cache
	Cache(Element)

	// retrieve an Element from the cache, or nil if non-existant
	Get(id ElementID) Element
}

// cache with LRU eviction policy
type LRUCache struct {
	l     *list.List
	m     map[ElementID]*list.Element
	mutex sync.Mutex
	size  int
}

// create a new cache with room for 'size' elements
func NewLRUCache(size int) *LRUCache {
	if size <= 0 {
		return nil
	}

	return &LRUCache{
		l:    list.New(),
		m:    make(map[ElementID]*list.Element),
		size: size,
	}
}

// update (insert, promote) an element in the cache
func (l *LRUCache) Cache(el Element) {
	// how cache with LRU eviction works:
	//
	// The cache itself is a linked list and a hash table.
	// new elements are put in the front of the list and
	// old ones are removed from the end back of the list.
	// The hash table is used for lookups in O(1) time
	//
	// If we assume a constant element size, this
	// implementation gives us constant space over time
	//
	// cache algorithm:
	//   if in cache:
	//     promote list element to front of list
	//   if not in cache:
	//     if cache not full:
	//       insert to front of list
	//       insert front element to hash table
	//     if cache is full:
	//       remove last element in list from hash table
	//       replace last element in list with new element
	//       move last element in list to front
	//       insert the now front element in list to hash table

	if l == nil || el == nil {
		return
	}

	id := el.ID()

	l.mutex.Lock()
	defer l.mutex.Unlock()

	listElem, exists := l.m[id]
	if exists {
		l.l.MoveToFront(listElem)
	} else {
		if len(l.m) < l.size {
			listElem = l.l.PushFront(el)
			l.m[id] = listElem
		} else {
			listElem = l.l.Back()
			listVal := listElem.Value.(Element)
			delete(l.m, listVal.ID())
			listElem.Value = el
			l.l.MoveToFront(listElem)
			l.m[id] = listElem
		}
	}
}

// retrieve an element from the cache, or nil if
// the element is not in cache
func (l *LRUCache) Get(id ElementID) Element {

	if l == nil {
		return nil
	}

	l.mutex.Lock()
	defer l.mutex.Unlock()

	el, exists := l.m[id]
	if !exists {
		return nil
	}

	return el.Value.(Element)
}

// Tells the store when to pass an element
// to the cache
type CacheMode int

const (
	CacheOnGet CacheMode = (1 << 0)
	CacheOnPut           = (1 << 1)
)

type Store struct {
	path string

	cache     Cache
	cacheMode CacheMode

	inventory map[ElementID]struct{}
	ilock     sync.RWMutex

	inMem     map[ElementID]Element
	inMemLock sync.RWMutex

	writeErr     error
	activeWrites sync.WaitGroup
}

func New(path string) (*Store, error) {
	s := &Store{
		path:      filepath.Clean(path),
		inventory: make(map[ElementID]struct{}),
		inMem:     make(map[ElementID]Element),
	}

	os.MkdirAll(s.path, 0700)
	walker := func(path string, info os.FileInfo, err error) error {
		if err == nil && info.Mode().IsRegular() {
			var id ElementID
			if err := id.FromString(info.Name()); err == nil {
				var x struct{}
				s.inventory[id] = x

			}
		}

		return err
	}

	if err := filepath.Walk(s.path, walker); err != nil {
		return nil, err
	}

	return s, nil
}

func (s *Store) SetCache(c Cache, mode CacheMode) {
	s.cache = c
	s.cacheMode = mode
}

func (s *Store) Has(id ElementID) bool {
	// we check inMem first, because the inventory
	// is updated before inMem is updated
	s.inMemLock.RLock()
	_, exists := s.inMem[id]
	s.inMemLock.RUnlock()
	if exists {
		return true
	}

	s.ilock.RLock()
	_, exists = s.inventory[id]
	s.ilock.RUnlock()
	return exists
}

func (s *Store) eldir(el Element) string {
	id := el.ID().String()
	if len(id) == 0 {
		panic("zero length ID size")
	}

	dirLen := 2
	if len(id) < dirLen {
		dirLen = len(id)
	}

	return filepath.Join(s.path, id[:dirLen])
}

func (s *Store) get(el Element) error {
	id := el.ID()
	path := filepath.Join(s.eldir(el), id.String())
	fh, err := os.Open(path)
	if err != nil {
		return err
	}

	defer fh.Close()
	return el.Load(fh)
}

// retrieve an Element either from cache or from disk.
// If retrieved from cache, the cache Element will be returned.
// If retrieved from disk, the Element passed to Get will be
// loaded with the data from disk. Therefor, this function
// should always be called like:
//   var el = SomeElement{ID: someID}
//   ret, err := s.Get(&el)
func (s *Store) Get(element Element) (Element, error) {
	id := element.ID()
	if s.cache != nil {
		if el := s.cache.Get(id); el != nil {
			return el, nil
		}
	}

	s.inMemLock.RLock()
	el, exists := s.inMem[id]
	s.inMemLock.RUnlock()
	if exists {
		return el, nil
	}

	// We check the inventory last because
	// an element is only added to the
	// inventory on a successful write.
	// it may be in write transfer cache (inMem)
	// and not in the inventory yet
	s.ilock.RLock()
	_, exists = s.inventory[id]
	s.ilock.RUnlock()
	if !exists {
		return nil, ErrDoesNotExist
	}

	err := s.get(element)
	if err != nil {
		return nil, err
	}

	if s.cache != nil && (s.cacheMode&CacheOnGet != 0) {
		s.cache.Cache(element)
	}

	return element, nil

}

func (s Store) put(el Element) error {
	dir := s.eldir(el)
	if err := os.MkdirAll(dir, 0700); err != nil {
		return err
	}

	path := filepath.Join(dir, el.ID().String())
	fh, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return err
	}

	defer fh.Close()
	return el.Store(fh)
}

func (s *Store) Put(el Element) error {

	if s.writeErr != nil {
		return s.writeErr
	}

	id := el.ID()
	if s.Has(id) {
		return ErrAlreadyExists
	}

	s.inMemLock.Lock()
	s.inMem[id] = el
	s.inMemLock.Unlock()

	s.activeWrites.Add(1)

	go func() {
		defer s.activeWrites.Done()

		defer func() {
			s.inMemLock.Lock()
			delete(s.inMem, id)
			s.inMemLock.Unlock()
		}()

		if err := s.put(el); err != nil {
			s.writeErr = err
		} else {
			var x struct{}
			s.ilock.Lock()
			s.inventory[id] = x
			s.ilock.Unlock()
			if s.cache != nil && (s.cacheMode&CacheOnPut) != 0 {
				s.cache.Cache(el)
			}
		}
	}()

	return nil
}

func (s *Store) Sync() {
	s.activeWrites.Wait()
}

func (s *Store) Remove() error {
	s.Sync()
	return os.RemoveAll(s.path)
}
<commit_msg>Added error check on closing of file in put<commit_after>//
// concurrency-safe file-backed element store
//
//   - Asynchronous writes (errors reported on next action)
//   - Optional cache with LRU eviction on either
//     store insertion or store retrieval
//
// To avoid a lot of casting from store.Element on Store#Get, you should probably
// write a wrapper around this element store for each type you intend
// to use it for.
//
// Pull requests welcome
package store

import (
	"container/list"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"sync"
)

var (
	ErrAlreadyExists = errors.New("element already exists in store")
	ErrDoesNotExist  = errors.New("element not present in store")
)

type ElementID uint64

func (id ElementID) String() string {
	return strconv.FormatUint(uint64(id), 36)
}

func (id *ElementID) FromString(str string) error {
	n, err := strconv.ParseUint(str, 36, 64)
	if err != nil {
		return err
	}
	*id = ElementID(n)
	return nil
}

type Element interface {
	// load an element from a reader
	Load(io.Reader) error

	// store an element to a writer
	Store(io.Writer) error

	// must return a file-system safe ID
	// the two first letters of ID will be
	// used to create directories and the
	// complete ID string will be used as
	// a file name
	ID() ElementID
}

type Cache interface {
	// update (insert, promote) an element in the cache
	Cache(Element)

	// retrieve an Element from the cache, or nil if non-existant
	Get(id ElementID) Element
}

// cache with LRU eviction policy
type LRUCache struct {
	l     *list.List
	m     map[ElementID]*list.Element
	mutex sync.Mutex
	size  int
}

// create a new cache with room for 'size' elements
func NewLRUCache(size int) *LRUCache {
	if size <= 0 {
		return nil
	}

	return &LRUCache{
		l:    list.New(),
		m:    make(map[ElementID]*list.Element),
		size: size,
	}
}

// update (insert, promote) an element in the cache
func (l *LRUCache) Cache(el Element) {
	// how cache with LRU eviction works:
	//
	// The cache itself is a linked list and a hash table.
	// new elements are put in the front of the list and
	// old ones are removed from the end back of the list.
	// The hash table is used for lookups in O(1) time
	//
	// If we assume a constant element size, this
	// implementation gives us constant space over time
	//
	// cache algorithm:
	//   if in cache:
	//     promote list element to front of list
	//   if not in cache:
	//     if cache not full:
	//       insert to front of list
	//       insert front element to hash table
	//     if cache is full:
	//       remove last element in list from hash table
	//       replace last element in list with new element
	//       move last element in list to front
	//       insert the now front element in list to hash table

	if l == nil || el == nil {
		return
	}

	id := el.ID()

	l.mutex.Lock()
	defer l.mutex.Unlock()

	listElem, exists := l.m[id]
	if exists {
		l.l.MoveToFront(listElem)
	} else {
		if len(l.m) < l.size {
			listElem = l.l.PushFront(el)
			l.m[id] = listElem
		} else {
			listElem = l.l.Back()
			listVal := listElem.Value.(Element)
			delete(l.m, listVal.ID())
			listElem.Value = el
			l.l.MoveToFront(listElem)
			l.m[id] = listElem
		}
	}
}

// retrieve an element from the cache, or nil if
// the element is not in cache
func (l *LRUCache) Get(id ElementID) Element {

	if l == nil {
		return nil
	}

	l.mutex.Lock()
	defer l.mutex.Unlock()

	el, exists := l.m[id]
	if !exists {
		return nil
	}

	return el.Value.(Element)
}

// Tells the store when to pass an element
// to the cache
type CacheMode int

const (
	CacheOnGet CacheMode = (1 << 0)
	CacheOnPut           = (1 << 1)
)

type Store struct {
	path string

	cache     Cache
	cacheMode CacheMode

	inventory map[ElementID]struct{}
	ilock     sync.RWMutex

	inMem     map[ElementID]Element
	inMemLock sync.RWMutex

	writeErr     error
	activeWrites sync.WaitGroup
}

func New(path string) (*Store, error) {
	s := &Store{
		path:      filepath.Clean(path),
		inventory: make(map[ElementID]struct{}),
		inMem:     make(map[ElementID]Element),
	}

	os.MkdirAll(s.path, 0700)
	walker := func(path string, info os.FileInfo, err error) error {
		if err == nil && info.Mode().IsRegular() {
			var id ElementID
			if err := id.FromString(info.Name()); err == nil {
				var x struct{}
				s.inventory[id] = x

			}
		}

		return err
	}

	if err := filepath.Walk(s.path, walker); err != nil {
		return nil, err
	}

	return s, nil
}

func (s *Store) SetCache(c Cache, mode CacheMode) {
	s.cache = c
	s.cacheMode = mode
}

func (s *Store) Has(id ElementID) bool {
	// we check inMem first, because the inventory
	// is updated before inMem is updated
	s.inMemLock.RLock()
	_, exists := s.inMem[id]
	s.inMemLock.RUnlock()
	if exists {
		return true
	}

	s.ilock.RLock()
	_, exists = s.inventory[id]
	s.ilock.RUnlock()
	return exists
}

func (s *Store) eldir(el Element) string {
	id := el.ID().String()
	if len(id) == 0 {
		panic("zero length ID size")
	}

	dirLen := 2
	if len(id) < dirLen {
		dirLen = len(id)
	}

	return filepath.Join(s.path, id[:dirLen])
}

func (s *Store) get(el Element) error {
	id := el.ID()
	path := filepath.Join(s.eldir(el), id.String())
	fh, err := os.Open(path)
	if err != nil {
		return err
	}

	defer fh.Close()
	return el.Load(fh)
}

// retrieve an Element either from cache or from disk.
// If retrieved from cache, the cache Element will be returned.
// If retrieved from disk, the Element passed to Get will be
// loaded with the data from disk. Therefor, this function
// should always be called like:
//   var el = SomeElement{ID: someID}
//   ret, err := s.Get(&el)
func (s *Store) Get(element Element) (Element, error) {
	id := element.ID()
	if s.cache != nil {
		if el := s.cache.Get(id); el != nil {
			return el, nil
		}
	}

	s.inMemLock.RLock()
	el, exists := s.inMem[id]
	s.inMemLock.RUnlock()
	if exists {
		return el, nil
	}

	// We check the inventory last because
	// an element is only added to the
	// inventory on a successful write.
	// it may be in write transfer cache (inMem)
	// and not in the inventory yet
	s.ilock.RLock()
	_, exists = s.inventory[id]
	s.ilock.RUnlock()
	if !exists {
		return nil, ErrDoesNotExist
	}

	err := s.get(element)
	if err != nil {
		return nil, err
	}

	if s.cache != nil && (s.cacheMode&CacheOnGet != 0) {
		s.cache.Cache(element)
	}

	return element, nil

}

func (s Store) put(el Element) error {
	dir := s.eldir(el)
	if err := os.MkdirAll(dir, 0700); err != nil {
		return err
	}

	path := filepath.Join(dir, el.ID().String())
	fh, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return err
	}

	if err := el.Store(fh); err != nil {
		return err
	}

	return fh.Close()
}

func (s *Store) Put(el Element) error {

	if s.writeErr != nil {
		return s.writeErr
	}

	id := el.ID()
	if s.Has(id) {
		return ErrAlreadyExists
	}

	s.inMemLock.Lock()
	s.inMem[id] = el
	s.inMemLock.Unlock()

	s.activeWrites.Add(1)

	go func() {
		defer s.activeWrites.Done()

		defer func() {
			s.inMemLock.Lock()
			delete(s.inMem, id)
			s.inMemLock.Unlock()
		}()

		if err := s.put(el); err != nil {
			s.writeErr = err
		} else {
			var x struct{}
			s.ilock.Lock()
			s.inventory[id] = x
			s.ilock.Unlock()
			if s.cache != nil && (s.cacheMode&CacheOnPut) != 0 {
				s.cache.Cache(el)
			}
		}
	}()

	return nil
}

func (s *Store) Sync() {
	s.activeWrites.Wait()
}

func (s *Store) Remove() error {
	s.Sync()
	return os.RemoveAll(s.path)
}
<|endoftext|>


<commit_before>package rtfdoc

import "fmt"

func getDefaultTableProperties() TableProperties {
	tp := TableProperties{
		align: "c",
	}
	tp.SetTableMargins(100, 100, 100, 100)
	return tp
}

func NewTable() Table {
	return Table{TableProperties: getDefaultTableProperties()}
}

func (t *Table) AddRow(row TableRow) {
	t.Data = append(t.Data, row)
}

func (t *TableProperties) SetTableMargins(left, top, right, bottom int) {
	margins := ""
	if left != 0 {
		margins += fmt.Sprintf(" \\trpaddl%d", left)
	}
	if top != 0 {
		margins += fmt.Sprintf(" \\trpaddt%d", top)
	}
	if right != 0 {
		margins += fmt.Sprintf(" \\trpaddr%d", right)
	}
	if bottom != 0 {
		margins += fmt.Sprintf(" \\trpaddb%d", bottom)
	}
	margins += " "
	t.margins = margins
}

func (t *TableProperties) getMargins() string {
	return t.margins
}

func (t Table) compose() string {
	res := ""
	var align = ""
	if t.align != "" {
		align = fmt.Sprintf("\\trq%s", t.align)
	}
	for _, tr := range t.Data {
		res += fmt.Sprintf("\n{\\trowd %s", align)
		res += t.getMargins()
		res += tr.compose()
		res += "\n\\row}"
	}
	return res
}

func NewTableRow() TableRow {
	return TableRow{}
}
func (tr *TableRow) AddCell(cell TableCell) {
	*tr = append(*tr, cell)
}

func (tr TableRow) compose() string {
	res := ""
	if len(tr) != 0 {
		cBegin := 0
		for _, dc := range tr {
			cBegin += dc.getCellWidth()
			res += fmt.Sprintf("\n%s %s %s %s \\cellx%v", dc.getVerticalMergedProperty(), dc.getCellMargins(), dc.getBorders(), dc.getCellTextVAlign(), cBegin)

		}
		for _, dc := range tr {
			res += dc.cellCompose()
		}
	}
	return res
}

func NewDataCell(width int) DataCell {
	cp := CellProperties{}
	cp.CellWidth = width
	dc := DataCell{
		Cell{
			content:        Paragraph{},
			CellProperties: cp,
		},
	}
	dc.SetBorders(true, true, true, true)
	return dc
}
func NewDataCellWithProperties(cp CellProperties) DataCell {
	return DataCell{Cell{
		content:        Paragraph{},
		CellProperties: cp,
	}}
}

func (cp *CellProperties) SetProperties(cellWidth int, borders string) {
	cp.CellWidth = cellWidth
	cp.borders = borders
	return
}

func (dc *DataCell) SetContent(c Paragraph) {
	dc.content = c
}

func (dc DataCell) cellCompose() string {
	res := fmt.Sprintf("\n\\pard\\intbl %s \\cell", dc.Cell.content.cellCompose())

	return res
}

func (dc DataCell) getCellWidth() int {
	return dc.CellWidth
}

func (dc *DataCell) SetBorders(left, top, right, bottom bool) {
	b := ""
	bTemplStr := "\\clbrdr%s\\brdrw15\\brdrs"
	if left {
		b += fmt.Sprintf(bTemplStr, "l")
	}
	if top {
		b += fmt.Sprintf(bTemplStr, "t")
	}
	if right {
		b += fmt.Sprintf(bTemplStr, "r")
	}
	if bottom {
		b += fmt.Sprintf(bTemplStr, "b")
	}
	dc.borders = b
}

func (dc DataCell) getBorders() string {
	return dc.borders
}

func (tp *TableProperties) SetAlign(align string) {
	switch align {
	case "c", "center":
		tp.align = "c"
	case "l", "left":
		tp.align = "l"
	case "r", "right":
		tp.align = "r"
	default:
		tp.align = ""
	}
}

func (tp *TableProperties) GetAlign() string {
	return tp.align
}

func GetTableCellWidthByRatio(tableWidth int, ratio ...float64) []int {

	cellRatioSum := 0.0
	for _, cellRatio := range ratio {
		cellRatioSum += cellRatio
	}
	var cellWidth = make([]int, len(ratio))
	for i := range ratio {
		cellWidth[i] = int(ratio[i] * (float64(tableWidth) / cellRatioSum))
	}
	return cellWidth
}

func (dc *DataCell) SetVerticalMerged(isFirst, isNext bool) {
	if isFirst {
		dc.VerticalMerged.code = "\\clvmgf"
	}
	if isNext {
		dc.VerticalMerged.code = "\\clvmrg"
	}
}

func (dc DataCell) getVerticalMergedProperty() string {
	return dc.VerticalMerged.code
}

func (dc *DataCell) SetCellMargins(left, top, right, bottom int) {
	m := ""
	if left != 0 {
		m += fmt.Sprintf("\\clpadl%d", left)
	}
	if top != 0 {
		m += fmt.Sprintf("\\clpadt%d", top)
	}
	if right != 0 {
		m += fmt.Sprintf("\\clpadr%d", right)
	}
	if bottom != 0 {
		m += fmt.Sprintf("\\clpadb%d", bottom)
	}
	dc.margins = m
}

func (dc DataCell) getCellMargins() string {
	return dc.margins
}

func (dc *DataCell) SetVAlign(valign string) {
	switch valign {
	case "c", "center":
		dc.vTextAlign = "\\clvertalc"
	case "t", "top":
		dc.vTextAlign = "\\clvertalt"
	case "b", "bottom":
		dc.vTextAlign = "\\clvertalb"
	default:
		dc.vTextAlign = ""
	}
}

func (dc DataCell) getCellTextVAlign() string {
	return dc.vTextAlign
}
<commit_msg>Delete spaces from row compose<commit_after>package rtfdoc

import "fmt"

func getDefaultTableProperties() TableProperties {
	tp := TableProperties{
		align: "c",
	}
	tp.SetTableMargins(100, 100, 100, 100)
	return tp
}

func NewTable() Table {
	return Table{TableProperties: getDefaultTableProperties()}
}

func (t *Table) AddRow(row TableRow) {
	t.Data = append(t.Data, row)
}

func (t *TableProperties) SetTableMargins(left, top, right, bottom int) {
	margins := ""
	if left != 0 {
		margins += fmt.Sprintf(" \\trpaddl%d", left)
	}
	if top != 0 {
		margins += fmt.Sprintf(" \\trpaddt%d", top)
	}
	if right != 0 {
		margins += fmt.Sprintf(" \\trpaddr%d", right)
	}
	if bottom != 0 {
		margins += fmt.Sprintf(" \\trpaddb%d", bottom)
	}
	margins += " "
	t.margins = margins
}

func (t *TableProperties) getMargins() string {
	return t.margins
}

func (t Table) compose() string {
	res := ""
	var align = ""
	if t.align != "" {
		align = fmt.Sprintf("\\trq%s", t.align)
	}
	for _, tr := range t.Data {
		res += fmt.Sprintf("\n{\\trowd %s", align)
		res += t.getMargins()
		res += tr.compose()
		res += "\n\\row}"
	}
	return res
}

func NewTableRow() TableRow {
	return TableRow{}
}
func (tr *TableRow) AddCell(cell TableCell) {
	*tr = append(*tr, cell)
}

func (tr TableRow) compose() string {
	res := ""
	if len(tr) != 0 {
		cBegin := 0
		for _, dc := range tr {
			cBegin += dc.getCellWidth()
			res += fmt.Sprintf("\n%s%s%s%s \\cellx%v", dc.getVerticalMergedProperty(), dc.getCellMargins(), dc.getBorders(), dc.getCellTextVAlign(), cBegin)

		}
		for _, dc := range tr {
			res += dc.cellCompose()
		}
	}
	return res
}

func NewDataCell(width int) DataCell {
	cp := CellProperties{}
	cp.CellWidth = width
	dc := DataCell{
		Cell{
			content:        Paragraph{},
			CellProperties: cp,
		},
	}
	dc.SetBorders(true, true, true, true)
	return dc
}
func NewDataCellWithProperties(cp CellProperties) DataCell {
	return DataCell{Cell{
		content:        Paragraph{},
		CellProperties: cp,
	}}
}

func (cp *CellProperties) SetProperties(cellWidth int, borders string) {
	cp.CellWidth = cellWidth
	cp.borders = borders
	return
}

func (dc *DataCell) SetContent(c Paragraph) {
	dc.content = c
}

func (dc DataCell) cellCompose() string {
	res := fmt.Sprintf("\n\\pard\\intbl %s \\cell", dc.Cell.content.cellCompose())

	return res
}

func (dc DataCell) getCellWidth() int {
	return dc.CellWidth
}

func (dc *DataCell) SetBorders(left, top, right, bottom bool) {
	b := ""
	bTemplStr := "\\clbrdr%s\\brdrw15\\brdrs"
	if left {
		b += fmt.Sprintf(bTemplStr, "l")
	}
	if top {
		b += fmt.Sprintf(bTemplStr, "t")
	}
	if right {
		b += fmt.Sprintf(bTemplStr, "r")
	}
	if bottom {
		b += fmt.Sprintf(bTemplStr, "b")
	}
	dc.borders = b
}

func (dc DataCell) getBorders() string {
	return dc.borders
}

func (tp *TableProperties) SetAlign(align string) {
	switch align {
	case "c", "center":
		tp.align = "c"
	case "l", "left":
		tp.align = "l"
	case "r", "right":
		tp.align = "r"
	default:
		tp.align = ""
	}
}

func (tp *TableProperties) GetAlign() string {
	return tp.align
}

func GetTableCellWidthByRatio(tableWidth int, ratio ...float64) []int {

	cellRatioSum := 0.0
	for _, cellRatio := range ratio {
		cellRatioSum += cellRatio
	}
	var cellWidth = make([]int, len(ratio))
	for i := range ratio {
		cellWidth[i] = int(ratio[i] * (float64(tableWidth) / cellRatioSum))
	}
	return cellWidth
}

func (dc *DataCell) SetVerticalMerged(isFirst, isNext bool) {
	if isFirst {
		dc.VerticalMerged.code = "\\clvmgf"
	}
	if isNext {
		dc.VerticalMerged.code = "\\clvmrg"
	}
}

func (dc DataCell) getVerticalMergedProperty() string {
	return dc.VerticalMerged.code
}

func (dc *DataCell) SetCellMargins(left, top, right, bottom int) {
	m := ""
	if left != 0 {
		m += fmt.Sprintf("\\clpadl%d", left)
	}
	if top != 0 {
		m += fmt.Sprintf("\\clpadt%d", top)
	}
	if right != 0 {
		m += fmt.Sprintf("\\clpadr%d", right)
	}
	if bottom != 0 {
		m += fmt.Sprintf("\\clpadb%d", bottom)
	}
	dc.margins = m
}

func (dc DataCell) getCellMargins() string {
	return dc.margins
}

func (dc *DataCell) SetVAlign(valign string) {
	switch valign {
	case "c", "center":
		dc.vTextAlign = "\\clvertalc"
	case "t", "top":
		dc.vTextAlign = "\\clvertalt"
	case "b", "bottom":
		dc.vTextAlign = "\\clvertalb"
	default:
		dc.vTextAlign = ""
	}
}

func (dc DataCell) getCellTextVAlign() string {
	return dc.vTextAlign
}
<|endoftext|>


<commit_before>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	"fmt"
	"html/template"
	"model"
	"net/url"
	"regexp"
	"strings"
	"time"

	. "db"

	"github.com/fatih/structs"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/logger"
	"github.com/polaris1119/set"
	"github.com/polaris1119/slices"
	"golang.org/x/net/context"
)

type CommentLogic struct{}

var DefaultComment = CommentLogic{}

// FindObjComments 获得某个对象的所有评论
// owner: 被评论对象属主
// TODO:分页暂不做
func (self CommentLogic) FindObjComments(ctx context.Context, objid, objtype int, owner, lastCommentUid int /*, page, pageNum int*/) (comments []map[string]interface{}, ownerUser, lastReplyUser *model.User) {
	objLog := GetLogger(ctx)

	commentList := make([]*model.Comment, 0)
	err := MasterDB.Where("objid=? AND objtype=?", objid, objtype).Find(&commentList)
	if err != nil {
		objLog.Errorln("CommentLogic FindObjComments Error:", err)
		return
	}

	uids := slices.StructsIntSlice(commentList, "Uid")

	// 避免某些情况下最后回复人没在回复列表中
	uids = append(uids, owner, lastCommentUid)

	// 获得用户信息
	userMap := DefaultUser.FindUserInfos(ctx, uids)
	ownerUser = userMap[owner]
	if lastCommentUid != 0 {
		lastReplyUser = userMap[lastCommentUid]
	}
	comments = make([]map[string]interface{}, 0, len(commentList))
	for _, comment := range commentList {
		tmpMap := structs.Map(comment)
		tmpMap["content"] = template.HTML(self.decodeCmtContent(ctx, comment))
		tmpMap["user"] = userMap[comment.Uid]
		comments = append(comments, tmpMap)
	}
	return
}

// FindObjectComments 获得某个对象的所有评论（新版）
// TODO:分页暂不做
func (self CommentLogic) FindObjectComments(ctx context.Context, objid, objtype int) (commentList []*model.Comment, err error) {
	objLog := GetLogger(ctx)

	commentList = make([]*model.Comment, 0)
	err = MasterDB.Where("objid=? AND objtype=?", objid, objtype).Asc("cid").Find(&commentList)
	if err != nil {
		objLog.Errorln("comment logic FindObjectComments Error:", err)
	}

	for _, comment := range commentList {
		self.decodeCmtContentForShow(ctx, comment, true)
	}

	return
}

// FindComment 获得评论和额外两个评论
func (self CommentLogic) FindComment(ctx context.Context, cid, objid, objtype int) (*model.Comment, []*model.Comment) {
	objLog := GetLogger(ctx)

	comment := &model.Comment{}
	_, err := MasterDB.Where("cid=?", cid).Get(comment)
	if err != nil {
		objLog.Errorln("CommentLogic FindComment error:", err)
		return comment, nil
	}
	self.decodeCmtContentForShow(ctx, comment, false)

	comments := make([]*model.Comment, 0)
	err = MasterDB.Where("objid=? AND objtype=? AND cid!=?", objid, objtype, cid).
		Limit(2).Find(&comments)
	if err != nil {
		objLog.Errorln("CommentLogic FindComment Find more error:", err)
		return comment, nil
	}
	for _, cmt := range comments {
		self.decodeCmtContentForShow(ctx, cmt, false)
	}

	return comment, comments
}

// Total 评论总数(objtypes[0] 取某一类型的评论总数)
func (CommentLogic) Total(objtypes ...int) int64 {
	var (
		total int64
		err   error
	)
	if len(objtypes) > 0 {
		total, err = MasterDB.Where("objtype=?", objtypes[0]).Count(new(model.Comment))
	} else {
		total, err = MasterDB.Count(new(model.Comment))
	}
	if err != nil {
		logger.Errorln("CommentLogic Total error:", err)
	}
	return total
}

// FindRecent 获得最近的评论
// 如果 uid!=0，表示获取某人的评论；
// 如果 objtype!=-1，表示获取某类型的评论；
func (self CommentLogic) FindRecent(ctx context.Context, uid, objtype, limit int) []*model.Comment {
	dbSession := MasterDB.OrderBy("cid DESC").Limit(limit)

	if uid != 0 {
		dbSession.And("uid=?", uid)
	}
	if objtype != -1 {
		dbSession.And("objtype=?", objtype)
	}

	comments := make([]*model.Comment, 0)
	err := dbSession.Find(&comments)
	if err != nil {
		logger.Errorln("CommentLogic FindRecent error:", err)
		return nil
	}

	cmtMap := make(map[int][]*model.Comment, len(model.PathUrlMap))
	for _, comment := range comments {
		self.decodeCmtContent(ctx, comment)

		if _, ok := cmtMap[comment.Objtype]; !ok {
			cmtMap[comment.Objtype] = make([]*model.Comment, 0, 10)
		}

		cmtMap[comment.Objtype] = append(cmtMap[comment.Objtype], comment)
	}

	cmtObjs := []CommentObjecter{
		model.TypeTopic:    TopicComment{},
		model.TypeArticle:  ArticleComment{},
		model.TypeResource: ResourceComment{},
		model.TypeWiki:     nil,
		model.TypeProject:  ProjectComment{},
		model.TypeBook:     BookComment{},
	}
	for cmtType, cmts := range cmtMap {
		self.fillObjinfos(cmts, cmtObjs[cmtType])
	}

	return comments
}

// Publish 发表评论（或回复）。
// objid 注册的评论对象
// uid 评论人
func (self CommentLogic) Publish(ctx context.Context, uid, objid int, form url.Values) (*model.Comment, error) {
	objLog := GetLogger(ctx)

	objtype := goutils.MustInt(form.Get("objtype"))
	comment := &model.Comment{
		Objid:   objid,
		Objtype: objtype,
		Uid:     uid,
		Content: form.Get("content"),
	}

	// TODO:评论楼层怎么处理，避免冲突？最后的楼层信息保存在内存中？

	// 暂时只是从数据库中取出最后的评论楼层
	tmpCmt := &model.Comment{}
	_, err := MasterDB.Where("objid=? AND objtype=?", objid, objtype).OrderBy("floor DESC").Get(tmpCmt)
	if err != nil {
		objLog.Errorln("post comment find last floor error:", err)
		return nil, err
	}

	comment.Floor = tmpCmt.Floor + 1

	if tmpCmt.Uid == comment.Uid && tmpCmt.Content == comment.Content {
		objLog.Infof("had post comment: %+v", *comment)
		return tmpCmt, nil
	}

	// 入评论库
	_, err = MasterDB.Insert(comment)
	if err != nil {
		objLog.Errorln("post comment service error:", err)
		return nil, err
	}
	self.decodeCmtContentForShow(ctx, comment, true)

	// 回调，不关心处理结果（有些对象可能不需要回调）
	if commenter, ok := commenters[objtype]; ok {
		now := time.Now()

		objLog.Debugf("评论[objid:%d] [objtype:%d] [uid:%d] 成功，通知被评论者更新", objid, objtype, uid)
		go commenter.UpdateComment(comment.Cid, objid, uid, now)

		DefaultFeed.updateComment(objid, objtype, uid, now)
	}

	go commentObservable.NotifyObservers(uid, objtype, comment.Cid)

	go self.sendSystemMsg(ctx, uid, objid, objtype, comment.Cid, form)

	return comment, nil
}

func (CommentLogic) sendSystemMsg(ctx context.Context, uid, objid, objtype, cid int, form url.Values) {
	// 给被评论对象所有者发系统消息 TODO: ext 考虑结构化
	ext := map[string]interface{}{
		"objid":   objid,
		"objtype": objtype,
		"cid":     cid,
		"uid":     uid,
	}

	to := 0
	switch objtype {
	case model.TypeTopic:
		to = DefaultTopic.getOwner(objid)
	case model.TypeArticle:
		to = DefaultArticle.getOwner(objid)
	case model.TypeResource:
		to = DefaultResource.getOwner(objid)
	case model.TypeWiki:
		to = DefaultWiki.getOwner(objid)
	case model.TypeProject:
		to = DefaultProject.getOwner(ctx, objid)
	}

	DefaultMessage.SendSystemMsgTo(ctx, to, objtype, ext)

	// @某人 发系统消息
	DefaultMessage.SendSysMsgAtUids(ctx, form.Get("uid"), ext, to)
	DefaultMessage.SendSysMsgAtUsernames(ctx, form.Get("usernames"), ext, to)
}

// Modify 修改评论信息
func (CommentLogic) Modify(ctx context.Context, cid int, content string) (errMsg string, err error) {
	objLog := GetLogger(ctx)

	_, err = MasterDB.Table(new(model.Comment)).Id(cid).Update(map[string]interface{}{"content": content})
	if err != nil {
		objLog.Errorf("更新评论内容 【%d】 失败：%s", cid, err)
		errMsg = "对不起，服务器内部错误，请稍后再试！"
		return
	}

	return
}

// fillObjinfos 填充评论对应的主体信息
func (CommentLogic) fillObjinfos(comments []*model.Comment, cmtObj CommentObjecter) {
	if len(comments) == 0 {
		return
	}
	count := len(comments)
	commentMap := make(map[int][]*model.Comment, count)
	idSet := set.New(set.NonThreadSafe)
	for _, comment := range comments {
		if _, ok := commentMap[comment.Objid]; !ok {
			commentMap[comment.Objid] = make([]*model.Comment, 0, count)
		}
		commentMap[comment.Objid] = append(commentMap[comment.Objid], comment)
		idSet.Add(comment.Objid)
	}
	cmtObj.SetObjinfo(set.IntSlice(idSet), commentMap)
}

// 提供给其他service调用（包内）
func (CommentLogic) findByIds(cids []int) map[int]*model.Comment {
	if len(cids) == 0 {
		return nil
	}

	comments := make(map[int]*model.Comment)
	err := MasterDB.In("cid", cids).Find(&comments)
	if err != nil {
		return nil
	}

	return comments
}

func (CommentLogic) findById(cid int) *model.Comment {
	comment := &model.Comment{}
	_, err := MasterDB.Where("cid=?", cid).Get(comment)
	if err != nil {
		logger.Errorln("CommentLogic findById error:", err)
	}

	return comment
}

func (CommentLogic) decodeCmtContent(ctx context.Context, comment *model.Comment) string {
	// 安全过滤
	content := template.HTMLEscapeString(comment.Content)
	// @别人
	content = parseAtUser(ctx, content)

	// 回复某一楼层
	reg := regexp.MustCompile(`#(\d+)楼`)
	url := fmt.Sprintf("%s%d#comment", model.PathUrlMap[comment.Objtype], comment.Objid)
	content = reg.ReplaceAllString(content, `<a href="`+url+`$1" title="$1">#$1<span>楼</span></a>`)

	comment.Content = content

	return content
}

// decodeCmtContentForShow 采用引用的方式显示对其他楼层的回复
func (CommentLogic) decodeCmtContentForShow(ctx context.Context, comment *model.Comment, isEscape bool) {
	// 安全过滤
	content := template.HTMLEscapeString(comment.Content)

	// 回复某一楼层
	reg := regexp.MustCompile(`#(\d+)楼 @([a-zA-Z0-9_]+)`)
	matches := reg.FindStringSubmatch(content)
	if len(matches) > 2 {
		comment.ReplyFloor = goutils.MustInt(matches[1])
		content = strings.TrimSpace(content[len(matches[0]):])
	}

	// @别人
	content = parseAtUser(ctx, content)

	comment.Content = content
}

// 填充 Comment 对象的 Objinfo 成员接口
// 评论属主应该实现该接口（以便填充 Objinfo 成员）
type CommentObjecter interface {
	// ids 是属主的主键 slice （comment 中的 objid）
	// commentMap 中的 key 是属主 id
	SetObjinfo(ids []int, commentMap map[int][]*model.Comment)
}

// FindAll 支持多页翻看
func (self CommentLogic) FindAll(ctx context.Context, paginator *Paginator, orderBy string, querystring string, args ...interface{}) []*model.Comment {
	objLog := GetLogger(ctx)

	comments := make([]*model.Comment, 0)
	session := MasterDB.OrderBy(orderBy)
	if querystring != "" {
		session.Where(querystring, args...)
	}
	err := session.Limit(paginator.PerPage(), paginator.Offset()).Find(&comments)
	if err != nil {
		objLog.Errorln("CommentLogical FindAll error:", err)
		return nil
	}

	cmtMap := make(map[int][]*model.Comment, len(model.PathUrlMap))
	for _, comment := range comments {
		self.decodeCmtContent(ctx, comment)
		if _, ok := cmtMap[comment.Objtype]; !ok {
			cmtMap[comment.Objtype] = make([]*model.Comment, 0, 10)
		}

		cmtMap[comment.Objtype] = append(cmtMap[comment.Objtype], comment)
	}

	cmtObjs := []CommentObjecter{
		model.TypeTopic:    TopicComment{},
		model.TypeArticle:  ArticleComment{},
		model.TypeResource: ResourceComment{},
		model.TypeWiki:     nil,
		model.TypeProject:  ProjectComment{},
		model.TypeBook:     BookComment{},
	}
	for cmtType, cmts := range cmtMap {
		self.fillObjinfos(cmts, cmtObjs[cmtType])
	}
	return comments
}

// Count 获取用户全部评论数
func (CommentLogic) Count(ctx context.Context, querystring string, args ...interface{}) int64 {
	objLog := GetLogger(ctx)

	var (
		total int64
		err   error
	)
	if querystring == "" {
		total, err = MasterDB.Count(new(model.Comment))
	} else {
		total, err = MasterDB.Where(querystring, args...).Count(new(model.Comment))
	}

	if err != nil {
		objLog.Errorln("CommentLogic Count error:", err)
	}

	return total
}
<commit_msg>解决评论回复内容有多余字符<commit_after>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	"fmt"
	"html/template"
	"model"
	"net/url"
	"regexp"
	"strings"
	"time"

	. "db"

	"github.com/fatih/structs"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/logger"
	"github.com/polaris1119/set"
	"github.com/polaris1119/slices"
	"golang.org/x/net/context"
)

type CommentLogic struct{}

var DefaultComment = CommentLogic{}

// FindObjComments 获得某个对象的所有评论
// owner: 被评论对象属主
// TODO:分页暂不做
func (self CommentLogic) FindObjComments(ctx context.Context, objid, objtype int, owner, lastCommentUid int /*, page, pageNum int*/) (comments []map[string]interface{}, ownerUser, lastReplyUser *model.User) {
	objLog := GetLogger(ctx)

	commentList := make([]*model.Comment, 0)
	err := MasterDB.Where("objid=? AND objtype=?", objid, objtype).Find(&commentList)
	if err != nil {
		objLog.Errorln("CommentLogic FindObjComments Error:", err)
		return
	}

	uids := slices.StructsIntSlice(commentList, "Uid")

	// 避免某些情况下最后回复人没在回复列表中
	uids = append(uids, owner, lastCommentUid)

	// 获得用户信息
	userMap := DefaultUser.FindUserInfos(ctx, uids)
	ownerUser = userMap[owner]
	if lastCommentUid != 0 {
		lastReplyUser = userMap[lastCommentUid]
	}
	comments = make([]map[string]interface{}, 0, len(commentList))
	for _, comment := range commentList {
		tmpMap := structs.Map(comment)
		tmpMap["content"] = template.HTML(self.decodeCmtContent(ctx, comment))
		tmpMap["user"] = userMap[comment.Uid]
		comments = append(comments, tmpMap)
	}
	return
}

// FindObjectComments 获得某个对象的所有评论（新版）
// TODO:分页暂不做
func (self CommentLogic) FindObjectComments(ctx context.Context, objid, objtype int) (commentList []*model.Comment, err error) {
	objLog := GetLogger(ctx)

	commentList = make([]*model.Comment, 0)
	err = MasterDB.Where("objid=? AND objtype=?", objid, objtype).Asc("cid").Find(&commentList)
	if err != nil {
		objLog.Errorln("comment logic FindObjectComments Error:", err)
	}

	for _, comment := range commentList {
		self.decodeCmtContentForShow(ctx, comment, true)
	}

	return
}

// FindComment 获得评论和额外两个评论
func (self CommentLogic) FindComment(ctx context.Context, cid, objid, objtype int) (*model.Comment, []*model.Comment) {
	objLog := GetLogger(ctx)

	comment := &model.Comment{}
	_, err := MasterDB.Where("cid=?", cid).Get(comment)
	if err != nil {
		objLog.Errorln("CommentLogic FindComment error:", err)
		return comment, nil
	}
	self.decodeCmtContentForShow(ctx, comment, false)

	comments := make([]*model.Comment, 0)
	err = MasterDB.Where("objid=? AND objtype=? AND cid!=?", objid, objtype, cid).
		Limit(2).Find(&comments)
	if err != nil {
		objLog.Errorln("CommentLogic FindComment Find more error:", err)
		return comment, nil
	}
	for _, cmt := range comments {
		self.decodeCmtContentForShow(ctx, cmt, false)
	}

	return comment, comments
}

// Total 评论总数(objtypes[0] 取某一类型的评论总数)
func (CommentLogic) Total(objtypes ...int) int64 {
	var (
		total int64
		err   error
	)
	if len(objtypes) > 0 {
		total, err = MasterDB.Where("objtype=?", objtypes[0]).Count(new(model.Comment))
	} else {
		total, err = MasterDB.Count(new(model.Comment))
	}
	if err != nil {
		logger.Errorln("CommentLogic Total error:", err)
	}
	return total
}

// FindRecent 获得最近的评论
// 如果 uid!=0，表示获取某人的评论；
// 如果 objtype!=-1，表示获取某类型的评论；
func (self CommentLogic) FindRecent(ctx context.Context, uid, objtype, limit int) []*model.Comment {
	dbSession := MasterDB.OrderBy("cid DESC").Limit(limit)

	if uid != 0 {
		dbSession.And("uid=?", uid)
	}
	if objtype != -1 {
		dbSession.And("objtype=?", objtype)
	}

	comments := make([]*model.Comment, 0)
	err := dbSession.Find(&comments)
	if err != nil {
		logger.Errorln("CommentLogic FindRecent error:", err)
		return nil
	}

	cmtMap := make(map[int][]*model.Comment, len(model.PathUrlMap))
	for _, comment := range comments {
		self.decodeCmtContent(ctx, comment)

		if _, ok := cmtMap[comment.Objtype]; !ok {
			cmtMap[comment.Objtype] = make([]*model.Comment, 0, 10)
		}

		cmtMap[comment.Objtype] = append(cmtMap[comment.Objtype], comment)
	}

	cmtObjs := []CommentObjecter{
		model.TypeTopic:    TopicComment{},
		model.TypeArticle:  ArticleComment{},
		model.TypeResource: ResourceComment{},
		model.TypeWiki:     nil,
		model.TypeProject:  ProjectComment{},
		model.TypeBook:     BookComment{},
	}
	for cmtType, cmts := range cmtMap {
		self.fillObjinfos(cmts, cmtObjs[cmtType])
	}

	return comments
}

// Publish 发表评论（或回复）。
// objid 注册的评论对象
// uid 评论人
func (self CommentLogic) Publish(ctx context.Context, uid, objid int, form url.Values) (*model.Comment, error) {
	objLog := GetLogger(ctx)

	objtype := goutils.MustInt(form.Get("objtype"))
	comment := &model.Comment{
		Objid:   objid,
		Objtype: objtype,
		Uid:     uid,
		Content: form.Get("content"),
	}

	// TODO:评论楼层怎么处理，避免冲突？最后的楼层信息保存在内存中？

	// 暂时只是从数据库中取出最后的评论楼层
	tmpCmt := &model.Comment{}
	_, err := MasterDB.Where("objid=? AND objtype=?", objid, objtype).OrderBy("floor DESC").Get(tmpCmt)
	if err != nil {
		objLog.Errorln("post comment find last floor error:", err)
		return nil, err
	}

	comment.Floor = tmpCmt.Floor + 1

	if tmpCmt.Uid == comment.Uid && tmpCmt.Content == comment.Content {
		objLog.Infof("had post comment: %+v", *comment)
		return tmpCmt, nil
	}

	// 入评论库
	_, err = MasterDB.Insert(comment)
	if err != nil {
		objLog.Errorln("post comment service error:", err)
		return nil, err
	}
	self.decodeCmtContentForShow(ctx, comment, true)

	// 回调，不关心处理结果（有些对象可能不需要回调）
	if commenter, ok := commenters[objtype]; ok {
		now := time.Now()

		objLog.Debugf("评论[objid:%d] [objtype:%d] [uid:%d] 成功，通知被评论者更新", objid, objtype, uid)
		go commenter.UpdateComment(comment.Cid, objid, uid, now)

		DefaultFeed.updateComment(objid, objtype, uid, now)
	}

	go commentObservable.NotifyObservers(uid, objtype, comment.Cid)

	go self.sendSystemMsg(ctx, uid, objid, objtype, comment.Cid, form)

	return comment, nil
}

func (CommentLogic) sendSystemMsg(ctx context.Context, uid, objid, objtype, cid int, form url.Values) {
	// 给被评论对象所有者发系统消息 TODO: ext 考虑结构化
	ext := map[string]interface{}{
		"objid":   objid,
		"objtype": objtype,
		"cid":     cid,
		"uid":     uid,
	}

	to := 0
	switch objtype {
	case model.TypeTopic:
		to = DefaultTopic.getOwner(objid)
	case model.TypeArticle:
		to = DefaultArticle.getOwner(objid)
	case model.TypeResource:
		to = DefaultResource.getOwner(objid)
	case model.TypeWiki:
		to = DefaultWiki.getOwner(objid)
	case model.TypeProject:
		to = DefaultProject.getOwner(ctx, objid)
	}

	DefaultMessage.SendSystemMsgTo(ctx, to, objtype, ext)

	// @某人 发系统消息
	DefaultMessage.SendSysMsgAtUids(ctx, form.Get("uid"), ext, to)
	DefaultMessage.SendSysMsgAtUsernames(ctx, form.Get("usernames"), ext, to)
}

// Modify 修改评论信息
func (CommentLogic) Modify(ctx context.Context, cid int, content string) (errMsg string, err error) {
	objLog := GetLogger(ctx)

	_, err = MasterDB.Table(new(model.Comment)).Id(cid).Update(map[string]interface{}{"content": content})
	if err != nil {
		objLog.Errorf("更新评论内容 【%d】 失败：%s", cid, err)
		errMsg = "对不起，服务器内部错误，请稍后再试！"
		return
	}

	return
}

// fillObjinfos 填充评论对应的主体信息
func (CommentLogic) fillObjinfos(comments []*model.Comment, cmtObj CommentObjecter) {
	if len(comments) == 0 {
		return
	}
	count := len(comments)
	commentMap := make(map[int][]*model.Comment, count)
	idSet := set.New(set.NonThreadSafe)
	for _, comment := range comments {
		if _, ok := commentMap[comment.Objid]; !ok {
			commentMap[comment.Objid] = make([]*model.Comment, 0, count)
		}
		commentMap[comment.Objid] = append(commentMap[comment.Objid], comment)
		idSet.Add(comment.Objid)
	}
	cmtObj.SetObjinfo(set.IntSlice(idSet), commentMap)
}

// 提供给其他service调用（包内）
func (CommentLogic) findByIds(cids []int) map[int]*model.Comment {
	if len(cids) == 0 {
		return nil
	}

	comments := make(map[int]*model.Comment)
	err := MasterDB.In("cid", cids).Find(&comments)
	if err != nil {
		return nil
	}

	return comments
}

func (CommentLogic) findById(cid int) *model.Comment {
	comment := &model.Comment{}
	_, err := MasterDB.Where("cid=?", cid).Get(comment)
	if err != nil {
		logger.Errorln("CommentLogic findById error:", err)
	}

	return comment
}

func (CommentLogic) decodeCmtContent(ctx context.Context, comment *model.Comment) string {
	// 安全过滤
	content := template.HTMLEscapeString(comment.Content)
	// @别人
	content = parseAtUser(ctx, content)

	// 回复某一楼层
	reg := regexp.MustCompile(`#(\d+)楼`)
	url := fmt.Sprintf("%s%d#comment", model.PathUrlMap[comment.Objtype], comment.Objid)
	content = reg.ReplaceAllString(content, `<a href="`+url+`$1" title="$1">#$1<span>楼</span></a>`)

	comment.Content = content

	return content
}

// decodeCmtContentForShow 采用引用的方式显示对其他楼层的回复
func (CommentLogic) decodeCmtContentForShow(ctx context.Context, comment *model.Comment, isEscape bool) {
	// 安全过滤
	content := template.HTMLEscapeString(comment.Content)

	// 回复某一楼层
	reg := regexp.MustCompile(`#(\d+)楼 @([a-zA-Z0-9_-]+)`)
	matches := reg.FindStringSubmatch(content)
	if len(matches) > 2 {
		comment.ReplyFloor = goutils.MustInt(matches[1])
		content = strings.TrimSpace(content[len(matches[0]):])
	}

	// @别人
	content = parseAtUser(ctx, content)

	comment.Content = content
}

// 填充 Comment 对象的 Objinfo 成员接口
// 评论属主应该实现该接口（以便填充 Objinfo 成员）
type CommentObjecter interface {
	// ids 是属主的主键 slice （comment 中的 objid）
	// commentMap 中的 key 是属主 id
	SetObjinfo(ids []int, commentMap map[int][]*model.Comment)
}

// FindAll 支持多页翻看
func (self CommentLogic) FindAll(ctx context.Context, paginator *Paginator, orderBy string, querystring string, args ...interface{}) []*model.Comment {
	objLog := GetLogger(ctx)

	comments := make([]*model.Comment, 0)
	session := MasterDB.OrderBy(orderBy)
	if querystring != "" {
		session.Where(querystring, args...)
	}
	err := session.Limit(paginator.PerPage(), paginator.Offset()).Find(&comments)
	if err != nil {
		objLog.Errorln("CommentLogical FindAll error:", err)
		return nil
	}

	cmtMap := make(map[int][]*model.Comment, len(model.PathUrlMap))
	for _, comment := range comments {
		self.decodeCmtContent(ctx, comment)
		if _, ok := cmtMap[comment.Objtype]; !ok {
			cmtMap[comment.Objtype] = make([]*model.Comment, 0, 10)
		}

		cmtMap[comment.Objtype] = append(cmtMap[comment.Objtype], comment)
	}

	cmtObjs := []CommentObjecter{
		model.TypeTopic:    TopicComment{},
		model.TypeArticle:  ArticleComment{},
		model.TypeResource: ResourceComment{},
		model.TypeWiki:     nil,
		model.TypeProject:  ProjectComment{},
		model.TypeBook:     BookComment{},
	}
	for cmtType, cmts := range cmtMap {
		self.fillObjinfos(cmts, cmtObjs[cmtType])
	}
	return comments
}

// Count 获取用户全部评论数
func (CommentLogic) Count(ctx context.Context, querystring string, args ...interface{}) int64 {
	objLog := GetLogger(ctx)

	var (
		total int64
		err   error
	)
	if querystring == "" {
		total, err = MasterDB.Count(new(model.Comment))
	} else {
		total, err = MasterDB.Where(querystring, args...).Count(new(model.Comment))
	}

	if err != nil {
		objLog.Errorln("CommentLogic Count error:", err)
	}

	return total
}
<|endoftext|>


<commit_before>package main

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

	"github.com/tpryan/deploystack"
)

var lineLength = 80

func main() {
	err := printFile()
	if err != nil {
		log.Fatalf("err %s", err)
	}
}

func printFile() error {
	f, err := os.Create("output.txt")
	if err != nil {
		return err
	}

	deploystack.Divider, err = deploystack.BuildDivider(lineLength)
	if err != nil {
		log.Fatal(err)
	}

	w := bufio.NewWriter(f)

	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANB, deploystack.Divider, deploystack.TERMCLEAR)
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANB, padString("Deploystack INSTALL", lineLength), deploystack.TERMCLEAR)
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANB, deploystack.Divider, deploystack.TERMCLEAR)
	fmt.Fprintf(w, "\n")
	fmt.Fprintf(w, "%s\n", "To continue, run this command at the command line below.")
	fmt.Fprintf(w, "%s\n", "This process should walk you through setting up and deploying")
	fmt.Fprintf(w, "%s\n", "this deploystack application.")
	fmt.Fprintf(w, "\n")
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANREV, deploystack.Divider, deploystack.TERMCLEAR)
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANREV, padString("./deploystack install", lineLength), deploystack.TERMCLEAR)
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANREV, deploystack.Divider, deploystack.TERMCLEAR)

	w.Flush()

	return nil
}

func padString(s string, linelength int) string {
	width := linelength - len(s)

	var sb strings.Builder
	sb.WriteString(s)

	for i := 0; i < width; i++ {
		sb.WriteString(" ")
	}

	return sb.String()
}
<commit_msg>updating to use the new oss path.<commit_after>package main

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

	"github.com/GoogleCloudPlatform/deploystack"
)

var lineLength = 80

func main() {
	err := printFile()
	if err != nil {
		log.Fatalf("err %s", err)
	}
}

func printFile() error {
	f, err := os.Create("output.txt")
	if err != nil {
		return err
	}

	deploystack.Divider, err = deploystack.BuildDivider(lineLength)
	if err != nil {
		log.Fatal(err)
	}

	w := bufio.NewWriter(f)

	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANB, deploystack.Divider, deploystack.TERMCLEAR)
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANB, padString("Deploystack INSTALL", lineLength), deploystack.TERMCLEAR)
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANB, deploystack.Divider, deploystack.TERMCLEAR)
	fmt.Fprintf(w, "\n")
	fmt.Fprintf(w, "%s\n", "To continue, run this command at the command line below.")
	fmt.Fprintf(w, "%s\n", "This process should walk you through setting up and deploying")
	fmt.Fprintf(w, "%s\n", "this deploystack application.")
	fmt.Fprintf(w, "\n")
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANREV, deploystack.Divider, deploystack.TERMCLEAR)
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANREV, padString("./deploystack install", lineLength), deploystack.TERMCLEAR)
	fmt.Fprintf(w, "%s%s%s\n", deploystack.TERMCYANREV, deploystack.Divider, deploystack.TERMCLEAR)

	w.Flush()

	return nil
}

func padString(s string, linelength int) string {
	width := linelength - len(s)

	var sb strings.Builder
	sb.WriteString(s)

	for i := 0; i < width; i++ {
		sb.WriteString(" ")
	}

	return sb.String()
}
<|endoftext|>


<commit_before>package raft

import (
  "time"
  "math/rand"
)

// Wrapper around time.Timer
type Timer struct {
  // Where the callback will be stored
  Callback func()

  // Store the Timer object
  // Each time Start() will be called, it will
  // create a new Timer.
  // Each time Stop() will be called, it will
  // set timer.Timer to nil to prevent reusing the Timer.
  Timer *time.Timer

  // Tell the Timer he can reset itself, the callback has been executed
  ResetChan chan struct{}

  // Break the for select loop to destroy the goroutine
  StopChan chan struct{}

  // The timer will be randomized base on Min and Max (in milliSecond)
  Min int
  Max int
}

// Basic function that create randomized between two ints
func Random(min, max int) int {
  // TODO: add more entropy ?
  return rand.Intn(max - min) + min
}

// Start a timer with the stored configuration in *Timer
func (t *Timer) Start() bool {
  // If a timer is already stored, do not run
  if t.Timer != nil {
    return false
  }

  sleepFor := time.Duration(Random(t.Min, t.Max)) * time.Millisecond
  t.Timer = time.AfterFunc(sleepFor, t.Callback)

  // Launch the goroutine which will handle the reset of the timer.
  // When the duration of AfterFunc will elapse, the callback will
  // trigger. After the callback will be executed, a struct{}{} will
  // be send to t.ResetChan, which will be catched by the following
  // goroutine. Ultimatly, the timer will reset, and everything will
  // be executed again until a struct{}{} will be sent to StopChan.
  go func() {
    LOOP: for {
      select {
      case <- t.ResetChan:
        sleepFor := time.Duration(Random(t.Min, t.Max)) * time.Millisecond
        t.Timer.Reset(sleepFor)
      case <- t.StopChan:
        break LOOP
      }
    }
  }()

  return true
}

// Stop the timer, the goroutine will be destroyed and
// timer stopped. The timer object will also be destroyed.
// No callback can be executed after Stop().
// If you want to restart the timer you'll have to:
// if stopped := timer.Stop(); stopped {
//   timer.Start()
// }
// To start the timer all over again
func (t *Timer) Stop() bool {
  t.StopChan <- struct{}{}
  stopped := t.Timer.Stop()

  t.Timer = nil

  return stopped
}

// Create a newTimer
func NewTimer(min, max int, cb func()) *Timer {
  timer := &Timer{
    Timer: nil,
    ResetChan: make(chan struct{}),
    StopChan: make(chan struct{}),
    Min: min,
    Max: max,
  }

  timer.Callback = func() {
    cb()
    timer.ResetChan <- struct{}{}
  }

  return timer
}
<commit_msg>Added a buffer channel to protect the internal values<commit_after>package raft

import (
  "time"
  "math/rand"
)

// Wrapper around time.Timer
type Timer struct {
  // Where the callback will be stored
  Callback func()

  // Store the Timer object
  // Each time Start() will be called, it will
  // create a new Timer.
  // Each time Stop() will be called, it will
  // set timer.Timer to nil to prevent reusing the Timer.
  Timer *time.Timer

  // Tell the Timer he can reset itself, the callback has been executed
  ResetChan chan struct{}

  // Break the for select loop to destroy the goroutine
  StopChan chan struct{}

  // The timer will be randomized base on Min and Max (in milliSecond)
  Min int
  Max int

  // Sync
  C chan struct{}
}

// Basic function that create randomized between two ints
func Random(min, max int) int {
  // TODO: add more entropy ?
  return rand.Intn(max - min) + min
}

// Start a timer with the stored configuration in *Timer
func (t *Timer) Start() bool {
  t.C <- struct{}{}
  defer func() {
    <- t.C
  }()

  // If a timer is already stored, do not run
  if t.Timer != nil {
    return false
  }

  sleepFor := time.Duration(Random(t.Min, t.Max)) * time.Millisecond
  t.Timer = time.AfterFunc(sleepFor, t.Callback)

  // Launch the goroutine which will handle the reset of the timer.
  // When the duration of AfterFunc will elapse, the callback will
  // trigger. After the callback will be executed, a struct{}{} will
  // be send to t.ResetChan, which will be catched by the following
  // goroutine. Ultimatly, the timer will reset, and everything will
  // be executed again until a struct{}{} will be sent to StopChan.
  go func() {
    LOOP: for {
      select {
      case <- t.ResetChan:
        sleepFor := time.Duration(Random(t.Min, t.Max)) * time.Millisecond
        t.Timer.Reset(sleepFor)
      case <- t.StopChan:
        break LOOP
      }
    }
  }()

  return true
}

// Stop the timer, the goroutine will be destroyed and
// timer stopped. The timer object will also be destroyed.
// No callback can be executed after Stop().
// If you want to restart the timer you'll have to:
// if stopped := timer.Stop(); stopped {
//   timer.Start()
// }
// To start the timer all over again
func (t *Timer) Stop() bool {
  t.C <- struct{}{}
  defer func() {
    <- t.C
  }()

  t.StopChan <- struct{}{}
  stopped := t.Timer.Stop()

  t.Timer = nil

  return stopped
}

// Create a newTimer
func NewTimer(min, max int, cb func()) *Timer {
  timer := &Timer{
    Timer: nil,
    ResetChan: make(chan struct{}),
    StopChan: make(chan struct{}),
    C: make(chan struct{}, 1),
    Min: min,
    Max: max,
  }

  timer.Callback = func() {
    cb()
    timer.ResetChan <- struct{}{}
  }

  return timer
}
<|endoftext|>


<commit_before>package tokenauth

import (
	"crypto/md5"
	"crypto/rand"
	"crypto/subtle"
	"encoding/base64"
	"errors"
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"strings"
)

var tokenEntropy = 32

// AuthTokenMiddleware provides a Token Auth implementation. On success, the wrapped middleware
// is called, and the userID is made available as request.Env["REMOTE_USER"].(string)
type AuthTokenMiddleware struct {
	// Realm name to display to the user. Required.
	Realm string

	// Callback function that should perform the authentication of the user based on token.
	// Must return userID as string on success, empty string on failure. Required.
	// The returned userID is normally the primary key for your user record.
	Authenticator func(token string) string

	// Callback function that should perform the authorization of the authenticated user.
	// Must return true on success, false on failure. Optional, defaults to success.
	// Called only after an authentication success.
	Authorizer func(request *rest.Request) bool
}

// MiddlewareFunc makes AuthTokenMiddleware implement the Middleware interface.
func (mw *AuthTokenMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.HandlerFunc {
	if mw.Realm == "" {
		log.Fatal("Realm is required")
	}

	if mw.Authenticator == nil {
		log.Fatal("Authenticator is required")
	}

	if mw.Authorizer == nil {
		mw.Authorizer = func(request *rest.Request) bool {
			return true
		}
	}

	return func(writer rest.ResponseWriter, request *rest.Request) {
		authHeader := request.Header.Get("Authorization")
		// Authorization header was not provided
		if authHeader == "" {
			mw.unauthorized(writer)
			return
		}

		token, err := mw.decodeAuthHeader(authHeader)
		// Authorization header was *malformed* such that we couldn't extract a token
		if err != nil {
			mw.unauthorized(writer)
			return
		}

		userID := mw.Authenticator(token)
		// The token didn't map to a user, it's most likely either invalid or expired
		if userID == "" {
			mw.unauthorized(writer)
			return
		}

		// The user's token was valid, but they're not authorized for the current request
		if !mw.Authorizer(request) {
			mw.unauthorized(writer)
			return
		}

		request.Env["REMOTE_USER"] = userID
		handler(writer, request)
	}
}

// Extract the token from the current request.
func (mw *AuthTokenMiddleware) decodeAuthHeader(header string) (string, error) {
	parts := strings.SplitN(header, " ", 2)
	if !(len(parts) == 2 && parts[0] == "Token") {
		return "", errors.New("Invalid authentication")
	}
	token, err := base64.URLEncoding.DecodeString(parts[1])
	if err != nil {
		return "", errors.New("Invalid base64")
	}
	return string(token), nil
}

func (mw *AuthTokenMiddleware) unauthorized(writer rest.ResponseWriter) {
	writer.Header().Set("WWW-Authenticate", "Token realm="+mw.Realm)
	rest.Error(writer, "Not Authorized", http.StatusUnauthorized)
}

// New generates a new random token
func New() (string, error) {
	bytes := make([]byte, tokenEntropy)
	_, err := rand.Read(bytes[:cap(bytes)])
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(bytes), nil
}

// Equal does constant-time XOR comparison of two tokens
func Equal(a, b string) bool {
	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

// Hash applies a simple MD5 hash over a token, making it safe to store
func Hash(token string) string {
	hashed := md5.Sum([]byte(token))
	return base64.URLEncoding.EncodeToString(hashed[:])
}
<commit_msg>Base64 decode of token for sanity check only<commit_after>package tokenauth

import (
	"crypto/md5"
	"crypto/rand"
	"crypto/subtle"
	"encoding/base64"
	"errors"
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"strings"
)

var tokenEntropy = 32

// AuthTokenMiddleware provides a Token Auth implementation. On success, the wrapped middleware
// is called, and the userID is made available as request.Env["REMOTE_USER"].(string)
type AuthTokenMiddleware struct {
	// Realm name to display to the user. Required.
	Realm string

	// Callback function that should perform the authentication of the user based on token.
	// Must return userID as string on success, empty string on failure. Required.
	// The returned userID is normally the primary key for your user record.
	Authenticator func(token string) string

	// Callback function that should perform the authorization of the authenticated user.
	// Must return true on success, false on failure. Optional, defaults to success.
	// Called only after an authentication success.
	Authorizer func(request *rest.Request) bool
}

// MiddlewareFunc makes AuthTokenMiddleware implement the Middleware interface.
func (mw *AuthTokenMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.HandlerFunc {
	if mw.Realm == "" {
		log.Fatal("Realm is required")
	}

	if mw.Authenticator == nil {
		log.Fatal("Authenticator is required")
	}

	if mw.Authorizer == nil {
		mw.Authorizer = func(request *rest.Request) bool {
			return true
		}
	}

	return func(writer rest.ResponseWriter, request *rest.Request) {
		authHeader := request.Header.Get("Authorization")
		// Authorization header was not provided
		if authHeader == "" {
			mw.unauthorized(writer)
			return
		}

		token, err := mw.decodeAuthHeader(authHeader)
		// Authorization header was *malformed* such that we couldn't extract a token
		if err != nil {
			mw.unauthorized(writer)
			return
		}

		userID := mw.Authenticator(token)
		// The token didn't map to a user, it's most likely either invalid or expired
		if userID == "" {
			mw.unauthorized(writer)
			return
		}

		// The user's token was valid, but they're not authorized for the current request
		if !mw.Authorizer(request) {
			mw.unauthorized(writer)
			return
		}

		request.Env["REMOTE_USER"] = userID
		handler(writer, request)
	}
}

// Extract the token from the current request.
func (mw *AuthTokenMiddleware) decodeAuthHeader(header string) (string, error) {
	parts := strings.SplitN(header, " ", 2)
	if !(len(parts) == 2 && parts[0] == "Token") {
		return "", errors.New("Invalid authentication")
	}
	_, err := base64.URLEncoding.DecodeString(parts[1])
	if err != nil {
		return "", errors.New("Invalid base64")
	}
	return string(parts[1]), nil
}

func (mw *AuthTokenMiddleware) unauthorized(writer rest.ResponseWriter) {
	writer.Header().Set("WWW-Authenticate", "Token realm="+mw.Realm)
	rest.Error(writer, "Not Authorized", http.StatusUnauthorized)
}

// New generates a new random token
func New() (string, error) {
	bytes := make([]byte, tokenEntropy)
	_, err := rand.Read(bytes[:cap(bytes)])
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(bytes), nil
}

// Equal does constant-time XOR comparison of two tokens
func Equal(a, b string) bool {
	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

// Hash applies a simple MD5 hash over a token, making it safe to store
func Hash(token string) string {
	hashed := md5.Sum([]byte(token))
	return base64.URLEncoding.EncodeToString(hashed[:])
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/template"
)

var (
	importName = "__log"
	importPath = `"github.com/jbardin/gotrace/log"`

	importStmt = `
import __log "github.com/jbardin/gotrace/log"
`

	setup = `
var _ = __log.Setup("stderr", "%s")
`

	tmpl = `
__traceID := __log.Next()
__log.L.Printf("[%d] {{.fname}}({{.formatters}}) \n", __traceID, {{.args}})
{{ if .return }}defer func() {
	__log.L.Printf("[%d] {{.fname}} returned\n", __traceID)
}(){{ end }}
`
)

var (
	funcTemplate *template.Template
	showReturn   bool
	exportedOnly bool
	prefix       string
	showPackage  bool
	writeFiles   bool
)

// return n '%v's for formatting
func formatters(n int) string {
	f := []string{}
	for i := 0; i < n; i++ {
		f = append(f, "%v")
	}
	return strings.Join(f, ", ")
}

// convert function parameters to a list of names
func paramNames(params *ast.FieldList) []string {
	var p []string
	for _, f := range params.List {
		for _, n := range f.Names {
			p = append(p, n.Name)
		}
	}
	return p
}

func debugCall(fName string, args ...string) []byte {
	vals := make(map[string]string)
	if len(args) > 0 {
		vals["formatters"] = formatters(len(args))
		vals["args"] = strings.Join(args, ", ")
	} else {
		vals["formatters"] = ""
		vals["args"] = ""
	}

	vals["fname"] = fName

	if showReturn {
		vals["return"] = "true"
	}

	var b bytes.Buffer
	err := funcTemplate.Execute(&b, vals)
	if err != nil {
		log.Fatal(err)
	}
	return b.Bytes()
}

type edit struct {
	pos int
	val []byte
}

type editList struct {
	edits       []edit
	packageName string
}

func (e *editList) Add(pos int, val []byte) {
	e.edits = append(e.edits, edit{pos: pos, val: val})
}

func (e *editList) inspect(node ast.Node) bool {
	if node == nil {
		return false
	}

	var funcType *ast.FuncType
	var body *ast.BlockStmt
	var funcName string

	switch n := node.(type) {
	case *ast.FuncDecl:
		body = n.Body
		if body == nil {
			return true
		}
		funcType = n.Type
		funcName = n.Name.Name

	case *ast.FuncLit:
		body = n.Body
		funcType = n.Type
		funcName = "func"

	default:
		return true
	}

	if exportedOnly && !ast.IsExported(funcName) {
		return true
	}

	if showPackage {
		funcName = e.packageName + "." + funcName
	}

	e.Add(int(body.Lbrace), debugCall(funcName, paramNames(funcType.Params)...))

	return true
}

func annotate(file string) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
	if err != nil {
		log.Fatal(err)
	}

	for _, imp := range f.Imports {
		if imp.Name != nil && imp.Name.Name == importName {
			log.Printf(`"%s" already imported. skipping %s`, importName, file)
			return
		}
		if imp.Path.Value == importPath {
			log.Printf(`"%s", already imported. skipping %s`, importPath, file)
			return
		}
	}

	edits := editList{packageName: f.Name.Name}

	// insert our import directly after the package line
	edits.Add(int(f.Name.End()), []byte(importStmt))

	ast.Inspect(f, edits.inspect)

	var buf bytes.Buffer
	if err := format.Node(&buf, fset, f); err != nil {
		log.Fatal(err)
	}

	data := buf.Bytes()

	var pos int
	var out []byte
	for _, e := range edits.edits {
		out = append(out, data[pos:e.pos]...)
		out = append(out, []byte(e.val)...)
		pos = e.pos
	}
	out = append(out, data[pos:]...)

	// it's easier to append the setup code at the end
	out = append(out, []byte(setup)...)

	src, err := format.Source(out)
	if err != nil {
		log.Fatal(err)
	}

	if !writeFiles {
		fmt.Println(string(src))
		return
	}

	err = ioutil.WriteFile(file, src, 0)
	if err != nil {
		log.Fatal(err)
	}
}

func init() {
	funcTemplate = template.Must(template.New("debug").Parse(tmpl))
}

func main() {
	flag.BoolVar(&showReturn, "returns", false, "show function return")
	flag.BoolVar(&exportedOnly, "exported", false, "only annotate exported functions")
	flag.StringVar(&prefix, "prefix", "\t", "log prefix")
	flag.BoolVar(&showPackage, "package", false, "show package name prefix on function calls")
	flag.BoolVar(&writeFiles, "w", false, "re-write files in place")
	flag.Parse()

	if flag.NArg() < 1 {
		flag.PrintDefaults()
		os.Exit(1)
	}

	setup = fmt.Sprintf(setup, prefix)

	for _, file := range flag.Args() {
		annotate(file)
	}
}
<commit_msg>use %#v for formatting<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/template"
)

var (
	importName = "__log"
	importPath = `"github.com/jbardin/gotrace/log"`

	importStmt = `
import __log "github.com/jbardin/gotrace/log"
`

	setup = `
var _ = __log.Setup("stderr", "%s")
`

	tmpl = `
__traceID := __log.Next()
__log.L.Printf("[%d] {{.fname}}({{.formatters}}) \n", __traceID, {{.args}})
{{ if .return }}defer func() {
	__log.L.Printf("[%d] {{.fname}} returned\n", __traceID)
}(){{ end }}
`
)

var (
	funcTemplate *template.Template
	showReturn   bool
	exportedOnly bool
	prefix       string
	showPackage  bool
	writeFiles   bool
)

// return n '%#v's for formatting
func formatters(n int) string {
	f := []string{}
	for i := 0; i < n; i++ {
		f = append(f, "%#v")
	}
	return strings.Join(f, ", ")
}

// convert function parameters to a list of names
func paramNames(params *ast.FieldList) []string {
	var p []string
	for _, f := range params.List {
		for _, n := range f.Names {
			p = append(p, n.Name)
		}
	}
	return p
}

func debugCall(fName string, args ...string) []byte {
	vals := make(map[string]string)
	if len(args) > 0 {
		vals["formatters"] = formatters(len(args))
		vals["args"] = strings.Join(args, ", ")
	} else {
		vals["formatters"] = ""
		vals["args"] = ""
	}

	vals["fname"] = fName

	if showReturn {
		vals["return"] = "true"
	}

	var b bytes.Buffer
	err := funcTemplate.Execute(&b, vals)
	if err != nil {
		log.Fatal(err)
	}
	return b.Bytes()
}

type edit struct {
	pos int
	val []byte
}

type editList struct {
	edits       []edit
	packageName string
}

func (e *editList) Add(pos int, val []byte) {
	e.edits = append(e.edits, edit{pos: pos, val: val})
}

func (e *editList) inspect(node ast.Node) bool {
	if node == nil {
		return false
	}

	var funcType *ast.FuncType
	var body *ast.BlockStmt
	var funcName string

	switch n := node.(type) {
	case *ast.FuncDecl:
		body = n.Body
		if body == nil {
			return true
		}
		funcType = n.Type
		funcName = n.Name.Name

	case *ast.FuncLit:
		body = n.Body
		funcType = n.Type
		funcName = "func"

	default:
		return true
	}

	if exportedOnly && !ast.IsExported(funcName) {
		return true
	}

	if showPackage {
		funcName = e.packageName + "." + funcName
	}

	e.Add(int(body.Lbrace), debugCall(funcName, paramNames(funcType.Params)...))

	return true
}

func annotate(file string) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
	if err != nil {
		log.Fatal(err)
	}

	for _, imp := range f.Imports {
		if imp.Name != nil && imp.Name.Name == importName {
			log.Printf(`"%s" already imported. skipping %s`, importName, file)
			return
		}
		if imp.Path.Value == importPath {
			log.Printf(`"%s", already imported. skipping %s`, importPath, file)
			return
		}
	}

	edits := editList{packageName: f.Name.Name}

	// insert our import directly after the package line
	edits.Add(int(f.Name.End()), []byte(importStmt))

	ast.Inspect(f, edits.inspect)

	var buf bytes.Buffer
	if err := format.Node(&buf, fset, f); err != nil {
		log.Fatal(err)
	}

	data := buf.Bytes()

	var pos int
	var out []byte
	for _, e := range edits.edits {
		out = append(out, data[pos:e.pos]...)
		out = append(out, []byte(e.val)...)
		pos = e.pos
	}
	out = append(out, data[pos:]...)

	// it's easier to append the setup code at the end
	out = append(out, []byte(setup)...)

	src, err := format.Source(out)
	if err != nil {
		log.Fatal(err)
	}

	if !writeFiles {
		fmt.Println(string(src))
		return
	}

	err = ioutil.WriteFile(file, src, 0)
	if err != nil {
		log.Fatal(err)
	}
}

func init() {
	funcTemplate = template.Must(template.New("debug").Parse(tmpl))
}

func main() {
	flag.BoolVar(&showReturn, "returns", false, "show function return")
	flag.BoolVar(&exportedOnly, "exported", false, "only annotate exported functions")
	flag.StringVar(&prefix, "prefix", "\t", "log prefix")
	flag.BoolVar(&showPackage, "package", false, "show package name prefix on function calls")
	flag.BoolVar(&writeFiles, "w", false, "re-write files in place")
	flag.Parse()

	if flag.NArg() < 1 {
		flag.PrintDefaults()
		os.Exit(1)
	}

	setup = fmt.Sprintf(setup, prefix)

	for _, file := range flag.Args() {
		annotate(file)
	}
}
<|endoftext|>


<commit_before>package trace

import (
	"fmt"
	"path/filepath"
	"runtime"
)

// Wrap takes the original error and wraps it into the Trace struct
// memorizing the context of the error.
func Wrap(err error, args ...interface{}) error {
	t := newTrace(runtime.Caller(1))
	t.error = err
	if len(args) != 0 {
		t.Message = fmt.Sprintf(fmt.Sprintf("%v", args[0]), args[1:]...)
	}
	return t
}

// Errorf is similar to fmt.Errorf except that it captures
// more information about the origin of error, such as
// callee, line number and function that simplifies debugging
func Errorf(format string, args ...interface{}) error {
	t := newTrace(runtime.Caller(1))
	t.error = fmt.Errorf(format, args...)
	return t
}

func newTrace(pc uintptr, filePath string, line int, ok bool) *TraceErr {
	if !ok {
		return &TraceErr{
			File: "unknown_file",
			Path: "unknown_path",
			Func: "unknown_func",
			Line: 0,
		}
	}
	return &TraceErr{
		File: filepath.Base(filePath),
		Path: filePath,
		Func: runtime.FuncForPC(pc).Name(),
		Line: line,
	}
}

// TraceErr contains error message and some additional
// information about the error origin
type TraceErr struct {
	error
	Message string
	File    string
	Path    string
	Func    string
	Line    int
}

func (e *TraceErr) Error() string {
	return fmt.Sprintf("[%v:%v] %v %v", e.File, e.Line, e.Message, e.error)
}

func (e *TraceErr) OrigError() error {
	return e.error
}

// Error is an interface that helps to adapt usage of trace in the code
// When applications define new error types, they can implement the interface
// So error handlers can use OrigError() to retrieve error from the wrapper
type Error interface {
	OrigError() error
}
<commit_msg>make sure to return proper code<commit_after>package trace

import (
	"fmt"
	"path/filepath"
	"runtime"
)

// Wrap takes the original error and wraps it into the Trace struct
// memorizing the context of the error.
func Wrap(err error, args ...interface{}) Error {
	t := newTrace(runtime.Caller(1))
	t.error = err
	if len(args) != 0 {
		t.Message = fmt.Sprintf(fmt.Sprintf("%v", args[0]), args[1:]...)
	}
	return t
}

// Errorf is similar to fmt.Errorf except that it captures
// more information about the origin of error, such as
// callee, line number and function that simplifies debugging
func Errorf(format string, args ...interface{}) error {
	t := newTrace(runtime.Caller(1))
	t.error = fmt.Errorf(format, args...)
	return t
}

func newTrace(pc uintptr, filePath string, line int, ok bool) *TraceErr {
	if !ok {
		return &TraceErr{
			File: "unknown_file",
			Path: "unknown_path",
			Func: "unknown_func",
			Line: 0,
		}
	}
	return &TraceErr{
		File: filepath.Base(filePath),
		Path: filePath,
		Func: runtime.FuncForPC(pc).Name(),
		Line: line,
	}
}

// TraceErr contains error message and some additional
// information about the error origin
type TraceErr struct {
	error
	Message string
	File    string
	Path    string
	Func    string
	Line    int
}

func (e *TraceErr) Error() string {
	return fmt.Sprintf("[%v:%v] %v %v", e.File, e.Line, e.Message, e.error)
}

func (e *TraceErr) OrigError() error {
	return e.error
}

// Error is an interface that helps to adapt usage of trace in the code
// When applications define new error types, they can implement the interface
// So error handlers can use OrigError() to retrieve error from the wrapper
type Error interface {
	OrigError() error
}
<|endoftext|>