<commit_before>package jade

import (
	"bytes"
	"fmt"
	"strings"
)

// NodeType identifies the type of a parse tree node.
type NodeType int
const (
	NodeList    NodeType = iota
	NodeText
	NodeTag
	NodeAttr
	NodeDoctype
)

func indentToString(nesting, indent int, n_or_i bool) string {
	if prettyOutput {
		idt := new(bytes.Buffer)
		if n_or_i {
			for i := 0; i < nesting; i++ {
				idt.WriteString(outputIndent)
			}
		} else {
			for i := 0; i < indent; i++ {
				idt.WriteByte(' ')
			}
		}
		return idt.String()
	}
	return ""
}

type NestNode struct {
	NodeType
	Pos
	tr    *Tree
	Nodes []Node

	typ 	itemType
	Tag     string
	Indent  int
	Nesting int

	id 		string
	class 	[]string
}

func (t *Tree) newNest(pos Pos, tag string, tp itemType, idt, nst int) *NestNode {
	return &NestNode{tr: t, NodeType: NodeTag, Pos: pos, Tag: tag, typ: tp, Indent: idt, Nesting: nst}
}

func (nn *NestNode) append(n Node) {
	nn.Nodes = append(nn.Nodes, n)
}
func (nn *NestNode) tree() *Tree {
	return nn.tr
}
func (nn *NestNode) tp() itemType {
	return nn.typ
}

func (nn *NestNode) String() string {
	// fmt.Printf("%s\t%s\t%d\t\t%s\n", itemToStr[nn.typ], nn.Tag, len(nn.Nodes), itemToStr[nn.Nodes[0].tp()])
	b   := new(bytes.Buffer)
	// idt := new(bytes.Buffer)
	idt := indentToString(nn.Nesting, nn.Indent, nestIndent)

	if prettyOutput && nn.typ != itemInlineTag && nn.typ != itemInlineVoidTag {
		idt = "\n" + idt
	}

	beginFormat := idt+"<%s"
	endFormat   := "</%s>"

	switch nn.typ {
	case itemDiv:
		nn.Tag = "div"
	case itemInlineTag, itemInlineVoidTag:
		beginFormat = "<%s"
	case itemComment:
		nn.Tag = "--"
		beginFormat = idt+"<!%s "
		endFormat = " %s>"
	case itemAction, itemActionEnd:
		beginFormat = idt+"{{ %s }}"
	}

	if len(nn.Nodes) > 1 || ( len(nn.Nodes) == 1 && nn.Nodes[0].tp() != itemEndAttr ) {
		endEl := nn.Nodes[len(nn.Nodes)-1].tp()
		if endEl != itemInlineText && endEl != itemInlineAction && endEl != itemInlineTag {
			endFormat = idt+endFormat
		}
	}

		fmt.Fprintf(b, beginFormat, nn.Tag)

	if len(nn.id) > 0 {
		fmt.Fprintf(b, " id=\"%s\"", nn.id)
	}
	if len(nn.class) > 0 {
		fmt.Fprintf(b, " class=\"%s\"", strings.Join(nn.class, " "))
	}
	for _, n := range nn.Nodes {
		fmt.Fprint(b, n)
	}
	switch nn.typ {
	case itemActionEnd:
		fmt.Fprint(b, idt+"{{ end }}")
	case itemTag, itemDiv, itemInlineTag, itemComment:
		fmt.Fprintf(b, endFormat, nn.Tag)
	}
	return b.String()
}

func (nn *NestNode) CopyNest() *NestNode {
	if nn == nil {
		return nn
	}
	n := nn.tr.newNest(nn.Pos, nn.Tag, nn.typ, nn.Indent, nn.Nesting)
	for _, elem := range nn.Nodes {
		n.append(elem.Copy())
	}
	return n
}
func (nn *NestNode) Copy() Node {
	return nn.CopyNest()
}

var doctype = map[string]string {
	"xml" 			: `<?xml version="1.0" encoding="utf-8" ?>`,
	"html" 			: `<!DOCTYPE html>`,
	"5" 			: `<!DOCTYPE html>`,
	"1.1" 			: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`,
	"xhtml" 		: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`,
	"basic" 		: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">`,
	"mobile" 		: `<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">`,
	"strict" 		: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">`,
	"frameset" 		: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">`,
	"transitional" 	: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">`,
	"4" 			: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">`,
	"4strict" 		: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">`,
	"4frameset" 	: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">`,
	"4transitional" : `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"> `,
}

type DoctypeNode struct {
	NodeType
	Pos
	tr    *Tree
	Doctype string
}

func (t *Tree) newDoctype(pos Pos, dt string) *DoctypeNode {
	return &DoctypeNode{tr: t, NodeType: NodeDoctype, Pos: pos, Doctype: dt}
}

func (d *DoctypeNode) String() string {
	if dt, ok := doctype[d.Doctype]; ok {
		return fmt.Sprintf("%s", dt)
	}
	return fmt.Sprintf("<!DOCTYPE html>")
}

func (d *DoctypeNode) tp() itemType {
	return itemDoctype
}
func (d *DoctypeNode) tree() *Tree {
	return d.tr
}
func (d *DoctypeNode) Copy() Node {
	return &DoctypeNode{tr: d.tr, NodeType: NodeDoctype, Pos: d.Pos, Doctype: d.Doctype}
}

// LineNode holds plain text.
type LineNode struct {
	NodeType
	Pos
	tr   *Tree

	Text []byte // The text; may span newlines.
	typ 	itemType
	Indent  int
	Nesting int
}

func (t *Tree) newLine(pos Pos, text string, tp itemType, idt, nst int) *LineNode {
	return &LineNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text), typ: tp, Indent: idt, Nesting: nst}
}

func (l *LineNode) String() string {
	idt := indentToString(l.Nesting, l.Indent, lineIndent)

	var lnFormat string

	switch l.typ {
	case itemTemplate:
		lnFormat = "\n"+idt+"{{ template %s }}"
	case itemInlineText:
		lnFormat = "%s"
	case itemInlineAction:
		lnFormat = "{{%s }}"
	default:
		lnFormat = "\n"+idt+"%s"
	}
	return fmt.Sprintf( lnFormat, l.Text )
}

func (l *LineNode) tp() itemType {
	return l.typ
}
func (l *LineNode) tree() *Tree {
	return l.tr
}
func (l *LineNode) Copy() Node {
	return &LineNode{tr: l.tr, NodeType: NodeText, Pos: l.Pos, Text: append([]byte{}, l.Text...), typ: l.typ, Indent: l.Indent, Nesting: l.Nesting}
}

type AttrNode struct {
	NodeType
	Pos
	tr    *Tree
	Attr  string
	typ   itemType
}

func (t *Tree) newAttr(pos Pos, attr string, tp itemType) *AttrNode {
	return &AttrNode{tr: t, NodeType: NodeAttr, Pos: pos, Attr: attr, typ: tp}
}

func (a *AttrNode) String() string {
	switch a.typ {
	case itemEndAttr:
		return fmt.Sprintf( "%s", a.Attr )
	case itemAttr:
		return fmt.Sprintf( "=%s", a.Attr )
	case itemAttrN:
		return fmt.Sprintf( "=\"%s\"", a.Attr )
	case itemAttrVoid:
		return fmt.Sprintf( " %s=\"%s\"", a.Attr, a.Attr )
	default:
		return fmt.Sprintf( " %s", a.Attr )
	}
}

func (a *AttrNode) tp() itemType {
	return a.typ
}
func (a *AttrNode) tree() *Tree {
	return a.tr
}
func (a *AttrNode) Copy() Node {
	return &AttrNode{tr: a.tr, NodeType: NodeAttr, Pos: a.Pos, Attr: a.Attr, typ: a.typ}
}<commit_msg>replace #{var} to {{var}}<commit_after>package jade

import (
	"bytes"
	"fmt"
	"strings"
	"regexp"
)

// NodeType identifies the type of a parse tree node.
type NodeType int
const (
	NodeList    NodeType = iota
	NodeText
	NodeTag
	NodeAttr
	NodeDoctype
)

func indentToString(nesting, indent int, n_or_i bool) string {
	if prettyOutput {
		idt := new(bytes.Buffer)
		if n_or_i {
			for i := 0; i < nesting; i++ {
				idt.WriteString(outputIndent)
			}
		} else {
			for i := 0; i < indent; i++ {
				idt.WriteByte(' ')
			}
		}
		return idt.String()
	}
	return ""
}

type NestNode struct {
	NodeType
	Pos
	tr    *Tree
	Nodes []Node

	typ 	itemType
	Tag     string
	Indent  int
	Nesting int

	id 		string
	class 	[]string
}

func (t *Tree) newNest(pos Pos, tag string, tp itemType, idt, nst int) *NestNode {
	return &NestNode{tr: t, NodeType: NodeTag, Pos: pos, Tag: tag, typ: tp, Indent: idt, Nesting: nst}
}

func (nn *NestNode) append(n Node) {
	nn.Nodes = append(nn.Nodes, n)
}
func (nn *NestNode) tree() *Tree {
	return nn.tr
}
func (nn *NestNode) tp() itemType {
	return nn.typ
}

func (nn *NestNode) String() string {
	// fmt.Printf("%s\t%s\t%d\t\t%s\n", itemToStr[nn.typ], nn.Tag, len(nn.Nodes), itemToStr[nn.Nodes[0].tp()])
	b   := new(bytes.Buffer)
	// idt := new(bytes.Buffer)
	idt := indentToString(nn.Nesting, nn.Indent, nestIndent)

	if prettyOutput && nn.typ != itemInlineTag && nn.typ != itemInlineVoidTag {
		idt = "\n" + idt
	}

	beginFormat := idt+"<%s"
	endFormat   := "</%s>"

	switch nn.typ {
	case itemDiv:
		nn.Tag = "div"
	case itemInlineTag, itemInlineVoidTag:
		beginFormat = "<%s"
	case itemComment:
		nn.Tag = "--"
		beginFormat = idt+"<!%s "
		endFormat = " %s>"
	case itemAction, itemActionEnd:
		beginFormat = idt+"{{ %s }}"
	}

	if len(nn.Nodes) > 1 || ( len(nn.Nodes) == 1 && nn.Nodes[0].tp() != itemEndAttr ) {
		endEl := nn.Nodes[len(nn.Nodes)-1].tp()
		if endEl != itemInlineText && endEl != itemInlineAction && endEl != itemInlineTag {
			endFormat = idt+endFormat
		}
	}

		fmt.Fprintf(b, beginFormat, nn.Tag)

	if len(nn.id) > 0 {
		fmt.Fprintf(b, " id=\"%s\"", nn.id)
	}
	if len(nn.class) > 0 {
		fmt.Fprintf(b, " class=\"%s\"", strings.Join(nn.class, " "))
	}
	for _, n := range nn.Nodes {
		fmt.Fprint(b, n)
	}
	switch nn.typ {
	case itemActionEnd:
		fmt.Fprint(b, idt+"{{ end }}")
	case itemTag, itemDiv, itemInlineTag, itemComment:
		fmt.Fprintf(b, endFormat, nn.Tag)
	}
	return b.String()
}

func (nn *NestNode) CopyNest() *NestNode {
	if nn == nil {
		return nn
	}
	n := nn.tr.newNest(nn.Pos, nn.Tag, nn.typ, nn.Indent, nn.Nesting)
	for _, elem := range nn.Nodes {
		n.append(elem.Copy())
	}
	return n
}
func (nn *NestNode) Copy() Node {
	return nn.CopyNest()
}

var doctype = map[string]string {
	"xml" 			: `<?xml version="1.0" encoding="utf-8" ?>`,
	"html" 			: `<!DOCTYPE html>`,
	"5" 			: `<!DOCTYPE html>`,
	"1.1" 			: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`,
	"xhtml" 		: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`,
	"basic" 		: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">`,
	"mobile" 		: `<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">`,
	"strict" 		: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">`,
	"frameset" 		: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">`,
	"transitional" 	: `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">`,
	"4" 			: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">`,
	"4strict" 		: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">`,
	"4frameset" 	: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">`,
	"4transitional" : `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"> `,
}

type DoctypeNode struct {
	NodeType
	Pos
	tr    *Tree
	Doctype string
}

func (t *Tree) newDoctype(pos Pos, dt string) *DoctypeNode {
	return &DoctypeNode{tr: t, NodeType: NodeDoctype, Pos: pos, Doctype: dt}
}

func (d *DoctypeNode) String() string {
	if dt, ok := doctype[d.Doctype]; ok {
		return fmt.Sprintf("%s", dt)
	}
	return fmt.Sprintf("<!DOCTYPE html>")
}

func (d *DoctypeNode) tp() itemType {
	return itemDoctype
}
func (d *DoctypeNode) tree() *Tree {
	return d.tr
}
func (d *DoctypeNode) Copy() Node {
	return &DoctypeNode{tr: d.tr, NodeType: NodeDoctype, Pos: d.Pos, Doctype: d.Doctype}
}

// LineNode holds plain text.
type LineNode struct {
	NodeType
	Pos
	tr   *Tree

	Text []byte // The text; may span newlines.
	typ 	itemType
	Indent  int
	Nesting int
}

func (t *Tree) newLine(pos Pos, text string, tp itemType, idt, nst int) *LineNode {
	return &LineNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text), typ: tp, Indent: idt, Nesting: nst}
}

func (l *LineNode) String() string {
	idt := indentToString(l.Nesting, l.Indent, lineIndent)
	rex := regexp.MustCompile("[!#]{(.+?)}")

	var lnFormat string

	switch l.typ {
	case itemInlineAction:
		lnFormat = "{{%s }}"
	case itemTemplate:
		lnFormat = "\n"+idt+"{{ template %s }}"
	case itemInlineText:
		return fmt.Sprintf( "%s", rex.ReplaceAll(l.Text, []byte("{{$1}}")) )
	default:
		return fmt.Sprintf( "\n"+idt+"%s", rex.ReplaceAll(l.Text, []byte("{{$1}}")) )
	}
	return fmt.Sprintf( lnFormat, l.Text )
}

func (l *LineNode) tp() itemType {
	return l.typ
}
func (l *LineNode) tree() *Tree {
	return l.tr
}
func (l *LineNode) Copy() Node {
	return &LineNode{tr: l.tr, NodeType: NodeText, Pos: l.Pos, Text: append([]byte{}, l.Text...), typ: l.typ, Indent: l.Indent, Nesting: l.Nesting}
}

type AttrNode struct {
	NodeType
	Pos
	tr    *Tree
	Attr  string
	typ   itemType
}

func (t *Tree) newAttr(pos Pos, attr string, tp itemType) *AttrNode {
	return &AttrNode{tr: t, NodeType: NodeAttr, Pos: pos, Attr: attr, typ: tp}
}

func (a *AttrNode) String() string {
	switch a.typ {
	case itemEndAttr:
		return fmt.Sprintf( "%s", a.Attr )
	case itemAttr:
		return fmt.Sprintf( "=%s", a.Attr )
	case itemAttrN:
		return fmt.Sprintf( "=\"%s\"", a.Attr )
	case itemAttrVoid:
		return fmt.Sprintf( " %s=\"%s\"", a.Attr, a.Attr )
	default:
		return fmt.Sprintf( " %s", a.Attr )
	}
}

func (a *AttrNode) tp() itemType {
	return a.typ
}
func (a *AttrNode) tree() *Tree {
	return a.tr
}
func (a *AttrNode) Copy() Node {
	return &AttrNode{tr: a.tr, NodeType: NodeAttr, Pos: a.Pos, Attr: a.Attr, typ: a.typ}
}<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/sunwxg/golibwireshark"
)

func main() {
	file := "../../1.pcap"
	key := "ip.addr"

	golibwireshark.Init(file, "")
	defer golibwireshark.Clean()

	var p golibwireshark.Packet

	for {
		p.GetPacket()
		if p.Edt == nil {
			break
		}

		if v, ok := p.IsKey(key); ok {
			for _, p := range v {
				fmt.Printf("[%s] %s\n", key, p)
			}
		}

		p.FreePacket()

	}
}
<commit_msg>updating findKey.go<commit_after>package main

import (
	"fmt"

	"github.com/sunwxg/golibwireshark"
)

func main() {
	file := "../../1.pcap"
	key := "ip.addr"

	golibwireshark.Init(file, "")
	defer golibwireshark.Clean()

	var p golibwireshark.Packet

	for {
		p.GetPacket()
		if p.Edt == nil {
			break
		}

		if valueList, ok := p.IsKey(key); ok {
			for _, v := range valueList {
				fmt.Printf("[%s] %s\n", key, v)
			}
		}

		p.FreePacket()

	}
}
<|endoftext|>


<commit_before>package examplelib

import (
	"github.com/hanwen/go-fuse/fuse"
	"fmt"
	"log"
	"os"
	"path"
	"strings"
	"testing"
)

var _ = strings.Join
var _ = log.Println

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

func IsDir(name string) bool {
	fi, _ := os.Lstat(name)
	return fi != nil && fi.IsDirectory()
}

func IsFile(name string) bool {
	fi, _ := os.Lstat(name)
	return fi != nil && fi.IsRegular()
}

func FileExists(name string) bool {
	_, err := os.Lstat(name)
	return err == nil
}

// Create and mount filesystem.
const magicMode uint32 = 0753

type testCase struct {
	origDir1 string
	origDir2 string
	mountDir string
	testDir  string
	tester   *testing.T

	fs    *SubmountFileSystem
	state *fuse.MountState
}

func (self *testCase) Setup(t *testing.T) {
	self.tester = t

	self.testDir = fuse.MakeTempDir()
	self.origDir1 = path.Join(self.testDir, "orig1")
	self.origDir2 = path.Join(self.testDir, "orig2")
	self.mountDir = path.Join(self.testDir, "mount")

	os.Mkdir(self.origDir1, 0700)
	os.Mkdir(self.origDir2, 0700)
	os.Mkdir(self.mountDir, 0700)

	fs1 := fuse.NewPathFileSystemConnector(NewPassThroughFuse(self.origDir1))
	fs2 := fuse.NewPathFileSystemConnector(NewPassThroughFuse(self.origDir2))

	self.fs = NewSubmountFileSystem()

	attr := fuse.Attr{
		Mode: uint32(magicMode),
	}

	self.fs.AddFileSystem("sub1", fs1, attr)
	self.fs.AddFileSystem("sub2", fs2, attr)

	self.state = fuse.NewMountState(self.fs)
	self.state.Mount(self.mountDir)

	self.state.Debug = true

	fmt.Println("tempdir: ", self.testDir)

	// Unthreaded, but in background.
	go self.state.Loop(false)
}

// Unmount and del.
func (self *testCase) Cleanup() {
	fmt.Println("Unmounting.")
	err := self.state.Unmount()
	if err != nil {
		self.tester.Errorf("Can't unmount a dir, err: %v", err)
	}
	os.RemoveAll(self.testDir)
}

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

func (self *testCase) testReaddir() {
	fmt.Println("testReaddir... ")
	dir, err := os.Open(self.mountDir, os.O_RDONLY, 0)
	if err != nil {
		self.tester.Errorf("opendir err %v", err)
		return
	}
	infos, err := dir.Readdir(10)
	if err != nil {
		self.tester.Errorf("readdir err %v", err)
	}

	wanted := map[string]bool{
		"sub1": true,
		"sub2": true,
	}
	if len(wanted) != len(infos) {
		self.tester.Errorf("Length mismatch %v", infos)
	} else {
		for _, v := range infos {
			_, ok := wanted[v.Name]
			if !ok {
				self.tester.Errorf("Unexpected name %v", v.Name)
			}

			if v.Mode&0777 != magicMode {
				self.tester.Errorf("Unexpected mode %o, %v", v.Mode, v)
			}
		}
	}

	dir.Close()
}

func (self *testCase) testSubFs() {
	fmt.Println("testSubFs... ")
	for i := 1; i <= 2; i++ {
		// orig := path.Join(self.testDir, fmt.Sprintf("orig%d", i))
		mount := path.Join(self.mountDir, fmt.Sprintf("sub%d", i))

		name := "testFile"

		mountFile := path.Join(mount, name)

		f, err := os.Open(mountFile, os.O_WRONLY, 0)
		if err == nil {
			self.tester.Errorf("Expected error for open write %v", name)
			continue
		}
		content1 := "booh!"
		f, err = os.Open(mountFile, os.O_WRONLY|os.O_CREATE, magicMode)
		if err != nil {
			self.tester.Errorf("Create %v", err)
		}

		f.Write([]byte(content1))
		f.Close()

		err = os.Chmod(mountFile, magicMode)
		if err != nil {
			self.tester.Errorf("chmod %v", err)
		}

		fi, err := os.Lstat(mountFile)
		if err != nil {
			self.tester.Errorf("Lstat %v", err)
		} else {
			if fi.Mode&0777 != magicMode {
				self.tester.Errorf("Mode %o", fi.Mode)
			}
		}

		g, err := os.Open(mountFile, os.O_RDONLY, 0)
		if err != nil {
			self.tester.Errorf("Open %v", err)
		} else {
			buf := make([]byte, 1024)
			n, err := g.Read(buf)
			if err != nil {
				self.tester.Errorf("read err %v", err)
			}
			if string(buf[:n]) != content1 {
				self.tester.Errorf("content %v", buf[:n])
			}
			g.Close()
		}
	}
}

func (self *testCase) testAddRemove() {
	self.tester.Log("testAddRemove")
	attr := fuse.Attr{
		Mode: 0755,
	}

	conn := fuse.NewPathFileSystemConnector(NewPassThroughFuse(self.origDir1))
	ok := self.fs.AddFileSystem("sub1", conn, attr)
	if ok {
		self.tester.Errorf("AddFileSystem should fail")
		return
	}
	ok = self.fs.AddFileSystem("third", conn, attr)
	if !ok {
		self.tester.Errorf("AddFileSystem fail")
	}
	conn.Init(new(fuse.InHeader), new(fuse.InitIn))

	fi, err := os.Lstat(path.Join(self.mountDir, "third"))
	if err != nil {
		self.tester.Errorf("third lstat err %v", err)
	} else {
		if !fi.IsDirectory() {
			self.tester.Errorf("not a directory %v", fi)
		}
	}

	fs := self.fs.RemoveFileSystem("third")
	if fs == nil {
		self.tester.Errorf("remove fail")
	}
	dir, err := os.Open(self.mountDir, os.O_RDONLY, 0)
	if err != nil {
		self.tester.Errorf("opendir err %v", err)
		return
	}
	infos, err := dir.Readdir(10)
	if len(infos) != 2 {
		self.tester.Errorf("lstat expect 2 infos %v", infos)
	}
	dir.Close()

	_, err = os.Open(path.Join(self.mountDir, "third"), os.O_RDONLY, 0)
	if err == nil {
		self.tester.Errorf("expect enoent %v", err)
	}
}

// Test driver.
func TestMount(t *testing.T) {
	ts := new(testCase)
	ts.Setup(t)

	ts.testReaddir()
	ts.testSubFs()
	ts.testAddRemove()

	ts.Cleanup()
}
<commit_msg>Remove naming conflicts in newly added stackfs test code.<commit_after>package examplelib

import (
	"github.com/hanwen/go-fuse/fuse"
	"fmt"
	"log"
	"os"
	"path"
	"strings"
	"testing"
)

var _ = strings.Join
var _ = log.Println

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

// Create and mount filesystem.
const magicMode uint32 = 0753

type stackFsTestCase struct {
	origDir1 string
	origDir2 string
	mountDir string
	testDir  string
	tester   *testing.T

	fs    *SubmountFileSystem
	state *fuse.MountState
}

func (self *stackFsTestCase) Setup(t *testing.T) {
	self.tester = t

	self.testDir = fuse.MakeTempDir()
	self.origDir1 = path.Join(self.testDir, "orig1")
	self.origDir2 = path.Join(self.testDir, "orig2")
	self.mountDir = path.Join(self.testDir, "mount")

	os.Mkdir(self.origDir1, 0700)
	os.Mkdir(self.origDir2, 0700)
	os.Mkdir(self.mountDir, 0700)

	fs1 := fuse.NewPathFileSystemConnector(NewPassThroughFuse(self.origDir1))
	fs2 := fuse.NewPathFileSystemConnector(NewPassThroughFuse(self.origDir2))

	self.fs = NewSubmountFileSystem()

	attr := fuse.Attr{
		Mode: uint32(magicMode),
	}

	self.fs.AddFileSystem("sub1", fs1, attr)
	self.fs.AddFileSystem("sub2", fs2, attr)

	self.state = fuse.NewMountState(self.fs)
	self.state.Mount(self.mountDir)

	self.state.Debug = true

	fmt.Println("tempdir: ", self.testDir)

	// Unthreaded, but in background.
	go self.state.Loop(false)
}

// Unmount and del.
func (self *stackFsTestCase) Cleanup() {
	fmt.Println("Unmounting.")
	err := self.state.Unmount()
	if err != nil {
		self.tester.Errorf("Can't unmount a dir, err: %v", err)
	}
	os.RemoveAll(self.testDir)
}

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

func (self *stackFsTestCase) testReaddir() {
	fmt.Println("testReaddir... ")
	dir, err := os.Open(self.mountDir, os.O_RDONLY, 0)
	if err != nil {
		self.tester.Errorf("opendir err %v", err)
		return
	}
	infos, err := dir.Readdir(10)
	if err != nil {
		self.tester.Errorf("readdir err %v", err)
	}

	wanted := map[string]bool{
		"sub1": true,
		"sub2": true,
	}
	if len(wanted) != len(infos) {
		self.tester.Errorf("Length mismatch %v", infos)
	} else {
		for _, v := range infos {
			_, ok := wanted[v.Name]
			if !ok {
				self.tester.Errorf("Unexpected name %v", v.Name)
			}

			if v.Mode&0777 != magicMode {
				self.tester.Errorf("Unexpected mode %o, %v", v.Mode, v)
			}
		}
	}

	dir.Close()
}

func (self *stackFsTestCase) testSubFs() {
	fmt.Println("testSubFs... ")
	for i := 1; i <= 2; i++ {
		// orig := path.Join(self.testDir, fmt.Sprintf("orig%d", i))
		mount := path.Join(self.mountDir, fmt.Sprintf("sub%d", i))

		name := "testFile"

		mountFile := path.Join(mount, name)

		f, err := os.Open(mountFile, os.O_WRONLY, 0)
		if err == nil {
			self.tester.Errorf("Expected error for open write %v", name)
			continue
		}
		content1 := "booh!"
		f, err = os.Open(mountFile, os.O_WRONLY|os.O_CREATE, magicMode)
		if err != nil {
			self.tester.Errorf("Create %v", err)
		}

		f.Write([]byte(content1))
		f.Close()

		err = os.Chmod(mountFile, magicMode)
		if err != nil {
			self.tester.Errorf("chmod %v", err)
		}

		fi, err := os.Lstat(mountFile)
		if err != nil {
			self.tester.Errorf("Lstat %v", err)
		} else {
			if fi.Mode&0777 != magicMode {
				self.tester.Errorf("Mode %o", fi.Mode)
			}
		}

		g, err := os.Open(mountFile, os.O_RDONLY, 0)
		if err != nil {
			self.tester.Errorf("Open %v", err)
		} else {
			buf := make([]byte, 1024)
			n, err := g.Read(buf)
			if err != nil {
				self.tester.Errorf("read err %v", err)
			}
			if string(buf[:n]) != content1 {
				self.tester.Errorf("content %v", buf[:n])
			}
			g.Close()
		}
	}
}

func (self *stackFsTestCase) testAddRemove() {
	self.tester.Log("testAddRemove")
	attr := fuse.Attr{
		Mode: 0755,
	}

	conn := fuse.NewPathFileSystemConnector(NewPassThroughFuse(self.origDir1))
	ok := self.fs.AddFileSystem("sub1", conn, attr)
	if ok {
		self.tester.Errorf("AddFileSystem should fail")
		return
	}
	ok = self.fs.AddFileSystem("third", conn, attr)
	if !ok {
		self.tester.Errorf("AddFileSystem fail")
	}
	conn.Init(new(fuse.InHeader), new(fuse.InitIn))

	fi, err := os.Lstat(path.Join(self.mountDir, "third"))
	if err != nil {
		self.tester.Errorf("third lstat err %v", err)
	} else {
		if !fi.IsDirectory() {
			self.tester.Errorf("not a directory %v", fi)
		}
	}

	fs := self.fs.RemoveFileSystem("third")
	if fs == nil {
		self.tester.Errorf("remove fail")
	}
	dir, err := os.Open(self.mountDir, os.O_RDONLY, 0)
	if err != nil {
		self.tester.Errorf("opendir err %v", err)
		return
	}
	infos, err := dir.Readdir(10)
	if len(infos) != 2 {
		self.tester.Errorf("lstat expect 2 infos %v", infos)
	}
	dir.Close()

	_, err = os.Open(path.Join(self.mountDir, "third"), os.O_RDONLY, 0)
	if err == nil {
		self.tester.Errorf("expect enoent %v", err)
	}
}

func TestStackFS(t *testing.T) {
	ts := new(stackFsTestCase)
	ts.Setup(t)

	ts.testReaddir()
	ts.testSubFs()
	ts.testAddRemove()

	ts.Cleanup()
}
<|endoftext|>


<commit_before>// Copyright 2015 flannel 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 alivpc

import (
	"encoding/json"
	"fmt"
	"os"

	log "github.com/golang/glog"
	"golang.org/x/net/context"

	"github.com/coreos/flannel/backend"
	"github.com/coreos/flannel/pkg/ip"
	"github.com/coreos/flannel/subnet"
	"github.com/denverdino/aliyungo/common"
	"github.com/denverdino/aliyungo/ecs"
	"github.com/denverdino/aliyungo/metadata"
)

func init() {
	backend.Register("ali-vpc", New)
}

type AliVpcBackend struct {
	sm       subnet.Manager
	extIface *backend.ExternalInterface
}

func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backend, error) {
	be := AliVpcBackend{
		sm:       sm,
		extIface: extIface,
	}
	return &be, nil
}

func (be *AliVpcBackend) Run(ctx context.Context) {
	<-ctx.Done()
}

func (be *AliVpcBackend) RegisterNetwork(ctx context.Context, network string, config *subnet.Config) (backend.Network, error) {
	// 1. Parse our configuration
	cfg := struct {
		AccessKeyID     string
		AccessKeySecret string
	}{}

	if len(config.Backend) > 0 {
		if err := json.Unmarshal(config.Backend, &cfg); err != nil {
			return nil, fmt.Errorf("error decoding VPC backend config: %v", err)
		}
	}
	log.Infof("Unmarshal Configure : %v\n", cfg)

	// 2. Acquire the lease form subnet manager
	attrs := subnet.LeaseAttrs{
		PublicIP: ip.FromIP(be.extIface.ExtAddr),
	}

	l, err := be.sm.AcquireLease(ctx, network, &attrs)
	switch err {
	case nil:

	case context.Canceled, context.DeadlineExceeded:
		return nil, err

	default:
		return nil, fmt.Errorf("failed to acquire lease: %v", err)
	}
	if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
		cfg.AccessKeyID = os.Getenv("ACCESS_KEY_ID")
		cfg.AccessKeySecret = os.Getenv("ACCESS_KEY_SECRET")

		if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
			return nil, fmt.Errorf("ACCESS_KEY_ID and ACCESS_KEY_SECRET must be provided! ")
		}
	}

	meta := metadata.NewMetaData(nil)
	REGION, err := meta.Region()
	if err != nil {
		return nil, err
	}
	instanceid, err := meta.InstanceID()
	if err != nil {
		return nil, err
	}
	VpcID, err := meta.VpcID()
	if err != nil {
		return nil, err
	}

	c := ecs.NewClient(cfg.AccessKeyID, cfg.AccessKeySecret)

	vpc, _, err := c.DescribeVpcs(&ecs.DescribeVpcsArgs{
		RegionId: common.Region(REGION),
		VpcId:    VpcID,
	})
	if err != nil || len(vpc) <= 0 {
		log.Errorf("Error DescribeVpcs: %s . \n", getErrorString(err))
		return nil, err
	}

	vroute, _, err := c.DescribeVRouters(&ecs.DescribeVRoutersArgs{
		VRouterId: vpc[0].VRouterId,
		RegionId:  common.Region(REGION),
	})
	if err != nil || len(vroute) <= 0 {
		log.Errorf("Error DescribeVRouters: %s .\n", getErrorString(err))
		return nil, err
	}
	vRouterId := vroute[0].VRouterId
	rTableId := vroute[0].RouteTableIds.RouteTableId[0]

	rtables, _, err := c.DescribeRouteTables(&ecs.DescribeRouteTablesArgs{
		VRouterId:    vRouterId,
		RouteTableId: rTableId,
	})
	if err != nil || len(rtables) <= 0 {
		log.Errorf("Error DescribeRouteTables: %s.\n", err.Error())
		return nil, err
	}

	route := &ecs.CreateRouteEntryArgs{
		DestinationCidrBlock: l.Subnet.String(),
		NextHopType:          ecs.NextHopIntance,
		NextHopId:            instanceid,
		ClientToken:          "",
		RouteTableId:         rTableId,
	}
	if err := be.recreateRoute(c, rtables[0], route); err != nil {
		return nil, err
	}

	if err := c.WaitForAllRouteEntriesAvailable(vRouterId, rTableId, 60); err != nil {
		return nil, err
	}
	return &backend.SimpleNetwork{
		SubnetLease: l,
		ExtIface:    be.extIface,
	}, nil
}

func (be *AliVpcBackend) recreateRoute(c *ecs.Client, table ecs.RouteTableSetType, route *ecs.CreateRouteEntryArgs) error {
	exist := false
	for _, e := range table.RouteEntrys.RouteEntry {
		if e.RouteTableId == route.RouteTableId &&
			e.Type == ecs.RouteTableCustom &&
			e.InstanceId == route.NextHopId {

			if e.DestinationCidrBlock == route.DestinationCidrBlock &&
				e.Status == ecs.RouteEntryStatusAvailable {
				exist = true
				log.Infof("Keep target entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
				continue
			}
			// Fix: here we delete all the route which targeted to us(instance) except the specified route.
			// That means only one CIDR was allowed to target to the instance. Think if We need to change this
			// to adapt to multi CIDR and deal with unavailable route entry.
			if err := c.DeleteRouteEntry(&ecs.DeleteRouteEntryArgs{
				RouteTableId:         route.RouteTableId,
				DestinationCidrBlock: e.DestinationCidrBlock,
				NextHopId:            route.NextHopId,
			}); err != nil {
				return err
			}

			log.Infof("Remove old route entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
			continue
		}

		log.Infof("Keep route entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
	}
	if !exist {
		return c.CreateRouteEntry(route)
	}
	return nil
}

func getErrorString(e error) string {
	if e == nil {
		return ""
	}
	return e.Error()
}
<commit_msg>Keep route: [ 0.0.0.0/0 via ECS instance ] to work with alicloud DNAT product.<commit_after>// Copyright 2015 flannel 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 alivpc

import (
	"encoding/json"
	"fmt"
	"os"

	log "github.com/golang/glog"
	"golang.org/x/net/context"

	"github.com/coreos/flannel/backend"
	"github.com/coreos/flannel/pkg/ip"
	"github.com/coreos/flannel/subnet"
	"github.com/denverdino/aliyungo/common"
	"github.com/denverdino/aliyungo/ecs"
	"github.com/denverdino/aliyungo/metadata"
)

func init() {
	backend.Register("ali-vpc", New)
}

type AliVpcBackend struct {
	sm       subnet.Manager
	extIface *backend.ExternalInterface
}

func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backend, error) {
	be := AliVpcBackend{
		sm:       sm,
		extIface: extIface,
	}
	return &be, nil
}

func (be *AliVpcBackend) Run(ctx context.Context) {
	<-ctx.Done()
}

func (be *AliVpcBackend) RegisterNetwork(ctx context.Context, network string, config *subnet.Config) (backend.Network, error) {
	// 1. Parse our configuration
	cfg := struct {
		AccessKeyID     string
		AccessKeySecret string
	}{}

	if len(config.Backend) > 0 {
		if err := json.Unmarshal(config.Backend, &cfg); err != nil {
			return nil, fmt.Errorf("error decoding VPC backend config: %v", err)
		}
	}
	log.Infof("Unmarshal Configure : %v\n", cfg)

	// 2. Acquire the lease form subnet manager
	attrs := subnet.LeaseAttrs{
		PublicIP: ip.FromIP(be.extIface.ExtAddr),
	}

	l, err := be.sm.AcquireLease(ctx, network, &attrs)
	switch err {
	case nil:

	case context.Canceled, context.DeadlineExceeded:
		return nil, err

	default:
		return nil, fmt.Errorf("failed to acquire lease: %v", err)
	}
	if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
		cfg.AccessKeyID = os.Getenv("ACCESS_KEY_ID")
		cfg.AccessKeySecret = os.Getenv("ACCESS_KEY_SECRET")

		if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
			return nil, fmt.Errorf("ACCESS_KEY_ID and ACCESS_KEY_SECRET must be provided! ")
		}
	}

	meta := metadata.NewMetaData(nil)
	REGION, err := meta.Region()
	if err != nil {
		return nil, err
	}
	instanceid, err := meta.InstanceID()
	if err != nil {
		return nil, err
	}
	VpcID, err := meta.VpcID()
	if err != nil {
		return nil, err
	}

	c := ecs.NewClient(cfg.AccessKeyID, cfg.AccessKeySecret)

	vpc, _, err := c.DescribeVpcs(&ecs.DescribeVpcsArgs{
		RegionId: common.Region(REGION),
		VpcId:    VpcID,
	})
	if err != nil || len(vpc) <= 0 {
		log.Errorf("Error DescribeVpcs: %s . \n", getErrorString(err))
		return nil, err
	}

	vroute, _, err := c.DescribeVRouters(&ecs.DescribeVRoutersArgs{
		VRouterId: vpc[0].VRouterId,
		RegionId:  common.Region(REGION),
	})
	if err != nil || len(vroute) <= 0 {
		log.Errorf("Error DescribeVRouters: %s .\n", getErrorString(err))
		return nil, err
	}
	vRouterId := vroute[0].VRouterId
	rTableId := vroute[0].RouteTableIds.RouteTableId[0]

	rtables, _, err := c.DescribeRouteTables(&ecs.DescribeRouteTablesArgs{
		VRouterId:    vRouterId,
		RouteTableId: rTableId,
	})
	if err != nil || len(rtables) <= 0 {
		log.Errorf("Error DescribeRouteTables: %s.\n", err.Error())
		return nil, err
	}

	route := &ecs.CreateRouteEntryArgs{
		DestinationCidrBlock: l.Subnet.String(),
		NextHopType:          ecs.NextHopIntance,
		NextHopId:            instanceid,
		ClientToken:          "",
		RouteTableId:         rTableId,
	}
	if err := be.recreateRoute(c, rtables[0], route); err != nil {
		return nil, err
	}

	if err := c.WaitForAllRouteEntriesAvailable(vRouterId, rTableId, 60); err != nil {
		return nil, err
	}
	return &backend.SimpleNetwork{
		SubnetLease: l,
		ExtIface:    be.extIface,
	}, nil
}

func (be *AliVpcBackend) recreateRoute(c *ecs.Client, table ecs.RouteTableSetType, route *ecs.CreateRouteEntryArgs) error {
	exist := false
	for _, e := range table.RouteEntrys.RouteEntry {
		if e.RouteTableId == route.RouteTableId &&
			e.Type == ecs.RouteTableCustom &&
			e.InstanceId == route.NextHopId {

			if e.DestinationCidrBlock == route.DestinationCidrBlock &&
				e.Status == ecs.RouteEntryStatusAvailable {
				exist = true
				log.Infof("Keep target entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
				continue
			}
			// 0.0.0.0/0 => ECS1 this kind of route is used for DNAT. so we keep it
			if e.DestinationCidrBlock == "0.0.0.0/0" {
				log.Infof("Keep route entry: rtableid=%s, CIDR=%s, NextHop=%s For DNAT\n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
				continue
			}
			// Fix: here we delete all the route which targeted to us(instance) except the specified route.
			// That means only one CIDR was allowed to target to the instance. Think if We need to change this
			// to adapt to multi CIDR and deal with unavailable route entry.
			if err := c.DeleteRouteEntry(&ecs.DeleteRouteEntryArgs{
				RouteTableId:         route.RouteTableId,
				DestinationCidrBlock: e.DestinationCidrBlock,
				NextHopId:            route.NextHopId,
			}); err != nil {
				return err
			}

			log.Infof("Remove old route entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
			continue
		}

		log.Infof("Keep route entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
	}
	if !exist {
		return c.CreateRouteEntry(route)
	}
	return nil
}

func getErrorString(e error) string {
	if e == nil {
		return ""
	}
	return e.Error()
}
<|endoftext|>


<commit_before>package lexer

// Note that most of the panic() calls should be removed once the lexer is bug-free.

import (
	"fmt"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/ark-lang/ark/util"
)

type lexer struct {
	input                      []rune
	startPos, endPos           int
	output                     []*Token
	filename                   string
	charNumber, lineNumber     int
	tokStartChar, tokStartLine int
	verbose                    bool
}

func (v *lexer) errWithCustomPosition(ln, cn int, err string, stuff ...interface{}) {
	fmt.Printf(util.TEXT_RED+util.TEXT_BOLD+"Lexer error:"+util.TEXT_RESET+" [%s:%d:%d] %s\n",
		v.filename, ln, cn, fmt.Sprintf(err, stuff...))
	os.Exit(1)
}

func (v *lexer) err(err string, stuff ...interface{}) {
	v.errWithCustomPosition(v.lineNumber, v.charNumber, err, stuff...)
}

func (v *lexer) peek(ahead int) rune {
	if ahead < 0 {
		panic(fmt.Sprintf("Tried to peek a negative number: %d", ahead))
	}

	if v.endPos+ahead >= len(v.input) {
		return 0
	}
	return v.input[v.endPos+ahead]
}

func (v *lexer) consume() {
	v.charNumber++
	if v.peek(0) == '\n' {
		v.charNumber = 1
		v.lineNumber++
	}

	v.endPos++
}

func (v *lexer) expect(r rune) {
	if v.peek(0) == r {
		v.consume()
	} else {
		v.err("Expected `%c`, found `%c`", r, v.peek(0))
	}
}

func (v *lexer) discardBuffer() {
	v.startPos = v.endPos

	v.tokStartChar = v.charNumber
	v.tokStartLine = v.lineNumber
}

// debugging func
func (v *lexer) printBuffer() {
	fmt.Printf("[%d:%d] `%s`\n", v.startPos, v.endPos, string(v.input[v.startPos:v.endPos]))
}

func (v *lexer) pushToken(t TokenType) {
	tok := &Token{
		Type:          t,
		Filename:      v.filename,
		CharNumber:    v.tokStartChar,
		LineNumber:    v.tokStartLine,
		EndCharNumber: v.charNumber,
		EndLineNumber: v.lineNumber,
		Contents:      string(v.input[v.startPos:v.endPos]),
	}

	v.output = append(v.output, tok)

	if v.verbose {
		fmt.Printf("[%4d:%4d:%-17s] `%s`\n", v.startPos, v.endPos, tok.Type, tok.Contents)
	}

	v.discardBuffer()
}

func Lex(input []rune, filename string, verbose bool) []*Token {
	v := &lexer{
		input:        input,
		startPos:     0,
		endPos:       0,
		filename:     filename,
		charNumber:   1,
		lineNumber:   1,
		tokStartChar: 1,
		tokStartLine: 1,
		verbose:      verbose,
	}

	if v.verbose {
		fmt.Println(util.TEXT_BOLD+util.TEXT_GREEN+"Starting lexing"+util.TEXT_RESET, filename)
	}
	t := time.Now()
	v.lex()
	dur := time.Since(t)
	if v.verbose {
		fmt.Printf(util.TEXT_BOLD+util.TEXT_GREEN+"Finished lexing"+util.TEXT_RESET+" %s (%.2fms)\n",
			filename, float32(dur)/1000000)
	}
	return v.output
}

func (v *lexer) lex() {
	for {
		v.skipLayoutAndComments()

		if isEOF(v.peek(0)) {
			return
		}

		if isDecimalDigit(v.peek(0)) {
			v.recognizeNumberToken()
		} else if isLetter(v.peek(0)) || v.peek(0) == '_' {
			v.recognizeIdentifierToken()
		} else if v.peek(0) == '"' {
			v.recognizeStringToken()
		} else if v.peek(0) == '\'' {
			v.recognizeCharacterToken()
		} else if isOperator(v.peek(0)) {
			v.recognizeOperatorToken()
		} else if isSeparator(v.peek(0)) {
			v.recognizeSeparatorToken()
		} else {
			v.err("Unrecognised token")
		}
	}
}

func (v *lexer) skipLayoutAndComments() {
start:
	for isLayout(v.peek(0)) {
		v.consume()
	}

	v.discardBuffer()

	lineNumber := v.lineNumber
	charNumber := v.charNumber

	// Block comments
	if v.peek(0) == '/' && v.peek(1) == '*' {
		v.consume()
		v.consume()
		isDoc := v.peek(0) == '*'

		for {
			if isEOF(v.peek(0)) {
				v.errWithCustomPosition(lineNumber, charNumber, "Unterminated block comment")
			}
			if v.peek(0) == '*' && v.peek(1) == '/' {
				v.consume()
				v.consume()
				if isDoc {
					v.pushToken(TOKEN_DOCCOMMENT)
				} else {
					v.pushToken(TOKEN_COMMENT)
				}
				goto start
			}
			v.consume()
		}
	}

	// Single-line comments
	if v.peek(0) == '/' && v.peek(1) == '/' {
		v.consume()
		v.consume()
		isDoc := v.peek(0) == '/'

		for {
			if isEOL(v.peek(0)) || isEOF(v.peek(0)) {
				if isDoc {
					v.pushToken(TOKEN_DOCCOMMENT)
				} else {
					v.pushToken(TOKEN_COMMENT)
				}
				v.consume()
				goto start
			}
			v.consume()
		}
	}

	//v.printBuffer()
	v.discardBuffer()
}

func (v *lexer) recognizeNumberToken() {
	v.consume()

	if v.peek(0) == 'x' || v.peek(0) == 'X' {
		// Hexadecimal
		v.consume()
		for isHexDigit(v.peek(0)) || v.peek(0) == '_' {
			v.consume()
		}
		v.pushToken(TOKEN_NUMBER)
	} else if v.peek(0) == 'b' {
		// Binary
		v.consume()
		for isBinaryDigit(v.peek(0)) || v.peek(0) == '_' {
			v.consume()
		}
		v.pushToken(TOKEN_NUMBER)
	} else if v.peek(0) == 'o' {
		// Octal
		v.consume()
		for isOctalDigit(v.peek(0)) || v.peek(0) == '_' {
			v.consume()
		}
		v.pushToken(TOKEN_NUMBER)
	} else {
		// Decimal or floating
		for {
			if isDecimalDigit(v.peek(0)) || v.peek(0) == '_' || v.peek(0) == '.' {
				v.consume()
				continue
			} else if v.peek(0) == 'f' || v.peek(0) == 'd' {
				v.consume()
			}
			v.pushToken(TOKEN_NUMBER)
			return
		}
	}
}

func (v *lexer) recognizeIdentifierToken() {
	v.consume()

	for isLetter(v.peek(0)) || isDecimalDigit(v.peek(0)) || v.peek(0) == '_' {
		v.consume()
	}

	v.pushToken(TOKEN_IDENTIFIER)
}

func (v *lexer) recognizeStringToken() {
	lineNumber := v.lineNumber
	charNumber := v.charNumber

	v.expect('"')

	for {
		if v.peek(0) == '\\' && v.peek(1) == '"' {
			v.consume()
			v.consume()
		} else if v.peek(0) == '"' {
			v.consume()
			v.pushToken(TOKEN_STRING)
			return
		} else if isEOF(v.peek(0)) {
			v.errWithCustomPosition(lineNumber, charNumber, "Unterminated string literal")
		} else {
			v.consume()
		}
	}
}

func (v *lexer) recognizeCharacterToken() {
	lineNumber := v.lineNumber
	charNumber := v.charNumber

	v.expect('\'')

	if v.peek(0) == '\'' {
		v.err("Empty character constant")
	}

	for {
		if v.peek(0) == '\\' && (v.peek(1) == '\'' || v.peek(1) == '\\') {
			v.consume()
			v.consume()
		} else if v.peek(0) == '\'' {
			v.consume()
			v.pushToken(TOKEN_RUNE)
			return
		} else if isEOF(v.peek(0)) {
			v.errWithCustomPosition(lineNumber, charNumber, "Unterminated character literal")
		} else {
			v.consume()
		}
	}
}

func (v *lexer) recognizeOperatorToken() {
	// stop := from being treated as an operator
	// treat them as individual operators instead.
	if v.peek(0) == ':' && v.peek(1) == '=' {
		v.consume()
	} else {
		v.consume()
		if isOperator(v.peek(0)) && v.peek(0) != '^' {
			v.consume()
		}
	}

	v.pushToken(TOKEN_OPERATOR)
}

func (v *lexer) recognizeSeparatorToken() {
	v.consume()
	v.pushToken(TOKEN_SEPARATOR)
}

func isDecimalDigit(r rune) bool {
	return r >= '0' && r <= '9'
}

func isHexDigit(r rune) bool {
	return isDecimalDigit(r) || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
}

func isBinaryDigit(r rune) bool {
	return r == '0' || r == '1'
}

func isOctalDigit(r rune) bool {
	return r >= '0' && r <= '7'
}

func isLetter(r rune) bool {
	return unicode.IsLetter(r)
}

func isOperator(r rune) bool {
	return strings.ContainsRune("+-*/=><!~?:|&%^", r)
}

func isExpressionOperator(r rune) bool {
	return strings.ContainsRune("+-*/=><!~?:|&%^()", r) // this is unused?
}

func isSeparator(r rune) bool {
	return strings.ContainsRune(" ;,.`@(){}[]", r)
}

func isEOL(r rune) bool {
	return r == '\n'
}

func isEOF(r rune) bool {
	return r == 0
}

func isLayout(r rune) bool {
	return (r <= ' ' || unicode.IsSpace(r)) && !isEOF(r)
}
<commit_msg>remove thing from lexer<commit_after>package lexer

// Note that most of the panic() calls should be removed once the lexer is bug-free.

import (
	"fmt"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/ark-lang/ark/util"
)

type lexer struct {
	input                      []rune
	startPos, endPos           int
	output                     []*Token
	filename                   string
	charNumber, lineNumber     int
	tokStartChar, tokStartLine int
	verbose                    bool
}

func (v *lexer) errWithCustomPosition(ln, cn int, err string, stuff ...interface{}) {
	fmt.Printf(util.TEXT_RED+util.TEXT_BOLD+"Lexer error:"+util.TEXT_RESET+" [%s:%d:%d] %s\n",
		v.filename, ln, cn, fmt.Sprintf(err, stuff...))
	os.Exit(1)
}

func (v *lexer) err(err string, stuff ...interface{}) {
	v.errWithCustomPosition(v.lineNumber, v.charNumber, err, stuff...)
}

func (v *lexer) peek(ahead int) rune {
	if ahead < 0 {
		panic(fmt.Sprintf("Tried to peek a negative number: %d", ahead))
	}

	if v.endPos+ahead >= len(v.input) {
		return 0
	}
	return v.input[v.endPos+ahead]
}

func (v *lexer) consume() {
	v.charNumber++
	if v.peek(0) == '\n' {
		v.charNumber = 1
		v.lineNumber++
	}

	v.endPos++
}

func (v *lexer) expect(r rune) {
	if v.peek(0) == r {
		v.consume()
	} else {
		v.err("Expected `%c`, found `%c`", r, v.peek(0))
	}
}

func (v *lexer) discardBuffer() {
	v.startPos = v.endPos

	v.tokStartChar = v.charNumber
	v.tokStartLine = v.lineNumber
}

// debugging func
func (v *lexer) printBuffer() {
	fmt.Printf("[%d:%d] `%s`\n", v.startPos, v.endPos, string(v.input[v.startPos:v.endPos]))
}

func (v *lexer) pushToken(t TokenType) {
	tok := &Token{
		Type:          t,
		Filename:      v.filename,
		CharNumber:    v.tokStartChar,
		LineNumber:    v.tokStartLine,
		EndCharNumber: v.charNumber,
		EndLineNumber: v.lineNumber,
		Contents:      string(v.input[v.startPos:v.endPos]),
	}

	v.output = append(v.output, tok)

	if v.verbose {
		fmt.Printf("[%4d:%4d:%-17s] `%s`\n", v.startPos, v.endPos, tok.Type, tok.Contents)
	}

	v.discardBuffer()
}

func Lex(input []rune, filename string, verbose bool) []*Token {
	v := &lexer{
		input:        input,
		startPos:     0,
		endPos:       0,
		filename:     filename,
		charNumber:   1,
		lineNumber:   1,
		tokStartChar: 1,
		tokStartLine: 1,
		verbose:      verbose,
	}

	if v.verbose {
		fmt.Println(util.TEXT_BOLD+util.TEXT_GREEN+"Starting lexing"+util.TEXT_RESET, filename)
	}
	t := time.Now()
	v.lex()
	dur := time.Since(t)
	if v.verbose {
		fmt.Printf(util.TEXT_BOLD+util.TEXT_GREEN+"Finished lexing"+util.TEXT_RESET+" %s (%.2fms)\n",
			filename, float32(dur)/1000000)
	}
	return v.output
}

func (v *lexer) lex() {
	for {
		v.skipLayoutAndComments()

		if isEOF(v.peek(0)) {
			return
		}

		if isDecimalDigit(v.peek(0)) {
			v.recognizeNumberToken()
		} else if isLetter(v.peek(0)) || v.peek(0) == '_' {
			v.recognizeIdentifierToken()
		} else if v.peek(0) == '"' {
			v.recognizeStringToken()
		} else if v.peek(0) == '\'' {
			v.recognizeCharacterToken()
		} else if isOperator(v.peek(0)) {
			v.recognizeOperatorToken()
		} else if isSeparator(v.peek(0)) {
			v.recognizeSeparatorToken()
		} else {
			v.err("Unrecognised token")
		}
	}
}

func (v *lexer) skipLayoutAndComments() {
start:
	for isLayout(v.peek(0)) {
		v.consume()
	}

	v.discardBuffer()

	lineNumber := v.lineNumber
	charNumber := v.charNumber

	// Block comments
	if v.peek(0) == '/' && v.peek(1) == '*' {
		v.consume()
		v.consume()
		isDoc := v.peek(0) == '*'

		for {
			if isEOF(v.peek(0)) {
				v.errWithCustomPosition(lineNumber, charNumber, "Unterminated block comment")
			}
			if v.peek(0) == '*' && v.peek(1) == '/' {
				v.consume()
				v.consume()
				if isDoc {
					v.pushToken(TOKEN_DOCCOMMENT)
				} else {
					v.pushToken(TOKEN_COMMENT)
				}
				goto start
			}
			v.consume()
		}
	}

	// Single-line comments
	if v.peek(0) == '/' && v.peek(1) == '/' {
		v.consume()
		v.consume()
		isDoc := v.peek(0) == '/'

		for {
			if isEOL(v.peek(0)) || isEOF(v.peek(0)) {
				if isDoc {
					v.pushToken(TOKEN_DOCCOMMENT)
				} else {
					v.pushToken(TOKEN_COMMENT)
				}
				v.consume()
				goto start
			}
			v.consume()
		}
	}

	//v.printBuffer()
	v.discardBuffer()
}

func (v *lexer) recognizeNumberToken() {
	v.consume()

	if v.peek(0) == 'x' || v.peek(0) == 'X' {
		// Hexadecimal
		v.consume()
		for isHexDigit(v.peek(0)) || v.peek(0) == '_' {
			v.consume()
		}
		v.pushToken(TOKEN_NUMBER)
	} else if v.peek(0) == 'b' {
		// Binary
		v.consume()
		for isBinaryDigit(v.peek(0)) || v.peek(0) == '_' {
			v.consume()
		}
		v.pushToken(TOKEN_NUMBER)
	} else if v.peek(0) == 'o' {
		// Octal
		v.consume()
		for isOctalDigit(v.peek(0)) || v.peek(0) == '_' {
			v.consume()
		}
		v.pushToken(TOKEN_NUMBER)
	} else {
		// Decimal or floating
		for {
			if isDecimalDigit(v.peek(0)) || v.peek(0) == '_' || v.peek(0) == '.' {
				v.consume()
				continue
			} else if v.peek(0) == 'f' || v.peek(0) == 'd' {
				v.consume()
			}
			v.pushToken(TOKEN_NUMBER)
			return
		}
	}
}

func (v *lexer) recognizeIdentifierToken() {
	v.consume()

	for isLetter(v.peek(0)) || isDecimalDigit(v.peek(0)) || v.peek(0) == '_' {
		v.consume()
	}

	v.pushToken(TOKEN_IDENTIFIER)
}

func (v *lexer) recognizeStringToken() {
	lineNumber := v.lineNumber
	charNumber := v.charNumber

	v.expect('"')

	for {
		if v.peek(0) == '\\' && v.peek(1) == '"' {
			v.consume()
			v.consume()
		} else if v.peek(0) == '"' {
			v.consume()
			v.pushToken(TOKEN_STRING)
			return
		} else if isEOF(v.peek(0)) {
			v.errWithCustomPosition(lineNumber, charNumber, "Unterminated string literal")
		} else {
			v.consume()
		}
	}
}

func (v *lexer) recognizeCharacterToken() {
	lineNumber := v.lineNumber
	charNumber := v.charNumber

	v.expect('\'')

	if v.peek(0) == '\'' {
		v.err("Empty character constant")
	}

	for {
		if v.peek(0) == '\\' && (v.peek(1) == '\'' || v.peek(1) == '\\') {
			v.consume()
			v.consume()
		} else if v.peek(0) == '\'' {
			v.consume()
			v.pushToken(TOKEN_RUNE)
			return
		} else if isEOF(v.peek(0)) {
			v.errWithCustomPosition(lineNumber, charNumber, "Unterminated character literal")
		} else {
			v.consume()
		}
	}
}

func (v *lexer) recognizeOperatorToken() {
	// stop := from being treated as an operator
	// treat them as individual operators instead.
	if v.peek(0) == ':' && v.peek(1) == '=' {
		v.consume()
	} else {
		v.consume()
		if isOperator(v.peek(0)) && v.peek(0) != '^' {
			v.consume()
		}
	}

	v.pushToken(TOKEN_OPERATOR)
}

func (v *lexer) recognizeSeparatorToken() {
	v.consume()
	v.pushToken(TOKEN_SEPARATOR)
}

func isDecimalDigit(r rune) bool {
	return r >= '0' && r <= '9'
}

func isHexDigit(r rune) bool {
	return isDecimalDigit(r) || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
}

func isBinaryDigit(r rune) bool {
	return r == '0' || r == '1'
}

func isOctalDigit(r rune) bool {
	return r >= '0' && r <= '7'
}

func isLetter(r rune) bool {
	return unicode.IsLetter(r)
}

func isOperator(r rune) bool {
	return strings.ContainsRune("+-*/=><!~?:|&%^", r)
}

func isSeparator(r rune) bool {
	return strings.ContainsRune(" ;,.`@(){}[]", r)
}

func isEOL(r rune) bool {
	return r == '\n'
}

func isEOF(r rune) bool {
	return r == 0
}

func isLayout(r rune) bool {
	return (r <= ' ' || unicode.IsSpace(r)) && !isEOF(r)
}
<|endoftext|>


<commit_before>package jirardeau

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/pkg/errors"
)

const (
	IssueTypeBug      = "1"
	IssueTypeTask     = "3"
	IssueTypePostTask = "25"
)

// Jira holds Url like https://jira.tld
type Jira struct {
	Log       *log.Logger
	Login     string
	Password  string
	Project   string
	ProjectID string
	URL       string
}

// Project holds JIRA Project
type Project struct {
	ID   string `json:"id"`
	Self string `json:"self"`
	Key  string `json:"key"`
	Name string `json:"name"`
}

// FixVersion holds JIRA Version
// Fields field used to customize issue fields
type FixVersion struct {
	Archived        bool   `json:"archived"`
	ID              string `json:"id"`
	Name            string `json:"name"`
	Overdue         bool   `json:"overdue"`
	ProjectID       int    `json:"projectId"`
	ReleaseDate     string `json:"releaseDate"`
	Released        bool   `json:"released"`
	Self            string `json:"self"`
	StartDate       string `json:"startDate"`
	UserReleaseDate string `json:"userReleaseDate"`
	UserStartDate   string `json:"userStartDate"`
	Fields          string `json:"-"`
}

// Issue holds issue data
type Issue struct {
	ID     string            `json:"id"`
	Self   string            `json:"self"`
	Key    string            `json:"key"`
	Fields IssueFields       `json:"fields"`
	Expand string            `json:"expand"`
	Names  map[string]string `json:"names"`
}

// IssueFields holds default fields
type IssueFields struct {
	Project      Project      `json:"project"`
	Summary      string       `json:"summary"`
	IssueType    IssueType    `json:"issuetype"`
	FixVersions  []FixVersion `json:"fixVersions"`
	Status       Status       `json:"status"`
	Created      string       `json:"created"`
	Description  string       `json:"description"`
	Comment      CommentField `json:"comment"`
	CustomFields CustomField  `json:"-"`
}

// CustomField holds custom field name and value
type CustomField map[string]string

// IssueType describes Issue type
type IssueType struct {
	ID          string `json:"id"`
	Self        string `json:"self"`
	Name        string `json:"name"`
	SubTask     bool   `json:"subtask"`
	Description string `json:"description"`
}

// CommentField holds Issue Comments
type CommentField struct {
	StartAt    int       `json:"startAt"`
	MaxResults int       `json:"maxResults"`
	Total      int       `json:"total"`
	Comments   []Comment `json:"comments"`
}

// Comment of Issue
type Comment struct {
	ID           string `json:"id"`
	Self         string `json:"self"`
	Author       Author `json:"author"`
	UpdateAuthor Author `json:"updateAuthor"`
	Body         string `json:"body"`
	Created      string `json:"created"`
	Updated      string `json:"updated"`
}

// Author of Issue or Comment
type Author struct {
	Self         string `json:"self"`
	Active       bool   `json:"active"`
	Name         string `json:"name"`
	DisplayName  string `json:"displayName"`
	EmailAddress string `json:"emailAddress"`
}

// Status of Issue
type Status struct {
	ID          string `json:"id"`
	Self        string `json:"self"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

// RequestCreateIssue creates issue
type RequestCreateIssue struct {
	Fields ModifyIssueFields `json:"fields"`
}

// RequestCreateIssue creates issue
type RequestUpdateIssue struct {
	Key    string            `json:"key"`
	Fields ModifyIssueFields `json:"fields"`
}

// CreateIssueFields used only for creating issues
type ModifyIssueFields struct {
	Project      Project      `json:"project"`
	Summary      string       `json:"summary"`
	IssueType    IssueType    `json:"issuetype"`
	FixVersions  []FixVersion `json:"fixVersions"`
	Description  string       `json:"description"`
	CustomFields CustomField  `json:"-"`
}

func (jira *Jira) request(method, relURL string, reqBody io.Reader) (respBody io.Reader, err error) {
	absURL, err := url.Parse(jira.URL + relURL)
	if err != nil {
		err = fmt.Errorf("Failed to parse %s and %s to URL: %s", jira.URL, relURL, err)
		jira.Log.Println(err)
		return
	}
	jira.Log.Println("STRT", method, absURL.String())

	req, err := http.NewRequest(method, absURL.String(), reqBody)
	if err != nil {
		err = fmt.Errorf("Failed to build HTTP request %s %s: %s", method, absURL.String(), err)
		jira.Log.Println(err)
		return
	}
	req.Header.Set("content-type", "application/json")
	req.SetBasicAuth(jira.Login, jira.Password)

	var buf bytes.Buffer
	resp, err := http.DefaultClient.Do(req)
	if resp != nil {
		defer resp.Body.Close()

		_, err = buf.ReadFrom(resp.Body)
		if err != nil {
			err = fmt.Errorf("Failed to read response from JIRA request %s %s: %s", method, absURL.String(), err)
			jira.Log.Println(err)
			return
		}
		respBody = &buf
		switch {
		case resp.StatusCode == 401:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: Unauthorized (401)", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode == 404:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: Wrong request", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode == 405:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: HTTP method is not allowed for the requested resource", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode == 415:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: Unsupported Media Type", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode == 502:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: Bad gateway", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode >= 400:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: %s", method, absURL.String(), resp.StatusCode, buf.String())
			jira.Log.Println(err)
			return
		}
	}

	if err != nil {
		err = fmt.Errorf("Failed to JIRA request %s %s: %s", method, absURL.String(), err)
		jira.Log.Println(err)
		return
	}

	jira.Log.Println("StatusCode:", resp.StatusCode)
	jira.Log.Println("Headers:", resp.Header)

	jira.Log.Println("DONE", method, absURL.String())
	return
}

// GetFixVersions returns versions of Jira.Project
func (jira *Jira) GetFixVersions() (releases []FixVersion, err error) {
	relURL := fmt.Sprintf("/project/%s/versions", jira.Project)
	resp, err := jira.request("GET", relURL, nil)
	if err != nil {
		return
	}
	err = json.NewDecoder(resp).Decode(&releases)
	if err != nil {
		return
	}

	return
}

// GetIssues returns issues of fixVersion specified by FixVersion
func (jira *Jira) GetIssues(fixVersion FixVersion) (issues map[string]Issue, err error) {
	var result struct {
		Issues []Issue `json:"issues"`
	}

	parameters := url.Values{}
	parameters.Add("jql", fmt.Sprintf(`project = %s AND fixVersion = "%s"`, jira.Project, fixVersion.Name))
	if fixVersion.Fields == "" {
		parameters.Add("fields", "id,key,self,summary,issuetype,status,description,created")
	} else {
		parameters.Add("fields", fixVersion.Fields)
	}

	relURL := fmt.Sprintf("/search?%s", parameters.Encode())

	resp, err := jira.request("GET", relURL, nil)
	if err != nil {
		return
	}
	err = json.NewDecoder(resp).Decode(&result)
	if err != nil {
		err = errors.Wrap(err, "decode failed")
		return
	}

	issues = make(map[string]Issue)
	for _, issue := range result.Issues {
		issues[issue.Key] = issue
	}

	return
}

// GetIssue by id/key
func (jira *Jira) GetIssue(id string, expand []string) (issue Issue, err error) {
	parameters := url.Values{}
	if expand != nil {
		parameters.Add("expand", strings.Join(expand, ","))
	}

	relURL := fmt.Sprintf("/issue/%s?%s", id, parameters.Encode())

	resp, err := jira.request("GET", relURL, nil)
	if err != nil {
		return
	}

	err = json.NewDecoder(resp).Decode(&issue)
	if err != nil {
		err = errors.Wrap(err, "decode failed")
		return
	}

	return
}

// CreateIssue creates issue based on filled fields
func (jira *Jira) CreateIssue(request RequestCreateIssue) (issue Issue, err error) {
	var buf bytes.Buffer
	err = json.NewEncoder(&buf).Encode(request)
	if err != nil {
		return issue, errors.Wrap(err, "failed create issue")
	}

	resp, err := jira.request("POST", "/issue", &buf)
	if err != nil {
		return issue, errors.Wrap(err, "failed create issue")
	}

	err = json.NewDecoder(resp).Decode(&issue)
	if err != nil {
		return issue, errors.Wrap(err, "failed create issue, failed to decode response")
	}

	issue.Fields.Description = request.Fields.Description
	issue.Fields.Project = request.Fields.Project
	issue.Fields.Summary = request.Fields.Summary
	issue.Fields.IssueType = request.Fields.IssueType
	issue.Fields.FixVersions = request.Fields.FixVersions
	issue.Fields.CustomFields = request.Fields.CustomFields

	return issue, nil
}

// UpdateIssue update existed issue with new fields values
func (jira *Jira) UpdateIssue(request RequestUpdateIssue) error {
	if request.Key == "" {
		return errors.New("failed update issue: issue Key is empty")
	}
	var buf bytes.Buffer
	err := json.NewEncoder(&buf).Encode(request)
	if err != nil {
		return errors.Wrap(err, "failed update issue")
	}

	_, err = jira.request("PUT", fmt.Sprintf("/issue/%s", request.Key), &buf)
	if err != nil {
		return errors.Wrap(err, "failed update issue")
	}

	return nil
}

// MarshalJSON encapsulate CustomFields in CreateIssueFields
// and handle JIRA's requirement of allowed fields for POST/PUT query
func (fields ModifyIssueFields) MarshalJSON() ([]byte, error) {
	cf := make(map[string]CustomField)

	for key, val := range fields.CustomFields {
		subCf := make(CustomField)
		subCf["value"] = val
		cf[key] = subCf
	}

	bytesCf, err := json.Marshal(cf)
	if err != nil {
		return nil, err
	}

	type AliasIssueFields struct {
		Project     Project      `json:"project"`
		Summary     string       `json:"summary"`
		IssueType   IssueType    `json:"issuetype"`
		FixVersions []FixVersion `json:"fixVersions"`
		Description string       `json:"description"`
	}

	issueFields := AliasIssueFields{}
	issueFields.Description = fields.Description
	issueFields.FixVersions = fields.FixVersions
	issueFields.IssueType = fields.IssueType
	issueFields.Project = fields.Project
	issueFields.Summary = fields.Summary

	bytesFields, err := json.Marshal(issueFields)
	if err != nil {
		return nil, err
	}
	bytesCf = bytes.TrimSuffix(bytesCf, []byte("}"))
	bytesFields = bytes.TrimPrefix(bytesFields, []byte("{"))

	allFields := [][]byte{
		bytesCf,
		bytesFields,
	}
	allBytes := bytes.Join(allFields, []byte(","))

	return allBytes, nil
}

// UnmarshalJSON gather custom fields values into CustomFields
func (fields *IssueFields) UnmarshalJSON(data []byte) (err error) {
	type AliasIssueFields IssueFields
	issueFields := AliasIssueFields{}
	err = json.Unmarshal(data, &issueFields)
	if err != nil {
		return
	}

	fields.Comment = issueFields.Comment
	fields.Status = issueFields.Status
	fields.Created = issueFields.Created
	fields.Description = issueFields.Description
	fields.FixVersions = issueFields.FixVersions
	fields.IssueType = issueFields.IssueType
	fields.Project = issueFields.Project

	fields.Summary = issueFields.Summary

	cf := make(map[string]interface{})

	err = json.Unmarshal(data, &cf)
	if err != nil {
		return
	}

	if fields.CustomFields == nil {
		fields.CustomFields = make(CustomField)
	}

	for key, val := range cf {
		if strings.HasPrefix(key, "customfield_") {

			switch val.(type) {
			case map[string]interface{}:
				for subKey, subVal := range val.(map[string]interface{}) {
					if strings.HasPrefix(subKey, "value") {
						switch subVal.(type) {
						case string:
							fields.CustomFields[key] = subVal.(string)
						}
					}
				}
			case string:
				fields.CustomFields[key] = val.(string)
			case nil:
				fields.CustomFields[key] = ""
			}
		}
	}

	return
}
<commit_msg>Add "comment" field to GetIssues<commit_after>package jirardeau

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/pkg/errors"
)

const (
	IssueTypeBug      = "1"
	IssueTypeTask     = "3"
	IssueTypePostTask = "25"
)

// Jira holds Url like https://jira.tld
type Jira struct {
	Log       *log.Logger
	Login     string
	Password  string
	Project   string
	ProjectID string
	URL       string
}

// Project holds JIRA Project
type Project struct {
	ID   string `json:"id"`
	Self string `json:"self"`
	Key  string `json:"key"`
	Name string `json:"name"`
}

// FixVersion holds JIRA Version
// Fields field used to customize issue fields
type FixVersion struct {
	Archived        bool   `json:"archived"`
	ID              string `json:"id"`
	Name            string `json:"name"`
	Overdue         bool   `json:"overdue"`
	ProjectID       int    `json:"projectId"`
	ReleaseDate     string `json:"releaseDate"`
	Released        bool   `json:"released"`
	Self            string `json:"self"`
	StartDate       string `json:"startDate"`
	UserReleaseDate string `json:"userReleaseDate"`
	UserStartDate   string `json:"userStartDate"`
	Fields          string `json:"-"`
}

// Issue holds issue data
type Issue struct {
	ID     string            `json:"id"`
	Self   string            `json:"self"`
	Key    string            `json:"key"`
	Fields IssueFields       `json:"fields"`
	Expand string            `json:"expand"`
	Names  map[string]string `json:"names"`
}

// IssueFields holds default fields
type IssueFields struct {
	Project      Project      `json:"project"`
	Summary      string       `json:"summary"`
	IssueType    IssueType    `json:"issuetype"`
	FixVersions  []FixVersion `json:"fixVersions"`
	Status       Status       `json:"status"`
	Created      string       `json:"created"`
	Description  string       `json:"description"`
	Comment      CommentField `json:"comment"`
	CustomFields CustomField  `json:"-"`
}

// CustomField holds custom field name and value
type CustomField map[string]string

// IssueType describes Issue type
type IssueType struct {
	ID          string `json:"id"`
	Self        string `json:"self"`
	Name        string `json:"name"`
	SubTask     bool   `json:"subtask"`
	Description string `json:"description"`
}

// CommentField holds Issue Comments
type CommentField struct {
	StartAt    int       `json:"startAt"`
	MaxResults int       `json:"maxResults"`
	Total      int       `json:"total"`
	Comments   []Comment `json:"comments"`
}

// Comment of Issue
type Comment struct {
	ID           string `json:"id"`
	Self         string `json:"self"`
	Author       Author `json:"author"`
	UpdateAuthor Author `json:"updateAuthor"`
	Body         string `json:"body"`
	Created      string `json:"created"`
	Updated      string `json:"updated"`
}

// Author of Issue or Comment
type Author struct {
	Self         string `json:"self"`
	Active       bool   `json:"active"`
	Name         string `json:"name"`
	DisplayName  string `json:"displayName"`
	EmailAddress string `json:"emailAddress"`
}

// Status of Issue
type Status struct {
	ID          string `json:"id"`
	Self        string `json:"self"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

// RequestCreateIssue creates issue
type RequestCreateIssue struct {
	Fields ModifyIssueFields `json:"fields"`
}

// RequestCreateIssue creates issue
type RequestUpdateIssue struct {
	Key    string            `json:"key"`
	Fields ModifyIssueFields `json:"fields"`
}

// CreateIssueFields used only for creating issues
type ModifyIssueFields struct {
	Project      Project      `json:"project"`
	Summary      string       `json:"summary"`
	IssueType    IssueType    `json:"issuetype"`
	FixVersions  []FixVersion `json:"fixVersions"`
	Description  string       `json:"description"`
	CustomFields CustomField  `json:"-"`
}

func (jira *Jira) request(method, relURL string, reqBody io.Reader) (respBody io.Reader, err error) {
	absURL, err := url.Parse(jira.URL + relURL)
	if err != nil {
		err = fmt.Errorf("Failed to parse %s and %s to URL: %s", jira.URL, relURL, err)
		jira.Log.Println(err)
		return
	}
	jira.Log.Println("STRT", method, absURL.String())

	req, err := http.NewRequest(method, absURL.String(), reqBody)
	if err != nil {
		err = fmt.Errorf("Failed to build HTTP request %s %s: %s", method, absURL.String(), err)
		jira.Log.Println(err)
		return
	}
	req.Header.Set("content-type", "application/json")
	req.SetBasicAuth(jira.Login, jira.Password)

	var buf bytes.Buffer
	resp, err := http.DefaultClient.Do(req)
	if resp != nil {
		defer resp.Body.Close()

		_, err = buf.ReadFrom(resp.Body)
		if err != nil {
			err = fmt.Errorf("Failed to read response from JIRA request %s %s: %s", method, absURL.String(), err)
			jira.Log.Println(err)
			return
		}
		respBody = &buf
		switch {
		case resp.StatusCode == 401:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: Unauthorized (401)", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode == 404:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: Wrong request", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode == 405:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: HTTP method is not allowed for the requested resource", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode == 415:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: Unsupported Media Type", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode == 502:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: Bad gateway", method, absURL.String(), resp.StatusCode)
			jira.Log.Println(err)
			return
		case resp.StatusCode >= 400:
			err = fmt.Errorf("Failed to JIRA request %s %s with HTTP code %d: %s", method, absURL.String(), resp.StatusCode, buf.String())
			jira.Log.Println(err)
			return
		}
	}

	if err != nil {
		err = fmt.Errorf("Failed to JIRA request %s %s: %s", method, absURL.String(), err)
		jira.Log.Println(err)
		return
	}

	jira.Log.Println("StatusCode:", resp.StatusCode)
	jira.Log.Println("Headers:", resp.Header)

	jira.Log.Println("DONE", method, absURL.String())
	return
}

// GetFixVersions returns versions of Jira.Project
func (jira *Jira) GetFixVersions() (releases []FixVersion, err error) {
	relURL := fmt.Sprintf("/project/%s/versions", jira.Project)
	resp, err := jira.request("GET", relURL, nil)
	if err != nil {
		return
	}
	err = json.NewDecoder(resp).Decode(&releases)
	if err != nil {
		return
	}

	return
}

// GetIssues returns issues of fixVersion specified by FixVersion
func (jira *Jira) GetIssues(fixVersion FixVersion) (issues map[string]Issue, err error) {
	var result struct {
		Issues []Issue `json:"issues"`
	}

	parameters := url.Values{}
	parameters.Add("jql", fmt.Sprintf(`project = %s AND fixVersion = "%s"`, jira.Project, fixVersion.Name))
	if fixVersion.Fields == "" {
		parameters.Add("fields", "id,key,self,summary,issuetype,status,description,created,comment")
	} else {
		parameters.Add("fields", fixVersion.Fields)
	}

	relURL := fmt.Sprintf("/search?%s", parameters.Encode())

	resp, err := jira.request("GET", relURL, nil)
	if err != nil {
		return
	}
	err = json.NewDecoder(resp).Decode(&result)
	if err != nil {
		err = errors.Wrap(err, "decode failed")
		return
	}

	issues = make(map[string]Issue)
	for _, issue := range result.Issues {
		issues[issue.Key] = issue
	}

	return
}

// GetIssue by id/key
func (jira *Jira) GetIssue(id string, expand []string) (issue Issue, err error) {
	parameters := url.Values{}
	if expand != nil {
		parameters.Add("expand", strings.Join(expand, ","))
	}

	relURL := fmt.Sprintf("/issue/%s?%s", id, parameters.Encode())

	resp, err := jira.request("GET", relURL, nil)
	if err != nil {
		return
	}

	err = json.NewDecoder(resp).Decode(&issue)
	if err != nil {
		err = errors.Wrap(err, "decode failed")
		return
	}

	return
}

// CreateIssue creates issue based on filled fields
func (jira *Jira) CreateIssue(request RequestCreateIssue) (issue Issue, err error) {
	var buf bytes.Buffer
	err = json.NewEncoder(&buf).Encode(request)
	if err != nil {
		return issue, errors.Wrap(err, "failed create issue")
	}

	resp, err := jira.request("POST", "/issue", &buf)
	if err != nil {
		return issue, errors.Wrap(err, "failed create issue")
	}

	err = json.NewDecoder(resp).Decode(&issue)
	if err != nil {
		return issue, errors.Wrap(err, "failed create issue, failed to decode response")
	}

	issue.Fields.Description = request.Fields.Description
	issue.Fields.Project = request.Fields.Project
	issue.Fields.Summary = request.Fields.Summary
	issue.Fields.IssueType = request.Fields.IssueType
	issue.Fields.FixVersions = request.Fields.FixVersions
	issue.Fields.CustomFields = request.Fields.CustomFields

	return issue, nil
}

// UpdateIssue update existed issue with new fields values
func (jira *Jira) UpdateIssue(request RequestUpdateIssue) error {
	if request.Key == "" {
		return errors.New("failed update issue: issue Key is empty")
	}
	var buf bytes.Buffer
	err := json.NewEncoder(&buf).Encode(request)
	if err != nil {
		return errors.Wrap(err, "failed update issue")
	}

	_, err = jira.request("PUT", fmt.Sprintf("/issue/%s", request.Key), &buf)
	if err != nil {
		return errors.Wrap(err, "failed update issue")
	}

	return nil
}

// MarshalJSON encapsulate CustomFields in CreateIssueFields
// and handle JIRA's requirement of allowed fields for POST/PUT query
func (fields ModifyIssueFields) MarshalJSON() ([]byte, error) {
	cf := make(map[string]CustomField)

	for key, val := range fields.CustomFields {
		subCf := make(CustomField)
		subCf["value"] = val
		cf[key] = subCf
	}

	bytesCf, err := json.Marshal(cf)
	if err != nil {
		return nil, err
	}

	type AliasIssueFields struct {
		Project     Project      `json:"project"`
		Summary     string       `json:"summary"`
		IssueType   IssueType    `json:"issuetype"`
		FixVersions []FixVersion `json:"fixVersions"`
		Description string       `json:"description"`
	}

	issueFields := AliasIssueFields{}
	issueFields.Description = fields.Description
	issueFields.FixVersions = fields.FixVersions
	issueFields.IssueType = fields.IssueType
	issueFields.Project = fields.Project
	issueFields.Summary = fields.Summary

	bytesFields, err := json.Marshal(issueFields)
	if err != nil {
		return nil, err
	}
	bytesCf = bytes.TrimSuffix(bytesCf, []byte("}"))
	bytesFields = bytes.TrimPrefix(bytesFields, []byte("{"))

	allFields := [][]byte{
		bytesCf,
		bytesFields,
	}
	allBytes := bytes.Join(allFields, []byte(","))

	return allBytes, nil
}

// UnmarshalJSON gather custom fields values into CustomFields
func (fields *IssueFields) UnmarshalJSON(data []byte) (err error) {
	type AliasIssueFields IssueFields
	issueFields := AliasIssueFields{}
	err = json.Unmarshal(data, &issueFields)
	if err != nil {
		return
	}

	fields.Comment = issueFields.Comment
	fields.Status = issueFields.Status
	fields.Created = issueFields.Created
	fields.Description = issueFields.Description
	fields.FixVersions = issueFields.FixVersions
	fields.IssueType = issueFields.IssueType
	fields.Project = issueFields.Project

	fields.Summary = issueFields.Summary

	cf := make(map[string]interface{})

	err = json.Unmarshal(data, &cf)
	if err != nil {
		return
	}

	if fields.CustomFields == nil {
		fields.CustomFields = make(CustomField)
	}

	for key, val := range cf {
		if strings.HasPrefix(key, "customfield_") {

			switch val.(type) {
			case map[string]interface{}:
				for subKey, subVal := range val.(map[string]interface{}) {
					if strings.HasPrefix(subKey, "value") {
						switch subVal.(type) {
						case string:
							fields.CustomFields[key] = subVal.(string)
						}
					}
				}
			case string:
				fields.CustomFields[key] = val.(string)
			case nil:
				fields.CustomFields[key] = ""
			}
		}
	}

	return
}
<|endoftext|>


<commit_before>package config

import (
	"errors"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"

	rt "github.com/zyedidia/micro/v2/runtime"
)

const (
	RTColorscheme  = 0
	RTSyntax       = 1
	RTHelp         = 2
	RTPlugin       = 3
	RTSyntaxHeader = 4
)

var (
	NumTypes = 5 // How many filetypes are there
)

type RTFiletype int

// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
type RuntimeFile interface {
	// Name returns a name of the file without paths or extensions
	Name() string
	// Data returns the content of the file.
	Data() ([]byte, error)
}

// allFiles contains all available files, mapped by filetype
var allFiles [][]RuntimeFile
var realFiles [][]RuntimeFile

func init() {
	allFiles = make([][]RuntimeFile, NumTypes)
	realFiles = make([][]RuntimeFile, NumTypes)
}

// NewRTFiletype creates a new RTFiletype
func NewRTFiletype() int {
	NumTypes++
	allFiles = append(allFiles, []RuntimeFile{})
	realFiles = append(realFiles, []RuntimeFile{})
	return NumTypes - 1
}

// some file on filesystem
type realFile string

// some asset file
type assetFile string

// some file on filesystem but with a different name
type namedFile struct {
	realFile
	name string
}

// a file with the data stored in memory
type memoryFile struct {
	name string
	data []byte
}

func (mf memoryFile) Name() string {
	return mf.name
}
func (mf memoryFile) Data() ([]byte, error) {
	return mf.data, nil
}

func (rf realFile) Name() string {
	fn := filepath.Base(string(rf))
	return fn[:len(fn)-len(filepath.Ext(fn))]
}

func (rf realFile) Data() ([]byte, error) {
	return ioutil.ReadFile(string(rf))
}

func (af assetFile) Name() string {
	fn := path.Base(string(af))
	return fn[:len(fn)-len(path.Ext(fn))]
}

func (af assetFile) Data() ([]byte, error) {
	return rt.Asset(string(af))
}

func (nf namedFile) Name() string {
	return nf.name
}

// AddRuntimeFile registers a file for the given filetype
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
	allFiles[fileType] = append(allFiles[fileType], file)
}

// AddRealRuntimeFile registers a file for the given filetype
func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
	allFiles[fileType] = append(allFiles[fileType], file)
	realFiles[fileType] = append(realFiles[fileType], file)
}

// AddRuntimeFilesFromDirectory registers each file from the given directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
	files, _ := ioutil.ReadDir(directory)
	for _, f := range files {
		if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
			fullPath := filepath.Join(directory, f.Name())
			AddRealRuntimeFile(fileType, realFile(fullPath))
		}
	}
}

// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
	files, err := rt.AssetDir(directory)
	if err != nil {
		return
	}
	for _, f := range files {
		if ok, _ := path.Match(pattern, f); ok {
			AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
		}
	}
}

// FindRuntimeFile finds a runtime file of the given filetype and name
// will return nil if no file was found
func FindRuntimeFile(fileType RTFiletype, name string) RuntimeFile {
	for _, f := range ListRuntimeFiles(fileType) {
		if f.Name() == name {
			return f
		}
	}
	return nil
}

// ListRuntimeFiles lists all known runtime files for the given filetype
func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
	return allFiles[fileType]
}

// ListRealRuntimeFiles lists all real runtime files (on disk) for a filetype
// these runtime files will be ones defined by the user and loaded from the config directory
func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
	return realFiles[fileType]
}

// InitRuntimeFiles initializes all assets file and the config directory
func InitRuntimeFiles() {
	add := func(fileType RTFiletype, dir, pattern string) {
		AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
		AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
	}

	add(RTColorscheme, "colorschemes", "*.micro")
	add(RTSyntax, "syntax", "*.yaml")
	add(RTSyntaxHeader, "syntax", "*.hdr")
	add(RTHelp, "help", "*.md")

	initlua := filepath.Join(ConfigDir, "init.lua")
	if _, err := os.Stat(initlua); !os.IsNotExist(err) {
		p := new(Plugin)
		p.Name = "initlua"
		p.DirName = "initlua"
		p.Srcs = append(p.Srcs, realFile(initlua))
		Plugins = append(Plugins, p)
	}

	// Search ConfigDir for plugin-scripts
	plugdir := filepath.Join(ConfigDir, "plug")
	files, _ := ioutil.ReadDir(plugdir)

	isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString

	for _, d := range files {
		if d.IsDir() {
			srcs, _ := ioutil.ReadDir(filepath.Join(plugdir, d.Name()))
			p := new(Plugin)
			p.Name = d.Name()
			p.DirName = d.Name()
			for _, f := range srcs {
				if strings.HasSuffix(f.Name(), ".lua") {
					p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
				} else if strings.HasSuffix(f.Name(), ".json") {
					data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
					if err != nil {
						continue
					}
					p.Info, err = NewPluginInfo(data)
					if err != nil {
						continue
					}
					p.Name = p.Info.Name
				}
			}

			if !isID(p.Name) || len(p.Srcs) <= 0 {
				log.Println(p.Name, "is not a plugin")
				continue
			}
			Plugins = append(Plugins, p)
		}
	}

	plugdir = filepath.Join("runtime", "plugins")
	if files, err := rt.AssetDir(plugdir); err == nil {
		for _, d := range files {
			if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil {
				p := new(Plugin)
				p.Name = d
				p.DirName = d
				p.Default = true
				for _, f := range srcs {
					if strings.HasSuffix(f, ".lua") {
						p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
					} else if strings.HasSuffix(f, ".json") {
						data, err := rt.Asset(filepath.Join(plugdir, d, f))
						if err != nil {
							continue
						}
						p.Info, err = NewPluginInfo(data)
						if err != nil {
							continue
						}
						p.Name = p.Info.Name
					}
				}
				if !isID(p.Name) || len(p.Srcs) <= 0 {
					log.Println(p.Name, "is not a plugin")
					continue
				}
				Plugins = append(Plugins, p)
			}
		}
	}
}

// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
func PluginReadRuntimeFile(fileType RTFiletype, name string) string {
	if file := FindRuntimeFile(fileType, name); file != nil {
		if data, err := file.Data(); err == nil {
			return string(data)
		}
	}
	return ""
}

// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
func PluginListRuntimeFiles(fileType RTFiletype) []string {
	files := ListRuntimeFiles(fileType)
	result := make([]string, len(files))
	for i, f := range files {
		result[i] = f.Name()
	}
	return result
}

// PluginAddRuntimeFile adds a file to the runtime files for a plugin
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) error {
	pl := FindPlugin(plugin)
	if pl == nil {
		return errors.New("Plugin " + plugin + " does not exist")
	}
	pldir := pl.DirName
	fullpath := filepath.Join(ConfigDir, "plug", pldir, filePath)
	if _, err := os.Stat(fullpath); err == nil {
		AddRealRuntimeFile(filetype, realFile(fullpath))
	} else {
		fullpath = path.Join("runtime", "plugins", pldir, filePath)
		AddRuntimeFile(filetype, assetFile(fullpath))
	}
	return nil
}

// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) error {
	pl := FindPlugin(plugin)
	if pl == nil {
		return errors.New("Plugin " + plugin + " does not exist")
	}
	pldir := pl.DirName
	fullpath := filepath.Join(ConfigDir, "plug", pldir, directory)
	if _, err := os.Stat(fullpath); err == nil {
		AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
	} else {
		fullpath = path.Join("runtime", "plugins", pldir, directory)
		AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
	}
	return nil
}

// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
func PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {
	AddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})
}
<commit_msg>plugins: load directories that are symlinks (#2214)<commit_after>package config

import (
	"errors"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"

	rt "github.com/zyedidia/micro/v2/runtime"
)

const (
	RTColorscheme  = 0
	RTSyntax       = 1
	RTHelp         = 2
	RTPlugin       = 3
	RTSyntaxHeader = 4
)

var (
	NumTypes = 5 // How many filetypes are there
)

type RTFiletype int

// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
type RuntimeFile interface {
	// Name returns a name of the file without paths or extensions
	Name() string
	// Data returns the content of the file.
	Data() ([]byte, error)
}

// allFiles contains all available files, mapped by filetype
var allFiles [][]RuntimeFile
var realFiles [][]RuntimeFile

func init() {
	allFiles = make([][]RuntimeFile, NumTypes)
	realFiles = make([][]RuntimeFile, NumTypes)
}

// NewRTFiletype creates a new RTFiletype
func NewRTFiletype() int {
	NumTypes++
	allFiles = append(allFiles, []RuntimeFile{})
	realFiles = append(realFiles, []RuntimeFile{})
	return NumTypes - 1
}

// some file on filesystem
type realFile string

// some asset file
type assetFile string

// some file on filesystem but with a different name
type namedFile struct {
	realFile
	name string
}

// a file with the data stored in memory
type memoryFile struct {
	name string
	data []byte
}

func (mf memoryFile) Name() string {
	return mf.name
}
func (mf memoryFile) Data() ([]byte, error) {
	return mf.data, nil
}

func (rf realFile) Name() string {
	fn := filepath.Base(string(rf))
	return fn[:len(fn)-len(filepath.Ext(fn))]
}

func (rf realFile) Data() ([]byte, error) {
	return ioutil.ReadFile(string(rf))
}

func (af assetFile) Name() string {
	fn := path.Base(string(af))
	return fn[:len(fn)-len(path.Ext(fn))]
}

func (af assetFile) Data() ([]byte, error) {
	return rt.Asset(string(af))
}

func (nf namedFile) Name() string {
	return nf.name
}

// AddRuntimeFile registers a file for the given filetype
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
	allFiles[fileType] = append(allFiles[fileType], file)
}

// AddRealRuntimeFile registers a file for the given filetype
func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
	allFiles[fileType] = append(allFiles[fileType], file)
	realFiles[fileType] = append(realFiles[fileType], file)
}

// AddRuntimeFilesFromDirectory registers each file from the given directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
	files, _ := ioutil.ReadDir(directory)
	for _, f := range files {
		if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
			fullPath := filepath.Join(directory, f.Name())
			AddRealRuntimeFile(fileType, realFile(fullPath))
		}
	}
}

// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
	files, err := rt.AssetDir(directory)
	if err != nil {
		return
	}
	for _, f := range files {
		if ok, _ := path.Match(pattern, f); ok {
			AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
		}
	}
}

// FindRuntimeFile finds a runtime file of the given filetype and name
// will return nil if no file was found
func FindRuntimeFile(fileType RTFiletype, name string) RuntimeFile {
	for _, f := range ListRuntimeFiles(fileType) {
		if f.Name() == name {
			return f
		}
	}
	return nil
}

// ListRuntimeFiles lists all known runtime files for the given filetype
func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
	return allFiles[fileType]
}

// ListRealRuntimeFiles lists all real runtime files (on disk) for a filetype
// these runtime files will be ones defined by the user and loaded from the config directory
func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
	return realFiles[fileType]
}

// InitRuntimeFiles initializes all assets file and the config directory
func InitRuntimeFiles() {
	add := func(fileType RTFiletype, dir, pattern string) {
		AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
		AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
	}

	add(RTColorscheme, "colorschemes", "*.micro")
	add(RTSyntax, "syntax", "*.yaml")
	add(RTSyntaxHeader, "syntax", "*.hdr")
	add(RTHelp, "help", "*.md")

	initlua := filepath.Join(ConfigDir, "init.lua")
	if _, err := os.Stat(initlua); !os.IsNotExist(err) {
		p := new(Plugin)
		p.Name = "initlua"
		p.DirName = "initlua"
		p.Srcs = append(p.Srcs, realFile(initlua))
		Plugins = append(Plugins, p)
	}

	// Search ConfigDir for plugin-scripts
	plugdir := filepath.Join(ConfigDir, "plug")
	files, _ := ioutil.ReadDir(plugdir)

	isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString

	for _, d := range files {
		plugpath := filepath.Join(plugdir, d.Name())
		if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
			srcs, _ := ioutil.ReadDir(plugpath)
			p := new(Plugin)
			p.Name = d.Name()
			p.DirName = d.Name()
			for _, f := range srcs {
				if strings.HasSuffix(f.Name(), ".lua") {
					p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
				} else if strings.HasSuffix(f.Name(), ".json") {
					data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
					if err != nil {
						continue
					}
					p.Info, err = NewPluginInfo(data)
					if err != nil {
						continue
					}
					p.Name = p.Info.Name
				}
			}

			if !isID(p.Name) || len(p.Srcs) <= 0 {
				log.Println(p.Name, "is not a plugin")
				continue
			}
			Plugins = append(Plugins, p)
		}
	}

	plugdir = filepath.Join("runtime", "plugins")
	if files, err := rt.AssetDir(plugdir); err == nil {
		for _, d := range files {
			if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil {
				p := new(Plugin)
				p.Name = d
				p.DirName = d
				p.Default = true
				for _, f := range srcs {
					if strings.HasSuffix(f, ".lua") {
						p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
					} else if strings.HasSuffix(f, ".json") {
						data, err := rt.Asset(filepath.Join(plugdir, d, f))
						if err != nil {
							continue
						}
						p.Info, err = NewPluginInfo(data)
						if err != nil {
							continue
						}
						p.Name = p.Info.Name
					}
				}
				if !isID(p.Name) || len(p.Srcs) <= 0 {
					log.Println(p.Name, "is not a plugin")
					continue
				}
				Plugins = append(Plugins, p)
			}
		}
	}
}

// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
func PluginReadRuntimeFile(fileType RTFiletype, name string) string {
	if file := FindRuntimeFile(fileType, name); file != nil {
		if data, err := file.Data(); err == nil {
			return string(data)
		}
	}
	return ""
}

// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
func PluginListRuntimeFiles(fileType RTFiletype) []string {
	files := ListRuntimeFiles(fileType)
	result := make([]string, len(files))
	for i, f := range files {
		result[i] = f.Name()
	}
	return result
}

// PluginAddRuntimeFile adds a file to the runtime files for a plugin
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) error {
	pl := FindPlugin(plugin)
	if pl == nil {
		return errors.New("Plugin " + plugin + " does not exist")
	}
	pldir := pl.DirName
	fullpath := filepath.Join(ConfigDir, "plug", pldir, filePath)
	if _, err := os.Stat(fullpath); err == nil {
		AddRealRuntimeFile(filetype, realFile(fullpath))
	} else {
		fullpath = path.Join("runtime", "plugins", pldir, filePath)
		AddRuntimeFile(filetype, assetFile(fullpath))
	}
	return nil
}

// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) error {
	pl := FindPlugin(plugin)
	if pl == nil {
		return errors.New("Plugin " + plugin + " does not exist")
	}
	pldir := pl.DirName
	fullpath := filepath.Join(ConfigDir, "plug", pldir, directory)
	if _, err := os.Stat(fullpath); err == nil {
		AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
	} else {
		fullpath = path.Join("runtime", "plugins", pldir, directory)
		AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
	}
	return nil
}

// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
func PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {
	AddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})
}
<|endoftext|>