<commit_before>package projects

import (
	"encoding/json"
	"net/http"

	h "github.com/ernestio/api-gateway/helpers"
	"github.com/ernestio/api-gateway/models"
)

// Update : responds to PUT /projects/:id: by updating
// an existing project
func Update(au models.User, project string, body []byte) (int, []byte) {
	var err error
	var d models.Project
	var existing models.Project
	var r models.Role
	var roles []models.Role

	if d.Map(body) != nil {
		return 400, models.NewJSONError("Invalid input")
	}

	if err = existing.FindByName(project); err != nil {
		return 404, models.NewJSONError("Project not found")
	}

	if st, res := h.IsAuthorizedToResource(&au, h.UpdateProject, d.GetType(), d.Name); st != 200 {
		return st, res
	}

	if err = r.FindAllByResource(existing.GetID(), existing.GetType(), &roles); err == nil {
		existing.Members = roles
	}

	existing.Credentials = d.Credentials

	if err = existing.Save(); err != nil {
		h.L.Error(err.Error())
		return 500, models.NewJSONError("Internal server error")
	}

	if d.Members == nil {
		if body, err = json.Marshal(d); err != nil {
			return 500, models.NewJSONError("Internal server error")
		}

		return http.StatusOK, body
	}

	for _, r := range d.Members {
		if r.ID == 0 {
			if !au.IsAdmin() {
				if ok := au.IsOwner(r.ResourceType, r.ResourceID); !ok {
					return 403, models.NewJSONError("You're not authorized to perform this action")
				}
			}

			err = r.Save()
			if err != nil {
				h.L.Error(err.Error())
				return http.StatusBadRequest, models.NewJSONError(err.Error())
			}

			continue
		}

		for _, er := range existing.Members {
			// update role
			if r.ID == er.ID && r.Role != er.Role {
				if !au.IsAdmin() {
					if ok := au.IsOwner(r.ResourceType, r.ResourceID); !ok {
						return 403, models.NewJSONError("You're not authorized to perform this action")
					}
				}

				err = r.Save()
				if err != nil {
					h.L.Error(err.Error())
					return http.StatusBadRequest, models.NewJSONError(err.Error())
				}
			}
		}
	}

	for _, er := range existing.Members {
		var exists bool

		for _, r := range d.Members {
			if r.ID == er.ID {
				exists = true
			}
		}

		// delete roles
		if !exists {
			if !au.IsAdmin() {
				if ok := au.IsOwner(er.ResourceType, er.ResourceID); !ok {
					return 403, models.NewJSONError("You're not authorized to perform this action")
				}
			}

			err = er.Delete()
			if err != nil {
				h.L.Error(err.Error())
				return http.StatusBadRequest, models.NewJSONError(err.Error())
			}
		}
	}

	if body, err = json.Marshal(d); err != nil {
		return 500, models.NewJSONError("Internal server error")
	}

	return http.StatusOK, body
}
<commit_msg>fixing tests<commit_after>package projects

import (
	"encoding/json"
	"net/http"
	"strconv"

	h "github.com/ernestio/api-gateway/helpers"
	"github.com/ernestio/api-gateway/models"
)

// Update : responds to PUT /projects/:id: by updating
// an existing project
func Update(au models.User, project string, body []byte) (int, []byte) {
	var err error
	var d models.Project
	var existing models.Project
	var r models.Role
	var roles []models.Role

	if d.Map(body) != nil {
		return 400, models.NewJSONError("Invalid input")
	}

	if err = existing.FindByName(project); err != nil {
		id, err := strconv.Atoi(project)
		if err = existing.FindByID(id); err != nil {
			return 404, models.NewJSONError("Project not found")
		}
	}

	if st, res := h.IsAuthorizedToResource(&au, h.UpdateProject, d.GetType(), d.Name); st != 200 {
		return st, res
	}

	if err = r.FindAllByResource(existing.GetID(), existing.GetType(), &roles); err == nil {
		existing.Members = roles
	}

	existing.Credentials = d.Credentials

	if err = existing.Save(); err != nil {
		h.L.Error(err.Error())
		return 500, models.NewJSONError("Internal server error")
	}

	if d.Members == nil {
		if body, err = json.Marshal(d); err != nil {
			return 500, models.NewJSONError("Internal server error")
		}

		return http.StatusOK, body
	}

	for _, r := range d.Members {
		if r.ID == 0 {
			if !au.IsAdmin() {
				if ok := au.IsOwner(r.ResourceType, r.ResourceID); !ok {
					return 403, models.NewJSONError("You're not authorized to perform this action")
				}
			}

			err = r.Save()
			if err != nil {
				h.L.Error(err.Error())
				return http.StatusBadRequest, models.NewJSONError(err.Error())
			}

			continue
		}

		for _, er := range existing.Members {
			// update role
			if r.ID == er.ID && r.Role != er.Role {
				if !au.IsAdmin() {
					if ok := au.IsOwner(r.ResourceType, r.ResourceID); !ok {
						return 403, models.NewJSONError("You're not authorized to perform this action")
					}
				}

				err = r.Save()
				if err != nil {
					h.L.Error(err.Error())
					return http.StatusBadRequest, models.NewJSONError(err.Error())
				}
			}
		}
	}

	for _, er := range existing.Members {
		var exists bool

		for _, r := range d.Members {
			if r.ID == er.ID {
				exists = true
			}
		}

		// delete roles
		if !exists {
			if !au.IsAdmin() {
				if ok := au.IsOwner(er.ResourceType, er.ResourceID); !ok {
					return 403, models.NewJSONError("You're not authorized to perform this action")
				}
			}

			err = er.Delete()
			if err != nil {
				h.L.Error(err.Error())
				return http.StatusBadRequest, models.NewJSONError(err.Error())
			}
		}
	}

	if body, err = json.Marshal(d); err != nil {
		return 500, models.NewJSONError("Internal server error")
	}

	return http.StatusOK, body
}
<|endoftext|>


<commit_before>package web

import "gopkg.in/webnice/debug.v1"
import "gopkg.in/webnice/log.v2"
import (
	"crypto/tls"
	"net"
	"os"
)

// Wait while web server is running
func (wsv *web) Wait() Interface { wsv.doCloseDone.Wait(); return wsv }

// ListenAndServe listens on the TCP network address addr and then calls Serve with handler to handle requests on incoming connections
func (wsv *web) ListenAndServe(addr string) Interface {
	var conf *Configuration

	if conf, wsv.err = parseAddress(addr); wsv.err != nil {
		return wsv
	}

	return wsv.ListenAndServeWithConfig(conf)
}

// ListenAndServeTLS listens on the TCP network address address with TLS and then calls Serve with handler
// to handle requests on incoming connections
func (wsv *web) ListenAndServeTLS(addr string, certFile string, keyFile string, tlsConfig *tls.Config) Interface {
	var conf *Configuration

	if conf, wsv.err = parseAddress(addr); wsv.err != nil {
		return wsv
	}
	conf.TLSPublicKeyPEM, conf.TLSPrivateKeyPEM = certFile, keyFile

	return wsv.ListenAndServeTLSWithConfig(conf, tlsConfig)
}

// ListenAndServeWithConfig Fully configurable web server listens and then calls Serve on incoming connections
func (wsv *web) ListenAndServeWithConfig(conf *Configuration) Interface {
	if conf == nil {
		wsv.err = ErrNoConfiguration()
		return wsv
	}
	wsv.conf = conf

	return wsv.Listen(nil)
}

// ListenAndServeTLSWithConfig Fully configurable web server listens and then calls Serve on incoming connections
func (wsv *web) ListenAndServeTLSWithConfig(conf *Configuration, tlsConfig *tls.Config) Interface {
	if conf == nil {
		wsv.err = ErrNoConfiguration()
		return wsv
	}
	wsv.conf = conf
	if tlsConfig == nil {
		if tlsConfig, wsv.err = wsv.tlsConfigDefault(conf.TLSPublicKeyPEM, conf.TLSPrivateKeyPEM); wsv.err != nil {
			return wsv
		}
	}

	return wsv.Listen(tlsConfig)
}

// NewListener Make new listener from web server configuration
func (wsv *web) NewListener(conf *Configuration) (ret net.Listener, err error) {
	var (
		lstWithNames map[string][]net.Listener
		listeners    []net.Listener
		ok           bool
	)

	defaultConfiguration(conf)
	switch conf.Mode {
	case netSystemd:
		if conf.Socket != "" {
			// Имена сокетов указаны
			if lstWithNames, err = wsv.ListenersSystemdWithNames(false); err != nil {
				return
			}
			// Выбор сокета по имени
			if listeners, ok = lstWithNames[conf.Socket]; ok {
				err = ErrListenSystemdNotFound()
				return
			}
		} else {
			// Имена сокетов не указаны
			if listeners, err = wsv.ListenersSystemdWithoutNames(false); err != nil {
				return
			}
		}
		if len(listeners) == 0 {
			log.Debug(debug.DumperString(lstWithNames, listeners, os.Environ()))
			err = ErrListenSystemdUnexpectedNumber()
			return
		}
		ret = listeners[0]
	case netUnix, netUnixPacket:
		_ = os.Remove(conf.Socket)
		ret, err = net.Listen(conf.Mode, conf.Socket)
		_ = os.Chmod(conf.Socket, os.FileMode(0666))
	default:
		ret, err = net.Listen(conf.Mode, conf.HostPort)
	}

	return
}

// NewListenerTLS Make new listener with TLS from web server configuration
func (wsv *web) NewListenerTLS(conf *Configuration, tlsConfig *tls.Config) (ret net.Listener, err error) {
	var l net.Listener

	if l, err = wsv.NewListener(conf); err != nil {
		return
	}
	if tlsConfig == nil {
		if tlsConfig, err = wsv.tlsConfigDefault(conf.TLSPublicKeyPEM, conf.TLSPrivateKeyPEM); err != nil {
			return
		}
	}
	ret = tls.NewListener(l, tlsConfig)

	return
}

// Конфигурация TLS по умолчанию
func (wsv *web) tlsConfigDefault(tlsPublicFile string, tlsPrivateFile string) (ret *tls.Config, err error) {
	ret = &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
		},
		Certificates: make([]tls.Certificate, 1),
	}
	if ret.Certificates[0], err = tls.LoadX509KeyPair(tlsPublicFile, tlsPrivateFile); err != nil {
		return
	}

	return
}

// Listen Begin listen port and web server serve
func (wsv *web) Listen(tlsConfig *tls.Config) Interface {
	var ltn net.Listener

	if wsv.isRun.Load().(bool) {
		wsv.err = ErrAlreadyRunning()
		return wsv
	}
	switch tlsConfig == nil {
	case true:
		ltn, wsv.err = wsv.NewListener(wsv.conf)
	case false:
		ltn, wsv.err = wsv.NewListenerTLS(wsv.conf, tlsConfig)
	}
	if wsv.err != nil {
		return wsv
	}

	return wsv.ServeTLS(ltn, tlsConfig)
}

// Serve accepts incoming connections on the listener, creating a new web server goroutine
func (wsv *web) Serve(ltn net.Listener) Interface { return wsv.ServeTLS(ltn, nil) }

// ServeTLS accepts incoming connections on the listener with TLS configuration, creating a new web server goroutine
func (wsv *web) ServeTLS(ltn net.Listener, tlsConfig *tls.Config) Interface {
	var conf *Configuration

	// TODO: Реализовать поддержку PROXY Protocol через "gopkg.in/webnice/web.v1/proxyp", conf.ProxyProtocol

	if wsv.conf == nil {
		conf, _ = parseAddress(ltn.Addr().String())
		defaultConfiguration(conf)
		wsv.conf = conf
	}
	wsv.listener = ltn
	wsv.isRun.Store(true)
	wsv.doCloseDone.Add(1)
	go wsv.run(tlsConfig)

	return wsv
}

// Goroutine of the web server
func (wsv *web) run(tlsConfig *tls.Config) {
	defer wsv.doCloseDone.Done()
	defer wsv.isRun.Store(false)
	defer func() {
		if wsv.conf.Socket == "" {
			return
		}
		switch wsv.conf.Mode {
		case netSystemd:
			return
		case netUnix, netUnixPacket:
			_ = os.Remove(wsv.conf.Socket)
		}
	}()

	// Configure net/http web server
	if wsv.server = wsv.loadConfiguration(tlsConfig); wsv.err != nil {
		return
	}
	// Configure keepalive of web server
	if wsv.conf.KeepAliveDisable {
		wsv.server.SetKeepAlivesEnabled(false)
	}
	// Begin serve
	if wsv.conf.TLSPrivateKeyPEM == "" || wsv.conf.TLSPublicKeyPEM == "" {
		wsv.err = wsv.server.Serve(wsv.listener)
		return
	}
	wsv.err = wsv.server.ServeTLS(wsv.listener, wsv.conf.TLSPublicKeyPEM, wsv.conf.TLSPrivateKeyPEM)
}
<commit_msg>Debug added temporarily<commit_after>package web

import "gopkg.in/webnice/debug.v1"
import "gopkg.in/webnice/log.v2"
import (
	"crypto/tls"
	"net"
	"os"
)

// Wait while web server is running
func (wsv *web) Wait() Interface { wsv.doCloseDone.Wait(); return wsv }

// ListenAndServe listens on the TCP network address addr and then calls Serve with handler to handle requests on incoming connections
func (wsv *web) ListenAndServe(addr string) Interface {
	var conf *Configuration

	if conf, wsv.err = parseAddress(addr); wsv.err != nil {
		return wsv
	}

	return wsv.ListenAndServeWithConfig(conf)
}

// ListenAndServeTLS listens on the TCP network address address with TLS and then calls Serve with handler
// to handle requests on incoming connections
func (wsv *web) ListenAndServeTLS(addr string, certFile string, keyFile string, tlsConfig *tls.Config) Interface {
	var conf *Configuration

	if conf, wsv.err = parseAddress(addr); wsv.err != nil {
		return wsv
	}
	conf.TLSPublicKeyPEM, conf.TLSPrivateKeyPEM = certFile, keyFile

	return wsv.ListenAndServeTLSWithConfig(conf, tlsConfig)
}

// ListenAndServeWithConfig Fully configurable web server listens and then calls Serve on incoming connections
func (wsv *web) ListenAndServeWithConfig(conf *Configuration) Interface {
	if conf == nil {
		wsv.err = ErrNoConfiguration()
		return wsv
	}
	wsv.conf = conf

	return wsv.Listen(nil)
}

// ListenAndServeTLSWithConfig Fully configurable web server listens and then calls Serve on incoming connections
func (wsv *web) ListenAndServeTLSWithConfig(conf *Configuration, tlsConfig *tls.Config) Interface {
	if conf == nil {
		wsv.err = ErrNoConfiguration()
		return wsv
	}
	wsv.conf = conf
	if tlsConfig == nil {
		if tlsConfig, wsv.err = wsv.tlsConfigDefault(conf.TLSPublicKeyPEM, conf.TLSPrivateKeyPEM); wsv.err != nil {
			return wsv
		}
	}

	return wsv.Listen(tlsConfig)
}

// NewListener Make new listener from web server configuration
func (wsv *web) NewListener(conf *Configuration) (ret net.Listener, err error) {
	var (
		lstWithNames map[string][]net.Listener
		listeners    []net.Listener
		ok           bool
	)

	defaultConfiguration(conf)
	switch conf.Mode {
	case netSystemd:
		if conf.Socket != "" {
			// Имена сокетов указаны
			if lstWithNames, err = wsv.ListenersSystemdWithNames(false); err != nil {
				return
			}
			// Выбор сокета по имени
			if listeners, ok = lstWithNames[conf.Socket]; !ok {
				err = ErrListenSystemdNotFound()
				return
			}
		} else {
			// Имена сокетов не указаны
			if listeners, err = wsv.ListenersSystemdWithoutNames(false); err != nil {
				return
			}
		}
		log.Debug(debug.DumperString(lstWithNames, listeners, os.Environ(), conf.Socket, ok))
		if len(listeners) == 0 {
			err = ErrListenSystemdUnexpectedNumber()
			return
		}
		ret = listeners[0]
	case netUnix, netUnixPacket:
		_ = os.Remove(conf.Socket)
		ret, err = net.Listen(conf.Mode, conf.Socket)
		_ = os.Chmod(conf.Socket, os.FileMode(0666))
	default:
		ret, err = net.Listen(conf.Mode, conf.HostPort)
	}

	return
}

// NewListenerTLS Make new listener with TLS from web server configuration
func (wsv *web) NewListenerTLS(conf *Configuration, tlsConfig *tls.Config) (ret net.Listener, err error) {
	var l net.Listener

	if l, err = wsv.NewListener(conf); err != nil {
		return
	}
	if tlsConfig == nil {
		if tlsConfig, err = wsv.tlsConfigDefault(conf.TLSPublicKeyPEM, conf.TLSPrivateKeyPEM); err != nil {
			return
		}
	}
	ret = tls.NewListener(l, tlsConfig)

	return
}

// Конфигурация TLS по умолчанию
func (wsv *web) tlsConfigDefault(tlsPublicFile string, tlsPrivateFile string) (ret *tls.Config, err error) {
	ret = &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
		},
		Certificates: make([]tls.Certificate, 1),
	}
	if ret.Certificates[0], err = tls.LoadX509KeyPair(tlsPublicFile, tlsPrivateFile); err != nil {
		return
	}

	return
}

// Listen Begin listen port and web server serve
func (wsv *web) Listen(tlsConfig *tls.Config) Interface {
	var ltn net.Listener

	if wsv.isRun.Load().(bool) {
		wsv.err = ErrAlreadyRunning()
		return wsv
	}
	switch tlsConfig == nil {
	case true:
		ltn, wsv.err = wsv.NewListener(wsv.conf)
	case false:
		ltn, wsv.err = wsv.NewListenerTLS(wsv.conf, tlsConfig)
	}
	if wsv.err != nil {
		return wsv
	}

	return wsv.ServeTLS(ltn, tlsConfig)
}

// Serve accepts incoming connections on the listener, creating a new web server goroutine
func (wsv *web) Serve(ltn net.Listener) Interface { return wsv.ServeTLS(ltn, nil) }

// ServeTLS accepts incoming connections on the listener with TLS configuration, creating a new web server goroutine
func (wsv *web) ServeTLS(ltn net.Listener, tlsConfig *tls.Config) Interface {
	var conf *Configuration

	// TODO: Реализовать поддержку PROXY Protocol через "gopkg.in/webnice/web.v1/proxyp", conf.ProxyProtocol

	if wsv.conf == nil {
		conf, _ = parseAddress(ltn.Addr().String())
		defaultConfiguration(conf)
		wsv.conf = conf
	}
	wsv.listener = ltn
	wsv.isRun.Store(true)
	wsv.doCloseDone.Add(1)
	go wsv.run(tlsConfig)

	return wsv
}

// Goroutine of the web server
func (wsv *web) run(tlsConfig *tls.Config) {
	defer wsv.doCloseDone.Done()
	defer wsv.isRun.Store(false)
	defer func() {
		if wsv.conf.Socket == "" {
			return
		}
		switch wsv.conf.Mode {
		case netSystemd:
			return
		case netUnix, netUnixPacket:
			_ = os.Remove(wsv.conf.Socket)
		}
	}()

	// Configure net/http web server
	if wsv.server = wsv.loadConfiguration(tlsConfig); wsv.err != nil {
		return
	}
	// Configure keepalive of web server
	if wsv.conf.KeepAliveDisable {
		wsv.server.SetKeepAlivesEnabled(false)
	}
	// Begin serve
	if wsv.conf.TLSPrivateKeyPEM == "" || wsv.conf.TLSPublicKeyPEM == "" {
		wsv.err = wsv.server.Serve(wsv.listener)
		return
	}
	wsv.err = wsv.server.ServeTLS(wsv.listener, wsv.conf.TLSPublicKeyPEM, wsv.conf.TLSPrivateKeyPEM)
}
<|endoftext|>


<commit_before>package html

import (
	"code.google.com/p/go.net/html"
	"errors"
	"github.com/slyrz/newscat/util"
	"io"
	"regexp"
	"unicode"
)

const (
	// We remember a few special node types when descending into their
	// children.
	AncestorArticle = 1 << iota
	AncestorAside
	AncestorBlockquote
	AncestorList
)

var (
	ignorePattern = regexp.MustCompile("(?i)comment|community|gallery|caption|description|credit|foot|story-feature|related|hide|hidden")
)

type Document struct {
	Title  *util.Text // the <title>...</title> text.
	Chunks []*Chunk   // list of all chunks found in this document

	// Unexported fields.
	root *html.Node // the ... part
	head *html.Node // the ... part
	body *html.Node // the ... part

	// State variables used when collectiong chunks.
	ancestors int // bitmask which stores ancestor of the current node

	// Number of non-space characters inside link tags / normal tags
	// per html.ElementNode.
	linkText map[*html.Node]int // length of text inside  tags
	normText map[*html.Node]int // length of text outside  tags
}

func NewDocument(r io.Reader) (*Document, error) {
	doc := new(Document)
	doc.Chunks = make([]*Chunk, 0, 512)
	if err := doc.Parse(r); err != nil {
		return nil, err
	}
	return doc, nil
}

func (doc *Document) Parse(r io.Reader) error {
	root, err := html.Parse(r)
	if err != nil {
		return err
	}
	// Assign the fields root, head and body from the HTML page.
	doc.setNodes(root)

	// Check if ,  and  nodes were found.
	if doc.root == nil || doc.head == nil || doc.body == nil {
		return errors.New("Document missing ,  or .")
	}

	doc.parseHead(doc.head)

	// If no title was found, detecting the main heading might fail.
	if doc.Title == nil {
		doc.Title = util.NewText()
	}
	doc.linkText = make(map[*html.Node]int)
	doc.normText = make(map[*html.Node]int)

	doc.cleanBody(doc.body, 0)
	doc.countText(doc.body, false)
	doc.parseBody(doc.body)

	// Now link the chunks.
	for i := range doc.Chunks {
		if i > 0 {
			doc.Chunks[i].Prev = doc.Chunks[i-1]
		}
		if i < len(doc.Chunks)-1 {
			doc.Chunks[i].Next = doc.Chunks[i+1]
		}
	}
	return nil
}

// Assign the struct fields root, head and body from the HTML tree of node n.
//  doc.root ->
//  doc.head ->
//  doc.body ->
func (doc *Document) setNodes(n *html.Node) {
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		switch c.Data {
		case "html":
			doc.root = c
			doc.setNodes(c)
		case "body":
			doc.body = c
		case "head":
			doc.head = c
		}
	}
}

// parseHead parses the ... part of the HTML page. Right now it
// only detects the <title>...</title>.
func (doc *Document) parseHead(n *html.Node) {
	if n.Type == html.ElementNode && n.Data == "title" {
		if chunk, err := NewChunk(doc, n); err == nil {
			doc.Title = chunk.Text
		}
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		doc.parseHead(c)
	}
}

// countText counts the link text and the normal text per html.Node.
// "Link text" is text inside  tags and "normal text" is text inside
// anything but  tags. Of course, counting is done cumulative, so the
// numbers of a parent node include the numbers of it's child nodes.
func (doc *Document) countText(n *html.Node, insideLink bool) (linkText int, normText int) {
	linkText = 0
	normText = 0
	if n.Type == html.ElementNode && n.Data == "a" {
		insideLink = true
	}
	for s := n.FirstChild; s != nil; s = s.NextSibling {
		linkTextChild, normTextChild := doc.countText(s, insideLink)
		linkText += linkTextChild
		normText += normTextChild
	}
	if n.Type == html.TextNode {
		count := 0
		for _, rune := range n.Data {
			if unicode.IsLetter(rune) {
				count += 1
			}
		}
		if insideLink {
			linkText += count
		} else {
			normText += count
		}
	}
	doc.linkText[n] = linkText
	doc.normText[n] = normText
	return
}

// cleanBody removes unwanted HTML elements from the HTML body.
func (doc *Document) cleanBody(n *html.Node, level int) {

	// removeNode returns true if a node should be removed from HTML document.
	removeNode := func(c *html.Node, level int) bool {
		switch c.Data {
		// Elements save to ignore.
		case "address", "audio", "button", "canvas", "caption", "fieldset",
			"figcaption", "figure", "footer", "form", "frame", "iframe",
			"map", "menu", "nav", "noscript", "object", "option", "output",
			"script", "select", "style", "svg", "textarea", "video":
			return true
		// High-level tables might be used to layout the document, so we better
		// not ignore them.
		case "table":
			return level > 5
		}
		return false
	}

	var curr *html.Node = n.FirstChild
	var next *html.Node = nil
	for ; curr != nil; curr = next {
		// We have to remember the next sibling here becase calling RemoveChild
		// sets curr's NextSibling pointer to nil and we would quit the loop
		// prematurely.
		next = curr.NextSibling
		if curr.Type == html.ElementNode {
			if removeNode(curr, level) {
				n.RemoveChild(curr)
			} else {
				doc.cleanBody(curr, level+1)
			}
		}
	}
}

// parseBody parses the ... part of the HTML page. It creates
// Chunks for every html.TextNode found in the body.
func (doc *Document) parseBody(n *html.Node) {
	switch n.Type {
	case html.ElementNode:
		// We ignore the node if it has some nasty classes/ids/itemprobs.
		for _, attr := range n.Attr {
			switch attr.Key {
			case "id", "class", "itemprop":
				if ignorePattern.FindStringIndex(attr.Val) != nil {
					return
				}
			}
		}
		ancestorMask := 0
		switch n.Data {
		// We convert headings and links to text immediately. This is easier
		// and feasible because headings and links don't contain many children.
		// Descending into these children and handling every TextNode separately
		// would make things unnecessary complicated and our results noisy.
		case "h1", "h2", "h3", "h4", "h5", "h6", "a":
			if chunk, err := NewChunk(doc, n); err == nil {
				doc.Chunks = append(doc.Chunks, chunk)
			}
			return
		// Now mask the element type, but only if it isn't already set.
		// If we mask a bit which was already set by one of our callers, we'd also
		// clear it at the end of this function, though it actually should be cleared
		// by the caller.
		case "article":
			ancestorMask = AncestorArticle &^ doc.ancestors
		case "aside":
			ancestorMask = AncestorAside &^ doc.ancestors
		case "blockquote":
			ancestorMask = AncestorBlockquote &^ doc.ancestors
		case "ul", "ol":
			ancestorMask = AncestorList &^ doc.ancestors
		}
		// Add our mask to the ancestor bitmask.
		doc.ancestors |= ancestorMask
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			doc.parseBody(c)
		}
		// Remove our mask from the ancestor bitmask.
		doc.ancestors &^= ancestorMask
	case html.TextNode:
		if chunk, err := NewChunk(doc, n); err == nil {
			doc.Chunks = append(doc.Chunks, chunk)
		}
	}
}

type TextStat struct {
	Words     int
	Sentences int
	Count     int
}

// GetClassStats groups the document chunks by their classes (defined by the
// class attribute of HTML nodes) and calculates TextStats for each class.
func (doc *Document) GetClassStats() map[string]*TextStat {
	result := make(map[string]*TextStat)
	for _, chunk := range doc.Chunks {
		for _, class := range chunk.Classes {
			if stat, ok := result[class]; ok {
				stat.Words += chunk.Text.Words
				stat.Sentences += chunk.Text.Sentences
				stat.Count += 1
			} else {
				result[class] = &TextStat{chunk.Text.Words, chunk.Text.Sentences, 1}
			}
		}
	}
	return result
}

// GetClusterStats groups the document chunks by common ancestors and
// calculates TextStats for each group of chunks.
func (doc *Document) GetClusterStats() map[*Chunk]*TextStat {
	// Don't ascend further than this constant.
	const maxAncestors = 3

	// Count TextStats for Chunk ancestors.
	ancestorStat := make(map[*html.Node]*TextStat)
	for _, chunk := range doc.Chunks {
		node, count := chunk.Block, 0
		for node != nil && count < maxAncestors {
			if stat, ok := ancestorStat[node]; ok {
				stat.Words += chunk.Text.Words
				stat.Sentences += chunk.Text.Sentences
				stat.Count += 1
			} else {
				ancestorStat[node] = &TextStat{chunk.Text.Words, chunk.Text.Sentences, 1}
			}
			node, count = node.Parent, count+1
		}
	}

	// Generate result.
	result := make(map[*Chunk]*TextStat)
	for _, chunk := range doc.Chunks {
		node := chunk.Block
		if node == nil {
			continue
		}
		// Start with the parent's TextStat. Then ascend and check if the
		// current chunk has an ancestor with better stats. Use the best stat
		// as result.
		stat := ancestorStat[node]
		for {
			if node = node.Parent; node == nil {
				break
			}
			if statPrev, ok := ancestorStat[node]; ok {
				if stat.Count < statPrev.Count {
					stat = statPrev
				}
			} else {
				break
			}
		}
		result[chunk] = stat
	}
	return result
}
<commit_msg>renamed variables and changed regex creation<commit_after>package html

import (
	"code.google.com/p/go.net/html"
	"errors"
	"github.com/slyrz/newscat/util"
	"io"
	"regexp"
	"strings"
	"unicode"
)

const (
	// We remember a few special node types when descending into their
	// children.
	AncestorArticle = 1 << iota
	AncestorAside
	AncestorBlockquote
	AncestorList
)

var (
	badNames *regexp.Regexp = nil
)

func init() {
	// Create a case insensitive regular expression which matches all given
	// arguments.
	buildRegex := func(words ...string) *regexp.Regexp {
		return regexp.MustCompile("(?i)" + strings.Join(words, "|"))
	}

	// If a class/id/itemprop value contains one of these words, we ignore the
	// element and all of it's children. So chose the words wisely.
	badNames = buildRegex(
		"caption",
		"comment",
		"community",
		"credit",
		"description",
		"foot",
		"gallery",
		"hidden",
		"hide",
		"related",
		"story-feature",
	)
}

type Document struct {
	Title  *util.Text // the <title>...</title> text.
	Chunks []*Chunk   // list of all chunks found in this document

	// Unexported fields.
	html *html.Node // the ... part
	head *html.Node // the ... part
	body *html.Node // the ... part

	// State variables used when collectiong chunks.
	ancestors int // bitmask which stores ancestor of the current node

	// Number of non-space characters inside link tags / normal tags
	// per html.ElementNode.
	linkText map[*html.Node]int // length of text inside  tags
	normText map[*html.Node]int // length of text outside  tags
}

func NewDocument(r io.Reader) (*Document, error) {
	doc := new(Document)
	doc.Chunks = make([]*Chunk, 0, 512)
	if err := doc.Parse(r); err != nil {
		return nil, err
	}
	return doc, nil
}

func (doc *Document) Parse(r io.Reader) error {
	root, err := html.Parse(r)
	if err != nil {
		return err
	}
	// Assign the fields html, head and body from the HTML page.
	doc.setNodes(root)

	// Check if ,  and  nodes were found.
	if doc.html == nil || doc.head == nil || doc.body == nil {
		return errors.New("Document missing ,  or .")
	}

	doc.parseHead(doc.head)

	// If no title was found, detecting the main heading might fail.
	if doc.Title == nil {
		doc.Title = util.NewText()
	}
	doc.linkText = make(map[*html.Node]int)
	doc.normText = make(map[*html.Node]int)

	doc.cleanBody(doc.body, 0)
	doc.countText(doc.body, false)
	doc.parseBody(doc.body)

	// Now link the chunks.
	for i := range doc.Chunks {
		if i > 0 {
			doc.Chunks[i].Prev = doc.Chunks[i-1]
		}
		if i < len(doc.Chunks)-1 {
			doc.Chunks[i].Next = doc.Chunks[i+1]
		}
	}
	return nil
}

// Assign the struct fields html, head and body from the HTML tree of node n.
//  doc.html ->
//  doc.head ->
//  doc.body ->
func (doc *Document) setNodes(n *html.Node) {
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		switch c.Data {
		case "html":
			doc.html = c
			doc.setNodes(c)
		case "body":
			doc.body = c
		case "head":
			doc.head = c
		}
	}
}

// parseHead parses the ... part of the HTML page. Right now it
// only detects the <title>...</title>.
func (doc *Document) parseHead(n *html.Node) {
	if n.Type == html.ElementNode && n.Data == "title" {
		if chunk, err := NewChunk(doc, n); err == nil {
			doc.Title = chunk.Text
		}
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		doc.parseHead(c)
	}
}

// countText counts the link text and the normal text per html.Node.
// "Link text" is text inside  tags and "normal text" is text inside
// anything but  tags. Of course, counting is done cumulative, so the
// numbers of a parent node include the numbers of it's child nodes.
func (doc *Document) countText(n *html.Node, insideLink bool) (linkText int, normText int) {
	linkText = 0
	normText = 0
	if n.Type == html.ElementNode && n.Data == "a" {
		insideLink = true
	}
	for s := n.FirstChild; s != nil; s = s.NextSibling {
		linkTextChild, normTextChild := doc.countText(s, insideLink)
		linkText += linkTextChild
		normText += normTextChild
	}
	if n.Type == html.TextNode {
		count := 0
		for _, rune := range n.Data {
			if unicode.IsLetter(rune) {
				count += 1
			}
		}
		if insideLink {
			linkText += count
		} else {
			normText += count
		}
	}
	doc.linkText[n] = linkText
	doc.normText[n] = normText
	return
}

// cleanBody removes unwanted HTML elements from the HTML body.
func (doc *Document) cleanBody(n *html.Node, level int) {

	// removeNode returns true if a node should be removed from HTML document.
	removeNode := func(c *html.Node, level int) bool {
		switch c.Data {
		// Elements save to ignore.
		case "address", "audio", "button", "canvas", "caption", "fieldset",
			"figcaption", "figure", "footer", "form", "frame", "iframe",
			"map", "menu", "nav", "noscript", "object", "option", "output",
			"script", "select", "style", "svg", "textarea", "video":
			return true
		// High-level tables might be used to layout the document, so we better
		// not ignore them.
		case "table":
			return level > 5
		}
		return false
	}

	var curr *html.Node = n.FirstChild
	var next *html.Node = nil
	for ; curr != nil; curr = next {
		// We have to remember the next sibling here becase calling RemoveChild
		// sets curr's NextSibling pointer to nil and we would quit the loop
		// prematurely.
		next = curr.NextSibling
		if curr.Type == html.ElementNode {
			if removeNode(curr, level) {
				n.RemoveChild(curr)
			} else {
				doc.cleanBody(curr, level+1)
			}
		}
	}
}

// parseBody parses the ... part of the HTML page. It creates
// Chunks for every html.TextNode found in the body.
func (doc *Document) parseBody(n *html.Node) {
	switch n.Type {
	case html.ElementNode:
		// We ignore the node if it has some nasty classes/ids/itemprobs.
		for _, attr := range n.Attr {
			switch attr.Key {
			case "id", "class", "itemprop":
				if badNames.FindStringIndex(attr.Val) != nil {
					return
				}
			}
		}
		ancestorMask := 0
		switch n.Data {
		// We convert headings and links to text immediately. This is easier
		// and feasible because headings and links don't contain many children.
		// Descending into these children and handling every TextNode separately
		// would make things unnecessary complicated and our results noisy.
		case "h1", "h2", "h3", "h4", "h5", "h6", "a":
			if chunk, err := NewChunk(doc, n); err == nil {
				doc.Chunks = append(doc.Chunks, chunk)
			}
			return
		// Now mask the element type, but only if it isn't already set.
		// If we mask a bit which was already set by one of our callers, we'd also
		// clear it at the end of this function, though it actually should be cleared
		// by the caller.
		case "article":
			ancestorMask = AncestorArticle &^ doc.ancestors
		case "aside":
			ancestorMask = AncestorAside &^ doc.ancestors
		case "blockquote":
			ancestorMask = AncestorBlockquote &^ doc.ancestors
		case "ul", "ol":
			ancestorMask = AncestorList &^ doc.ancestors
		}
		// Add our mask to the ancestor bitmask.
		doc.ancestors |= ancestorMask
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			doc.parseBody(c)
		}
		// Remove our mask from the ancestor bitmask.
		doc.ancestors &^= ancestorMask
	case html.TextNode:
		if chunk, err := NewChunk(doc, n); err == nil {
			doc.Chunks = append(doc.Chunks, chunk)
		}
	}
}

type TextStat struct {
	Words     int
	Sentences int
	Count     int
}

// GetClassStats groups the document chunks by their classes (defined by the
// class attribute of HTML nodes) and calculates TextStats for each class.
func (doc *Document) GetClassStats() map[string]*TextStat {
	result := make(map[string]*TextStat)
	for _, chunk := range doc.Chunks {
		for _, class := range chunk.Classes {
			if stat, ok := result[class]; ok {
				stat.Words += chunk.Text.Words
				stat.Sentences += chunk.Text.Sentences
				stat.Count += 1
			} else {
				result[class] = &TextStat{chunk.Text.Words, chunk.Text.Sentences, 1}
			}
		}
	}
	return result
}

// GetClusterStats groups the document chunks by common ancestors and
// calculates TextStats for each group of chunks.
func (doc *Document) GetClusterStats() map[*Chunk]*TextStat {
	// Don't ascend further than this constant.
	const maxAncestors = 3

	// Count TextStats for Chunk ancestors.
	ancestorStat := make(map[*html.Node]*TextStat)
	for _, chunk := range doc.Chunks {
		node, count := chunk.Block, 0
		for node != nil && count < maxAncestors {
			if stat, ok := ancestorStat[node]; ok {
				stat.Words += chunk.Text.Words
				stat.Sentences += chunk.Text.Sentences
				stat.Count += 1
			} else {
				ancestorStat[node] = &TextStat{chunk.Text.Words, chunk.Text.Sentences, 1}
			}
			node, count = node.Parent, count+1
		}
	}

	// Generate result.
	result := make(map[*Chunk]*TextStat)
	for _, chunk := range doc.Chunks {
		node := chunk.Block
		if node == nil {
			continue
		}
		// Start with the parent's TextStat. Then ascend and check if the
		// current chunk has an ancestor with better stats. Use the best stat
		// as result.
		stat := ancestorStat[node]
		for {
			if node = node.Parent; node == nil {
				break
			}
			if statPrev, ok := ancestorStat[node]; ok {
				if stat.Count < statPrev.Count {
					stat = statPrev
				}
			} else {
				break
			}
		}
		result[chunk] = stat
	}
	return result
}
<|endoftext|>


<commit_before>package http

import (
	"encoding/hex"
	"net/http"

	"github.com/hashicorp/vault/vault"
)

func handleSysInit(core *vault.Core) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			handleSysInitGet(core, w, r)
		case "PUT":
			handleSysInitPut(core, w, r)
		default:
			respondError(w, http.StatusMethodNotAllowed, nil)
		}
	})
}

func handleSysInitGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
	init, err := core.Initialized()
	if err != nil {
		respondError(w, http.StatusInternalServerError, err)
		return
	}

	respondOk(w, &InitStatusResponse{
		Initialized: init,
	})
}

func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) {
	// Parse the request
	var req InitRequest
	if err := parseRequest(r, &req); err != nil {
		respondError(w, http.StatusBadRequest, err)
		return
	}

	// Initialize
	result, err := core.Initialize(&vault.SealConfig{
		SecretShares:    req.SecretShares,
		SecretThreshold: req.SecretThreshold,
		PGPKeys:         req.PGPKeys,
	})
	if err != nil {
		respondError(w, http.StatusBadRequest, err)
		return
	}

	// Encode the keys
	keys := make([]string, 0, len(result.SecretShares))
	for _, k := range result.SecretShares {
		keys = append(keys, hex.EncodeToString(k))
	}

	respondOk(w, &InitResponse{
		Keys:      keys,
		RootToken: result.RootToken,
	})
}

type InitRequest struct {
	SecretShares    int      `json:"secret_shares"`
	SecretThreshold int      `json:"secret_threshold"`
	PGPKeys         []string `json:"pgp_keys"`
}

type InitResponse struct {
	Keys      []string `json:"keys"`
	RootToken string   `json:"root_token"`
}

type InitStatusResponse struct {
	Initialized bool `json:"initialized"`
}
<commit_msg>We treat put/post the same, so allow init to use POST<commit_after>package http

import (
	"encoding/hex"
	"net/http"

	"github.com/hashicorp/vault/vault"
)

func handleSysInit(core *vault.Core) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			handleSysInitGet(core, w, r)
		case "PUT", "POST":
			handleSysInitPut(core, w, r)
		default:
			respondError(w, http.StatusMethodNotAllowed, nil)
		}
	})
}

func handleSysInitGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
	init, err := core.Initialized()
	if err != nil {
		respondError(w, http.StatusInternalServerError, err)
		return
	}

	respondOk(w, &InitStatusResponse{
		Initialized: init,
	})
}

func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) {
	// Parse the request
	var req InitRequest
	if err := parseRequest(r, &req); err != nil {
		respondError(w, http.StatusBadRequest, err)
		return
	}

	// Initialize
	result, err := core.Initialize(&vault.SealConfig{
		SecretShares:    req.SecretShares,
		SecretThreshold: req.SecretThreshold,
		PGPKeys:         req.PGPKeys,
	})
	if err != nil {
		respondError(w, http.StatusBadRequest, err)
		return
	}

	// Encode the keys
	keys := make([]string, 0, len(result.SecretShares))
	for _, k := range result.SecretShares {
		keys = append(keys, hex.EncodeToString(k))
	}

	respondOk(w, &InitResponse{
		Keys:      keys,
		RootToken: result.RootToken,
	})
}

type InitRequest struct {
	SecretShares    int      `json:"secret_shares"`
	SecretThreshold int      `json:"secret_threshold"`
	PGPKeys         []string `json:"pgp_keys"`
}

type InitResponse struct {
	Keys      []string `json:"keys"`
	RootToken string   `json:"root_token"`
}

type InitStatusResponse struct {
	Initialized bool `json:"initialized"`
}
<|endoftext|>


<commit_before>package rest

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"net/http/httputil"
	"os"
	"runtime"
	"strings"
	"time"
)

var (
	isTTY     bool      // флаг, что поддерживается вывод в цвете
	accessLog io.Writer // вывод в лог
)

func init() {
	// инициализируем вывод в лог
	SetLogger(os.Stderr)
}

// SetLogger позволяет определить вывод лога обработки запросов и ошибок.
// Если установлен флаг Debug, то в лог так же пишутся все запросы, которые
// вызвали ошибку и дамп вызовов функций, приведших к panic.
func SetLogger(out io.Writer) {
	if out, ok := out.(*os.File); ok {
		fi, err := out.Stat()
		if err == nil {
			m := os.ModeDevice | os.ModeCharDevice
			isTTY = fi.Mode()&m == m
		}
	} else {
		isTTY = false
	}
	accessLog = out
}

// log выводит информацию в лог, если он определен.
func (c *Context) log() {
	if accessLog == nil {
		return // не выводим в лог, если он не определен
	}
	stop := time.Now()                   // время окончания обработки запроса
	buf := buffers.Get().(*bytes.Buffer) // формируем буфер для генерации лога
	buf.Reset()
	// время окончания обработки и вывода в лог
	buf.WriteString(stop.Format("2006/01/02 15:04:05"))
	// адрес пользователя
	remoteAddr := c.Request.Header.Get("X-Real-IP")
	// Если IP-адрес путой, то смотрим на адрес proxy
	if remoteAddr == "" {
		remoteAddr = c.Request.Header.Get("X-Forwarded-For")
	}
	// Если и этот адрес не указан, то читаем адрес socket
	if remoteAddr == "" {
		remoteAddr, _, _ = net.SplitHostPort(c.Request.RemoteAddr)
	}
	// адрес пользователя, продолжительность обработки и размер переданных данных
	fmt.Fprintf(buf, " %-15s %12v %9.3fKb ",
		remoteAddr, stop.Sub(c.started), float32(c.size)/1024)
	// Определяем цвет для вывода статуса, в зависимости от кода.
	if isTTY {
		buf.WriteString("\x1b[3")
		switch {
		case c.status < 200:
			buf.WriteRune('4') // blue
		case c.status < 300:
			buf.WriteRune('2') // green
		case c.status < 400:
			buf.WriteRune('3') // yellow
		case c.status < 500:
			buf.WriteRune('5') // magenta
		default:
			buf.WriteRune('1') // red
		}
		buf.WriteString(";2m")
	}
	fmt.Fprintf(buf, "%3d", c.status) // код ответа сервера
	if isTTY {
		buf.WriteString("\x1b[0m")
	}
	fmt.Fprintf(buf, " %7s ", c.Request.Method) // метод HTTP-запроса
	buf.WriteString(c.URL.RequestURI())         // URL
	if Debug {
		hn := c.Data(dataSet(137))
		if name, ok := hn.(string); ok {
			fmt.Fprintf(buf, " {%s}", name) // имя функции обработчика
		}
	}
	buf.WriteRune('\n')
	buf.WriteTo(accessLog)
	buffers.Put(buf)
}

// errorLog выводит в лог информацию об ошибке
func (c *Context) errorLog(err interface{}, trace bool) {
	if accessLog == nil {
		return // не выводим в лог, если он не определен
	}
	buf := buffers.Get().(*bytes.Buffer) // формируем буфер для генерации лога
	buf.Reset()
	// время окончания обработки и вывода в лог
	buf.WriteString(time.Now().Format("2006/01/02 15:04:05"))
	// публикуем саму ошибку
	if isTTY {
		buf.WriteString(" \x1b[31mError:\x1b[0m ")
	} else {
		buf.WriteString(" Error: ")
	}
	fmt.Fprintf(buf, "%v\n", err)
	// добавляем информацию о файле и строке, где произошла ошибка
	callLevel := 2
	if trace {
		callLevel = 4
	}
	if _, file, line, ok := runtime.Caller(callLevel); ok && file != "<autogenerated>" {
		if isTTY {
			buf.WriteString("## \x1b[31mFile:\x1b[0m ")
		} else {
			buf.WriteString("## File: ")
		}
		fmt.Fprintf(buf, "%s:%d\n", file, line)
	}
	if Debug {
		hn := c.Data(dataSet(137))
		if name, ok := hn.(string); ok {
			if isTTY {
				buf.WriteString("## \x1b[31mHandler:\x1b[0m ")
			} else {
				buf.WriteString("## Handler: ")
			}
			buf.WriteString(name + "\n")
		}
		if trace {
			traceBuffer := make([]byte, 1<<16)
			n := runtime.Stack(traceBuffer, false)
			if isTTY {
				buf.WriteString("## \x1b[31mStack dump\x1b[0m\n")
			} else {
				buf.WriteString("## Stack dump:\n")
			}
			buf.Write(traceBuffer[:n])
			buf.WriteString(strings.Repeat("-", 80) + "\n")
		}
	}
	if dump, err := httputil.DumpRequest(c.Request, true); err == nil {
		if isTTY {
			buf.WriteString("## \x1b[31mRequest dump\x1b[0m\n")
		} else {
			buf.WriteString("## Request dump:\n")
		}
		buf.Write(dump)
		buf.WriteRune('\n')
	}
	buf.WriteTo(accessLog)
	buffers.Put(buf)
}
<commit_msg>Вывод лога ошибки<commit_after>package rest

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"net/http/httputil"
	"os"
	"runtime"
	"strings"
	"time"
)

var (
	isTTY     bool      // флаг, что поддерживается вывод в цвете
	accessLog io.Writer // вывод в лог
)

func init() {
	// инициализируем вывод в лог
	SetLogger(os.Stderr)
}

// SetLogger позволяет определить вывод лога обработки запросов и ошибок.
// Если установлен флаг Debug, то в лог так же пишутся все запросы, которые
// вызвали ошибку и дамп вызовов функций, приведших к panic.
func SetLogger(out io.Writer) {
	if out, ok := out.(*os.File); ok {
		fi, err := out.Stat()
		if err == nil {
			m := os.ModeDevice | os.ModeCharDevice
			isTTY = fi.Mode()&m == m
		}
	} else {
		isTTY = false
	}
	accessLog = out
}

// log выводит информацию в лог, если он определен.
func (c *Context) log() {
	if accessLog == nil {
		return // не выводим в лог, если он не определен
	}
	stop := time.Now()                   // время окончания обработки запроса
	buf := buffers.Get().(*bytes.Buffer) // формируем буфер для генерации лога
	buf.Reset()
	// время окончания обработки и вывода в лог
	buf.WriteString(stop.Format("2006/01/02 15:04:05"))
	// адрес пользователя
	remoteAddr := c.Request.Header.Get("X-Real-IP")
	// Если IP-адрес путой, то смотрим на адрес proxy
	if remoteAddr == "" {
		remoteAddr = c.Request.Header.Get("X-Forwarded-For")
	}
	// Если и этот адрес не указан, то читаем адрес socket
	if remoteAddr == "" {
		remoteAddr, _, _ = net.SplitHostPort(c.Request.RemoteAddr)
	}
	// адрес пользователя, продолжительность обработки и размер переданных данных
	fmt.Fprintf(buf, " %-15s %12v %9.3fKb ",
		remoteAddr, stop.Sub(c.started), float32(c.size)/1024)
	// Определяем цвет для вывода статуса, в зависимости от кода.
	if isTTY {
		buf.WriteString("\x1b[3")
		switch {
		case c.status < 200:
			buf.WriteRune('4') // blue
		case c.status < 300:
			buf.WriteRune('2') // green
		case c.status < 400:
			buf.WriteRune('3') // yellow
		case c.status < 500:
			buf.WriteRune('5') // magenta
		default:
			buf.WriteRune('1') // red
		}
		buf.WriteString(";2m")
	}
	fmt.Fprintf(buf, "%3d", c.status) // код ответа сервера
	if isTTY {
		buf.WriteString("\x1b[0m")
	}
	fmt.Fprintf(buf, " %7s ", c.Request.Method) // метод HTTP-запроса
	buf.WriteString(c.URL.RequestURI())         // URL
	if Debug {
		hn := c.Data(dataSet(137))
		if name, ok := hn.(string); ok {
			fmt.Fprintf(buf, " {%s}", name) // имя функции обработчика
		}
	}
	buf.WriteRune('\n')
	buf.WriteTo(accessLog)
	buffers.Put(buf)
}

// errorLog выводит в лог информацию об ошибке
func (c *Context) errorLog(err interface{}, trace bool) {
	if accessLog == nil {
		return // не выводим в лог, если он не определен
	}
	buf := buffers.Get().(*bytes.Buffer) // формируем буфер для генерации лога
	buf.Reset()
	// время окончания обработки и вывода в лог
	buf.WriteString(time.Now().Format("2006/01/02 15:04:05"))
	// публикуем саму ошибку
	if isTTY {
		buf.WriteString(" \x1b[31mError:\x1b[0m ")
	} else {
		buf.WriteString(" Error: ")
	}
	fmt.Fprintf(buf, "%v\n", err)
	// добавляем информацию о файле и строке, где произошла ошибка
	callLevel := 2
	if trace {
		callLevel = 4
	}
	if _, file, line, ok := runtime.Caller(callLevel); ok && file != "<autogenerated>" {
		if isTTY {
			buf.WriteString("## \x1b[34mFile:\x1b[0m ")
		} else {
			buf.WriteString("## File: ")
		}
		fmt.Fprintf(buf, "%s:%d\n", file, line)
	}
	if Debug {
		hn := c.Data(dataSet(137))
		if name, ok := hn.(string); ok {
			if isTTY {
				buf.WriteString("## \x1b[34mHandler:\x1b[0m ")
			} else {
				buf.WriteString("## Handler: ")
			}
			buf.WriteString(name + "\n")
		}
		if dump, err := httputil.DumpRequest(c.Request, true); err == nil {
			if isTTY {
				buf.WriteString("## \x1b[34mRequest dump\x1b[0m\n")
			} else {
				buf.WriteString("## Request dump:\n")
			}
			buf.Write(dump)
			buf.WriteRune('\n')
		}
		if trace {
			traceBuffer := make([]byte, 1<<16)
			n := runtime.Stack(traceBuffer, false)
			if isTTY {
				buf.WriteString("## \x1b[34mStack dump\x1b[0m\n")
			} else {
				buf.WriteString("## Stack dump:\n")
			}
			buf.Write(traceBuffer[:n])
			buf.WriteString(strings.Repeat("-", 80) + "\n")
		}
	}
	buf.WriteTo(accessLog)
	buffers.Put(buf)
}
<|endoftext|>


<commit_before>// Copyright 2013, Örjan Persson. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package logging implements a logging infrastructure for Go. It supports
// different logging backends like syslog, file and memory. Multiple backends
// can be utilized with different log levels per backend and logger.
package logging

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"strings"
	"sync/atomic"
	"time"
)

// Redactor is an interface for types that may contain sensitive information
// (like passwords), which shouldn't be printed to the log. The idea was found
// in relog as part of the vitness project.
type Redactor interface {
	Redacted() interface{}
}

// Redact returns a string of * having the same length as s.
func Redact(s string) string {
	return strings.Repeat("*", len(s))
}

var (
	// Sequence number is incremented and utilized for all log records created.
	sequenceNo uint64

	// timeNow is a customizable for testing purposes.
	timeNow = time.Now
)

// Record represents a log record and contains the timestamp when the record
// was created, an increasing id, filename and line and finally the actual
// formatted log line.
type Record struct {
	Id     uint64
	Time   time.Time
	Module string
	Level  Level

	// message is kept as a pointer to have shallow copies update this once
	// needed.
	message   *string
	args      []interface{}
	fmt       string
	formatter Formatter
	formatted string
}

func (r *Record) Formatted(calldepth int) string {
	if r.formatted == "" {
		var buf bytes.Buffer
		r.formatter.Format(calldepth+1, r, &buf)
		r.formatted = buf.String()
	}
	return r.formatted
}

func (r *Record) Message() string {
	if r.message == nil {
		// Redact the arguments that implements the Redactor interface
		for i, arg := range r.args {
			if redactor, ok := arg.(Redactor); ok == true {
				r.args[i] = redactor.Redacted()
			}
		}
		msg := fmt.Sprintf(r.fmt, r.args...)
		r.message = &msg
	}
	return *r.message
}

type Logger struct {
	Module string
}

// TODO call NewLogger and remove MustGetLogger?
// GetLogger creates and returns a Logger object based on the module name.
func GetLogger(module string) (*Logger, error) {
	return &Logger{Module: module}, nil
}

// MustGetLogger is like GetLogger but panics if the logger can't be created.
// It simplifies safe initialization of a global logger for eg. a package.
func MustGetLogger(module string) *Logger {
	logger, err := GetLogger(module)
	if err != nil {
		panic("logger: " + module + ": " + err.Error())
	}
	return logger
}

// Reset restores the internal state of the logging library.
func Reset() {
	// TODO make a global Init() method to be less magic? or make it such that
	// if there's no backends at all configured, we could use some tricks to
	// automatically setup backends based if we have a TTY or not.
	sequenceNo = 0
	b := SetBackend(NewLogBackend(os.Stderr, "", log.LstdFlags))
	b.SetLevel(DEBUG, "")
	SetFormatter(DefaultFormatter)
	timeNow = time.Now
}

// InitForTesting is a convenient method when using logging in a test. Once
// called, the time will be frozen to January 1, 1970 UTC.
func InitForTesting(level Level) *MemoryBackend {
	Reset()

	memoryBackend := NewMemoryBackend(10240)

	leveledBackend := AddModuleLevel(memoryBackend)
	leveledBackend.SetLevel(level, "")
	SetBackend(leveledBackend)

	timeNow = func() time.Time {
		return time.Unix(0, 0).UTC()
	}
	return memoryBackend
}

// IsEnabledFor returns true if the logger is enabled for the given level.
func (l *Logger) IsEnabledFor(level Level) bool {
	return defaultBackend.IsEnabledFor(level, l.Module)
}

func (l *Logger) log(lvl Level, format string, args ...interface{}) {
	// Create the logging record and pass it in to the backend
	record := &Record{
		Id:     atomic.AddUint64(&sequenceNo, 1),
		Time:   timeNow(),
		Module: l.Module,
		Level:  lvl,
		fmt:    format,
		args:   args,
	}

	// TODO use channels to fan out the records to all backends?
	// TODO in case of errors, do something (tricky)

	// calldepth=2 brings the stack up to the caller of the level
	// methods, Info(), Fatal(), etc.
	defaultBackend.Log(lvl, 2, record)
}

// Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1).
func (l *Logger) Fatal(args ...interface{}) {
	s := fmt.Sprint(args...)
	l.log(CRITICAL, "%s", s)
	os.Exit(1)
}

// Fatalf is equivalent to l.Critical followed by a call to os.Exit(1).
func (l *Logger) Fatalf(format string, args ...interface{}) {
	l.log(CRITICAL, format, args...)
	os.Exit(1)
}

// Panic is equivalent to l.Critical(fmt.Sprint()) followed by a call to panic().
func (l *Logger) Panic(args ...interface{}) {
	s := fmt.Sprint(args...)
	l.log(CRITICAL, "%s", s)
	panic(s)
}

// Panicf is equivalent to l.Critical followed by a call to panic().
func (l *Logger) Panicf(format string, args ...interface{}) {
	s := fmt.Sprintf(format, args...)
	l.log(CRITICAL, "%s", s)
	panic(s)
}

// Critical logs a message using CRITICAL as log level.
func (l *Logger) Critical(format string, args ...interface{}) {
	l.log(CRITICAL, format, args...)
}

// Error logs a message using ERROR as log level.
func (l *Logger) Error(format string, args ...interface{}) {
	l.log(ERROR, format, args...)
}

// Warning logs a message using WARNING as log level.
func (l *Logger) Warning(format string, args ...interface{}) {
	l.log(WARNING, format, args...)
}

// Notice logs a message using NOTICE as log level.
func (l *Logger) Notice(format string, args ...interface{}) {
	l.log(NOTICE, format, args...)
}

// Info logs a message using INFO as log level.
func (l *Logger) Info(format string, args ...interface{}) {
	l.log(INFO, format, args...)
}

// Debug logs a message using DEBUG as log level.
func (l *Logger) Debug(format string, args ...interface{}) {
	l.log(DEBUG, format, args...)
}

func init() {
	Reset()
}
<commit_msg>Add ability to set backend to separate logger<commit_after>// Copyright 2013, Örjan Persson. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package logging implements a logging infrastructure for Go. It supports
// different logging backends like syslog, file and memory. Multiple backends
// can be utilized with different log levels per backend and logger.
package logging

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"strings"
	"sync/atomic"
	"time"
)

// Redactor is an interface for types that may contain sensitive information
// (like passwords), which shouldn't be printed to the log. The idea was found
// in relog as part of the vitness project.
type Redactor interface {
	Redacted() interface{}
}

// Redact returns a string of * having the same length as s.
func Redact(s string) string {
	return strings.Repeat("*", len(s))
}

var (
	// Sequence number is incremented and utilized for all log records created.
	sequenceNo uint64

	// timeNow is a customizable for testing purposes.
	timeNow = time.Now
)

// Record represents a log record and contains the timestamp when the record
// was created, an increasing id, filename and line and finally the actual
// formatted log line.
type Record struct {
	Id     uint64
	Time   time.Time
	Module string
	Level  Level

	// message is kept as a pointer to have shallow copies update this once
	// needed.
	message   *string
	args      []interface{}
	fmt       string
	formatter Formatter
	formatted string
}

func (r *Record) Formatted(calldepth int) string {
	if r.formatted == "" {
		var buf bytes.Buffer
		r.formatter.Format(calldepth+1, r, &buf)
		r.formatted = buf.String()
	}
	return r.formatted
}

func (r *Record) Message() string {
	if r.message == nil {
		// Redact the arguments that implements the Redactor interface
		for i, arg := range r.args {
			if redactor, ok := arg.(Redactor); ok == true {
				r.args[i] = redactor.Redacted()
			}
		}
		msg := fmt.Sprintf(r.fmt, r.args...)
		r.message = &msg
	}
	return *r.message
}

type Logger struct {
	Module      string
	backend     LeveledBackend
	haveBackend bool
}

func (l *Logger) SetBackend(backend LeveledBackend) {
	l.backend = backend
	l.haveBackend = true
}

// TODO call NewLogger and remove MustGetLogger?
// GetLogger creates and returns a Logger object based on the module name.
func GetLogger(module string) (*Logger, error) {
	return &Logger{Module: module}, nil
}

// MustGetLogger is like GetLogger but panics if the logger can't be created.
// It simplifies safe initialization of a global logger for eg. a package.
func MustGetLogger(module string) *Logger {
	logger, err := GetLogger(module)
	if err != nil {
		panic("logger: " + module + ": " + err.Error())
	}
	return logger
}

// Reset restores the internal state of the logging library.
func Reset() {
	// TODO make a global Init() method to be less magic? or make it such that
	// if there's no backends at all configured, we could use some tricks to
	// automatically setup backends based if we have a TTY or not.
	sequenceNo = 0
	b := SetBackend(NewLogBackend(os.Stderr, "", log.LstdFlags))
	b.SetLevel(DEBUG, "")
	SetFormatter(DefaultFormatter)
	timeNow = time.Now
}

// InitForTesting is a convenient method when using logging in a test. Once
// called, the time will be frozen to January 1, 1970 UTC.
func InitForTesting(level Level) *MemoryBackend {
	Reset()

	memoryBackend := NewMemoryBackend(10240)

	leveledBackend := AddModuleLevel(memoryBackend)
	leveledBackend.SetLevel(level, "")
	SetBackend(leveledBackend)

	timeNow = func() time.Time {
		return time.Unix(0, 0).UTC()
	}
	return memoryBackend
}

// IsEnabledFor returns true if the logger is enabled for the given level.
func (l *Logger) IsEnabledFor(level Level) bool {
	return defaultBackend.IsEnabledFor(level, l.Module)
}

func (l *Logger) log(lvl Level, format string, args ...interface{}) {
	// Create the logging record and pass it in to the backend
	record := &Record{
		Id:     atomic.AddUint64(&sequenceNo, 1),
		Time:   timeNow(),
		Module: l.Module,
		Level:  lvl,
		fmt:    format,
		args:   args,
	}

	// TODO use channels to fan out the records to all backends?
	// TODO in case of errors, do something (tricky)

	// calldepth=2 brings the stack up to the caller of the level
	// methods, Info(), Fatal(), etc.
	if l.haveBackend {
		l.backend.Log(lvl, 2, record)
		return
	}

	defaultBackend.Log(lvl, 2, record)
}

// Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1).
func (l *Logger) Fatal(args ...interface{}) {
	s := fmt.Sprint(args...)
	l.log(CRITICAL, "%s", s)
	os.Exit(1)
}

// Fatalf is equivalent to l.Critical followed by a call to os.Exit(1).
func (l *Logger) Fatalf(format string, args ...interface{}) {
	l.log(CRITICAL, format, args...)
	os.Exit(1)
}

// Panic is equivalent to l.Critical(fmt.Sprint()) followed by a call to panic().
func (l *Logger) Panic(args ...interface{}) {
	s := fmt.Sprint(args...)
	l.log(CRITICAL, "%s", s)
	panic(s)
}

// Panicf is equivalent to l.Critical followed by a call to panic().
func (l *Logger) Panicf(format string, args ...interface{}) {
	s := fmt.Sprintf(format, args...)
	l.log(CRITICAL, "%s", s)
	panic(s)
}

// Critical logs a message using CRITICAL as log level.
func (l *Logger) Critical(format string, args ...interface{}) {
	l.log(CRITICAL, format, args...)
}

// Error logs a message using ERROR as log level.
func (l *Logger) Error(format string, args ...interface{}) {
	l.log(ERROR, format, args...)
}

// Warning logs a message using WARNING as log level.
func (l *Logger) Warning(format string, args ...interface{}) {
	l.log(WARNING, format, args...)
}

// Notice logs a message using NOTICE as log level.
func (l *Logger) Notice(format string, args ...interface{}) {
	l.log(NOTICE, format, args...)
}

// Info logs a message using INFO as log level.
func (l *Logger) Info(format string, args ...interface{}) {
	l.log(INFO, format, args...)
}

// Debug logs a message using DEBUG as log level.
func (l *Logger) Debug(format string, args ...interface{}) {
	l.log(DEBUG, format, args...)
}

func init() {
	Reset()
}
<|endoftext|>


<commit_before>package ecfr

import (
	"bytes"
	"errors"
	"fmt"
)

type Datagram struct {
	DatagramHeader
	data           []byte
	WorkingCounter uint16
	buffer         []byte
}

func (dg *Datagram) Overlay(d []byte) (b []byte, err error) {
	//fmt.Printf("overlaying datagram over %s\n", spew.Sdump(d))
	b, err = dg.DatagramHeader.Overlay(d)
	if err != nil {
		return
	}

	if len(b) < int(dg.DataLength()) {
		err = fmt.Errorf("overlaying ecat dgram: need %d bytes of data, have %d", dg.DataLength(), len(b))
		return
	}

	dg.data = b[:dg.DataLength()]
	b = b[dg.DataLength():]

	if len(b) < 2 {
		err = fmt.Errorf("overlaying ecat dgram: need 2 bytes for working counter, got %d", len(b))
		return
	}

	// guarded by condition above
	dg.WorkingCounter, b = getUint16(b)

	dg.buffer = d
	return
}

func (dg *Datagram) Commit() (d []byte, err error) {
	_, err = dg.DatagramHeader.Commit()
	if err != nil {

	}

	// the data is already committed

	wcoffs := datagramHeaderLength + len(dg.data)
	if len(dg.buffer) < (wcoffs + 2) {
		err = fmt.Errorf("cannot commit data: buffer too short, need %d bytes, have %d", wcoffs+2, len(dg.buffer))
		return
	}

	putUint16(dg.buffer[wcoffs:wcoffs+2], dg.WorkingCounter)

	d = dg.buffer[:wcoffs+2]

	return
}

func (dg *Datagram) ByteLen() int {
	return DatagramOverheadLength + len(dg.Data())
}

type DatagramHeader struct {
	Command   CommandType
	Index     uint8
	Addr32    uint32
	LenWord   uint16
	Interrupt uint16
	buffer    []byte
}

const (
	datagramHeaderByteLen = 10
)

func (dh *DatagramHeader) Overlay(d []byte) (b []byte, err error) {
	b = d
	if len(b) < datagramHeaderByteLen {
		err = fmt.Errorf("need %d bytes for dgram header, have %d", datagramHeaderByteLen, len(b))
		return
	}

	var c8 uint8
	c8, b = getUint8(b)
	dh.Command = CommandType(c8)
	dh.Index, b = getUint8(b)
	dh.Addr32, b = getUint32(b)
	dh.LenWord, b = getUint16(b)
	dh.Interrupt, b = getUint16(b)

	dh.buffer = d

	return
}

func (dh *DatagramHeader) Commit() (d []byte, err error) {
	b := dh.buffer

	b = putUint8(b, uint8(dh.Command))
	b = putUint8(b, dh.Index)
	b = putUint32(b, dh.Addr32)
	b = putUint16(b, dh.LenWord)
	b = putUint16(b, dh.Interrupt)

	d = dh.buffer[:len(b)]
	return
}

func (dh *DatagramHeader) SlaveAddr() uint16 {
	return uint16(dh.Addr32)
}

func (dh *DatagramHeader) OffsetAddr() uint16 {
	return uint16(dh.Addr32 >> 16)
}

func (dh *DatagramHeader) LogicalAddr() uint32 {
	return dh.Addr32
}

func (dh *DatagramHeader) DataLength() uint16 {
	return dh.LenWord & ((1 << 11) - 1)
}

func (dh *DatagramHeader) Roundtrip() bool {
	return (dh.LenWord & (1 << roundtripBit)) != 0
}

func (dh *DatagramHeader) Last() bool {
	return (dh.LenWord & (1 << lastindicatorBit)) == 0
}

func (dh *DatagramHeader) SetLast(last bool) {
	if last {
		dh.LenWord &^= (1 << lastindicatorBit)
	} else {
		dh.LenWord |= (1 << lastindicatorBit)
	}
}

func (dg *Datagram) Summary() string {
	buf := bytes.NewBuffer(nil)
	fmt.Fprintf(buf, "%-4s x%02x %04x %04x len %03x wc % 2d IRQ %04x ", dg.Command.String(),
		dg.Index,
		dg.SlaveAddr(),
		dg.OffsetAddr(),
		dg.DataLength(),
		dg.WorkingCounter,
		dg.Interrupt)

	cutoffstr := ""
	data := dg.Data()
	if len(data) > 8 {
		data = data[:8]
		cutoffstr = " ..."
	}

	fmt.Fprintf(buf, "> % x%s", data, cutoffstr)

	switch len(dg.Data()) {
	case 2:
		fmt.Fprintf(buf, " (%04x)", xgetUint16(dg.Data()))
	case 4:
		fmt.Fprintf(buf, " (%04x %04x | %08x)", xgetUint16(dg.Data()), xgetUint16(dg.Data()[2:]), xgetUint32(dg.Data()))
	}

	return buf.String()
}

func PointDatagramHeaderTo(d []byte) (dh DatagramHeader, err error) {
	if len(d) < datagramHeaderLength {
		err = fmt.Errorf("datagram header needs %d bytes, only have %d", datagramHeaderLength, len(d))
		return
	}

	dh.buffer = d[0:datagramHeaderLength]
	return
}

func (dg *Datagram) Data() []byte {
	return dg.data
}

func (dg *Datagram) SetDataLen(ndl int) error {
	nl := DatagramOverheadLength + ndl
	if (cap(dg.buffer)) < nl {
		return fmt.Errorf("datagram with new size needs %d bytes of space, only %d in buffer", nl, cap(dg.buffer))
	}

	if ndl > datagramMaxDataLength {
		return errors.New("new data length exceeds maximum datagram data length")
	}

	dg.data = dg.data[0:ndl]
	dg.buffer = dg.buffer[0:nl]

	dg.LenWord &^= datagramDataLengthMask
	dg.LenWord |= (uint16(ndl) & datagramDataLengthMask)

	return nil
}

func PointDatagramTo(d []byte) (dg Datagram, err error) {
	if len(d) < DatagramOverheadLength {
		err = errors.New("byte slice too short to be pointed to by a datagram")
		return
	}

	dg = Datagram{
		buffer: d,
		data:   d[datagramHeaderLength:datagramHeaderLength],
	}
	dg.DatagramHeader, err = PointDatagramHeaderTo(d)

	return
}

const (
	roundtripBit     = 14
	lastindicatorBit = 15

	datagramHeaderLength   = 10
	datagramFooterLength   = 2
	DatagramOverheadLength = datagramHeaderLength + datagramFooterLength

	datagramDataLengthMask = (1 << 12) - 1
	datagramMaxDataLength  = datagramDataLengthMask
)

type CommandType uint8

func (ct CommandType) String() string {
	if cts, ok := commandTypeName[ct]; ok {
		return cts
	}
	return fmt.Sprintf("CommandType(%d)", uint(ct))
}

type DatagramAddressType uint

const (
	UninitializedDatagramAddressType DatagramAddressType = 0
	Positional                       DatagramAddressType = 1
	Fixed                            DatagramAddressType = 2
	Broadcast                        DatagramAddressType = 3
	Logical                          DatagramAddressType = 4
)

type DatagramAddress struct {
	addr uint32
	typ  DatagramAddressType
}

func (d DatagramAddress) Addr32() uint32 {
	return d.addr
}

func (d DatagramAddress) Type() DatagramAddressType {
	return d.typ
}

func DatagramAddressFromCommand(addr32 uint32, ct CommandType) DatagramAddress {
	typ := UninitializedDatagramAddressType
	var ok bool
	if typ, ok = datagramAddressByOperation[ct]; ok {
		return DatagramAddress{addr32, typ}
	}
	return DatagramAddress{addr32, typ}
}

func (d DatagramAddress) Offset() uint16 {
	return uint16(d.Addr32() >> 16)
}

func (d DatagramAddress) PositionOrAddress() uint16 {
	return uint16(d.Addr32())
}

func (d *DatagramAddress) IncrementSlaveAddr() {
	d.addr = (d.addr & 0xffff0000) | ((d.addr + 1) & 0x0000ffff)
}

func PositionalAddr(position int16, offset uint16) DatagramAddress {
	return DatagramAddress{uint32(uint16(position)) | uint32(offset)<<16, Positional}
}

var datagramAddressByOperation = map[CommandType]DatagramAddressType{
	NOP:  UninitializedDatagramAddressType,
	APRD: Positional,
	APWR: Positional,
	APRW: Positional,
	FPRD: Fixed,
	FPWR: Fixed,
	FPRW: Fixed,
	BRD:  Broadcast,
	BWR:  Broadcast,
	BRW:  Broadcast,
	LRD:  Logical,
	LWR:  Logical,
	LRW:  Logical,
	ARMW: Positional,
	FRMW: Positional,
}

const (
	NOP  CommandType = 0
	APRD CommandType = 1
	APWR CommandType = 2
	APRW CommandType = 3
	FPRD CommandType = 4
	FPWR CommandType = 5
	FPRW CommandType = 6
	BRD  CommandType = 7
	BWR  CommandType = 8
	BRW  CommandType = 9
	LRD  CommandType = 10
	LWR  CommandType = 11
	LRW  CommandType = 12
	ARMW CommandType = 13
	FRMW CommandType = 14
)

var commandTypeName = map[CommandType]string{
	NOP:  "NOP",
	APRD: "APRD",
	APWR: "APWR",
	APRW: "APRW",
	FPRD: "FPRD",
	FPWR: "FPWR",
	FPRW: "FPRW",
	BRD:  "BRD",
	BWR:  "BWR",
	BRW:  "BRW",
	LRD:  "LRD",
	LWR:  "LWR",
	LRW:  "LRW",
	ARMW: "ARMW",
	FRMW: "FRMW",
}
<commit_msg>utility functions for datagram addresses and command types<commit_after>package ecfr

import (
	"bytes"
	"errors"
	"fmt"
)

type Datagram struct {
	DatagramHeader
	data           []byte
	WorkingCounter uint16
	buffer         []byte
}

func (dg *Datagram) Overlay(d []byte) (b []byte, err error) {
	//fmt.Printf("overlaying datagram over %s\n", spew.Sdump(d))
	b, err = dg.DatagramHeader.Overlay(d)
	if err != nil {
		return
	}

	if len(b) < int(dg.DataLength()) {
		err = fmt.Errorf("overlaying ecat dgram: need %d bytes of data, have %d", dg.DataLength(), len(b))
		return
	}

	dg.data = b[:dg.DataLength()]
	b = b[dg.DataLength():]

	if len(b) < 2 {
		err = fmt.Errorf("overlaying ecat dgram: need 2 bytes for working counter, got %d", len(b))
		return
	}

	// guarded by condition above
	dg.WorkingCounter, b = getUint16(b)

	dg.buffer = d
	return
}

func (dg *Datagram) Commit() (d []byte, err error) {
	_, err = dg.DatagramHeader.Commit()
	if err != nil {

	}

	// the data is already committed

	wcoffs := datagramHeaderLength + len(dg.data)
	if len(dg.buffer) < (wcoffs + 2) {
		err = fmt.Errorf("cannot commit data: buffer too short, need %d bytes, have %d", wcoffs+2, len(dg.buffer))
		return
	}

	putUint16(dg.buffer[wcoffs:wcoffs+2], dg.WorkingCounter)

	d = dg.buffer[:wcoffs+2]

	return
}

func (dg *Datagram) ByteLen() int {
	return DatagramOverheadLength + len(dg.Data())
}

type DatagramHeader struct {
	Command   CommandType
	Index     uint8
	Addr32    uint32
	LenWord   uint16
	Interrupt uint16
	buffer    []byte
}

const (
	datagramHeaderByteLen = 10
)

func (dh *DatagramHeader) Overlay(d []byte) (b []byte, err error) {
	b = d
	if len(b) < datagramHeaderByteLen {
		err = fmt.Errorf("need %d bytes for dgram header, have %d", datagramHeaderByteLen, len(b))
		return
	}

	var c8 uint8
	c8, b = getUint8(b)
	dh.Command = CommandType(c8)
	dh.Index, b = getUint8(b)
	dh.Addr32, b = getUint32(b)
	dh.LenWord, b = getUint16(b)
	dh.Interrupt, b = getUint16(b)

	dh.buffer = d

	return
}

func (dh *DatagramHeader) Commit() (d []byte, err error) {
	b := dh.buffer

	b = putUint8(b, uint8(dh.Command))
	b = putUint8(b, dh.Index)
	b = putUint32(b, dh.Addr32)
	b = putUint16(b, dh.LenWord)
	b = putUint16(b, dh.Interrupt)

	d = dh.buffer[:len(b)]
	return
}

func (dh *DatagramHeader) SlaveAddr() uint16 {
	return uint16(dh.Addr32)
}

func (dh *DatagramHeader) OffsetAddr() uint16 {
	return uint16(dh.Addr32 >> 16)
}

func (dh *DatagramHeader) LogicalAddr() uint32 {
	return dh.Addr32
}

func (dh *DatagramHeader) DataLength() uint16 {
	return dh.LenWord & ((1 << 11) - 1)
}

func (dh *DatagramHeader) Roundtrip() bool {
	return (dh.LenWord & (1 << roundtripBit)) != 0
}

func (dh *DatagramHeader) Last() bool {
	return (dh.LenWord & (1 << lastindicatorBit)) == 0
}

func (dh *DatagramHeader) SetLast(last bool) {
	if last {
		dh.LenWord &^= (1 << lastindicatorBit)
	} else {
		dh.LenWord |= (1 << lastindicatorBit)
	}
}

func (dg *Datagram) Summary() string {
	buf := bytes.NewBuffer(nil)
	fmt.Fprintf(buf, "%-4s x%02x %04x %04x len %03x wc % 2d IRQ %04x ", dg.Command.String(),
		dg.Index,
		dg.SlaveAddr(),
		dg.OffsetAddr(),
		dg.DataLength(),
		dg.WorkingCounter,
		dg.Interrupt)

	cutoffstr := ""
	data := dg.Data()
	if len(data) > 8 {
		data = data[:8]
		cutoffstr = " ..."
	}

	fmt.Fprintf(buf, "> % x%s", data, cutoffstr)

	switch len(dg.Data()) {
	case 2:
		fmt.Fprintf(buf, " (%04x)", xgetUint16(dg.Data()))
	case 4:
		fmt.Fprintf(buf, " (%04x %04x | %08x)", xgetUint16(dg.Data()), xgetUint16(dg.Data()[2:]), xgetUint32(dg.Data()))
	}

	return buf.String()
}

func PointDatagramHeaderTo(d []byte) (dh DatagramHeader, err error) {
	if len(d) < datagramHeaderLength {
		err = fmt.Errorf("datagram header needs %d bytes, only have %d", datagramHeaderLength, len(d))
		return
	}

	dh.buffer = d[0:datagramHeaderLength]
	return
}

func (dg *Datagram) Data() []byte {
	return dg.data
}

func (dg *Datagram) SetDataLen(ndl int) error {
	nl := DatagramOverheadLength + ndl
	if (cap(dg.buffer)) < nl {
		return fmt.Errorf("datagram with new size needs %d bytes of space, only %d in buffer", nl, cap(dg.buffer))
	}

	if ndl > datagramMaxDataLength {
		return errors.New("new data length exceeds maximum datagram data length")
	}

	dg.data = dg.data[0:ndl]
	dg.buffer = dg.buffer[0:nl]

	dg.LenWord &^= datagramDataLengthMask
	dg.LenWord |= (uint16(ndl) & datagramDataLengthMask)

	return nil
}

func PointDatagramTo(d []byte) (dg Datagram, err error) {
	if len(d) < DatagramOverheadLength {
		err = errors.New("byte slice too short to be pointed to by a datagram")
		return
	}

	dg = Datagram{
		buffer: d,
		data:   d[datagramHeaderLength:datagramHeaderLength],
	}
	dg.DatagramHeader, err = PointDatagramHeaderTo(d)

	return
}

const (
	roundtripBit     = 14
	lastindicatorBit = 15

	datagramHeaderLength   = 10
	datagramFooterLength   = 2
	DatagramOverheadLength = datagramHeaderLength + datagramFooterLength

	datagramDataLengthMask = (1 << 12) - 1
	datagramMaxDataLength  = datagramDataLengthMask
)

type CommandType uint8

func (ct CommandType) String() string {
	if cts, ok := commandTypeName[ct]; ok {
		return cts
	}
	return fmt.Sprintf("CommandType(%d)", uint(ct))
}

type DatagramAddressType uint

const (
	UninitializedDatagramAddressType DatagramAddressType = 0
	Positional                       DatagramAddressType = 1
	Fixed                            DatagramAddressType = 2
	Broadcast                        DatagramAddressType = 3
	Logical                          DatagramAddressType = 4
)

type DatagramAddress struct {
	addr uint32
	typ  DatagramAddressType
}

func (d DatagramAddress) Addr32() uint32 {
	return d.addr
}

func (d DatagramAddress) Type() DatagramAddressType {
	return d.typ
}

func DatagramAddressFromCommand(addr32 uint32, ct CommandType) DatagramAddress {
	typ := UninitializedDatagramAddressType
	var ok bool
	if typ, ok = datagramAddressByOperation[ct]; ok {
		return DatagramAddress{addr32, typ}
	}
	return DatagramAddress{addr32, typ}
}

func (d DatagramAddress) Offset() uint16 {
	return uint16(d.Addr32() >> 16)
}

func (d *DatagramAddress) SetOffset(offs uint16) {
	d.addr &^= 0xffff0000
	d.addr |= uint32(offs) << 16
}

func (d DatagramAddress) PositionOrAddress() uint16 {
	return uint16(d.Addr32())
}

func (d *DatagramAddress) IncrementSlaveAddr() {
	d.addr = (d.addr & 0xffff0000) | ((d.addr + 1) & 0x0000ffff)
}

func (d *DatagramAddress) IsPhysical() bool {
	switch d.Type() {
	case Positional:
	case Fixed:
	case Broadcast:
	default:
		return false
	}
	return true
}

func PositionalAddr(position int16, offset uint16) DatagramAddress {
	return DatagramAddress{uint32(uint16(position)) | uint32(offset)<<16, Positional}
}

var datagramAddressByOperation = map[CommandType]DatagramAddressType{
	NOP:  UninitializedDatagramAddressType,
	APRD: Positional,
	APWR: Positional,
	APRW: Positional,
	FPRD: Fixed,
	FPWR: Fixed,
	FPRW: Fixed,
	BRD:  Broadcast,
	BWR:  Broadcast,
	BRW:  Broadcast,
	LRD:  Logical,
	LWR:  Logical,
	LRW:  Logical,
	ARMW: Positional,
	FRMW: Positional,
}

const (
	NOP  CommandType = 0
	APRD CommandType = 1
	APWR CommandType = 2
	APRW CommandType = 3
	FPRD CommandType = 4
	FPWR CommandType = 5
	FPRW CommandType = 6
	BRD  CommandType = 7
	BWR  CommandType = 8
	BRW  CommandType = 9
	LRD  CommandType = 10
	LWR  CommandType = 11
	LRW  CommandType = 12
	ARMW CommandType = 13
	FRMW CommandType = 14
)

var commandTypeName = map[CommandType]string{
	NOP:  "NOP",
	APRD: "APRD",
	APWR: "APWR",
	APRW: "APRW",
	FPRD: "FPRD",
	FPWR: "FPWR",
	FPRW: "FPRW",
	BRD:  "BRD",
	BWR:  "BWR",
	BRW:  "BRW",
	LRD:  "LRD",
	LWR:  "LWR",
	LRW:  "LRW",
	ARMW: "ARMW",
	FRMW: "FRMW",
}

func (ct CommandType) DoesRead() bool {
	switch ct {
	case APRD:
	case APRW:
	case FPRD:
	case FPRW:
	case BRD:
	case BRW:
	case LRD:
	case LRW:
	case ARMW:
	case FRMW:
	default:
		return false
	}
	return true
}

func (ct CommandType) DoesWrite() bool {
	switch ct {
	case APWR:
	case APRW:
	case FPWR:
	case FPRW:
	case BWR:
	case BRW:
	case LWR:
	case LRW:
	case ARMW:
	case FRMW:
	default:
		return false
	}
	return true
}
<|endoftext|>