<commit_before>package ego

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"io"
	"sort"
	"strings"
	"unicode"
)

// Template represents an entire Ego template.
// A template consists of zero or more blocks.
// Blocks can be either a TextBlock, a PrintBlock, a RawPrintBlock, or a CodeBlock.
type Template struct {
	Path   string
	Blocks []Block
}

// WriteTo writes the template to a writer.
func (t *Template) WriteTo(w io.Writer) (n int64, err error) {
	var buf bytes.Buffer

	// Write "generated" header comment.
	buf.WriteString("// Generated by ego.\n")
	buf.WriteString("// DO NOT EDIT\n\n")

	// Write blocks.
	writeBlocksTo(&buf, t.Blocks)

	// Parse buffer as a Go file.
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "", buf.Bytes(), parser.ParseComments)
	if err != nil {
		n, _ = buf.WriteTo(w)
		return n, err
	}

	// Inject required packages.
	injectImports(f)

	// Attempt to gofmt.
	var result bytes.Buffer
	if err := format.Node(&result, fset, f); err != nil {
		n, _ = buf.WriteTo(w)
		return n, err
	}

	// Write to output writer.
	return result.WriteTo(w)
}

func writeBlocksTo(buf *bytes.Buffer, blks []Block) {
	for _, blk := range blks {
		// Write line comment.
		if pos := Position(blk); pos.Path != "" && pos.LineNo > 0 {
			fmt.Fprintf(buf, "//line %s:%d\n", pos.Path, pos.LineNo)
		}

		// Write block.
		switch blk := blk.(type) {
		case *TextBlock:
			fmt.Fprintf(buf, `_, _ = io.WriteString(w, %q)`+"\n", blk.Content)

		case *CodeBlock:
			fmt.Fprintln(buf, blk.Content)

		case *PrintBlock:
			fmt.Fprintf(buf, `_, _ = io.WriteString(w, html.EscapeString(fmt.Sprint(%s)))`+"\n", blk.Content)

		case *RawPrintBlock:
			fmt.Fprintf(buf, `_, _ = fmt.Fprint(w, %s)`+"\n", blk.Content)

		case *ComponentStartBlock:
			if blk.Package != "" {
				fmt.Fprintf(buf, "{\nvar EGO %s.%s\n", blk.Package, blk.Name)
			} else {
				fmt.Fprintf(buf, "{\nvar EGO %s\n", blk.Name)
			}

			for _, field := range blk.Fields {
				fmt.Fprintf(buf, "EGO.%s = %s\n", field.Name, field.Value)
			}

			if len(blk.Attrs) > 0 {
				fmt.Fprintf(buf, "EGO.Attrs = map[string]string{\n")
				for _, attr := range blk.Attrs {
					fmt.Fprintf(buf, "	%q: fmt.Sprint(%s),\n", attr.Name, attr.Value)
				}
				fmt.Fprintf(buf, "}\n")
			}

			for _, attrBlock := range blk.AttrBlocks {
				fmt.Fprintf(buf, "EGO.%s = func() {\n", attrBlock.Name)
				writeBlocksTo(buf, attrBlock.Yield)
				fmt.Fprint(buf, "}\n")
			}

			if len(blk.Yield) > 0 {
				buf.WriteString("EGO.Yield = func() {\n")
				writeBlocksTo(buf, blk.Yield)
				buf.WriteString("}\n")
			}

			fmt.Fprint(buf, "EGO.Render(ctx, w) }\n")
		}
	}
}

// Normalize joins together adjacent text blocks.
func normalizeBlocks(a []Block) []Block {
	a = joinAdjacentTextBlocks(a)
	a = trimLeftRight(a)
	a = trimTrailingEmptyTextBlocks(a)
	return a
}

func joinAdjacentTextBlocks(a []Block) []Block {
	var other []Block
	for _, blk := range a {
		curr, isTextBlock := blk.(*TextBlock)

		// Always append the first block.
		if len(other) == 0 {
			other = append(other, blk)
			continue
		}

		// Simply append if this block or prev block are not text blocks.
		prev, isPrevTextBlock := other[len(other)-1].(*TextBlock)
		if !isTextBlock || !isPrevTextBlock {
			other = append(other, blk)
			continue
		}

		// Append this text block's content to the previous block.
		prev.Content += curr.Content
	}

	return other
}

func trimLeftRight(a []Block) []Block {
	for i, blk := range a {
		trimLeft, trimRight := blk.trim()
		if trimLeft && i > 1 {
			if textBlock, ok := a[i-1].(*TextBlock); ok {
				textBlock.Content = strings.TrimRightFunc(textBlock.Content, unicode.IsSpace)
			}
		}
		if trimRight && i+1 < len(a) {
			if textBlock, ok := a[i+1].(*TextBlock); ok {
				textBlock.Content = strings.TrimLeftFunc(textBlock.Content, unicode.IsSpace)
			}
		}
	}
	return a
}

func trimTrailingEmptyTextBlocks(a []Block) []Block {
	for len(a) > 0 {
		blk, ok := a[len(a)-1].(*TextBlock)
		if !ok || strings.TrimSpace(blk.Content) != "" {
			break
		}
		a[len(a)-1] = nil
		a = a[:len(a)-1]
	}
	return a
}

func injectImports(f *ast.File) {
	names := []string{`"fmt"`, `"html"`, `"io"`, `"context"`}

	// Strip packages from existing imports.
	for i := 0; i < len(f.Decls); i++ {
		decl, ok := f.Decls[i].(*ast.GenDecl)
		if !ok || decl.Tok != token.IMPORT {
			continue
		}

		// Remove listed imports.
		removeImportSpecs(decl, names)

		// Remove declaration if it has no imports.
		if len(decl.Specs) == 0 {
			copy(f.Decls[i:], f.Decls[i+1:])
			f.Decls[len(f.Decls)-1] = nil
			f.Decls = f.Decls[:len(f.Decls)-1]
			i--
		}
	}

	// Generate new import.
	for i := len(names) - 1; i >= 0; i-- {
		f.Decls = append([]ast.Decl{&ast.GenDecl{
			Tok: token.IMPORT,
			Specs: []ast.Spec{
				&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: names[i]}},
			},
		}}, f.Decls...)
	}

	// Add unnamed vars at the end of the file to ensure imports are used.
	f.Decls = append(f.Decls, &ast.GenDecl{
		Tok: token.VAR,
		Specs: []ast.Spec{
			&ast.ValueSpec{Names: []*ast.Ident{{Name: "_"}}, Type: &ast.Ident{Name: "fmt.Stringer"}},
		},
	})
	f.Decls = append(f.Decls, &ast.GenDecl{
		Tok: token.VAR,
		Specs: []ast.Spec{
			&ast.ValueSpec{Names: []*ast.Ident{{Name: "_"}}, Type: &ast.Ident{Name: "io.Reader"}},
		},
	})
	f.Decls = append(f.Decls, &ast.GenDecl{
		Tok: token.VAR,
		Specs: []ast.Spec{
			&ast.ValueSpec{Names: []*ast.Ident{{Name: "_"}}, Type: &ast.Ident{Name: "context.Context"}},
		},
	})
	f.Decls = append(f.Decls, &ast.GenDecl{
		Tok: token.VAR,
		Specs: []ast.Spec{
			&ast.ValueSpec{Names: []*ast.Ident{{Name: "_"}}, Values: []ast.Expr{&ast.Ident{Name: "html.EscapeString"}}},
		},
	})
}

func removeImportSpecs(decl *ast.GenDecl, names []string) {
	for i := 0; i < len(decl.Specs); i++ {
		spec, ok := decl.Specs[i].(*ast.ImportSpec)
		if !ok || !stringSliceContains(names, spec.Path.Value) {
			continue
		}

		// Delete spec.
		copy(decl.Specs[i:], decl.Specs[i+1:])
		decl.Specs[len(decl.Specs)-1] = nil
		decl.Specs = decl.Specs[:len(decl.Specs)-1]

		i--
	}
}

// Block represents an element of the template.
type Block interface {
	block()
	trim() (bool, bool)
}

func (*TextBlock) block()           {}
func (*CodeBlock) block()           {}
func (*PrintBlock) block()          {}
func (*RawPrintBlock) block()       {}
func (*ComponentStartBlock) block() {}
func (*ComponentEndBlock) block()   {}
func (*AttrStartBlock) block()      {}
func (*AttrEndBlock) block()        {}

func (*TextBlock) trim() (bool, bool)           { return false, false }
func (b *CodeBlock) trim() (bool, bool)         { return b.TrimLeft, b.TrimRight }
func (b *PrintBlock) trim() (bool, bool)        { return b.TrimLeft, b.TrimRight }
func (b *RawPrintBlock) trim() (bool, bool)     { return b.TrimLeft, b.TrimRight }
func (*ComponentStartBlock) trim() (bool, bool) { return false, false }
func (*ComponentEndBlock) trim() (bool, bool)   { return false, false }
func (*AttrStartBlock) trim() (bool, bool)      { return false, false }
func (*AttrEndBlock) trim() (bool, bool)        { return false, false }

// TextBlock represents a UTF-8 encoded block of text that is written to the writer as-is.
type TextBlock struct {
	Pos     Pos
	Content string
}

// CodeBlock represents a Go code block that is printed as-is to the template.
type CodeBlock struct {
	Pos       Pos
	Content   string
	TrimLeft  bool
	TrimRight bool
}

// PrintBlock represents a block that will HTML escape the contents before outputting
type PrintBlock struct {
	Pos       Pos
	Content   string
	TrimLeft  bool
	TrimRight bool
}

// RawPrintBlock represents a block of the template that is printed out to the writer.
type RawPrintBlock struct {
	Pos       Pos
	Content   string
	TrimLeft  bool
	TrimRight bool
}

// ComponentStartBlock represents the opening block of an ego component.
type ComponentStartBlock struct {
	Pos        Pos
	Package    string
	Name       string
	Closed     bool
	Fields     []*Field
	Attrs      []*Attr
	AttrBlocks []*AttrStartBlock
	Yield      []Block
}

// Namespace returns the block package, if defined. Otherwise returns "ego".
func (blk *ComponentStartBlock) Namespace() string {
	if blk.Package == "" {
		return "ego"
	}
	return blk.Package
}

// ComponentEndBlock represents the closing block of an ego component.
type ComponentEndBlock struct {
	Pos     Pos
	Package string
	Name    string
}

// Namespace returns the block package, if defined. Otherwise returns "ego".
func (blk *ComponentEndBlock) Namespace() string {
	if blk.Package == "" {
		return "ego"
	}
	return blk.Package
}

// AttrStartBlock represents the opening block of an ego component attribute.
type AttrStartBlock struct {
	Pos     Pos
	Package string
	Name    string
	Yield   []Block
}

// Namespace returns the block package, if defined. Otherwise returns "ego".
func (blk *AttrStartBlock) Namespace() string {
	if blk.Package == "" {
		return "ego"
	}
	return blk.Package
}

// AttrEndBlock represents the closing block of an ego component attribute.
type AttrEndBlock struct {
	Pos     Pos
	Package string
	Name    string
}

// Namespace returns the block package, if defined. Otherwise returns "ego".
func (blk *AttrEndBlock) Namespace() string {
	if blk.Package == "" {
		return "ego"
	}
	return blk.Package
}

func shortComponentBlockString(blk Block) string {
	switch blk := blk.(type) {
	case *ComponentStartBlock:
		return fmt.Sprintf("<%s:%s>", blk.Namespace(), blk.Name)
	case *ComponentEndBlock:
		return fmt.Sprintf("</%s:%s>", blk.Namespace(), blk.Name)
	case *AttrStartBlock:
		return fmt.Sprintf("<%s::%s>", blk.Namespace(), blk.Name)
	case *AttrEndBlock:
		return fmt.Sprintf("</%s::%s>", blk.Namespace(), blk.Name)
	default:
		return "<UNKNOWN>"
	}
}

// Field represents a key/value pair on a component.
type Field struct {
	Name    string
	NamePos Pos

	Value    string
	ValuePos Pos
}

// Attr represents a key/value passthrough pair on a component.
type Attr struct {
	Name    string
	NamePos Pos

	Value    string
	ValuePos Pos
}

// Position returns the position of the block.
func Position(blk Block) Pos {
	switch blk := blk.(type) {
	case *TextBlock:
		return blk.Pos
	case *CodeBlock:
		return blk.Pos
	case *PrintBlock:
		return blk.Pos
	case *RawPrintBlock:
		return blk.Pos
	case *ComponentStartBlock:
		return blk.Pos
	case *ComponentEndBlock:
		return blk.Pos
	case *AttrStartBlock:
		return blk.Pos
	case *AttrEndBlock:
		return blk.Pos
	default:
		panic("unreachable")
	}
}

// Pos represents a position in a given file.
type Pos struct {
	Path   string
	LineNo int
}

func stringSliceContains(a []string, v string) bool {
	for i := range a {
		if a[i] == v {
			return true
		}
	}
	return false
}

// AttrNames returns a sorted list of names for an attribute set.
func AttrNames(attrs map[string]interface{}) []string {
	a := make([]string, 0, len(attrs))
	for k := range attrs {
		a = append(a, k)
	}
	sort.Strings(a)
	return a
}
<commit_msg>replace trimTrailingEmptyTextBlocks with trimEmptyTextBlocks<commit_after>package ego

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"io"
	"sort"
	"strings"
	"unicode"
)

// Template represents an entire Ego template.
// A template consists of zero or more blocks.
// Blocks can be either a TextBlock, a PrintBlock, a RawPrintBlock, or a CodeBlock.
type Template struct {
	Path   string
	Blocks []Block
}

// WriteTo writes the template to a writer.
func (t *Template) WriteTo(w io.Writer) (n int64, err error) {
	var buf bytes.Buffer

	// Write "generated" header comment.
	buf.WriteString("// Generated by ego.\n")
	buf.WriteString("// DO NOT EDIT\n\n")

	// Write blocks.
	writeBlocksTo(&buf, t.Blocks)

	// Parse buffer as a Go file.
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "", buf.Bytes(), parser.ParseComments)
	if err != nil {
		n, _ = buf.WriteTo(w)
		return n, err
	}

	// Inject required packages.
	injectImports(f)

	// Attempt to gofmt.
	var result bytes.Buffer
	if err := format.Node(&result, fset, f); err != nil {
		n, _ = buf.WriteTo(w)
		return n, err
	}

	// Write to output writer.
	return result.WriteTo(w)
}

func writeBlocksTo(buf *bytes.Buffer, blks []Block) {
	for _, blk := range blks {
		// Write line comment.
		if pos := Position(blk); pos.Path != "" && pos.LineNo > 0 {
			fmt.Fprintf(buf, "//line %s:%d\n", pos.Path, pos.LineNo)
		}

		// Write block.
		switch blk := blk.(type) {
		case *TextBlock:
			fmt.Fprintf(buf, `_, _ = io.WriteString(w, %q)`+"\n", blk.Content)

		case *CodeBlock:
			fmt.Fprintln(buf, blk.Content)

		case *PrintBlock:
			fmt.Fprintf(buf, `_, _ = io.WriteString(w, html.EscapeString(fmt.Sprint(%s)))`+"\n", blk.Content)

		case *RawPrintBlock:
			fmt.Fprintf(buf, `_, _ = fmt.Fprint(w, %s)`+"\n", blk.Content)

		case *ComponentStartBlock:
			if blk.Package != "" {
				fmt.Fprintf(buf, "{\nvar EGO %s.%s\n", blk.Package, blk.Name)
			} else {
				fmt.Fprintf(buf, "{\nvar EGO %s\n", blk.Name)
			}

			for _, field := range blk.Fields {
				fmt.Fprintf(buf, "EGO.%s = %s\n", field.Name, field.Value)
			}

			if len(blk.Attrs) > 0 {
				fmt.Fprintf(buf, "EGO.Attrs = map[string]string{\n")
				for _, attr := range blk.Attrs {
					fmt.Fprintf(buf, "	%q: fmt.Sprint(%s),\n", attr.Name, attr.Value)
				}
				fmt.Fprintf(buf, "}\n")
			}

			for _, attrBlock := range blk.AttrBlocks {
				fmt.Fprintf(buf, "EGO.%s = func() {\n", attrBlock.Name)
				writeBlocksTo(buf, attrBlock.Yield)
				fmt.Fprint(buf, "}\n")
			}

			if len(blk.Yield) > 0 {
				buf.WriteString("EGO.Yield = func() {\n")
				writeBlocksTo(buf, blk.Yield)
				buf.WriteString("}\n")
			}

			fmt.Fprint(buf, "EGO.Render(ctx, w) }\n")
		}
	}
}

// Normalize joins together adjacent text blocks.
func normalizeBlocks(a []Block) []Block {
	a = joinAdjacentTextBlocks(a)
	a = trimLeftRight(a)
	a = trimEmptyTextBlocks(a)
	return a
}

func joinAdjacentTextBlocks(a []Block) []Block {
	var other []Block
	for _, blk := range a {
		curr, isTextBlock := blk.(*TextBlock)

		// Always append the first block.
		if len(other) == 0 {
			other = append(other, blk)
			continue
		}

		// Simply append if this block or prev block are not text blocks.
		prev, isPrevTextBlock := other[len(other)-1].(*TextBlock)
		if !isTextBlock || !isPrevTextBlock {
			other = append(other, blk)
			continue
		}

		// Append this text block's content to the previous block.
		prev.Content += curr.Content
	}

	return other
}

func trimLeftRight(a []Block) []Block {
	for i, blk := range a {
		trimLeft, trimRight := blk.trim()
		if trimLeft && i > 1 {
			if textBlock, ok := a[i-1].(*TextBlock); ok {
				textBlock.Content = strings.TrimRightFunc(textBlock.Content, unicode.IsSpace)
			}
		}
		if trimRight && i+1 < len(a) {
			if textBlock, ok := a[i+1].(*TextBlock); ok {
				textBlock.Content = strings.TrimLeftFunc(textBlock.Content, unicode.IsSpace)
			}
		}
	}
	return a
}

func trimEmptyTextBlocks(a []Block) []Block {
	b := make([]Block, 0, len(a))
	for _, blk := range a {
		if tb, ok := blk.(*TextBlock); !ok || strings.TrimSpace(tb.Content) != "" {
			b = append(b, blk)
		}
	}
	return b
}

func injectImports(f *ast.File) {
	names := []string{`"fmt"`, `"html"`, `"io"`, `"context"`}

	// Strip packages from existing imports.
	for i := 0; i < len(f.Decls); i++ {
		decl, ok := f.Decls[i].(*ast.GenDecl)
		if !ok || decl.Tok != token.IMPORT {
			continue
		}

		// Remove listed imports.
		removeImportSpecs(decl, names)

		// Remove declaration if it has no imports.
		if len(decl.Specs) == 0 {
			copy(f.Decls[i:], f.Decls[i+1:])
			f.Decls[len(f.Decls)-1] = nil
			f.Decls = f.Decls[:len(f.Decls)-1]
			i--
		}
	}

	// Generate new import.
	for i := len(names) - 1; i >= 0; i-- {
		f.Decls = append([]ast.Decl{&ast.GenDecl{
			Tok: token.IMPORT,
			Specs: []ast.Spec{
				&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: names[i]}},
			},
		}}, f.Decls...)
	}

	// Add unnamed vars at the end of the file to ensure imports are used.
	f.Decls = append(f.Decls, &ast.GenDecl{
		Tok: token.VAR,
		Specs: []ast.Spec{
			&ast.ValueSpec{Names: []*ast.Ident{{Name: "_"}}, Type: &ast.Ident{Name: "fmt.Stringer"}},
		},
	})
	f.Decls = append(f.Decls, &ast.GenDecl{
		Tok: token.VAR,
		Specs: []ast.Spec{
			&ast.ValueSpec{Names: []*ast.Ident{{Name: "_"}}, Type: &ast.Ident{Name: "io.Reader"}},
		},
	})
	f.Decls = append(f.Decls, &ast.GenDecl{
		Tok: token.VAR,
		Specs: []ast.Spec{
			&ast.ValueSpec{Names: []*ast.Ident{{Name: "_"}}, Type: &ast.Ident{Name: "context.Context"}},
		},
	})
	f.Decls = append(f.Decls, &ast.GenDecl{
		Tok: token.VAR,
		Specs: []ast.Spec{
			&ast.ValueSpec{Names: []*ast.Ident{{Name: "_"}}, Values: []ast.Expr{&ast.Ident{Name: "html.EscapeString"}}},
		},
	})
}

func removeImportSpecs(decl *ast.GenDecl, names []string) {
	for i := 0; i < len(decl.Specs); i++ {
		spec, ok := decl.Specs[i].(*ast.ImportSpec)
		if !ok || !stringSliceContains(names, spec.Path.Value) {
			continue
		}

		// Delete spec.
		copy(decl.Specs[i:], decl.Specs[i+1:])
		decl.Specs[len(decl.Specs)-1] = nil
		decl.Specs = decl.Specs[:len(decl.Specs)-1]

		i--
	}
}

// Block represents an element of the template.
type Block interface {
	block()
	trim() (bool, bool)
}

func (*TextBlock) block()           {}
func (*CodeBlock) block()           {}
func (*PrintBlock) block()          {}
func (*RawPrintBlock) block()       {}
func (*ComponentStartBlock) block() {}
func (*ComponentEndBlock) block()   {}
func (*AttrStartBlock) block()      {}
func (*AttrEndBlock) block()        {}

func (*TextBlock) trim() (bool, bool)           { return false, false }
func (b *CodeBlock) trim() (bool, bool)         { return b.TrimLeft, b.TrimRight }
func (b *PrintBlock) trim() (bool, bool)        { return b.TrimLeft, b.TrimRight }
func (b *RawPrintBlock) trim() (bool, bool)     { return b.TrimLeft, b.TrimRight }
func (*ComponentStartBlock) trim() (bool, bool) { return false, false }
func (*ComponentEndBlock) trim() (bool, bool)   { return false, false }
func (*AttrStartBlock) trim() (bool, bool)      { return false, false }
func (*AttrEndBlock) trim() (bool, bool)        { return false, false }

// TextBlock represents a UTF-8 encoded block of text that is written to the writer as-is.
type TextBlock struct {
	Pos     Pos
	Content string
}

// CodeBlock represents a Go code block that is printed as-is to the template.
type CodeBlock struct {
	Pos       Pos
	Content   string
	TrimLeft  bool
	TrimRight bool
}

// PrintBlock represents a block that will HTML escape the contents before outputting
type PrintBlock struct {
	Pos       Pos
	Content   string
	TrimLeft  bool
	TrimRight bool
}

// RawPrintBlock represents a block of the template that is printed out to the writer.
type RawPrintBlock struct {
	Pos       Pos
	Content   string
	TrimLeft  bool
	TrimRight bool
}

// ComponentStartBlock represents the opening block of an ego component.
type ComponentStartBlock struct {
	Pos        Pos
	Package    string
	Name       string
	Closed     bool
	Fields     []*Field
	Attrs      []*Attr
	AttrBlocks []*AttrStartBlock
	Yield      []Block
}

// Namespace returns the block package, if defined. Otherwise returns "ego".
func (blk *ComponentStartBlock) Namespace() string {
	if blk.Package == "" {
		return "ego"
	}
	return blk.Package
}

// ComponentEndBlock represents the closing block of an ego component.
type ComponentEndBlock struct {
	Pos     Pos
	Package string
	Name    string
}

// Namespace returns the block package, if defined. Otherwise returns "ego".
func (blk *ComponentEndBlock) Namespace() string {
	if blk.Package == "" {
		return "ego"
	}
	return blk.Package
}

// AttrStartBlock represents the opening block of an ego component attribute.
type AttrStartBlock struct {
	Pos     Pos
	Package string
	Name    string
	Yield   []Block
}

// Namespace returns the block package, if defined. Otherwise returns "ego".
func (blk *AttrStartBlock) Namespace() string {
	if blk.Package == "" {
		return "ego"
	}
	return blk.Package
}

// AttrEndBlock represents the closing block of an ego component attribute.
type AttrEndBlock struct {
	Pos     Pos
	Package string
	Name    string
}

// Namespace returns the block package, if defined. Otherwise returns "ego".
func (blk *AttrEndBlock) Namespace() string {
	if blk.Package == "" {
		return "ego"
	}
	return blk.Package
}

func shortComponentBlockString(blk Block) string {
	switch blk := blk.(type) {
	case *ComponentStartBlock:
		return fmt.Sprintf("<%s:%s>", blk.Namespace(), blk.Name)
	case *ComponentEndBlock:
		return fmt.Sprintf("</%s:%s>", blk.Namespace(), blk.Name)
	case *AttrStartBlock:
		return fmt.Sprintf("<%s::%s>", blk.Namespace(), blk.Name)
	case *AttrEndBlock:
		return fmt.Sprintf("</%s::%s>", blk.Namespace(), blk.Name)
	default:
		return "<UNKNOWN>"
	}
}

// Field represents a key/value pair on a component.
type Field struct {
	Name    string
	NamePos Pos

	Value    string
	ValuePos Pos
}

// Attr represents a key/value passthrough pair on a component.
type Attr struct {
	Name    string
	NamePos Pos

	Value    string
	ValuePos Pos
}

// Position returns the position of the block.
func Position(blk Block) Pos {
	switch blk := blk.(type) {
	case *TextBlock:
		return blk.Pos
	case *CodeBlock:
		return blk.Pos
	case *PrintBlock:
		return blk.Pos
	case *RawPrintBlock:
		return blk.Pos
	case *ComponentStartBlock:
		return blk.Pos
	case *ComponentEndBlock:
		return blk.Pos
	case *AttrStartBlock:
		return blk.Pos
	case *AttrEndBlock:
		return blk.Pos
	default:
		panic("unreachable")
	}
}

// Pos represents a position in a given file.
type Pos struct {
	Path   string
	LineNo int
}

func stringSliceContains(a []string, v string) bool {
	for i := range a {
		if a[i] == v {
			return true
		}
	}
	return false
}

// AttrNames returns a sorted list of names for an attribute set.
func AttrNames(attrs map[string]interface{}) []string {
	a := make([]string, 0, len(attrs))
	for k := range attrs {
		a = append(a, k)
	}
	sort.Strings(a)
	return a
}
<|endoftext|>


<commit_before>package eme

import (
	"crypto/cipher"
	"log"
)

const (
	directionEncrypt = iota
	directionDecrypt
)

func multByTwo(in []byte) (out []byte) {
	if len(in) != 16 {
		panic("len must be 16")
	}
	out = make([]byte, 16)

	out[0] = 2 * in[0]
	if in[15] >= 128 {
		out[0] = out[0] ^ 135
	}
	for j := 1; j < 16; j++ {
		out[j] = 2 * in[j]
		if in[j-1] >= 128 {
			out[j] += 1
		}
	}
	return out
}

func xorBlocks(in1 []byte, in2 []byte) (out []byte) {
	if len(in1) != len(in2) {
		log.Panicf("len(in1)=%d is not equal to len(in2)=%d", len(in1), len(in2))
	}

	out = make([]byte, len(in1))
	for i := range in1 {
		out[i] = in1[i] ^ in2[i]
	}
	return out
}

func transformAES(dst []byte, src []byte, direction int, bc cipher.Block) {
	if direction == directionEncrypt {
		bc.Encrypt(dst, src)
		return
	} else if direction == directionDecrypt {
		bc.Decrypt(dst, src)
		return
	} else {
		log.Panicf("unknown direction %d", direction)
	}
}

func TransformEME32(bc cipher.Block, T []byte, P []byte, direction int) (C []byte) {
	C = make([]byte, 512)
	m := len(P) / bc.BlockSize()

	if m == 0 || m >= bc.BlockSize()*8 {
		log.Panicf("EME operates on 1-%d block-cipher blocks", bc.BlockSize()*8)
	}

	/* set L = 2*AESenc(K; 0) */
	zero := make([]byte, 16)
	bc.Encrypt(zero, zero)
	L := multByTwo(zero)

	for j := 0; j < m; j++ {
		Pj := P[j*16 : (j+1)*16]
		/* PPj = 2**(j-1)*L xor Pj */
		PPj := xorBlocks(Pj, L)
		/* PPPj = AESenc(K; PPj) */
		transformAES(C[j*16:(j+1)*16], PPj, direction, bc)
		L = multByTwo(L)
	}

	/* MP =(xorSum PPPj) xor T */
	MP := xorBlocks(C[0:16], T)
	for j := 1; j < m; j++ {
		MP = xorBlocks(MP, C[j*16:(j+1)*16])
	}

	/* MC = AESenc(K; MP) */
	MC := make([]byte, 16)
	transformAES(MC, MP, direction, bc)

	/* M = MP xor MC */
	M := xorBlocks(MP, MC)

	for j := 1; j < m; j++ {
		M = multByTwo(M)
		/* CCCj = 2**(j-1)*M xor PPPj */
		CCCj := xorBlocks(C[j*16:(j+1)*16], M)
		copy(C[j*16:(j+1)*16], CCCj)
	}

	/* CCC1 = (xorSum CCCj) xor T xor MC */
	CCC1 := xorBlocks(MC, T)
	for j := 1; j < m; j++ {
		CCC1 = xorBlocks(CCC1, C[j*16:(j+1)*16])
	}
	copy(C[0:16], CCC1)

	/* reset L = 2*AESenc(K; 0) */
	L = multByTwo(zero)
	for j := 0; j < m; j++ {
		/* CCj = AES-enc(K; CCCj) */
		transformAES(C[j*16:(j+1)*16], C[j*16:(j+1)*16], direction, bc)
		/* Cj = 2**(j-1)*L xor CCj */
		Cj := xorBlocks(C[j*16:(j+1)*16], L)
		copy(C[j*16:(j+1)*16], Cj)
		L = multByTwo(L)
	}

	return C
}
<commit_msg>Get rid of allocations inside xorBlocks<commit_after>package eme

import (
	"crypto/cipher"
	"log"
)

const (
	directionEncrypt = iota
	directionDecrypt
)

func multByTwo(in []byte) (out []byte) {
	if len(in) != 16 {
		panic("len must be 16")
	}
	out = make([]byte, 16)

	out[0] = 2 * in[0]
	if in[15] >= 128 {
		out[0] = out[0] ^ 135
	}
	for j := 1; j < 16; j++ {
		out[j] = 2 * in[j]
		if in[j-1] >= 128 {
			out[j] += 1
		}
	}
	return out
}

func xorBlocks(out []byte, in1 []byte, in2 []byte) {
	if len(in1) != len(in2) {
		log.Panicf("len(in1)=%d is not equal to len(in2)=%d", len(in1), len(in2))
	}

	for i := range in1 {
		out[i] = in1[i] ^ in2[i]
	}
}

func transformAES(dst []byte, src []byte, direction int, bc cipher.Block) {
	if direction == directionEncrypt {
		bc.Encrypt(dst, src)
		return
	} else if direction == directionDecrypt {
		bc.Decrypt(dst, src)
		return
	} else {
		log.Panicf("unknown direction %d", direction)
	}
}

func TransformEME32(bc cipher.Block, T []byte, P []byte, direction int) (C []byte) {
	C = make([]byte, 512)
	m := len(P) / bc.BlockSize()

	if m == 0 || m >= bc.BlockSize()*8 {
		log.Panicf("EME operates on 1-%d block-cipher blocks", bc.BlockSize()*8)
	}

	/* set L = 2*AESenc(K; 0) */
	zero := make([]byte, 16)
	bc.Encrypt(zero, zero)
	L := multByTwo(zero)

	PPj := make([]byte, 16)
	for j := 0; j < m; j++ {
		Pj := P[j*16 : (j+1)*16]
		/* PPj = 2**(j-1)*L xor Pj */
		xorBlocks(PPj, Pj, L)
		/* PPPj = AESenc(K; PPj) */
		transformAES(C[j*16:(j+1)*16], PPj, direction, bc)
		L = multByTwo(L)
	}

	/* MP =(xorSum PPPj) xor T */
	MP := make([]byte, 16)
	xorBlocks(MP, C[0:16], T)
	for j := 1; j < m; j++ {
		xorBlocks(MP, MP, C[j*16:(j+1)*16])
	}

	/* MC = AESenc(K; MP) */
	MC := make([]byte, 16)
	transformAES(MC, MP, direction, bc)

	/* M = MP xor MC */
	M := make([]byte, 16)
	xorBlocks(M, MP, MC)
	CCCj := make([]byte, 16)
	for j := 1; j < m; j++ {
		M = multByTwo(M)
		/* CCCj = 2**(j-1)*M xor PPPj */
		xorBlocks(CCCj, C[j*16:(j+1)*16], M)
		copy(C[j*16:(j+1)*16], CCCj)
	}

	/* CCC1 = (xorSum CCCj) xor T xor MC */
	CCC1 := make([]byte, 16)
	xorBlocks(CCC1, MC, T)
	for j := 1; j < m; j++ {
		xorBlocks(CCC1, CCC1, C[j*16:(j+1)*16])
	}
	copy(C[0:16], CCC1)

	/* reset L = 2*AESenc(K; 0) */
	L = multByTwo(zero)
	for j := 0; j < m; j++ {
		/* CCj = AES-enc(K; CCCj) */
		transformAES(C[j*16:(j+1)*16], C[j*16:(j+1)*16], direction, bc)
		/* Cj = 2**(j-1)*L xor CCj */
		xorBlocks(C[j*16:(j+1)*16], C[j*16:(j+1)*16], L)
		L = multByTwo(L)
	}

	return C
}
<|endoftext|>


<commit_before>package env

import (
	"errors"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"
)

var (
	// ErrNotAStructPtr is returned if you pass something that is not a pointer to a
	// Struct to Parse
	ErrNotAStructPtr = errors.New("Expected a pointer to a Struct")
	// ErrUnsupportedType if the struct field type is not supported by env
	ErrUnsupportedType = errors.New("Type is not supported")
	// ErrUnsupportedSliceType if the slice element type is not supported by env
	ErrUnsupportedSliceType = errors.New("Unsupported slice type")
	// Friendly names for reflect types
	sliceOfInts     = reflect.TypeOf([]int(nil))
	sliceOfInt64s   = reflect.TypeOf([]int64(nil))
	sliceOfStrings  = reflect.TypeOf([]string(nil))
	sliceOfBools    = reflect.TypeOf([]bool(nil))
	sliceOfFloat32s = reflect.TypeOf([]float32(nil))
	sliceOfFloat64s = reflect.TypeOf([]float64(nil))
)

// Parse parses a struct containing `env` tags and loads its values from
// environment variables.
func Parse(v interface{}) error {
	ptrRef := reflect.ValueOf(v)
	if ptrRef.Kind() != reflect.Ptr {
		return ErrNotAStructPtr
	}
	ref := ptrRef.Elem()
	if ref.Kind() != reflect.Struct {
		return ErrNotAStructPtr
	}
	return doParse(ref)
}

func doParse(ref reflect.Value) error {
	refType := ref.Type()
	for i := 0; i < refType.NumField(); i++ {
		value, err := get(refType.Field(i))
		if err != nil {
			return err
		}
		if value == "" {
			continue
		}
		if err := set(ref.Field(i), refType.Field(i), value); err != nil {
			return err
		}
	}
	return nil
}

func get(field reflect.StructField) (string, error) {
	var (
		val string
		err error
	)

	key, opts := parseKeyForOption(field.Tag.Get("env"))

	defaultValue := field.Tag.Get("envDefault")
	val = getOr(key, defaultValue)

	if len(opts) > 0 {
		for _, opt := range opts {
			// The only option supported is "required".
			switch opt {
			case "":
				break
			case "required":
				val, err = getRequired(key)
			default:
				err = errors.New("Env tag option " + opt + " not supported.")
			}
		}
	}

	return val, err
}

// split the env tag's key into the expected key and desired option, if any.
func parseKeyForOption(key string) (string, []string) {
	opts := strings.Split(key, ",")
	return opts[0], opts[1:]
}

func getRequired(key string) (string, error) {
	if value := os.Getenv(key); value != "" {
		return value, nil
	}
	// We do not use fmt.Errorf to avoid another import.
	return "", errors.New("Required environment variable " + key + " is not set")
}

func getOr(key, defaultValue string) string {
	value := os.Getenv(key)
	if value != "" {
		return value
	}
	return defaultValue
}

func set(field reflect.Value, refType reflect.StructField, value string) error {
	switch field.Kind() {
	case reflect.Slice:
		separator := refType.Tag.Get("envSeparator")
		return handleSlice(field, value, separator)
	case reflect.String:
		field.SetString(value)
	case reflect.Bool:
		bvalue, err := strconv.ParseBool(value)
		if err != nil {
			return err
		}
		field.SetBool(bvalue)
	case reflect.Int:
		intValue, err := strconv.ParseInt(value, 10, 32)
		if err != nil {
			return err
		}
		field.SetInt(intValue)
	case reflect.Float32:
		v, err := strconv.ParseFloat(value, 32)
		if err != nil {
			return err
		}
		field.SetFloat(v)
	case reflect.Float64:
		v, err := strconv.ParseFloat(value, 64)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(v))
	case reflect.Int64:
		if refType.Type.String() == "time.Duration" {
			dValue, err := time.ParseDuration(value)
			if err != nil {
				return err
			}
			field.Set(reflect.ValueOf(dValue))
		} else {
			intValue, err := strconv.ParseInt(value, 10, 64)
			if err != nil {
				return err
			}
			field.SetInt(intValue)
		}
	default:
		return ErrUnsupportedType
	}
	return nil
}

func handleSlice(field reflect.Value, value, separator string) error {
	if separator == "" {
		separator = ","
	}

	splitData := strings.Split(value, separator)

	switch field.Type() {
	case sliceOfStrings:
		field.Set(reflect.ValueOf(splitData))
	case sliceOfInts:
		intData, err := parseInts(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(intData))
	case sliceOfInt64s:
		int64Data, err := parseInt64s(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(int64Data))

	case sliceOfFloat32s:
		data, err := parseFloat32s(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(data))
	case sliceOfFloat64s:
		data, err := parseFloat64s(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(data))
	case sliceOfBools:
		boolData, err := parseBools(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(boolData))
	default:
		return ErrUnsupportedSliceType
	}
	return nil
}

func parseInts(data []string) ([]int, error) {
	var intSlice []int

	for _, v := range data {
		intValue, err := strconv.ParseInt(v, 10, 32)
		if err != nil {
			return nil, err
		}
		intSlice = append(intSlice, int(intValue))
	}
	return intSlice, nil
}

func parseInt64s(data []string) ([]int64, error) {
	var intSlice []int64

	for _, v := range data {
		intValue, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			return nil, err
		}
		intSlice = append(intSlice, int64(intValue))
	}
	return intSlice, nil
}

func parseFloat32s(data []string) ([]float32, error) {
	var float32Slice []float32

	for _, v := range data {
		data, err := strconv.ParseFloat(v, 32)
		if err != nil {
			return nil, err
		}
		float32Slice = append(float32Slice, float32(data))
	}
	return float32Slice, nil
}

func parseFloat64s(data []string) ([]float64, error) {
	var float64Slice []float64

	for _, v := range data {
		data, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return nil, err
		}
		float64Slice = append(float64Slice, float64(data))
	}
	return float64Slice, nil
}

func parseBools(data []string) ([]bool, error) {
	var boolSlice []bool

	for _, v := range data {
		bvalue, err := strconv.ParseBool(v)
		if err != nil {
			return nil, err
		}

		boolSlice = append(boolSlice, bvalue)
	}
	return boolSlice, nil
}
<commit_msg>Include all errors in a error message<commit_after>package env

import (
	"errors"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"
)

var (
	// ErrNotAStructPtr is returned if you pass something that is not a pointer to a
	// Struct to Parse
	ErrNotAStructPtr = errors.New("Expected a pointer to a Struct")
	// ErrUnsupportedType if the struct field type is not supported by env
	ErrUnsupportedType = errors.New("Type is not supported")
	// ErrUnsupportedSliceType if the slice element type is not supported by env
	ErrUnsupportedSliceType = errors.New("Unsupported slice type")
	// Friendly names for reflect types
	sliceOfInts     = reflect.TypeOf([]int(nil))
	sliceOfInt64s   = reflect.TypeOf([]int64(nil))
	sliceOfStrings  = reflect.TypeOf([]string(nil))
	sliceOfBools    = reflect.TypeOf([]bool(nil))
	sliceOfFloat32s = reflect.TypeOf([]float32(nil))
	sliceOfFloat64s = reflect.TypeOf([]float64(nil))
)

// Parse parses a struct containing `env` tags and loads its values from
// environment variables.
func Parse(v interface{}) error {
	ptrRef := reflect.ValueOf(v)
	if ptrRef.Kind() != reflect.Ptr {
		return ErrNotAStructPtr
	}
	ref := ptrRef.Elem()
	if ref.Kind() != reflect.Struct {
		return ErrNotAStructPtr
	}
	return doParse(ref)
}

func doParse(ref reflect.Value) error {
	refType := ref.Type()
	var errorList []string

	for i := 0; i < refType.NumField(); i++ {
		value, err := get(refType.Field(i))
		if err != nil {
			errorList = append(errorList, err.Error())
			continue
		}
		if value == "" {
			continue
		}
		if err := set(ref.Field(i), refType.Field(i), value); err != nil {
			errorList = append(errorList, err.Error())
			continue
		}
	}
	if len(errorList) == 0 {
		return nil
	}
	return errors.New(strings.Join(errorList, ". "))
}

func get(field reflect.StructField) (string, error) {
	var (
		val string
		err error
	)

	key, opts := parseKeyForOption(field.Tag.Get("env"))

	defaultValue := field.Tag.Get("envDefault")
	val = getOr(key, defaultValue)

	if len(opts) > 0 {
		for _, opt := range opts {
			// The only option supported is "required".
			switch opt {
			case "":
				break
			case "required":
				val, err = getRequired(key)
			default:
				err = errors.New("Env tag option " + opt + " not supported.")
			}
		}
	}

	return val, err
}

// split the env tag's key into the expected key and desired option, if any.
func parseKeyForOption(key string) (string, []string) {
	opts := strings.Split(key, ",")
	return opts[0], opts[1:]
}

func getRequired(key string) (string, error) {
	if value := os.Getenv(key); value != "" {
		return value, nil
	}
	// We do not use fmt.Errorf to avoid another import.
	return "", errors.New("Required environment variable " + key + " is not set")
}

func getOr(key, defaultValue string) string {
	value := os.Getenv(key)
	if value != "" {
		return value
	}
	return defaultValue
}

func set(field reflect.Value, refType reflect.StructField, value string) error {
	switch field.Kind() {
	case reflect.Slice:
		separator := refType.Tag.Get("envSeparator")
		return handleSlice(field, value, separator)
	case reflect.String:
		field.SetString(value)
	case reflect.Bool:
		bvalue, err := strconv.ParseBool(value)
		if err != nil {
			return err
		}
		field.SetBool(bvalue)
	case reflect.Int:
		intValue, err := strconv.ParseInt(value, 10, 32)
		if err != nil {
			return err
		}
		field.SetInt(intValue)
	case reflect.Float32:
		v, err := strconv.ParseFloat(value, 32)
		if err != nil {
			return err
		}
		field.SetFloat(v)
	case reflect.Float64:
		v, err := strconv.ParseFloat(value, 64)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(v))
	case reflect.Int64:
		if refType.Type.String() == "time.Duration" {
			dValue, err := time.ParseDuration(value)
			if err != nil {
				return err
			}
			field.Set(reflect.ValueOf(dValue))
		} else {
			intValue, err := strconv.ParseInt(value, 10, 64)
			if err != nil {
				return err
			}
			field.SetInt(intValue)
		}
	default:
		return ErrUnsupportedType
	}
	return nil
}

func handleSlice(field reflect.Value, value, separator string) error {
	if separator == "" {
		separator = ","
	}

	splitData := strings.Split(value, separator)

	switch field.Type() {
	case sliceOfStrings:
		field.Set(reflect.ValueOf(splitData))
	case sliceOfInts:
		intData, err := parseInts(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(intData))
	case sliceOfInt64s:
		int64Data, err := parseInt64s(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(int64Data))

	case sliceOfFloat32s:
		data, err := parseFloat32s(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(data))
	case sliceOfFloat64s:
		data, err := parseFloat64s(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(data))
	case sliceOfBools:
		boolData, err := parseBools(splitData)
		if err != nil {
			return err
		}
		field.Set(reflect.ValueOf(boolData))
	default:
		return ErrUnsupportedSliceType
	}
	return nil
}

func parseInts(data []string) ([]int, error) {
	var intSlice []int

	for _, v := range data {
		intValue, err := strconv.ParseInt(v, 10, 32)
		if err != nil {
			return nil, err
		}
		intSlice = append(intSlice, int(intValue))
	}
	return intSlice, nil
}

func parseInt64s(data []string) ([]int64, error) {
	var intSlice []int64

	for _, v := range data {
		intValue, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			return nil, err
		}
		intSlice = append(intSlice, int64(intValue))
	}
	return intSlice, nil
}

func parseFloat32s(data []string) ([]float32, error) {
	var float32Slice []float32

	for _, v := range data {
		data, err := strconv.ParseFloat(v, 32)
		if err != nil {
			return nil, err
		}
		float32Slice = append(float32Slice, float32(data))
	}
	return float32Slice, nil
}

func parseFloat64s(data []string) ([]float64, error) {
	var float64Slice []float64

	for _, v := range data {
		data, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return nil, err
		}
		float64Slice = append(float64Slice, float64(data))
	}
	return float64Slice, nil
}

func parseBools(data []string) ([]bool, error) {
	var boolSlice []bool

	for _, v := range data {
		bvalue, err := strconv.ParseBool(v)
		if err != nil {
			return nil, err
		}

		boolSlice = append(boolSlice, bvalue)
	}
	return boolSlice, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"github.com/nu11p01n73R/walker"
	"os"
	"os/exec"
	"runtime"
	"strings"
)

// Fuz Modes
const (
	SEARCH = iota
	NORMAL = iota
)

// Event Type
const (
	NOP       = iota
	UPWARD    = iota
	DONWARD   = iota
	TOGGLE    = iota
	OPEN      = iota
	BACKSPACE = iota
	EXIT      = iota
)

const MAX_VIEWPORT_SIZE = 20

// Walks the directory.
// Removes the prefix of current working dir,
// for more clear path strings
// Return
//	[]string List of formated file paths.
func intialWalk(dir string) ([]string, error) {
	prefix := dir + "/"
	return walker.Walk(dir, func(files []string) []string {
		for i, file := range files {
			files[i] = strings.TrimPrefix(file, prefix)
		}
		return files
	}, []string{`\.git`, `.*\.sw.*`, `node_modules`, `vendor`})
}

func printHeader() {
	logo := `
	 / _|_   _ ____
	| |_| | | |_  /
	|  _| |_| |/ /
	|_|  \__,_/___|
	`
	fmt.Println(logo)
	fmt.Println("-----------------------------------")
}

func printList(files []string, cursorAt int, searchString string, mode int) {
	for i := 0; i < MAX_VIEWPORT_SIZE && i < len(files); i++ {
		cursor := "  "
		if cursorAt == i {
			cursor = " >"
		}
		fmt.Printf("%s %s\n", cursor, files[i])
	}

	currentMode := "S"
	if mode == NORMAL {
		currentMode = "N"
	}

	fmt.Println("-----------------------------------")
	fmt.Printf("[%s] %s", currentMode, searchString)
}

func clearScreen() {
	cmd := exec.Command("clear")
	cmd.Stdout = os.Stdout
	cmd.Run()
}

func openEditor(file string) error {
	editor := os.Getenv("EDITOR")
	if len(editor) == 0 {
		return errors.New("Cannot open the file because $EDITOR not set")
	}

	cmd := exec.Command(editor, file)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func toggleMode(curr int) int {
	if curr == SEARCH {
		return NORMAL
	} else {
		return SEARCH
	}
}

func normalMode(char string, files []string, size int, cursorAt *int) (bool, error) {
	var err error
	var done bool

	switch char {
	case "j":
		// pressing j
		if *cursorAt == 0 {
			*cursorAt = size - 1
		} else {
			*cursorAt--
		}
		break
	case "k":
		*cursorAt = (*cursorAt + 1) % size
		break
	case "o":
		err = openEditor(files[*cursorAt])
		done = true
		break
	}
	return done, err
}

func contains(str, search string) bool {
	var j int
	for i := 0; i < len(search); i++ {
		for ; j < len(str); j++ {
			if str[j] == search[i] {
				break
			}
		}

		if j == len(str) {
			return false
		}
	}
	return true
}

func filterFiles(files []string, searchString string) []string {
	output := []string{}
	for i := 0; i < len(files); i++ { //&& contains(files[i], searchString); i++ {
		if contains(files[i], searchString) {
			output = append(output, files[i])
		}
	}
	return output
}

// Determins the event for key press by the user.
// The type of event depends upon the current mode.
// Some event like TOGGLE and OPEN doesn't depend on
// the mode.
// Params
//	key 	byte 	The key pressed by the user
//	mode	int	The current mode fuz is on.
// Return
//	int	The even identified based on key
func keyHandler(key byte, mode int) int {
	switch key {
	case 27:
		return TOGGLE
	case 10:
		return OPEN
	}

	if mode == NORMAL {
		switch key {
		case 106:
			return DONWARD
		case 107:
			return UPWARD
		case 113:
			return EXIT
		}
	}

	if mode == SEARCH && key == 127 {
		return BACKSPACE
	}

	return NOP
}

// Get the size of the view port.
// This determins the total number of
// files to be shown by default.
// At max fuz shows the first 10 files
// in the list.
// Params
//	files []string Files to be displayed
// Return
//	int	Number of files in viewport
//	int	Position of the cursor initailly
func getViewPortSize(files []string) (int, int) {
	size := len(files)
	if size > MAX_VIEWPORT_SIZE {
		return MAX_VIEWPORT_SIZE, MAX_VIEWPORT_SIZE - 1
	}
	return size, size - 1
}

func viewPort(files []string) error {
	var searchString string
	var err error

	viewPortSize, cursorAt := getViewPortSize(files)
	fileCount := len(files)
	mode := SEARCH
	allFiles := files

	fileFlag := "-F"
	if runtime.GOOS == "darwin" {
		fileFlag = "-f"
	}

	exec.Command("stty", fileFlag, "/dev/tty", "cbreak", "min", "1").Run()
	err = exec.Command("/bin/stty", fileFlag, "/dev/tty", "-echo").Run()
	if err != nil {
		return err
	}

	char := make([]byte, 1)

keyWait:
	for {
		clearScreen()
		printHeader()
		printList(files, cursorAt, searchString, mode)

		os.Stdin.Read(char)

		event := keyHandler(char[0], mode)

		switch event {
		case EXIT:
			break keyWait
		case UPWARD:
			if cursorAt == 0 {
				cursorAt = viewPortSize - 1
			} else {
				cursorAt--
			}
			break
		case DONWARD:
			cursorAt = (cursorAt + 1) % viewPortSize
			break
		case TOGGLE:
			mode = toggleMode(mode)
			break
		case OPEN:
			if cursorAt < fileCount {
				err = openEditor(files[cursorAt])
				break keyWait
			}
		case SEARCH:
			searchString += string(char)
			files = filterFiles(files, searchString)
			viewPortSize, cursorAt = getViewPortSize(files)
			fileCount = len(files)
		case BACKSPACE:
			if len(searchString) > 0 {
				searchString = searchString[:len(searchString)-1]
				files = filterFiles(allFiles, searchString)
				viewPortSize, cursorAt = getViewPortSize(files)
				fileCount = len(files)
			}
		}

	}
	return err
}

func cleanUp() {
	fmt.Println()

	fileFlag := "-F"
	if runtime.GOOS == "darwin" {
		fileFlag = "-f"
	}

	exec.Command("/bin/stty", fileFlag, "/dev/tty", "echo").Run()
}

// Handles any errors
func handleError(err error) {
	if err != nil {
		cleanUp()
		fmt.Println(err.Error())
		os.Exit(1)
	}
}

func main() {
	pwd, err := os.Getwd()
	handleError(err)

	files, err := intialWalk(pwd)
	handleError(err)

	err = viewPort(files)
	handleError(err)

	cleanUp()
}
<commit_msg>Change header text<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/nu11p01n73R/walker"
	"os"
	"os/exec"
	"runtime"
	"strings"
)

// Fuz Modes
const (
	SEARCH = iota
	NORMAL = iota
)

// Event Type
const (
	NOP       = iota
	UPWARD    = iota
	DONWARD   = iota
	TOGGLE    = iota
	OPEN      = iota
	BACKSPACE = iota
	EXIT      = iota
)

const MAX_VIEWPORT_SIZE = 20

// Walks the directory.
// Removes the prefix of current working dir,
// for more clear path strings
// Return
//	[]string List of formated file paths.
func intialWalk(dir string) ([]string, error) {
	prefix := dir + "/"
	return walker.Walk(dir, func(files []string) []string {
		for i, file := range files {
			files[i] = strings.TrimPrefix(file, prefix)
		}
		return files
	}, []string{`\.git`, `.*\.sw.*`, `node_modules`, `vendor`})
}

func printHeader() {
	logo := `
	   __| │  │_  )
	   _|  │  │  /
  	 _|   ____│___|
	`
	fmt.Println(logo)
	fmt.Println("-----------------------------------")
}

func printList(files []string, cursorAt int, searchString string, mode int) {
	for i := 0; i < MAX_VIEWPORT_SIZE && i < len(files); i++ {
		cursor := "  "
		if cursorAt == i {
			cursor = " >"
		}
		fmt.Printf("%s %s\n", cursor, files[i])
	}

	currentMode := "S"
	if mode == NORMAL {
		currentMode = "N"
	}

	fmt.Println("-----------------------------------")
	fmt.Printf("[%s] %s", currentMode, searchString)
}

func clearScreen() {
	cmd := exec.Command("clear")
	cmd.Stdout = os.Stdout
	cmd.Run()
}

func openEditor(file string) error {
	editor := os.Getenv("EDITOR")
	if len(editor) == 0 {
		return errors.New("Cannot open the file because $EDITOR not set")
	}

	cmd := exec.Command(editor, file)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func toggleMode(curr int) int {
	if curr == SEARCH {
		return NORMAL
	} else {
		return SEARCH
	}
}

func normalMode(char string, files []string, size int, cursorAt *int) (bool, error) {
	var err error
	var done bool

	switch char {
	case "j":
		// pressing j
		if *cursorAt == 0 {
			*cursorAt = size - 1
		} else {
			*cursorAt--
		}
		break
	case "k":
		*cursorAt = (*cursorAt + 1) % size
		break
	case "o":
		err = openEditor(files[*cursorAt])
		done = true
		break
	}
	return done, err
}

func contains(str, search string) bool {
	var j int
	for i := 0; i < len(search); i++ {
		for ; j < len(str); j++ {
			if str[j] == search[i] {
				break
			}
		}

		if j == len(str) {
			return false
		}
	}
	return true
}

func filterFiles(files []string, searchString string) []string {
	output := []string{}
	for i := 0; i < len(files); i++ { //&& contains(files[i], searchString); i++ {
		if contains(files[i], searchString) {
			output = append(output, files[i])
		}
	}
	return output
}

// Determins the event for key press by the user.
// The type of event depends upon the current mode.
// Some event like TOGGLE and OPEN doesn't depend on
// the mode.
// Params
//	key 	byte 	The key pressed by the user
//	mode	int	The current mode fuz is on.
// Return
//	int	The even identified based on key
func keyHandler(key byte, mode int) int {
	switch key {
	case 27:
		return TOGGLE
	case 10:
		return OPEN
	}

	if mode == NORMAL {
		switch key {
		case 106:
			return DONWARD
		case 107:
			return UPWARD
		case 113:
			return EXIT
		}
	}

	if mode == SEARCH && key == 127 {
		return BACKSPACE
	}

	return NOP
}

// Get the size of the view port.
// This determins the total number of
// files to be shown by default.
// At max fuz shows the first 10 files
// in the list.
// Params
//	files []string Files to be displayed
// Return
//	int	Number of files in viewport
//	int	Position of the cursor initailly
func getViewPortSize(files []string) (int, int) {
	size := len(files)
	if size > MAX_VIEWPORT_SIZE {
		return MAX_VIEWPORT_SIZE, MAX_VIEWPORT_SIZE - 1
	}
	return size, size - 1
}

func viewPort(files []string) error {
	var searchString string
	var err error

	viewPortSize, cursorAt := getViewPortSize(files)
	fileCount := len(files)
	mode := SEARCH
	allFiles := files

	fileFlag := "-F"
	if runtime.GOOS == "darwin" {
		fileFlag = "-f"
	}

	exec.Command("stty", fileFlag, "/dev/tty", "cbreak", "min", "1").Run()
	err = exec.Command("/bin/stty", fileFlag, "/dev/tty", "-echo").Run()
	if err != nil {
		return err
	}

	char := make([]byte, 1)

keyWait:
	for {
		clearScreen()
		printHeader()
		printList(files, cursorAt, searchString, mode)

		os.Stdin.Read(char)

		event := keyHandler(char[0], mode)

		switch event {
		case EXIT:
			break keyWait
		case UPWARD:
			if cursorAt == 0 {
				cursorAt = viewPortSize - 1
			} else {
				cursorAt--
			}
			break
		case DONWARD:
			cursorAt = (cursorAt + 1) % viewPortSize
			break
		case TOGGLE:
			mode = toggleMode(mode)
			break
		case OPEN:
			if cursorAt < fileCount {
				err = openEditor(files[cursorAt])
				break keyWait
			}
		case SEARCH:
			searchString += string(char)
			files = filterFiles(files, searchString)
			viewPortSize, cursorAt = getViewPortSize(files)
			fileCount = len(files)
		case BACKSPACE:
			if len(searchString) > 0 {
				searchString = searchString[:len(searchString)-1]
				files = filterFiles(allFiles, searchString)
				viewPortSize, cursorAt = getViewPortSize(files)
				fileCount = len(files)
			}
		}

	}
	return err
}

func cleanUp() {
	fmt.Println()

	fileFlag := "-F"
	if runtime.GOOS == "darwin" {
		fileFlag = "-f"
	}

	exec.Command("/bin/stty", fileFlag, "/dev/tty", "echo").Run()
}

// Handles any errors
func handleError(err error) {
	if err != nil {
		cleanUp()
		fmt.Println(err.Error())
		os.Exit(1)
	}
}

func main() {
	pwd, err := os.Getwd()
	handleError(err)

	files, err := intialWalk(pwd)
	handleError(err)

	err = viewPort(files)
	handleError(err)

	cleanUp()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
)

const (
	ansiEraseDisplay = "\033[2J"
	ansiResetCursor  = "\033[H"
	carriageReturn   = "\015"
)

var originalSttyState bytes.Buffer

func getSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", "-g")
	cmd.Stdin = os.Stdin
	cmd.Stdout = state
	return cmd.Run()
}

func setSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", state.String())
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func drawInitialScreen() (err error) {
	_, err = io.WriteString(os.Stdout, ansiEraseDisplay)
	if err != nil {
		return err
	}
	_, err = io.WriteString(os.Stdout, ansiResetCursor)

	return err
}

func setCursorPos(line int, col int) (err error) {
	str := fmt.Sprintf("\033[%d;%dH", line + 1, col + 1)
	_, err = io.WriteString(os.Stdout, str)
	return err
}

func main() {
	err := getSttyState(&originalSttyState)
	if err != nil {
		log.Fatal(err)
	}
	defer setSttyState(&originalSttyState)

	setSttyState(bytes.NewBufferString("cbreak"))
	setSttyState(bytes.NewBufferString("-echo"))

	err = drawInitialScreen()
	if err != nil {
		log.Fatal(err)
	}

	var input []byte = make([]byte, 0)
	var b []byte = make([]byte, 1)
	for {
		os.Stdin.Read(b)
		switch b[0] {
		case 127:
			// Backspace
			fmt.Printf("%s", "\b \b")
			input = input[:len(input)-1]
		case 4, 13:
			// Return or Ctrl-D
			fmt.Println("Result:")
			fmt.Printf("%s%s\n", carriageReturn, string(input[:len(input)]))
		default:
			// Everything else
			input = append(input, b...)
			fmt.Printf("%c", b[0])
		}

		iname := fmt.Sprintf("*%s*", input[:len(input)])
		out, err := exec.Command("find", ".", "-iname", iname).Output()
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("\n%s", out)
		setCursorPos(0, len(input))
	}
}
<commit_msg>Add getWinsize function<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"syscall"
	"unsafe"
)

const (
	ansiEraseDisplay = "\033[2J"
	ansiResetCursor  = "\033[H"
	carriageReturn   = "\015"
)

var originalSttyState bytes.Buffer
var winRows uint16
var winCols uint16

func getSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", "-g")
	cmd.Stdin = os.Stdin
	cmd.Stdout = state
	return cmd.Run()
}

func setSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", state.String())
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func drawInitialScreen() (err error) {
	_, err = io.WriteString(os.Stdout, ansiEraseDisplay)
	if err != nil {
		return err
	}
	_, err = io.WriteString(os.Stdout, ansiResetCursor)

	return err
}

func setCursorPos(line int, col int) (err error) {
	str := fmt.Sprintf("\033[%d;%dH", line + 1, col + 1)
	_, err = io.WriteString(os.Stdout, str)
	return err
}

type winsize struct {
	rows, cols, xpixel, ypixel uint16
}

func getWinsize() winsize {
	ws := winsize{}
	syscall.Syscall(syscall.SYS_IOCTL,
		uintptr(0), uintptr(syscall.TIOCGWINSZ),
		uintptr(unsafe.Pointer(&ws)))
	return ws
}

func init() {
	ws := getWinsize()
	winRows = ws.rows
	winCols = ws.cols
}

func main() {
	err := getSttyState(&originalSttyState)
	if err != nil {
		log.Fatal(err)
	}
	defer setSttyState(&originalSttyState)

	setSttyState(bytes.NewBufferString("cbreak"))
	setSttyState(bytes.NewBufferString("-echo"))

	err = drawInitialScreen()
	if err != nil {
		log.Fatal(err)
	}

	var input []byte = make([]byte, 0)
	var b []byte = make([]byte, 1)
	for {
		os.Stdin.Read(b)
		switch b[0] {
		case 127:
			// Backspace
			fmt.Printf("%s", "\b \b")
			input = input[:len(input)-1]
		case 4, 13:
			// Return or Ctrl-D
			fmt.Println("Result:")
			fmt.Printf("%s%s\n", carriageReturn, string(input[:len(input)]))
		default:
			// Everything else
			input = append(input, b...)
			fmt.Printf("%c", b[0])
		}

		iname := fmt.Sprintf("*%s*", input[:len(input)])
		out, err := exec.Command("find", ".", "-iname", iname).Output()
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("\n%s", out)
		setCursorPos(0, len(input))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

type mpath struct {
	name string
	path string
}

type vdir string

func (vd vdir) trunc() string {
	s := string(vd)
	if len(s) > 0 {
		return s[:7]
	}
	return ""
}

var mpathre = regexp.MustCompile(`^\.(.*)###(.*)$`)
var vdirre = regexp.MustCompile(`^[a-f0-9]{64}$`)

func newmpath(mp string) *mpath {
	res := mpathre.FindAllStringSubmatch(mp, -1)
	var mres *mpath
	if res != nil && len(res) == 1 && len(res[0]) == 3 {
		name := res[0][1]
		path := res[0][2]
		path = strings.Replace(path, ",#,", "/", -1)
		mres = &mpath{name, path}
	}
	return mres
}

func (mp *mpath) String() string {
	return "." + mp.name + "###" + strings.Replace(mp.path, "/", ",#,", -1)
}

func newvdir(vd string) vdir {
	res := vdirre.FindString(vd)
	vres := vdir("")
	if res != "" && res == vd {
		vres = vdir(vd)
	}
	return vres
}

type marker struct {
	mp  *mpath
	dir vdir
}

func (mk *marker) String() string {
	return fmt.Sprintf("marker '%s'<%s->%s>", mk.dir.trunc(), mk.mp.name, mk.mp.path)
}

type markers []*marker

var allmarkers = markers{}

func (marks markers) getMarker(name string, path string, dir vdir) *marker {
	var mark *marker
	var pathdiff *marker
	for _, marker := range marks {
		if marker.mp.name == name {
			if marker.mp.path == path {
				mark = marker
				break
			} else {
				pathdiff = marker
			}
		}
	}
	if mark == nil {
		if pathdiff != nil {
			fmt.Printf("Invalid volume path detected: '%s' (not found in marker '%s')\n", path, pathdiff)
			return nil
		}
		mark = &marker{mp: &mpath{name: name, path: path}, dir: dir}
	}
	ldir := mark.dir
	if mark.dir != dir {
		// move dir and ln -s mark.dir dir
		ldir = dir
		cmd(fmt.Sprintf("sudo move /mnt/sda1/var/lib/docker/vfs/dir/%s /mnt/sda1/var/lib/docker/vfs/dir/_%s", dir, dir))
		fmt.Printf("Mark container named '%s' for path '%s' as link to '%s' (from '%s')\n", name, path, mark.dir, dir)
	}
	_, err := cmd(fmt.Sprintf("sudo ls -1L /mnt/sda1/var/lib/docker/vfs/dir/%s", mark.mp.String()))
	if err != nil {
		mustcmd(fmt.Sprintf("sudo ln -s %s /mnt/sda1/var/lib/docker/vfs/dir/%s", ldir, mark.mp.String()))
	}
	return mark
}

type volume struct {
	dir      vdir
	mark     *marker
	orphaned bool
}

type volumes []*volume

var allvolumes = volumes{}

func (v *volume) String() string {
	return fmt.Sprintf("vol '%s'<%v>", v.dir.trunc(), v.mark)
}

func (vols volumes) getVolume(vd vdir, path string, name string) *volume {
	var vol *volume
	for _, volume := range vols {
		if string(volume.dir) == string(vd) {
			vol = volume
			if vol.mark == nil {
				vol.mark = allmarkers.getMarker(name, path, vol.dir)
			}
			if vol.mark == nil {
				return nil
			}
			vol.orphaned = false
			break
		}
	}
	if vol == nil {
		vol = &volume{dir: vd, orphaned: true}
		vol.mark = allmarkers.getMarker(name, path, vol.dir)
		if vol.mark == nil {
			return nil
		}
		vol.orphaned = false
	}
	return vol
}

type container struct {
	name     string
	id       string
	stopped  bool
	orphaned bool
	volumes  []*volume
}

func (c *container) trunc() string {
	if len(c.id) > 0 {
		return c.id[:7]
	}
	return ""
}

func (c *container) String() string {
	return "cnt '" + c.name + "' (" + c.trunc() + ")" + fmt.Sprintf("[%v] - %d vol", c.stopped, len(c.volumes))
}

type containers []*container

var allcontainers = containers{}

func mustcmd(acmd string) string {
	out, err := cmd(acmd)
	if err != nil {
		log.Fatal(fmt.Sprintf("out='%s', err='%s'", out, err))
	}
	return string(out)
}

func initLists() {
	allmarkers = markers{}
	allvolumes = volumes{}
	allcontainers = containers{}
	orphanedContainers = containers{}
	orphanedVolumes = volumes{}
}

type fcmd func(cmd string) (string, error)

var cmd = execcmd

func execcmd(cmd string) (string, error) {
	fmt.Println(cmd)
	out, err := exec.Command("sh", "-c", cmd).Output()
	return strings.TrimSpace(string(out)), err
}

// Looks for volume folder or marker symlinks in /var/lib/docker/vfs/dir
func readVolumes() {
	out := mustcmd("sudo ls -a1F /mnt/sda1/var/lib/docker/vfs/dir")
	vollines := strings.Split(out, "\n")
	for _, volline := range vollines {
		dir := volline
		if dir == "./" || dir == "../" || dir == "" {
			continue
		}
		if strings.HasSuffix(dir, "@") {
			dir = dir[:len(dir)-1]
			fdir := fmt.Sprintf("/mnt/sda1/var/lib/docker/vfs/dir/%s", dir)
			mp := newmpath(dir)
			if mp == nil {
				fmt.Printf("Invalid marker detected: '%s'\n", dir)
				mustcmd("sudo rm " + fdir)
			} else {
				dirlink, err := cmd("sudo readlink " + fdir)
				fmt.Printf("---\ndir: '%s'\ndlk: '%s'\nerr='%v'\n", dir, dirlink, err)
				if err != nil {
					fmt.Printf("Invalid marker (no readlink) detected: '%s'\n", dir)
					mustcmd("sudo rm " + fdir)
				} else {
					_, err := cmd("sudo ls /mnt/sda1/var/lib/docker/vfs/dir/" + dirlink)
					if err != nil {
						fmt.Printf("Invalid marker (readlink no ls) detected: '%s'\n", dir)
						mustcmd("sudo rm " + fdir)
					} else {
						vd := newvdir(dirlink)
						if vd == "" {
							fmt.Printf("Invalid marker (readlink no vdir) detected: '%s'\n", dir)
							mustcmd("sudo rm " + fdir)
						} else {
							allmarkers = append(allmarkers, &marker{mp, vd})
						}
					}
				}
			}
		} else if strings.HasSuffix(dir, "/") {

			dir = dir[:len(dir)-1]
			fdir := fmt.Sprintf("/mnt/sda1/var/lib/docker/vfs/dir/%s", dir)
			vd := newvdir(dir)
			if vd == "" {
				fmt.Printf("Invalid volume folder detected: '%s'\n", dir)
				mustcmd("sudo rm " + fdir)
			} else {
				allvolumes = append(allvolumes, &volume{dir: vd, orphaned: true})
			}
		} else {
			fdir := fmt.Sprintf("/mnt/sda1/var/lib/docker/vfs/dir/%s", dir)
			fmt.Printf("Invalid file detected: '%s'\n", dir)
			mustcmd("sudo rm " + fdir)
		}
	}
}

func readContainer() {

	out := mustcmd("docker ps -aq --no-trunc")
	contlines := strings.Split(out, "\n")
	// fmt.Println(contlines)
	for _, contline := range contlines {
		if contline == "" {
			continue
		}
		id := contline
		res := mustcmd("docker inspect -f '{{ .Name }},{{ range $key, $value := .Volumes }}{{ $key }},{{ $value }}##~#{{ end }}' " + id)
		// fmt.Println("res1: '" + res + "'")
		name := res[1:strings.Index(res, ",")]
		cont := &container{name: name, id: id}
		res = res[strings.Index(res, ",")+1:]
		// fmt.Println("res2: '" + res + "'")
		vols := strings.Split(res, "##~#")
		// fmt.Println(vols)
		for _, vol := range vols {
			elts := strings.Split(vol, ",")
			if len(elts) == 2 {
				// fmt.Printf("elts: '%v'\n", elts)
				path := elts[0]
				vfs := elts[1]
				if strings.Contains(vfs, "/var/lib/docker/vfs/dir/") {
					vd := newvdir(filepath.Base(vfs))
					if vd == "" {
						fmt.Printf("Invalid container volume folder detected: '%s'\n", vfs)
						break
					}
					newvol := allvolumes.getVolume(vd, path, name)
					if newvol != nil {
						cont.volumes = append(cont.volumes, newvol)
					} else {
						cont.orphaned = true
					}

				}
			}
		}
		allcontainers = append(allcontainers, cont)
		if cont.orphaned {
			orphanedContainers = append(orphanedContainers, cont)
		}
	}
}

// docker run --rm -i -t -v `pwd`:`pwd` -w `pwd` --entrypoint="/bin/bash" go -c 'go build gcl.go'
func main() {
	initLists()
	readVolumes()
	readContainer()
	for _, vol := range allvolumes {
		if vol.orphaned {
			orphanedVolumes = append(orphanedVolumes, vol)
		}
	}
}

// Containers returns all detected containers
func Containers() []*container {
	return allcontainers
}

var orphanedContainers = []*container{}

// OrphanedContainers returns all detected containers with missing volumes
// They are not orphaned if they have no volumes
// They are orphaned if they have volumes which don't exist
func OrphanedContainers() []*container {
	return orphanedContainers
}

// Volumes returns all detected volumes
func Volumes() volumes {
	return allvolumes
}

var orphanedVolumes = volumes{}

// OrphanedVolumes returns all detected volumes with missing containers
// They are orphaned because no container reference them directly
// or through a marker
func OrphanedVolumes() volumes {
	return orphanedVolumes
}

// Markers returns all detected or created markers
func Markers() markers {
	return allmarkers
}
<commit_msg>gcl.go: container string trunc() takes into account short names<commit_after>package main

import (
	"fmt"
	"log"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

type mpath struct {
	name string
	path string
}

type vdir string

func (vd vdir) trunc() string {
	s := string(vd)
	if len(s) > 0 {
		return s[:7]
	}
	return ""
}

var mpathre = regexp.MustCompile(`^\.(.*)###(.*)$`)
var vdirre = regexp.MustCompile(`^[a-f0-9]{64}$`)

func newmpath(mp string) *mpath {
	res := mpathre.FindAllStringSubmatch(mp, -1)
	var mres *mpath
	if res != nil && len(res) == 1 && len(res[0]) == 3 {
		name := res[0][1]
		path := res[0][2]
		path = strings.Replace(path, ",#,", "/", -1)
		mres = &mpath{name, path}
	}
	return mres
}

func (mp *mpath) String() string {
	return "." + mp.name + "###" + strings.Replace(mp.path, "/", ",#,", -1)
}

func newvdir(vd string) vdir {
	res := vdirre.FindString(vd)
	vres := vdir("")
	if res != "" && res == vd {
		vres = vdir(vd)
	}
	return vres
}

type marker struct {
	mp  *mpath
	dir vdir
}

func (mk *marker) String() string {
	return fmt.Sprintf("marker '%s'<%s->%s>", mk.dir.trunc(), mk.mp.name, mk.mp.path)
}

type markers []*marker

var allmarkers = markers{}

func (marks markers) getMarker(name string, path string, dir vdir) *marker {
	var mark *marker
	var pathdiff *marker
	for _, marker := range marks {
		if marker.mp.name == name {
			if marker.mp.path == path {
				mark = marker
				break
			} else {
				pathdiff = marker
			}
		}
	}
	if mark == nil {
		if pathdiff != nil {
			fmt.Printf("Invalid volume path detected: '%s' (not found in marker '%s')\n", path, pathdiff)
			return nil
		}
		mark = &marker{mp: &mpath{name: name, path: path}, dir: dir}
	}
	ldir := mark.dir
	if mark.dir != dir {
		// move dir and ln -s mark.dir dir
		ldir = dir
		cmd(fmt.Sprintf("sudo move /mnt/sda1/var/lib/docker/vfs/dir/%s /mnt/sda1/var/lib/docker/vfs/dir/_%s", dir, dir))
		fmt.Printf("Mark container named '%s' for path '%s' as link to '%s' (from '%s')\n", name, path, mark.dir, dir)
	}
	_, err := cmd(fmt.Sprintf("sudo ls -1L /mnt/sda1/var/lib/docker/vfs/dir/%s", mark.mp.String()))
	if err != nil {
		mustcmd(fmt.Sprintf("sudo ln -s %s /mnt/sda1/var/lib/docker/vfs/dir/%s", ldir, mark.mp.String()))
	}
	return mark
}

type volume struct {
	dir      vdir
	mark     *marker
	orphaned bool
}

type volumes []*volume

var allvolumes = volumes{}

func (v *volume) String() string {
	return fmt.Sprintf("vol '%s'<%v>", v.dir.trunc(), v.mark)
}

func (vols volumes) getVolume(vd vdir, path string, name string) *volume {
	var vol *volume
	for _, volume := range vols {
		if string(volume.dir) == string(vd) {
			vol = volume
			if vol.mark == nil {
				vol.mark = allmarkers.getMarker(name, path, vol.dir)
			}
			if vol.mark == nil {
				return nil
			}
			vol.orphaned = false
			break
		}
	}
	if vol == nil {
		vol = &volume{dir: vd, orphaned: true}
		vol.mark = allmarkers.getMarker(name, path, vol.dir)
		if vol.mark == nil {
			return nil
		}
		vol.orphaned = false
	}
	return vol
}

type container struct {
	name     string
	id       string
	stopped  bool
	orphaned bool
	volumes  []*volume
}

func (c *container) trunc() string {
	switch {
	case len(c.id) == 0:
		return ""
	case len(c.id) > 6:
		return c.id[:7]
	default:
		return c.id
	}
}

func (c *container) String() string {
	return "cnt '" + c.name + "' (" + c.trunc() + ")" + fmt.Sprintf("[%v] - %d vol", c.stopped, len(c.volumes))
}

type containers []*container

var allcontainers = containers{}

func mustcmd(acmd string) string {
	out, err := cmd(acmd)
	if err != nil {
		log.Fatal(fmt.Sprintf("out='%s', err='%s'", out, err))
	}
	return string(out)
}

func initLists() {
	allmarkers = markers{}
	allvolumes = volumes{}
	allcontainers = containers{}
	orphanedContainers = containers{}
	orphanedVolumes = volumes{}
}

type fcmd func(cmd string) (string, error)

var cmd = execcmd

func execcmd(cmd string) (string, error) {
	fmt.Println(cmd)
	out, err := exec.Command("sh", "-c", cmd).Output()
	return strings.TrimSpace(string(out)), err
}

// Looks for volume folder or marker symlinks in /var/lib/docker/vfs/dir
func readVolumes() {
	out := mustcmd("sudo ls -a1F /mnt/sda1/var/lib/docker/vfs/dir")
	vollines := strings.Split(out, "\n")
	for _, volline := range vollines {
		dir := volline
		if dir == "./" || dir == "../" || dir == "" {
			continue
		}
		if strings.HasSuffix(dir, "@") {
			dir = dir[:len(dir)-1]
			fdir := fmt.Sprintf("/mnt/sda1/var/lib/docker/vfs/dir/%s", dir)
			mp := newmpath(dir)
			if mp == nil {
				fmt.Printf("Invalid marker detected: '%s'\n", dir)
				mustcmd("sudo rm " + fdir)
			} else {
				dirlink, err := cmd("sudo readlink " + fdir)
				fmt.Printf("---\ndir: '%s'\ndlk: '%s'\nerr='%v'\n", dir, dirlink, err)
				if err != nil {
					fmt.Printf("Invalid marker (no readlink) detected: '%s'\n", dir)
					mustcmd("sudo rm " + fdir)
				} else {
					_, err := cmd("sudo ls /mnt/sda1/var/lib/docker/vfs/dir/" + dirlink)
					if err != nil {
						fmt.Printf("Invalid marker (readlink no ls) detected: '%s'\n", dir)
						mustcmd("sudo rm " + fdir)
					} else {
						vd := newvdir(dirlink)
						if vd == "" {
							fmt.Printf("Invalid marker (readlink no vdir) detected: '%s'\n", dir)
							mustcmd("sudo rm " + fdir)
						} else {
							allmarkers = append(allmarkers, &marker{mp, vd})
						}
					}
				}
			}
		} else if strings.HasSuffix(dir, "/") {

			dir = dir[:len(dir)-1]
			fdir := fmt.Sprintf("/mnt/sda1/var/lib/docker/vfs/dir/%s", dir)
			vd := newvdir(dir)
			if vd == "" {
				fmt.Printf("Invalid volume folder detected: '%s'\n", dir)
				mustcmd("sudo rm " + fdir)
			} else {
				allvolumes = append(allvolumes, &volume{dir: vd, orphaned: true})
			}
		} else {
			fdir := fmt.Sprintf("/mnt/sda1/var/lib/docker/vfs/dir/%s", dir)
			fmt.Printf("Invalid file detected: '%s'\n", dir)
			mustcmd("sudo rm " + fdir)
		}
	}
}

func readContainer() {

	out := mustcmd("docker ps -aq --no-trunc")
	contlines := strings.Split(out, "\n")
	// fmt.Println(contlines)
	for _, contline := range contlines {
		if contline == "" {
			continue
		}
		id := contline
		res := mustcmd("docker inspect -f '{{ .Name }},{{ range $key, $value := .Volumes }}{{ $key }},{{ $value }}##~#{{ end }}' " + id)
		// fmt.Println("res1: '" + res + "'")
		name := res[1:strings.Index(res, ",")]
		cont := &container{name: name, id: id}
		res = res[strings.Index(res, ",")+1:]
		// fmt.Println("res2: '" + res + "'")
		vols := strings.Split(res, "##~#")
		// fmt.Println(vols)
		for _, vol := range vols {
			elts := strings.Split(vol, ",")
			if len(elts) == 2 {
				// fmt.Printf("elts: '%v'\n", elts)
				path := elts[0]
				vfs := elts[1]
				if strings.Contains(vfs, "/var/lib/docker/vfs/dir/") {
					vd := newvdir(filepath.Base(vfs))
					if vd == "" {
						fmt.Printf("Invalid container volume folder detected: '%s'\n", vfs)
						break
					}
					newvol := allvolumes.getVolume(vd, path, name)
					if newvol != nil {
						cont.volumes = append(cont.volumes, newvol)
					} else {
						cont.orphaned = true
					}

				}
			}
		}
		allcontainers = append(allcontainers, cont)
		if cont.orphaned {
			orphanedContainers = append(orphanedContainers, cont)
		}
	}
}

// docker run --rm -i -t -v `pwd`:`pwd` -w `pwd` --entrypoint="/bin/bash" go -c 'go build gcl.go'
func main() {
	initLists()
	readVolumes()
	readContainer()
	for _, vol := range allvolumes {
		if vol.orphaned {
			orphanedVolumes = append(orphanedVolumes, vol)
		}
	}
}

// Containers returns all detected containers
func Containers() []*container {
	return allcontainers
}

var orphanedContainers = []*container{}

// OrphanedContainers returns all detected containers with missing volumes
// They are not orphaned if they have no volumes
// They are orphaned if they have volumes which don't exist
func OrphanedContainers() []*container {
	return orphanedContainers
}

// Volumes returns all detected volumes
func Volumes() volumes {
	return allvolumes
}

var orphanedVolumes = volumes{}

// OrphanedVolumes returns all detected volumes with missing containers
// They are orphaned because no container reference them directly
// or through a marker
func OrphanedVolumes() volumes {
	return orphanedVolumes
}

// Markers returns all detected or created markers
func Markers() markers {
	return allmarkers
}
<|endoftext|>


<commit_before>//go:generate templates -s templates -o templates/templates.go
package schematic

import (
	"bytes"
	"encoding/json"
	"fmt"
	"go/format"
	"os"
	"strings"
	"text/template"

	bundle "github.com/interagent/schematic/templates"
)

var templates *template.Template

func init() {
	templates = template.New("package.tmpl").Funcs(helpers)
	templates = template.Must(bundle.Parse(templates))
}

// ResolvedSet stores a set of pointers to objects that have already been
// resolved to prevent infinite loops.
type ResolvedSet map[interface{}]bool

// Insert marks a pointer as resolved.
func (rs ResolvedSet) Insert(o interface{}) {
	rs[o] = true
}

// Has returns if a pointer has already been resolved.
func (rs ResolvedSet) Has(o interface{}) bool {
	return rs[o]
}

// Generate generates code according to the schema.
func (s *Schema) Generate() ([]byte, error) {
	var buf bytes.Buffer

	s = s.Resolve(nil, ResolvedSet{})

	name := strings.ToLower(strings.Split(s.Title, " ")[0])
	templates.ExecuteTemplate(&buf, "package.tmpl", name)

	// TODO: Check if we need time.
	templates.ExecuteTemplate(&buf, "imports.tmpl", []string{
		"encoding/json", "fmt", "io", "reflect", "net/http", "runtime",
		"time", "bytes", "context", "strings",
		"github.com/google/go-querystring/query",
	})
	templates.ExecuteTemplate(&buf, "service.tmpl", struct {
		Name    string
		URL     string
		Version string
	}{
		Name:    name,
		URL:     s.URL(),
		Version: s.Version,
	})

	for _, name := range sortedKeys(s.Properties) {
		schema := s.Properties[name]
		// Skipping definitions because there is no links, nor properties.
		if schema.Links == nil && schema.Properties == nil {
			continue
		}

		context := struct {
			Name       string
			Definition *Schema
		}{
			Name:       name,
			Definition: schema,
		}

		if !context.Definition.AreTitleLinksUnique() {
			return nil, fmt.Errorf("duplicate titles detected for %s", context.Name)
		}

		templates.ExecuteTemplate(&buf, "struct.tmpl", context)
		templates.ExecuteTemplate(&buf, "funcs.tmpl", context)
	}

	// Remove blank lines added by text/template
	bytes := newlines.ReplaceAll(buf.Bytes(), []byte(""))

	// Format sources
	clean, err := format.Source(bytes)
	if err != nil {
		return buf.Bytes(), err
	}
	return clean, nil
}

// Resolve resolves reference inside the schema.
func (s *Schema) Resolve(r *Schema, rs ResolvedSet) *Schema {
	if r == nil {
		r = s
	}

	for {
		if s.Ref != nil {
			s = s.Ref.Resolve(r)
		} else if len(s.OneOf) > 0 {
			s = s.OneOf[0].Ref.Resolve(r)
		} else if len(s.AnyOf) > 0 {
			s = s.AnyOf[0].Ref.Resolve(r)
		} else {
			break
		}
	}

	if rs.Has(s) {
		// Already resolved
		return s
	}
	rs.Insert(s)

	for n, d := range s.Definitions {
		s.Definitions[n] = d.Resolve(r, rs)
	}
	for n, p := range s.Properties {
		s.Properties[n] = p.Resolve(r, rs)
	}
	for n, p := range s.PatternProperties {
		s.PatternProperties[n] = p.Resolve(r, rs)
	}
	if s.Items != nil {
		s.Items = s.Items.Resolve(r, rs)
	}
	for _, l := range s.Links {
		l.Resolve(r, rs)
	}
	return s
}

// Types returns the array of types described by this schema.
func (s *Schema) Types() (types []string, err error) {
	if arr, ok := s.Type.([]interface{}); ok {
		for _, v := range arr {
			types = append(types, v.(string))
		}
	} else if str, ok := s.Type.(string); ok {
		types = append(types, str)
	} else {
		err = fmt.Errorf("unknown type %v", s.Type)
	}
	return types, err
}

// GoType returns the Go type for the given schema as string.
func (s *Schema) GoType() string {
	return s.goType(true, true)
}

// IsCustomType returns true if the schema declares a custom type.
func (s *Schema) IsCustomType() bool {
	return len(s.Properties) > 0
}

func (s *Schema) goType(required bool, force bool) (goType string) {
	// Resolve JSON reference/pointer
	types, err := s.Types()
	if err != nil {
		fail(s, err)
	}
	for _, kind := range types {
		switch kind {
		case "boolean":
			goType = "bool"
		case "string":
			switch s.Format {
			case "date-time":
				goType = "time.Time"
			default:
				goType = "string"
			}
		case "number":
			goType = "float64"
		case "integer":
			goType = "int"
		case "any":
			goType = "interface{}"
		case "array":
			if s.Items != nil {
				goType = "[]" + s.Items.goType(required, force)
			} else {
				goType = "[]interface{}"
			}
		case "object":
			// Check if patternProperties exists.
			if s.PatternProperties != nil {
				for _, prop := range s.PatternProperties {
					goType = fmt.Sprintf("map[string]%s", prop.GoType())
					break // We don't support more than one pattern for now.
				}
				continue
			}
			buf := bytes.NewBufferString("struct {")
			for _, name := range sortedKeys(s.Properties) {
				prop := s.Properties[name]
				req := contains(name, s.Required) || force
				templates.ExecuteTemplate(buf, "field.tmpl", struct {
					Definition *Schema
					Name       string
					Required   bool
					Type       string
				}{
					Definition: prop,
					Name:       name,
					Required:   req,
					Type:       prop.goType(req, force),
				})
			}
			buf.WriteString("}")
			goType = buf.String()
		case "null":
			continue
		default:
			fail(s, fmt.Errorf("unknown type %s", kind))
		}
	}
	if goType == "" {
		fail(s, fmt.Errorf("type not found : %s", types))
	}
	// Types allow null
	if contains("null", types) || !(required || force) {
		// Don't need a pointer for these types to be "nilable"
		if goType != "interface{}" && !strings.HasPrefix(goType, "[]") && !strings.HasPrefix(goType, "map[") {
			return "*" + goType
		}
	}
	return goType
}

// Values returns function return values types.
func (s *Schema) Values(name string, l *Link) []string {
	var values []string
	name = returnType(name, s, l)
	if s.EmptyResult(l) {
		values = append(values, "error")
	} else if s.ReturnsCustomType(l) {
		values = append(values, fmt.Sprintf("*%s", name), "error")
	} else {
		values = append(values, name, "error")
	}
	return values
}

// AreTitleLinksUnique ensures that all titles are unique for a schema.
//
// If more than one link in a given schema has the same title, we cannot
// accurately generate the client from the schema. Although it's not strictly a
// schema violation, it needs to be fixed before the client can be properly
// generated.
func (s *Schema) AreTitleLinksUnique() bool {
	titles := map[string]bool{}
	var uniqueLinks []*Link
	for _, link := range s.Links {
		title := strings.ToLower(link.Title)
		if _, ok := titles[title]; !ok {
			uniqueLinks = append(uniqueLinks, link)
		}
		titles[title] = true
	}

	return len(uniqueLinks) == len(s.Links)
}

// URL returns schema base URL.
func (s *Schema) URL() string {
	for _, l := range s.Links {
		if l.Rel == "self" {
			return l.HRef.String()
		}
	}
	return ""
}

// ReturnsCustomType returns true if the link returns a custom type.
func (s *Schema) ReturnsCustomType(l *Link) bool {
	if l.TargetSchema != nil {
		return len(l.TargetSchema.Properties) > 0
	}
	return len(s.Properties) > 0
}

// ReturnedGoType returns Go type returned by the given link as a string.
func (s *Schema) ReturnedGoType(name string, l *Link) string {
	if l.TargetSchema != nil {
		if l.TargetSchema.Items == s {
			return "[]" + initialCap(name)
		}
		return l.TargetSchema.goType(true, true)
	}
	return s.goType(true, true)
}

// EmptyResult retursn true if the link result should be empty.
func (s *Schema) EmptyResult(l *Link) bool {
	var (
		types []string
		err   error
	)
	if l.TargetSchema != nil {
		types, err = l.TargetSchema.Types()
	} else {
		types, err = s.Types()
	}
	if err != nil {
		return true
	}
	return len(types) == 1 && types[0] == "null"
}

// Parameters returns function parameters names and types.
func (l *Link) Parameters(name string) ([]string, map[string]string) {
	if l.HRef == nil {
		// No HRef property
		panic(fmt.Errorf("no href property declared for %s", l.Title))
	}
	var order []string
	params := make(map[string]string)
	for _, name := range l.HRef.Order {
		def := l.HRef.Schemas[name]
		order = append(order, name)
		params[name] = def.GoType()
	}
	if l.Schema != nil {
		order = append(order, "o")
		t, required := l.GoType()
		if l.AcceptsCustomType() {
			params["o"] = paramType(name, l)
		} else {
			params["o"] = t
		}
		if !required {
			params["o"] = "*" + params["o"]
		}
	}
	if l.Rel == "instances" && strings.ToUpper(l.Method) == "GET" {
		order = append(order, "lr")
		params["lr"] = "*ListRange"
	}
	return order, params
}

// AcceptsCustomType returns true if the link schema is not a primitive type
func (l *Link) AcceptsCustomType() bool {
	if l.Schema != nil && l.Schema.IsCustomType() {
		return true
	}
	return false
}

// Resolve resolve link schema and href.
func (l *Link) Resolve(r *Schema, rs ResolvedSet) {
	if l.Schema != nil {
		l.Schema = l.Schema.Resolve(r, rs)
	}
	if l.TargetSchema != nil {
		l.TargetSchema = l.TargetSchema.Resolve(r, rs)
	}
	l.HRef.Resolve(r, rs)
}

// GoType returns Go type for the given schema as string and a bool specifying whether it is required
func (l *Link) GoType() (string, bool) {
	t := l.Schema.goType(true, false)
	if t[0] == '*' {
		return t[1:], false
	}
	return t, true
}

func fail(v interface{}, err error) {
	el, _ := json.MarshalIndent(v, "    ", "  ")

	fmt.Fprintf(
		os.Stderr,
		"Error processing schema element:\n    %s\n\nFailed with: %s\n",
		el,
		err,
	)
	os.Exit(1)
}
<commit_msg>Added check if time is required<commit_after>//go:generate templates -s templates -o templates/templates.go
package schematic

import (
	"bytes"
	"encoding/json"
	"fmt"
	"go/format"
	"os"
	"strings"
	"text/template"

	bundle "github.com/interagent/schematic/templates"
)

var templates *template.Template

func init() {
	templates = template.New("package.tmpl").Funcs(helpers)
	templates = template.Must(bundle.Parse(templates))
}

// ResolvedSet stores a set of pointers to objects that have already been
// resolved to prevent infinite loops.
type ResolvedSet map[interface{}]bool

// Insert marks a pointer as resolved.
func (rs ResolvedSet) Insert(o interface{}) {
	rs[o] = true
}

// Has returns if a pointer has already been resolved.
func (rs ResolvedSet) Has(o interface{}) bool {
	return rs[o]
}

func (s *Schema) needsTime() bool {
	if s.Type == "string" && s.goType(true, true) == "time.Time" {
		return true
	}

	for _, propertySchema := range s.Properties {
		if propertySchema.needsTime() {
			return true
		}
	}

	return false
}

// Generate generates code according to the schema.
func (s *Schema) Generate() ([]byte, error) {
	var buf bytes.Buffer

	s = s.Resolve(nil, ResolvedSet{})

	name := strings.ToLower(strings.Split(s.Title, " ")[0])
	templates.ExecuteTemplate(&buf, "package.tmpl", name)
	imports := []string{
		"encoding/json", "fmt", "io", "reflect", "net/http", "runtime",
		"bytes", "context", "strings",
		"github.com/google/go-querystring/query",
	}
	if s.needsTime() {
		imports = append(imports, "time")
	}

	templates.ExecuteTemplate(&buf, "imports.tmpl", imports)
	templates.ExecuteTemplate(&buf, "service.tmpl", struct {
		Name    string
		URL     string
		Version string
	}{
		Name:    name,
		URL:     s.URL(),
		Version: s.Version,
	})

	for _, name := range sortedKeys(s.Properties) {
		schema := s.Properties[name]
		// Skipping definitions because there is no links, nor properties.
		if schema.Links == nil && schema.Properties == nil {
			continue
		}

		context := struct {
			Name       string
			Definition *Schema
		}{
			Name:       name,
			Definition: schema,
		}

		if !context.Definition.AreTitleLinksUnique() {
			return nil, fmt.Errorf("duplicate titles detected for %s", context.Name)
		}

		templates.ExecuteTemplate(&buf, "struct.tmpl", context)
		templates.ExecuteTemplate(&buf, "funcs.tmpl", context)
	}

	// Remove blank lines added by text/template
	bytes := newlines.ReplaceAll(buf.Bytes(), []byte(""))

	// Format sources
	clean, err := format.Source(bytes)
	if err != nil {
		return buf.Bytes(), err
	}
	return clean, nil
}

// Resolve resolves reference inside the schema.
func (s *Schema) Resolve(r *Schema, rs ResolvedSet) *Schema {
	if r == nil {
		r = s
	}

	for {
		if s.Ref != nil {
			s = s.Ref.Resolve(r)
		} else if len(s.OneOf) > 0 {
			s = s.OneOf[0].Ref.Resolve(r)
		} else if len(s.AnyOf) > 0 {
			s = s.AnyOf[0].Ref.Resolve(r)
		} else {
			break
		}
	}

	if rs.Has(s) {
		// Already resolved
		return s
	}
	rs.Insert(s)

	for n, d := range s.Definitions {
		s.Definitions[n] = d.Resolve(r, rs)
	}
	for n, p := range s.Properties {
		s.Properties[n] = p.Resolve(r, rs)
	}
	for n, p := range s.PatternProperties {
		s.PatternProperties[n] = p.Resolve(r, rs)
	}
	if s.Items != nil {
		s.Items = s.Items.Resolve(r, rs)
	}
	for _, l := range s.Links {
		l.Resolve(r, rs)
	}
	return s
}

// Types returns the array of types described by this schema.
func (s *Schema) Types() (types []string, err error) {
	if arr, ok := s.Type.([]interface{}); ok {
		for _, v := range arr {
			types = append(types, v.(string))
		}
	} else if str, ok := s.Type.(string); ok {
		types = append(types, str)
	} else {
		err = fmt.Errorf("unknown type %v", s.Type)
	}
	return types, err
}

// GoType returns the Go type for the given schema as string.
func (s *Schema) GoType() string {
	return s.goType(true, true)
}

// IsCustomType returns true if the schema declares a custom type.
func (s *Schema) IsCustomType() bool {
	return len(s.Properties) > 0
}

func (s *Schema) goType(required bool, force bool) (goType string) {
	// Resolve JSON reference/pointer
	types, err := s.Types()
	if err != nil {
		fail(s, err)
	}
	for _, kind := range types {
		switch kind {
		case "boolean":
			goType = "bool"
		case "string":
			switch s.Format {
			case "date-time":
				goType = "time.Time"
			default:
				goType = "string"
			}
		case "number":
			goType = "float64"
		case "integer":
			goType = "int"
		case "any":
			goType = "interface{}"
		case "array":
			if s.Items != nil {
				goType = "[]" + s.Items.goType(required, force)
			} else {
				goType = "[]interface{}"
			}
		case "object":
			// Check if patternProperties exists.
			if s.PatternProperties != nil {
				for _, prop := range s.PatternProperties {
					goType = fmt.Sprintf("map[string]%s", prop.GoType())
					break // We don't support more than one pattern for now.
				}
				continue
			}
			buf := bytes.NewBufferString("struct {")
			for _, name := range sortedKeys(s.Properties) {
				prop := s.Properties[name]
				req := contains(name, s.Required) || force
				templates.ExecuteTemplate(buf, "field.tmpl", struct {
					Definition *Schema
					Name       string
					Required   bool
					Type       string
				}{
					Definition: prop,
					Name:       name,
					Required:   req,
					Type:       prop.goType(req, force),
				})
			}
			buf.WriteString("}")
			goType = buf.String()
		case "null":
			continue
		default:
			fail(s, fmt.Errorf("unknown type %s", kind))
		}
	}
	if goType == "" {
		fail(s, fmt.Errorf("type not found : %s", types))
	}
	// Types allow null
	if contains("null", types) || !(required || force) {
		// Don't need a pointer for these types to be "nilable"
		if goType != "interface{}" && !strings.HasPrefix(goType, "[]") && !strings.HasPrefix(goType, "map[") {
			return "*" + goType
		}
	}
	return goType
}

// Values returns function return values types.
func (s *Schema) Values(name string, l *Link) []string {
	var values []string
	name = returnType(name, s, l)
	if s.EmptyResult(l) {
		values = append(values, "error")
	} else if s.ReturnsCustomType(l) {
		values = append(values, fmt.Sprintf("*%s", name), "error")
	} else {
		values = append(values, name, "error")
	}
	return values
}

// AreTitleLinksUnique ensures that all titles are unique for a schema.
//
// If more than one link in a given schema has the same title, we cannot
// accurately generate the client from the schema. Although it's not strictly a
// schema violation, it needs to be fixed before the client can be properly
// generated.
func (s *Schema) AreTitleLinksUnique() bool {
	titles := map[string]bool{}
	var uniqueLinks []*Link
	for _, link := range s.Links {
		title := strings.ToLower(link.Title)
		if _, ok := titles[title]; !ok {
			uniqueLinks = append(uniqueLinks, link)
		}
		titles[title] = true
	}

	return len(uniqueLinks) == len(s.Links)
}

// URL returns schema base URL.
func (s *Schema) URL() string {
	for _, l := range s.Links {
		if l.Rel == "self" {
			return l.HRef.String()
		}
	}
	return ""
}

// ReturnsCustomType returns true if the link returns a custom type.
func (s *Schema) ReturnsCustomType(l *Link) bool {
	if l.TargetSchema != nil {
		return len(l.TargetSchema.Properties) > 0
	}
	return len(s.Properties) > 0
}

// ReturnedGoType returns Go type returned by the given link as a string.
func (s *Schema) ReturnedGoType(name string, l *Link) string {
	if l.TargetSchema != nil {
		if l.TargetSchema.Items == s {
			return "[]" + initialCap(name)
		}
		return l.TargetSchema.goType(true, true)
	}
	return s.goType(true, true)
}

// EmptyResult retursn true if the link result should be empty.
func (s *Schema) EmptyResult(l *Link) bool {
	var (
		types []string
		err   error
	)
	if l.TargetSchema != nil {
		types, err = l.TargetSchema.Types()
	} else {
		types, err = s.Types()
	}
	if err != nil {
		return true
	}
	return len(types) == 1 && types[0] == "null"
}

// Parameters returns function parameters names and types.
func (l *Link) Parameters(name string) ([]string, map[string]string) {
	if l.HRef == nil {
		// No HRef property
		panic(fmt.Errorf("no href property declared for %s", l.Title))
	}
	var order []string
	params := make(map[string]string)
	for _, name := range l.HRef.Order {
		def := l.HRef.Schemas[name]
		order = append(order, name)
		params[name] = def.GoType()
	}
	if l.Schema != nil {
		order = append(order, "o")
		t, required := l.GoType()
		if l.AcceptsCustomType() {
			params["o"] = paramType(name, l)
		} else {
			params["o"] = t
		}
		if !required {
			params["o"] = "*" + params["o"]
		}
	}
	if l.Rel == "instances" && strings.ToUpper(l.Method) == "GET" {
		order = append(order, "lr")
		params["lr"] = "*ListRange"
	}
	return order, params
}

// AcceptsCustomType returns true if the link schema is not a primitive type
func (l *Link) AcceptsCustomType() bool {
	if l.Schema != nil && l.Schema.IsCustomType() {
		return true
	}
	return false
}

// Resolve resolve link schema and href.
func (l *Link) Resolve(r *Schema, rs ResolvedSet) {
	if l.Schema != nil {
		l.Schema = l.Schema.Resolve(r, rs)
	}
	if l.TargetSchema != nil {
		l.TargetSchema = l.TargetSchema.Resolve(r, rs)
	}
	l.HRef.Resolve(r, rs)
}

// GoType returns Go type for the given schema as string and a bool specifying whether it is required
func (l *Link) GoType() (string, bool) {
	t := l.Schema.goType(true, false)
	if t[0] == '*' {
		return t[1:], false
	}
	return t, true
}

func fail(v interface{}, err error) {
	el, _ := json.MarshalIndent(v, "    ", "  ")

	fmt.Fprintf(
		os.Stderr,
		"Error processing schema element:\n    %s\n\nFailed with: %s\n",
		el,
		err,
	)
	os.Exit(1)
}
<|endoftext|>