<commit_before>package gobundle

import (
    "bufio"
    "encoding/json"
    "io"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "regexp"
    "strconv"
    "strings"
)

// Structures

type NpmPackage struct {
    Name, Main, Version string
}

type Resolver struct {
    Path string
}

type ModRef struct {
    Path, Name string
}

type ModRefGraph struct {
    RootPath string
    EntryFile string
    Nodes map[string][][]string
}

var RequireStmt = regexp.MustCompile(`` +
        `(?i)` +        // Set case-insensitive flag
        `require\(` +
        `(?:"|')` +     // Single or double quote non-capture group
        `([a-z0-9\./\\-]+)` +
        `(?:"|')` +     // Single or double quote non-capture group
        `\)`)

//

func Bundle(entryFile string) ModRefGraph {
    rootPath := filepath.Dir(entryFile)
    r := Resolver{Path: rootPath}
    modPath := r.relPath(entryFile)
    return r.graph(rootPath, modPath)
}

func WriteBundle(b *os.File, bundle ModRefGraph) {
    r := Resolver{Path: bundle.RootPath}

    id := makeIdFunc()

    b.WriteString("(function(L, entry) {")
    b.WriteString(    "var cache = {};")
    b.WriteString(    "function run(id) {")
    b.WriteString(        "if (cache[id]) return cache[id];")
    b.WriteString(        "var m = {exports:{}},")
    b.WriteString(            "fn = L[id][0],")
    b.WriteString(            "deps = L[id][1];")
    b.WriteString(        "function require(name) {")
    b.WriteString(            "return run(deps[name]);")
    b.WriteString(        "}")
    b.WriteString(        "cache[id] = m.exports;");
    b.WriteString(        "fn(require, m, m.exports);");
    b.WriteString(        "return m.exports;");
    b.WriteString(    "}")
    b.WriteString(    "run(entry);")
    b.WriteString("}(")

    i := 0
    b.WriteString("{")
    log.Println(bundle.Nodes)
    // TODO: Filter list beforehand
    for path, children := range bundle.Nodes {
        // TODO: Investigate why empty/nil children are being added here.
        // NOTE: Comma appears at the tail end of list if null entry is last
        if len(path) == 0 {
            continue
        }

        modRef := r.loadModule(path)
        if modRef == nil {
            log.Panic("Unable to load module at", path)
        }

        b.WriteString(strconv.Itoa(id(path)))
        b.WriteString(":[")
        b.WriteString("function(require,module,exports){")
        modRef.writeContents(b)
        b.WriteString("},")

        b.WriteString("{")
        for j, pathTuple := range children {
            childPath := pathTuple[0]
            childRelPath := pathTuple[1]
            b.WriteString("'")
            b.WriteString(childPath)
            b.WriteString("':")
            b.WriteString(strconv.Itoa(id(childRelPath)))
            if j < len(children) - 1 {
                b.WriteString(",")
            }
        }
        b.WriteString("}")

        b.WriteString("]")
        if i < len(bundle.Nodes) - 1 {
            b.WriteString(",")
        }
        i++
    }
    b.WriteString("},")
    b.WriteString(strconv.Itoa(id(bundle.EntryFile)))
    b.WriteString("));\n")
}

//

func (self Resolver) loadModule(name string) *ModRef {
    return self.loadModuleRelativeTo(self.Path, name)
}

func (self Resolver) loadModuleRelativeTo(path, name string) *ModRef {
    log.Println("Trying to load", name, "from", path)
    // NOTE: This important to prevent the case where you have a file name
    // that matches an NPM module. (Ex. you have "shim/jquery.js" which
    // requires "jquery")
    if isRelative(name) {
        if result := self.loadFile(path, name); result != nil {
            return result
        }
        if result := self.loadFolder(path, name); result != nil {
            return result
        }
    }
    if result := self.loadNodeModule(path, name); result != nil {
        return result
    }
    return nil
}

func loadPackage(path string) (*NpmPackage, error) {
    pkg := NpmPackage{}
    data, err := ioutil.ReadFile(path)
    if err != nil {
        return &pkg, err
    }
    err = json.Unmarshal(data, &pkg)
    if err != nil {
        return &pkg, err
    }
    return &pkg, nil
}

func (self Resolver) loadFile(path , name string) *ModRef {
    dir := filepath.Dir(filepath.Join(path, name))
    base := filepath.Base(name)

    file := filepath.Join(dir, base)
    if exists(file) {
        return &ModRef{Path: dir, Name: base}
    }

    file = filepath.Join(dir, base + ".js")
    if exists(file) {
        return &ModRef{Path: dir, Name: base + ".js"}
    }

    file = filepath.Join(dir, base + ".json")
    if exists(file) {
        return &ModRef{Path: dir, Name: base + ".json"}
    }

    return nil
}

func (self Resolver) loadFolder(path, name string) *ModRef {
    dirPath := filepath.Join(path, name)
    pkgFile := filepath.Join(dirPath, "package.json")
    if exists(pkgFile) {
        pkg, err := loadPackage(pkgFile)
        if err != nil {
            log.Panic("Invalid package.json format ", pkgFile)
        }
        if len(pkg.Main) > 0 {
            return self.loadFile(dirPath, pkg.Main)
        }
    }

    file := filepath.Join(dirPath, "index.js")
    if exists(file) {
        return &ModRef{Path: dirPath, Name: "index.js"}
    }

    file = filepath.Join(dirPath, "index.json")
    if exists(file) {
        return &ModRef{Path: dirPath, Name: "index.json"}
    }

    return nil
}

func (self Resolver) loadNodeModule(path, name string) *ModRef {
    absPath, _ := filepath.Abs(path)
    dirPaths := nodeModulePaths(absPath)
    for _, dirPath := range dirPaths {
        log.Println("Trying to load NPM module", name, "from", path)
        if result := self.loadFile(dirPath, name); result != nil {
            return result
        }
        if result := self.loadFolder(dirPath, name); result != nil {
            return result
        }
    }
    return nil
}

func (self Resolver) graph(rootPath, modPath string) ModRefGraph {
    result := ModRefGraph{
        RootPath: rootPath,
        EntryFile: modPath,
        Nodes: make(map[string][][]string),
    }
    self.graph2(result, self.Path, modPath)
    return result
}

func (self Resolver) graph2(result ModRefGraph, relPath, modPath string) *ModRef {
    modRef := self.loadModuleRelativeTo(relPath, modPath)
    if modRef == nil {
        log.Panic("Could not load module: ", modPath, " from ", relPath)
    }

    k := self.relPath(modRef.fullPath())

    if _, exists := result.Nodes[k]; !exists {
        children := modRef.parse()
        // Placeholder to prevent recursive loops.
        // Note: Do we need this if graph never allows for cyclical deps?
        result.Nodes[k] = nil
        childPaths := make([][]string, 0)
        for _, childPath := range children {
            childRef := self.graph2(result, modRef.Path, childPath)
            childRelPath := self.relPath(childRef.fullPath())
            childPaths = append(childPaths, []string{childPath, childRelPath})
        }
        result.Nodes[k] = childPaths
    }
    return modRef
}

func (self Resolver) relPath(path string) string {
    result, _ := filepath.Rel(self.Path, path)
    return result
}

func (self ModRef) fullPath() string {
    return filepath.Join(self.Path, self.Name)
}

// Return map of {moduleName: path, ...} for all dependencies referenced
// by this module.
func (self ModRef) parse() []string {
    fp, _ := os.Open(self.fullPath())
    defer fp.Close()

    scanner := bufio.NewScanner(bufio.NewReader(fp))
    scanner.Split(bufio.ScanLines)

    result := make([]string, 0)
    for scanner.Scan() {
        matches := RequireStmt.FindAllStringSubmatch(scanner.Text(), -1)
        for _, match := range matches {
            // Skip first match (entire unmatched line).
            for _, moduleName := range match[1:] {
                result = append(result, moduleName)
            }
        }
    }
    return result
}

func (self ModRef) writeContents(writer *os.File) {
    fp, _ := os.Open(self.fullPath())
    defer fp.Close()
    io.Copy(writer, fp)
}

// Helpers

func isRelative(name string) bool {
    return strings.HasPrefix(name, "./") || strings.HasPrefix(name, "../")
}

// Return true if a file exists at `path`.
func exists(path string) bool {
    f, err := os.Stat(path)
    if err != nil {
        return false
    }
    return !f.IsDir()
}

func nodeModulePaths(path string) []string {
    result := []string{}
    for ; len(path) > 1; path = filepath.Dir(path) {
        result = append(result, filepath.Join(path, "node_modules"))
    }
    return result
}
<commit_msg>Fix node_modules lookup on Windows<commit_after>package gobundle

import (
    "bufio"
    "encoding/json"
    "io"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "regexp"
    "strconv"
    "strings"
)

// Structures

type NpmPackage struct {
    Name, Main, Version string
}

type Resolver struct {
    Path string
}

type ModRef struct {
    Path, Name string
}

type ModRefGraph struct {
    RootPath string
    EntryFile string
    Nodes map[string][][]string
}

var RequireStmt = regexp.MustCompile(`` +
        `(?i)` +        // Set case-insensitive flag
        `require\(` +
        `(?:"|')` +     // Single or double quote non-capture group
        `([a-z0-9\./\\-]+)` +
        `(?:"|')` +     // Single or double quote non-capture group
        `\)`)

//

func Bundle(entryFile string) ModRefGraph {
    absPath, _ := filepath.Abs(entryFile)
    entryFile = absPath

    rootPath := filepath.Dir(entryFile)
    r := Resolver{Path: rootPath}

    modPath := r.relPath(entryFile)
    return r.graph(rootPath, modPath)
}

func WriteBundle(b *os.File, bundle ModRefGraph) {
    r := Resolver{Path: bundle.RootPath}

    id := makeIdFunc()

    b.WriteString("(function(L, entry) {")
    b.WriteString(    "var cache = {};")
    b.WriteString(    "function run(id) {")
    b.WriteString(        "if (cache[id]) return cache[id];")
    b.WriteString(        "var m = {exports:{}},")
    b.WriteString(            "fn = L[id][0],")
    b.WriteString(            "deps = L[id][1];")
    b.WriteString(        "function require(name) {")
    b.WriteString(            "return run(deps[name]);")
    b.WriteString(        "}")
    b.WriteString(        "cache[id] = m.exports;");
    b.WriteString(        "fn(require, m, m.exports);");
    b.WriteString(        "return m.exports;");
    b.WriteString(    "}")
    b.WriteString(    "run(entry);")
    b.WriteString("}(")

    i := 0
    b.WriteString("{")
    log.Println(bundle.Nodes)
    // TODO: Filter list beforehand
    for path, children := range bundle.Nodes {
        // TODO: Investigate why empty/nil children are being added here.
        // NOTE: Comma appears at the tail end of list if null entry is last
        if len(path) == 0 {
            continue
        }

        modRef := r.loadModule(path)
        if modRef == nil {
            log.Panic("Unable to load module at", path)
        }

        b.WriteString(strconv.Itoa(id(path)))
        b.WriteString(":[")
        b.WriteString("function(require,module,exports){")
        modRef.writeContents(b)
        b.WriteString("},")

        b.WriteString("{")
        for j, pathTuple := range children {
            childPath := pathTuple[0]
            childRelPath := pathTuple[1]
            b.WriteString("'")
            b.WriteString(childPath)
            b.WriteString("':")
            b.WriteString(strconv.Itoa(id(childRelPath)))
            if j < len(children) - 1 {
                b.WriteString(",")
            }
        }
        b.WriteString("}")

        b.WriteString("]")
        if i < len(bundle.Nodes) - 1 {
            b.WriteString(",")
        }
        i++
    }
    b.WriteString("},")
    b.WriteString(strconv.Itoa(id(bundle.EntryFile)))
    b.WriteString("));\n")
}

//

func (self Resolver) loadModule(name string) *ModRef {
    return self.loadModuleRelativeTo(self.Path, name)
}

func (self Resolver) loadModuleRelativeTo(path, name string) *ModRef {
    log.Println("Trying to load", name, "from", path)
    // NOTE: This important to prevent the case where you have a file name
    // that matches an NPM module. (Ex. you have "shim/jquery.js" which
    // requires "jquery")
    if isRelative(name) {
        if result := self.loadFile(path, name); result != nil {
            return result
        }
        if result := self.loadFolder(path, name); result != nil {
            return result
        }
    }
    if result := self.loadNodeModule(path, name); result != nil {
        return result
    }
    return nil
}

func loadPackage(path string) (*NpmPackage, error) {
    pkg := NpmPackage{}
    data, err := ioutil.ReadFile(path)
    if err != nil {
        return &pkg, err
    }
    err = json.Unmarshal(data, &pkg)
    if err != nil {
        return &pkg, err
    }
    return &pkg, nil
}

func (self Resolver) loadFile(path , name string) *ModRef {
    dir := filepath.Dir(filepath.Join(path, name))
    base := filepath.Base(name)

    file := filepath.Join(dir, base)
    if exists(file) {
        return &ModRef{Path: dir, Name: base}
    }

    file = filepath.Join(dir, base + ".js")
    if exists(file) {
        return &ModRef{Path: dir, Name: base + ".js"}
    }

    file = filepath.Join(dir, base + ".json")
    if exists(file) {
        return &ModRef{Path: dir, Name: base + ".json"}
    }

    return nil
}

func (self Resolver) loadFolder(path, name string) *ModRef {
    dirPath := filepath.Join(path, name)
    pkgFile := filepath.Join(dirPath, "package.json")
    if exists(pkgFile) {
        pkg, err := loadPackage(pkgFile)
        if err != nil {
            log.Panic("Invalid package.json format ", pkgFile)
        }
        if len(pkg.Main) > 0 {
            return self.loadFile(dirPath, pkg.Main)
        }
    }

    file := filepath.Join(dirPath, "index.js")
    if exists(file) {
        return &ModRef{Path: dirPath, Name: "index.js"}
    }

    file = filepath.Join(dirPath, "index.json")
    if exists(file) {
        return &ModRef{Path: dirPath, Name: "index.json"}
    }

    return nil
}

func (self Resolver) loadNodeModule(path, name string) *ModRef {
    absPath, _ := filepath.Abs(path)
    dirPaths := nodeModulePaths(absPath)
    for _, dirPath := range dirPaths {
        log.Println("Trying to load NPM module", name, "from", path)
        if result := self.loadFile(dirPath, name); result != nil {
            return result
        }
        if result := self.loadFolder(dirPath, name); result != nil {
            return result
        }
    }
    return nil
}

func (self Resolver) graph(rootPath, modPath string) ModRefGraph {
    result := ModRefGraph{
        RootPath: rootPath,
        EntryFile: modPath,
        Nodes: make(map[string][][]string),
    }
    self.graph2(result, self.Path, modPath)
    return result
}

func (self Resolver) graph2(result ModRefGraph, relPath, modPath string) *ModRef {
    modRef := self.loadModuleRelativeTo(relPath, modPath)
    if modRef == nil {
        log.Panic("Could not load module: ", modPath, " from ", relPath)
    }

    k := self.relPath(modRef.fullPath())

    if _, exists := result.Nodes[k]; !exists {
        children := modRef.parse()
        // Placeholder to prevent recursive loops.
        // Note: Do we need this if graph never allows for cyclical deps?
        result.Nodes[k] = nil
        childPaths := make([][]string, 0)
        for _, childPath := range children {
            childRef := self.graph2(result, modRef.Path, childPath)
            childRelPath := self.relPath(childRef.fullPath())
            childPaths = append(childPaths, []string{childPath, childRelPath})
        }
        result.Nodes[k] = childPaths
    }
    return modRef
}

func (self Resolver) relPath(path string) string {
    result, _ := filepath.Rel(self.Path, path)
    return result
}

func (self ModRef) fullPath() string {
    return filepath.Join(self.Path, self.Name)
}

// Return map of {moduleName: path, ...} for all dependencies referenced
// by this module.
func (self ModRef) parse() []string {
    fp, _ := os.Open(self.fullPath())
    defer fp.Close()

    scanner := bufio.NewScanner(bufio.NewReader(fp))
    scanner.Split(bufio.ScanLines)

    result := make([]string, 0)
    for scanner.Scan() {
        matches := RequireStmt.FindAllStringSubmatch(scanner.Text(), -1)
        for _, match := range matches {
            // Skip first match (entire unmatched line).
            for _, moduleName := range match[1:] {
                result = append(result, moduleName)
            }
        }
    }
    return result
}

func (self ModRef) writeContents(writer *os.File) {
    fp, _ := os.Open(self.fullPath())
    defer fp.Close()
    io.Copy(writer, fp)
}

// Helpers

func isRelative(name string) bool {
    return strings.HasPrefix(name, "./") || strings.HasPrefix(name, "../")
}

// Return true if a file exists at `path`.
func exists(path string) bool {
    f, err := os.Stat(path)
    if err != nil {
        return false
    }
    return !f.IsDir()
}

func nodeModulePaths(path string) []string {
    result := []string{}
    prevPath := ""
    for ; path != prevPath; {
        result = append(result, filepath.Join(path, "node_modules"))
        prevPath = path
        path = filepath.Dir(path)
    }
    return result
}
<|endoftext|>


<commit_before>// alot of moving code to Go - examples?
// coming in Go 1.3 contiguous stacks
// coming in Go 1.4 compiler/linker to be rewritten in Go

// how do debuggers debugger? ptrace()

/*
  signals forwarded to tracing process

  what happens when you set a breakpoint?
  a few debug registers on the cpu; to open more, send the int 0x3 interrupt

  how to debug with debuggers
  pass to compile `-gcflags "-N -l"`
  prevents inlining functions and registering variables

  if not building a standalone, pass `-c` with flags to `go test` to compile for debugging

  */

package main

import(
  "fmt"
)

func main() {
  a := Rational{1, 2}
  b := Rational{2, 3}
  c := a.Add(b)
  d := a.Mult(b)

  println("\na")
  Print( a )

  println("\nb")
  Print( b )

  println("\na < b")
  println( a.LessThan( b ) )

  println("\nb is the max")
  Print( Max(a, b) )

  println("\na + b = c")
  Print( c )
  println("\na * b = d")
  Print( d )

  println("\na and b are unchanged")
  Print( a )
  Print( b )
}

type Rational struct {
  Numer int
  Denom int
}

func (this Rational) Add(that Rational) Rational {
  return Rational{
    (this.Numer * that.Denom) + (this.Denom * that.Numer),
    this.Denom * that.Denom }
}

func (this Rational) Mult(that Rational) Rational {
  return Rational{
    this.Numer * that.Numer,
    this.Denom * that.Denom }
}

func (this Rational) LessThan(that Rational) bool {
  return this.Numer * that.Denom < that.Numer * this.Denom
}

func Max(a Rational, b Rational) Rational {
  if (a.LessThan( b )) {
    return a
  } else {
    return b
  }
}

func Print(some Rational) {
  fmt.Printf("Rational: %d/%d\n", some.Numer, some.Denom)
}
<commit_msg>use library functions rather than methods<commit_after>// alot of moving code to Go - examples?
// coming in Go 1.3 contiguous stacks
// coming in Go 1.4 compiler/linker to be rewritten in Go

// how do debuggers debugger? ptrace()

/*
  signals forwarded to tracing process

  what happens when you set a breakpoint?
  a few debug registers on the cpu; to open more, send the int 0x3 interrupt

  how to debug with debuggers
  pass to compile `-gcflags "-N -l"`
  prevents inlining functions and registering variables

  if not building a standalone, pass `-c` with flags to `go test` to compile for debugging

  */

package main

import(
  "fmt"
)

func main() {
  a := Rational{1, 2}
  b := Rational{2, 3}
  c := Add(a, b)
  d := Mult(a, b)

  println("\na")
  Print( a )

  println("\nb")
  Print( b )

  println("\na < b")
  println( LessThan( a, b ) )

  println("\nb is the max")
  Print( Max(a, b) )

  println("\na + b = c")
  Print( c )
  println("\na * b = d")
  Print( d )

  println("\na and b are unchanged")
  Print( a )
  Print( b )
}

type Rational struct {
  Numer int
  Denom int
}

func Add(this Rational, that Rational) Rational {
  return Rational{
    (this.Numer * that.Denom) + (this.Denom * that.Numer),
    this.Denom * that.Denom }
}

func Mult(this Rational, that Rational) Rational {
  return Rational{
    this.Numer * that.Numer,
    this.Denom * that.Denom }
}

func LessThan(this Rational, that Rational) bool {
  return this.Numer * that.Denom < that.Numer * this.Denom
}

func Max(a Rational, b Rational) Rational {
  if (LessThan( a, b )) {
    return a
  } else {
    return b
  }
}

func Print(some Rational) {
  fmt.Printf("Rational: %d/%d\n", some.Numer, some.Denom)
}
<|endoftext|>


<commit_before>// Copyright 2014 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

package memory

import (
	"runtime"
	"time"

	"github.com/chihaya/chihaya/drivers/tracker"
	"github.com/chihaya/chihaya/models"
)

// Conn implements a connection to a memory-based tracker data store.
type Conn struct {
	*Pool
}

func (c *Conn) FindUser(passkey string) (*models.User, error) {
	c.usersM.RLock()
	defer c.usersM.RUnlock()

	user, exists := c.users[passkey]
	if !exists {
		return nil, tracker.ErrUserDNE
	}
	return &*user, nil
}

func (c *Conn) FindTorrent(infohash string) (*models.Torrent, error) {
	c.torrentsM.RLock()
	defer c.torrentsM.RUnlock()

	torrent, exists := c.torrents[infohash]
	if !exists {
		return nil, tracker.ErrTorrentDNE
	}
	return &*torrent, nil
}

func (c *Conn) FindClient(peerID string) error {
	c.whitelistM.RLock()
	defer c.whitelistM.RUnlock()

	_, ok := c.whitelist[peerID]
	if !ok {
		return tracker.ErrClientUnapproved
	}
	return nil
}

func (c *Conn) IncrementSnatches(infohash string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Snatches++

	return nil
}

func (c *Conn) TouchTorrent(infohash string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.LastAction = time.Now().Unix()

	return nil
}

func (c *Conn) AddLeecher(infohash string, p *models.Peer) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Leechers[p.Key()] = *p

	return nil
}

func (c *Conn) AddSeeder(infohash string, p *models.Peer) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Seeders[p.Key()] = *p

	return nil
}

func (c *Conn) DeleteLeecher(infohash, peerkey string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	delete(t.Leechers, peerkey)

	return nil
}

func (c *Conn) DeleteSeeder(infohash, peerkey string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	delete(t.Seeders, peerkey)

	return nil
}

func (c *Conn) PutLeecher(infohash string, p *models.Peer) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Leechers[p.Key()] = *p

	return nil
}

func (c *Conn) PutSeeder(infohash string, p *models.Peer) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Seeders[p.Key()] = *p

	return nil
}

func (c *Conn) PutTorrent(t *models.Torrent) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	c.torrents[t.Infohash] = &*t

	return nil
}

func (c *Conn) DeleteTorrent(infohash string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	delete(c.torrents, infohash)

	return nil
}

func (c *Conn) PurgeInactiveTorrent(infohash string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	torrent, exists := c.torrents[infohash]
	if !exists {
		return tracker.ErrTorrentDNE
	}

	if torrent.PeerCount() == 0 {
		delete(c.torrents, infohash)
	}

	return nil
}

func (c *Conn) PutUser(u *models.User) error {
	c.usersM.Lock()
	defer c.usersM.Unlock()

	c.users[u.Passkey] = &*u

	return nil
}

func (c *Conn) DeleteUser(passkey string) error {
	c.usersM.Lock()
	defer c.usersM.Unlock()

	delete(c.users, passkey)

	return nil
}

func (c *Conn) PutClient(peerID string) error {
	c.whitelistM.Lock()
	defer c.whitelistM.Unlock()

	c.whitelist[peerID] = true

	return nil
}

func (c *Conn) DeleteClient(peerID string) error {
	c.whitelistM.Lock()
	defer c.whitelistM.Unlock()

	delete(c.whitelist, peerID)

	return nil
}

func (c *Conn) PurgeInactivePeers(purgeEmptyTorrents bool, before time.Time) error {
	unixtime := before.Unix()

	// Build array of map keys to operate on.
	c.torrentsM.RLock()
	index := 0
	keys := make([]string, len(c.torrents))

	for infohash, _ := range c.torrents {
		keys[index] = infohash
		index++
	}

	c.torrentsM.RUnlock()

	// Process keys.
	for _, infohash := range keys {
		runtime.Gosched() // Let other goroutines run, since this is low priority.

		c.torrentsM.Lock()
		torrent := c.torrents[infohash]

		for key, peer := range torrent.Seeders {
			if peer.LastAnnounce < unixtime {
				delete(torrent.Seeders, key)
			}
		}

		for key, peer := range torrent.Leechers {
			if peer.LastAnnounce < unixtime {
				delete(torrent.Leechers, key)
			}
		}

		peers := torrent.PeerCount()
		c.torrentsM.Unlock()

		if purgeEmptyTorrents && peers == 0 {
			c.PurgeInactiveTorrent(infohash)
		}
	}

	return nil
}
<commit_msg>Handle case where a torrent is deleted before being processed<commit_after>// Copyright 2014 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

package memory

import (
	"runtime"
	"time"

	"github.com/chihaya/chihaya/drivers/tracker"
	"github.com/chihaya/chihaya/models"
)

// Conn implements a connection to a memory-based tracker data store.
type Conn struct {
	*Pool
}

func (c *Conn) FindUser(passkey string) (*models.User, error) {
	c.usersM.RLock()
	defer c.usersM.RUnlock()

	user, exists := c.users[passkey]
	if !exists {
		return nil, tracker.ErrUserDNE
	}
	return &*user, nil
}

func (c *Conn) FindTorrent(infohash string) (*models.Torrent, error) {
	c.torrentsM.RLock()
	defer c.torrentsM.RUnlock()

	torrent, exists := c.torrents[infohash]
	if !exists {
		return nil, tracker.ErrTorrentDNE
	}
	return &*torrent, nil
}

func (c *Conn) FindClient(peerID string) error {
	c.whitelistM.RLock()
	defer c.whitelistM.RUnlock()

	_, ok := c.whitelist[peerID]
	if !ok {
		return tracker.ErrClientUnapproved
	}
	return nil
}

func (c *Conn) IncrementSnatches(infohash string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Snatches++

	return nil
}

func (c *Conn) TouchTorrent(infohash string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.LastAction = time.Now().Unix()

	return nil
}

func (c *Conn) AddLeecher(infohash string, p *models.Peer) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Leechers[p.Key()] = *p

	return nil
}

func (c *Conn) AddSeeder(infohash string, p *models.Peer) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Seeders[p.Key()] = *p

	return nil
}

func (c *Conn) DeleteLeecher(infohash, peerkey string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	delete(t.Leechers, peerkey)

	return nil
}

func (c *Conn) DeleteSeeder(infohash, peerkey string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	delete(t.Seeders, peerkey)

	return nil
}

func (c *Conn) PutLeecher(infohash string, p *models.Peer) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Leechers[p.Key()] = *p

	return nil
}

func (c *Conn) PutSeeder(infohash string, p *models.Peer) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	t, ok := c.torrents[infohash]
	if !ok {
		return tracker.ErrTorrentDNE
	}
	t.Seeders[p.Key()] = *p

	return nil
}

func (c *Conn) PutTorrent(t *models.Torrent) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	c.torrents[t.Infohash] = &*t

	return nil
}

func (c *Conn) DeleteTorrent(infohash string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	delete(c.torrents, infohash)

	return nil
}

func (c *Conn) PurgeInactiveTorrent(infohash string) error {
	c.torrentsM.Lock()
	defer c.torrentsM.Unlock()

	torrent, exists := c.torrents[infohash]
	if !exists {
		return tracker.ErrTorrentDNE
	}

	if torrent.PeerCount() == 0 {
		delete(c.torrents, infohash)
	}

	return nil
}

func (c *Conn) PutUser(u *models.User) error {
	c.usersM.Lock()
	defer c.usersM.Unlock()

	c.users[u.Passkey] = &*u

	return nil
}

func (c *Conn) DeleteUser(passkey string) error {
	c.usersM.Lock()
	defer c.usersM.Unlock()

	delete(c.users, passkey)

	return nil
}

func (c *Conn) PutClient(peerID string) error {
	c.whitelistM.Lock()
	defer c.whitelistM.Unlock()

	c.whitelist[peerID] = true

	return nil
}

func (c *Conn) DeleteClient(peerID string) error {
	c.whitelistM.Lock()
	defer c.whitelistM.Unlock()

	delete(c.whitelist, peerID)

	return nil
}

func (c *Conn) PurgeInactivePeers(purgeEmptyTorrents bool, before time.Time) error {
	unixtime := before.Unix()

	// Build array of map keys to operate on.
	c.torrentsM.RLock()
	index := 0
	keys := make([]string, len(c.torrents))

	for infohash, _ := range c.torrents {
		keys[index] = infohash
		index++
	}

	c.torrentsM.RUnlock()

	// Process keys.
	for _, infohash := range keys {
		runtime.Gosched() // Let other goroutines run, since this is low priority.

		c.torrentsM.Lock()
		torrent := c.torrents[infohash]

		if torrent == nil {
			continue // Torrent deleted since keys were computed.
		}

		for key, peer := range torrent.Seeders {
			if peer.LastAnnounce < unixtime {
				delete(torrent.Seeders, key)
			}
		}

		for key, peer := range torrent.Leechers {
			if peer.LastAnnounce < unixtime {
				delete(torrent.Leechers, key)
			}
		}

		peers := torrent.PeerCount()
		c.torrentsM.Unlock()

		if purgeEmptyTorrents && peers == 0 {
			c.PurgeInactiveTorrent(infohash)
		}
	}

	return nil
}
<|endoftext|>


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

package iptables

import (
	"crypto/rand"
	"math/big"
	"reflect"
	"testing"
)

func randChain(t *testing.T) string {
	n, err := rand.Int(rand.Reader, big.NewInt(1000000))
	if err != nil {
		t.Fatalf("Failed to generate random chain name: %v", err)
	}

	return "TEST-" + n.String()
}

func TestChain(t *testing.T) {
	chain := randChain(t)

	ipt, err := New()
	if err != nil {
		t.Fatalf("New failed: %v", err)
	}

	// chain shouldn't exist, this will create new
	err = ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("ClearChain (of missing) failed: %v", err)
	}

	// chain now exists
	err = ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("ClearChain (of empty) failed: %v", err)
	}

	// put a simple rule in
	err = ipt.Append("filter", chain, "-s", "0.0.0.0/0", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Append failed: %v", err)
	}

	// can't delete non-empty chain
	err = ipt.DeleteChain("filter", chain)
	if err == nil {
		t.Fatalf("DeleteChain of non-empty chain did not fail")
	}

	err = ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("ClearChain (of non-empty) failed: %v", err)
	}

	// rename the chain
	newChain := randChain(t)
	err = ipt.RenameChain("filter", chain, newChain)
	if err != nil {
		t.Fatalf("RenameChain failed: %v", err)
	}

	// chain empty, should be ok
	err = ipt.DeleteChain("filter", newChain)
	if err != nil {
		t.Fatalf("DeleteChain of empty chain failed: %v", err)
	}
}

func TestRules(t *testing.T) {
	chain := randChain(t)

	ipt, err := New()
	if err != nil {
		t.Fatalf("New failed: %v", err)
	}

	// chain shouldn't exist, this will create new
	err = ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("ClearChain (of missing) failed: %v", err)
	}

	err = ipt.Append("filter", chain, "-s", "10.1.0.0/16", "-d", "8.8.8.8/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Append failed: %v", err)
	}

	err = ipt.AppendUnique("filter", chain, "-s", "10.1.0.0/16", "-d", "8.8.8.8/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("AppendUnique failed: %v", err)
	}

	err = ipt.Append("filter", chain, "-s", "10.2.0.0/16", "-d", "8.8.8.8/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Append failed: %v", err)
	}

	err = ipt.Insert("filter", chain, 2, "-s", "10.2.0.0/16", "-d", "9.9.9.9/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Insert failed: %v", err)
	}

	err = ipt.Insert("filter", chain, 1, "-s", "10.1.0.0/16", "-d", "9.9.9.9/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Insert failed: %v", err)
	}

	err = ipt.Delete("filter", chain, "-s", "10.1.0.0/16", "-d", "9.9.9.9/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	rules, err := ipt.List("filter", chain)
	if err != nil {
		t.Fatalf("List failed: %v", err)
	}

	expected := []string{
		"-N " + chain,
		"-A " + chain + " -s 10.1.0.0/16 -d 8.8.8.8/32 -j ACCEPT",
		"-A " + chain + " -s 10.2.0.0/16 -d 9.9.9.9/32 -j ACCEPT",
		"-A " + chain + " -s 10.2.0.0/16 -d 8.8.8.8/32 -j ACCEPT",
	}

	if !reflect.DeepEqual(rules, expected) {
		t.Fatalf("List mismatch: \ngot  %#v \nneed %#v", rules, expected)
	}

	// Clear the chain that was created.
	err = ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("Failed to clear test chain: %v", err)
	}

	// Delete the chain that was created
	err = ipt.DeleteChain("filter", chain)
	if err != nil {
		t.Fatalf("Failed to delete test chain: %v", err)
	}
}
<commit_msg>tests: ensure we test without hasWait/hasCheck<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package iptables

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"reflect"
	"testing"
)

func randChain(t *testing.T) string {
	n, err := rand.Int(rand.Reader, big.NewInt(1000000))
	if err != nil {
		t.Fatalf("Failed to generate random chain name: %v", err)
	}

	return "TEST-" + n.String()
}

// Create an array of IPTables with different hasWait/hasCheck to
// test different behaviours
func mustTestableIptables() []*IPTables {
	ipt, err := New()
	if err != nil {
		panic(fmt.Sprintf("New failed: %v", err))
	}
	ipts := []*IPTables{ipt}
	// ensure we check one variant without built-in locking
	if ipt.hasWait {
		iptNoWait := &IPTables{
			path:    ipt.path,
			hasWait: false,
		}
		ipts = append(ipts, iptNoWait)
	}
	// ensure we check one variant without built-in checking
	if ipt.hasCheck {
		iptNoCheck := &IPTables{
			path:     ipt.path,
			hasCheck: false,
		}
		ipts = append(ipts, iptNoCheck)
	}
	return ipts
}

func TestChain(t *testing.T) {
	for _, ipt := range mustTestableIptables() {
		runChainTests(t, ipt)
	}
}

func runChainTests(t *testing.T, ipt *IPTables) {
	t.Logf("testing iptables (hasWait=%t, hasCheck=%t)", ipt.hasWait, ipt.hasCheck)

	chain := randChain(t)

	// chain shouldn't exist, this will create new
	err := ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("ClearChain (of missing) failed: %v", err)
	}

	// chain now exists
	err = ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("ClearChain (of empty) failed: %v", err)
	}

	// put a simple rule in
	err = ipt.Append("filter", chain, "-s", "0.0.0.0/0", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Append failed: %v", err)
	}

	// can't delete non-empty chain
	err = ipt.DeleteChain("filter", chain)
	if err == nil {
		t.Fatalf("DeleteChain of non-empty chain did not fail")
	}

	err = ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("ClearChain (of non-empty) failed: %v", err)
	}

	// rename the chain
	newChain := randChain(t)
	err = ipt.RenameChain("filter", chain, newChain)
	if err != nil {
		t.Fatalf("RenameChain failed: %v", err)
	}

	// chain empty, should be ok
	err = ipt.DeleteChain("filter", newChain)
	if err != nil {
		t.Fatalf("DeleteChain of empty chain failed: %v", err)
	}
}

func TestRules(t *testing.T) {
	for _, ipt := range mustTestableIptables() {
		runRulesTests(t, ipt)
	}
}

func runRulesTests(t *testing.T, ipt *IPTables) {
	t.Logf("testing iptables (hasWait=%t, hasCheck=%t)", ipt.hasWait, ipt.hasCheck)

	chain := randChain(t)

	// chain shouldn't exist, this will create new
	err := ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("ClearChain (of missing) failed: %v", err)
	}

	err = ipt.Append("filter", chain, "-s", "10.1.0.0/16", "-d", "8.8.8.8/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Append failed: %v", err)
	}

	err = ipt.AppendUnique("filter", chain, "-s", "10.1.0.0/16", "-d", "8.8.8.8/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("AppendUnique failed: %v", err)
	}

	err = ipt.Append("filter", chain, "-s", "10.2.0.0/16", "-d", "8.8.8.8/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Append failed: %v", err)
	}

	err = ipt.Insert("filter", chain, 2, "-s", "10.2.0.0/16", "-d", "9.9.9.9/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Insert failed: %v", err)
	}

	err = ipt.Insert("filter", chain, 1, "-s", "10.1.0.0/16", "-d", "9.9.9.9/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Insert failed: %v", err)
	}

	err = ipt.Delete("filter", chain, "-s", "10.1.0.0/16", "-d", "9.9.9.9/32", "-j", "ACCEPT")
	if err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	rules, err := ipt.List("filter", chain)
	if err != nil {
		t.Fatalf("List failed: %v", err)
	}

	expected := []string{
		"-N " + chain,
		"-A " + chain + " -s 10.1.0.0/16 -d 8.8.8.8/32 -j ACCEPT",
		"-A " + chain + " -s 10.2.0.0/16 -d 9.9.9.9/32 -j ACCEPT",
		"-A " + chain + " -s 10.2.0.0/16 -d 8.8.8.8/32 -j ACCEPT",
	}

	if !reflect.DeepEqual(rules, expected) {
		t.Fatalf("List mismatch: \ngot  %#v \nneed %#v", rules, expected)
	}

	// Clear the chain that was created.
	err = ipt.ClearChain("filter", chain)
	if err != nil {
		t.Fatalf("Failed to clear test chain: %v", err)
	}

	// Delete the chain that was created
	err = ipt.DeleteChain("filter", chain)
	if err != nil {
		t.Fatalf("Failed to delete test chain: %v", err)
	}
}
<|endoftext|>


<commit_before>// Package md2web contains the MD2Web trim.Application.
package md2web

import (
	"fmt"
	"io/ioutil"
	"net/http"
	gourl "net/url"
	"path/filepath"
	"strings"

	"github.com/jwowillo/pack"
	"github.com/jwowillo/trim/application"
	"github.com/jwowillo/trim/controller"
	"github.com/jwowillo/trim/request"
	"github.com/jwowillo/trim/response"
	"github.com/jwowillo/trim/url"
	"github.com/russross/blackfriday"
)

// MD2Web is a trim.Applications which turns directories of markdown files and
// folders into a website.
type MD2Web struct {
	*application.Web
}

// New creates a MD2Web excluding the provided files which has the given host.
func New(h, bf string, excs []string) *MD2Web {
	app := &MD2Web{Web: application.NewWeb()}
	app.RemoveAPI()
	app.ClearControllers()
	set := pack.NewHashSet(pack.StringHasher)
	for _, exc := range excs {
		set.Add(exc)
	}
	s := url.NewBuilder(h).
		SetSubdomain(app.Static().Subdomain()).
		SetPath(app.Static().BasePath()).
		Build().String()
	if err := app.AddController(newClientController(s, bf, set)); err != nil {
		panic(err)
	}
	return app
}

// NewDebug creates an MD2Web that doesn't cache which has the given host.
func NewDebug(h, bf string, excs []string) *MD2Web {
	cf := application.ClientDefault
	cf.CacheDuration = 0
	app := &MD2Web{
		Web: application.NewWebWithConfig(
			cf,
			application.APIDefault,
			application.StaticDefault,
		),
	}
	app.RemoveAPI()
	app.ClearControllers()
	set := pack.NewHashSet(pack.StringHasher)
	for _, exc := range excs {
		set.Add(exc)
	}
	s := url.NewBuilder(h).
		SetSubdomain(app.Static().Subdomain()).
		SetPath(app.Static().BasePath()).
		Build().String()
	if err := app.AddController(newClientController(s, bf, set)); err != nil {
		panic(err)
	}
	return app
}

// clientController which renders markdown page's based on request paths.
type clientController struct {
	controller.Bare
	static, baseFolder string
	excludes           pack.Set
}

// newClientController creates a controller with the given template file and
// base folder.
func newClientController(
	static, bf string,
	excs pack.Set,
) *clientController {
	excs.Add("static")
	excs.Add(".git")
	excs.Add(".gitignore")
	return &clientController{static: static, baseFolder: bf, excludes: excs}
}

// Path of the clientController.
//
// Always a variable path which captures the entire path into the key
// 'fullName'.
func (c *clientController) Path() string {
	return "/:name*"
}

// Handle trim.Request by rendering the markdown page at the file name stored in
// the path.
func (c *clientController) Handle(req *request.Request) response.Response {
	fn := req.URL().Path()
	path := filepath.Join(c.baseFolder, buildPath(fn))
	path, err := gourl.QueryUnescape(path)
	hl, err := headerLinks(c.baseFolder, path, c.excludes)
	nl, err := navLinks(path, c.excludes)
	bs, err := content(path)
	args := pack.AnyMap{
		"title":       filepath.Base(fn),
		"static":      c.static,
		"headerLinks": hl,
		"navLinks":    nl,
		"content": strings.Replace(
			string(bs),
			"{{ static }}",
			c.static,
			-1,
		),
	}
	if err != nil {
		args["headerLinks"] = map[string]string{"/": "/"}
		args["navLinks"] = nil
		args["content"] = fmt.Sprintf("%s couldn't be served.", fn)
		return response.NewTemplateFromString(
			Template,
			args,
			http.StatusInternalServerError,
		)
	}
	return response.NewTemplateFromString(Template, args, http.StatusOK)
}

// headerLinks are links to files along the provided path except what is in the
// provided set map mapped to their link text.
func headerLinks(bf, path string, excs pack.Set) ([]linkPair, error) {
	ls := []linkPair{linkPair{Real: "/", Fake: "/"}}
	working := ""
	for _, part := range strings.Split(filepath.Dir(path), "/") {
		if part == "." || part == bf {
			continue
		}
		working = filepath.Join(working, part)
		if excs.Contains(working) {
			return nil, fmt.Errorf("%s excluded", working)
		}
		if part == "main.md" {
			break
		}
		if strings.HasSuffix(part, ".md") {
			part = part[:len(part)-len(".md")]
		} else {
			part += "/"
		}
		ls = append(ls, linkPair{Real: "/" + working + "/", Fake: part})
	}
	return ls, nil
}

// navLinks are links to adjacent markdown files and folders to the provided
// path except what is in the excluded provided set mapped to their link text.
//
// Returns an error if the directory of the given path can't be read.
func navLinks(path string, excs pack.Set) ([]linkPair, error) {
	fs, err := ioutil.ReadDir(filepath.Dir(path))
	if err != nil {
		return nil, err
	}
	var ls []linkPair
	for _, f := range fs {
		fn := f.Name()
		if excs.Contains(fn) || excs.Contains(filepath.Base(fn)) {
			continue
		}
		key := f.Name()
		switch mode := f.Mode(); {
		case mode.IsDir():
			key = key + "/"
		case mode.IsRegular():
			if !strings.HasSuffix(fn, ".md") {
				continue
			}
			if fn == "main.md" {
				continue
			}
		}
		if strings.HasSuffix(key, ".md") {
			key = key[:len(key)-len(".md")]
			fn = fn[:len(fn)-len(".md")]
		}
		ls = append(ls, linkPair{Real: key, Fake: fn})
	}
	return ls, nil
}

// content of file at path.
//
// Returns an error if the file isn't a markdown file.
func content(path string) ([]byte, error) {
	if filepath.Ext(path) != ".md" {
		return nil, fmt.Errorf("%s isn't a markdown file", path)
	}
	bs, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return blackfriday.MarkdownCommon(bs), nil
}

// buildPath to markdown file represented by given name.
func buildPath(name string) string {
	path := "." + name
	if path == "" || path[len(path)-1] == '/' {
		path += "main"
	}
	path += ".md"
	return path
}

// Template file shown as page.
const Template = `
<!DOCTYPE html>

    <meta charset="utf-8">
    <title>{{ title }}</title>
    <link rel="icon" href="http://{{ static }}/favicon.png">
    <meta name="viewport" content="width=device-width, initial-scale=1">

      	{% for p in headerLinks %}
      	  {{ p.Fake }}
      	{% endfor %}

        {% for p in navLinks %}
          {{ p.Fake }}
        {% endfor %}

        {{ content | safe }}

`

// linkPair is a pair of a real and a fake link.
type linkPair struct {
	Real, Fake string
}
<commit_msg>Remove Need to Put Protocol Before URLs in Content<commit_after>// Package md2web contains the MD2Web trim.Application.
package md2web

import (
	"fmt"
	"io/ioutil"
	"net/http"
	gourl "net/url"
	"path/filepath"
	"strings"

	"github.com/jwowillo/pack"
	"github.com/jwowillo/trim/application"
	"github.com/jwowillo/trim/controller"
	"github.com/jwowillo/trim/request"
	"github.com/jwowillo/trim/response"
	"github.com/jwowillo/trim/url"
	"github.com/russross/blackfriday"
)

// MD2Web is a trim.Applications which turns directories of markdown files and
// folders into a website.
type MD2Web struct {
	*application.Web
}

// New creates a MD2Web excluding the provided files which has the given host.
func New(h, bf string, excs []string) *MD2Web {
	app := &MD2Web{Web: application.NewWeb()}
	app.RemoveAPI()
	app.ClearControllers()
	set := pack.NewHashSet(pack.StringHasher)
	for _, exc := range excs {
		set.Add(exc)
	}
	s := url.NewBuilder(h).
		SetSubdomain(app.Static().Subdomain()).
		SetPath(app.Static().BasePath())
	if err := app.AddController(newClientController(s, bf, set)); err != nil {
		panic(err)
	}
	return app
}

// NewDebug creates an MD2Web that doesn't cache which has the given host.
func NewDebug(h, bf string, excs []string) *MD2Web {
	cf := application.ClientDefault
	cf.CacheDuration = 0
	app := &MD2Web{
		Web: application.NewWebWithConfig(
			cf,
			application.APIDefault,
			application.StaticDefault,
		),
	}
	app.RemoveAPI()
	app.ClearControllers()
	set := pack.NewHashSet(pack.StringHasher)
	for _, exc := range excs {
		set.Add(exc)
	}
	s := url.NewBuilder(h).
		SetSubdomain(app.Static().Subdomain()).
		SetPath(app.Static().BasePath())
	if err := app.AddController(newClientController(s, bf, set)); err != nil {
		panic(err)
	}
	return app
}

// clientController which renders markdown page's based on request paths.
type clientController struct {
	controller.Bare
	staticBuilder *url.Builder
	baseFolder    string
	excludes      pack.Set
}

// newClientController creates a controller with the given template file and
// base folder.
func newClientController(
	sb *url.Builder,
	bf string,
	excs pack.Set,
) *clientController {
	excs.Add("static")
	excs.Add(".git")
	excs.Add(".gitignore")
	return &clientController{
		staticBuilder: sb,
		baseFolder:    bf,
		excludes:      excs,
	}
}

// Path of the clientController.
//
// Always a variable path which captures the entire path into the key
// 'fullName'.
func (c *clientController) Path() string {
	return "/:name*"
}

// Handle trim.Request by rendering the markdown page at the file name stored in
// the path.
func (c *clientController) Handle(req *request.Request) response.Response {
	fn := req.URL().Path()
	path := filepath.Join(c.baseFolder, buildPath(fn))
	path, err := gourl.QueryUnescape(path)
	hl, err := headerLinks(c.baseFolder, path, c.excludes)
	nl, err := navLinks(path, c.excludes)
	bs, err := content(path)
	proto := "http://"
	if req.TLS() != nil {
		proto = "https://"
	}
	static := c.staticBuilder.SetProtocol(proto).Build()
	args := pack.AnyMap{
		"title":       filepath.Base(fn),
		"static":      static,
		"headerLinks": hl,
		"navLinks":    nl,
		"content": strings.Replace(
			string(bs),
			"{{ static }}",
			static.String(),
			-1,
		),
	}
	if err != nil {
		args["headerLinks"] = map[string]string{"/": "/"}
		args["navLinks"] = nil
		args["content"] = fmt.Sprintf("%s couldn't be served.", fn)
		return response.NewTemplateFromString(
			Template,
			args,
			http.StatusInternalServerError,
		)
	}
	return response.NewTemplateFromString(Template, args, http.StatusOK)
}

// headerLinks are links to files along the provided path except what is in the
// provided set map mapped to their link text.
func headerLinks(bf, path string, excs pack.Set) ([]linkPair, error) {
	ls := []linkPair{linkPair{Real: "/", Fake: "/"}}
	working := ""
	for _, part := range strings.Split(filepath.Dir(path), "/") {
		if part == "." || part == bf {
			continue
		}
		working = filepath.Join(working, part)
		if excs.Contains(working) {
			return nil, fmt.Errorf("%s excluded", working)
		}
		if part == "main.md" {
			break
		}
		if strings.HasSuffix(part, ".md") {
			part = part[:len(part)-len(".md")]
		} else {
			part += "/"
		}
		ls = append(ls, linkPair{Real: "/" + working + "/", Fake: part})
	}
	return ls, nil
}

// navLinks are links to adjacent markdown files and folders to the provided
// path except what is in the excluded provided set mapped to their link text.
//
// Returns an error if the directory of the given path can't be read.
func navLinks(path string, excs pack.Set) ([]linkPair, error) {
	fs, err := ioutil.ReadDir(filepath.Dir(path))
	if err != nil {
		return nil, err
	}
	var ls []linkPair
	for _, f := range fs {
		fn := f.Name()
		if excs.Contains(fn) || excs.Contains(filepath.Base(fn)) {
			continue
		}
		key := f.Name()
		switch mode := f.Mode(); {
		case mode.IsDir():
			key = key + "/"
		case mode.IsRegular():
			if !strings.HasSuffix(fn, ".md") {
				continue
			}
			if fn == "main.md" {
				continue
			}
		}
		if strings.HasSuffix(key, ".md") {
			key = key[:len(key)-len(".md")]
			fn = fn[:len(fn)-len(".md")]
		}
		ls = append(ls, linkPair{Real: key, Fake: fn})
	}
	return ls, nil
}

// content of file at path.
//
// Returns an error if the file isn't a markdown file.
func content(path string) ([]byte, error) {
	if filepath.Ext(path) != ".md" {
		return nil, fmt.Errorf("%s isn't a markdown file", path)
	}
	bs, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return blackfriday.MarkdownCommon(bs), nil
}

// buildPath to markdown file represented by given name.
func buildPath(name string) string {
	path := "." + name
	if path == "" || path[len(path)-1] == '/' {
		path += "main"
	}
	path += ".md"
	return path
}

// Template file shown as page.
const Template = `
<!DOCTYPE html>

    <meta charset="utf-8">
    <title>{{ title }}</title>
    <link rel="icon" href="{{ static }}/favicon.png">
    <meta name="viewport" content="width=device-width, initial-scale=1">

      	{% for p in headerLinks %}
      	  {{ p.Fake }}
      	{% endfor %}

        {% for p in navLinks %}
          {{ p.Fake }}
        {% endfor %}

        {{ content | safe }}

`

// linkPair is a pair of a real and a fake link.
type linkPair struct {
	Real, Fake string
}
<|endoftext|>


<commit_before>// Package md2web contains the MD2Web trim.Application.
package md2web

import (
	"fmt"
	"io/ioutil"
	"net/http"
	gourl "net/url"
	"path/filepath"
	"strings"

	"github.com/jwowillo/pack"
	"github.com/jwowillo/trim/application"
	"github.com/jwowillo/trim/controller"
	"github.com/jwowillo/trim/request"
	"github.com/jwowillo/trim/response"
	"github.com/jwowillo/trim/url"
	"github.com/russross/blackfriday"
)

// MD2Web is a trim.Applications which turns directories of markdown files and
// folders into a website.
type MD2Web struct {
	*application.Web
}

// New creates a MD2Web excluding the provided files which has the given host.
func New(h, bf string, excs []string) *MD2Web {
	app := &MD2Web{Web: application.NewWeb()}
	app.RemoveAPI()
	app.ClearControllers()
	set := pack.NewHashSet(pack.StringHasher)
	for _, exc := range excs {
		set.Add(exc)
	}
	s := url.NewBuilder(h).
		SetSubdomain(app.Static().Subdomain()).
		SetPath(app.Static().BasePath())
	if err := app.AddController(newClientController(s, bf, set)); err != nil {
		panic(err)
	}
	return app
}

// NewDebug creates an MD2Web that doesn't cache which has the given host.
func NewDebug(h, bf string, excs []string) *MD2Web {
	cf := application.ClientDefault
	cf.CacheDuration = 0
	app := &MD2Web{
		Web: application.NewWebWithConfig(
			cf,
			application.APIDefault,
			application.StaticDefault,
		),
	}
	app.RemoveAPI()
	app.ClearControllers()
	set := pack.NewHashSet(pack.StringHasher)
	for _, exc := range excs {
		set.Add(exc)
	}
	s := url.NewBuilder(h).
		SetSubdomain(app.Static().Subdomain()).
		SetPath(app.Static().BasePath())
	if err := app.AddController(newClientController(s, bf, set)); err != nil {
		panic(err)
	}
	return app
}

// clientController which renders markdown page's based on request paths.
type clientController struct {
	controller.Bare
	staticBuilder *url.Builder
	baseFolder    string
	excludes      pack.Set
}

// newClientController creates a controller with the given template file and
// base folder.
func newClientController(
	sb *url.Builder,
	bf string,
	excs pack.Set,
) *clientController {
	excs.Add("static")
	excs.Add(".git")
	excs.Add(".gitignore")
	return &clientController{
		staticBuilder: sb,
		baseFolder:    bf,
		excludes:      excs,
	}
}

// Path of the clientController.
//
// Always a variable path which captures the entire path into the key
// 'fullName'.
func (c *clientController) Path() string {
	return "/:name*"
}

// Handle trim.Request by rendering the markdown page at the file name stored in
// the path.
func (c *clientController) Handle(req *request.Request) response.Response {
	fn := req.URL().Path()
	path := filepath.Join(c.baseFolder, buildPath(fn))
	path, err := gourl.QueryUnescape(path)
	hl, err := headerLinks(c.baseFolder, path, c.excludes)
	nl, err := navLinks(path, c.excludes)
	bs, err := content(path)
	proto := "http://"
	if req.TLS() != nil {
		proto = "https://"
	}
	static := c.staticBuilder.SetProtocol(proto).Build()
	args := pack.AnyMap{
		"title":       filepath.Base(fn),
		"static":      static,
		"headerLinks": hl,
		"navLinks":    nl,
		"content": strings.Replace(
			string(bs),
			"{{ static }}",
			static.String(),
			-1,
		),
	}
	if err != nil {
		args["headerLinks"] = map[string]string{"/": "/"}
		args["navLinks"] = nil
		args["content"] = fmt.Sprintf("%s couldn't be served.", fn)
		return response.NewTemplateFromString(
			Template,
			args,
			http.StatusInternalServerError,
		)
	}
	return response.NewTemplateFromString(Template, args, http.StatusOK)
}

// headerLinks are links to files along the provided path except what is in the
// provided set map mapped to their link text.
func headerLinks(bf, path string, excs pack.Set) ([]linkPair, error) {
	ls := []linkPair{linkPair{Real: "/", Fake: "/"}}
	working := ""
	for _, part := range strings.Split(filepath.Dir(path), "/") {
		if part == "." || part == bf {
			continue
		}
		working = filepath.Join(working, part)
		if excs.Contains(working) {
			return nil, fmt.Errorf("%s excluded", working)
		}
		if part == "main.md" {
			break
		}
		if strings.HasSuffix(part, ".md") {
			part = part[:len(part)-len(".md")]
		} else {
			part += "/"
		}
		ls = append(ls, linkPair{Real: "/" + working + "/", Fake: part})
	}
	return ls, nil
}

// navLinks are links to adjacent markdown files and folders to the provided
// path except what is in the excluded provided set mapped to their link text.
//
// Returns an error if the directory of the given path can't be read.
func navLinks(path string, excs pack.Set) ([]linkPair, error) {
	fs, err := ioutil.ReadDir(filepath.Dir(path))
	if err != nil {
		return nil, err
	}
	var ls []linkPair
	for _, f := range fs {
		fn := f.Name()
		if excs.Contains(fn) || excs.Contains(filepath.Base(fn)) {
			continue
		}
		key := f.Name()
		switch mode := f.Mode(); {
		case mode.IsDir():
			key = key + "/"
		case mode.IsRegular():
			if !strings.HasSuffix(fn, ".md") {
				continue
			}
			if fn == "main.md" {
				continue
			}
		}
		if strings.HasSuffix(key, ".md") {
			key = key[:len(key)-len(".md")]
			fn = fn[:len(fn)-len(".md")]
		}
		ls = append(ls, linkPair{Real: key, Fake: fn})
	}
	return ls, nil
}

// content of file at path.
//
// Returns an error if the file isn't a markdown file.
func content(path string) ([]byte, error) {
	if filepath.Ext(path) != ".md" {
		return nil, fmt.Errorf("%s isn't a markdown file", path)
	}
	bs, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return blackfriday.MarkdownCommon(bs), nil
}

// buildPath to markdown file represented by given name.
func buildPath(name string) string {
	path := "." + name
	if path == "" || path[len(path)-1] == '/' {
		path += "main"
	}
	path += ".md"
	return path
}

// Template file shown as page.
const Template = `
<!DOCTYPE html>

    <meta charset="utf-8">
    <title>{{ title }}</title>
    <link rel="icon" href="{{ static }}/favicon.png">
    <meta name="viewport" content="width=device-width, initial-scale=1">

      	{% for p in headerLinks %}
      	  {{ p.Fake }}
      	{% endfor %}

        {% for p in navLinks %}
          {{ p.Fake }}
        {% endfor %}

        {{ content | safe }}

`

// linkPair is a pair of a real and a fake link.
type linkPair struct {
	Real, Fake string
}
<commit_msg>Increase Page Width<commit_after>// Package md2web contains the MD2Web trim.Application.
package md2web

import (
	"fmt"
	"io/ioutil"
	"net/http"
	gourl "net/url"
	"path/filepath"
	"strings"

	"github.com/jwowillo/pack"
	"github.com/jwowillo/trim/application"
	"github.com/jwowillo/trim/controller"
	"github.com/jwowillo/trim/request"
	"github.com/jwowillo/trim/response"
	"github.com/jwowillo/trim/url"
	"github.com/russross/blackfriday"
)

// MD2Web is a trim.Applications which turns directories of markdown files and
// folders into a website.
type MD2Web struct {
	*application.Web
}

// New creates a MD2Web excluding the provided files which has the given host.
func New(h, bf string, excs []string) *MD2Web {
	app := &MD2Web{Web: application.NewWeb()}
	app.RemoveAPI()
	app.ClearControllers()
	set := pack.NewHashSet(pack.StringHasher)
	for _, exc := range excs {
		set.Add(exc)
	}
	s := url.NewBuilder(h).
		SetSubdomain(app.Static().Subdomain()).
		SetPath(app.Static().BasePath())
	if err := app.AddController(newClientController(s, bf, set)); err != nil {
		panic(err)
	}
	return app
}

// NewDebug creates an MD2Web that doesn't cache which has the given host.
func NewDebug(h, bf string, excs []string) *MD2Web {
	cf := application.ClientDefault
	cf.CacheDuration = 0
	app := &MD2Web{
		Web: application.NewWebWithConfig(
			cf,
			application.APIDefault,
			application.StaticDefault,
		),
	}
	app.RemoveAPI()
	app.ClearControllers()
	set := pack.NewHashSet(pack.StringHasher)
	for _, exc := range excs {
		set.Add(exc)
	}
	s := url.NewBuilder(h).
		SetSubdomain(app.Static().Subdomain()).
		SetPath(app.Static().BasePath())
	if err := app.AddController(newClientController(s, bf, set)); err != nil {
		panic(err)
	}
	return app
}

// clientController which renders markdown page's based on request paths.
type clientController struct {
	controller.Bare
	staticBuilder *url.Builder
	baseFolder    string
	excludes      pack.Set
}

// newClientController creates a controller with the given template file and
// base folder.
func newClientController(
	sb *url.Builder,
	bf string,
	excs pack.Set,
) *clientController {
	excs.Add("static")
	excs.Add(".git")
	excs.Add(".gitignore")
	return &clientController{
		staticBuilder: sb,
		baseFolder:    bf,
		excludes:      excs,
	}
}

// Path of the clientController.
//
// Always a variable path which captures the entire path into the key
// 'fullName'.
func (c *clientController) Path() string {
	return "/:name*"
}

// Handle trim.Request by rendering the markdown page at the file name stored in
// the path.
func (c *clientController) Handle(req *request.Request) response.Response {
	fn := req.URL().Path()
	path := filepath.Join(c.baseFolder, buildPath(fn))
	path, err := gourl.QueryUnescape(path)
	hl, err := headerLinks(c.baseFolder, path, c.excludes)
	nl, err := navLinks(path, c.excludes)
	bs, err := content(path)
	proto := "http://"
	if req.TLS() != nil {
		proto = "https://"
	}
	static := c.staticBuilder.SetProtocol(proto).Build()
	args := pack.AnyMap{
		"title":       filepath.Base(fn),
		"static":      static,
		"headerLinks": hl,
		"navLinks":    nl,
		"content": strings.Replace(
			string(bs),
			"{{ static }}",
			static.String(),
			-1,
		),
	}
	if err != nil {
		args["headerLinks"] = map[string]string{"/": "/"}
		args["navLinks"] = nil
		args["content"] = fmt.Sprintf("%s couldn't be served.", fn)
		return response.NewTemplateFromString(
			Template,
			args,
			http.StatusInternalServerError,
		)
	}
	return response.NewTemplateFromString(Template, args, http.StatusOK)
}

// headerLinks are links to files along the provided path except what is in the
// provided set map mapped to their link text.
func headerLinks(bf, path string, excs pack.Set) ([]linkPair, error) {
	ls := []linkPair{linkPair{Real: "/", Fake: "/"}}
	working := ""
	for _, part := range strings.Split(filepath.Dir(path), "/") {
		if part == "." || part == bf {
			continue
		}
		working = filepath.Join(working, part)
		if excs.Contains(working) {
			return nil, fmt.Errorf("%s excluded", working)
		}
		if part == "main.md" {
			break
		}
		if strings.HasSuffix(part, ".md") {
			part = part[:len(part)-len(".md")]
		} else {
			part += "/"
		}
		ls = append(ls, linkPair{Real: "/" + working + "/", Fake: part})
	}
	return ls, nil
}

// navLinks are links to adjacent markdown files and folders to the provided
// path except what is in the excluded provided set mapped to their link text.
//
// Returns an error if the directory of the given path can't be read.
func navLinks(path string, excs pack.Set) ([]linkPair, error) {
	fs, err := ioutil.ReadDir(filepath.Dir(path))
	if err != nil {
		return nil, err
	}
	var ls []linkPair
	for _, f := range fs {
		fn := f.Name()
		if excs.Contains(fn) || excs.Contains(filepath.Base(fn)) {
			continue
		}
		key := f.Name()
		switch mode := f.Mode(); {
		case mode.IsDir():
			key = key + "/"
		case mode.IsRegular():
			if !strings.HasSuffix(fn, ".md") {
				continue
			}
			if fn == "main.md" {
				continue
			}
		}
		if strings.HasSuffix(key, ".md") {
			key = key[:len(key)-len(".md")]
			fn = fn[:len(fn)-len(".md")]
		}
		ls = append(ls, linkPair{Real: key, Fake: fn})
	}
	return ls, nil
}

// content of file at path.
//
// Returns an error if the file isn't a markdown file.
func content(path string) ([]byte, error) {
	if filepath.Ext(path) != ".md" {
		return nil, fmt.Errorf("%s isn't a markdown file", path)
	}
	bs, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return blackfriday.MarkdownCommon(bs), nil
}

// buildPath to markdown file represented by given name.
func buildPath(name string) string {
	path := "." + name
	if path == "" || path[len(path)-1] == '/' {
		path += "main"
	}
	path += ".md"
	return path
}

// Template file shown as page.
const Template = `
<!DOCTYPE html>

    <meta charset="utf-8">
    <title>{{ title }}</title>
    <link rel="icon" href="{{ static }}/favicon.png">
    <meta name="viewport" content="width=device-width, initial-scale=1">

      	{% for p in headerLinks %}
      	  {{ p.Fake }}
      	{% endfor %}

        {% for p in navLinks %}
          {{ p.Fake }}
        {% endfor %}

        {{ content | safe }}

`

// linkPair is a pair of a real and a fake link.
type linkPair struct {
	Real, Fake string
}
<|endoftext|>


<commit_before>package mudlib

import (
  "fmt"
  "io"
  "log"
  "sort"
  "strings"
)

type command struct {
  name string
  minArgs, maxArgs int
  usage []string
  do func(client, []string) (*string, *message)
}

var commands = make(map[string]command)

func init() {
  commands["quit"] = command {
    minArgs: 0,
    maxArgs: 0,
    usage: []string {""},
    do: func(cl client, args []string) (*string, *message) {
      io.WriteString(cl.conn, "Bye!\n")
      cl.conn.Close()
      return nil, &message {
        from: cl,
        message: "",
        messageType: messageTypeQuit,
      }
    },
  }
  commands["say"] = command {
    minArgs: 1,
    maxArgs: -1,
    usage: []string{"<message>"},
    do: func(cl client, args []string) (*string, *message) {
      return nil, &message {
        message: strings.Join(args, " "),
        messageType: messageTypeSay,
      }
    },
  }
  commands["tell"] = command {
    minArgs: 2,
    maxArgs: -1,
    usage: []string{"<player> <message>"},
    do: func(cl client, args []string) (*string, *message) {
      player, err := players.get(args[0])
      if err != nil {
        ret := fmt.Sprintf("Can't find player %q\n", args[0])
        return &ret, nil
      }
      if conn, _ := player.isConnected(); conn {
        return nil, &message {
          to: player.Nickname,
          message: strings.Join(args[1:], " "),
          messageType: messageTypeTell,
        }
      }
      ret := fmt.Sprintf("%q is not online.\n", args[0])
      return &ret, nil
    },
  }
  commands["me"] = command {
    minArgs: 1,
    maxArgs: -1,
    usage: []string{"<emotes>"},
    do: func(cl client, args []string) (*string, *message) {
      return nil, &message{
        message: strings.Join(args, " "),
        messageType: messageTypeEmote,
      }
    },
  }
  commands["shout"] = command {
    minArgs: 1,
    maxArgs: -1,
    usage: []string{"<message>"},
    do: func(cl client, args []string) (*string, *message) {
      return nil, &message{
        message: strings.Join(args, " "),
        messageType: messageTypeShout,
      }
    },
  }
  commands["who"] = command {
    minArgs: 0,
    maxArgs: 0,
    usage: []string{""},
    do: func(cl client, args []string) (*string, *message) {
      ret := setFg(colorWhite, fmt.Sprintf("%v\n", getConnected()))
      return &ret, nil
    },
  }
  commands["finger"] = command {
    minArgs: 1,
    maxArgs: 1,
    usage: []string{"<player>"},
    do: func(cl client, args []string) (*string, *message) {
      toPrint := ""
			if player, err := players.get(args[0]); err == nil {
				toPrint = setFg(colorWhite, fmt.Sprintf("%+v ", player.finger()))
				if c, _ := player.isConnected(); c {
					toPrint += setFgBold(colorGreen, "[online]\n")
				} else {
					toPrint += setFgBold(colorRed, "[offline]\n")
				}
			} else {
        toPrint = fmt.Sprintf("Failed to find player %q.\n", args[0])
      }
      return &toPrint, nil
		},
  }
  commands["look"] = command {
    minArgs: 0,
    maxArgs: -1,
    usage: []string {"", "<object>", "<player>"},
    do: func(cl client, args []string) (*string, *message) {
      switch len(args) {
      case 0:
        // room look
        if room, err := rooms.get(cl.player.Room); err == nil {
          desc := room.describe()
          return &desc, nil
        }
        // TODO: handle limbo
        log.Printf("%+v in limbo.\n", cl.player)
        desc := "You're in limbo.\n"
        return &desc, nil
      default:
        // TODO: look at objects/players
        return nil, nil
      }
    },
  }
  commands["help"] = command {
    minArgs: 0,
    maxArgs: 1,
    usage: []string {"", "<command>"},
    do: func (cl client, args []string) (*string, *message) {
      switch len(args) {
      case 0:
        ret := fmt.Sprintf("Available commands:\n")
        keys := []string{}
        for k := range(commands) {
          keys = append(keys, k)
        }
        sort.Strings(keys)
        ret += fmt.Sprintf("  %s\n", strings.Join(keys, ", "))
        return &ret, nil
      case 1:
        if c, ok := commands[args[0]]; ok {
          // Use abstracted usage print method
          ret := c.printUsage(args[0])
          return &ret, nil
        }
        ret := fmt.Sprintf("Unknown command %q.\n", args[0])
        return &ret, nil
      }
      return nil, nil
    },
  }
}

func (c command) printUsage(cmd string) string {
  usage := "Usage:\n"
  for _, s := range c.usage {
    usage += fmt.Sprintf("  /%s %s\n", cmd, s);
  }
  return usage
}

func doCommand(cl client, ch chan<- message, cmd string, args []string) error {
  if c, ok := commands[cmd[1:]]; ok {
    if (c.minArgs != -1 && len(args) < c.minArgs) || (c.maxArgs != -1 && len(args) > c.maxArgs) {
      // TODO: manage multiple usage options.
      io.WriteString(cl.conn, c.printUsage(cmd))
      return nil
    }
    toPrint, msg := c.do(cl, args)
    if toPrint != nil {
      io.WriteString(cl.conn, *toPrint)
    }
    if msg != nil {
      msg.from = cl
      ch <- *msg
    }
    return nil
  }
  io.WriteString(cl.conn, "What? (try \"/help\")\n")
  return fmt.Errorf("Failed to find command %q", cmd)
}
<commit_msg>Remove done todo<commit_after>package mudlib

import (
  "fmt"
  "io"
  "log"
  "sort"
  "strings"
)

type command struct {
  name string
  minArgs, maxArgs int
  usage []string
  do func(client, []string) (*string, *message)
}

var commands = make(map[string]command)

func init() {
  commands["quit"] = command {
    minArgs: 0,
    maxArgs: 0,
    usage: []string {""},
    do: func(cl client, args []string) (*string, *message) {
      io.WriteString(cl.conn, "Bye!\n")
      cl.conn.Close()
      return nil, &message {
        from: cl,
        message: "",
        messageType: messageTypeQuit,
      }
    },
  }
  commands["say"] = command {
    minArgs: 1,
    maxArgs: -1,
    usage: []string{"<message>"},
    do: func(cl client, args []string) (*string, *message) {
      return nil, &message {
        message: strings.Join(args, " "),
        messageType: messageTypeSay,
      }
    },
  }
  commands["tell"] = command {
    minArgs: 2,
    maxArgs: -1,
    usage: []string{"<player> <message>"},
    do: func(cl client, args []string) (*string, *message) {
      player, err := players.get(args[0])
      if err != nil {
        ret := fmt.Sprintf("Can't find player %q\n", args[0])
        return &ret, nil
      }
      if conn, _ := player.isConnected(); conn {
        return nil, &message {
          to: player.Nickname,
          message: strings.Join(args[1:], " "),
          messageType: messageTypeTell,
        }
      }
      ret := fmt.Sprintf("%q is not online.\n", args[0])
      return &ret, nil
    },
  }
  commands["me"] = command {
    minArgs: 1,
    maxArgs: -1,
    usage: []string{"<emotes>"},
    do: func(cl client, args []string) (*string, *message) {
      return nil, &message{
        message: strings.Join(args, " "),
        messageType: messageTypeEmote,
      }
    },
  }
  commands["shout"] = command {
    minArgs: 1,
    maxArgs: -1,
    usage: []string{"<message>"},
    do: func(cl client, args []string) (*string, *message) {
      return nil, &message{
        message: strings.Join(args, " "),
        messageType: messageTypeShout,
      }
    },
  }
  commands["who"] = command {
    minArgs: 0,
    maxArgs: 0,
    usage: []string{""},
    do: func(cl client, args []string) (*string, *message) {
      ret := setFg(colorWhite, fmt.Sprintf("%v\n", getConnected()))
      return &ret, nil
    },
  }
  commands["finger"] = command {
    minArgs: 1,
    maxArgs: 1,
    usage: []string{"<player>"},
    do: func(cl client, args []string) (*string, *message) {
      toPrint := ""
			if player, err := players.get(args[0]); err == nil {
				toPrint = setFg(colorWhite, fmt.Sprintf("%+v ", player.finger()))
				if c, _ := player.isConnected(); c {
					toPrint += setFgBold(colorGreen, "[online]\n")
				} else {
					toPrint += setFgBold(colorRed, "[offline]\n")
				}
			} else {
        toPrint = fmt.Sprintf("Failed to find player %q.\n", args[0])
      }
      return &toPrint, nil
		},
  }
  commands["look"] = command {
    minArgs: 0,
    maxArgs: -1,
    usage: []string {"", "<object>", "<player>"},
    do: func(cl client, args []string) (*string, *message) {
      switch len(args) {
      case 0:
        // room look
        if room, err := rooms.get(cl.player.Room); err == nil {
          desc := room.describe()
          return &desc, nil
        }
        // TODO: handle limbo
        log.Printf("%+v in limbo.\n", cl.player)
        desc := "You're in limbo.\n"
        return &desc, nil
      default:
        // TODO: look at objects/players
        return nil, nil
      }
    },
  }
  commands["help"] = command {
    minArgs: 0,
    maxArgs: 1,
    usage: []string {"", "<command>"},
    do: func (cl client, args []string) (*string, *message) {
      switch len(args) {
      case 0:
        ret := fmt.Sprintf("Available commands:\n")
        keys := []string{}
        for k := range(commands) {
          keys = append(keys, k)
        }
        sort.Strings(keys)
        ret += fmt.Sprintf("  %s\n", strings.Join(keys, ", "))
        return &ret, nil
      case 1:
        if c, ok := commands[args[0]]; ok {
          // Use abstracted usage print method
          ret := c.printUsage(args[0])
          return &ret, nil
        }
        ret := fmt.Sprintf("Unknown command %q.\n", args[0])
        return &ret, nil
      }
      return nil, nil
    },
  }
}

func (c command) printUsage(cmd string) string {
  usage := "Usage:\n"
  for _, s := range c.usage {
    usage += fmt.Sprintf("  /%s %s\n", cmd, s);
  }
  return usage
}

func doCommand(cl client, ch chan<- message, cmd string, args []string) error {
  if c, ok := commands[cmd[1:]]; ok {
    if (c.minArgs != -1 && len(args) < c.minArgs) || (c.maxArgs != -1 && len(args) > c.maxArgs) {
      io.WriteString(cl.conn, c.printUsage(cmd))
      return nil
    }
    toPrint, msg := c.do(cl, args)
    if toPrint != nil {
      io.WriteString(cl.conn, *toPrint)
    }
    if msg != nil {
      msg.from = cl
      ch <- *msg
    }
    return nil
  }
  io.WriteString(cl.conn, "What? (try \"/help\")\n")
  return fmt.Errorf("Failed to find command %q", cmd)
}
<|endoftext|>


<commit_before>// Copyright (c) Ilia Kravets, 2015. All rights reserved. PROVIDED "AS IS"
// WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. See LICENSE file for details.

package sim

import (
	"log"

	"github.com/cznic/b"
	"github.com/ikravets/errs"

	"my/ev/packet"
)

type Book interface {
	ApplyOperation(operation SimOperation)
	GetTop(packet.OptionId, packet.MarketSide, int) []PriceLevel
	NumOptions() int
}

func NewBook() Book {
	return &book{
		options:            make(map[packet.OptionId]*optionState),
		newOptionSideState: NewOptionSideStateDeep,
	}
}
func NewBookTop() Book {
	return &book{
		options:            make(map[packet.OptionId]*optionState),
		newOptionSideState: NewOptionSideStateTop,
	}
}

type book struct {
	options            map[packet.OptionId]*optionState
	newOptionSideState func(side packet.MarketSide) optionSideState
}

func (b *book) ApplyOperation(operation SimOperation) {
	oid := operation.GetOptionId()
	if oid.Invalid() {
		return
	}
	os, ok := b.options[oid]
	if !ok {
		os = NewOptionState(b.newOptionSideState)
		b.options[oid] = os
	}
	os.Side(operation.GetSide()).updateLevel(operation.GetPrice(), operation.GetSizeDelta())
}
func (b *book) GetTop(optionId packet.OptionId, side packet.MarketSide, levels int) []PriceLevel {
	os, ok := b.options[optionId]
	if !ok {
		return nil
	}
	s := os.Side(side)
	return s.getTop(optionId, side, levels)
}
func (b *book) NumOptions() int {
	return len(b.options)
}

type PriceLevel struct {
	Price int
	Size  int
}

func (l *PriceLevel) UpdateSize(delta int) bool {
	l.Size += delta
	if l.Size < 0 {
		log.Fatal("size becomes negative ", l, delta)
	}
	return l.Size != 0
}

type optionState struct {
	bid optionSideState
	ask optionSideState
}

func NewOptionState(noss func(side packet.MarketSide) optionSideState) *optionState {
	return &optionState{
		bid: noss(packet.MarketSideBid),
		ask: noss(packet.MarketSideAsk),
	}
}

func (os *optionState) Side(side packet.MarketSide) optionSideState {
	switch side {
	case packet.MarketSideBid:
		return os.bid
	case packet.MarketSideAsk:
		return os.ask
	default:
		log.Fatal("wrong side ", side)
	}
	return nil
}

type optionSideState interface {
	updateLevel(price int, delta int)
	getTop(optionId packet.OptionId, side packet.MarketSide, levels int) []PriceLevel
}

type optionSideStateDeep struct {
	levels *b.Tree
}

func NewOptionSideStateDeep(side packet.MarketSide) optionSideState {
	ComparePrice := func(lhs, rhs interface{}) int {
		l, r := lhs.(int), rhs.(int)
		return l - r
	}
	ComparePriceRev := func(lhs, rhs interface{}) int {
		l, r := lhs.(int), rhs.(int)
		return r - l
	}

	var cmp b.Cmp
	switch side {
	case packet.MarketSideBid:
		cmp = ComparePriceRev
	case packet.MarketSideAsk:
		cmp = ComparePrice
	default:
		log.Fatal("unexpected market side ", side)
	}
	s := &optionSideStateDeep{
		levels: b.TreeNew(cmp),
	}
	return s
}

func (s *optionSideStateDeep) updateLevel(price int, delta int) {
	if price == 0 {
		return
	}
	upd := func(oldV interface{}, exists bool) (newV interface{}, write bool) {
		var v PriceLevel
		if exists {
			v = oldV.(PriceLevel)
		} else {
			v = PriceLevel{Price: price}
		}
		write = v.UpdateSize(delta)
		return v, write
	}
	if _, written := s.levels.Put(price, upd); !written {
		s.levels.Delete(price)
	}
}
func (s *optionSideStateDeep) getTop(optionId packet.OptionId, side packet.MarketSide, levels int) []PriceLevel {
	pl := make([]PriceLevel, 0, levels)
	if it, err := s.levels.SeekFirst(); err == nil {
		for i := 0; i < levels || levels == 0; i++ {
			if _, v, err := it.Next(); err != nil {
				break
			} else {
				pl = append(pl, v.(PriceLevel))
			}
		}
		it.Close()
	}
	return pl
}

type optionSideStateTop struct {
	top PriceLevel
}

func NewOptionSideStateTop(side packet.MarketSide) optionSideState {
	return &optionSideStateTop{}
}
func (s *optionSideStateTop) updateLevel(price int, delta int) {
	errs.Check(price != 0 || delta == 0, price, delta)
	errs.Check(delta >= 0)
	s.top = PriceLevel{
		Price: price,
		Size:  delta,
	}
}
func (s *optionSideStateTop) getTop(optionId packet.OptionId, side packet.MarketSide, levels int) []PriceLevel {
	pl := make([]PriceLevel, 0, 1)
	if s.top.Price != 0 || s.top.Size != 0 {
		pl = append(pl, s.top)
	}
	return pl
}
<commit_msg>sim: rework PriceLevel<commit_after>// Copyright (c) Ilia Kravets, 2015. All rights reserved. PROVIDED "AS IS"
// WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. See LICENSE file for details.

package sim

import (
	"log"

	"github.com/cznic/b"

	"my/ev/packet"
)

type PriceLevel interface {
	Price() int
	Size(SizeKind) int
	Equals(PriceLevel) bool
	Clone() PriceLevel
}

type Book interface {
	ApplyOperation(operation SimOperation)
	GetTop(packet.OptionId, packet.MarketSide, int) []PriceLevel
	NumOptions() int
}

func NewBook() Book {
	return &book{
		options:            make(map[packet.OptionId]*optionState),
		newOptionSideState: NewOptionSideStateDeep,
	}
}
func NewBookTop() Book {
	return &book{
		options:            make(map[packet.OptionId]*optionState),
		newOptionSideState: NewOptionSideStateTop,
	}
}

type book struct {
	options            map[packet.OptionId]*optionState
	newOptionSideState func(side packet.MarketSide) optionSideState
}

func (b *book) ApplyOperation(operation SimOperation) {
	oid := operation.GetOptionId()
	if oid.Invalid() {
		return
	}
	os, ok := b.options[oid]
	if !ok {
		os = NewOptionState(b.newOptionSideState)
		b.options[oid] = os
	}
	os.Side(operation.GetSide()).updateLevel(operation)
}
func (b *book) GetTop(optionId packet.OptionId, side packet.MarketSide, levels int) []PriceLevel {
	os, ok := b.options[optionId]
	if !ok {
		return nil
	}
	s := os.Side(side)
	return s.getTop(levels)
}
func (b *book) NumOptions() int {
	return len(b.options)
}

type optionState struct {
	bid optionSideState
	ask optionSideState
}

func NewOptionState(noss func(side packet.MarketSide) optionSideState) *optionState {
	return &optionState{
		bid: noss(packet.MarketSideBid),
		ask: noss(packet.MarketSideAsk),
	}
}

func (os *optionState) Side(side packet.MarketSide) optionSideState {
	switch side {
	case packet.MarketSideBid:
		return os.bid
	case packet.MarketSideAsk:
		return os.ask
	default:
		log.Fatal("wrong side ", side)
	}
	return nil
}

type optionSideState interface {
	updateLevel(operation SimOperation)
	getTop(levels int) []PriceLevel
}

type optionSideStateDeep struct {
	levels *b.Tree
}

func NewOptionSideStateDeep(side packet.MarketSide) optionSideState {
	ComparePrice := func(lhs, rhs interface{}) int {
		l, r := lhs.(int), rhs.(int)
		return l - r
	}
	ComparePriceRev := func(lhs, rhs interface{}) int {
		l, r := lhs.(int), rhs.(int)
		return r - l
	}

	var cmp b.Cmp
	switch side {
	case packet.MarketSideBid:
		cmp = ComparePriceRev
	case packet.MarketSideAsk:
		cmp = ComparePrice
	default:
		log.Fatal("unexpected market side ", side)
	}
	s := &optionSideStateDeep{
		levels: b.TreeNew(cmp),
	}
	return s
}

func (s *optionSideStateDeep) updateLevel(operation SimOperation) {
	price, delta := operation.GetPrice(), operation.GetDefaultSizeDelta()
	if price == 0 {
		return
	}
	upd := func(oldV interface{}, exists bool) (newV interface{}, write bool) {
		var v *priceLevelDefault
		if exists {
			v = oldV.(*priceLevelDefault)
		} else {
			v = newPriceLevelDefault(price)
		}
		write = v.updateSize(delta)
		return v, write
	}
	if _, written := s.levels.Put(price, upd); !written {
		s.levels.Delete(price)
	}
}
func (s *optionSideStateDeep) getTop(levels int) []PriceLevel {
	pl := make([]PriceLevel, 0, levels)
	if it, err := s.levels.SeekFirst(); err == nil {
		for i := 0; i < levels || levels == 0; i++ {
			if _, v, err := it.Next(); err != nil {
				break
			} else {
				pl = append(pl, v.(PriceLevel))
			}
		}
		it.Close()
	}
	return pl
}

type optionSideStateTop struct {
	top priceLevelMulti
}

func NewOptionSideStateTop(side packet.MarketSide) optionSideState {
	return &optionSideStateTop{}
}
func (s *optionSideStateTop) updateLevel(operation SimOperation) {
	s.top.price = operation.GetPrice()
	for i := SizeKindDefault; i < SizeKinds; i++ {
		s.top.sizes[i] = operation.GetNewSize(i)
	}
}
func (s *optionSideStateTop) getTop(levels int) []PriceLevel {
	pl := make([]PriceLevel, 0, 1)
	if s.top != (priceLevelMulti{}) {
		pl = append(pl, &s.top)
	}
	return pl
}

var _ PriceLevel = &priceLevelDefault{}
var _ PriceLevel = &priceLevelMulti{}

type priceLevelDefault struct {
	price int
	size  int
}

func newPriceLevelDefault(price int) *priceLevelDefault {
	return &priceLevelDefault{price: price}
}
func (l *priceLevelDefault) updateSize(delta int) bool {
	l.size += delta
	if l.size < 0 {
		log.Fatal("size becomes negative ", l, delta)
	}
	return l.size != 0
}
func (l *priceLevelDefault) Price() int {
	return l.price
}
func (l *priceLevelDefault) Size(sk SizeKind) int {
	if sk == SizeKindDefault {
		return l.size
	} else {
		return 0
	}
}
func (l *priceLevelDefault) Equals(rhs PriceLevel) (eq bool) {
	if r, ok := rhs.(*priceLevelDefault); ok {
		eq = *l == *r
	}
	return
}
func (l *priceLevelDefault) Clone() PriceLevel {
	return &priceLevelDefault{
		price: l.price,
		size:  l.size,
	}
}

type priceLevelMulti struct {
	price int
	sizes [SizeKinds]int
}

func newPriceLevelMulti(price int) *priceLevelMulti {
	return &priceLevelMulti{price: price}
}
func (l *priceLevelMulti) Price() int {
	return l.price
}
func (l *priceLevelMulti) Size(sk SizeKind) int {
	return l.sizes[sk]
}
func (l *priceLevelMulti) Equals(rhs PriceLevel) (eq bool) {
	if r, ok := rhs.(*priceLevelMulti); ok {
		eq = *l == *r
	}
	return
}
func (l *priceLevelMulti) Clone() PriceLevel {
	return &priceLevelMulti{
		price: l.price,
		sizes: l.sizes,
	}
}

type priceLevelEmpty struct{}

func newpriceLevelEmpty(price int) *priceLevelEmpty {
	return &priceLevelEmpty{}
}
func (l *priceLevelEmpty) Price() int          { return 0 }
func (l *priceLevelEmpty) Size(_ SizeKind) int { return 0 }
func (l *priceLevelEmpty) Clone() PriceLevel   { return l }
func (l *priceLevelEmpty) Equals(rhs PriceLevel) (eq bool) {
	_, eq = rhs.(*priceLevelEmpty)
	return
}

var EmptyPriceLevel PriceLevel = &priceLevelEmpty{}
<|endoftext|>


<commit_before>/*
Package minify is a minifier written in Go that has built-in HTML, CSS and JS minifiers.

Usage example:

	package main

	import (
		"fmt"
		"os"
		"os/exec"

		"github.com/tdewolff/minify"
		"github.com/tdewolff/minify/html"
		"github.com/tdewolff/minify/css"
		"github.com/tdewolff/minify/js"
	)

	// Minifies HTML from stdin to stdout.
	// Note that reading the file into a buffer first and writing to a buffer would be faster.
	func main() {
		m := minify.New()
		m.AddFunc("text/html", html.Minify)
		m.AddFunc("text/css", css.Minify)
		m.AddFunc("text/javascript", js.Minify)
		// Or use an external tool:
		// m.AddCmd("text/javascript", exec.Command("java", "-jar", "build/compiler.jar"))

		if err := m.Minify("text/html", os.Stdout, os.Stdin); err != nil {
			fmt.Println("minify.Minify:", err)
		}
	}

*/
package minify // import "github.com/tdewolff/minify"

import (
	"errors"
	"io"
	"os/exec"
	"regexp"

	"github.com/tdewolff/buffer"
)

// ErrNotExist is returned when no minifier exists for a given mediatype.
var ErrNotExist = errors.New("minify function does not exist for mediatype")

////////////////////////////////////////////////////////////////

// Func is the function interface for minifiers.
// The Minifier parameter is used for embedded resources, such as JS within HTML.
// The mediatype string is for wildcard minifiers so they know what they minify and for parameter passing (charset for example).
type Func func(m Minifier, mediatype string, w io.Writer, r io.Reader) error

// Minifier is the interface which all minifier functions accept as first parameter.
// It's used to extract parameter values of the mediatype and to recursively call other minifier functions.
type Minifier interface {
	Minify(mediatype string, w io.Writer, r io.Reader) error
}

func cmdFunc(origCmd *exec.Cmd) func(_ Minifier, _ string, w io.Writer, r io.Reader) error {
	return func(_ Minifier, _ string, w io.Writer, r io.Reader) error {
		cmd := &exec.Cmd{}
		*cmd = *origCmd // concurrency safe
		cmd.Stdout = w
		cmd.Stdin = r
		return cmd.Run()
	}
}

////////////////////////////////////////////////////////////////

type regexpFunc struct {
	re *regexp.Regexp
	Func
}

// Minify holds a map of mediatype => function to allow recursive minifier calls of the minifier functions.
type Minify struct {
	literal map[string]Func
	regexp  []regexpFunc
}

// New returns a new Minify. It is the same as doing `Minify{}`.
func New() *Minify {
	return &Minify{
		map[string]Func{},
		[]regexpFunc{},
	}
}

// AddFunc adds a minify function to the mediatype => function map (unsafe for concurrent use).
// It allows one to implement a custom minifier for a specific mediatype.
func (m *Minify) AddFunc(mediatype string, minifyFunc Func) {
	m.literal[mediatype] = minifyFunc
}

// AddFuncRegexp adds a minify function to the mediatype => function map (unsafe for concurrent use).
// It allows one to implement a custom minifier for a specific mediatype regular expression.
func (m *Minify) AddFuncRegexp(mediatype *regexp.Regexp, minifyFunc Func) {
	m.regexp = append(m.regexp, regexpFunc{mediatype, minifyFunc})
}

// AddCmd adds a minify function to the mediatype => function map (unsafe for concurrent use) that executes a command to process the minification.
// It allows the use of external tools like ClosureCompiler, UglifyCSS, etc. for a specific mediatype.
// Be aware that running external tools will slow down minification a lot!
func (m *Minify) AddCmd(mediatype string, cmd *exec.Cmd) {
	m.literal[mediatype] = cmdFunc(cmd)
}

// AddCmd adds a minify function to the mediatype => function map (unsafe for concurrent use) that executes a command to process the minification.
// It allows the use of external tools like ClosureCompiler, UglifyCSS, etc. for a specific mediatype regular expression.
// Be aware that running external tools will slow down minification a lot!
func (m *Minify) AddCmdRegexp(mediatype *regexp.Regexp, cmd *exec.Cmd) {
	m.regexp = append(m.regexp, regexpFunc{mediatype, cmdFunc(cmd)})
}

// Minify minifies the content of a Reader and writes it to a Writer (safe for concurrent use).
// An error is returned when no such mediatype exists (ErrNotExist) or when an error occurred in the minifier function.
// Mediatype may take the form of 'text/plain', 'text/*', '*/*' or 'text/plain; charset=UTF-8; version=2.0'.
func (m Minify) Minify(mediatype string, w io.Writer, r io.Reader) error {
	mimetype := mediatype
	for i, c := range mediatype {
		if c == ';' {
			mimetype = mediatype[:i]
			break
		}
	}
	if minifyFunc, ok := m.literal[mimetype]; ok {
		if err := minifyFunc(m, mediatype, w, r); err != nil {
			return err
		}
		return nil
	}
	for _, minifyRegexp := range m.regexp {
		if minifyRegexp.re.MatchString(mimetype) {
			if err := minifyRegexp.Func(m, mediatype, w, r); err != nil {
				return err
			}
			return nil
		}
	}
	return ErrNotExist
}

// Bytes minifies an array of bytes (safe for concurrent use). When an error occurs it return the original array and the error.
// It returns an error when no such mediatype exists (ErrNotExist) or any error occurred in the minifier function.
func Bytes(m Minifier, mediatype string, v []byte) ([]byte, error) {
	out := buffer.NewWriter(make([]byte, 0, len(v)))
	if err := m.Minify(mediatype, out, buffer.NewReader(v)); err != nil {
		return v, err
	}
	return out.Bytes(), nil
}

// String minifies a string (safe for concurrent use). When an error occurs it return the original string and the error.
// It returns an error when no such mediatype exists (ErrNotExist) or any error occurred in the minifier function.
func String(m Minifier, mediatype string, v string) (string, error) {
	out := buffer.NewWriter(make([]byte, 0, len(v)))
	if err := m.Minify(mediatype, out, buffer.NewReader([]byte(v))); err != nil {
		return v, err
	}
	return string(out.Bytes()), nil
}
<commit_msg>Commentary changes<commit_after>// Package minify is a minifier written in Go that can attach minification functions to MIME types. Several minifiers are provided in subpackages.
package minify // import "github.com/tdewolff/minify"

import (
	"errors"
	"io"
	"os/exec"
	"regexp"

	"github.com/tdewolff/buffer"
)

// ErrNotExist is returned when no minifier exists for a given mediatype.
var ErrNotExist = errors.New("minify function does not exist for mediatype")

////////////////////////////////////////////////////////////////

// Func is the function interface for minifiers.
// The Minifier parameter is used for embedded resources, such as JS within HTML.
// The mediatype string is for wildcard minifiers so they know what they minify and for parameter passing (charset for example).
type Func func(m Minifier, mediatype string, w io.Writer, r io.Reader) error

// Minifier is the interface which all minifier functions accept as first parameter.
// It's used to extract parameter values of the mediatype and to recursively call other minifier functions.
type Minifier interface {
	Minify(mediatype string, w io.Writer, r io.Reader) error
}

////////////////////////////////////////////////////////////////

type regexpFunc struct {
	re *regexp.Regexp
	Func
}

func cmdFunc(origCmd *exec.Cmd) func(_ Minifier, _ string, w io.Writer, r io.Reader) error {
	return func(_ Minifier, _ string, w io.Writer, r io.Reader) error {
		cmd := &exec.Cmd{}
		*cmd = *origCmd // concurrency safety
		cmd.Stdout = w
		cmd.Stdin = r
		return cmd.Run()
	}
}

////////////////////////////////////////////////////////////////

// Minify holds a map of mediatype => function to allow recursive minifier calls of the minifier functions.
type Minify struct {
	literal map[string]Func
	regexp  []regexpFunc
}

// New returns a new Minify. It is the same as doing `Minify{}`.
func New() *Minify {
	return &Minify{
		map[string]Func{},
		[]regexpFunc{},
	}
}

// AddFunc adds a minify function to the mediatype => function map (unsafe for concurrent use).
// It allows one to implement a custom minifier for a specific mediatype.
func (m *Minify) AddFunc(mediatype string, minifyFunc Func) {
	m.literal[mediatype] = minifyFunc
}

// AddFuncRegexp adds a minify function to the mediatype => function map (unsafe for concurrent use).
// It allows one to implement a custom minifier for a specific mediatype regular expression.
func (m *Minify) AddFuncRegexp(mediatype *regexp.Regexp, minifyFunc Func) {
	m.regexp = append(m.regexp, regexpFunc{mediatype, minifyFunc})
}

// AddCmd adds a minify function to the mediatype => function map (unsafe for concurrent use) that executes a command to process the minification.
// It allows the use of external tools like ClosureCompiler, UglifyCSS, etc. for a specific mediatype.
// Be aware that running external tools will slow down minification a lot!
func (m *Minify) AddCmd(mediatype string, cmd *exec.Cmd) {
	m.literal[mediatype] = cmdFunc(cmd)
}

// AddCmd adds a minify function to the mediatype => function map (unsafe for concurrent use) that executes a command to process the minification.
// It allows the use of external tools like ClosureCompiler, UglifyCSS, etc. for a specific mediatype regular expression.
// Be aware that running external tools will slow down minification a lot!
func (m *Minify) AddCmdRegexp(mediatype *regexp.Regexp, cmd *exec.Cmd) {
	m.regexp = append(m.regexp, regexpFunc{mediatype, cmdFunc(cmd)})
}

// Minify minifies the content of a Reader and writes it to a Writer (safe for concurrent use).
// An error is returned when no such mediatype exists (ErrNotExist) or when an error occurred in the minifier function.
// Mediatype may take the form of 'text/plain', 'text/*', '*/*' or 'text/plain; charset=UTF-8; version=2.0'.
func (m Minify) Minify(mediatype string, w io.Writer, r io.Reader) error {
	mimetype := mediatype
	for i, c := range mediatype {
		if c == ';' {
			mimetype = mediatype[:i]
			break
		}
	}
	if minifyFunc, ok := m.literal[mimetype]; ok {
		if err := minifyFunc(m, mediatype, w, r); err != nil {
			return err
		}
		return nil
	}
	for _, minifyRegexp := range m.regexp {
		if minifyRegexp.re.MatchString(mimetype) {
			if err := minifyRegexp.Func(m, mediatype, w, r); err != nil {
				return err
			}
			return nil
		}
	}
	return ErrNotExist
}

// Bytes minifies an array of bytes (safe for concurrent use). When an error occurs it return the original array and the error.
// It returns an error when no such mediatype exists (ErrNotExist) or any error occurred in the minifier function.
func Bytes(m Minifier, mediatype string, v []byte) ([]byte, error) {
	out := buffer.NewWriter(make([]byte, 0, len(v)))
	if err := m.Minify(mediatype, out, buffer.NewReader(v)); err != nil {
		return v, err
	}
	return out.Bytes(), nil
}

// String minifies a string (safe for concurrent use). When an error occurs it return the original string and the error.
// It returns an error when no such mediatype exists (ErrNotExist) or any error occurred in the minifier function.
func String(m Minifier, mediatype string, v string) (string, error) {
	out := buffer.NewWriter(make([]byte, 0, len(v)))
	if err := m.Minify(mediatype, out, buffer.NewReader([]byte(v))); err != nil {
		return v, err
	}
	return string(out.Bytes()), nil
}
<|endoftext|>