<commit_before>// Package dht implements a distributed hash table that satisfies the ipfs routing
// interface. This DHT is modeled after kademlia with Coral and S/Kademlia modifications.
package dht

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	pb "github.com/libp2p/go-libp2p-kad-dht/pb"
	providers "github.com/libp2p/go-libp2p-kad-dht/providers"
	routing "github.com/libp2p/go-libp2p-routing"

	proto "github.com/gogo/protobuf/proto"
	cid "github.com/ipfs/go-cid"
	ds "github.com/ipfs/go-datastore"
	logging "github.com/ipfs/go-log"
	goprocess "github.com/jbenet/goprocess"
	goprocessctx "github.com/jbenet/goprocess/context"
	ci "github.com/libp2p/go-libp2p-crypto"
	host "github.com/libp2p/go-libp2p-host"
	kb "github.com/libp2p/go-libp2p-kbucket"
	peer "github.com/libp2p/go-libp2p-peer"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	protocol "github.com/libp2p/go-libp2p-protocol"
	record "github.com/libp2p/go-libp2p-record"
	recpb "github.com/libp2p/go-libp2p-record/pb"
	base32 "github.com/whyrusleeping/base32"
)

var log = logging.Logger("dht")

var ProtocolDHT protocol.ID = "/ipfs/kad/1.0.0"
var ProtocolDHTOld protocol.ID = "/ipfs/dht"

// NumBootstrapQueries defines the number of random dht queries to do to
// collect members of the routing table.
const NumBootstrapQueries = 5

// IpfsDHT is an implementation of Kademlia with Coral and S/Kademlia modifications.
// It is used to implement the base IpfsRouting module.
type IpfsDHT struct {
	host      host.Host        // the network services we need
	self      peer.ID          // Local peer (yourself)
	peerstore pstore.Peerstore // Peer Registry

	datastore ds.Datastore // Local data

	routingTable *kb.RoutingTable // Array of routing tables for differently distanced nodes
	providers    *providers.ProviderManager

	birth    time.Time  // When this peer started up
	diaglock sync.Mutex // lock to make diagnostics work better

	Validator record.Validator // record validator funcs
	Selector  record.Selector  // record selection funcs

	ctx  context.Context
	proc goprocess.Process

	clientOnly bool

	strmap map[peer.ID]*messageSender
	smlk   sync.Mutex
}

// NewDHT creates a new DHT object with the given peer as the 'local' host
func NewDHT(ctx context.Context, h host.Host, dstore ds.Batching) *IpfsDHT {
	dht := NewDHTClient(ctx, h, dstore)

	h.SetStreamHandler(ProtocolDHT, dht.handleNewStream)
	h.SetStreamHandler(ProtocolDHTOld, dht.handleNewStream)

	return dht
}

// NewDHTClient creates a new DHT object with the given peer as the 'local' host
func NewDHTClient(ctx context.Context, h host.Host, dstore ds.Batching) *IpfsDHT {
	dht := makeDHT(ctx, h, dstore)

	// register for network notifs.
	dht.host.Network().Notify((*netNotifiee)(dht))

	dht.proc = goprocessctx.WithContextAndTeardown(ctx, func() error {
		// remove ourselves from network notifs.
		dht.host.Network().StopNotify((*netNotifiee)(dht))
		return nil
	})

	dht.proc.AddChild(dht.providers.Process())

	dht.Validator["pk"] = record.PublicKeyValidator
	dht.Selector["pk"] = record.PublicKeySelector

	return dht
}

func makeDHT(ctx context.Context, h host.Host, dstore ds.Batching) *IpfsDHT {
	return &IpfsDHT{
		datastore:    dstore,
		self:         h.ID(),
		peerstore:    h.Peerstore(),
		host:         h,
		strmap:       make(map[peer.ID]*messageSender),
		ctx:          ctx,
		providers:    providers.NewProviderManager(ctx, h.ID(), dstore),
		birth:        time.Now(),
		routingTable: kb.NewRoutingTable(KValue, kb.ConvertPeerID(h.ID()), time.Minute, h.Peerstore()),

		Validator: make(record.Validator),
		Selector:  make(record.Selector),
	}
}

// putValueToPeer stores the given key/value pair at the peer 'p'
func (dht *IpfsDHT) putValueToPeer(ctx context.Context, p peer.ID,
	key string, rec *recpb.Record) error {

	pmes := pb.NewMessage(pb.Message_PUT_VALUE, key, 0)
	pmes.Record = rec
	rpmes, err := dht.sendRequest(ctx, p, pmes)
	switch err {
	case ErrReadTimeout:
		log.Warningf("read timeout: %s %s", p.Pretty(), key)
		fallthrough
	default:
		return err
	case nil:
		break
	}

	if err != nil {
		return err
	}

	if !bytes.Equal(rpmes.GetRecord().Value, pmes.GetRecord().Value) {
		return errors.New("value not put correctly")
	}
	return nil
}

var errInvalidRecord = errors.New("received invalid record")

// getValueOrPeers queries a particular peer p for the value for
// key. It returns either the value or a list of closer peers.
// NOTE: It will update the dht's peerstore with any new addresses
// it finds for the given peer.
func (dht *IpfsDHT) getValueOrPeers(ctx context.Context, p peer.ID, key string) (*recpb.Record, []pstore.PeerInfo, error) {

	pmes, err := dht.getValueSingle(ctx, p, key)
	if err != nil {
		return nil, nil, err
	}

	// Perhaps we were given closer peers
	peers := pb.PBPeersToPeerInfos(pmes.GetCloserPeers())

	if record := pmes.GetRecord(); record != nil {
		// Success! We were given the value
		log.Debug("getValueOrPeers: got value")

		// make sure record is valid.
		err = dht.verifyRecordOnline(ctx, record)
		if err != nil {
			log.Info("Received invalid record! (discarded)")
			// return a sentinal to signify an invalid record was received
			err = errInvalidRecord
			record = new(recpb.Record)
		}
		return record, peers, err
	}

	if len(peers) > 0 {
		log.Debug("getValueOrPeers: peers")
		return nil, peers, nil
	}

	log.Warning("getValueOrPeers: routing.ErrNotFound")
	return nil, nil, routing.ErrNotFound
}

// getValueSingle simply performs the get value RPC with the given parameters
func (dht *IpfsDHT) getValueSingle(ctx context.Context, p peer.ID, key string) (*pb.Message, error) {
	meta := logging.LoggableMap{
		"key":  key,
		"peer": p,
	}

	defer log.EventBegin(ctx, "getValueSingle", meta).Done()

	pmes := pb.NewMessage(pb.Message_GET_VALUE, key, 0)
	resp, err := dht.sendRequest(ctx, p, pmes)
	switch err {
	case nil:
		return resp, nil
	case ErrReadTimeout:
		log.Warningf("read timeout: %s %s", p.Pretty(), key)
		fallthrough
	default:
		return nil, err
	}
}

// getLocal attempts to retrieve the value from the datastore
func (dht *IpfsDHT) getLocal(key string) (*recpb.Record, error) {
	log.Debugf("getLocal %s", key)

	v, err := dht.datastore.Get(mkDsKey(key))
	if err != nil {
		return nil, err
	}
	log.Debugf("found %s in local datastore")

	byt, ok := v.([]byte)
	if !ok {
		return nil, errors.New("value stored in datastore not []byte")
	}
	rec := new(recpb.Record)
	err = proto.Unmarshal(byt, rec)
	if err != nil {
		return nil, err
	}

	err = dht.verifyRecordLocally(rec)
	if err != nil {
		log.Debugf("local record verify failed: %s (discarded)", err)
		return nil, err
	}

	return rec, nil
}

// getOwnPrivateKey attempts to load the local peers private
// key from the peerstore.
func (dht *IpfsDHT) getOwnPrivateKey() (ci.PrivKey, error) {
	sk := dht.peerstore.PrivKey(dht.self)
	if sk == nil {
		log.Warningf("%s dht cannot get own private key!", dht.self)
		return nil, fmt.Errorf("cannot get private key to sign record!")
	}
	return sk, nil
}

// putLocal stores the key value pair in the datastore
func (dht *IpfsDHT) putLocal(key string, rec *recpb.Record) error {
	data, err := proto.Marshal(rec)
	if err != nil {
		return err
	}

	return dht.datastore.Put(mkDsKey(key), data)
}

// Update signals the routingTable to Update its last-seen status
// on the given peer.
func (dht *IpfsDHT) Update(ctx context.Context, p peer.ID) {
	log.Event(ctx, "updatePeer", p)
	dht.routingTable.Update(p)
}

// FindLocal looks for a peer with a given ID connected to this dht and returns the peer and the table it was found in.
func (dht *IpfsDHT) FindLocal(id peer.ID) pstore.PeerInfo {
	p := dht.routingTable.Find(id)
	if p != "" {
		return dht.peerstore.PeerInfo(p)
	}
	return pstore.PeerInfo{}
}

// findPeerSingle asks peer 'p' if they know where the peer with id 'id' is
func (dht *IpfsDHT) findPeerSingle(ctx context.Context, p peer.ID, id peer.ID) (*pb.Message, error) {
	defer log.EventBegin(ctx, "findPeerSingle", p, id).Done()

	pmes := pb.NewMessage(pb.Message_FIND_NODE, string(id), 0)
	resp, err := dht.sendRequest(ctx, p, pmes)
	switch err {
	case nil:
		return resp, nil
	case ErrReadTimeout:
		log.Warningf("read timeout: %s %s", p.Pretty(), id)
		fallthrough
	default:
		return nil, err
	}
}

func (dht *IpfsDHT) findProvidersSingle(ctx context.Context, p peer.ID, key *cid.Cid) (*pb.Message, error) {
	defer log.EventBegin(ctx, "findProvidersSingle", p, key).Done()

	pmes := pb.NewMessage(pb.Message_GET_PROVIDERS, key.KeyString(), 0)
	resp, err := dht.sendRequest(ctx, p, pmes)
	switch err {
	case nil:
		return resp, nil
	case ErrReadTimeout:
		log.Warningf("read timeout: %s %s", p.Pretty(), key)
		fallthrough
	default:
		return nil, err
	}
}

// nearestPeersToQuery returns the routing tables closest peers.
func (dht *IpfsDHT) nearestPeersToQuery(pmes *pb.Message, count int) []peer.ID {
	closer := dht.routingTable.NearestPeers(kb.ConvertKey(pmes.GetKey()), count)
	return closer
}

// betterPeerToQuery returns nearestPeersToQuery, but iff closer than self.
func (dht *IpfsDHT) betterPeersToQuery(pmes *pb.Message, p peer.ID, count int) []peer.ID {
	closer := dht.nearestPeersToQuery(pmes, count)

	// no node? nil
	if closer == nil {
		return nil
	}

	// == to self? thats bad
	for _, p := range closer {
		if p == dht.self {
			log.Debug("attempted to return self! this shouldn't happen...")
			return nil
		}
	}

	var filtered []peer.ID
	for _, clp := range closer {
		// Dont send a peer back themselves
		if p == clp {
			continue
		}

		filtered = append(filtered, clp)
	}

	// ok seems like closer nodes
	return filtered
}

// Context return dht's context
func (dht *IpfsDHT) Context() context.Context {
	return dht.ctx
}

// Process return dht's process
func (dht *IpfsDHT) Process() goprocess.Process {
	return dht.proc
}

// Close calls Process Close
func (dht *IpfsDHT) Close() error {
	return dht.proc.Close()
}

func mkDsKey(s string) ds.Key {
	return ds.NewKey(base32.RawStdEncoding.EncodeToString([]byte(s)))
}
<commit_msg>Remove diaglock as per #26<commit_after>// Package dht implements a distributed hash table that satisfies the ipfs routing
// interface. This DHT is modeled after kademlia with Coral and S/Kademlia modifications.
package dht

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	pb "github.com/libp2p/go-libp2p-kad-dht/pb"
	providers "github.com/libp2p/go-libp2p-kad-dht/providers"
	routing "github.com/libp2p/go-libp2p-routing"

	proto "github.com/gogo/protobuf/proto"
	cid "github.com/ipfs/go-cid"
	ds "github.com/ipfs/go-datastore"
	logging "github.com/ipfs/go-log"
	goprocess "github.com/jbenet/goprocess"
	goprocessctx "github.com/jbenet/goprocess/context"
	ci "github.com/libp2p/go-libp2p-crypto"
	host "github.com/libp2p/go-libp2p-host"
	kb "github.com/libp2p/go-libp2p-kbucket"
	peer "github.com/libp2p/go-libp2p-peer"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	protocol "github.com/libp2p/go-libp2p-protocol"
	record "github.com/libp2p/go-libp2p-record"
	recpb "github.com/libp2p/go-libp2p-record/pb"
	base32 "github.com/whyrusleeping/base32"
)

var log = logging.Logger("dht")

var ProtocolDHT protocol.ID = "/ipfs/kad/1.0.0"
var ProtocolDHTOld protocol.ID = "/ipfs/dht"

// NumBootstrapQueries defines the number of random dht queries to do to
// collect members of the routing table.
const NumBootstrapQueries = 5

// IpfsDHT is an implementation of Kademlia with Coral and S/Kademlia modifications.
// It is used to implement the base IpfsRouting module.
type IpfsDHT struct {
	host      host.Host        // the network services we need
	self      peer.ID          // Local peer (yourself)
	peerstore pstore.Peerstore // Peer Registry

	datastore ds.Datastore // Local data

	routingTable *kb.RoutingTable // Array of routing tables for differently distanced nodes
	providers    *providers.ProviderManager

	birth    time.Time  // When this peer started up

	Validator record.Validator // record validator funcs
	Selector  record.Selector  // record selection funcs

	ctx  context.Context
	proc goprocess.Process

	clientOnly bool

	strmap map[peer.ID]*messageSender
	smlk   sync.Mutex
}

// NewDHT creates a new DHT object with the given peer as the 'local' host
func NewDHT(ctx context.Context, h host.Host, dstore ds.Batching) *IpfsDHT {
	dht := NewDHTClient(ctx, h, dstore)

	h.SetStreamHandler(ProtocolDHT, dht.handleNewStream)
	h.SetStreamHandler(ProtocolDHTOld, dht.handleNewStream)

	return dht
}

// NewDHTClient creates a new DHT object with the given peer as the 'local' host
func NewDHTClient(ctx context.Context, h host.Host, dstore ds.Batching) *IpfsDHT {
	dht := makeDHT(ctx, h, dstore)

	// register for network notifs.
	dht.host.Network().Notify((*netNotifiee)(dht))

	dht.proc = goprocessctx.WithContextAndTeardown(ctx, func() error {
		// remove ourselves from network notifs.
		dht.host.Network().StopNotify((*netNotifiee)(dht))
		return nil
	})

	dht.proc.AddChild(dht.providers.Process())

	dht.Validator["pk"] = record.PublicKeyValidator
	dht.Selector["pk"] = record.PublicKeySelector

	return dht
}

func makeDHT(ctx context.Context, h host.Host, dstore ds.Batching) *IpfsDHT {
	return &IpfsDHT{
		datastore:    dstore,
		self:         h.ID(),
		peerstore:    h.Peerstore(),
		host:         h,
		strmap:       make(map[peer.ID]*messageSender),
		ctx:          ctx,
		providers:    providers.NewProviderManager(ctx, h.ID(), dstore),
		birth:        time.Now(),
		routingTable: kb.NewRoutingTable(KValue, kb.ConvertPeerID(h.ID()), time.Minute, h.Peerstore()),

		Validator: make(record.Validator),
		Selector:  make(record.Selector),
	}
}

// putValueToPeer stores the given key/value pair at the peer 'p'
func (dht *IpfsDHT) putValueToPeer(ctx context.Context, p peer.ID,
	key string, rec *recpb.Record) error {

	pmes := pb.NewMessage(pb.Message_PUT_VALUE, key, 0)
	pmes.Record = rec
	rpmes, err := dht.sendRequest(ctx, p, pmes)
	switch err {
	case ErrReadTimeout:
		log.Warningf("read timeout: %s %s", p.Pretty(), key)
		fallthrough
	default:
		return err
	case nil:
		break
	}

	if err != nil {
		return err
	}

	if !bytes.Equal(rpmes.GetRecord().Value, pmes.GetRecord().Value) {
		return errors.New("value not put correctly")
	}
	return nil
}

var errInvalidRecord = errors.New("received invalid record")

// getValueOrPeers queries a particular peer p for the value for
// key. It returns either the value or a list of closer peers.
// NOTE: It will update the dht's peerstore with any new addresses
// it finds for the given peer.
func (dht *IpfsDHT) getValueOrPeers(ctx context.Context, p peer.ID, key string) (*recpb.Record, []pstore.PeerInfo, error) {

	pmes, err := dht.getValueSingle(ctx, p, key)
	if err != nil {
		return nil, nil, err
	}

	// Perhaps we were given closer peers
	peers := pb.PBPeersToPeerInfos(pmes.GetCloserPeers())

	if record := pmes.GetRecord(); record != nil {
		// Success! We were given the value
		log.Debug("getValueOrPeers: got value")

		// make sure record is valid.
		err = dht.verifyRecordOnline(ctx, record)
		if err != nil {
			log.Info("Received invalid record! (discarded)")
			// return a sentinal to signify an invalid record was received
			err = errInvalidRecord
			record = new(recpb.Record)
		}
		return record, peers, err
	}

	if len(peers) > 0 {
		log.Debug("getValueOrPeers: peers")
		return nil, peers, nil
	}

	log.Warning("getValueOrPeers: routing.ErrNotFound")
	return nil, nil, routing.ErrNotFound
}

// getValueSingle simply performs the get value RPC with the given parameters
func (dht *IpfsDHT) getValueSingle(ctx context.Context, p peer.ID, key string) (*pb.Message, error) {
	meta := logging.LoggableMap{
		"key":  key,
		"peer": p,
	}

	defer log.EventBegin(ctx, "getValueSingle", meta).Done()

	pmes := pb.NewMessage(pb.Message_GET_VALUE, key, 0)
	resp, err := dht.sendRequest(ctx, p, pmes)
	switch err {
	case nil:
		return resp, nil
	case ErrReadTimeout:
		log.Warningf("read timeout: %s %s", p.Pretty(), key)
		fallthrough
	default:
		return nil, err
	}
}

// getLocal attempts to retrieve the value from the datastore
func (dht *IpfsDHT) getLocal(key string) (*recpb.Record, error) {
	log.Debugf("getLocal %s", key)

	v, err := dht.datastore.Get(mkDsKey(key))
	if err != nil {
		return nil, err
	}
	log.Debugf("found %s in local datastore")

	byt, ok := v.([]byte)
	if !ok {
		return nil, errors.New("value stored in datastore not []byte")
	}
	rec := new(recpb.Record)
	err = proto.Unmarshal(byt, rec)
	if err != nil {
		return nil, err
	}

	err = dht.verifyRecordLocally(rec)
	if err != nil {
		log.Debugf("local record verify failed: %s (discarded)", err)
		return nil, err
	}

	return rec, nil
}

// getOwnPrivateKey attempts to load the local peers private
// key from the peerstore.
func (dht *IpfsDHT) getOwnPrivateKey() (ci.PrivKey, error) {
	sk := dht.peerstore.PrivKey(dht.self)
	if sk == nil {
		log.Warningf("%s dht cannot get own private key!", dht.self)
		return nil, fmt.Errorf("cannot get private key to sign record!")
	}
	return sk, nil
}

// putLocal stores the key value pair in the datastore
func (dht *IpfsDHT) putLocal(key string, rec *recpb.Record) error {
	data, err := proto.Marshal(rec)
	if err != nil {
		return err
	}

	return dht.datastore.Put(mkDsKey(key), data)
}

// Update signals the routingTable to Update its last-seen status
// on the given peer.
func (dht *IpfsDHT) Update(ctx context.Context, p peer.ID) {
	log.Event(ctx, "updatePeer", p)
	dht.routingTable.Update(p)
}

// FindLocal looks for a peer with a given ID connected to this dht and returns the peer and the table it was found in.
func (dht *IpfsDHT) FindLocal(id peer.ID) pstore.PeerInfo {
	p := dht.routingTable.Find(id)
	if p != "" {
		return dht.peerstore.PeerInfo(p)
	}
	return pstore.PeerInfo{}
}

// findPeerSingle asks peer 'p' if they know where the peer with id 'id' is
func (dht *IpfsDHT) findPeerSingle(ctx context.Context, p peer.ID, id peer.ID) (*pb.Message, error) {
	defer log.EventBegin(ctx, "findPeerSingle", p, id).Done()

	pmes := pb.NewMessage(pb.Message_FIND_NODE, string(id), 0)
	resp, err := dht.sendRequest(ctx, p, pmes)
	switch err {
	case nil:
		return resp, nil
	case ErrReadTimeout:
		log.Warningf("read timeout: %s %s", p.Pretty(), id)
		fallthrough
	default:
		return nil, err
	}
}

func (dht *IpfsDHT) findProvidersSingle(ctx context.Context, p peer.ID, key *cid.Cid) (*pb.Message, error) {
	defer log.EventBegin(ctx, "findProvidersSingle", p, key).Done()

	pmes := pb.NewMessage(pb.Message_GET_PROVIDERS, key.KeyString(), 0)
	resp, err := dht.sendRequest(ctx, p, pmes)
	switch err {
	case nil:
		return resp, nil
	case ErrReadTimeout:
		log.Warningf("read timeout: %s %s", p.Pretty(), key)
		fallthrough
	default:
		return nil, err
	}
}

// nearestPeersToQuery returns the routing tables closest peers.
func (dht *IpfsDHT) nearestPeersToQuery(pmes *pb.Message, count int) []peer.ID {
	closer := dht.routingTable.NearestPeers(kb.ConvertKey(pmes.GetKey()), count)
	return closer
}

// betterPeerToQuery returns nearestPeersToQuery, but iff closer than self.
func (dht *IpfsDHT) betterPeersToQuery(pmes *pb.Message, p peer.ID, count int) []peer.ID {
	closer := dht.nearestPeersToQuery(pmes, count)

	// no node? nil
	if closer == nil {
		return nil
	}

	// == to self? thats bad
	for _, p := range closer {
		if p == dht.self {
			log.Debug("attempted to return self! this shouldn't happen...")
			return nil
		}
	}

	var filtered []peer.ID
	for _, clp := range closer {
		// Dont send a peer back themselves
		if p == clp {
			continue
		}

		filtered = append(filtered, clp)
	}

	// ok seems like closer nodes
	return filtered
}

// Context return dht's context
func (dht *IpfsDHT) Context() context.Context {
	return dht.ctx
}

// Process return dht's process
func (dht *IpfsDHT) Process() goprocess.Process {
	return dht.proc
}

// Close calls Process Close
func (dht *IpfsDHT) Close() error {
	return dht.proc.Close()
}

func mkDsKey(s string) ds.Key {
	return ds.NewKey(base32.RawStdEncoding.EncodeToString([]byte(s)))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/miekg/dns"
)

func dnsSetup(cfg *Config, etc *etcd.Client) chan error {
	dns.HandleFunc(".", func(w dns.ResponseWriter, req *dns.Msg) { dnsQueryServe(cfg, etc, w, req) })
	etc.CreateDir("dns", 0)
	exit := make(chan error, 1)

	go func() {
		exit <- dns.ListenAndServe("0.0.0.0:53", "tcp", nil) // TODO: should use cfg to define the listening ip/port
	}()

	go func() {
		exit <- dns.ListenAndServe("0.0.0.0:53", "udp", nil) // TODO: should use cfg to define the listening ip/port
	}()

	return exit
}

func dnsQueryServe(cfg *Config, etc *etcd.Client, w dns.ResponseWriter, req *dns.Msg) {
	q := req.Question[0]

	if req.MsgHdr.Response == true { // supposed responses sent to us are bogus
		fmt.Printf("DNS Query IS BOGUS %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr())
		return
	}

	fmt.Printf("DNS Query %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr())

	// TODO: lookup in an in-memory cache (obeying TTLs!)

	qType := dns.Type(q.Qtype).String()                              // query type
	pathParts := strings.Split(strings.TrimSuffix(q.Name, "."), ".") // breakup the queryed name
	queryPath := strings.Join(reverseSlice(pathParts), "/")          // reverse and join them with a slash delimiter
	key := strings.ToLower("/dns/" + queryPath + "/@" + qType)       // structure the lookup key
	response, err := etc.Get(strings.ToLower(key), true, true)       // do the lookup
	if err == nil && response != nil && response.Node != nil && len(response.Node.Nodes) > 0 {
		var vals *etcd.Node
		meta := make(map[string]string)
		for _, node := range response.Node.Nodes {
			nodeKey := strings.Replace(node.Key, key+"/", "", 1)
			if nodeKey == "val" && node.Dir {
				vals = node
			} else if !node.Dir {
				meta[nodeKey] = node.Value // NOTE: the keys are case-sensitive
			}
		}

		answerMsg := new(dns.Msg)
		answerMsg.Id = req.Id
		answerMsg.Response = true
		answerMsg.Authoritative = true
		answerMsg.Question = req.Question
		answerMsg.Rcode = dns.RcodeSuccess
		answerMsg.Extra = []dns.RR{}
		answerTTL := uint32(10800) // this is the default TTL = 3 hours
		gotTTL, _ := strconv.Atoi(meta["TTL"])
		if gotTTL > 0 {
			answerTTL = uint32(gotTTL)
		}

		switch qType {
		case "SOA":
			answer := new(dns.SOA)
			answer.Header().Name = q.Name
			answer.Header().Ttl = answerTTL
			answer.Header().Rrtype = dns.TypeSOA
			answer.Header().Class = dns.ClassINET
			answer.Ns = strings.TrimSuffix(meta["NS"], ".") + "."
			answer.Mbox = strings.TrimSuffix(meta["MBOX"], ".") + "."
			answer.Serial = uint32(time.Now().Unix())
			answer.Refresh = uint32(60) // only used for master->slave timing
			answer.Retry = uint32(60)   // only used for master->slave timing
			answer.Expire = uint32(60)  // only used for master->slave timing
			answer.Minttl = uint32(60)  // how long caching resolvers should cache a miss (NXDOMAIN status)
			answerMsg.Answer = append(answerMsg.Answer, answer)
		default:
			// ... for answers that have values
			if vals != nil && vals.Nodes != nil {
				for _, child := range vals.Nodes {
					if child.Expiration != nil && child.Expiration.Unix() < time.Now().Unix() {
						continue
					}
					if child.TTL > 0 && uint32(child.TTL) < answerTTL {
						answerTTL = uint32(child.TTL)
					}

					// this builds the attributes for complex types, like MX and SRV
					attr := make(map[string]string)
					if child.Nodes != nil {
						for _, attrNode := range child.Nodes {
							nodeKey := strings.Replace(attrNode.Key, child.Key+"/", "", 1)
							attr[nodeKey] = attrNode.Value
						}
					}

					switch qType {
					// FIXME: Add more RR types!
					//        http://godoc.org/github.com/miekg/dns has info as well as
					//        http://en.wikipedia.org/wiki/List_of_DNS_record_types
					case "TXT":
						answer := new(dns.TXT)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeTXT
						answer.Header().Class = dns.ClassINET
						answer.Txt = []string{child.Value}
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "A":
						answer := new(dns.A)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeA
						answer.Header().Class = dns.ClassINET
						answer.A = net.ParseIP(child.Value)
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "AAAA":
						answer := new(dns.AAAA)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeAAAA
						answer.Header().Class = dns.ClassINET
						answer.AAAA = net.ParseIP(child.Value)
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "NS":
						answer := new(dns.NS)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeNS
						answer.Header().Class = dns.ClassINET
						answer.Ns = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "CNAME":
						// FIXME: This is not being used quite correctly.  For example, we
						//        we need to ensure that we are not supplying conflicting
						//        RRs in the same space.  We need to be using the CNAME if
						//        it exists even if the client is asking for a different
						//        RR type.  We have a CNAME in this space and they ask for
						//        an A record?  Return the CNAME!  And maybe even resolve
						//        the value for them.  Or at least carry them as far down
						//        the CNAME chain as possible depending on our willingness
						//        to talk to external DNS servers for recursive queries.
						//        For now, I consider the present behavior incredibly
						//        broken.  It needs to be fixed.  See also DNAME.
						//				... http://en.wikipedia.org/wiki/CNAME_record
						answer := new(dns.CNAME)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeCNAME
						answer.Header().Class = dns.ClassINET
						answer.Target = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "DNAME":
						// FIXME: This is not being used correctly.  See the notes about
						//        fixing CNAME and then consider that DNAME takes it a
						//        big step forward and aliases an entire subtree, not just
						//        a single name in the tree.  Note that this is for pointing
						//        to subtree, not to the self-equivalent.  See the Wikipedia
						//        article about it, linked below.  See also CNAME above.
						//				... http://en.wikipedia.org/wiki/CNAME_record#DNAME_record
						answer := new(dns.DNAME)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeDNAME
						answer.Header().Class = dns.ClassINET
						answer.Target = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "PTR":
						answer := new(dns.PTR)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypePTR
						answer.Header().Class = dns.ClassINET
						answer.Ptr = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "MX":
						answer := new(dns.MX)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeMX
						answer.Header().Class = dns.ClassINET
						answer.Preference = 50 // default if not defined
						priority, err := strconv.Atoi(attr["PRIORITY"])
						if err == nil {
							answer.Preference = uint16(priority)
						}
						if target, ok := attr["TARGET"]; ok {
							answer.Mx = strings.TrimSuffix(target, ".") + "."
						} else if child.Value != "" { // allows for simplified setting
							answer.Mx = strings.TrimSuffix(child.Value, ".") + "."
						}
						// FIXME: are we supposed to be returning these in prio ordering?
						//        ... or maybe it does that for us?  or maybe it's the enduser's problem?
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "SRV":
						answer := new(dns.SRV)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeSRV
						answer.Header().Class = dns.ClassINET
						answer.Priority = 50 // default if not defined
						priority, err := strconv.Atoi(attr["PRIORITY"])
						if err == nil {
							answer.Priority = uint16(priority)
						}
						answer.Weight = 50 // default if not defined
						weight, err := strconv.Atoi(attr["WEIGHT"])
						if err == nil {
							answer.Weight = uint16(weight)
						}
						answer.Port = 50 // default if not defined
						port, err := strconv.Atoi(attr["PORT"])
						if err == nil {
							answer.Port = uint16(port)
						}
						if target, ok := attr["TARGET"]; ok {
							answer.Target = strings.TrimSuffix(target, ".") + "."
						} else if child.Value != "" { // allows for simplified setting
							targetParts := strings.Split(child.Value, ":")
							answer.Target = strings.TrimSuffix(targetParts[0], ".") + "."
							port, err := strconv.Atoi(targetParts[1])
							if err == nil {
								answer.Port = uint16(port)
							}
						}
						// FIXME: are we supposed to be returning these rando-weighted and in priority ordering?
						//        ... or maybe it does that for us?  or maybe it's the enduser's problem?
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "SSHFP":
						// TODO: implement SSHFP
						//       http://godoc.org/github.com/miekg/dns#SSHFP
						//       NOTE: we must implement DNSSEC before using this RR type
					}
				}
			}
		}

		if len(answerMsg.Answer) > 0 {
			for _, answer := range answerMsg.Answer {
				answer.Header().Ttl = answerTTL
			}
			//fmt.Printf("OUR DATA: [%+v]\n", answerMsg)
			w.WriteMsg(answerMsg)

			// TODO: cache the response locally in RAM?

			return
		}
	}

	// check to see if we host this zone; if yes, don't allow use of ext forwarders
	// ... also, check to see if we hit a DNAME so we can handle that aliasing
	allowUseForwarder := true
	{
		keyParts := strings.Split(key, "/")
		for i := len(keyParts); i > 2; i-- {
			parentKey := strings.Join(keyParts[0:i], "/")
			//fmt.Printf("PARENTKEY: [%s]\n", parentKey)
			{ // test for an SOA (which tells us we have authority)
				parentKey := parentKey + "/@soa"
				response, err := etc.Get(strings.ToLower(parentKey), false, false) // do the lookup
				if err == nil && response != nil && response.Node != nil {
					//fmt.Printf("PARENTKEY EXISTS\n")
					allowUseForwarder = false
					break
				}
			}
			{ // test for a DNAME which has special handling for aliasing of subdomains within
				parentKey := parentKey + "/@dname"
				response, err := etc.Get(strings.ToLower(parentKey), false, false) // do the lookup
				if err == nil && response != nil && response.Node != nil {
					fmt.Printf("DNAME EXISTS!  WE NEED TO HANDLE THIS CORRECTLY... FIXME\n")
					allowUseForwarder = false
					// FIXME!  THIS NEEDS TO HANDLE DNAME ALIASING CORRECTLY INSTEAD OF IGNORING IT...
					break
				}
			}
		}
	}

	if allowUseForwarder {
		forwarders := cfg.DNSForwarders()
		if len(forwarders) == 0 {
			// we have no upstreams, so we'll just not use any
		} else if strings.TrimSpace(forwarders[0]) == "!" {
			// we've been told explicitly to not pass anything along to any upsteams
		} else {
			c := new(dns.Client)
			for _, server := range forwarders {
				c.Net = "udp"
				m, _, err := c.Exchange(req, strings.TrimSpace(server))

				if m != nil && m.MsgHdr.Truncated {
					c.Net = "tcp"
					m, _, err = c.Exchange(req, strings.TrimSpace(server))
				}

				// FIXME: Cache misses.  And cache hits, too.

				if err != nil {
					fmt.Println(err)
				} else {
					w.WriteMsg(m)
					return // because we're done here
				}
			}
		}
	}

	// if we got here, it means we didn't find what we were looking for.
	failMsg := new(dns.Msg)
	failMsg.Id = req.Id
	failMsg.Response = true
	failMsg.Authoritative = true
	failMsg.Question = req.Question
	failMsg.Rcode = dns.RcodeNameError
	w.WriteMsg(failMsg)
	return
}

// FIXME: please support DNSSEC, verification, signing, etc...
<commit_msg>Added TODO note about AXFR/IXFR<commit_after>package main

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/miekg/dns"
)

func dnsSetup(cfg *Config, etc *etcd.Client) chan error {
	dns.HandleFunc(".", func(w dns.ResponseWriter, req *dns.Msg) { dnsQueryServe(cfg, etc, w, req) })
	etc.CreateDir("dns", 0)
	exit := make(chan error, 1)

	go func() {
		exit <- dns.ListenAndServe("0.0.0.0:53", "tcp", nil) // TODO: should use cfg to define the listening ip/port
	}()

	go func() {
		exit <- dns.ListenAndServe("0.0.0.0:53", "udp", nil) // TODO: should use cfg to define the listening ip/port
	}()

	return exit
}

func dnsQueryServe(cfg *Config, etc *etcd.Client, w dns.ResponseWriter, req *dns.Msg) {
	q := req.Question[0]

	if req.MsgHdr.Response == true { // supposed responses sent to us are bogus
		fmt.Printf("DNS Query IS BOGUS %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr())
		return
	}

	fmt.Printf("DNS Query %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr())

	// TODO: lookup in an in-memory cache (obeying TTLs!)

	// TODO: handle AXFR/IXFR (full and incremental) *someday* for use by non-netcore slaves
	//       ... also if we do that, also handle sending NOTIFY to listed slaves attached to the SOA record

	qType := dns.Type(q.Qtype).String()                              // query type
	pathParts := strings.Split(strings.TrimSuffix(q.Name, "."), ".") // breakup the queryed name
	queryPath := strings.Join(reverseSlice(pathParts), "/")          // reverse and join them with a slash delimiter
	key := strings.ToLower("/dns/" + queryPath + "/@" + qType)       // structure the lookup key
	response, err := etc.Get(strings.ToLower(key), true, true)       // do the lookup
	if err == nil && response != nil && response.Node != nil && len(response.Node.Nodes) > 0 {
		var vals *etcd.Node
		meta := make(map[string]string)
		for _, node := range response.Node.Nodes {
			nodeKey := strings.Replace(node.Key, key+"/", "", 1)
			if nodeKey == "val" && node.Dir {
				vals = node
			} else if !node.Dir {
				meta[nodeKey] = node.Value // NOTE: the keys are case-sensitive
			}
		}

		answerMsg := new(dns.Msg)
		answerMsg.Id = req.Id
		answerMsg.Response = true
		answerMsg.Authoritative = true
		answerMsg.Question = req.Question
		answerMsg.Rcode = dns.RcodeSuccess
		answerMsg.Extra = []dns.RR{}
		answerTTL := uint32(10800) // this is the default TTL = 3 hours
		gotTTL, _ := strconv.Atoi(meta["TTL"])
		if gotTTL > 0 {
			answerTTL = uint32(gotTTL)
		}

		switch qType {
		case "SOA":
			answer := new(dns.SOA)
			answer.Header().Name = q.Name
			answer.Header().Ttl = answerTTL
			answer.Header().Rrtype = dns.TypeSOA
			answer.Header().Class = dns.ClassINET
			answer.Ns = strings.TrimSuffix(meta["NS"], ".") + "."
			answer.Mbox = strings.TrimSuffix(meta["MBOX"], ".") + "."
			answer.Serial = uint32(time.Now().Unix())
			answer.Refresh = uint32(60) // only used for master->slave timing
			answer.Retry = uint32(60)   // only used for master->slave timing
			answer.Expire = uint32(60)  // only used for master->slave timing
			answer.Minttl = uint32(60)  // how long caching resolvers should cache a miss (NXDOMAIN status)
			answerMsg.Answer = append(answerMsg.Answer, answer)
		default:
			// ... for answers that have values
			if vals != nil && vals.Nodes != nil {
				for _, child := range vals.Nodes {
					if child.Expiration != nil && child.Expiration.Unix() < time.Now().Unix() {
						continue
					}
					if child.TTL > 0 && uint32(child.TTL) < answerTTL {
						answerTTL = uint32(child.TTL)
					}

					// this builds the attributes for complex types, like MX and SRV
					attr := make(map[string]string)
					if child.Nodes != nil {
						for _, attrNode := range child.Nodes {
							nodeKey := strings.Replace(attrNode.Key, child.Key+"/", "", 1)
							attr[nodeKey] = attrNode.Value
						}
					}

					switch qType {
					// FIXME: Add more RR types!
					//        http://godoc.org/github.com/miekg/dns has info as well as
					//        http://en.wikipedia.org/wiki/List_of_DNS_record_types
					case "TXT":
						answer := new(dns.TXT)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeTXT
						answer.Header().Class = dns.ClassINET
						answer.Txt = []string{child.Value}
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "A":
						answer := new(dns.A)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeA
						answer.Header().Class = dns.ClassINET
						answer.A = net.ParseIP(child.Value)
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "AAAA":
						answer := new(dns.AAAA)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeAAAA
						answer.Header().Class = dns.ClassINET
						answer.AAAA = net.ParseIP(child.Value)
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "NS":
						answer := new(dns.NS)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeNS
						answer.Header().Class = dns.ClassINET
						answer.Ns = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "CNAME":
						// FIXME: This is not being used quite correctly.  For example, we
						//        we need to ensure that we are not supplying conflicting
						//        RRs in the same space.  We need to be using the CNAME if
						//        it exists even if the client is asking for a different
						//        RR type.  We have a CNAME in this space and they ask for
						//        an A record?  Return the CNAME!  And maybe even resolve
						//        the value for them.  Or at least carry them as far down
						//        the CNAME chain as possible depending on our willingness
						//        to talk to external DNS servers for recursive queries.
						//        For now, I consider the present behavior incredibly
						//        broken.  It needs to be fixed.  See also DNAME.
						//				... http://en.wikipedia.org/wiki/CNAME_record
						answer := new(dns.CNAME)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeCNAME
						answer.Header().Class = dns.ClassINET
						answer.Target = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "DNAME":
						// FIXME: This is not being used correctly.  See the notes about
						//        fixing CNAME and then consider that DNAME takes it a
						//        big step forward and aliases an entire subtree, not just
						//        a single name in the tree.  Note that this is for pointing
						//        to subtree, not to the self-equivalent.  See the Wikipedia
						//        article about it, linked below.  See also CNAME above.
						//				... http://en.wikipedia.org/wiki/CNAME_record#DNAME_record
						answer := new(dns.DNAME)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeDNAME
						answer.Header().Class = dns.ClassINET
						answer.Target = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "PTR":
						answer := new(dns.PTR)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypePTR
						answer.Header().Class = dns.ClassINET
						answer.Ptr = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "MX":
						answer := new(dns.MX)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeMX
						answer.Header().Class = dns.ClassINET
						answer.Preference = 50 // default if not defined
						priority, err := strconv.Atoi(attr["PRIORITY"])
						if err == nil {
							answer.Preference = uint16(priority)
						}
						if target, ok := attr["TARGET"]; ok {
							answer.Mx = strings.TrimSuffix(target, ".") + "."
						} else if child.Value != "" { // allows for simplified setting
							answer.Mx = strings.TrimSuffix(child.Value, ".") + "."
						}
						// FIXME: are we supposed to be returning these in prio ordering?
						//        ... or maybe it does that for us?  or maybe it's the enduser's problem?
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "SRV":
						answer := new(dns.SRV)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeSRV
						answer.Header().Class = dns.ClassINET
						answer.Priority = 50 // default if not defined
						priority, err := strconv.Atoi(attr["PRIORITY"])
						if err == nil {
							answer.Priority = uint16(priority)
						}
						answer.Weight = 50 // default if not defined
						weight, err := strconv.Atoi(attr["WEIGHT"])
						if err == nil {
							answer.Weight = uint16(weight)
						}
						answer.Port = 50 // default if not defined
						port, err := strconv.Atoi(attr["PORT"])
						if err == nil {
							answer.Port = uint16(port)
						}
						if target, ok := attr["TARGET"]; ok {
							answer.Target = strings.TrimSuffix(target, ".") + "."
						} else if child.Value != "" { // allows for simplified setting
							targetParts := strings.Split(child.Value, ":")
							answer.Target = strings.TrimSuffix(targetParts[0], ".") + "."
							port, err := strconv.Atoi(targetParts[1])
							if err == nil {
								answer.Port = uint16(port)
							}
						}
						// FIXME: are we supposed to be returning these rando-weighted and in priority ordering?
						//        ... or maybe it does that for us?  or maybe it's the enduser's problem?
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "SSHFP":
						// TODO: implement SSHFP
						//       http://godoc.org/github.com/miekg/dns#SSHFP
						//       NOTE: we must implement DNSSEC before using this RR type
					}
				}
			}
		}

		if len(answerMsg.Answer) > 0 {
			for _, answer := range answerMsg.Answer {
				answer.Header().Ttl = answerTTL
			}
			//fmt.Printf("OUR DATA: [%+v]\n", answerMsg)
			w.WriteMsg(answerMsg)

			// TODO: cache the response locally in RAM?

			return
		}
	}

	// check to see if we host this zone; if yes, don't allow use of ext forwarders
	// ... also, check to see if we hit a DNAME so we can handle that aliasing
	allowUseForwarder := true
	{
		keyParts := strings.Split(key, "/")
		for i := len(keyParts); i > 2; i-- {
			parentKey := strings.Join(keyParts[0:i], "/")
			//fmt.Printf("PARENTKEY: [%s]\n", parentKey)
			{ // test for an SOA (which tells us we have authority)
				parentKey := parentKey + "/@soa"
				response, err := etc.Get(strings.ToLower(parentKey), false, false) // do the lookup
				if err == nil && response != nil && response.Node != nil {
					//fmt.Printf("PARENTKEY EXISTS\n")
					allowUseForwarder = false
					break
				}
			}
			{ // test for a DNAME which has special handling for aliasing of subdomains within
				parentKey := parentKey + "/@dname"
				response, err := etc.Get(strings.ToLower(parentKey), false, false) // do the lookup
				if err == nil && response != nil && response.Node != nil {
					fmt.Printf("DNAME EXISTS!  WE NEED TO HANDLE THIS CORRECTLY... FIXME\n")
					allowUseForwarder = false
					// FIXME!  THIS NEEDS TO HANDLE DNAME ALIASING CORRECTLY INSTEAD OF IGNORING IT...
					break
				}
			}
		}
	}

	if allowUseForwarder {
		forwarders := cfg.DNSForwarders()
		if len(forwarders) == 0 {
			// we have no upstreams, so we'll just not use any
		} else if strings.TrimSpace(forwarders[0]) == "!" {
			// we've been told explicitly to not pass anything along to any upsteams
		} else {
			c := new(dns.Client)
			for _, server := range forwarders {
				c.Net = "udp"
				m, _, err := c.Exchange(req, strings.TrimSpace(server))

				if m != nil && m.MsgHdr.Truncated {
					c.Net = "tcp"
					m, _, err = c.Exchange(req, strings.TrimSpace(server))
				}

				// FIXME: Cache misses.  And cache hits, too.

				if err != nil {
					fmt.Println(err)
				} else {
					w.WriteMsg(m)
					return // because we're done here
				}
			}
		}
	}

	// if we got here, it means we didn't find what we were looking for.
	failMsg := new(dns.Msg)
	failMsg.Id = req.Id
	failMsg.Response = true
	failMsg.Authoritative = true
	failMsg.Question = req.Question
	failMsg.Rcode = dns.RcodeNameError
	w.WriteMsg(failMsg)
	return
}

// FIXME: please support DNSSEC, verification, signing, etc...
<|endoftext|>


<commit_before>/*
Package cron implements a cron spec parser and job runner.

Usage

Callers may register Funcs to be invoked on a given schedule.  Cron will run
them in their own goroutines.

	c := cron.New()
	c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
	c.AddFunc("@hourly",      func() { fmt.Println("Every hour") })
	c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
	c.Start()
	..
	// Funcs are invoked in their own goroutine, asynchronously.
	...
	// Funcs may also be added to a running Cron
	c.AddFunc("@daily", func() { fmt.Println("Every day") })
	..
	// Inspect the cron job entries' next and previous run times.
	inspect(c.Entries())
	..
	c.Stop()  // Stop the scheduler (does not stop any jobs already running).

CRON Expression Format

A cron expression represents a set of times, using 6 space-separated fields.

	Field name   | Mandatory? | Allowed values  | Allowed special characters
	----------   | ---------- | --------------  | --------------------------
	Seconds      | Yes        | 0-59            | * / , -
	Minutes      | Yes        | 0-59            | * / , -
	Hours        | Yes        | 0-23            | * / , -
	Day of month | Yes        | 1-31            | * / , - ?
	Month        | Yes        | 1-12 or JAN-DEC | * / , -
	Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?

Note: Month and Day-of-week field values are case insensitive.  "SUN", "Sun",
and "sun" are equally accepted.

Special Characters

Asterisk ( * )

The asterisk indicates that the cron expression will match for all values of the
field; e.g., using an asterisk in the 5th field (month) would indicate every
month.

Slash ( / )

Slashes are used to describe increments of ranges. For example 3-59/15 in the
1st field (minutes) would indicate the 3rd minute of the hour and every 15
minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
that is, an increment over the largest possible range of the field.  The form
"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
increment until the end of that specific range.  It does not wrap around.

Comma ( , )

Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.

Hyphen ( - )

Hyphens are used to define ranges. For example, 9-17 would indicate every
hour between 9am and 5pm inclusive.

Question mark ( ? )

Question mark may be used instead of '*' for leaving either day-of-month or
day-of-week blank.

Predefined schedules

You may use one of several pre-defined schedules in place of a cron expression.

	Entry                  | Description                                | Equivalent To
	-----                  | -----------                                | -------------
	@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 0 1 1 *
	@monthly               | Run once a month, midnight, first of month | 0 0 0 1 * *
	@weekly                | Run once a week, midnight on Sunday        | 0 0 0 * * 0
	@daily (or @midnight)  | Run once a day, midnight                   | 0 0 0 * * *
	@hourly                | Run once an hour, beginning of hour        | 0 0 * * * *

Intervals

You may also schedule a job to execute at fixed intervals.  This is supported by
formatting the cron spec like this:

    @every <duration>

where "duration" is a string accepted by time.ParseDuration
(http://golang.org/pkg/time/#ParseDuration).

For example, "@every 1h30m10s" would indicate a schedule that activates every
1 hour, 30 minutes, 10 seconds.

Note: The interval does not take the job runtime into account.  For example,
if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
it will have only 2 minutes of idle time between each run.

Time zones

All interpretation and scheduling is done in the machine's local time zone (as
provided by the Go time package (http://www.golang.org/pkg/time).

Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
not be run!

Thread safety

Since the Cron service runs concurrently with the calling code, some amount of
care must be taken to ensure proper synchronization.

All cron methods are designed to be correctly synchronized as long as the caller
ensures that invocations have a clear happens-before ordering between them.

Implementation

Cron entries are stored in an array, sorted by their next activation time.  Cron
sleeps until the next job is due to be run.

Upon waking:
 - it runs each entry that is active on that second
 - it calculates the next run times for the jobs that were run
 - it re-sorts the array of entries by next activation time.
 - it goes to sleep until the soonest job.
*/
package cron
<commit_msg>Clarify meaning of @every<commit_after>/*
Package cron implements a cron spec parser and job runner.

Usage

Callers may register Funcs to be invoked on a given schedule.  Cron will run
them in their own goroutines.

	c := cron.New()
	c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
	c.AddFunc("@hourly",      func() { fmt.Println("Every hour") })
	c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
	c.Start()
	..
	// Funcs are invoked in their own goroutine, asynchronously.
	...
	// Funcs may also be added to a running Cron
	c.AddFunc("@daily", func() { fmt.Println("Every day") })
	..
	// Inspect the cron job entries' next and previous run times.
	inspect(c.Entries())
	..
	c.Stop()  // Stop the scheduler (does not stop any jobs already running).

CRON Expression Format

A cron expression represents a set of times, using 6 space-separated fields.

	Field name   | Mandatory? | Allowed values  | Allowed special characters
	----------   | ---------- | --------------  | --------------------------
	Seconds      | Yes        | 0-59            | * / , -
	Minutes      | Yes        | 0-59            | * / , -
	Hours        | Yes        | 0-23            | * / , -
	Day of month | Yes        | 1-31            | * / , - ?
	Month        | Yes        | 1-12 or JAN-DEC | * / , -
	Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?

Note: Month and Day-of-week field values are case insensitive.  "SUN", "Sun",
and "sun" are equally accepted.

Special Characters

Asterisk ( * )

The asterisk indicates that the cron expression will match for all values of the
field; e.g., using an asterisk in the 5th field (month) would indicate every
month.

Slash ( / )

Slashes are used to describe increments of ranges. For example 3-59/15 in the
1st field (minutes) would indicate the 3rd minute of the hour and every 15
minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
that is, an increment over the largest possible range of the field.  The form
"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
increment until the end of that specific range.  It does not wrap around.

Comma ( , )

Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.

Hyphen ( - )

Hyphens are used to define ranges. For example, 9-17 would indicate every
hour between 9am and 5pm inclusive.

Question mark ( ? )

Question mark may be used instead of '*' for leaving either day-of-month or
day-of-week blank.

Predefined schedules

You may use one of several pre-defined schedules in place of a cron expression.

	Entry                  | Description                                | Equivalent To
	-----                  | -----------                                | -------------
	@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 0 1 1 *
	@monthly               | Run once a month, midnight, first of month | 0 0 0 1 * *
	@weekly                | Run once a week, midnight on Sunday        | 0 0 0 * * 0
	@daily (or @midnight)  | Run once a day, midnight                   | 0 0 0 * * *
	@hourly                | Run once an hour, beginning of hour        | 0 0 * * * *

Intervals

You may also schedule a job to execute at fixed intervals, starting at the time it's added
or cron is run. This is supported by formatting the cron spec like this:

    @every <duration>

where "duration" is a string accepted by time.ParseDuration
(http://golang.org/pkg/time/#ParseDuration).

For example, "@every 1h30m10s" would indicate a schedule that activates immediately,
and then every 1 hour, 30 minutes, 10 seconds.

Note: The interval does not take the job runtime into account.  For example,
if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
it will have only 2 minutes of idle time between each run.

Time zones

All interpretation and scheduling is done in the machine's local time zone (as
provided by the Go time package (http://www.golang.org/pkg/time).

Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
not be run!

Thread safety

Since the Cron service runs concurrently with the calling code, some amount of
care must be taken to ensure proper synchronization.

All cron methods are designed to be correctly synchronized as long as the caller
ensures that invocations have a clear happens-before ordering between them.

Implementation

Cron entries are stored in an array, sorted by their next activation time.  Cron
sleeps until the next job is due to be run.

Upon waking:
 - it runs each entry that is active on that second
 - it calculates the next run times for the jobs that were run
 - it re-sorts the array of entries by next activation time.
 - it goes to sleep until the soonest job.
*/
package cron
<|endoftext|>


<commit_before>/**
 *
 *
 */

package atm
<commit_msg>Edited doc.go<commit_after>//Package atm contains a template manager that tries to manage html.Template.
/*
Here is some sample code from the README.md file. Assume this is the directory
structure.

    /tmp/template-dir/
    -----------------/template-1.html
    -----------------/template-2.html
    -----------------/atoms/
    -----------------------/atom-1.html
    -----------------------/atom-2.tpl  //this one is a tpl file extension
    -----------------------/subatoms/sub-atom-1.html
    -----------------/pages/
    -----------------------/page-1.html

Now you can do:

	package main
	import(
		//url is long but package name is actually atm
		"github.com/starJammer/atomic-template-manager"
		"os"
		"fmt"
	)

	var man Manager

	func main() {
		man = atm.New()

		//takes an unlimited number of strings that
		//should point to directories.
		man.AddDirectories( "/tmp/template-dir" )

		//Parse template produces a slice of errors.
		//It tries to do as much as it can but you should
		//check to see if this slice is none nil
		errs := man.ParseTemplates()

		if errs != nil {
			fmt.Println( errs )
		}

		//We can execute one of the templates using a shorthand name
		//like in pattern lab.
		//Notice that there is no file extension
		//Additionally, the path separator becomes a hyphen
		man.ExecuteTemplate( os.Stdout, "pages-page-1", nil )

		//we can also use the full path to call the same template
		man.ExecuteTemplate( os.Stdout, "pages/page-1.html", nil )

		//Notice in the call below that the subatoms subdirectory is omitted from
		//the template name
		man.ExecuteTemplate( os.Stdout, "atoms-sub-atom-1", nil )

		//we can also just use the long name
		man.ExecuteTemplate( os.Stdout, "atoms/subatoms/sub-atom-1.html", nil )

		//The manager scans for both tpl and html files so we can
		//still call atoms-2 with:
		man.ExecuteTemplate( os.Stdout, "atoms-atom-2", nil )

		//anything at the top directory is called with either shorthand
		man.ExecuteTemplate( os.Stdout, "template-1", nil )

		//OR the full filename
		man.ExecuteTemplate( os.Stdout, "template-1.html", nil )

		//use a custom file extension. Call this before ParseTemplates()
		man.AddFileExtension( "new-tpl" )
		//Ignore all files with html extension. Call this before ParseTemplates()
		man.RemoveFileExtension( "html" )
		//FYI, using files with no extension is not supported.

		//same as html/template package
		man.Delims( "<<<", ">>>" )

		//same to html.Template.Funcs
		man.Funcs( map[string]interface{}{
			"func1" : func( arg string ) string {return arg}
			"func2" : func( arg string ) string {return arg+"2"}
		})

		//Same as html/template package
		man.Lookup( "template-name" )

		//Same as html/template package
		man.Templates()

		//reparse all templates on ExecuteTemplate, ok for dev not for prod
		man.SetReparseOnExecute(true)
	}
*/
package atm
<|endoftext|>


<commit_before>// Package exprel provides a Spreadsheet-like expression evaluator.
//
//   // Quick start
//
//   import (
//     "layeh.com/exprel"
//   )
//
//   data := map[string]interface{}{
//     "name": "Tim",
//   }
//   expression := `=LOWER(name) & ".jpg"`
//   filename, err := exprel.String(exprel.Evaluate(expression, exprel.SourceMap(data)))
//   if err != nil {
//     panic(err)
//   }
//   // filename = "tim.jpg"
//
// Introduction
//
// All expressions return a single value. Here are a few examples of some valid
// expressions and their return values:
//  Expression                        Return value
//  ----------------------------------------------
//  Hey there                         "Hey there"
//  1234                              "1234"
//  =5+5*2                            15
//  ="A" & " " & "B"                  "A B"
//  =IF(AND(NOT(FALSE());1=1);1+2;2)  3
//
// Expressions with logic must start with an equals sign (=). Otherwise, the
// evaluated value is simply the source string.
//
// Values
//
// The following values are can be returned by and used in an expression:
//  string
//  float64 (number)
//  bool (boolean)
//
// Sources may also return the following type, which defines a function that
// can be called from an expression:
//  func(c *Call) (value interface{}, err error)
//
// Operators
//
// The following operators and built-ins are defined:
//                    Usage              Notes
//  ------------------------------------------
//  Addition          a + b              number
//  Subtraction       a - b              number
//  Multiplication    a * b              number
//  Division          a / b              number
//  Exponentiation    a ^ b              number
//  Modulo            a % b              number
//  Concatenation     a & b              string
//
//  Equality          a = b              string, number, boolean
//  Inequality        a <> b             string, number, boolean
//  Greater than      a > b              string, number
//  Greater or equal  a > b              string, number
//  Less than         a < b              string, number
//  Less or equal     a <= b             string, number
//
//  Logical AND       AND(bool...)       Operands lazily evaluated
//  Logical OR        OR(bool...)        Operands lazily evaluated
//  Logical NOT       NOT(bool)          Operands lazily evaluated
//
//  Condition         IF(bool;ANY;ANY)   Lazily evaluated
//
//  Boolean true      TRUE()
//  Boolean false     FALSE()
//
//
// The following functions are defined as part of Base:
//  CHOOSE(number index; ANY...) ANY
//    Returns the index item of the remaining arguments
//  TYPE(ANY a) number
//    Identifies the type of a. Types are mapped in the following way:
//      Number  = 1
//      String  = 2
//      Boolean = 4
//
//  ABS(number a) number
//    Returns the absolute value of a.
//  EXP(number a) number
//    Returns e^a.
//  LN(number a) number
//    Returns the natural logarithm of a.
//  LOG10(number a) number
//    Returns the base-10 logarithm of a.
//  PI() number
//    Returns π.
//  RAND() number
//    Returns a random number in the range [0, 1).
//  SIGN(number a) number
//    Returns the sign of a.
//
//  CHAR(number...) string
//    Returns a string whose code points are given as arguments.
//  JOIN(string sep; string...) string
//    Returns the trailing string arguments concatenated together with sep.
//  LEFT(string a; number count = 1) string
//    Returns the count left-most characters of a.
//  LEN(string a) number
//    Returns the length of a.
//  LOWER(string a) string
//    Returns a with all uppercase characters transformed to lowercase.
//  MID(string a; number start; number length = 1) string
//    Returns length characters of a, starting from start.
//  REPT(string a; number count) string
//    Returns the string a, repeated count times.
//  RIGHT(string a; number count = 1) string
//    Returns the count right-most characters of a.
//  SEARCH(string needle; string haystack; number start = 1) number
//    Returns the position of needle in haystack, starting from start. -1 is
//    returned if needle was not found.
//  TRIM(string a) string
//    Returns a with whitespace removed from the beginning and end.
//  UPPER(string a) string
//    Returns a with all lowercase characters transformed to uppercase.
package exprel // import "layeh.com/exprel"
<commit_msg>fix typo in package documentation<commit_after>// Package exprel provides a Spreadsheet-like expression evaluator.
//
//   // Quick start
//
//   import (
//     "layeh.com/exprel"
//   )
//
//   data := map[string]interface{}{
//     "name": "Tim",
//   }
//   expression := `=LOWER(name) & ".jpg"`
//   filename, err := exprel.String(exprel.Evaluate(expression, exprel.SourceMap(data)))
//   if err != nil {
//     panic(err)
//   }
//   // filename = "tim.jpg"
//
// Introduction
//
// All expressions return a single value. Here are a few examples of some valid
// expressions and their return values:
//  Expression                        Return value
//  ----------------------------------------------
//  Hey there                         "Hey there"
//  1234                              "1234"
//  =5+5*2                            15
//  ="A" & " " & "B"                  "A B"
//  =IF(AND(NOT(FALSE());1=1);1+2;2)  3
//
// Expressions with logic must start with an equals sign (=). Otherwise, the
// evaluated value is simply the source string.
//
// Values
//
// The following values are can be returned by and used in an expression:
//  string
//  float64 (number)
//  bool (boolean)
//
// Sources may also return the following type, which defines a function that
// can be called from an expression:
//  func(c *Call) (value interface{}, err error)
//
// Operators
//
// The following operators and built-ins are defined:
//                    Usage              Notes
//  ------------------------------------------
//  Addition          a + b              number
//  Subtraction       a - b              number
//  Multiplication    a * b              number
//  Division          a / b              number
//  Exponentiation    a ^ b              number
//  Modulo            a % b              number
//  Concatenation     a & b              string
//
//  Equality          a = b              string, number, boolean
//  Inequality        a <> b             string, number, boolean
//  Greater than      a > b              string, number
//  Greater or equal  a >= b             string, number
//  Less than         a < b              string, number
//  Less or equal     a <= b             string, number
//
//  Logical AND       AND(bool...)       Operands lazily evaluated
//  Logical OR        OR(bool...)        Operands lazily evaluated
//  Logical NOT       NOT(bool)          Operands lazily evaluated
//
//  Condition         IF(bool;ANY;ANY)   Lazily evaluated
//
//  Boolean true      TRUE()
//  Boolean false     FALSE()
//
//
// The following functions are defined as part of Base:
//  CHOOSE(number index; ANY...) ANY
//    Returns the index item of the remaining arguments
//  TYPE(ANY a) number
//    Identifies the type of a. Types are mapped in the following way:
//      Number  = 1
//      String  = 2
//      Boolean = 4
//
//  ABS(number a) number
//    Returns the absolute value of a.
//  EXP(number a) number
//    Returns e^a.
//  LN(number a) number
//    Returns the natural logarithm of a.
//  LOG10(number a) number
//    Returns the base-10 logarithm of a.
//  PI() number
//    Returns π.
//  RAND() number
//    Returns a random number in the range [0, 1).
//  SIGN(number a) number
//    Returns the sign of a.
//
//  CHAR(number...) string
//    Returns a string whose code points are given as arguments.
//  JOIN(string sep; string...) string
//    Returns the trailing string arguments concatenated together with sep.
//  LEFT(string a; number count = 1) string
//    Returns the count left-most characters of a.
//  LEN(string a) number
//    Returns the length of a.
//  LOWER(string a) string
//    Returns a with all uppercase characters transformed to lowercase.
//  MID(string a; number start; number length = 1) string
//    Returns length characters of a, starting from start.
//  REPT(string a; number count) string
//    Returns the string a, repeated count times.
//  RIGHT(string a; number count = 1) string
//    Returns the count right-most characters of a.
//  SEARCH(string needle; string haystack; number start = 1) number
//    Returns the position of needle in haystack, starting from start. -1 is
//    returned if needle was not found.
//  TRIM(string a) string
//    Returns a with whitespace removed from the beginning and end.
//  UPPER(string a) string
//    Returns a with all lowercase characters transformed to uppercase.
package exprel // import "layeh.com/exprel"
<|endoftext|>


<commit_before>// CheeseGull is a webserver that functions as a cache middleman between the
// official osu! mirrors and requesters of beatmaps, as well as also a cache
// middleman for beatmaps metadata retrieved from the official osu! API.
package main

// Version is the version of cheesegull.
const Version = "v2.0.3"
<commit_msg>⬆ v2.0.4 ⬆<commit_after>// CheeseGull is a webserver that functions as a cache middleman between the
// official osu! mirrors and requesters of beatmaps, as well as also a cache
// middleman for beatmaps metadata retrieved from the official osu! API.
package main

// Version is the version of cheesegull.
const Version = "v2.0.4"
<|endoftext|>


<commit_before>/*
Syndication (feed) generator library for golang.

Installing

	go get github.com/gorilla/feeds

Syndicate provides a simple, generic Feed interface with a generic Item object as well as RSS and Atom specific RssFeed and AtomFeed objects which allow access to all of each spec's defined elements.

Examples

Create a Feed and some Items in that feed using the generic interfaces:

	import (
		"time"
		. "github.com/gorilla/feeds
	)

	now = time.Now()

	feed := &Feed{
		Title:       "jmoiron.net blog",
		Link:        &Link{Href: "http://jmoiron.net/blog"},
		Description: "discussion about tech, footie, photos",
		Author:      &Author{"Jason Moiron", "jmoiron@jmoiron.net"},
		Created:     now,
		Copyright:   "This work is copyright © Benjamin Button",
	}

	feed.Items = []*Item{
		&Item{
			Title:       "Limiting Concurrency in Go",
			Link:        &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
			Description: "A discussion on controlled parallelism in golang",
			Author:      &Author{"Jason Moiron", "jmoiron@jmoiron.net"},
			Created:     now,
		},
		&Item{
			Title:       "Logic-less Template Redux",
			Link:        &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
			Description: "More thoughts on logicless templates",
			Created:     now,
		},
		&Item{
			Title:       "Idiomatic Code Reuse in Go",
			Link:        &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
			Description: "How to use interfaces effectively",
			Created:     now,
		},
	}

From here, you can output Atom or RSS versions of this feed easily

	atom, err := feed.ToAtom()
	rss, err := feed.ToRss()

You can also get access to the underlying objects that feeds uses to export its XML

	atomFeed := &Atom{feed}.AtomFeed()
	rssFeed := &Rss{feed}.RssFeed()

From here, you can modify or add each syndication's specific fields before outputting

	atomFeed.Subtitle = "plays the blues"
	atom, err := ToXML(atomFeed)
	rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)"
	rss, err := ToXML(rssFeed)

*/
package feeds
<commit_msg>Update doc.go<commit_after>/*
Syndication (feed) generator library for golang.

Installing

	go get github.com/gorilla/feeds

Feeds provides a simple, generic Feed interface with a generic Item object as well as RSS and Atom specific RssFeed and AtomFeed objects which allow access to all of each spec's defined elements.

Examples

Create a Feed and some Items in that feed using the generic interfaces:

	import (
		"time"
		. "github.com/gorilla/feeds
	)

	now = time.Now()

	feed := &Feed{
		Title:       "jmoiron.net blog",
		Link:        &Link{Href: "http://jmoiron.net/blog"},
		Description: "discussion about tech, footie, photos",
		Author:      &Author{"Jason Moiron", "jmoiron@jmoiron.net"},
		Created:     now,
		Copyright:   "This work is copyright © Benjamin Button",
	}

	feed.Items = []*Item{
		&Item{
			Title:       "Limiting Concurrency in Go",
			Link:        &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
			Description: "A discussion on controlled parallelism in golang",
			Author:      &Author{"Jason Moiron", "jmoiron@jmoiron.net"},
			Created:     now,
		},
		&Item{
			Title:       "Logic-less Template Redux",
			Link:        &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
			Description: "More thoughts on logicless templates",
			Created:     now,
		},
		&Item{
			Title:       "Idiomatic Code Reuse in Go",
			Link:        &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
			Description: "How to use interfaces effectively",
			Created:     now,
		},
	}

From here, you can output Atom or RSS versions of this feed easily

	atom, err := feed.ToAtom()
	rss, err := feed.ToRss()

You can also get access to the underlying objects that feeds uses to export its XML

	atomFeed := &Atom{feed}.AtomFeed()
	rssFeed := &Rss{feed}.RssFeed()

From here, you can modify or add each syndication's specific fields before outputting

	atomFeed.Subtitle = "plays the blues"
	atom, err := ToXML(atomFeed)
	rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)"
	rss, err := ToXML(rssFeed)

*/
package feeds
<|endoftext|>


<commit_before>/*
Package i18n is a helper for the Core (https://github.com/volatile/core).
It provides internationalization functions following the client preferences.

Installation

In the terminal:

	$ go get github.com/volatile/i18n

Usage

Example:

	package main

	import (
		"github.com/volatile/core"
		"github.com/volatile/i18n"
		"github.com/volatile/response"
	)

	func main() {
		i18n.Use(locales, "en", true)        // Default locale local is "en" and client local will be saved in a cookie on first match.
		response.ViewsFuncs(i18n.ViewsFuncs) // Functions for views templates

		core.Use(func(c *core.Context) {
			response.View(c, "hello", map[string]interface{}{
				"name":        "John Doe",
				"coinsNumber": 500,
			})
		})

		core.Run()
	}

	var locales = i18n.Locales{
		"en": i18n.Locale{
			"decimalMark":   ".",
			"thousandsMark": ",",

			"hello":       "Hello %s,",
			"how":         "How are you?",
			"coins.zero":  "Your wallet is empty.",
			"coins.one":   "You have a single and precious coin.",
			"coins.other": "You have " + i18n.TransNPlaceholder + " coins.",
		},
		"fr": i18n.Locale{
			"decimalMark":   ",",
			"thousandsMark": " ",

			"hello":       "Bonjour %s,",
			"how":         "Comment allez-vous?",
			"coins.zero":  "Vous êtes fauché.",
			"coins.one":   "Vous avez une seule et précieuse pièce.",
			"coins.other": "Vous possédez " + i18n.TransNPlaceholder + " pièces.",
		},
	}

In "views/hello.gohtml":

	{{define "hello"}}
		<!DOCTYPE html>

				<title>Hello</title>

				{{trans .c "hello" .name}}

				{{trans .c "how"}}

				{{transn .c "coins" .coinsNumber}}

	{{end}}

Translations

Use Trans to get the translation for the client matched locale.
If the translation value contains format verbs (like %s or %d), the last variadic receives the content for them.

When the translation associated to key doesn't exist, an empty string is returned in production mode (otherwise, the key).

Pluralization

TransN works like Trans but it tries to find the best translation form, following a number of elements.

A pluralized translation has 3 forms: zero, one, other.
They are defined at the end of the key: "myTranslationKey.zero", "myTranslationKey.one" and "myTranslationKey.other".
If TransNPlaceholder is used in the translation, the number of elements will take this place.

Translation example:

	"apple.zero" = "There are no apples."
	"apple.one" = "There is a single apple."
	"apple.other" = "There are " + i18n.TransNPlaceholder + " apples."

Function example:

	i18n.TransN(c, "apple", 7)

…results in "There are 7 apples".

Numbers

Use #Num to get a formatted number with decimal and thousands marks.
If set, the special "decimalMark" and "thousandsMark" keys will be used from the matched locale.

Views functions

ViewsFuncs provides a map of all functions usable in templates.

Example with the [Response](https://github.com/volatile/response) package:

	response.ViewsFuncs(i18n.ViewsFuncs)

Get current locale

Use ClientLocale to get the locale used by the client.

Set current locale

Use SetClientLocale to manually set the locale used by client.
The change is ignored if the provided locale doesn't exists.
*/
package i18n
<commit_msg>Fix doc<commit_after>/*
Package i18n is a helper for the Core (https://github.com/volatile/core).
It provides internationalization functions following the client preferences.

Installation

In the terminal:

	$ go get github.com/volatile/i18n

Usage

Example:

	package main

	import (
		"github.com/volatile/core"
		"github.com/volatile/i18n"
		"github.com/volatile/response"
	)

	func main() {
		i18n.Use(locales, "en", true)        // Default locale local is "en" and client local will be saved in a cookie on first match.
		response.ViewsFuncs(i18n.ViewsFuncs) // Functions for views templates

		core.Use(func(c *core.Context) {
			response.View(c, "hello", map[string]interface{}{
				"name":        "John Doe",
				"coinsNumber": 500,
			})
		})

		core.Run()
	}

	var locales = i18n.Locales{
		"en": i18n.Locale{
			"decimalMark":   ".",
			"thousandsMark": ",",

			"hello":       "Hello %s,",
			"how":         "How are you?",
			"coins.zero":  "Your wallet is empty.",
			"coins.one":   "You have a single and precious coin.",
			"coins.other": "You have " + i18n.TransNPlaceholder + " coins.",
		},
		"fr": i18n.Locale{
			"decimalMark":   ",",
			"thousandsMark": " ",

			"hello":       "Bonjour %s,",
			"how":         "Comment allez-vous?",
			"coins.zero":  "Vous êtes fauché.",
			"coins.one":   "Vous avez une seule et précieuse pièce.",
			"coins.other": "Vous possédez " + i18n.TransNPlaceholder + " pièces.",
		},
	}

In "views/hello.gohtml":

	{{define "hello"}}
		<!DOCTYPE html>

				<title>Hello</title>

				{{trans .c "hello" .name}}

				{{trans .c "how"}}

				{{transn .c "coins" .coinsNumber}}

	{{end}}

Translations

Use Trans to get the translation for the client matched locale.
If the translation value contains format verbs (like %s or %d), the last variadic receives the content for them.

When the translation associated to key doesn't exist, an empty string is returned in production mode (otherwise, the key).

Pluralization

TransN works like Trans but it tries to find the best translation form, following a number of elements.

A pluralized translation has 3 forms: zero, one, other.
They are defined at the end of the key: "myTranslationKey.zero", "myTranslationKey.one" and "myTranslationKey.other".
If TransNPlaceholder is used in the translation, the number of elements will take this place.

Translation example:

	"apple.zero" = "There are no apples."
	"apple.one" = "There is a single apple."
	"apple.other" = "There are " + i18n.TransNPlaceholder + " apples."

Function example:

	i18n.TransN(c, "apple", 7)

…results in "There are 7 apples".

Numbers

Use #Num to get a formatted number with decimal and thousands marks.
If set, the special "decimalMark" and "thousandsMark" keys will be used from the matched locale.

Views functions

ViewsFuncs provides a map of all functions usable in templates.

Example with the Response (https://github.com/volatile/response) package:

	response.ViewsFuncs(i18n.ViewsFuncs)

Get current locale

Use ClientLocale to get the locale used by the client.

Set current locale

Use SetClientLocale to manually set the locale used by client.
The change is ignored if the provided locale doesn't exists.
*/
package i18n
<|endoftext|>


<commit_before>// Package unchained provides Django password hashers in Go.
//
// These hashers can be used to perform validation against
// legacy databases. It can also be used as a standard for
// newer applications.
//
// Django provides a flexible password storage system and
// uses PBKDF2 by default.
//
// The default password used in Django is a string in this format:
//
//    <algorithm>$<iterations>$<salt>$<hash>
//
// This library supports Argon2, BCrypt, PBKDF2, MD5 and SHA1 algorithms.
//
package unchained
<commit_msg>Go doc description update<commit_after>// Package unchained provides password hashers that are compatible with Django.
//
// These hashers can be also used to perform validation against
// legacy and shared Django databases.
//
// Django provides a flexible password storage system and
// uses PBKDF2 by default.
//
// The password format/representation is the same as the one used in Django:
//
//    <algorithm>$<iterations>$<salt>$<hash>
//
// This library supports Argon2, BCrypt, PBKDF2, MD5 and SHA1 algorithms.
//
package unchained
<|endoftext|>