<commit_before>package demeter

import (
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"

	"golang.org/x/tools/go/ast/astutil"
)

// Violation represents a violation of the Law of Demeter.
type Violation struct {
	Filename string
	Line     int
	Col      int
}

func analyzeFile(filename string, f *ast.File, fset *token.FileSet) ([]*Violation, error) {
	info := &types.Info{
		// TODO: check if we can remove any
		Types:      make(map[ast.Expr]types.TypeAndValue),
		Defs:       make(map[*ast.Ident]types.Object),
		Uses:       make(map[*ast.Ident]types.Object),
		Selections: make(map[*ast.SelectorExpr]*types.Selection),
		Implicits:  make(map[ast.Node]types.Object),
	}

	config := &types.Config{
		Error: func(err error) {
			fmt.Println(err)
		},
		Importer: importer.Default(),
	}

	_, err := config.Check(filename, fset, []*ast.File{f}, info)
	if err != nil {
		return nil, err
	}

	visitor := newAstVisitor(f, fset, info)
	ast.Walk(visitor, f)

	return visitor.Violations, nil
}

// AnalyzeFile analyzes a single file and returns the violations.
func AnalyzeFile(filename string) ([]*Violation, error) {
	fset := token.NewFileSet()

	f, err := parser.ParseFile(fset, filename, nil, 0)
	if err != nil {
		return nil, err
	}

	return analyzeFile(filename, f, fset)
}

// AnalyzeDir analyzes a directory (non-recursively) and returns the violations.
func AnalyzeDir(dirname string) ([]*Violation, error) {
	fset := token.NewFileSet()

	packages, err := parser.ParseDir(fset, dirname, nil, 0)
	if err != nil {
		return nil, err
	}

	violations := []*Violation{}
	for _, p := range packages {
		for filename, f := range p.Files {
			v, err := analyzeFile(filename, f, fset)
			if err != nil {
				return nil, err
			}
			violations = append(violations, v...)
		}
	}

	return violations, nil
}

type astVisitor struct {
	info       *types.Info
	f          *ast.File
	fset       *token.FileSet
	Violations []*Violation
}

func newAstVisitor(f *ast.File, fset *token.FileSet, info *types.Info) *astVisitor {
	return &astVisitor{
		info:       info,
		f:          f,
		fset:       fset,
		Violations: []*Violation{},
	}
}

func (v *astVisitor) enclosingFuncDecl(expr ast.Node) *ast.FuncDecl {
	path, _ := astutil.PathEnclosingInterval(v.f, expr.Pos(), expr.End())
	for _, n := range path {
		// fmt.Printf("n: %#v\n", n)
		if funcDecl, ok := n.(*ast.FuncDecl); ok {
			return funcDecl
		}
	}
	return nil
}

func exprToIdent(expr ast.Expr) *ast.Ident {
	if ident, ok := expr.(*ast.Ident); ok {
		return ident
	}
	return expr.(*ast.StarExpr).X.(*ast.Ident)
}

func (v *astVisitor) addViolation(expr *ast.CallExpr) {
	fpos := v.fset.Position(expr.Pos())
	violation := &Violation{
		Filename: fpos.Filename,
		Line:     fpos.Line,
		Col:      fpos.Column,
	}
	v.Violations = append(v.Violations, violation)
}

func (v *astVisitor) VisitCallExpr(callExpr *ast.CallExpr) (visitor ast.Visitor) {
	visitor = v

	fun, ok := callExpr.Fun.(*ast.SelectorExpr)
	if !ok {
		// Package-local function call
		return
	}

	switch call := v.info.ObjectOf(fun.Sel).(type) {
	case *types.Var:
		// TODO
	case *types.Func:
		callRecv := call.Type().(*types.Signature).Recv()
		if callRecv == nil {
			// Not a method call
			return
		}

		funcDecl := v.enclosingFuncDecl(fun)
		if funcDecl.Recv == nil {
			// Not inside a method
			return
		}

		if _, ok := fun.X.(*ast.CallExpr); ok {
			// Chained method call
			v.addViolation(callExpr)
			return
		}

		funcDeclRecv := funcDecl.Recv.List[0].Names[0]
		if callRecv.Name() == funcDeclRecv.Name {
			// Call on O itself
			return
		}

		x, sel := v.lookupXSel(fun)

		if funcDecl.Type.Params.NumFields() > 0 {
			for _, param := range funcDecl.Type.Params.List {
				name := param.Names[0].Name
				if name == sel.Name {
					// Call on one of m's parameters
					return
				}
			}
		}

		funcDeclScope := v.info.ObjectOf(funcDecl.Name).(*types.Func).Scope()
		if funcDeclScope.Lookup(sel.Name) != nil {
			// Call on object created in m
			// XXX: check if object *instantiated* in m, as opposed
			// to just declared
			return
		}

		for _, name := range exprToIdent(funcDecl.Recv.List[0].Type).Obj.Decl.(*ast.TypeSpec).Type.(*ast.StructType).Fields.List {
			if name.Names[0].Name == sel.Name {
				if x != nil && x.Name == funcDeclRecv.Name {
					// Call on one of O's direct components
					// XXX: check embedded methods
					return
				}
				break
			}
		}

		selVar := v.info.ObjectOf(sel).(*types.Var)
		if selVar.Pkg().Scope() == selVar.Parent() {
			// Call on global object
			return
		}

		v.addViolation(callExpr)
	}

	return
}

func (v *astVisitor) Visit(node ast.Node) ast.Visitor {
	if n, ok := node.(*ast.CallExpr); ok {
		return v.VisitCallExpr(n)
	}
	return v
}

func (v *astVisitor) lookupXSel(sexpr *ast.SelectorExpr) (retx, retsel *ast.Ident) {
	if ident, ok := sexpr.X.(*ast.Ident); ok {
		retsel = ident
		return
	}

	sexpr = sexpr.X.(*ast.SelectorExpr)
	retsel = sexpr.Sel

	ident, ok := sexpr.X.(*ast.Ident)
	if ok {
		retx = ident
	}
	for ; !ok; retx = sexpr.Sel {
		sexpr = sexpr.X.(*ast.SelectorExpr)
		_, ok = sexpr.X.(*ast.Ident)
	}

	return
}
<commit_msg>Change function order<commit_after>package demeter

import (
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"

	"golang.org/x/tools/go/ast/astutil"
)

// Violation represents a violation of the Law of Demeter.
type Violation struct {
	Filename string
	Line     int
	Col      int
}

func analyzeFile(filename string, f *ast.File, fset *token.FileSet) ([]*Violation, error) {
	info := &types.Info{
		// TODO: check if we can remove any
		Types:      make(map[ast.Expr]types.TypeAndValue),
		Defs:       make(map[*ast.Ident]types.Object),
		Uses:       make(map[*ast.Ident]types.Object),
		Selections: make(map[*ast.SelectorExpr]*types.Selection),
		Implicits:  make(map[ast.Node]types.Object),
	}

	config := &types.Config{
		Error: func(err error) {
			fmt.Println(err)
		},
		Importer: importer.Default(),
	}

	_, err := config.Check(filename, fset, []*ast.File{f}, info)
	if err != nil {
		return nil, err
	}

	visitor := newAstVisitor(f, fset, info)
	ast.Walk(visitor, f)

	return visitor.Violations, nil
}

// AnalyzeFile analyzes a single file and returns the violations.
func AnalyzeFile(filename string) ([]*Violation, error) {
	fset := token.NewFileSet()

	f, err := parser.ParseFile(fset, filename, nil, 0)
	if err != nil {
		return nil, err
	}

	return analyzeFile(filename, f, fset)
}

// AnalyzeDir analyzes a directory (non-recursively) and returns the violations.
func AnalyzeDir(dirname string) ([]*Violation, error) {
	fset := token.NewFileSet()

	packages, err := parser.ParseDir(fset, dirname, nil, 0)
	if err != nil {
		return nil, err
	}

	violations := []*Violation{}
	for _, p := range packages {
		for filename, f := range p.Files {
			v, err := analyzeFile(filename, f, fset)
			if err != nil {
				return nil, err
			}
			violations = append(violations, v...)
		}
	}

	return violations, nil
}

type astVisitor struct {
	info       *types.Info
	f          *ast.File
	fset       *token.FileSet
	Violations []*Violation
}

func newAstVisitor(f *ast.File, fset *token.FileSet, info *types.Info) *astVisitor {
	return &astVisitor{
		info:       info,
		f:          f,
		fset:       fset,
		Violations: []*Violation{},
	}
}

func (v *astVisitor) Visit(node ast.Node) ast.Visitor {
	if n, ok := node.(*ast.CallExpr); ok {
		return v.VisitCallExpr(n)
	}
	return v
}

func (v *astVisitor) VisitCallExpr(callExpr *ast.CallExpr) (visitor ast.Visitor) {
	visitor = v

	fun, ok := callExpr.Fun.(*ast.SelectorExpr)
	if !ok {
		// Package-local function call
		return
	}

	switch call := v.info.ObjectOf(fun.Sel).(type) {
	case *types.Var:
		// TODO
	case *types.Func:
		callRecv := call.Type().(*types.Signature).Recv()
		if callRecv == nil {
			// Not a method call
			return
		}

		funcDecl := v.enclosingFuncDecl(fun)
		if funcDecl.Recv == nil {
			// Not inside a method
			return
		}

		if _, ok := fun.X.(*ast.CallExpr); ok {
			// Chained method call
			v.addViolation(callExpr)
			return
		}

		funcDeclRecv := funcDecl.Recv.List[0].Names[0]
		if callRecv.Name() == funcDeclRecv.Name {
			// Call on O itself
			return
		}

		x, sel := v.lookupXSel(fun)

		if funcDecl.Type.Params.NumFields() > 0 {
			for _, param := range funcDecl.Type.Params.List {
				name := param.Names[0].Name
				if name == sel.Name {
					// Call on one of m's parameters
					return
				}
			}
		}

		funcDeclScope := v.info.ObjectOf(funcDecl.Name).(*types.Func).Scope()
		if funcDeclScope.Lookup(sel.Name) != nil {
			// Call on object created in m
			// XXX: check if object *instantiated* in m, as opposed
			// to just declared
			return
		}

		for _, name := range exprToIdent(funcDecl.Recv.List[0].Type).Obj.Decl.(*ast.TypeSpec).Type.(*ast.StructType).Fields.List {
			if name.Names[0].Name == sel.Name {
				if x != nil && x.Name == funcDeclRecv.Name {
					// Call on one of O's direct components
					// XXX: check embedded methods
					return
				}
				break
			}
		}

		selVar := v.info.ObjectOf(sel).(*types.Var)
		if selVar.Pkg().Scope() == selVar.Parent() {
			// Call on global object
			return
		}

		v.addViolation(callExpr)
	}

	return
}

func (v *astVisitor) addViolation(expr *ast.CallExpr) {
	fpos := v.fset.Position(expr.Pos())
	violation := &Violation{
		Filename: fpos.Filename,
		Line:     fpos.Line,
		Col:      fpos.Column,
	}
	v.Violations = append(v.Violations, violation)
}

func (v *astVisitor) enclosingFuncDecl(expr ast.Node) *ast.FuncDecl {
	path, _ := astutil.PathEnclosingInterval(v.f, expr.Pos(), expr.End())
	for _, n := range path {
		// fmt.Printf("n: %#v\n", n)
		if funcDecl, ok := n.(*ast.FuncDecl); ok {
			return funcDecl
		}
	}
	return nil
}

func (v *astVisitor) lookupXSel(sexpr *ast.SelectorExpr) (retx, retsel *ast.Ident) {
	if ident, ok := sexpr.X.(*ast.Ident); ok {
		retsel = ident
		return
	}

	sexpr = sexpr.X.(*ast.SelectorExpr)
	retsel = sexpr.Sel

	ident, ok := sexpr.X.(*ast.Ident)
	if ok {
		retx = ident
	}
	for ; !ok; retx = sexpr.Sel {
		sexpr = sexpr.X.(*ast.SelectorExpr)
		_, ok = sexpr.X.(*ast.Ident)
	}

	return
}

func exprToIdent(expr ast.Expr) *ast.Ident {
	if ident, ok := expr.(*ast.Ident); ok {
		return ident
	}
	return expr.(*ast.StarExpr).X.(*ast.Ident)
}
<|endoftext|>


<commit_before>package routeros

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
)

// A reply can contain multiple pairs. A pair is a string key->value.
// A reply can also contain subpairs, that is, a array of pair arrays.
type Reply struct {
	Pairs    []Pair
	SubPairs []map[string]string
}

func (r *Reply) GetPairVal(key string) (string, error) {
	for _, p := range r.Pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

func (r *Reply) GetSubPairByName(key string) (map[string]string, error) {
	for _, p := range r.SubPairs {
		if _, ok := p["name"]; ok {
			if p["name"] == key {
				return p, nil
			}
		}
	}
	return nil, errors.New("key not found")
}

func GetPairVal(pairs []Pair, key string) (string, error) {
	for _, p := range pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

// Client is a RouterOS API client.
type Client struct {
	// Network Address.
	// E.g. "10.0.0.1:8728" or "router.example.com:8728"
	address  string
	user     string
	password string
	debug    bool     // debug logging enabled
	ready    bool     // Ready for work (login ok and connection not terminated)
	conn     net.Conn // Connection to pass around
}

// Pair is a Key-Value pair for RouterOS Attribute, Query, and Reply words
// use slices of pairs instead of map because we care about order
type Pair struct {
	Key   string
	Value string
	// Op is used for Query words to signify logical operations
	// valid operators are -, =, <, >
	// see http://wiki.mikrotik.com/wiki/Manual:API#Queries for details.
	Op string
}

type Query struct {
	Pairs    []Pair
	Op       string
	Proplist []string
}

func NewPair(key string, value string) *Pair {
	p := new(Pair)
	p.Key = key
	p.Value = value
	return p
}

// Create a new instance of the RouterOS API client
func New(address string) (*Client, error) {
	// basic validation of host address
	_, _, err := net.SplitHostPort(address)
	if err != nil {
		return nil, err
	}

	var c Client
	c.address = address

	return &c, nil
}

func (c *Client) Close() {
	c.conn.Close()
}

func (c *Client) Connect(user string, password string) error {
	conn, err := net.Dial("tcp", c.address)
	if err != nil {
		return err
	}

	// stash conn in instance
	c.conn = conn

	// try to log in
	res, err := c.Call("/login", nil)
	if err != nil {
		return err
	}

	// handle challenge/response
	challengeEnc, err := res.GetPairVal("ret")
	if err != nil {
		return errors.New("Didn't get challenge from ROS")
	}
	challenge, err := hex.DecodeString(challengeEnc)
	if err != nil {
		return err
	}
	h := md5.New()
	io.WriteString(h, "\000")
	io.WriteString(h, password)
	h.Write(challenge)
	resp := fmt.Sprintf("00%x", h.Sum(nil))
	var loginParams []Pair
	loginParams = append(loginParams, *NewPair("name", user))
	loginParams = append(loginParams, *NewPair("response", resp))

	// try to log in again with challenge/response
	res, err = c.Call("/login", loginParams)
	if err != nil {
		return err
	}

	if len(res.Pairs) > 0 {
		return fmt.Errorf("Unexpected result on login: %+v", res)
	}

	return nil
}

func (c *Client) Query(command string, q Query) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// Set property list if present
	if len(q.Proplist) > 0 {
		proplist := fmt.Sprintf("=.proplist=%s", strings.Join(q.Proplist, ","))
		err = c.send(proplist)
		if err != nil {
			return Reply{}, err
		}
	}

	// send params if we got them
	if len(q.Pairs) > 0 {
		for _, v := range q.Pairs {
			word := fmt.Sprintf("?%s%s=%s", v.Op, v.Key, v.Value)
			c.send(word)
		}

		if q.Op != "" {
			word := fmt.Sprintf("?#%s", q.Op)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}

func (c *Client) Call(command string, params []Pair) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// send params if we got them
	if len(params) > 0 {
		for _, v := range params {
			word := fmt.Sprintf("=%s=%s", v.Key, v.Value)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}
<commit_msg>add tls support<commit_after>package routeros

import (
	"crypto/md5"
	"crypto/tls"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
)

// A reply can contain multiple pairs. A pair is a string key->value.
// A reply can also contain subpairs, that is, a array of pair arrays.
type Reply struct {
	Pairs    []Pair
	SubPairs []map[string]string
}

func (r *Reply) GetPairVal(key string) (string, error) {
	for _, p := range r.Pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

func (r *Reply) GetSubPairByName(key string) (map[string]string, error) {
	for _, p := range r.SubPairs {
		if _, ok := p["name"]; ok {
			if p["name"] == key {
				return p, nil
			}
		}
	}
	return nil, errors.New("key not found")
}

func GetPairVal(pairs []Pair, key string) (string, error) {
	for _, p := range pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

// Client is a RouterOS API client.
type Client struct {
	// Network Address.
	// E.g. "10.0.0.1:8728" or "router.example.com:8728"
	address  string
	user     string
	password string
	debug    bool     // debug logging enabled
	ready    bool     // Ready for work (login ok and connection not terminated)
	conn     net.Conn // Connection to pass around
	TLSConfig *tls.Config
}

// Pair is a Key-Value pair for RouterOS Attribute, Query, and Reply words
// use slices of pairs instead of map because we care about order
type Pair struct {
	Key   string
	Value string
	// Op is used for Query words to signify logical operations
	// valid operators are -, =, <, >
	// see http://wiki.mikrotik.com/wiki/Manual:API#Queries for details.
	Op string
}

type Query struct {
	Pairs    []Pair
	Op       string
	Proplist []string
}

func NewPair(key string, value string) *Pair {
	p := new(Pair)
	p.Key = key
	p.Value = value
	return p
}

// Create a new instance of the RouterOS API client
func New(address string) (*Client, error) {
	// basic validation of host address
	_, _, err := net.SplitHostPort(address)
	if err != nil {
		return nil, err
	}

	var c Client
	c.address = address

	return &c, nil
}

func (c *Client) Close() {
	c.conn.Close()
}

func (c *Client) Connect(user string, password string) error {

	var err error
	if c.TLSConfig != nil {
		c.conn, err = tls.Dial("tcp", c.address, c.TLSConfig)
	} else {
		c.conn, err = net.Dial("tcp", c.address)
	}
	if err != nil {
		return err
	}

	// try to log in
	res, err := c.Call("/login", nil)
	if err != nil {
		return err
	}

	// handle challenge/response
	challengeEnc, err := res.GetPairVal("ret")
	if err != nil {
		return errors.New("Didn't get challenge from ROS")
	}
	challenge, err := hex.DecodeString(challengeEnc)
	if err != nil {
		return err
	}
	h := md5.New()
	io.WriteString(h, "\000")
	io.WriteString(h, password)
	h.Write(challenge)
	resp := fmt.Sprintf("00%x", h.Sum(nil))
	var loginParams []Pair
	loginParams = append(loginParams, *NewPair("name", user))
	loginParams = append(loginParams, *NewPair("response", resp))

	// try to log in again with challenge/response
	res, err = c.Call("/login", loginParams)
	if err != nil {
		return err
	}

	if len(res.Pairs) > 0 {
		return fmt.Errorf("Unexpected result on login: %+v", res)
	}

	return nil
}

func (c *Client) Query(command string, q Query) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// Set property list if present
	if len(q.Proplist) > 0 {
		proplist := fmt.Sprintf("=.proplist=%s", strings.Join(q.Proplist, ","))
		err = c.send(proplist)
		if err != nil {
			return Reply{}, err
		}
	}

	// send params if we got them
	if len(q.Pairs) > 0 {
		for _, v := range q.Pairs {
			word := fmt.Sprintf("?%s%s=%s", v.Op, v.Key, v.Value)
			c.send(word)
		}

		if q.Op != "" {
			word := fmt.Sprintf("?#%s", q.Op)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}

func (c *Client) Call(command string, params []Pair) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// send params if we got them
	if len(params) > 0 {
		for _, v := range params {
			word := fmt.Sprintf("=%s=%s", v.Key, v.Value)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}
<|endoftext|>


<commit_before>// Package fbapi provides wrappers to access the Facebook API.
package fbapi

import (
	"crypto/tls"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/daaku/go.fburl"
	"github.com/daaku/go.httpcontrol"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const redactedStub = "$1=-- XX -- REDACTED -- XX --"

var (
	insecureSSL = flag.Bool(
		"fbapi.insecure", false, "Skip SSL certificate validation.")
	redact = flag.Bool(
		"fbapi.redact",
		true,
		"When true known sensitive information will be stripped from errors.")
	timeout = flag.Duration(
		"fbapi.timeout",
		5*time.Second,
		"Timeout for http requests.")
	maxTries = flag.Uint(
		"fbapi.max-tries",
		3,
		"Number of retries for known safe to retry calls.")
	cleanURLRegExp  = regexp.MustCompile("(access_token|client_secret)=([^&]*)")
	httpClientCache *http.Client
)

// An Error from the API.
type Error struct {
	Message string `json:"message"`
	Type    string `json:"type"`
	Code    int    `json:"code"`
	Body    []byte
}

// Wrapper for "error"
type errorResponse struct {
	Error Error `json:"error"`
}

// Represents a thing that wants to modify the url.Values.
type Values interface {
	Set(url.Values)
}

// Represents an "access_token" for the Facebook API.
type Token string

const (
	PublicToken = Token("")
)

// Generic Page options for list type queries.
type Page struct {
	Limit  int
	Offset int
}

// Set the corresponding values for the Page.
func (page Page) Set(values url.Values) {
	if page.Limit != 0 {
		values.Set("limit", strconv.Itoa(page.Limit))
	}
	if page.Offset != 0 {
		values.Set("offset", strconv.Itoa(page.Offset))
	}
}

// A slice of field names.
type Fields []string

// For selecting fields.
func (fields Fields) Set(values url.Values) {
	if len(fields) > 0 {
		values.Set("fields", strings.Join(fields, ","))
	}
}

// Set the token if necessary.
func (token Token) Set(values url.Values) {
	if token != PublicToken {
		values.Set("access_token", string(token))
	}
}

// String representation as defined by the error interface.
func (e *Error) Error() string {
	return fmt.Sprintf("API call failed with error body:\n%s", string(e.Body))
}

// Disable SSL cert, useful when debugging or hitting internal self-signed certs
func httpClient() *http.Client {
	if httpClientCache == nil {
		transport := &httpcontrol.Transport{
			Proxy:           http.ProxyFromEnvironment,
			TLSClientConfig: &tls.Config{InsecureSkipVerify: *insecureSSL},
			RequestTimeout:  *timeout,
			MaxTries:        *maxTries,
		}
		transport.Start()
		httpClientCache = &http.Client{Transport: transport}
	}
	return httpClientCache
}

// remove known sensitive tokens from data
func cleanURL(url string) string {
	if *redact {
		return cleanURLRegExp.ReplaceAllString(url, redactedStub)
	}
	return url
}

// Make a GET Graph API request and get the raw body byte slice.
func GetRaw(path string, values url.Values) ([]byte, error) {
	const phpRFC3339 = `Y-m-d\TH:i:s\Z`
	values.Set("date_format", phpRFC3339)
	u := &fburl.URL{
		Scheme:    "https",
		SubDomain: fburl.DGraph,
		Path:      path,
		Values:    values,
	}
	resp, err := httpClient().Get(u.String())
	if err != nil {
		return nil, fmt.Errorf(
			"Request for URL %s failed with error %s.", cleanURL(u.String()), err)
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf(
			"Request for URL %s failed because body could not be read "+
				"with error %s.",
			cleanURL(u.String()), err)
	}
	if resp.StatusCode > 399 || resp.StatusCode < 200 {
		apiError := &errorResponse{Error{Body: b}}
		err = json.Unmarshal(b, apiError)
		if err != nil {
			return nil, fmt.Errorf(
				"Parsing error response failed with %s:\n%s", err, string(b))
		}
		return nil, &apiError.Error
	}
	return b, nil
}

// Make a GET Graph API request.
func Get(result interface{}, path string, values ...Values) error {
	final := url.Values{}
	for _, v := range values {
		v.Set(final)
	}
	b, err := GetRaw(path, final)
	if err != nil {
		return err
	}
	err = json.Unmarshal(b, result)
	if err != nil {
		return fmt.Errorf(
			"Request for path %s with response %s failed with "+
				"json.Unmarshal error %s.",
			cleanURL(path), string(b), err)
	}
	return nil
}
<commit_msg>split github imports<commit_after>// Package fbapi provides wrappers to access the Facebook API.
package fbapi

import (
	"crypto/tls"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/daaku/go.fburl"
	"github.com/daaku/go.httpcontrol"
)

const redactedStub = "$1=-- XX -- REDACTED -- XX --"

var (
	insecureSSL = flag.Bool(
		"fbapi.insecure", false, "Skip SSL certificate validation.")
	redact = flag.Bool(
		"fbapi.redact",
		true,
		"When true known sensitive information will be stripped from errors.")
	timeout = flag.Duration(
		"fbapi.timeout",
		5*time.Second,
		"Timeout for http requests.")
	maxTries = flag.Uint(
		"fbapi.max-tries",
		3,
		"Number of retries for known safe to retry calls.")
	cleanURLRegExp  = regexp.MustCompile("(access_token|client_secret)=([^&]*)")
	httpClientCache *http.Client
)

// An Error from the API.
type Error struct {
	Message string `json:"message"`
	Type    string `json:"type"`
	Code    int    `json:"code"`
	Body    []byte
}

// Wrapper for "error"
type errorResponse struct {
	Error Error `json:"error"`
}

// Represents a thing that wants to modify the url.Values.
type Values interface {
	Set(url.Values)
}

// Represents an "access_token" for the Facebook API.
type Token string

const (
	PublicToken = Token("")
)

// Generic Page options for list type queries.
type Page struct {
	Limit  int
	Offset int
}

// Set the corresponding values for the Page.
func (page Page) Set(values url.Values) {
	if page.Limit != 0 {
		values.Set("limit", strconv.Itoa(page.Limit))
	}
	if page.Offset != 0 {
		values.Set("offset", strconv.Itoa(page.Offset))
	}
}

// A slice of field names.
type Fields []string

// For selecting fields.
func (fields Fields) Set(values url.Values) {
	if len(fields) > 0 {
		values.Set("fields", strings.Join(fields, ","))
	}
}

// Set the token if necessary.
func (token Token) Set(values url.Values) {
	if token != PublicToken {
		values.Set("access_token", string(token))
	}
}

// String representation as defined by the error interface.
func (e *Error) Error() string {
	return fmt.Sprintf("API call failed with error body:\n%s", string(e.Body))
}

// Disable SSL cert, useful when debugging or hitting internal self-signed certs
func httpClient() *http.Client {
	if httpClientCache == nil {
		transport := &httpcontrol.Transport{
			Proxy:           http.ProxyFromEnvironment,
			TLSClientConfig: &tls.Config{InsecureSkipVerify: *insecureSSL},
			RequestTimeout:  *timeout,
			MaxTries:        *maxTries,
		}
		transport.Start()
		httpClientCache = &http.Client{Transport: transport}
	}
	return httpClientCache
}

// remove known sensitive tokens from data
func cleanURL(url string) string {
	if *redact {
		return cleanURLRegExp.ReplaceAllString(url, redactedStub)
	}
	return url
}

// Make a GET Graph API request and get the raw body byte slice.
func GetRaw(path string, values url.Values) ([]byte, error) {
	const phpRFC3339 = `Y-m-d\TH:i:s\Z`
	values.Set("date_format", phpRFC3339)
	u := &fburl.URL{
		Scheme:    "https",
		SubDomain: fburl.DGraph,
		Path:      path,
		Values:    values,
	}
	resp, err := httpClient().Get(u.String())
	if err != nil {
		return nil, fmt.Errorf(
			"Request for URL %s failed with error %s.", cleanURL(u.String()), err)
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf(
			"Request for URL %s failed because body could not be read "+
				"with error %s.",
			cleanURL(u.String()), err)
	}
	if resp.StatusCode > 399 || resp.StatusCode < 200 {
		apiError := &errorResponse{Error{Body: b}}
		err = json.Unmarshal(b, apiError)
		if err != nil {
			return nil, fmt.Errorf(
				"Parsing error response failed with %s:\n%s", err, string(b))
		}
		return nil, &apiError.Error
	}
	return b, nil
}

// Make a GET Graph API request.
func Get(result interface{}, path string, values ...Values) error {
	final := url.Values{}
	for _, v := range values {
		v.Set(final)
	}
	b, err := GetRaw(path, final)
	if err != nil {
		return err
	}
	err = json.Unmarshal(b, result)
	if err != nil {
		return fmt.Errorf(
			"Request for path %s with response %s failed with "+
				"json.Unmarshal error %s.",
			cleanURL(path), string(b), err)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2013 Matthew Baird
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
The API package provides the basic support for using HTTP to talk to the Mandrill and Mailchimp API's.
Each Struct contains a Key, Transport and endpoint property
*/
package gochimp

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
)

const (
      mandrill_uri     = "mandrillapp.com/api/"
      mandrill_version = "1.0"
)

type MandrillAPI struct {
	Key       string
	Transport http.RoundTripper
	endpoint  string
}

type ChimpAPI struct {
	Key       string
	Transport http.RoundTripper
	endpoint  string
}

// see https://mandrillapp.com/api/docs/
// currently supporting json output formats
func NewMandrill(apiKey string) (*MandrillAPI, error) {
	u := url.URL{}
	u.Scheme = "https"
	u.Host = mandrill_uri
	u.Path = mandrill_version
	return &MandrillAPI{Key: apiKey, endpoint: u.String()}, nil
}

const mailchimp_uri string = "%s.api.mailchimp.com"
const mailchimp_version string = "/2.0"
const debug bool = false

var mailchimp_datacenter = regexp.MustCompile("[a-z]+[0-9]+$")

func NewChimp(apiKey string, https bool) *ChimpAPI {
	u := url.URL{}
	if https {
		u.Scheme = "https"
	} else {
		u.Scheme = "http"
	}
	u.Host = fmt.Sprintf("%s.api.mailchimp.com", mailchimp_datacenter.FindString(apiKey))
	u.Path = mailchimp_version
	return &ChimpAPI{Key: apiKey, endpoint: u.String()}
}

func runChimp(api *ChimpAPI, path string, parameters interface{}) ([]byte, error) {
	b, err := json.Marshal(parameters)
	if err != nil {
		return nil, err
	}
	requestUrl := fmt.Sprintf("%s%s", api.endpoint, path)
	if debug {
		log.Printf("Request URL:%s", requestUrl)
	}
	client := &http.Client{Transport: api.Transport}
	resp, err := client.Post(requestUrl, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Response Body:%s", string(body))
	}
	if err = chimpErrorCheck(body); err != nil {
		return nil, err
	}
	return body, nil
}

func runMandrill(api *MandrillAPI, path string, parameters map[string]interface{}) ([]byte, error) {
	if parameters == nil {
		parameters = make(map[string]interface{})
	}
	parameters["key"] = api.Key
	b, err := json.Marshal(parameters)
	if debug {
		log.Printf("Payload:%s", string(b))
	}
	if err != nil {
		return nil, err
	}
	requestUrl := fmt.Sprintf("%s%s", api.endpoint, path)
	if debug {
		log.Printf("Request URL:%s", requestUrl)
	}
	client := &http.Client{Transport: api.Transport}
	resp, err := client.Post(requestUrl, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Response Body:%s", string(body))
	}
	if err = mandrillErrorCheck(body); err != nil {
		return nil, err
	}
	return body, nil
}

func parseString(body []byte, err error) (string, error) {
	if err != nil {
		return "", err
	}
	return strconv.Unquote(string(body))
}

func parseMandrillJson(api *MandrillAPI, path string, parameters map[string]interface{}, retval interface{}) error {
	body, err := runMandrill(api, path, parameters)
	if err != nil {
		return err
	}
	json.Unmarshal(body, retval)
	return nil
}

func parseChimpJson(api *ChimpAPI, method string, parameters interface{}, retval interface{}) error {
	body, err := runChimp(api, method, parameters)
	if err != nil {
		return err
	}
	if retval != nil {
		return parseJson(body, retval)
	}
	return nil
}

type JsonAlterer interface {
	alterJson(b []byte) []byte
}

func parseJson(body []byte, retval interface{}) error {
	switch r := retval.(type) {
	case JsonAlterer:
		return json.Unmarshal(r.alterJson(body), retval)
	default:
		return json.Unmarshal(body, retval)
	}
	return nil
}
<commit_msg>go fmt<commit_after>// Copyright 2013 Matthew Baird
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
The API package provides the basic support for using HTTP to talk to the Mandrill and Mailchimp API's.
Each Struct contains a Key, Transport and endpoint property
*/
package gochimp

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
)

const (
	mandrill_uri     = "mandrillapp.com/api/"
	mandrill_version = "1.0"
)

type MandrillAPI struct {
	Key       string
	Transport http.RoundTripper
	endpoint  string
}

type ChimpAPI struct {
	Key       string
	Transport http.RoundTripper
	endpoint  string
}

// see https://mandrillapp.com/api/docs/
// currently supporting json output formats
func NewMandrill(apiKey string) (*MandrillAPI, error) {
	u := url.URL{}
	u.Scheme = "https"
	u.Host = mandrill_uri
	u.Path = mandrill_version
	return &MandrillAPI{Key: apiKey, endpoint: u.String()}, nil
}

const mailchimp_uri string = "%s.api.mailchimp.com"
const mailchimp_version string = "/2.0"
const debug bool = false

var mailchimp_datacenter = regexp.MustCompile("[a-z]+[0-9]+$")

func NewChimp(apiKey string, https bool) *ChimpAPI {
	u := url.URL{}
	if https {
		u.Scheme = "https"
	} else {
		u.Scheme = "http"
	}
	u.Host = fmt.Sprintf("%s.api.mailchimp.com", mailchimp_datacenter.FindString(apiKey))
	u.Path = mailchimp_version
	return &ChimpAPI{Key: apiKey, endpoint: u.String()}
}

func runChimp(api *ChimpAPI, path string, parameters interface{}) ([]byte, error) {
	b, err := json.Marshal(parameters)
	if err != nil {
		return nil, err
	}
	requestUrl := fmt.Sprintf("%s%s", api.endpoint, path)
	if debug {
		log.Printf("Request URL:%s", requestUrl)
	}
	client := &http.Client{Transport: api.Transport}
	resp, err := client.Post(requestUrl, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Response Body:%s", string(body))
	}
	if err = chimpErrorCheck(body); err != nil {
		return nil, err
	}
	return body, nil
}

func runMandrill(api *MandrillAPI, path string, parameters map[string]interface{}) ([]byte, error) {
	if parameters == nil {
		parameters = make(map[string]interface{})
	}
	parameters["key"] = api.Key
	b, err := json.Marshal(parameters)
	if debug {
		log.Printf("Payload:%s", string(b))
	}
	if err != nil {
		return nil, err
	}
	requestUrl := fmt.Sprintf("%s%s", api.endpoint, path)
	if debug {
		log.Printf("Request URL:%s", requestUrl)
	}
	client := &http.Client{Transport: api.Transport}
	resp, err := client.Post(requestUrl, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Response Body:%s", string(body))
	}
	if err = mandrillErrorCheck(body); err != nil {
		return nil, err
	}
	return body, nil
}

func parseString(body []byte, err error) (string, error) {
	if err != nil {
		return "", err
	}
	return strconv.Unquote(string(body))
}

func parseMandrillJson(api *MandrillAPI, path string, parameters map[string]interface{}, retval interface{}) error {
	body, err := runMandrill(api, path, parameters)
	if err != nil {
		return err
	}
	json.Unmarshal(body, retval)
	return nil
}

func parseChimpJson(api *ChimpAPI, method string, parameters interface{}, retval interface{}) error {
	body, err := runChimp(api, method, parameters)
	if err != nil {
		return err
	}
	if retval != nil {
		return parseJson(body, retval)
	}
	return nil
}

type JsonAlterer interface {
	alterJson(b []byte) []byte
}

func parseJson(body []byte, retval interface{}) error {
	switch r := retval.(type) {
	case JsonAlterer:
		return json.Unmarshal(r.alterJson(body), retval)
	default:
		return json.Unmarshal(body, retval)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the system primitive functions.

package golisp

import (
	"fmt"
	"os"
	"strings"
	"time"
)

func RegisterSystemPrimitives() {
	MakePrimitiveFunction("load", 1, LoadFileImpl)
	MakePrimitiveFunction("sleep", 1, SleepImpl)
	MakePrimitiveFunction("millis", 0, MillisImpl)
	MakePrimitiveFunction("write-line", 1, WriteLineImpl)
	MakePrimitiveFunction("str", -1, MakeStringImpl)
	MakePrimitiveFunction("intern", 1, InternImpl)
	MakePrimitiveFunction("time", 1, TimeImpl)
	MakePrimitiveFunction("quit", 0, QuitImpl)
}

func LoadFileImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	filename := Car(args)
	if !StringP(filename) {
		err = ProcessError("Filename must be a string", env)
		return
	}

	return ProcessFile(StringValue(filename))
}

func QuitImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	WriteHistoryToFile(".golisp_history")
	os.Exit(0)
	return
}

func SleepImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	n, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !IntegerP(n) {
		err = ProcessError(fmt.Sprintf("Number expected, received %s", String(n)), env)
		return
	}
	millis := IntegerValue(n)
	time.Sleep(time.Duration(millis) * time.Millisecond)
	return
}

func MillisImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	result = IntegerWithValue(int64(time.Now().UnixNano() / 1e6))
	return
}

func WriteLineImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	data, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	println(PrintString(data))
	return
}

func MakeStringImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	pieces := make([]string, 2)
	for cell := args; NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		s, err := Eval(sexpr, env)
		if err != nil {
			break
		}
		pieces = append(pieces, PrintString(s))
	}
	return StringWithValue(strings.Join(pieces, "")), nil
}

func TimeImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	fmt.Printf("Starting timer.\n")
	startTime := time.Now()

	for cell := args; NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		result, err = Eval(sexpr, env)
		if err != nil {
			break
		}
	}

	d := time.Since(startTime)
	fmt.Printf("Stopped timer.\nTook %v to run.\n", d)
	result = IntegerWithValue(int64(d.Nanoseconds() / 1000000))
	return
}

func InternImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	sym, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !StringP(sym) {
		err = ProcessError(fmt.Sprintf("intern expects a string, but received %s.", String(sym)), env)
		return
	}

	return SymbolWithName(StringValue(sym)), nil
}
<commit_msg>The argument to load should be evaluated so that we can compute filenames.<commit_after>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the system primitive functions.

package golisp

import (
	"fmt"
	"os"
	"strings"
	"time"
)

func RegisterSystemPrimitives() {
	MakePrimitiveFunction("load", 1, LoadFileImpl)
	MakePrimitiveFunction("sleep", 1, SleepImpl)
	MakePrimitiveFunction("millis", 0, MillisImpl)
	MakePrimitiveFunction("write-line", 1, WriteLineImpl)
	MakePrimitiveFunction("str", -1, MakeStringImpl)
	MakePrimitiveFunction("intern", 1, InternImpl)
	MakePrimitiveFunction("time", 1, TimeImpl)
	MakePrimitiveFunction("quit", 0, QuitImpl)
}

func LoadFileImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	filename := Eval(Car(args), env)
	if !StringP(filename) {
		err = ProcessError("Filename must be a string", env)
		return
	}

	return ProcessFile(StringValue(filename))
}

func QuitImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	WriteHistoryToFile(".golisp_history")
	os.Exit(0)
	return
}

func SleepImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	n, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !IntegerP(n) {
		err = ProcessError(fmt.Sprintf("Number expected, received %s", String(n)), env)
		return
	}
	millis := IntegerValue(n)
	time.Sleep(time.Duration(millis) * time.Millisecond)
	return
}

func MillisImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	result = IntegerWithValue(int64(time.Now().UnixNano() / 1e6))
	return
}

func WriteLineImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	data, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	println(PrintString(data))
	return
}

func MakeStringImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	pieces := make([]string, 2)
	for cell := args; NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		s, err := Eval(sexpr, env)
		if err != nil {
			break
		}
		pieces = append(pieces, PrintString(s))
	}
	return StringWithValue(strings.Join(pieces, "")), nil
}

func TimeImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	fmt.Printf("Starting timer.\n")
	startTime := time.Now()

	for cell := args; NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		result, err = Eval(sexpr, env)
		if err != nil {
			break
		}
	}

	d := time.Since(startTime)
	fmt.Printf("Stopped timer.\nTook %v to run.\n", d)
	result = IntegerWithValue(int64(d.Nanoseconds() / 1000000))
	return
}

func InternImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	sym, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !StringP(sym) {
		err = ProcessError(fmt.Sprintf("intern expects a string, but received %s.", String(sym)), env)
		return
	}

	return SymbolWithName(StringValue(sym)), nil
}
<|endoftext|>


<commit_before>// Copyright 2012 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
/*
	Unmarshal an arbitrary XML doc to a map[string]interface{} or a JSON string.

	DocToMap() returns an intermediate result with the XML doc unmarshal'd to a map
	of type map[string]interface{}. It is analogous to unmarshal'ng a JSON string to
	a map using json.Unmarshal(). (This was the original purpose of this library.)

	DocToTree()/WriteTree() let you examine the parsed XML doc.

	XML values are all type 'string'. The optional argument 'recast' for DocToJson()
	and DocToMap() will convert element values to JSON data types - 'float64' and 'bool' -
	if possible.  This, however, should be done with caution as it will recast ALL numeric
	and boolean string values, even those that are meant to be of type 'string'.
 */
package x2j

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"regexp"
	"strings"
	"strconv"
)

type Node struct {
	dup bool
	key string
	val string
	nodes []*Node
}

// DocToJson - return an XML doc as a JSON string.
//	If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
func DocToJson(doc string,recast ...bool) (string,error) {
	var r bool
	if len(recast) == 1 {
		r = recast[0]
	}
	m,merr := DocToMap(doc,r)
	if m == nil || merr != nil {
		return "",merr
	}

	b, berr := json.Marshal(m)
	if berr != nil {
		return "",berr
	}

	return string(b),nil
}

// DocToJsonIndent - return an XML doc as a prettified JSON string.
//	If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
func DocToJsonIndent(doc string,recast ...bool) (string,error) {
	var r bool
	if len(recast) == 1 {
		r = recast[0]
	}
	m,merr := DocToMap(doc,r)
	if m == nil || merr != nil {
		return "",merr
	}

	b, berr := json.MarshalIndent(m,"","  ")
	if berr != nil {
		return "",berr
	}

	return string(b),nil
}

// DocToMap - convert an XML doc into a map[string]interface{}.
// (This is analogous to unmarshalling a JSON string to map[string]interface{} using json.Unmarshal().)
//	If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
func DocToMap(doc string,recast ...bool) (map[string]interface{},error) {
	var r bool
	if len(recast) == 1 {
		r = recast[0]
	}
	n,err := DocToTree(doc)
	if err != nil {
		return nil,err
	}

	m := make(map[string]interface{})
	m[n.key] = n.treeToMap(r)

	return m,nil
}

// DocToTree - convert an XML doc into a tree of nodes.
func DocToTree(doc string) (*Node, error) {
	// xml.Decoder doesn't properly handle whitespace in some doc
	// see songTextString.xml test case ...
	reg,_ := regexp.Compile("[ \t\n\r]*<")
	doc = reg.ReplaceAllString(doc,"<")

	b := bytes.NewBufferString(doc)
	p := xml.NewDecoder(b)
	n, berr := xmlToTree("",nil,p)
	if berr != nil {
		return nil, berr
	}

	return n,nil
}

// (*Node)WriteTree - convert a tree of nodes into a printable string.
//	'padding' is the starting indentation; typically: n.WriteTree().
func (n *Node)WriteTree(padding ...int) string {
	var indent int
	if len(padding) == 1 {
		indent = padding[0]
	}

	var s string
	if n.val != "" {
		for i := 0 ; i < indent ; i++ {
			s += "  "
		}
		s += n.key+" : "+n.val+"\n"
	} else {
		for i := 0 ; i < indent ; i++ {
			s += "  "
		}
		s += n.key+" :"+"\n"
		for _,nn := range n.nodes {
			s += nn.WriteTree(indent+1)
		}
	}
	return s
}

// xmlToTree - load a 'clean' XML doc into a tree of *Node.
func xmlToTree(skey string,a []xml.Attr,p *xml.Decoder) (*Node, error) {
	n := new(Node)
	n.nodes = make([]*Node,0)

	if skey != "" {
		n.key = skey
		if len(a) > 0 {
			for _,v := range a {
				na := new(Node)
				na.key = `-`+v.Name.Local
				na.val = v.Value
				n.nodes = append(n.nodes,na)
			}
		}
	}
	for {
		t,err := p.Token()
		if err != nil {
			return nil, err
		}
		switch t.(type) {
			case xml.StartElement:
				tt := t.(xml.StartElement)
				// handle root
				if n.key == "" {
					n.key = tt.Name.Local
					if len(tt.Attr) > 0 {
						for _,v := range tt.Attr {
							na := new(Node)
							na.key = `-`+v.Name.Local
							na.val = v.Value
							n.nodes = append(n.nodes,na)
						}
					}
				} else {
					nn, nnerr := xmlToTree(tt.Name.Local,tt.Attr,p)
					if nnerr != nil {
						return nil, nnerr
					}
					n.nodes = append(n.nodes,nn)
				}
			case xml.EndElement:
				// scan n.nodes for duplicate n.key values
				n.markDuplicateKeys()
				return n, nil
			case xml.CharData:
				tt := string(t.(xml.CharData))
				if len(n.nodes) > 0 {
					nn := new(Node)
					nn.key = "#text"
					nn.val = tt
					n.nodes = append(n.nodes,nn)
				} else {
					n.val = tt
				}
			default:
				// noop
		}
	}
	// Logically we can't get here, but provide an error message anyway.
	return nil, errors.New("Unknown parse error in xmlToTree() for: "+n.key)
}

// (*Node)markDuplicateKeys - set node.dup flag for loading map[string]interface{}.
func (n *Node)markDuplicateKeys() {
	l := len(n.nodes)
	for i := 0 ; i < l ; i++ {
		if n.nodes[i].dup {
			continue
		}
		for j := i+1 ; j < l ; j++ {
			if n.nodes[i].key == n.nodes[j].key {
				n.nodes[i].dup = true
				n.nodes[j].dup = true
			}
		}
	}
}

// (*Node)treeToMap - convert a tree of nodes into a map[string]interface{}.
//	(Parses to map that is structurally the same as from json.Unmarshal().)
// Note: root is not instantiated; call with: "m[n.key] = treeToMap()".
func (n *Node)treeToMap(r bool) interface{} {
	if len(n.nodes) == 0 {
		return recast(n.val,r)
	}

	m := make(map[string]interface{},0)
	for _,v := range n.nodes {
		// just a value
		if !v.dup && len(v.nodes) == 0 {
			m[v.key] = recast(v.val,r)
			continue
		}

		// a list of values
		if v.dup {
			var a []interface{}
			if vv,ok := m[v.key]; ok {
				a = vv.([]interface{})
			} else {
				a = make([]interface{},0)
			}
			a = append(a,v.treeToMap(r))
			m[v.key] = interface{}(a)
			continue
		}

		// it's a unique key
		m[v.key] = v.treeToMap(r)
	}

	return interface{}(m)
}

// recast - try to cast string values to bool or float64
func recast(s string,r bool) interface{} {
	if r {
		// handle numeric strings ahead of boolean
		if f, err := strconv.ParseFloat(s,64); err == nil {
			return interface{}(f)
		}
		// ParseBool treats "1"==true & "0"==false
		if b, err := strconv.ParseBool(s); err == nil {
			return interface{}(b)
		}
	}
	return interface{}(s)
}

// WriteMap - dumps the map[string]interface{} for examination.
//	'offset' is initial indentation count; typically: WriteMap(m).
//	NOTE: with XML all element types are 'string'.
//	But code written as generic for use with maps[string]interface{} values from json.Unmarshal().
// Or it can handle a DocToMap(doc,true) result where values have be recast'd.
func WriteMap(m interface{}, offset ...int) string {
	var indent int
	if len(offset) == 1 {
		indent = offset[0]
	}

	var s string
	switch m.(type) {
		case nil:
			return "[nil] nil"
		case string:
			return "[string] "+m.(string)
		case float64:
			return "[float64] "+strconv.FormatFloat(m.(float64),'e',2,64)
		case bool:
			return "[bool] "+strconv.FormatBool(m.(bool))
		case []interface{}:
			s += "[[]interface{}]"
			for i,v := range m.([]interface{}) {
				s += "\n"
				for i := 0 ; i < indent ; i++ {
					s += "  "
				}
				s += "[item: "+strconv.FormatInt(int64(i),10)+"]"
				switch v.(type) {
					case string,float64,bool:
						s += "\n"
					default:
						// noop
				}
				for i := 0 ; i < indent ; i++ {
					s += "  "
				}
				s += WriteMap(v,indent+1)
			}
		case map[string]interface{}:
			for k,v := range m.(map[string]interface{}) {
				s += "\n"
				for i := 0 ; i < indent ; i++ {
					s += "  "
				}
				// s += "[map[string]interface{}] "+k+" :"+WriteMap(v,indent+1)
				s += k+" :"+WriteMap(v,indent+1)
		}
		default:
			// shouldn't ever be here ...
			s += fmt.Sprintf("unknown type for: %v",m)
	}
	return s
}

// ValueInMap - retrieves value based on walking the map, 'm'.
//	'path' is a period-separated hierarchy of keys in the map.
//	If the path can't be traversed, an error is returned.
func ValueInMap(m map[string]interface{},path string) (interface{}, error) {
	keys := strings.Split(path,".")

	// initialize return value to 'm' so a path of "" will work correctly
	var v interface{} = m
	var ok bool
	var isMap bool = true
	for _,key := range keys {
		if !isMap {
			return nil, errors.New("value type is not map[string]interface{}. Looking for: "+key)
		}
		if v,ok = m[key]; !ok {
			return nil, errors.New("no key in map: "+key)
		} else {
			switch v.(type) {
				case map[string]interface{}:
					m = v.(map[string]interface{})
					isMap = true
				default:
					isMap = false
			}
		}
	}
	return v, nil
}

// DocToValue - return a value for a specific tag
//	'path' is a hierarchy of XML tags, e.g., "doc.name"
//	The optional argument 'recast' will try and coerce the string values to float64 or bool.
func DocToValue(doc, path string,recast ...bool) (interface{},error) {
	var r bool
	if len(recast) == 1 {
		r = recast[0]
	}
	n,err := DocToTree(doc)
	if err != nil {
		return nil,err
	}

	m := make(map[string]interface{})
	m[n.key] = n.treeToMap(r)

	v,verr := ValueInMap(m,path)
	if verr != nil {
		return nil, verr
	}
	return v,nil
}

<commit_msg>Add comment.<commit_after>// Copyright 2012 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
/*
	Unmarshal an arbitrary XML doc to a map[string]interface{} or a JSON string.

	DocToMap() returns an intermediate result with the XML doc unmarshal'd to a map
	of type map[string]interface{}. It is analogous to unmarshal'ng a JSON string to
	a map using json.Unmarshal(). (This was the original purpose of this library.)

	DocToTree()/WriteTree() let you examine the parsed XML doc.

	XML values are all type 'string'. The optional argument 'recast' for DocToJson()
	and DocToMap() will convert element values to JSON data types - 'float64' and 'bool' -
	if possible.  This, however, should be done with caution as it will recast ALL numeric
	and boolean string values, even those that are meant to be of type 'string'.
 */
package x2j

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"regexp"
	"strings"
	"strconv"
)

type Node struct {
	dup bool
	key string
	val string
	nodes []*Node
}

// DocToJson - return an XML doc as a JSON string.
//	If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
func DocToJson(doc string,recast ...bool) (string,error) {
	var r bool
	if len(recast) == 1 {
		r = recast[0]
	}
	m,merr := DocToMap(doc,r)
	if m == nil || merr != nil {
		return "",merr
	}

	b, berr := json.Marshal(m)
	if berr != nil {
		return "",berr
	}

	return string(b),nil
}

// DocToJsonIndent - return an XML doc as a prettified JSON string.
//	If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
func DocToJsonIndent(doc string,recast ...bool) (string,error) {
	var r bool
	if len(recast) == 1 {
		r = recast[0]
	}
	m,merr := DocToMap(doc,r)
	if m == nil || merr != nil {
		return "",merr
	}

	b, berr := json.MarshalIndent(m,"","  ")
	if berr != nil {
		return "",berr
	}

	return string(b),nil
}

// DocToMap - convert an XML doc into a map[string]interface{}.
// (This is analogous to unmarshalling a JSON string to map[string]interface{} using json.Unmarshal().)
//	If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
func DocToMap(doc string,recast ...bool) (map[string]interface{},error) {
	var r bool
	if len(recast) == 1 {
		r = recast[0]
	}
	n,err := DocToTree(doc)
	if err != nil {
		return nil,err
	}

	m := make(map[string]interface{})
	m[n.key] = n.treeToMap(r)

	return m,nil
}

// DocToValue - return a value for a specific tag
//	'doc' is a valid XML message.
//	'path' is a hierarchy of XML tags, e.g., "doc.name".
//	The optional argument 'recast' will try and coerce the string values to float64 or bool.
func DocToValue(doc, path string,recast ...bool) (interface{},error) {
	var r bool
	if len(recast) == 1 {
		r = recast[0]
	}
	n,err := DocToTree(doc)
	if err != nil {
		return nil,err
	}

	m := make(map[string]interface{})
	m[n.key] = n.treeToMap(r)

	v,verr := ValueInMap(m,path)
	if verr != nil {
		return nil, verr
	}
	return v,nil
}

// DocToTree - convert an XML doc into a tree of nodes.
func DocToTree(doc string) (*Node, error) {
	// xml.Decoder doesn't properly handle whitespace in some doc
	// see songTextString.xml test case ...
	reg,_ := regexp.Compile("[ \t\n\r]*<")
	doc = reg.ReplaceAllString(doc,"<")

	b := bytes.NewBufferString(doc)
	p := xml.NewDecoder(b)
	n, berr := xmlToTree("",nil,p)
	if berr != nil {
		return nil, berr
	}

	return n,nil
}

// (*Node)WriteTree - convert a tree of nodes into a printable string.
//	'padding' is the starting indentation; typically: n.WriteTree().
func (n *Node)WriteTree(padding ...int) string {
	var indent int
	if len(padding) == 1 {
		indent = padding[0]
	}

	var s string
	if n.val != "" {
		for i := 0 ; i < indent ; i++ {
			s += "  "
		}
		s += n.key+" : "+n.val+"\n"
	} else {
		for i := 0 ; i < indent ; i++ {
			s += "  "
		}
		s += n.key+" :"+"\n"
		for _,nn := range n.nodes {
			s += nn.WriteTree(indent+1)
		}
	}
	return s
}

// xmlToTree - load a 'clean' XML doc into a tree of *Node.
func xmlToTree(skey string,a []xml.Attr,p *xml.Decoder) (*Node, error) {
	n := new(Node)
	n.nodes = make([]*Node,0)

	if skey != "" {
		n.key = skey
		if len(a) > 0 {
			for _,v := range a {
				na := new(Node)
				na.key = `-`+v.Name.Local
				na.val = v.Value
				n.nodes = append(n.nodes,na)
			}
		}
	}
	for {
		t,err := p.Token()
		if err != nil {
			return nil, err
		}
		switch t.(type) {
			case xml.StartElement:
				tt := t.(xml.StartElement)
				// handle root
				if n.key == "" {
					n.key = tt.Name.Local
					if len(tt.Attr) > 0 {
						for _,v := range tt.Attr {
							na := new(Node)
							na.key = `-`+v.Name.Local
							na.val = v.Value
							n.nodes = append(n.nodes,na)
						}
					}
				} else {
					nn, nnerr := xmlToTree(tt.Name.Local,tt.Attr,p)
					if nnerr != nil {
						return nil, nnerr
					}
					n.nodes = append(n.nodes,nn)
				}
			case xml.EndElement:
				// scan n.nodes for duplicate n.key values
				n.markDuplicateKeys()
				return n, nil
			case xml.CharData:
				tt := string(t.(xml.CharData))
				if len(n.nodes) > 0 {
					nn := new(Node)
					nn.key = "#text"
					nn.val = tt
					n.nodes = append(n.nodes,nn)
				} else {
					n.val = tt
				}
			default:
				// noop
		}
	}
	// Logically we can't get here, but provide an error message anyway.
	return nil, errors.New("Unknown parse error in xmlToTree() for: "+n.key)
}

// (*Node)markDuplicateKeys - set node.dup flag for loading map[string]interface{}.
func (n *Node)markDuplicateKeys() {
	l := len(n.nodes)
	for i := 0 ; i < l ; i++ {
		if n.nodes[i].dup {
			continue
		}
		for j := i+1 ; j < l ; j++ {
			if n.nodes[i].key == n.nodes[j].key {
				n.nodes[i].dup = true
				n.nodes[j].dup = true
			}
		}
	}
}

// (*Node)treeToMap - convert a tree of nodes into a map[string]interface{}.
//	(Parses to map that is structurally the same as from json.Unmarshal().)
// Note: root is not instantiated; call with: "m[n.key] = treeToMap()".
func (n *Node)treeToMap(r bool) interface{} {
	if len(n.nodes) == 0 {
		return recast(n.val,r)
	}

	m := make(map[string]interface{},0)
	for _,v := range n.nodes {
		// just a value
		if !v.dup && len(v.nodes) == 0 {
			m[v.key] = recast(v.val,r)
			continue
		}

		// a list of values
		if v.dup {
			var a []interface{}
			if vv,ok := m[v.key]; ok {
				a = vv.([]interface{})
			} else {
				a = make([]interface{},0)
			}
			a = append(a,v.treeToMap(r))
			m[v.key] = interface{}(a)
			continue
		}

		// it's a unique key
		m[v.key] = v.treeToMap(r)
	}

	return interface{}(m)
}

// recast - try to cast string values to bool or float64
func recast(s string,r bool) interface{} {
	if r {
		// handle numeric strings ahead of boolean
		if f, err := strconv.ParseFloat(s,64); err == nil {
			return interface{}(f)
		}
		// ParseBool treats "1"==true & "0"==false
		if b, err := strconv.ParseBool(s); err == nil {
			return interface{}(b)
		}
	}
	return interface{}(s)
}

// WriteMap - dumps the map[string]interface{} for examination.
//	'offset' is initial indentation count; typically: WriteMap(m).
//	NOTE: with XML all element types are 'string'.
//	But code written as generic for use with maps[string]interface{} values from json.Unmarshal().
// Or it can handle a DocToMap(doc,true) result where values have be recast'd.
func WriteMap(m interface{}, offset ...int) string {
	var indent int
	if len(offset) == 1 {
		indent = offset[0]
	}

	var s string
	switch m.(type) {
		case nil:
			return "[nil] nil"
		case string:
			return "[string] "+m.(string)
		case float64:
			return "[float64] "+strconv.FormatFloat(m.(float64),'e',2,64)
		case bool:
			return "[bool] "+strconv.FormatBool(m.(bool))
		case []interface{}:
			s += "[[]interface{}]"
			for i,v := range m.([]interface{}) {
				s += "\n"
				for i := 0 ; i < indent ; i++ {
					s += "  "
				}
				s += "[item: "+strconv.FormatInt(int64(i),10)+"]"
				switch v.(type) {
					case string,float64,bool:
						s += "\n"
					default:
						// noop
				}
				for i := 0 ; i < indent ; i++ {
					s += "  "
				}
				s += WriteMap(v,indent+1)
			}
		case map[string]interface{}:
			for k,v := range m.(map[string]interface{}) {
				s += "\n"
				for i := 0 ; i < indent ; i++ {
					s += "  "
				}
				// s += "[map[string]interface{}] "+k+" :"+WriteMap(v,indent+1)
				s += k+" :"+WriteMap(v,indent+1)
		}
		default:
			// shouldn't ever be here ...
			s += fmt.Sprintf("unknown type for: %v",m)
	}
	return s
}

// ValueInMap - retrieves value based on walking the map, 'm'.
//	'm' is the map value of interest.
//	'path' is a period-separated hierarchy of keys in the map.
//	If the path can't be traversed, an error is returned.
func ValueInMap(m map[string]interface{},path string) (interface{}, error) {
	keys := strings.Split(path,".")

	// initialize return value to 'm' so a path of "" will work correctly
	var v interface{} = m
	var ok bool
	var isMap bool = true
	for _,key := range keys {
		if !isMap {
			return nil, errors.New("value type is not map[string]interface{}. Looking for: "+key)
		}
		if v,ok = m[key]; !ok {
			return nil, errors.New("no key in map: "+key)
		} else {
			switch v.(type) {
				case map[string]interface{}:
					m = v.(map[string]interface{})
					isMap = true
				default:
					isMap = false
			}
		}
	}
	return v, nil
}

<|endoftext|>


<commit_before>// Copyright 2017 The go-xdg Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package xdg implements a freedesktop.org XDG Base Directory Specification.
// XDG Base Directory Specification:
//  https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.8.html
//
// The XDG Base Directory Specification is based on the following concepts:
//
// There is a single base directory relative to which user-specific data files should be written. This directory is defined by the environment variable $XDG_DATA_HOME.
//
// There is a single base directory relative to which user-specific configuration files should be written. This directory is defined by the environment variable $XDG_CONFIG_HOME.
//
// There is a set of preference ordered base directories relative to which data files should be searched. This set of directories is defined by the environment variable $XDG_DATA_DIRS.
//
// There is a set of preference ordered base directories relative to which configuration files should be searched. This set of directories is defined by the environment variable $XDG_CONFIG_DIRS.
//
// There is a single base directory relative to which user-specific non-essential (cached) data should be written. This directory is defined by the environment variable $XDG_CACHE_HOME.
package xdg

import (
	"log"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
)

var usr = &user.User{}

func init() {
	cUser, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}
	usr = cUser
}

// DataHome return the XDG_DATA_HOME based directory path.
//
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
func DataHome() string {
	dataHome := os.Getenv("XDG_DATA_HOME")
	if dataHome == "" {
		dataHome = filepath.Join(homeDir(), ".local", "share")
	}
	return dataHome
}

// ConfigHome return the XDG_CONFIG_HOME based directory path.
//
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.
func ConfigHome() string {
	configHome := os.Getenv("XDG_CONFIG_HOME")
	if configHome == "" {
		configHome = filepath.Join(homeDir(), ".config")
	}
	return configHome
}

// DataDirs return the XDG_DATA_DIRS based directory path.
//
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition
// to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
func DataDirs() string {
	dataDirs := os.Getenv("XDG_DATA_DIRS")
	if dataDirs == "" {
		dataDirs = filepath.Join("usr", "local", "share", string(filepath.ListSeparator), "usr", "share")
	}
	return dataDirs
}

// ConfigDirs return the XDG_CONFIG_DIRS based directory path.
//
// $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for configuration files in addition
// to the $XDG_CONFIG_HOME base directory. The directories in $XDG_CONFIG_DIRS should be seperated with a colon ':'.
// If $XDG_CONFIG_DIRS is either not set or empty, a value equal to /etc/xdg should be used.
func ConfigDirs() string {
	configDirs := os.Getenv("XDG_CONFIG_DIRS")
	if configDirs == "" {
		configDirs = filepath.Join("etc", "xdg")
	}
	return configDirs
}

// CacheHome return the XDG_CACHE_HOME based directory path.
//
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
func CacheHome() string {
	cacheHome := os.Getenv("XDG_CACHE_HOME")
	if cacheHome == "" {
		cacheHome = filepath.Join(homeDir(), ".cache")
	}
	return cacheHome
}

// RuntimeDir return the XDG_RUNTIME_DIR based directory path.
//
// $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and
// other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user,
// and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
func RuntimeDir() string {
	runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
	if runtimeDir == "" {
		runtimeDir = filepath.Join("run", "user", usr.Uid)
	}
	return runtimeDir
}

func homeDir() string {
	if runtime.GOOS == "windows" {
		home := filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"))
		if home == "" {
			home = os.Getenv("USERPROFILE")
		}
		return home
	}

	homeDir := os.Getenv("HOME")
	if homeDir != "" {
		homeDir = usr.HomeDir
	}

	return homeDir
}
<commit_msg>xdg: fix homeDir behavior & add some TODO for OS specific issue<commit_after>// Copyright 2017 The go-xdg Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package xdg implements a freedesktop.org XDG Base Directory Specification.
// XDG Base Directory Specification:
//  https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.8.html
//
// The XDG Base Directory Specification is based on the following concepts:
//
// There is a single base directory relative to which user-specific data files should be written. This directory is defined by the environment variable $XDG_DATA_HOME.
//
// There is a single base directory relative to which user-specific configuration files should be written. This directory is defined by the environment variable $XDG_CONFIG_HOME.
//
// There is a set of preference ordered base directories relative to which data files should be searched. This set of directories is defined by the environment variable $XDG_DATA_DIRS.
//
// There is a set of preference ordered base directories relative to which configuration files should be searched. This set of directories is defined by the environment variable $XDG_CONFIG_DIRS.
//
// There is a single base directory relative to which user-specific non-essential (cached) data should be written. This directory is defined by the environment variable $XDG_CACHE_HOME.
package xdg

import (
	"log"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
)

var usrHome = os.Getenv("HOME")
var usr = &user.User{}

// TODO(zchee): Support cross-platform compile.
// user.Current() uses cgo build in the Go stdlib internal.
func init() {
	cUser, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}
	usr = cUser
}

// DataHome return the XDG_DATA_HOME based directory path.
//
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
func DataHome() string {
	dataHome := os.Getenv("XDG_DATA_HOME")
	if dataHome == "" {
		dataHome = filepath.Join(homeDir(), ".local", "share")
	}
	return dataHome
}

// ConfigHome return the XDG_CONFIG_HOME based directory path.
//
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.
func ConfigHome() string {
	configHome := os.Getenv("XDG_CONFIG_HOME")
	if configHome == "" {
		configHome = filepath.Join(homeDir(), ".config")
	}
	return configHome
}

// DataDirs return the XDG_DATA_DIRS based directory path.
//
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition
// to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
func DataDirs() string {
	dataDirs := os.Getenv("XDG_DATA_DIRS")
	if dataDirs == "" {
		dataDirs = filepath.Join("usr", "local", "share", string(filepath.ListSeparator), "usr", "share")
	}
	return dataDirs
}

// ConfigDirs return the XDG_CONFIG_DIRS based directory path.
//
// $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for configuration files in addition
// to the $XDG_CONFIG_HOME base directory. The directories in $XDG_CONFIG_DIRS should be seperated with a colon ':'.
// If $XDG_CONFIG_DIRS is either not set or empty, a value equal to /etc/xdg should be used.
func ConfigDirs() string {
	configDirs := os.Getenv("XDG_CONFIG_DIRS")
	if configDirs == "" {
		configDirs = filepath.Join("etc", "xdg")
	}
	return configDirs
}

// CacheHome return the XDG_CACHE_HOME based directory path.
//
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
//
// TODO(zchee): In macOS, Is it better to use the ~/Library/Caches directory? Or add the configurable by users setting?
// Apple's "File System Programming Guide" describe the this directory should be used if users cache files.
// However, some user who is using the macOS as Unix-like prefers $HOME/.cache.
//  https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html#//apple_ref/doc/uid/TP40010672-CH10-SW1
func CacheHome() string {
	cacheHome := os.Getenv("XDG_CACHE_HOME")
	if cacheHome == "" {
		cacheHome = filepath.Join(homeDir(), ".cache")
	}
	return cacheHome
}

// RuntimeDir return the XDG_RUNTIME_DIR based directory path.
//
// $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and
// other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user,
// and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
//
// TODO(zchee): Avoid use usr.Uid for support the cross-platform compile.
// TODO(zchee): XDG_RUNTIME_DIR seems to change depending on the each distro or init system such as systemd.
// Also In macOS, normal user haven't permission for write to this directory.
func RuntimeDir() string {
	runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
	if runtimeDir == "" {
		runtimeDir = filepath.Join("run", "user", usr.Uid)
	}
	return runtimeDir
}

func homeDir() string {
	if usrHome != "" {
		return usrHome
	}

	// TODO(zchee): In Windows OS, which of $HOME and these checks has priority?
	if runtime.GOOS == "windows" {
		usrHome = filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"))
		if usrHome == "" {
			usrHome = os.Getenv("USERPROFILE")
		}
		return usrHome
	}

	usrHome = usr.HomeDir

	return usrHome
}
<|endoftext|>


<commit_before>// Copyright 2011 Miek Gieben. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dns

// Envelope is used when doing [IA]XFR with a remote server.
type Envelope struct {
	RR    []RR  // The set of RRs in the answer section of the AXFR reply message.
	Error error // If something went wrong, this contains the error.
}

// TransferIn performs a [AI]XFR request (depends on the message's Qtype). It returns
// a channel of *Envelope on which the replies from the server are sent. At the end of
// the transfer the channel is closed.
// The messages are TSIG checked if
// needed, no other post-processing is performed. The caller must dissect the returned
// messages.
//
// Basic use pattern for receiving an AXFR:
//
//	// m contains the AXFR request
//	t, e := c.TransferIn(m, "127.0.0.1:53")
//	for r := range t {
//		// ... deal with r.RR or r.Error
//	}
func (c *Client) TransferIn(q *Msg, a string) (chan *Envelope, error) {
	w := new(reply)
	w.client = c
	w.addr = a
	w.req = q
	if err := w.dial(); err != nil {
		return nil, err
	}
	if err := w.send(q); err != nil {
		return nil, err
	}
	e := make(chan *Envelope)
	switch q.Question[0].Qtype {
	case TypeAXFR:
		go w.axfrIn(q, e)
		return e, nil
	case TypeIXFR:
		go w.ixfrIn(q, e)
		return e, nil
	default:
		return nil, nil
	}
	panic("dns: not reached")
}

func (w *reply) axfrIn(q *Msg, c chan *Envelope) {
	first := true
	defer w.conn.Close()
	defer close(c)
	for {
		in, err := w.receive()
		if err != nil {
			c <- &Envelope{nil, err}
			return
		}
		if in.Id != q.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			if !checkXfrSOA(in, true) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			first = !first
		}

		if !first {
			w.tsigTimersOnly = true // Subsequent envelopes use this.
			if checkXfrSOA(in, false) {
				c <- &Envelope{in.Answer, nil}
				return
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
	panic("dns: not reached")
}

func (w *reply) ixfrIn(q *Msg, c chan *Envelope) {
	var serial uint32 // The first serial seen is the current server serial
	first := true
	defer w.conn.Close()
	defer close(c)
	for {
		in, err := w.receive()
		if err != nil {
			c <- &Envelope{in.Answer, err}
			return
		}
		if q.Id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			// A single SOA RR signals "no changes"
			if len(in.Answer) == 1 && checkXfrSOA(in, true) {
				c <- &Envelope{in.Answer, nil}
				return
			}

			// Check if the returned answer is ok
			if !checkXfrSOA(in, true) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			// This serial is important
			serial = in.Answer[0].(*SOA).Serial
			first = !first
		}

		// Now we need to check each message for SOA records, to see what we need to do
		if !first {
			w.tsigTimersOnly = true
			// If the last record in the IXFR contains the servers' SOA,  we should quit
			if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok {
				if v.Serial == serial {
					c <- &Envelope{in.Answer, nil}
					return
				}
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
	panic("dns: not reached")
}

// Check if he SOA record exists in the Answer section of
// the packet. If first is true the first RR must be a SOA
// if false, the last one should be a SOA.
func checkXfrSOA(in *Msg, first bool) bool {
	if len(in.Answer) > 0 {
		if first {
			return in.Answer[0].Header().Rrtype == TypeSOA
		} else {
			return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA
		}
	}
	return false
}

// TransferOut performs an outgoing [AI]XFR depending on the request message. The
// caller is responsible for sending the correct sequence of RR sets through
// the channel c. For reasons of symmetry Envelope is re-used.
// Errors are signaled via the error pointer, when an error occurs the function
// sets the error and returns (it does not close the channel).
// TSIG and enveloping is handled by TransferOut.
//
// Basic use pattern for sending an AXFR:
//
//	// q contains the AXFR request
//	c := make(chan *Envelope)
//	var e *error
//	err := TransferOut(w, q, c, e)
//	w.Hijack()		// hijack the connection so that the package doesn't close it
//	for _, rrset := range rrsets {	// rrsets is a []RR
//		c <- &{Envelope{RR: rrset}
//		if e != nil {
//			close(c)
//			break
//		}
//	}
//	// w.Close() // Don't! Let the client close the connection
func TransferOut(w ResponseWriter, q *Msg, c chan *Envelope, e *error) error {
	switch q.Question[0].Qtype {
	case TypeAXFR, TypeIXFR:
		go xfrOut(w, q, c, e)
		return nil
	default:
		return nil
	}
	panic("dns: not reached")
}

// TODO(mg): count the RRs and the resulting size.
func xfrOut(w ResponseWriter, req *Msg, c chan *Envelope, e *error) {
	rep := new(Msg)
	rep.SetReply(req)
	rep.Authoritative = true

	for x := range c {
		// assume it fits
		rep.Answer = append(rep.Answer, x.RR...)
		if err := w.WriteMsg(rep); e != nil {
			*e = err
			return
		}
		w.TsigTimersOnly(true)
		rep.Answer = nil
	}
}
<commit_msg>Do not fail AXFR if first anwser is just SOA<commit_after>// Copyright 2011 Miek Gieben. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dns

// Envelope is used when doing [IA]XFR with a remote server.
type Envelope struct {
	RR    []RR  // The set of RRs in the answer section of the AXFR reply message.
	Error error // If something went wrong, this contains the error.
}

// TransferIn performs a [AI]XFR request (depends on the message's Qtype). It returns
// a channel of *Envelope on which the replies from the server are sent. At the end of
// the transfer the channel is closed.
// The messages are TSIG checked if
// needed, no other post-processing is performed. The caller must dissect the returned
// messages.
//
// Basic use pattern for receiving an AXFR:
//
//	// m contains the AXFR request
//	t, e := c.TransferIn(m, "127.0.0.1:53")
//	for r := range t {
//		// ... deal with r.RR or r.Error
//	}
func (c *Client) TransferIn(q *Msg, a string) (chan *Envelope, error) {
	w := new(reply)
	w.client = c
	w.addr = a
	w.req = q
	if err := w.dial(); err != nil {
		return nil, err
	}
	if err := w.send(q); err != nil {
		return nil, err
	}
	e := make(chan *Envelope)
	switch q.Question[0].Qtype {
	case TypeAXFR:
		go w.axfrIn(q, e)
		return e, nil
	case TypeIXFR:
		go w.ixfrIn(q, e)
		return e, nil
	default:
		return nil, nil
	}
	panic("dns: not reached")
}

func (w *reply) axfrIn(q *Msg, c chan *Envelope) {
	first := true
	defer w.conn.Close()
	defer close(c)
	for {
		in, err := w.receive()
		if err != nil {
			c <- &Envelope{nil, err}
			return
		}
		if in.Id != q.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			if !checkXfrSOA(in, true) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			first = !first
			// only one answer that is SOA, receive more
			if (len(in.Answer) == 1) {
				w.tsigTimersOnly = true
				c <- &Envelope{in.Answer, nil}
				continue
			}
		}

		if !first {
			w.tsigTimersOnly = true // Subsequent envelopes use this.
			if checkXfrSOA(in, false) {
				c <- &Envelope{in.Answer, nil}
				return
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
	panic("dns: not reached")
}

func (w *reply) ixfrIn(q *Msg, c chan *Envelope) {
	var serial uint32 // The first serial seen is the current server serial
	first := true
	defer w.conn.Close()
	defer close(c)
	for {
		in, err := w.receive()
		if err != nil {
			c <- &Envelope{in.Answer, err}
			return
		}
		if q.Id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			// A single SOA RR signals "no changes"
			if len(in.Answer) == 1 && checkXfrSOA(in, true) {
				c <- &Envelope{in.Answer, nil}
				return
			}

			// Check if the returned answer is ok
			if !checkXfrSOA(in, true) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			// This serial is important
			serial = in.Answer[0].(*SOA).Serial
			first = !first
		}

		// Now we need to check each message for SOA records, to see what we need to do
		if !first {
			w.tsigTimersOnly = true
			// If the last record in the IXFR contains the servers' SOA,  we should quit
			if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok {
				if v.Serial == serial {
					c <- &Envelope{in.Answer, nil}
					return
				}
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
	panic("dns: not reached")
}

// Check if he SOA record exists in the Answer section of
// the packet. If first is true the first RR must be a SOA
// if false, the last one should be a SOA.
func checkXfrSOA(in *Msg, first bool) bool {
	if len(in.Answer) > 0 {
		if first {
			return in.Answer[0].Header().Rrtype == TypeSOA
		} else {
			return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA
		}
	}
	return false
}

// TransferOut performs an outgoing [AI]XFR depending on the request message. The
// caller is responsible for sending the correct sequence of RR sets through
// the channel c. For reasons of symmetry Envelope is re-used.
// Errors are signaled via the error pointer, when an error occurs the function
// sets the error and returns (it does not close the channel).
// TSIG and enveloping is handled by TransferOut.
//
// Basic use pattern for sending an AXFR:
//
//	// q contains the AXFR request
//	c := make(chan *Envelope)
//	var e *error
//	err := TransferOut(w, q, c, e)
//	w.Hijack()		// hijack the connection so that the package doesn't close it
//	for _, rrset := range rrsets {	// rrsets is a []RR
//		c <- &{Envelope{RR: rrset}
//		if e != nil {
//			close(c)
//			break
//		}
//	}
//	// w.Close() // Don't! Let the client close the connection
func TransferOut(w ResponseWriter, q *Msg, c chan *Envelope, e *error) error {
	switch q.Question[0].Qtype {
	case TypeAXFR, TypeIXFR:
		go xfrOut(w, q, c, e)
		return nil
	default:
		return nil
	}
	panic("dns: not reached")
}

// TODO(mg): count the RRs and the resulting size.
func xfrOut(w ResponseWriter, req *Msg, c chan *Envelope, e *error) {
	rep := new(Msg)
	rep.SetReply(req)
	rep.Authoritative = true

	for x := range c {
		// assume it fits
		rep.Answer = append(rep.Answer, x.RR...)
		if err := w.WriteMsg(rep); e != nil {
			*e = err
			return
		}
		w.TsigTimersOnly(true)
		rep.Answer = nil
	}
}
<|endoftext|>