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

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

	"github.com/jwowillo/pack"
	"github.com/jwowillo/trim"
	"github.com/jwowillo/trim/application"
	"github.com/jwowillo/trim/response"
	"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 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)
	}
	static := app.URLFor(
		trim.Pattern{
			app.Static().Subdomain(),
			app.Static().BasePath(),
		}, h,
	).String()
	if err := app.AddController(newClientController(static, set)); err != nil {
		panic(err)
	}
	return app
}

// NewDebug creates an MD2Web that doesn't cache which has the given host.
func NewDebug(h 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)
	}
	static := app.URLFor(
		trim.Pattern{
			app.Static().Subdomain(),
			app.Static().BasePath(),
		}, h,
	).String()
	if err := app.AddController(newClientController(static, set)); err != nil {
		panic(err)
	}
	return app
}

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

// newClientController creates a controller with the given template file and
// base folder.
func newClientController(
	static string,
	excs pack.Set,
) *clientController {
	excs.Add("static")
	excs.Add(".git")
	excs.Add(".gitignore")
	return &clientController{static: static, 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 *trim.Request) trim.Response {
	fn := req.URL().Path()
	path := buildPath(fn)
	hl, err := headerLinks(path, c.excludes)
	nl, err := navLinks(path, c.excludes)
	bs, err := content(path)
	args := trim.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(path string, excs pack.Set) ([]linkPair, error) {
	ls := []linkPair{linkPair{Real: "/", Fake: "/"}}
	working := ""
	for _, part := range strings.Split(filepath.Dir(path), "/") {
		if part == "." {
			continue
		}
		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>Trim update<commit_after>// Package md2web contains the MD2Web trim.Application.
package md2web

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"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 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)
	}
	static := app.URLFor(
		url.Pattern{
			app.Static().Subdomain(),
			app.Static().BasePath(),
		}, h,
	).String()
	if err := app.AddController(newClientController(static, set)); err != nil {
		panic(err)
	}
	return app
}

// NewDebug creates an MD2Web that doesn't cache which has the given host.
func NewDebug(h 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)
	}
	static := app.URLFor(
		url.Pattern{
			app.Static().Subdomain(),
			app.Static().BasePath(),
		}, h,
	).String()
	if err := app.AddController(newClientController(static, set)); err != nil {
		panic(err)
	}
	return app
}

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

// newClientController creates a controller with the given template file and
// base folder.
func newClientController(
	static string,
	excs pack.Set,
) *clientController {
	excs.Add("static")
	excs.Add(".git")
	excs.Add(".gitignore")
	return &clientController{static: static, 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 := buildPath(fn)
	hl, err := headerLinks(path, c.excludes)
	nl, err := navLinks(path, c.excludes)
	bs, err := content(path)
	args := request.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(path string, excs pack.Set) ([]linkPair, error) {
	ls := []linkPair{linkPair{Real: "/", Fake: "/"}}
	working := ""
	for _, part := range strings.Split(filepath.Dir(path), "/") {
		if part == "." {
			continue
		}
		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
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

const (
	HELP = `md2xml [-h] file.md
Transform a given Markdown file into XML.
-h        To print this help page.
-x        Print intermediate XHTML output.
-a        Output article (instead of blog entry).
-s        Print stylesheet used for transformation.
file.md   The markdown file to convert.
Note: this program calls xsltproc that must have been installed.`
	STYLESHEET_ARTICLE = `<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">

  <xsl:output method="xml" encoding="UTF-8"/>
  <xsl:param name="id">ID</xsl:param>
  <xsl:param name="date">DATE</xsl:param>
  <xsl:param name="title">TITLE</xsl:param>
  <xsl:param name="author">AUTHOR</xsl:param>
  <xsl:param name="email">EMAIL</xsl:param>
  <xsl:param name="lang">LANG</xsl:param>
  <xsl:param name="toc">TOC</xsl:param>

  <xsl:template match="/xhtml">
    <xsl:text disable-output-escaping="yes">
    <!DOCTYPE article PUBLIC "-//CAFEBABE//DTD blog 1.0//EN"
                             "../dtd/article.dtd">
    </xsl:text>

      <xsl:attribute name="id"><xsl:value-of select="$id"/></xsl:attribute>
      <xsl:attribute name="date"><xsl:value-of select="$date"/></xsl:attribute>
      <xsl:attribute name="author"><xsl:value-of select="$author"/></xsl:attribute>
      <xsl:attribute name="email"><xsl:value-of select="$email"/></xsl:attribute>
      <xsl:attribute name="lang"><xsl:value-of select="$lang"/></xsl:attribute>
      <xsl:attribute name="toc"><xsl:value-of select="$toc"/></xsl:attribute>
      <title><xsl:value-of select="$title"/></title>
      <text>
       <xsl:apply-templates/>
      </text>

  </xsl:template>

  <xsl:template match="h1">
    <sect level="1"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h2">
    <sect level="2"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h3">
    <sect level="3"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h4">
    <sect level="4"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h5">
    <sect level="5"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h6">
    <sect level="6"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="p[@class='caption']">
  </xsl:template>

  <xsl:template match="p[count(text())=0 and count(code)=1]">
    <source><xsl:apply-templates select="code"/></source>
  </xsl:template>

  <xsl:template match="p[count(text())=1 and count(img)=1]">
    <xsl:apply-templates select="img"/>
  </xsl:template>

  <xsl:template match="img">

      <xsl:if test="@title">
        <title><xsl:value-of select="@title"/></title>
      </xsl:if>

  </xsl:template>

  <xsl:template match="p">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="ul">
    <list><xsl:apply-templates/></list>
  </xsl:template>

  <xsl:template match="ol">
    <enum><xsl:apply-templates/></enum>
  </xsl:template>

  <xsl:template match="li">
    <item><xsl:apply-templates/></item>
  </xsl:template>

  <xsl:template match="table">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="tr[count(th)=0]">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="tr[count(th) > 0]">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="th">
    <co><xsl:apply-templates/></co>
  </xsl:template>

  <xsl:template match="td">
    <co><xsl:apply-templates/></co>
  </xsl:template>

  <xsl:template match="pre">
    <source><xsl:apply-templates/></source>
  </xsl:template>

  <xsl:template match="em">
    <term><xsl:apply-templates/></term>
  </xsl:template>

  <xsl:template match="strong">
    <imp><xsl:apply-templates/></imp>
  </xsl:template>

  <xsl:template match="a">
    <link url="{@href}"><xsl:apply-templates/></link>
  </xsl:template>

</xsl:stylesheet>`
	STYLESHEET_BLOG = `<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">

  <xsl:output method="xml" encoding="UTF-8"/>
  <xsl:param name="id">ID</xsl:param>
  <xsl:param name="date">DATE</xsl:param>
  <xsl:param name="title">TITLE</xsl:param>

  <xsl:template match="/xhtml">
    <xsl:text disable-output-escaping="yes">
    <!DOCTYPE blog PUBLIC "-//CAFEBABE//DTD blog 1.0//EN"
                          "../dtd/blog.dtd">
    </xsl:text>
    <blog>
      <xsl:attribute name="id"><xsl:value-of select="$id"/></xsl:attribute>
      <xsl:attribute name="date"><xsl:value-of select="$date"/></xsl:attribute>
      <title><xsl:value-of select="$title"/></title>
      <xsl:apply-templates/>
    </blog>
  </xsl:template>

  <xsl:template match="h1">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h2">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h3">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h4">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h5">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h6">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="p[@class='caption']">
  </xsl:template>

  <xsl:template match="p[count(text())=0 and count(code)=1]">
    <source><xsl:apply-templates select="code"/></source>
  </xsl:template>

  <xsl:template match="p[count(text())=1 and count(img)=1]">
    <xsl:apply-templates select="img"/>
  </xsl:template>

  <xsl:template match="img">

      <xsl:if test="@title">
        <title><xsl:value-of select="@title"/></title>
      </xsl:if>

  </xsl:template>

  <xsl:template match="p">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="ul">
    <list><xsl:apply-templates/></list>
  </xsl:template>

  <xsl:template match="ol">
    <enum><xsl:apply-templates/></enum>
  </xsl:template>

  <xsl:template match="li">
    <item><xsl:apply-templates/></item>
  </xsl:template>

  <xsl:template match="table">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="tr[count(th)=0]">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="tr[count(th) > 0]">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="th">
    <co><xsl:apply-templates/></co>
  </xsl:template>

  <xsl:template match="td">
    <co><xsl:apply-templates/></co>
  </xsl:template>

  <xsl:template match="pre">
    <source><xsl:apply-templates/></source>
  </xsl:template>

  <xsl:template match="em">
    <term><xsl:apply-templates/></term>
  </xsl:template>

  <xsl:template match="strong">
    <imp><xsl:apply-templates/></imp>
  </xsl:template>

  <xsl:template match="a">
    <link url="{@href}"><xsl:apply-templates/></link>
  </xsl:template>

</xsl:stylesheet>`
	XHTML_HEADER = "<xhtml>\n"
	XHTML_FOOTER = "\n</xhtml>"
)

func processXsl(xmlFile string, data map[string]string, article bool) []byte {
	xslFile, err := ioutil.TempFile("/tmp", "md2xsl-")
	if err != nil {
		panic(err)
	}
	stylesheet := STYLESHEET_ARTICLE
	if !article {
		stylesheet = STYLESHEET_BLOG
	}
	err = ioutil.WriteFile(xslFile.Name(), []byte(stylesheet), 0755)
	if err != nil {
		panic(err)
	}
	defer os.Remove(xslFile.Name())
	params := make([]string, 0, 2+3*len(data))
	for name, value := range data {
		params = append(params, "--stringparam")
		params = append(params, name)
		params = append(params, value)
	}
	params = append(params, xslFile.Name())
	params = append(params, xmlFile)
	command := exec.Command("xsltproc", params...)
	result, err := command.CombinedOutput()
	if err != nil {
        println(result)
		panic(err)
	}
	return result
}

func markdown2xhtml(markdown string) []byte {
	mdFile, err := ioutil.TempFile("/tmp", "md2xsl-")
	if err != nil {
		panic(err)
	}
	defer os.Remove(mdFile.Name())
	ioutil.WriteFile(mdFile.Name(), []byte(markdown), 0x755)
	command := exec.Command("pandoc", mdFile.Name(), "-f", "markdown", "-t", "html")
	result, err := command.CombinedOutput()
	if err != nil {
	 println(result)
		panic(err)
	}
	return []byte(XHTML_HEADER + string(result) + XHTML_FOOTER)
}

func markdownData(text string) (map[string]string, string) {
	data := make(map[string]string)
	lines := strings.Split(text, "\n")
	var limit int
	for index, line := range lines {
		if strings.HasPrefix(line, "% ") && strings.Index(line, ":") >= 0 {
			name := strings.TrimSpace(line[2:strings.Index(line, ":")])
			value := strings.TrimSpace(line[strings.Index(line, ":")+1 : len(line)])
			data[name] = value
		} else {
			limit = index
			break
		}
	}
	return data, strings.Join(lines[limit:len(lines)], "\n")
}

func processFile(filename string, printXhtml bool, article bool) string {
	source, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	}
	data, markdown := markdownData(string(source))
	xhtml := markdown2xhtml(markdown)
	if printXhtml {
		return string(xhtml)
	}
	xmlFile, err := ioutil.TempFile("/tmp", "md2xml-")
	if err != nil {
		panic(err)
	}
	defer os.Remove(xmlFile.Name())
	ioutil.WriteFile(xmlFile.Name(), xhtml, 0755)
	result := processXsl(xmlFile.Name(), data, article)
	return string(result)
}

func main() {
	xhtml := false
	article := false
	if len(os.Args) < 2 {
		fmt.Println(HELP)
		os.Exit(1)
	}
	for _, arg := range os.Args[1:] {
		if arg == "-h" || os.Args[1] == "--help" {
			fmt.Println(HELP)
			os.Exit(0)
		} else if arg == "-x" || arg == "--xhtml" {
			xhtml = true
		} else if arg == "-a" || arg == "--article" {
			article = true
		} else if arg == "-s" || arg == "--stylesheet" {
   if article {
       fmt.Println(STYLESHEET_ARTICLE)
   } else {
       fmt.Println(STYLESHEET_BLOG)
   }
		} else {
			fmt.Println(processFile(arg, xhtml, article))
		}
	}
}
<commit_msg>Fixed toc<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

const (
	HELP = `md2xml [-h] file.md
Transform a given Markdown file into XML.
-h        To print this help page.
-x        Print intermediate XHTML output.
-a        Output article (instead of blog entry).
-s        Print stylesheet used for transformation.
file.md   The markdown file to convert.
Note: this program calls xsltproc that must have been installed.`
	STYLESHEET_ARTICLE = `<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">

  <xsl:output method="xml" encoding="UTF-8"/>
  <xsl:param name="id">ID</xsl:param>
  <xsl:param name="date">DATE</xsl:param>
  <xsl:param name="title">TITLE</xsl:param>
  <xsl:param name="author">AUTHOR</xsl:param>
  <xsl:param name="email">EMAIL</xsl:param>
  <xsl:param name="lang">fr</xsl:param>
  <xsl:param name="toc">yes</xsl:param>

  <xsl:template match="/xhtml">
    <xsl:text disable-output-escaping="yes">
    <!DOCTYPE article PUBLIC "-//CAFEBABE//DTD blog 1.0//EN"
                             "../dtd/article.dtd">
    </xsl:text>

      <xsl:attribute name="id"><xsl:value-of select="$id"/></xsl:attribute>
      <xsl:attribute name="date"><xsl:value-of select="$date"/></xsl:attribute>
      <xsl:attribute name="author"><xsl:value-of select="$author"/></xsl:attribute>
      <xsl:attribute name="email"><xsl:value-of select="$email"/></xsl:attribute>
      <xsl:attribute name="lang"><xsl:value-of select="$lang"/></xsl:attribute>
      <xsl:attribute name="toc"><xsl:value-of select="$toc"/></xsl:attribute>
      <title><xsl:value-of select="$title"/></title>
      <text>
       <xsl:apply-templates/>
      </text>

  </xsl:template>

  <xsl:template match="h1">
    <sect level="1"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h2">
    <sect level="2"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h3">
    <sect level="3"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h4">
    <sect level="4"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h5">
    <sect level="5"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="h6">
    <sect level="6"><title><xsl:value-of select="."/></title></sect>
  </xsl:template>

  <xsl:template match="p[@class='caption']">
  </xsl:template>

  <xsl:template match="p[count(text())=0 and count(code)=1]">
    <source><xsl:apply-templates select="code"/></source>
  </xsl:template>

  <xsl:template match="p[count(text())=1 and count(img)=1]">
    <xsl:apply-templates select="img"/>
  </xsl:template>

  <xsl:template match="img">

      <xsl:if test="@title">
        <title><xsl:value-of select="@title"/></title>
      </xsl:if>

  </xsl:template>

  <xsl:template match="p">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="ul">
    <list><xsl:apply-templates/></list>
  </xsl:template>

  <xsl:template match="ol">
    <enum><xsl:apply-templates/></enum>
  </xsl:template>

  <xsl:template match="li">
    <item><xsl:apply-templates/></item>
  </xsl:template>

  <xsl:template match="table">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="tr[count(th)=0]">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="tr[count(th) > 0]">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="th">
    <co><xsl:apply-templates/></co>
  </xsl:template>

  <xsl:template match="td">
    <co><xsl:apply-templates/></co>
  </xsl:template>

  <xsl:template match="pre">
    <source><xsl:apply-templates/></source>
  </xsl:template>

  <xsl:template match="em">
    <term><xsl:apply-templates/></term>
  </xsl:template>

  <xsl:template match="strong">
    <imp><xsl:apply-templates/></imp>
  </xsl:template>

  <xsl:template match="a">
    <link url="{@href}"><xsl:apply-templates/></link>
  </xsl:template>

</xsl:stylesheet>`
	STYLESHEET_BLOG = `<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">

  <xsl:output method="xml" encoding="UTF-8"/>
  <xsl:param name="id">ID</xsl:param>
  <xsl:param name="date">DATE</xsl:param>
  <xsl:param name="title">TITLE</xsl:param>

  <xsl:template match="/xhtml">
    <xsl:text disable-output-escaping="yes">
    <!DOCTYPE blog PUBLIC "-//CAFEBABE//DTD blog 1.0//EN"
                          "../dtd/blog.dtd">
    </xsl:text>
    <blog>
      <xsl:attribute name="id"><xsl:value-of select="$id"/></xsl:attribute>
      <xsl:attribute name="date"><xsl:value-of select="$date"/></xsl:attribute>
      <title><xsl:value-of select="$title"/></title>
      <xsl:apply-templates/>
    </blog>
  </xsl:template>

  <xsl:template match="h1">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h2">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h3">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h4">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h5">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="h6">

<imp><xsl:value-of select="."/></imp>

  </xsl:template>

  <xsl:template match="p[@class='caption']">
  </xsl:template>

  <xsl:template match="p[count(text())=0 and count(code)=1]">
    <source><xsl:apply-templates select="code"/></source>
  </xsl:template>

  <xsl:template match="p[count(text())=1 and count(img)=1]">
    <xsl:apply-templates select="img"/>
  </xsl:template>

  <xsl:template match="img">

      <xsl:if test="@title">
        <title><xsl:value-of select="@title"/></title>
      </xsl:if>

  </xsl:template>

  <xsl:template match="p">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="ul">
    <list><xsl:apply-templates/></list>
  </xsl:template>

  <xsl:template match="ol">
    <enum><xsl:apply-templates/></enum>
  </xsl:template>

  <xsl:template match="li">
    <item><xsl:apply-templates/></item>
  </xsl:template>

  <xsl:template match="table">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="tr[count(th)=0]">

<xsl:apply-templates/>

  </xsl:template>

  <xsl:template match="tr[count(th) > 0]">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="th">
    <co><xsl:apply-templates/></co>
  </xsl:template>

  <xsl:template match="td">
    <co><xsl:apply-templates/></co>
  </xsl:template>

  <xsl:template match="pre">
    <source><xsl:apply-templates/></source>
  </xsl:template>

  <xsl:template match="em">
    <term><xsl:apply-templates/></term>
  </xsl:template>

  <xsl:template match="strong">
    <imp><xsl:apply-templates/></imp>
  </xsl:template>

  <xsl:template match="a">
    <link url="{@href}"><xsl:apply-templates/></link>
  </xsl:template>

</xsl:stylesheet>`
	XHTML_HEADER = "<xhtml>\n"
	XHTML_FOOTER = "\n</xhtml>"
)

func processXsl(xmlFile string, data map[string]string, article bool) []byte {
	xslFile, err := ioutil.TempFile("/tmp", "md2xsl-")
	if err != nil {
		panic(err)
	}
	stylesheet := STYLESHEET_ARTICLE
	if !article {
		stylesheet = STYLESHEET_BLOG
	}
	err = ioutil.WriteFile(xslFile.Name(), []byte(stylesheet), 0755)
	if err != nil {
		panic(err)
	}
	defer os.Remove(xslFile.Name())
	params := make([]string, 0, 2+3*len(data))
	for name, value := range data {
		params = append(params, "--stringparam")
		params = append(params, name)
		params = append(params, value)
	}
	params = append(params, xslFile.Name())
	params = append(params, xmlFile)
	command := exec.Command("xsltproc", params...)
	result, err := command.CombinedOutput()
	if err != nil {
        println(result)
		panic(err)
	}
	return result
}

func markdown2xhtml(markdown string) []byte {
	mdFile, err := ioutil.TempFile("/tmp", "md2xsl-")
	if err != nil {
		panic(err)
	}
	defer os.Remove(mdFile.Name())
	ioutil.WriteFile(mdFile.Name(), []byte(markdown), 0x755)
	command := exec.Command("pandoc", mdFile.Name(), "-f", "markdown", "-t", "html")
	result, err := command.CombinedOutput()
	if err != nil {
	 println(result)
		panic(err)
	}
	return []byte(XHTML_HEADER + string(result) + XHTML_FOOTER)
}

func markdownData(text string) (map[string]string, string) {
	data := make(map[string]string)
	lines := strings.Split(text, "\n")
	var limit int
	for index, line := range lines {
		if strings.HasPrefix(line, "% ") && strings.Index(line, ":") >= 0 {
			name := strings.TrimSpace(line[2:strings.Index(line, ":")])
			value := strings.TrimSpace(line[strings.Index(line, ":")+1 : len(line)])
			data[name] = value
		} else {
			limit = index
			break
		}
	}
	return data, strings.Join(lines[limit:len(lines)], "\n")
}

func processFile(filename string, printXhtml bool, article bool) string {
	source, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	}
	data, markdown := markdownData(string(source))
	xhtml := markdown2xhtml(markdown)
	if printXhtml {
		return string(xhtml)
	}
	xmlFile, err := ioutil.TempFile("/tmp", "md2xml-")
	if err != nil {
		panic(err)
	}
	defer os.Remove(xmlFile.Name())
	ioutil.WriteFile(xmlFile.Name(), xhtml, 0755)
	result := processXsl(xmlFile.Name(), data, article)
	return string(result)
}

func main() {
	xhtml := false
	article := false
	if len(os.Args) < 2 {
		fmt.Println(HELP)
		os.Exit(1)
	}
	for _, arg := range os.Args[1:] {
		if arg == "-h" || os.Args[1] == "--help" {
			fmt.Println(HELP)
			os.Exit(0)
		} else if arg == "-x" || arg == "--xhtml" {
			xhtml = true
		} else if arg == "-a" || arg == "--article" {
			article = true
		} else if arg == "-s" || arg == "--stylesheet" {
   if article {
       fmt.Println(STYLESHEET_ARTICLE)
   } else {
       fmt.Println(STYLESHEET_BLOG)
   }
		} else {
			fmt.Println(processFile(arg, xhtml, article))
		}
	}
}
<|endoftext|>


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

package math

// Nextafter32 returns the next representable float32 value after x towards y.
// Special cases:
//	Nextafter32(x, x)   = x
//      Nextafter32(NaN, y) = NaN
//      Nextafter32(x, NaN) = NaN
func Nextafter32(x, y float32) (r float32) {
	switch {
	case IsNaN(float64(x)) || IsNaN(float64(y)): // special case
		r = float32(NaN())
	case x == y:
		r = x
	case x == 0:
		r = float32(Copysign(float64(Float32frombits(1)), float64(y)))
	case (y > x) == (x > 0):
		r = Float32frombits(Float32bits(x) + 1)
	default:
		r = Float32frombits(Float32bits(x) - 1)
	}
	return
}

// Nextafter returns the next representable float64 value after x towards y.
// Special cases:
//	Nextafter64(x, x)   = x
//      Nextafter64(NaN, y) = NaN
//      Nextafter64(x, NaN) = NaN
func Nextafter(x, y float64) (r float64) {
	switch {
	case IsNaN(x) || IsNaN(y): // special case
		r = NaN()
	case x == y:
		r = x
	case x == 0:
		r = Copysign(Float64frombits(1), y)
	case (y > x) == (x > 0):
		r = Float64frombits(Float64bits(x) + 1)
	default:
		r = Float64frombits(Float64bits(x) - 1)
	}
	return
}
<commit_msg>math: be consistent in how we document special cases<commit_after>// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package math

// Nextafter32 returns the next representable float32 value after x towards y.
//
// Special cases are:
//	Nextafter32(x, x)   = x
//	Nextafter32(NaN, y) = NaN
//	Nextafter32(x, NaN) = NaN
func Nextafter32(x, y float32) (r float32) {
	switch {
	case IsNaN(float64(x)) || IsNaN(float64(y)): // special case
		r = float32(NaN())
	case x == y:
		r = x
	case x == 0:
		r = float32(Copysign(float64(Float32frombits(1)), float64(y)))
	case (y > x) == (x > 0):
		r = Float32frombits(Float32bits(x) + 1)
	default:
		r = Float32frombits(Float32bits(x) - 1)
	}
	return
}

// Nextafter returns the next representable float64 value after x towards y.
//
// Special cases are:
//	Nextafter64(x, x)   = x
//	Nextafter64(NaN, y) = NaN
//	Nextafter64(x, NaN) = NaN
func Nextafter(x, y float64) (r float64) {
	switch {
	case IsNaN(x) || IsNaN(y): // special case
		r = NaN()
	case x == y:
		r = x
	case x == 0:
		r = Copysign(Float64frombits(1), y)
	case (y > x) == (x > 0):
		r = Float64frombits(Float64bits(x) + 1)
	default:
		r = Float64frombits(Float64bits(x) - 1)
	}
	return
}
<|endoftext|>


<commit_before>/*
   Copyright The containerd Authors.

   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 cgroups

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	"golang.org/x/sys/unix"

	specs "github.com/opencontainers/runtime-spec/specs-go"
)

func NewMemory(root string) *memoryController {
	return &memoryController{
		root: filepath.Join(root, string(Memory)),
	}
}

type memoryController struct {
	root string
}

func (m *memoryController) Name() Name {
	return Memory
}

func (m *memoryController) Path(path string) string {
	return filepath.Join(m.root, path)
}

func (m *memoryController) Create(path string, resources *specs.LinuxResources) error {
	if err := os.MkdirAll(m.Path(path), defaultDirPerm); err != nil {
		return err
	}
	if resources.Memory == nil {
		return nil
	}
	if resources.Memory.Kernel != nil {
		// Check if kernel memory is enabled
		// We have to limit the kernel memory here as it won't be accounted at all
		// until a limit is set on the cgroup and limit cannot be set once the
		// cgroup has children, or if there are already tasks in the cgroup.
		for _, i := range []int64{1, -1} {
			if err := ioutil.WriteFile(
				filepath.Join(m.Path(path), "memory.kmem.limit_in_bytes"),
				[]byte(strconv.FormatInt(i, 10)),
				defaultFilePerm,
			); err != nil {
				return checkEBUSY(err)
			}
		}
	}
	return m.set(path, getMemorySettings(resources))
}

func (m *memoryController) Update(path string, resources *specs.LinuxResources) error {
	if resources.Memory == nil {
		return nil
	}
	g := func(v *int64) bool {
		return v != nil && *v > 0
	}
	settings := getMemorySettings(resources)
	if g(resources.Memory.Limit) && g(resources.Memory.Swap) {
		// if the updated swap value is larger than the current memory limit set the swap changes first
		// then set the memory limit as swap must always be larger than the current limit
		current, err := readUint(filepath.Join(m.Path(path), "memory.limit_in_bytes"))
		if err != nil {
			return err
		}
		if current < uint64(*resources.Memory.Swap) {
			settings[0], settings[1] = settings[1], settings[0]
		}
	}
	return m.set(path, settings)
}

func (m *memoryController) Stat(path string, stats *Metrics) error {
	f, err := os.Open(filepath.Join(m.Path(path), "memory.stat"))
	if err != nil {
		return err
	}
	defer f.Close()
	stats.Memory = &MemoryStat{
		Usage:     &MemoryEntry{},
		Swap:      &MemoryEntry{},
		Kernel:    &MemoryEntry{},
		KernelTCP: &MemoryEntry{},
	}
	if err := m.parseStats(f, stats.Memory); err != nil {
		return err
	}
	for _, t := range []struct {
		module string
		entry  *MemoryEntry
	}{
		{
			module: "",
			entry:  stats.Memory.Usage,
		},
		{
			module: "memsw",
			entry:  stats.Memory.Swap,
		},
		{
			module: "kmem",
			entry:  stats.Memory.Kernel,
		},
		{
			module: "kmem.tcp",
			entry:  stats.Memory.KernelTCP,
		},
	} {
		for _, tt := range []struct {
			name  string
			value *uint64
		}{
			{
				name:  "usage_in_bytes",
				value: &t.entry.Usage,
			},
			{
				name:  "max_usage_in_bytes",
				value: &t.entry.Max,
			},
			{
				name:  "failcnt",
				value: &t.entry.Failcnt,
			},
			{
				name:  "limit_in_bytes",
				value: &t.entry.Limit,
			},
		} {
			parts := []string{"memory"}
			if t.module != "" {
				parts = append(parts, t.module)
			}
			parts = append(parts, tt.name)
			v, err := readUint(filepath.Join(m.Path(path), strings.Join(parts, ".")))
			if err != nil {
				return err
			}
			*tt.value = v
		}
	}
	return nil
}

func (m *memoryController) OOMEventFD(path string) (uintptr, error) {
	root := m.Path(path)
	f, err := os.Open(filepath.Join(root, "memory.oom_control"))
	if err != nil {
		return 0, err
	}
	defer f.Close()
	fd, _, serr := unix.RawSyscall(unix.SYS_EVENTFD2, 0, unix.EFD_CLOEXEC, 0)
	if serr != 0 {
		return 0, serr
	}
	if err := writeEventFD(root, f.Fd(), fd); err != nil {
		unix.Close(int(fd))
		return 0, err
	}
	return fd, nil
}

func writeEventFD(root string, cfd, efd uintptr) error {
	f, err := os.OpenFile(filepath.Join(root, "cgroup.event_control"), os.O_WRONLY, 0)
	if err != nil {
		return err
	}
	_, err = f.WriteString(fmt.Sprintf("%d %d", efd, cfd))
	f.Close()
	return err
}

func (m *memoryController) parseStats(r io.Reader, stat *MemoryStat) error {
	var (
		raw  = make(map[string]uint64)
		sc   = bufio.NewScanner(r)
		line int
	)
	for sc.Scan() {
		if err := sc.Err(); err != nil {
			return err
		}
		key, v, err := parseKV(sc.Text())
		if err != nil {
			return fmt.Errorf("%d: %v", line, err)
		}
		raw[key] = v
		line++
	}
	stat.Cache = raw["cache"]
	stat.RSS = raw["rss"]
	stat.RSSHuge = raw["rss_huge"]
	stat.MappedFile = raw["mapped_file"]
	stat.Dirty = raw["dirty"]
	stat.Writeback = raw["writeback"]
	stat.PgPgIn = raw["pgpgin"]
	stat.PgPgOut = raw["pgpgout"]
	stat.PgFault = raw["pgfault"]
	stat.PgMajFault = raw["pgmajfault"]
	stat.InactiveAnon = raw["inactive_anon"]
	stat.ActiveAnon = raw["active_anon"]
	stat.InactiveFile = raw["inactive_file"]
	stat.ActiveFile = raw["active_file"]
	stat.Unevictable = raw["unevictable"]
	stat.HierarchicalMemoryLimit = raw["hierarchical_memory_limit"]
	stat.HierarchicalSwapLimit = raw["hierarchical_memsw_limit"]
	stat.TotalCache = raw["total_cache"]
	stat.TotalRSS = raw["total_rss"]
	stat.TotalRSSHuge = raw["total_rss_huge"]
	stat.TotalMappedFile = raw["total_mapped_file"]
	stat.TotalDirty = raw["total_dirty"]
	stat.TotalWriteback = raw["total_writeback"]
	stat.TotalPgPgIn = raw["total_pgpgin"]
	stat.TotalPgPgOut = raw["total_pgpgout"]
	stat.TotalPgFault = raw["total_pgfault"]
	stat.TotalPgMajFault = raw["total_pgmajfault"]
	stat.TotalInactiveAnon = raw["total_inactive_anon"]
	stat.TotalActiveAnon = raw["total_active_anon"]
	stat.TotalInactiveFile = raw["total_inactive_file"]
	stat.TotalActiveFile = raw["total_active_file"]
	stat.TotalUnevictable = raw["total_unevictable"]
	return nil
}

func (m *memoryController) set(path string, settings []memorySettings) error {
	for _, t := range settings {
		if t.value != nil {
			if err := ioutil.WriteFile(
				filepath.Join(m.Path(path), fmt.Sprintf("memory.%s", t.name)),
				[]byte(strconv.FormatInt(*t.value, 10)),
				defaultFilePerm,
			); err != nil {
				return err
			}
		}
	}
	return nil
}

type memorySettings struct {
	name  string
	value *int64
}

func getMemorySettings(resources *specs.LinuxResources) []memorySettings {
	mem := resources.Memory
	var swappiness *int64
	if mem.Swappiness != nil {
		v := int64(*mem.Swappiness)
		swappiness = &v
	}
	return []memorySettings{
		{
			name:  "limit_in_bytes",
			value: mem.Limit,
		},
		{
			name:  "memsw.limit_in_bytes",
			value: mem.Swap,
		},
		{
			name:  "kmem.limit_in_bytes",
			value: mem.Kernel,
		},
		{
			name:  "kmem.tcp.limit_in_bytes",
			value: mem.KernelTCP,
		},
		{
			name:  "oom_control",
			value: getOomControlValue(mem),
		},
		{
			name:  "swappiness",
			value: swappiness,
		},
	}
}

func checkEBUSY(err error) error {
	if pathErr, ok := err.(*os.PathError); ok {
		if errNo, ok := pathErr.Err.(syscall.Errno); ok {
			if errNo == unix.EBUSY {
				return fmt.Errorf(
					"failed to set memory.kmem.limit_in_bytes, because either tasks have already joined this cgroup or it has children")
			}
		}
	}
	return err
}

func getOomControlValue(mem *specs.LinuxMemory) *int64 {
	if mem.DisableOOMKiller != nil && *mem.DisableOOMKiller {
		i := int64(1)
		return &i
	}
	return nil
}
<commit_msg> fixed an issue with invalid soft memory limits<commit_after>/*
   Copyright The containerd Authors.

   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 cgroups

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	"golang.org/x/sys/unix"

	specs "github.com/opencontainers/runtime-spec/specs-go"
)

func NewMemory(root string) *memoryController {
	return &memoryController{
		root: filepath.Join(root, string(Memory)),
	}
}

type memoryController struct {
	root string
}

func (m *memoryController) Name() Name {
	return Memory
}

func (m *memoryController) Path(path string) string {
	return filepath.Join(m.root, path)
}

func (m *memoryController) Create(path string, resources *specs.LinuxResources) error {
	if err := os.MkdirAll(m.Path(path), defaultDirPerm); err != nil {
		return err
	}
	if resources.Memory == nil {
		return nil
	}
	if resources.Memory.Kernel != nil {
		// Check if kernel memory is enabled
		// We have to limit the kernel memory here as it won't be accounted at all
		// until a limit is set on the cgroup and limit cannot be set once the
		// cgroup has children, or if there are already tasks in the cgroup.
		for _, i := range []int64{1, -1} {
			if err := ioutil.WriteFile(
				filepath.Join(m.Path(path), "memory.kmem.limit_in_bytes"),
				[]byte(strconv.FormatInt(i, 10)),
				defaultFilePerm,
			); err != nil {
				return checkEBUSY(err)
			}
		}
	}
	return m.set(path, getMemorySettings(resources))
}

func (m *memoryController) Update(path string, resources *specs.LinuxResources) error {
	if resources.Memory == nil {
		return nil
	}
	g := func(v *int64) bool {
		return v != nil && *v > 0
	}
	settings := getMemorySettings(resources)
	if g(resources.Memory.Limit) && g(resources.Memory.Swap) {
		// if the updated swap value is larger than the current memory limit set the swap changes first
		// then set the memory limit as swap must always be larger than the current limit
		current, err := readUint(filepath.Join(m.Path(path), "memory.limit_in_bytes"))
		if err != nil {
			return err
		}
		if current < uint64(*resources.Memory.Swap) {
			settings[0], settings[1] = settings[1], settings[0]
		}
	}
	return m.set(path, settings)
}

func (m *memoryController) Stat(path string, stats *Metrics) error {
	f, err := os.Open(filepath.Join(m.Path(path), "memory.stat"))
	if err != nil {
		return err
	}
	defer f.Close()
	stats.Memory = &MemoryStat{
		Usage:     &MemoryEntry{},
		Swap:      &MemoryEntry{},
		Kernel:    &MemoryEntry{},
		KernelTCP: &MemoryEntry{},
	}
	if err := m.parseStats(f, stats.Memory); err != nil {
		return err
	}
	for _, t := range []struct {
		module string
		entry  *MemoryEntry
	}{
		{
			module: "",
			entry:  stats.Memory.Usage,
		},
		{
			module: "memsw",
			entry:  stats.Memory.Swap,
		},
		{
			module: "kmem",
			entry:  stats.Memory.Kernel,
		},
		{
			module: "kmem.tcp",
			entry:  stats.Memory.KernelTCP,
		},
	} {
		for _, tt := range []struct {
			name  string
			value *uint64
		}{
			{
				name:  "usage_in_bytes",
				value: &t.entry.Usage,
			},
			{
				name:  "max_usage_in_bytes",
				value: &t.entry.Max,
			},
			{
				name:  "failcnt",
				value: &t.entry.Failcnt,
			},
			{
				name:  "limit_in_bytes",
				value: &t.entry.Limit,
			},
		} {
			parts := []string{"memory"}
			if t.module != "" {
				parts = append(parts, t.module)
			}
			parts = append(parts, tt.name)
			v, err := readUint(filepath.Join(m.Path(path), strings.Join(parts, ".")))
			if err != nil {
				return err
			}
			*tt.value = v
		}
	}
	return nil
}

func (m *memoryController) OOMEventFD(path string) (uintptr, error) {
	root := m.Path(path)
	f, err := os.Open(filepath.Join(root, "memory.oom_control"))
	if err != nil {
		return 0, err
	}
	defer f.Close()
	fd, _, serr := unix.RawSyscall(unix.SYS_EVENTFD2, 0, unix.EFD_CLOEXEC, 0)
	if serr != 0 {
		return 0, serr
	}
	if err := writeEventFD(root, f.Fd(), fd); err != nil {
		unix.Close(int(fd))
		return 0, err
	}
	return fd, nil
}

func writeEventFD(root string, cfd, efd uintptr) error {
	f, err := os.OpenFile(filepath.Join(root, "cgroup.event_control"), os.O_WRONLY, 0)
	if err != nil {
		return err
	}
	_, err = f.WriteString(fmt.Sprintf("%d %d", efd, cfd))
	f.Close()
	return err
}

func (m *memoryController) parseStats(r io.Reader, stat *MemoryStat) error {
	var (
		raw  = make(map[string]uint64)
		sc   = bufio.NewScanner(r)
		line int
	)
	for sc.Scan() {
		if err := sc.Err(); err != nil {
			return err
		}
		key, v, err := parseKV(sc.Text())
		if err != nil {
			return fmt.Errorf("%d: %v", line, err)
		}
		raw[key] = v
		line++
	}
	stat.Cache = raw["cache"]
	stat.RSS = raw["rss"]
	stat.RSSHuge = raw["rss_huge"]
	stat.MappedFile = raw["mapped_file"]
	stat.Dirty = raw["dirty"]
	stat.Writeback = raw["writeback"]
	stat.PgPgIn = raw["pgpgin"]
	stat.PgPgOut = raw["pgpgout"]
	stat.PgFault = raw["pgfault"]
	stat.PgMajFault = raw["pgmajfault"]
	stat.InactiveAnon = raw["inactive_anon"]
	stat.ActiveAnon = raw["active_anon"]
	stat.InactiveFile = raw["inactive_file"]
	stat.ActiveFile = raw["active_file"]
	stat.Unevictable = raw["unevictable"]
	stat.HierarchicalMemoryLimit = raw["hierarchical_memory_limit"]
	stat.HierarchicalSwapLimit = raw["hierarchical_memsw_limit"]
	stat.TotalCache = raw["total_cache"]
	stat.TotalRSS = raw["total_rss"]
	stat.TotalRSSHuge = raw["total_rss_huge"]
	stat.TotalMappedFile = raw["total_mapped_file"]
	stat.TotalDirty = raw["total_dirty"]
	stat.TotalWriteback = raw["total_writeback"]
	stat.TotalPgPgIn = raw["total_pgpgin"]
	stat.TotalPgPgOut = raw["total_pgpgout"]
	stat.TotalPgFault = raw["total_pgfault"]
	stat.TotalPgMajFault = raw["total_pgmajfault"]
	stat.TotalInactiveAnon = raw["total_inactive_anon"]
	stat.TotalActiveAnon = raw["total_active_anon"]
	stat.TotalInactiveFile = raw["total_inactive_file"]
	stat.TotalActiveFile = raw["total_active_file"]
	stat.TotalUnevictable = raw["total_unevictable"]
	return nil
}

func (m *memoryController) set(path string, settings []memorySettings) error {
	for _, t := range settings {
		if t.value != nil {
			if err := ioutil.WriteFile(
				filepath.Join(m.Path(path), fmt.Sprintf("memory.%s", t.name)),
				[]byte(strconv.FormatInt(*t.value, 10)),
				defaultFilePerm,
			); err != nil {
				return err
			}
		}
	}
	return nil
}

type memorySettings struct {
	name  string
	value *int64
}

func getMemorySettings(resources *specs.LinuxResources) []memorySettings {
	mem := resources.Memory
	var swappiness *int64
	if mem.Swappiness != nil {
		v := int64(*mem.Swappiness)
		swappiness = &v
	}
	return []memorySettings{
		{
			name:  "limit_in_bytes",
			value: mem.Limit,
		},
		{
			name: "soft_limit_in_bytes",
			value: mem.Reservation,
		},
		{
			name:  "memsw.limit_in_bytes",
			value: mem.Swap,
		},
		{
			name:  "kmem.limit_in_bytes",
			value: mem.Kernel,
		},
		{
			name:  "kmem.tcp.limit_in_bytes",
			value: mem.KernelTCP,
		},
		{
			name:  "oom_control",
			value: getOomControlValue(mem),
		},
		{
			name:  "swappiness",
			value: swappiness,
		},
	}
}

func checkEBUSY(err error) error {
	if pathErr, ok := err.(*os.PathError); ok {
		if errNo, ok := pathErr.Err.(syscall.Errno); ok {
			if errNo == unix.EBUSY {
				return fmt.Errorf(
					"failed to set memory.kmem.limit_in_bytes, because either tasks have already joined this cgroup or it has children")
			}
		}
	}
	return err
}

func getOomControlValue(mem *specs.LinuxMemory) *int64 {
	if mem.DisableOOMKiller != nil && *mem.DisableOOMKiller {
		i := int64(1)
		return &i
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 Hiram Jerónimo Pérez worg{at}linuxmail[dot]org

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package merger is an utility to merge structs of the same type
package merger

import (
	"errors"
	"reflect"
	"strings"
)

var (
	// ErrDistinctType occurs when trying to merge structs of distinct type
	ErrDistinctType = errors.New(`dst and src must be of the same type`)
	// ErrNoPtr occurs when no struct pointer is sent as destination
	ErrNoPtr = errors.New(`src must be a pointer to a struct`)
	// ErrNilArguments occurs on receiving nil as arguments
	ErrNilArguments = errors.New(`no nil values allowed`)
	// ErrUnknown occurs if the type can't be merged
	ErrUnknown = errors.New(`could not merge`)
)

// Merge sets zero values from dst to non zero values of src
// accepts two structs of the same type as arguments
// dst must be a pointer to a struct
func Merge(dst, src interface{}) error {
	if dst == nil || src == nil {
		return ErrNilArguments
	}

	if !isStructPtr(dst) {
		return ErrNoPtr
	}

	if !typesMatch(src, dst) {
		return ErrDistinctType
	}

	vSrc := getValue(src)
	vDst := getValue(dst)

	for i := 0; i < vSrc.NumField(); i++ {
		df := vDst.Field(i)
		sf := vSrc.Field(i)
		if err := merge(df, sf); err != nil {
			return err
		}
	}

	return nil
}

// merge merges two reflect values based upon their kinds
func merge(dst, src reflect.Value) (err error) {
	if dst.CanSet() && !isZero(src) {
		switch dst.Kind() {
		case reflect.Int, reflect.Int64, reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
			if isZero(dst) {
				switch dst.Kind() {
				case reflect.Int, reflect.Int64:
					dst.SetInt(src.Int())
				case reflect.Float32, reflect.Float64:
					dst.SetFloat(src.Float())
				case reflect.String:
					dst.SetString(src.String())
				case reflect.Bool:
					dst.SetBool(src.Bool())
				}
			}
		case reflect.Slice:
			dst.Set(mergeSlice(dst, src))
		case reflect.Struct:
			// handle structs with IsZero method
			if fnZero, ok := dst.Type().MethodByName(`IsZero`); ok {
				res := fnZero.Func.Call([]reflect.Value{dst})
				if len(res) > 0 {
					if v, isok := res[0].Interface().(bool); isok && v {
						dst.Set(src)
					}
				}
			}

			for i := 0; i < src.NumField(); i++ {
				df := dst.Field(i)
				sf := src.Field(i)
				if err := merge(df, sf); err != nil {
					return err
				}
			}
		case reflect.Map:
			dst.Set(mergeMap(dst, src))
		case reflect.Ptr:
			// defer pointers
			if !dst.IsNil() {
				dst = getValue(dst)
			} else {
				dst.Set(src)
				break
			}
			if src.CanAddr() && src.IsNil() {
				src = getValue(src)
				if err := merge(dst, src); err != nil {
					return err
				}
			}
		default:
			return ErrUnknown
		}
	}
	return
}

// mergeSlice merges two slices only if dst slice fields are zero and
// src fields are nonzero
func mergeSlice(dst, src reflect.Value) (res reflect.Value) {
	for i := 0; i < src.Len(); i++ {
		if i >= dst.Len() {
			dst = reflect.Append(dst, src.Index(i))
		}
		if err := merge(dst.Index(i), src.Index(i)); err != nil {
			res = dst
			return
		}
	}

	res = dst
	return
}

// mergeMap traverses a map and merges the nonzero values of
// src into dst
func mergeMap(dst, src reflect.Value) (res reflect.Value) {
	if dst.IsNil() {
		dst = reflect.MakeMap(dst.Type())
	}

	for _, k := range src.MapKeys() {
		vs := src.MapIndex(k)
		vd := dst.MapIndex(k)
		if !vd.IsValid() && isZero(vd) && !isZero(vs) {
			dst.SetMapIndex(k, vs)
		}
	}

	return dst
}

// typesMatch typechecks two interfaces
func typesMatch(a, b interface{}) bool {
	return strings.TrimPrefix(reflect.TypeOf(a).String(), "*") == strings.TrimPrefix(reflect.TypeOf(b).String(), "*")
}

// getValue returns a reflect.Value from an interface
// deferring pointers if needed
func getValue(t interface{}) (rslt reflect.Value) {
	rslt = reflect.ValueOf(t)

	for rslt.Kind() == reflect.Ptr && !rslt.IsNil() {
		rslt = rslt.Elem()
	}

	return
}

// isStructPtr determines if a value is a struct pointer
func isStructPtr(v interface{}) bool {
	t := reflect.TypeOf(v)
	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}

// isZero is mostly stolen from encoding/json package's isEmptyValue function
// determines if a value has the zero value of its type
func isZero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
		return v.Len() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Interface, reflect.Ptr, reflect.Func:
		return v.IsNil()
	case reflect.Struct:
		zero := reflect.Zero(v.Type()).Interface()
		return reflect.DeepEqual(v.Interface(), zero)
	default:
		zero := reflect.Zero(v.Type())
		return v.Interface() == zero.Interface()
	}

}
<commit_msg>include sized ints and uint in base type comparission, prevent panic on isZero<commit_after>// Copyright (c) 2015 Hiram Jerónimo Pérez https://worg.xyz

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package merger is an utility to merge structs of the same type
package merger

import (
	"errors"
	"reflect"
	"strings"
)

var (
	// ErrDistinctType occurs when trying to merge structs of distinct type
	ErrDistinctType = errors.New(`dst and src must be of the same type`)
	// ErrNoPtr occurs when no struct pointer is sent as destination
	ErrNoPtr = errors.New(`dst must be a pointer to a struct`)
	// ErrNilArguments occurs on receiving nil as arguments
	ErrNilArguments = errors.New(`no nil values allowed`)
	// ErrUnknown occurs if the type can't be merged
	ErrUnknown = errors.New(`could not merge`)
)

// Merge sets zero values from dst to non zero values of src
// accepts two structs of the same type as arguments
// dst must be a pointer to a struct
func Merge(dst, src interface{}) error {
	if dst == nil || src == nil {
		return ErrNilArguments
	}

	if !isStructPtr(dst) {
		return ErrNoPtr
	}

	if !typesMatch(src, dst) {
		return ErrDistinctType
	}

	vSrc := getValue(src)
	vDst := getValue(dst)

	for i := 0; i < vSrc.NumField(); i++ {
		df := vDst.Field(i)
		sf := vSrc.Field(i)
		if err := merge(df, sf); err != nil {
			return err
		}
	}

	return nil
}

// merge merges two reflect values based upon their kinds
func merge(dst, src reflect.Value) (err error) {
	if dst.CanSet() && !isZero(src) {
		switch dst.Kind() {
		// base types
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
			reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
			if isZero(dst) {
				switch dst.Kind() {
				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
					dst.SetInt(src.Int())
				case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
					dst.SetUint(src.Uint())
				case reflect.Float32, reflect.Float64:
					dst.SetFloat(src.Float())
				case reflect.String:
					dst.SetString(src.String())
				case reflect.Bool:
					dst.SetBool(src.Bool())
				}
			}
		case reflect.Slice:
			dst.Set(mergeSlice(dst, src))
		case reflect.Struct:
			// handle structs with IsZero method [ie time.Time]
			if fnZero, ok := dst.Type().MethodByName(`IsZero`); ok {
				res := fnZero.Func.Call([]reflect.Value{dst})
				if len(res) > 0 {
					if v, isok := res[0].Interface().(bool); isok && v {
						dst.Set(src)
					}
				}
			}

			for i := 0; i < src.NumField(); i++ {
				df := dst.Field(i)
				sf := src.Field(i)
				if err := merge(df, sf); err != nil {
					return err
				}
			}
		case reflect.Map:
			dst.Set(mergeMap(dst, src))
		case reflect.Ptr:
			// defer pointers
			if !dst.IsNil() {
				dst = getValue(dst)
			} else {
				dst.Set(src)
				break
			}
			if src.CanAddr() && src.IsNil() {
				src = getValue(src)
				if err := merge(dst, src); err != nil {
					return err
				}
			}
		default:
			return ErrUnknown
		}
	}
	return
}

// mergeSlice merges two slices only if dst slice fields are zero and
// src fields are nonzero
func mergeSlice(dst, src reflect.Value) (res reflect.Value) {
	for i := 0; i < src.Len(); i++ {
		if i >= dst.Len() {
			dst = reflect.Append(dst, src.Index(i))
		}
		if err := merge(dst.Index(i), src.Index(i)); err != nil {
			res = dst
			return
		}
	}

	res = dst
	return
}

// mergeMap traverses a map and merges the nonzero values of
// src into dst
func mergeMap(dst, src reflect.Value) (res reflect.Value) {
	if dst.IsNil() {
		dst = reflect.MakeMap(dst.Type())
	}

	for _, k := range src.MapKeys() {
		vs := src.MapIndex(k)
		vd := dst.MapIndex(k)
		if !vd.IsValid() && isZero(vd) && !isZero(vs) {
			dst.SetMapIndex(k, vs)
		}
	}

	return dst
}

// typesMatch typechecks two interfaces
func typesMatch(a, b interface{}) bool {
	return strings.TrimPrefix(reflect.TypeOf(a).String(), "*") == strings.TrimPrefix(reflect.TypeOf(b).String(), "*")
}

// getValue returns a reflect.Value from an interface
// deferring pointers if needed
func getValue(t interface{}) (rslt reflect.Value) {
	rslt = reflect.ValueOf(t)

	for rslt.Kind() == reflect.Ptr && !rslt.IsNil() {
		rslt = rslt.Elem()
	}

	return
}

// isStructPtr determines if a value is a struct pointer
func isStructPtr(v interface{}) bool {
	t := reflect.TypeOf(v)
	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}

// isZero is mostly stolen from encoding/json package's isEmptyValue function
// determines if a value has the zero value of its type
func isZero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
		return v.Len() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Interface, reflect.Ptr, reflect.Func:
		return v.IsNil()
	case reflect.Struct:
		zero := reflect.Zero(v.Type()).Interface()
		return reflect.DeepEqual(v.Interface(), zero)
	default:
		if !v.IsValid() {
			return true
		}

		zero := reflect.Zero(v.Type())
		return v.Interface() == zero.Interface()
	}

}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

//Structs used when parsing a song file
type Song struct {
	Filename          string
	Title             string
	Section           string
	StanzaCount       int
	SongNumber        int
	Stanzas           []Stanza
	ShowStanzaNumbers bool
	BeforeComments    []string
	AfterComments     []string
	UseLiberationFont bool
	transpose         int
}

func ParseSongFile(filename string, transpose int) (*Song, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	filename = filepath.Base(filename)

	var (
		//Song variables
		stanzas         []Stanza
		stanza_count    = 1
		title           = ""
		section         = ""
		scanner         = bufio.NewScanner(file)
		song_stanza_num = true
		//Stanza variables
		lines           []Line
		is_chorus       = false
		stanza_show_num = true
		useLibFont      = false
	)

	//We need to handle /r only as Mac OS <= 9 uses this as end-of-line marker
	//This is based on bufio/scan.go ScanLines function
	split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		if atEOF && len(data) == 0 {
			return 0, nil, nil
		}
		i := bytes.IndexByte(data, '\n')

		if i < 0 {
			i = bytes.IndexByte(data, '\r')
		}

		ind := 0
		if i > 0 && data[i-1] == '\r' {
			ind = -1
		}

		if i >= 0 {

			// We have a full newline-terminated line.
			return i + 1, data[0 : i+ind], nil
		}
		// If we're at EOF, we have a final, non-terminated line. Return it.
		if atEOF {
			return len(data), data[0 : len(data)+ind], nil
		}
		// Request more data.
		return 0, nil, nil
	}
	scanner.Split(split)

	stanza_before_comments := make([]string, 0)
	stanza_after_comments := make([]string, 0)
	song_before_comments := make([]string, 0)
	song_after_comments := make([]string, 0)

	chord_regex := regexp.MustCompile("\\[.*?\\]")
	bad_command_regex := regexp.MustCompile("\\{|\\}")
	song_started := false

	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, "ā") {
			useLibFont = true
		}
		echo := -1

		//is this a command
		if strings.HasPrefix(line, "{") {
			command := strings.ToLower(line)
			if strings.HasPrefix(command, "{start_of_chorus}") {
				is_chorus = true
				continue
			} else if strings.HasPrefix(command, "{end_of_chorus}") {
				continue
			} else if strings.HasPrefix(command, "{title:") {
				title = parseCommand(line)
				continue
			} else if strings.HasPrefix(command, "{section:") {
				section = parseCommand(line)
				continue
			} else if strings.HasPrefix(command, "{comments:") {
				if !song_started {
					song_before_comments = append(song_before_comments, parseCommand(line))
				} else {
					if len(lines) > 0 {
						stanza_after_comments = append(stanza_after_comments, parseCommand(line))
					} else {
						stanza_before_comments = append(stanza_before_comments, parseCommand(line))
					}
				}
				continue
			} else if strings.HasPrefix(command, "{no_number") {
				if !song_started {
					song_stanza_num = false
				} else {
					stanza_show_num = false
				}
				continue
			} else if i := strings.Index(command, "{echo"); i >= 0 {
				//fall through
			} else {
				fmt.Println(filename)
				fmt.Printf("Unknown tag: %s\n", line)
				continue
			}
		}

		//blank line separates stanzas
		if len(line) == 0 {
			song_started = true

			if len(lines) > 0 {
				stanzas = append(stanzas, *&Stanza{
					Lines:          lines,
					Number:         stanza_count,
					IsChorus:       is_chorus,
					ShowNumber:     stanza_show_num,
					BeforeComments: stanza_before_comments,
					AfterComments:  stanza_after_comments})

				//Choruses do not get stanza numbers
				if !is_chorus {
					stanza_count++
				}

				is_chorus = false
				stanza_show_num = true
				lines = make([]Line, 0)
				stanza_before_comments = make([]string, 0)
				stanza_after_comments = make([]string, 0)
			}
		} else {
			song_started = true
			//check for echo marker
			if i := strings.Index(line, "{echo:"); i >= 0 {
				end := strings.Index(line, "}")

				if end < 1 {
					fmt.Printf("Bad echo tag: %s\n", line)
				} else {
					//to work out the index we have to remove the chords
					clean := chord_regex.ReplaceAllString(line, "")
					echo = strings.Index(clean, "{echo:")

					echo_txt := line[i+len("{echo:") : end]
					echo_txt = strings.TrimSpace(echo_txt)

					//remove command from text
					tmp := line[0:i]
					tmp += echo_txt

					line = tmp
				}
			}

			chords_pos := chord_regex.FindAllStringIndex(line, -1)
			chord_len := 0
			chords := make([]Chord, 0)

			for _, pos := range chords_pos {
				chord_text := line[pos[0]+1 : pos[1]-1]
				chord_len += pos[1] - pos[0]
				position := pos[1] - chord_len

				chords = append(chords, Chord{text: chord_text, Position: position, Transpose: transpose})
			}

			//remove all chord markers
			line = chord_regex.ReplaceAllString(line, "")
			lines = append(lines, Line{Text: line, Chords: chords, EchoIndex: echo})

			//check for bad commands
			for _, pos := range bad_command_regex.FindAllStringIndex(line, -1) {
				fmt.Println(filename)
				fmt.Println(line)
				for i := 0; i < pos[0]; i++ {
					fmt.Print(" ")
				}
				fmt.Println("^")
			}

			//Default title is first line text
			if len(title) == 0 {
				//Replace all quotation marks in title
				title = line
				re := regexp.MustCompile("[\"“”]")
				title = re.ReplaceAllString(title, "")
				title = strings.TrimSpace(title)

				//trim any trailing/leading puncutation
				start_reg := regexp.MustCompile("^[a-zA-Z0-9]")
				end_reg := regexp.MustCompile("[a-zA-Z0-9]$")
				start_done := false
				end_done := false

				for {
					if len(title) == 1 {
						break
					}

					pos := start_reg.FindAllStringIndex(title, 1)

					//No match, title has punctuation at the start
					if !start_done && len(pos) == 0 {
						title = title[1:]
					} else {
						start_done = true
					}

					pos = end_reg.FindAllStringIndex(title, 1)

					if !end_done && len(pos) == 0 {
						title = title[0 : len(title)-1]
					} else {
						end_done = true
					}

					if start_done && end_done {
						break
					}
				}
			}
		}
	}

	//check for last stanza
	if len(lines) > 0 {
		stanzas = append(stanzas, Stanza{
			Lines:          lines,
			Number:         stanza_count,
			IsChorus:       is_chorus,
			ShowNumber:     stanza_show_num,
			BeforeComments: stanza_before_comments,
			AfterComments:  stanza_after_comments})
	} else if len(stanza_before_comments) > 0 {
		song_after_comments = stanza_before_comments
	}

	return &Song{
			Filename:          filename,
			Title:             title,
			Section:           section,
			StanzaCount:       0,
			SongNumber:        -1,
			ShowStanzaNumbers: song_stanza_num,
			Stanzas:           stanzas,
			BeforeComments:    song_before_comments,
			AfterComments:     song_after_comments,
			UseLiberationFont: useLibFont,
			transpose:         transpose},
		nil
}

func parseCommand(command string) string {
	return strings.TrimSpace(command[strings.Index(command, ":")+1 : strings.Index(command, "}")])
}

func (song Song) GetTranspose() int {
	return song.transpose
}

func (song *Song) Transpose(change_by int) {
	song.transpose = change_by

	for _, s := range song.Stanzas {
		for _, l := range s.Lines {
			//index range here because we are modifying the Chord
			for i := range l.Chords {
				l.Chords[i].Transpose = change_by
			}
		}
	}
}

func (song Song) String() string {
	var buffer bytes.Buffer

	if len(song.Section) > 0 {
		buffer.WriteString(fmt.Sprintf("Section: %s\n", song.Section))
	}

	if len(song.BeforeComments) > 0 {
		buffer.WriteString(fmt.Sprintf("/%s/\n", song.BeforeComments))
	}

	for _, s := range song.Stanzas {
		if s.IsChorus {
			buffer.WriteString("---CHORUS--\n")
		} else {
			buffer.WriteString(fmt.Sprintf("STANZA: %d\n", s.Number))
		}
		for _, l := range s.Lines {
			buffer.WriteString(fmt.Sprintf("%s\n", l.Text))
		}
		if s.IsChorus {
			buffer.WriteString(fmt.Sprintln("---END CHORUS--"))
		}

		buffer.WriteString("\n")
	}

	return buffer.String()
}

func (song Song) HasBeforeComments() bool {
	return len(song.BeforeComments) > 0
}

func (song Song) Link() string {
	return song.Filename[0 : len(song.Filename)-5]
}
<commit_msg>Fix Song styling and add comments<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

//Song represents an invididual song read from a .song file
type Song struct {
	Filename          string
	Title             string
	Section           string
	StanzaCount       int
	SongNumber        int
	Stanzas           []Stanza
	ShowStanzaNumbers bool
	BeforeComments    []string
	AfterComments     []string
	UseLiberationFont bool
	transpose         int
}

//ParseSongFile attempts to read a Song from the given filename.
//Applying any transposition to the Chords.
//Returns the newly created Song, or error on failure.
func ParseSongFile(filename string, transpose int) (*Song, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	filename = filepath.Base(filename)

	var (
		//Song variables
		stanzas       []Stanza
		stanzaCount   = 1
		title         = ""
		section       = ""
		scanner       = bufio.NewScanner(file)
		songStanzaNum = true
		//Stanza variables
		lines         []Line
		isChorus      = false
		stanzaShowNum = true
		useLibFont    = false
	)

	//We need to handle /r only as Mac OS <= 9 uses this as end-of-line marker
	//This is based on bufio/scan.go ScanLines function
	split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		if atEOF && len(data) == 0 {
			return 0, nil, nil
		}
		i := bytes.IndexByte(data, '\n')

		if i < 0 {
			i = bytes.IndexByte(data, '\r')
		}

		ind := 0
		if i > 0 && data[i-1] == '\r' {
			ind = -1
		}

		if i >= 0 {

			// We have a full newline-terminated line.
			return i + 1, data[0 : i+ind], nil
		}
		// If we're at EOF, we have a final, non-terminated line. Return it.
		if atEOF {
			return len(data), data[0 : len(data)+ind], nil
		}
		// Request more data.
		return 0, nil, nil
	}
	scanner.Split(split)

	stanzaBeforeComments := make([]string, 0)
	stanzaAfterComments := make([]string, 0)
	songBeforeComments := make([]string, 0)
	songAfterComments := make([]string, 0)

	chordRegex := regexp.MustCompile("\\[.*?\\]")
	badCommandRegex := regexp.MustCompile("\\{|\\}")
	songStarted := false

	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, "ā") {
			useLibFont = true
		}
		echo := -1

		//is this a command
		if strings.HasPrefix(line, "{") {
			command := strings.ToLower(line)
			if strings.HasPrefix(command, "{start_of_chorus}") {
				isChorus = true
				continue
			} else if strings.HasPrefix(command, "{end_of_chorus}") {
				continue
			} else if strings.HasPrefix(command, "{title:") {
				title = parseCommand(line)
				continue
			} else if strings.HasPrefix(command, "{section:") {
				section = parseCommand(line)
				continue
			} else if strings.HasPrefix(command, "{comments:") {
				if !songStarted {
					songBeforeComments = append(songBeforeComments, parseCommand(line))
				} else {
					if len(lines) > 0 {
						stanzaAfterComments = append(stanzaAfterComments, parseCommand(line))
					} else {
						stanzaBeforeComments = append(stanzaBeforeComments, parseCommand(line))
					}
				}
				continue
			} else if strings.HasPrefix(command, "{no_number") {
				if !songStarted {
					songStanzaNum = false
				} else {
					stanzaShowNum = false
				}
				continue
			} else if i := strings.Index(command, "{echo"); i >= 0 {
				//fall through
			} else {
				fmt.Println(filename)
				fmt.Printf("Unknown tag: %s\n", line)
				continue
			}
		}

		//blank line separates stanzas
		if len(line) == 0 {
			songStarted = true

			if len(lines) > 0 {
				stanzas = append(stanzas, *&Stanza{
					Lines:          lines,
					Number:         stanzaCount,
					IsChorus:       isChorus,
					ShowNumber:     stanzaShowNum,
					BeforeComments: stanzaBeforeComments,
					AfterComments:  stanzaAfterComments})

				//Choruses do not get stanza numbers
				if !isChorus {
					stanzaCount++
				}

				isChorus = false
				stanzaShowNum = true
				lines = make([]Line, 0)
				stanzaBeforeComments = make([]string, 0)
				stanzaAfterComments = make([]string, 0)
			}
		} else {
			songStarted = true
			//check for echo marker
			if i := strings.Index(line, "{echo:"); i >= 0 {
				end := strings.Index(line, "}")

				if end < 1 {
					fmt.Printf("Bad echo tag: %s\n", line)
				} else {
					//to work out the index we have to remove the chords
					clean := chordRegex.ReplaceAllString(line, "")
					echo = strings.Index(clean, "{echo:")

					echoTxt := line[i+len("{echo:") : end]
					echoTxt = strings.TrimSpace(echoTxt)

					//remove command from text
					tmp := line[0:i]
					tmp += echoTxt

					line = tmp
				}
			}

			chordsPos := chordRegex.FindAllStringIndex(line, -1)
			chordLen := 0
			chords := make([]Chord, 0)

			for _, pos := range chordsPos {
				chordText := line[pos[0]+1 : pos[1]-1]
				chordLen += pos[1] - pos[0]
				position := pos[1] - chordLen

				chords = append(chords, Chord{text: chordText, Position: position, Transpose: transpose})
			}

			//remove all chord markers
			line = chordRegex.ReplaceAllString(line, "")
			lines = append(lines, Line{Text: line, Chords: chords, EchoIndex: echo})

			//check for bad commands
			for _, pos := range badCommandRegex.FindAllStringIndex(line, -1) {
				fmt.Println(filename)
				fmt.Println(line)
				for i := 0; i < pos[0]; i++ {
					fmt.Print(" ")
				}
				fmt.Println("^")
			}

			//Default title is first line text
			if len(title) == 0 {
				//Replace all quotation marks in title
				title = line
				re := regexp.MustCompile("[\"“”]")
				title = re.ReplaceAllString(title, "")
				title = strings.TrimSpace(title)

				//trim any trailing/leading puncutation
				startReg := regexp.MustCompile("^[a-zA-Z0-9]")
				endReg := regexp.MustCompile("[a-zA-Z0-9]$")
				startDone := false
				endDone := false

				for {
					if len(title) == 1 {
						break
					}

					pos := startReg.FindAllStringIndex(title, 1)

					//No match, title has punctuation at the start
					if !startDone && len(pos) == 0 {
						title = title[1:]
					} else {
						startDone = true
					}

					pos = endReg.FindAllStringIndex(title, 1)

					if !endDone && len(pos) == 0 {
						title = title[0 : len(title)-1]
					} else {
						endDone = true
					}

					if startDone && endDone {
						break
					}
				}
			}
		}
	}

	//check for last stanza
	if len(lines) > 0 {
		stanzas = append(stanzas, Stanza{
			Lines:          lines,
			Number:         stanzaCount,
			IsChorus:       isChorus,
			ShowNumber:     stanzaShowNum,
			BeforeComments: stanzaBeforeComments,
			AfterComments:  stanzaAfterComments})
	} else if len(stanzaBeforeComments) > 0 {
		songAfterComments = stanzaBeforeComments
	}

	return &Song{
			Filename:          filename,
			Title:             title,
			Section:           section,
			StanzaCount:       0,
			SongNumber:        -1,
			ShowStanzaNumbers: songStanzaNum,
			Stanzas:           stanzas,
			BeforeComments:    songBeforeComments,
			AfterComments:     songAfterComments,
			UseLiberationFont: useLibFont,
			transpose:         transpose},
		nil
}

//parseCommand parses a given command string and strips off the framing characters.
//i.e. given "{command: setting}", it will return "setting"
func parseCommand(command string) string {
	return strings.TrimSpace(command[strings.Index(command, ":")+1 : strings.Index(command, "}")])
}

//GetTranspose returns the current chord transposition setting for this Song.
//The transposition is an integer representing the half-notes up or down (+/-)
//that the Chords are being adjusted. All Chords contained in this song should
//have the same Transpose setting.
func (song Song) GetTranspose() int {
	return song.transpose
}

//Transpose will iterate through all Chords contained in this song and call
//Chord.Transpose(changeBy) on each one.
func (song *Song) Transpose(changeBy int) {
	song.transpose = changeBy

	for _, s := range song.Stanzas {
		for _, l := range s.Lines {
			//index range here because we are modifying the Chord
			for i := range l.Chords {
				l.Chords[i].Transpose = changeBy
			}
		}
	}
}

func (song Song) String() string {
	var buffer bytes.Buffer

	if len(song.Section) > 0 {
		buffer.WriteString(fmt.Sprintf("Section: %s\n", song.Section))
	}

	if len(song.BeforeComments) > 0 {
		buffer.WriteString(fmt.Sprintf("/%s/\n", song.BeforeComments))
	}

	for _, s := range song.Stanzas {
		if s.IsChorus {
			buffer.WriteString("---CHORUS--\n")
		} else {
			buffer.WriteString(fmt.Sprintf("STANZA: %d\n", s.Number))
		}
		for _, l := range s.Lines {
			buffer.WriteString(fmt.Sprintf("%s\n", l.Text))
		}
		if s.IsChorus {
			buffer.WriteString(fmt.Sprintln("---END CHORUS--"))
		}

		buffer.WriteString("\n")
	}

	return buffer.String()
}

//HasBeforeComments returns true if this Song has any BeforeComments set,
//false otherwise.
func (song Song) HasBeforeComments() bool {
	return len(song.BeforeComments) > 0
}

//Link provides a substring of this Song's Filename as a way to easily
//provide HTML links.
//i.e. if Filename is ".../song name.song" Link will return "song name"
func (song Song) Link() string {
	return song.Filename[0 : len(song.Filename)-5]
}
<|endoftext|>


<commit_before>// (c) Copyright IBM Corp. 2021
// (c) Copyright Instana Inc. 2016

package instana

import (
	"bytes"
	"strings"
	"sync"
	"time"

	"github.com/instana/go-sensor/logger"
	ot "github.com/opentracing/opentracing-go"
	otlog "github.com/opentracing/opentracing-go/log"
)

const minSpanLogLevel = logger.WarnLevel

type spanS struct {
	Service     string
	Operation   string
	Start       time.Time
	Duration    time.Duration
	Correlation EUMCorrelationData
	Tags        ot.Tags
	Logs        []ot.LogRecord
	ErrorCount  int

	tracer *tracerS
	mu     sync.Mutex

	context SpanContext
}

func (r *spanS) BaggageItem(key string) string {
	r.mu.Lock()
	defer r.mu.Unlock()

	return r.context.Baggage[key]
}

func (r *spanS) SetBaggageItem(key, val string) ot.Span {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.context = r.context.WithBaggageItem(key, val)

	return r
}

func (r *spanS) Context() ot.SpanContext {
	return r.context
}

func (r *spanS) Finish() {
	r.FinishWithOptions(ot.FinishOptions{})
}

func (r *spanS) FinishWithOptions(opts ot.FinishOptions) {
	finishTime := opts.FinishTime
	if finishTime.IsZero() {
		finishTime = time.Now()
	}

	duration := finishTime.Sub(r.Start)

	r.mu.Lock()
	defer r.mu.Unlock()

	for _, lr := range opts.LogRecords {
		r.appendLog(lr)
	}

	for _, ld := range opts.BulkLogData {
		r.appendLog(ld.ToLogRecord())
	}

	r.Duration = duration
	if !r.context.Suppressed {
		r.tracer.recorder.RecordSpan(r)
		r.sendOpenTracingLogRecords()
	}
}

func (r *spanS) appendLog(lr ot.LogRecord) {
	maxLogs := r.tracer.Options().MaxLogsPerSpan
	if maxLogs == 0 || len(r.Logs) < maxLogs {
		r.Logs = append(r.Logs, lr)
	}
}

func (r *spanS) Log(ld ot.LogData) {
	if r.tracer.Options().DropAllLogs {
		return
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	if ld.Timestamp.IsZero() {
		ld.Timestamp = time.Now()
	}

	r.appendLog(ld.ToLogRecord())
}

func (r *spanS) LogEvent(event string) {
	r.Log(ot.LogData{
		Event: event})
}

func (r *spanS) LogEventWithPayload(event string, payload interface{}) {
	r.Log(ot.LogData{
		Event:   event,
		Payload: payload})
}

func (r *spanS) LogFields(fields ...otlog.Field) {

	for _, v := range fields {
		// If this tag indicates an error, increase the error count
		if openTracingLogFieldLevel(v) == logger.ErrorLevel {
			r.ErrorCount++
		}
	}

	lr := ot.LogRecord{
		Fields: fields,
	}

	if r.tracer.Options().DropAllLogs {
		return
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	if lr.Timestamp.IsZero() {
		lr.Timestamp = time.Now()
	}

	r.appendLog(lr)
}

func (r *spanS) LogKV(keyValues ...interface{}) {
	fields, err := otlog.InterleavedKVToFields(keyValues...)
	if err != nil {
		r.LogFields(otlog.Error(err), otlog.String("function", "LogKV"))

		return
	}

	r.LogFields(fields...)
}

func (r *spanS) SetOperationName(operationName string) ot.Span {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.Operation = operationName

	return r
}

func (r *spanS) SetTag(key string, value interface{}) ot.Span {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.Tags == nil {
		r.Tags = ot.Tags{}
	}

	// If this tag indicates an error, increase the error count
	if key == "error" {
		r.ErrorCount++
	}

	if key == suppressTracingTag {
		r.context.Suppressed = true
		return r
	}

	r.Tags[key] = value

	return r
}

func (r *spanS) Tracer() ot.Tracer {
	return r.tracer
}

// sendOpenTracingLogRecords converts OpenTracing log records that contain errors
// to Instana log spans and sends them to the agent
func (r *spanS) sendOpenTracingLogRecords() {
	for _, lr := range r.Logs {
		r.sendOpenTracingLogRecord(lr)
	}
}

func (r *spanS) sendOpenTracingLogRecord(lr ot.LogRecord) {
	lvl := openTracingHighestLogRecordLevel(lr)

	if lvl > minSpanLogLevel {
		return
	}

	buf := bytes.NewBuffer(nil)

	enc := newOpenTracingLogEncoder(buf)
	for _, lf := range lr.Fields {
		lf.Marshal(enc)
		buf.WriteByte(' ')
	}

	r.tracer.StartSpan(
		"log.go",
		ot.ChildOf(r.context),
		ot.StartTime(lr.Timestamp),
		ot.Tags{
			"log.level":   lvl.String(),
			"log.message": strings.TrimSpace(buf.String()),
		},
	).FinishWithOptions(
		ot.FinishOptions{
			FinishTime: lr.Timestamp,
		},
	)
}

func openTracingHighestLogRecordLevel(lr ot.LogRecord) logger.Level {
	highestLvl := logger.DebugLevel

	for _, lf := range lr.Fields {
		if lvl := openTracingLogFieldLevel(lf); lvl < highestLvl {
			highestLvl = lvl
		}
	}

	return highestLvl
}

func openTracingLogFieldLevel(lf otlog.Field) logger.Level {
	switch lf.Key() {
	case "error", "error.object":
		return logger.ErrorLevel
	case "warn":
		return logger.WarnLevel
	default:
		return logger.DebugLevel
	}
}
<commit_msg>Improve log level comparison while filtering the log records<commit_after>// (c) Copyright IBM Corp. 2021
// (c) Copyright Instana Inc. 2016

package instana

import (
	"bytes"
	"strings"
	"sync"
	"time"

	"github.com/instana/go-sensor/logger"
	ot "github.com/opentracing/opentracing-go"
	otlog "github.com/opentracing/opentracing-go/log"
)

const minSpanLogLevel = logger.WarnLevel

type spanS struct {
	Service     string
	Operation   string
	Start       time.Time
	Duration    time.Duration
	Correlation EUMCorrelationData
	Tags        ot.Tags
	Logs        []ot.LogRecord
	ErrorCount  int

	tracer *tracerS
	mu     sync.Mutex

	context SpanContext
}

func (r *spanS) BaggageItem(key string) string {
	r.mu.Lock()
	defer r.mu.Unlock()

	return r.context.Baggage[key]
}

func (r *spanS) SetBaggageItem(key, val string) ot.Span {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.context = r.context.WithBaggageItem(key, val)

	return r
}

func (r *spanS) Context() ot.SpanContext {
	return r.context
}

func (r *spanS) Finish() {
	r.FinishWithOptions(ot.FinishOptions{})
}

func (r *spanS) FinishWithOptions(opts ot.FinishOptions) {
	finishTime := opts.FinishTime
	if finishTime.IsZero() {
		finishTime = time.Now()
	}

	duration := finishTime.Sub(r.Start)

	r.mu.Lock()
	defer r.mu.Unlock()

	for _, lr := range opts.LogRecords {
		r.appendLog(lr)
	}

	for _, ld := range opts.BulkLogData {
		r.appendLog(ld.ToLogRecord())
	}

	r.Duration = duration
	if !r.context.Suppressed {
		r.tracer.recorder.RecordSpan(r)
		r.sendOpenTracingLogRecords()
	}
}

func (r *spanS) appendLog(lr ot.LogRecord) {
	maxLogs := r.tracer.Options().MaxLogsPerSpan
	if maxLogs == 0 || len(r.Logs) < maxLogs {
		r.Logs = append(r.Logs, lr)
	}
}

func (r *spanS) Log(ld ot.LogData) {
	if r.tracer.Options().DropAllLogs {
		return
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	if ld.Timestamp.IsZero() {
		ld.Timestamp = time.Now()
	}

	r.appendLog(ld.ToLogRecord())
}

func (r *spanS) LogEvent(event string) {
	r.Log(ot.LogData{
		Event: event})
}

func (r *spanS) LogEventWithPayload(event string, payload interface{}) {
	r.Log(ot.LogData{
		Event:   event,
		Payload: payload})
}

func (r *spanS) LogFields(fields ...otlog.Field) {

	for _, v := range fields {
		// If this tag indicates an error, increase the error count
		if openTracingLogFieldLevel(v) == logger.ErrorLevel {
			r.ErrorCount++
		}
	}

	lr := ot.LogRecord{
		Fields: fields,
	}

	if r.tracer.Options().DropAllLogs {
		return
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	if lr.Timestamp.IsZero() {
		lr.Timestamp = time.Now()
	}

	r.appendLog(lr)
}

func (r *spanS) LogKV(keyValues ...interface{}) {
	fields, err := otlog.InterleavedKVToFields(keyValues...)
	if err != nil {
		r.LogFields(otlog.Error(err), otlog.String("function", "LogKV"))

		return
	}

	r.LogFields(fields...)
}

func (r *spanS) SetOperationName(operationName string) ot.Span {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.Operation = operationName

	return r
}

func (r *spanS) SetTag(key string, value interface{}) ot.Span {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.Tags == nil {
		r.Tags = ot.Tags{}
	}

	// If this tag indicates an error, increase the error count
	if key == "error" {
		r.ErrorCount++
	}

	if key == suppressTracingTag {
		r.context.Suppressed = true
		return r
	}

	r.Tags[key] = value

	return r
}

func (r *spanS) Tracer() ot.Tracer {
	return r.tracer
}

// sendOpenTracingLogRecords converts OpenTracing log records that contain errors
// to Instana log spans and sends them to the agent
func (r *spanS) sendOpenTracingLogRecords() {
	for _, lr := range r.Logs {
		r.sendOpenTracingLogRecord(lr)
	}
}

func (r *spanS) sendOpenTracingLogRecord(lr ot.LogRecord) {
	lvl := openTracingHighestLogRecordLevel(lr)

	if lvl.Less(minSpanLogLevel) {
		return
	}

	buf := bytes.NewBuffer(nil)

	enc := newOpenTracingLogEncoder(buf)
	for _, lf := range lr.Fields {
		lf.Marshal(enc)
		buf.WriteByte(' ')
	}

	r.tracer.StartSpan(
		"log.go",
		ot.ChildOf(r.context),
		ot.StartTime(lr.Timestamp),
		ot.Tags{
			"log.level":   lvl.String(),
			"log.message": strings.TrimSpace(buf.String()),
		},
	).FinishWithOptions(
		ot.FinishOptions{
			FinishTime: lr.Timestamp,
		},
	)
}

// openTracingHighestLogRecordLevel determines the level of this record by inspecting its fields.
// If there are multiple fields suggesting the log level, i.e. both "error" and "warn" are present,
// the highest one takes precedence.
func openTracingHighestLogRecordLevel(lr ot.LogRecord) logger.Level {
	highestLvl := logger.DebugLevel

	for _, lf := range lr.Fields {
		if lvl := openTracingLogFieldLevel(lf); highestLvl.Less(lvl) {
			highestLvl = lvl
		}
	}

	return highestLvl
}

func openTracingLogFieldLevel(lf otlog.Field) logger.Level {
	switch lf.Key() {
	case "error", "error.object":
		return logger.ErrorLevel
	case "warn":
		return logger.WarnLevel
	default:
		return logger.DebugLevel
	}
}
<|endoftext|>


<commit_before>package scheduler

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/go-gorp/gorp"
	"github.com/gorhill/cronexpr"
	"github.com/lib/pq"

	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

var schedulerStatus = "Not Running"

//Scheduler is the goroutine which compute date of next execution for pipeline scheduler
func Scheduler(c context.Context, DBFunc func() *gorp.DbMap) {
	tick := time.NewTicker(2 * time.Second).C
	for {
		select {
		case <-c.Done():
			if c.Err() != nil {
				log.Error("Exiting scheduler.Scheduler: %v", c.Err())
				return
			}
		case <-tick:
			_, status, err := Run(DBFunc())

			if err != nil {
				log.Error("%s: %s", status, err)
			}
			schedulerStatus = status
		}
	}
}

//Run is the core function of Scheduler goroutine
func Run(db *gorp.DbMap) ([]sdk.PipelineSchedulerExecution, string, error) {
	//Load unscheduled pipelines
	ps, errl := LoadUnscheduledPipelines(db)
	if errl != nil {
		return nil, "Run> Unable to load unscheduled pipelines : %s", errl
	}

	execs := []sdk.PipelineSchedulerExecution{}

	for i := range ps {
		tx, errb := db.Begin()
		if errb != nil {
			return nil, "Run> Unable to start a transaction", errb
		}

		query := `
			SELECT pipeline_scheduler.*
			FROM pipeline_scheduler
			JOIN (
				SELECT 	pipeline_scheduler_id
				FROM  	pipeline_scheduler_execution
				WHERE 	pipeline_scheduler_id = $1
				AND 	executed = 'true'
				ORDER BY  execution_planned_date DESC
				LIMIT 1
				) execs ON pipeline_scheduler.id = execs.pipeline_scheduler_id
			WHERE pipeline_scheduler.id = $1
			FOR UPDATE NOWAIT`

		var gorpPS = &PipelineScheduler{}
		if err := tx.SelectOne(gorpPS, query, ps[i].ID); err != nil {
			if pqerr, ok := err.(*pq.Error); ok && pqerr.Code != "55P03" || err != sql.ErrNoRows {
				log.Error("Run> Unable to lock to pipeline_scheduler %s", err)
			}
			_ = tx.Rollback()
			continue
		}

		s := sdk.PipelineScheduler(*gorpPS)
		//Skip disabled scheduler
		if s.Disabled {
			_ = tx.Rollback()
			continue
		}

		//Compute a new execution
		e, errn := Next(tx, &s)
		if errn != nil {
			//Nothing to compute
			_ = tx.Rollback()
			continue
		}
		//Insert it
		if err := InsertExecution(tx, e); err != nil {
			_ = tx.Rollback()
			continue
		}
		execs = append(execs, *e)

		if err := tx.Commit(); err != nil {
			_ = tx.Rollback()
			return nil, "Run> Unable to commit a transaction : %s", err
		}
	}

	return execs, "OK", nil
}

//Next Compute the next PipelineSchedulerExecution
func Next(db gorp.SqlExecutor, s *sdk.PipelineScheduler) (*sdk.PipelineSchedulerExecution, error) {
	cronExpr, err := cronexpr.Parse(s.Crontab)
	if err != nil {
		log.Warning("scheduler.Next> Unable to parse cronexpr for ID %d : %s", s.ID, err)
		return nil, err
	}
	exec, err := LoadLastExecution(db, s.ID)
	if err != nil {
		return nil, nil
	}

	loc, err := time.LoadLocation(s.Timezone)
	if err != nil {
		return nil, err
	}

	t := time.Now().In(loc)
	if exec == nil {
		exec = &sdk.PipelineSchedulerExecution{
			Executed:      true,
			ExecutionDate: &t,
		}
	}

	if !exec.Executed {
		return nil, fmt.Errorf("Last execution %d not ran", s.ID)
	}
	//Don't take last execution date as reference: time.Now() is enough
	e := &sdk.PipelineSchedulerExecution{
		ExecutionPlannedDate: cronExpr.Next(t),
		PipelineSchedulerID:  s.ID,
		Executed:             false,
	}
	return e, nil
}

// Status returns Event status
func Status() string {
	if schedulerStatus != "OK" {
		return "⚠ " + schedulerStatus
	}
	return schedulerStatus
}
<commit_msg>fix (api): scheduler execution lock (#1226)<commit_after>package scheduler

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/go-gorp/gorp"
	"github.com/gorhill/cronexpr"
	"github.com/lib/pq"

	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

var schedulerStatus = "Not Running"

//Scheduler is the goroutine which compute date of next execution for pipeline scheduler
func Scheduler(c context.Context, DBFunc func() *gorp.DbMap) {
	tick := time.NewTicker(2 * time.Second).C
	for {
		select {
		case <-c.Done():
			if c.Err() != nil {
				log.Error("Exiting scheduler.Scheduler: %v", c.Err())
				return
			}
		case <-tick:
			_, status, err := Run(DBFunc())

			if err != nil {
				log.Error("%s: %s", status, err)
			}
			schedulerStatus = status
		}
	}
}

//Run is the core function of Scheduler goroutine
func Run(db *gorp.DbMap) ([]sdk.PipelineSchedulerExecution, string, error) {
	//Load unscheduled pipelines
	ps, errl := LoadUnscheduledPipelines(db)
	if errl != nil {
		return nil, "Run> Unable to load unscheduled pipelines : %s", errl
	}

	execs := []sdk.PipelineSchedulerExecution{}

	for i := range ps {
		tx, errb := db.Begin()
		if errb != nil {
			return nil, "Run> Unable to start a transaction", errb
		}

		query := `
			SELECT pipeline_scheduler.*
			FROM pipeline_scheduler ,
			(
				SELECT 	count(pipeline_scheduler_id) as total
				FROM  	pipeline_scheduler_execution
				WHERE 	pipeline_scheduler_id = $1
				AND     executed = 'false'
			) nb_execs
			WHERE   pipeline_scheduler.id = $1
			AND     nb_execs.total = 0
			FOR UPDATE NOWAIT`

		var gorpPS = &PipelineScheduler{}
		if err := tx.SelectOne(gorpPS, query, ps[i].ID); err != nil {
			if pqerr, ok := err.(*pq.Error); ok && pqerr.Code != "55P03" || err != sql.ErrNoRows {
				log.Error("Run> Unable to lock to pipeline_scheduler %s", err)
			}
			_ = tx.Rollback()
			continue
		}

		s := sdk.PipelineScheduler(*gorpPS)
		//Skip disabled scheduler
		if s.Disabled {
			_ = tx.Rollback()
			continue
		}

		//Compute a new execution
		e, errn := Next(tx, &s)
		if errn != nil {
			//Nothing to compute
			_ = tx.Rollback()
			continue
		}
		//Insert it
		if err := InsertExecution(tx, e); err != nil {
			_ = tx.Rollback()
			continue
		}
		execs = append(execs, *e)

		if err := tx.Commit(); err != nil {
			_ = tx.Rollback()
			return nil, "Run> Unable to commit a transaction : %s", err
		}
	}

	return execs, "OK", nil
}

//Next Compute the next PipelineSchedulerExecution
func Next(db gorp.SqlExecutor, s *sdk.PipelineScheduler) (*sdk.PipelineSchedulerExecution, error) {
	cronExpr, err := cronexpr.Parse(s.Crontab)
	if err != nil {
		log.Warning("scheduler.Next> Unable to parse cronexpr for ID %d : %s", s.ID, err)
		return nil, err
	}
	exec, err := LoadLastExecution(db, s.ID)
	if err != nil {
		return nil, nil
	}

	loc, err := time.LoadLocation(s.Timezone)
	if err != nil {
		return nil, err
	}

	t := time.Now().In(loc)
	if exec == nil {
		exec = &sdk.PipelineSchedulerExecution{
			Executed:      true,
			ExecutionDate: &t,
		}
	}

	if !exec.Executed {
		return nil, fmt.Errorf("Last execution %d not ran", s.ID)
	}
	//Don't take last execution date as reference: time.Now() is enough
	e := &sdk.PipelineSchedulerExecution{
		ExecutionPlannedDate: cronExpr.Next(t),
		PipelineSchedulerID:  s.ID,
		Executed:             false,
	}
	return e, nil
}

// Status returns Event status
func Status() string {
	if schedulerStatus != "OK" {
		return "⚠ " + schedulerStatus
	}
	return schedulerStatus
}
<|endoftext|>