<commit_before>package zettajail

import "fmt"
import "io/ioutil"
import "log"
import "net"
import "net/url"
import "os"
import "path"
import "path/filepath"
import "strconv"
import "strings"

import "github.com/3ofcoins/zettajail/cli"

const jailRcConf = `sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
cron_enable="NO"
devd_enable="NO"
syslogd_enable="NO"
`

func (rt *Runtime) CmdCtlJail() error {
	var op string
	switch rt.Command {
	case "start":
		op = "-c"
	case "stop":
		op = "-r"
	case "restart":
		op = "-rc"
	case "modify":
		switch {
		case rt.ModForce && rt.ModStart:
			op = "-cmr"
		case rt.ModForce:
			op = "-rm"
		case rt.ModStart:
			op = "-cm"
		default:
			op = "-m"
		}
	}
	return rt.ForEachJail(func(jail *Jail) error {
		// FIXME: feedback
		return jail.RunJail(op)
	})
}

func (rt *Runtime) CmdInfo() error {
	if len(rt.Args) == 0 {
		log.Println("Root ZFS dataset:", rt.Host().Name)
		if !rt.Host().Exists() {
			log.Println("Root ZFS dataset does not exist. Please run `zjail init`.")
			return nil
		}
		log.Println("File system root:", rt.Host().Mountpoint)
		iface, err := net.InterfaceByName(rt.Host().Properties["zettajail:jail:interface"])
		if err != nil {
			return err
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return err
		}
		log.Printf("Interface: %v (%v)\n", iface.Name, addrs[0])
		return nil
	}
	return rt.ForEachJail(func(jail *Jail) error {
		if err := jail.Status(); err != nil {
			return err
		}
		if jail.Origin != "" {
			origin := jail.Origin
			if strings.HasPrefix(origin, rt.Host().Name+"/") {
				origin = origin[len(rt.Host().Name)+1:]
			}
			log.Println("Origin:", origin)
		}
		log.Println("Snapshots:", jail.Snapshots())
		return jail.WriteConfigTo(os.Stdout)
	})
}

func printTree(allJails []*Jail, snap Dataset, indent string) {
	origin := ""
	if snap != ZeroDataset {
		origin = snap.Name
	}

	jails := []*Jail{}
	for _, jail := range allJails {
		if jail.Origin == origin {
			jails = append(jails, jail)
		}
	}

	for i, jail := range jails {
		halfdent := "┃"
		item := "┠"
		if i == len(jails)-1 {
			halfdent = " "
			item = "┖"
		}
		fmt.Printf("%s%s%s\n", indent, item, jail)

		snaps := jail.Snapshots()
		for i, snap := range snaps {
			halfdent2 := "│"
			item := "├"
			if i == len(snaps)-1 {
				halfdent2 = " "
				item = "└"
			}
			fmt.Printf("%s%s%s%s\n", indent, halfdent, item, snap)
			printTree(allJails, snap, indent+halfdent+halfdent2)
		}
	}
}

func (rt *Runtime) CmdTree() error {
	printTree(rt.Host().Jails(), ZeroDataset, "")
	return nil
}

func (rt *Runtime) CmdStatus() error {
	return rt.ForEachJail(func(jail *Jail) error {
		return jail.Status()
	})
}

func (rt *Runtime) CmdPs() error {
	jail, err := rt.Host().GetJail(rt.Shift())
	if err != nil {
		return err
	}
	jid := jail.Jid()
	if jid == 0 {
		return fmt.Errorf("%s is not running", jail)
	}
	psArgs := []string{"-J", strconv.Itoa(jid)}
	psArgs = append(psArgs, rt.Args...)
	return RunCommand("ps", psArgs...)
}

func (rt *Runtime) CmdConsole() error {
	if len(rt.Args) == 0 {
		return cli.ErrUsage
	}
	jail, err := rt.Host().GetJail(rt.Shift())
	if err != nil {
		return err
	}
	if !jail.IsActive() {
		return fmt.Errorf("%s is not started", jail)
	}

	args := rt.Args
	user := rt.User
	if len(args) == 0 {
		args = []string{"login", "-f", user}
		user = ""
	}
	if user == "root" {
		user = ""
	}
	log.Println(user, args)
	return jail.RunJexec(user, args)
}

func (rt *Runtime) CmdSet() error {
	// FIXME: modify if running, -f for force-modify
	if len(rt.Args) < 2 {
		return cli.ErrUsage
	}
	jail, err := rt.Host().GetJail(rt.Shift())
	if err != nil {
		return err
	}
	return jail.SetProperties(rt.Properties())
}

func (rt *Runtime) cmdInitDwim() (*Host, error) {
	if rt.Folder == "" {
		return CreateHost(rt.ZFSRoot, rt.Properties())
	} else {
		folder := rt.Folder
		rt.Folder = ""
		return rt.Host().CreateFolder(folder, rt.Properties())
	}
}

func (rt *Runtime) CmdInit() error {
	host, err := rt.cmdInitDwim()
	if err != nil {
		return err
	}
	rt.host = host
	return rt.CmdInfo()
}

func (rt *Runtime) CmdSnapshot() error {
	return rt.ForEachJail(func(jail *Jail) error {
		// FIXME: feedback
		_, err := jail.Snapshot(rt.Snapshot, false)
		return err
	})
}

func (rt *Runtime) CmdCreate() error {
	jailName := rt.Shift()
	jail, err := rt.Host().CreateJail(jailName, rt.Properties())
	if err != nil {
		return err
	}
	if rt.Install == "" {
		return nil
	}

	// Maybe just use fetch(1)'s copy/link behaviour here?
	switch fi, err := os.Stat(rt.Install); {
	case err == nil && fi.IsDir():
		rt.Install = filepath.Join(rt.Install, "base.txz")
		if _, err = os.Stat(rt.Install); err != nil {
			return err
		}
	case err == nil:
		// Pass. It is a file, so we assume it's base.txz
	case os.IsNotExist(err):
		if url, err := url.Parse(rt.Install); err != nil {
			return err
		} else {
			// FIXME: fetch MANIFEST, check checksum
			if path.Ext(url.Path) != "txz" {
				// Directory URL
				url.Path = path.Join(url.Path, "base.txz")
			}
			distdir := jail.Path("dist")
			if err := os.MkdirAll(distdir, 0755); err != nil {
				return err
			}
			distfile := filepath.Join(distdir, path.Base(url.Path))

			log.Println("Downloading", url)
			if err := RunCommand("fetch", "-o", distfile, "-m", "-l", url.String()); err != nil {
				return err
			}
			rt.Install = distfile
		}
		// Check if it's an URL, fetch if yes, bomb if not
	default:
		// Weird error we can't handle
		return err
	}

	log.Println("Unpacking", rt.Install)
	if err := RunCommand("tar", "-C", jail.Mountpoint, "-xpf", rt.Install); err != nil {
		return err
	}

	log.Println("Configuring", jail.Mountpoint)
	if err := ioutil.WriteFile(filepath.Join(jail.Mountpoint, "/etc/rc.conf"), []byte(jailRcConf), 0644); err != nil {
		return err
	}

	if bb, err := ioutil.ReadFile("/etc/resolv.conf"); err != nil {
		return err
	} else {
		if err := ioutil.WriteFile(filepath.Join(jail.Mountpoint, "/etc/resolv.conf"), bb, 0644); err != nil {
			return err
		}
	}

	rf, err := os.Open("/dev/random")
	if err != nil {
		return err
	}
	defer rf.Close()
	entropy := make([]byte, 4096)
	if _, err := rf.Read(entropy); err != nil {
		return err
	}
	return ioutil.WriteFile(filepath.Join(jail.Mountpoint, "/entropy"), entropy, 0600)
}

func (rt *Runtime) CmdClone() error {
	snapName := rt.Shift()
	jailName := rt.Shift()
	_, err := rt.Host().CloneJail(snapName, jailName, rt.Properties())
	return err
}
<commit_msg>pretty tree<commit_after>package zettajail

import "fmt"
import "io/ioutil"
import "log"
import "net"
import "net/url"
import "os"
import "path"
import "path/filepath"
import "strconv"
import "strings"

import "github.com/3ofcoins/zettajail/cli"

const jailRcConf = `sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
cron_enable="NO"
devd_enable="NO"
syslogd_enable="NO"
`

func (rt *Runtime) CmdCtlJail() error {
	var op string
	switch rt.Command {
	case "start":
		op = "-c"
	case "stop":
		op = "-r"
	case "restart":
		op = "-rc"
	case "modify":
		switch {
		case rt.ModForce && rt.ModStart:
			op = "-cmr"
		case rt.ModForce:
			op = "-rm"
		case rt.ModStart:
			op = "-cm"
		default:
			op = "-m"
		}
	}
	return rt.ForEachJail(func(jail *Jail) error {
		// FIXME: feedback
		return jail.RunJail(op)
	})
}

func (rt *Runtime) CmdInfo() error {
	if len(rt.Args) == 0 {
		log.Println("Root ZFS dataset:", rt.Host().Name)
		if !rt.Host().Exists() {
			log.Println("Root ZFS dataset does not exist. Please run `zjail init`.")
			return nil
		}
		log.Println("File system root:", rt.Host().Mountpoint)
		iface, err := net.InterfaceByName(rt.Host().Properties["zettajail:jail:interface"])
		if err != nil {
			return err
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return err
		}
		log.Printf("Interface: %v (%v)\n", iface.Name, addrs[0])
		return nil
	}
	return rt.ForEachJail(func(jail *Jail) error {
		if err := jail.Status(); err != nil {
			return err
		}
		if jail.Origin != "" {
			origin := jail.Origin
			if strings.HasPrefix(origin, rt.Host().Name+"/") {
				origin = origin[len(rt.Host().Name)+1:]
			}
			log.Println("Origin:", origin)
		}
		log.Println("Snapshots:", jail.Snapshots())
		return jail.WriteConfigTo(os.Stdout)
	})
}

func printTree(allJails []*Jail, snap Dataset, indent string) {
	origin := ""
	if snap != ZeroDataset {
		origin = snap.Name
	}

	jails := []*Jail{}
	for _, jail := range allJails {
		if jail.Origin == origin {
			jails = append(jails, jail)
		}
	}

	for i, jail := range jails {
		halfdent := "┃"
		item := "┠"
		if i == len(jails)-1 {
			halfdent = " "
			item = "┖"
		}
		fmt.Printf("%s%s%s\n", indent, item, jail)

		snaps := jail.Snapshots()
		for i, snap := range snaps {
			halfdent2 := "│"
			item := "├"
			if i == len(snaps)-1 {
				halfdent2 = " "
				item = "└"
			}
			fmt.Printf("%s%s%s%s\n", indent, halfdent, item, snap.Name[strings.Index(snap.Name, "@"):])
			printTree(allJails, snap, indent+halfdent+halfdent2)
		}
	}
}

func (rt *Runtime) CmdTree() error {
	printTree(rt.Host().Jails(), ZeroDataset, "")
	return nil
}

func (rt *Runtime) CmdStatus() error {
	return rt.ForEachJail(func(jail *Jail) error {
		return jail.Status()
	})
}

func (rt *Runtime) CmdPs() error {
	jail, err := rt.Host().GetJail(rt.Shift())
	if err != nil {
		return err
	}
	jid := jail.Jid()
	if jid == 0 {
		return fmt.Errorf("%s is not running", jail)
	}
	psArgs := []string{"-J", strconv.Itoa(jid)}
	psArgs = append(psArgs, rt.Args...)
	return RunCommand("ps", psArgs...)
}

func (rt *Runtime) CmdConsole() error {
	if len(rt.Args) == 0 {
		return cli.ErrUsage
	}
	jail, err := rt.Host().GetJail(rt.Shift())
	if err != nil {
		return err
	}
	if !jail.IsActive() {
		return fmt.Errorf("%s is not started", jail)
	}

	args := rt.Args
	user := rt.User
	if len(args) == 0 {
		args = []string{"login", "-f", user}
		user = ""
	}
	if user == "root" {
		user = ""
	}
	log.Println(user, args)
	return jail.RunJexec(user, args)
}

func (rt *Runtime) CmdSet() error {
	// FIXME: modify if running, -f for force-modify
	if len(rt.Args) < 2 {
		return cli.ErrUsage
	}
	jail, err := rt.Host().GetJail(rt.Shift())
	if err != nil {
		return err
	}
	return jail.SetProperties(rt.Properties())
}

func (rt *Runtime) cmdInitDwim() (*Host, error) {
	if rt.Folder == "" {
		return CreateHost(rt.ZFSRoot, rt.Properties())
	} else {
		folder := rt.Folder
		rt.Folder = ""
		return rt.Host().CreateFolder(folder, rt.Properties())
	}
}

func (rt *Runtime) CmdInit() error {
	host, err := rt.cmdInitDwim()
	if err != nil {
		return err
	}
	rt.host = host
	return rt.CmdInfo()
}

func (rt *Runtime) CmdSnapshot() error {
	return rt.ForEachJail(func(jail *Jail) error {
		// FIXME: feedback
		_, err := jail.Snapshot(rt.Snapshot, false)
		return err
	})
}

func (rt *Runtime) CmdCreate() error {
	jailName := rt.Shift()
	jail, err := rt.Host().CreateJail(jailName, rt.Properties())
	if err != nil {
		return err
	}
	if rt.Install == "" {
		return nil
	}

	// Maybe just use fetch(1)'s copy/link behaviour here?
	switch fi, err := os.Stat(rt.Install); {
	case err == nil && fi.IsDir():
		rt.Install = filepath.Join(rt.Install, "base.txz")
		if _, err = os.Stat(rt.Install); err != nil {
			return err
		}
	case err == nil:
		// Pass. It is a file, so we assume it's base.txz
	case os.IsNotExist(err):
		if url, err := url.Parse(rt.Install); err != nil {
			return err
		} else {
			// FIXME: fetch MANIFEST, check checksum
			if path.Ext(url.Path) != "txz" {
				// Directory URL
				url.Path = path.Join(url.Path, "base.txz")
			}
			distdir := jail.Path("dist")
			if err := os.MkdirAll(distdir, 0755); err != nil {
				return err
			}
			distfile := filepath.Join(distdir, path.Base(url.Path))

			log.Println("Downloading", url)
			if err := RunCommand("fetch", "-o", distfile, "-m", "-l", url.String()); err != nil {
				return err
			}
			rt.Install = distfile
		}
		// Check if it's an URL, fetch if yes, bomb if not
	default:
		// Weird error we can't handle
		return err
	}

	log.Println("Unpacking", rt.Install)
	if err := RunCommand("tar", "-C", jail.Mountpoint, "-xpf", rt.Install); err != nil {
		return err
	}

	log.Println("Configuring", jail.Mountpoint)
	if err := ioutil.WriteFile(filepath.Join(jail.Mountpoint, "/etc/rc.conf"), []byte(jailRcConf), 0644); err != nil {
		return err
	}

	if bb, err := ioutil.ReadFile("/etc/resolv.conf"); err != nil {
		return err
	} else {
		if err := ioutil.WriteFile(filepath.Join(jail.Mountpoint, "/etc/resolv.conf"), bb, 0644); err != nil {
			return err
		}
	}

	rf, err := os.Open("/dev/random")
	if err != nil {
		return err
	}
	defer rf.Close()
	entropy := make([]byte, 4096)
	if _, err := rf.Read(entropy); err != nil {
		return err
	}
	return ioutil.WriteFile(filepath.Join(jail.Mountpoint, "/entropy"), entropy, 0600)
}

func (rt *Runtime) CmdClone() error {
	snapName := rt.Shift()
	jailName := rt.Shift()
	_, err := rt.Host().CloneJail(snapName, jailName, rt.Properties())
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"text/tabwriter"
	"time"
	"unicode"

	"go/ast"
	"go/build"
	"go/types"

	"golang.org/x/tools/go/ast/astutil"
)

type command struct {
	name     commandName
	action   func(*Session, string) error
	complete func(*Session, string) []string
	arg      string
	document string
}

// TODO
// - :edit
// - :undo
// - :type
var commands []command

func init() {
	commands = []command{
		{
			name:     commandName("i[mport]"),
			action:   actionImport,
			complete: completeImport,
			arg:      "<package>",
			document: "import a package",
		},
		{
			name:     commandName("t[ype]"),
			action:   actionType,
			arg:      "<expr>",
			complete: completeDoc,
			document: "print the type of expression",
		},
		{
			name:     commandName("print"),
			action:   actionPrint,
			document: "print current source",
		},
		{
			name:     commandName("w[rite]"),
			action:   actionWrite,
			complete: nil, // TODO implement
			arg:      "[<file>]",
			document: "write out current source",
		},
		{
			name:     commandName("clear"),
			action:   actionClear,
			document: "clear the codes",
		},
		{
			name:     commandName("d[oc]"),
			action:   actionDoc,
			complete: completeDoc,
			arg:      "<expr or pkg>",
			document: "show documentation",
		},
		{
			name:     commandName("h[elp]"),
			action:   actionHelp,
			document: "show this help",
		},
		{
			name:     commandName("q[uit]"),
			action:   actionQuit,
			document: "quit the session",
		},
	}
}

func actionImport(s *Session, arg string) error {
	if arg == "" {
		return fmt.Errorf("arg required")
	}

	if strings.Contains(arg, " ") {
		for _, v := range strings.Fields(arg) {
			if v == "" {
				continue
			}
			if err := actionImport(s, v); err != nil {
				return err
			}
		}

		return nil
	}

	path := strings.Trim(arg, `"`)

	// check if the package specified by path is importable
	_, err := s.Types.Importer.Import(path)
	if err != nil {
		return err
	}

	astutil.AddImport(s.Fset, s.File, path)

	return nil
}

var gorootSrc = filepath.Join(filepath.Clean(runtime.GOROOT()), "src")

func completeImport(s *Session, prefix string) []string {
	result := []string{}
	seen := map[string]bool{}

	p := strings.LastIndexFunc(prefix, unicode.IsSpace) + 1

	d, fn := path.Split(prefix[p:])
	for _, srcDir := range build.Default.SrcDirs() {
		dir := filepath.Join(srcDir, d)

		if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
			if err != nil && !os.IsNotExist(err) {
				errorf("Stat %s: %s", dir, err)
			}
			continue
		}

		entries, err := ioutil.ReadDir(dir)
		if err != nil {
			errorf("ReadDir %s: %s", dir, err)
			continue
		}
		for _, fi := range entries {
			if !fi.IsDir() {
				continue
			}

			name := fi.Name()
			if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") || name == "testdata" {
				continue
			}

			if strings.HasPrefix(name, fn) {
				r := path.Join(d, name)
				if srcDir != gorootSrc {
					// append "/" if this directory is not a repository
					// e.g. does not have VCS directory such as .git or .hg
					// TODO: do not append "/" to subdirectories of repos
					var isRepo bool
					for _, vcsDir := range []string{".git", ".hg", ".svn", ".bzr"} {
						_, err := os.Stat(filepath.Join(srcDir, filepath.FromSlash(r), vcsDir))
						if err == nil {
							isRepo = true
							break
						}
					}
					if !isRepo {
						r = r + "/"
					}
				}

				if !seen[r] {
					result = append(result, prefix[:p]+r)
					seen[r] = true
				}
			}
		}
	}

	return result
}

func completeDoc(s *Session, prefix string) []string {
	pos, cands, err := s.completeCode(prefix, len(prefix), false)
	if err != nil {
		errorf("completeCode: %s", err)
		return nil
	}

	result := make([]string, 0, len(cands))
	for _, c := range cands {
		result = append(result, prefix[0:pos]+c)
	}

	return result
}

func actionPrint(s *Session, _ string) error {
	source, err := s.source(true)
	if err != nil {
		return err
	}

	fmt.Println(source)

	return nil
}

func actionType(s *Session, in string) error {
	s.clearQuickFix()

	s.storeCode()
	defer s.restoreCode()

	expr, err := s.evalExpr(in)
	if err != nil {
		return err
	}

	s.TypeInfo = types.Info{
		Types:  make(map[ast.Expr]types.TypeAndValue),
		Uses:   make(map[*ast.Ident]types.Object),
		Defs:   make(map[*ast.Ident]types.Object),
		Scopes: make(map[ast.Node]*types.Scope),
	}
	_, err = s.Types.Check("_tmp", s.Fset, []*ast.File{s.File}, &s.TypeInfo)
	if err != nil {
		debugf("typecheck error (ignored): %s", err)
	}

	typ := s.TypeInfo.TypeOf(expr)
	if typ == nil {
		return fmt.Errorf("cannot get type: %v", expr)
	}
	if typ, ok := typ.(*types.Basic); ok && typ.Kind() == types.Invalid {
		return fmt.Errorf("cannot get type: %v", expr)
	}
	fmt.Fprintf(s.stdout, "%v\n", typ)
	return nil
}

func actionWrite(s *Session, filename string) error {
	source, err := s.source(false)
	if err != nil {
		return err
	}

	if filename == "" {
		filename = fmt.Sprintf("gore_session_%s.go", time.Now().Format("20060102_150405"))
	}

	err = ioutil.WriteFile(filename, []byte(source), 0644)
	if err != nil {
		return err
	}

	infof("Source wrote to %s", filename)

	return nil
}

func actionClear(s *Session, _ string) error {
	return s.init()
}

func actionDoc(s *Session, in string) error {
	s.clearQuickFix()

	s.storeCode()
	defer s.restoreCode()

	expr, err := s.evalExpr(in)
	if err != nil {
		return err
	}

	s.TypeInfo = types.Info{
		Types:  make(map[ast.Expr]types.TypeAndValue),
		Uses:   make(map[*ast.Ident]types.Object),
		Defs:   make(map[*ast.Ident]types.Object),
		Scopes: make(map[ast.Node]*types.Scope),
	}
	_, err = s.Types.Check("_tmp", s.Fset, []*ast.File{s.File}, &s.TypeInfo)
	if err != nil {
		debugf("typecheck error (ignored): %s", err)
	}

	// :doc patterns:
	// - "json" -> "encoding/json" (package name)
	// - "json.Encoder" -> "encoding/json", "Encoder" (package member)
	// - "json.NewEncoder(nil).Encode" -> "encoding/json", "Decode" (package type member)
	var docObj types.Object
	if sel, ok := expr.(*ast.SelectorExpr); ok {
		// package member, package type member
		docObj = s.TypeInfo.ObjectOf(sel.Sel)
	} else if t := s.TypeInfo.TypeOf(expr); t != nil && t != types.Typ[types.Invalid] {
		for {
			if pt, ok := t.(*types.Pointer); ok {
				t = pt.Elem()
			} else {
				break
			}
		}
		switch t := t.(type) {
		case *types.Named:
			docObj = t.Obj()
		case *types.Basic:
			// builtin types
			docObj = types.Universe.Lookup(t.Name())
		}
	} else if ident, ok := expr.(*ast.Ident); ok {
		// package name
		mainScope := s.TypeInfo.Scopes[s.mainFunc().Type]
		_, docObj = mainScope.LookupParent(ident.Name, ident.NamePos)
	}

	if docObj == nil {
		return fmt.Errorf("cannot determine the document location")
	}

	debugf("doc :: obj=%#v", docObj)

	var pkgPath, objName string
	if pkgName, ok := docObj.(*types.PkgName); ok {
		pkgPath = pkgName.Imported().Path()
	} else {
		if pkg := docObj.Pkg(); pkg != nil {
			pkgPath = pkg.Path()
		} else {
			pkgPath = "builtin"
		}
		objName = docObj.Name()
	}

	debugf("doc :: %q %q", pkgPath, objName)

	args := []string{"doc", pkgPath}
	if objName != "" {
		args = append(args, objName)
	}

	godoc := exec.Command("go", args...)
	godoc.Stderr = s.stderr

	// TODO just use PAGER?
	if pagerCmd := os.Getenv("GORE_PAGER"); pagerCmd != "" {
		r, err := godoc.StdoutPipe()
		if err != nil {
			return err
		}

		pager := exec.Command(pagerCmd)
		pager.Stdin = r
		pager.Stdout = s.stdout
		pager.Stderr = s.stderr

		err = pager.Start()
		if err != nil {
			return err
		}

		err = godoc.Run()
		if err != nil {
			return err
		}

		return pager.Wait()
	}
	godoc.Stdout = s.stdout
	return godoc.Run()
}

func actionHelp(s *Session, _ string) error {
	w := tabwriter.NewWriter(s.stdout, 0, 8, 4, ' ', 0)
	for _, command := range commands {
		cmd := fmt.Sprintf(":%s", command.name)
		if command.arg != "" {
			cmd = cmd + " " + command.arg
		}
		w.Write([]byte("    " + cmd + "\t" + command.document + "\n"))
	}
	w.Flush()

	return nil
}

func actionQuit(s *Session, _ string) error {
	return ErrQuit
}
<commit_msg>remove todo comment<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"text/tabwriter"
	"time"
	"unicode"

	"go/ast"
	"go/build"
	"go/types"

	"golang.org/x/tools/go/ast/astutil"
)

type command struct {
	name     commandName
	action   func(*Session, string) error
	complete func(*Session, string) []string
	arg      string
	document string
}

var commands []command

func init() {
	commands = []command{
		{
			name:     commandName("i[mport]"),
			action:   actionImport,
			complete: completeImport,
			arg:      "<package>",
			document: "import a package",
		},
		{
			name:     commandName("t[ype]"),
			action:   actionType,
			arg:      "<expr>",
			complete: completeDoc,
			document: "print the type of expression",
		},
		{
			name:     commandName("print"),
			action:   actionPrint,
			document: "print current source",
		},
		{
			name:     commandName("w[rite]"),
			action:   actionWrite,
			complete: nil, // TODO implement
			arg:      "[<file>]",
			document: "write out current source",
		},
		{
			name:     commandName("clear"),
			action:   actionClear,
			document: "clear the codes",
		},
		{
			name:     commandName("d[oc]"),
			action:   actionDoc,
			complete: completeDoc,
			arg:      "<expr or pkg>",
			document: "show documentation",
		},
		{
			name:     commandName("h[elp]"),
			action:   actionHelp,
			document: "show this help",
		},
		{
			name:     commandName("q[uit]"),
			action:   actionQuit,
			document: "quit the session",
		},
	}
}

func actionImport(s *Session, arg string) error {
	if arg == "" {
		return fmt.Errorf("arg required")
	}

	if strings.Contains(arg, " ") {
		for _, v := range strings.Fields(arg) {
			if v == "" {
				continue
			}
			if err := actionImport(s, v); err != nil {
				return err
			}
		}

		return nil
	}

	path := strings.Trim(arg, `"`)

	// check if the package specified by path is importable
	_, err := s.Types.Importer.Import(path)
	if err != nil {
		return err
	}

	astutil.AddImport(s.Fset, s.File, path)

	return nil
}

var gorootSrc = filepath.Join(filepath.Clean(runtime.GOROOT()), "src")

func completeImport(s *Session, prefix string) []string {
	result := []string{}
	seen := map[string]bool{}

	p := strings.LastIndexFunc(prefix, unicode.IsSpace) + 1

	d, fn := path.Split(prefix[p:])
	for _, srcDir := range build.Default.SrcDirs() {
		dir := filepath.Join(srcDir, d)

		if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
			if err != nil && !os.IsNotExist(err) {
				errorf("Stat %s: %s", dir, err)
			}
			continue
		}

		entries, err := ioutil.ReadDir(dir)
		if err != nil {
			errorf("ReadDir %s: %s", dir, err)
			continue
		}
		for _, fi := range entries {
			if !fi.IsDir() {
				continue
			}

			name := fi.Name()
			if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") || name == "testdata" {
				continue
			}

			if strings.HasPrefix(name, fn) {
				r := path.Join(d, name)
				if srcDir != gorootSrc {
					// append "/" if this directory is not a repository
					// e.g. does not have VCS directory such as .git or .hg
					// TODO: do not append "/" to subdirectories of repos
					var isRepo bool
					for _, vcsDir := range []string{".git", ".hg", ".svn", ".bzr"} {
						_, err := os.Stat(filepath.Join(srcDir, filepath.FromSlash(r), vcsDir))
						if err == nil {
							isRepo = true
							break
						}
					}
					if !isRepo {
						r = r + "/"
					}
				}

				if !seen[r] {
					result = append(result, prefix[:p]+r)
					seen[r] = true
				}
			}
		}
	}

	return result
}

func completeDoc(s *Session, prefix string) []string {
	pos, cands, err := s.completeCode(prefix, len(prefix), false)
	if err != nil {
		errorf("completeCode: %s", err)
		return nil
	}

	result := make([]string, 0, len(cands))
	for _, c := range cands {
		result = append(result, prefix[0:pos]+c)
	}

	return result
}

func actionPrint(s *Session, _ string) error {
	source, err := s.source(true)
	if err != nil {
		return err
	}

	fmt.Println(source)

	return nil
}

func actionType(s *Session, in string) error {
	s.clearQuickFix()

	s.storeCode()
	defer s.restoreCode()

	expr, err := s.evalExpr(in)
	if err != nil {
		return err
	}

	s.TypeInfo = types.Info{
		Types:  make(map[ast.Expr]types.TypeAndValue),
		Uses:   make(map[*ast.Ident]types.Object),
		Defs:   make(map[*ast.Ident]types.Object),
		Scopes: make(map[ast.Node]*types.Scope),
	}
	_, err = s.Types.Check("_tmp", s.Fset, []*ast.File{s.File}, &s.TypeInfo)
	if err != nil {
		debugf("typecheck error (ignored): %s", err)
	}

	typ := s.TypeInfo.TypeOf(expr)
	if typ == nil {
		return fmt.Errorf("cannot get type: %v", expr)
	}
	if typ, ok := typ.(*types.Basic); ok && typ.Kind() == types.Invalid {
		return fmt.Errorf("cannot get type: %v", expr)
	}
	fmt.Fprintf(s.stdout, "%v\n", typ)
	return nil
}

func actionWrite(s *Session, filename string) error {
	source, err := s.source(false)
	if err != nil {
		return err
	}

	if filename == "" {
		filename = fmt.Sprintf("gore_session_%s.go", time.Now().Format("20060102_150405"))
	}

	err = ioutil.WriteFile(filename, []byte(source), 0644)
	if err != nil {
		return err
	}

	infof("Source wrote to %s", filename)

	return nil
}

func actionClear(s *Session, _ string) error {
	return s.init()
}

func actionDoc(s *Session, in string) error {
	s.clearQuickFix()

	s.storeCode()
	defer s.restoreCode()

	expr, err := s.evalExpr(in)
	if err != nil {
		return err
	}

	s.TypeInfo = types.Info{
		Types:  make(map[ast.Expr]types.TypeAndValue),
		Uses:   make(map[*ast.Ident]types.Object),
		Defs:   make(map[*ast.Ident]types.Object),
		Scopes: make(map[ast.Node]*types.Scope),
	}
	_, err = s.Types.Check("_tmp", s.Fset, []*ast.File{s.File}, &s.TypeInfo)
	if err != nil {
		debugf("typecheck error (ignored): %s", err)
	}

	// :doc patterns:
	// - "json" -> "encoding/json" (package name)
	// - "json.Encoder" -> "encoding/json", "Encoder" (package member)
	// - "json.NewEncoder(nil).Encode" -> "encoding/json", "Decode" (package type member)
	var docObj types.Object
	if sel, ok := expr.(*ast.SelectorExpr); ok {
		// package member, package type member
		docObj = s.TypeInfo.ObjectOf(sel.Sel)
	} else if t := s.TypeInfo.TypeOf(expr); t != nil && t != types.Typ[types.Invalid] {
		for {
			if pt, ok := t.(*types.Pointer); ok {
				t = pt.Elem()
			} else {
				break
			}
		}
		switch t := t.(type) {
		case *types.Named:
			docObj = t.Obj()
		case *types.Basic:
			// builtin types
			docObj = types.Universe.Lookup(t.Name())
		}
	} else if ident, ok := expr.(*ast.Ident); ok {
		// package name
		mainScope := s.TypeInfo.Scopes[s.mainFunc().Type]
		_, docObj = mainScope.LookupParent(ident.Name, ident.NamePos)
	}

	if docObj == nil {
		return fmt.Errorf("cannot determine the document location")
	}

	debugf("doc :: obj=%#v", docObj)

	var pkgPath, objName string
	if pkgName, ok := docObj.(*types.PkgName); ok {
		pkgPath = pkgName.Imported().Path()
	} else {
		if pkg := docObj.Pkg(); pkg != nil {
			pkgPath = pkg.Path()
		} else {
			pkgPath = "builtin"
		}
		objName = docObj.Name()
	}

	debugf("doc :: %q %q", pkgPath, objName)

	args := []string{"doc", pkgPath}
	if objName != "" {
		args = append(args, objName)
	}

	godoc := exec.Command("go", args...)
	godoc.Stderr = s.stderr

	// TODO just use PAGER?
	if pagerCmd := os.Getenv("GORE_PAGER"); pagerCmd != "" {
		r, err := godoc.StdoutPipe()
		if err != nil {
			return err
		}

		pager := exec.Command(pagerCmd)
		pager.Stdin = r
		pager.Stdout = s.stdout
		pager.Stderr = s.stderr

		err = pager.Start()
		if err != nil {
			return err
		}

		err = godoc.Run()
		if err != nil {
			return err
		}

		return pager.Wait()
	}
	godoc.Stdout = s.stdout
	return godoc.Run()
}

func actionHelp(s *Session, _ string) error {
	w := tabwriter.NewWriter(s.stdout, 0, 8, 4, ' ', 0)
	for _, command := range commands {
		cmd := fmt.Sprintf(":%s", command.name)
		if command.arg != "" {
			cmd = cmd + " " + command.arg
		}
		w.Write([]byte("    " + cmd + "\t" + command.document + "\n"))
	}
	w.Flush()

	return nil
}

func actionQuit(s *Session, _ string) error {
	return ErrQuit
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/AlexanderThaller/logger"
	"github.com/jinzhu/now"
	"github.com/juju/errgo"
)

type Command struct {
	Action        string
	Args          []string
	DataPath      string
	EndTime       time.Time
	Project       string
	SCM           string
	SCMAutoCommit bool
	SCMAutoPush   bool
	StartTime     time.Time
	TimeStamp     time.Time
	Value         string
}

const (
	CommitMessageTimeStampFormat = RecordTimeStampFormat
	DateFormat                   = "2006-01-02"
)

const (
	ActionDone         = "done"
	ActionListDates    = "listdates"
	ActionList         = "list"
	ActionListNotes    = "listnotes"
	ActionListProjects = "listprojects"
	ActionListTodos    = "listtodos"
	ActionNote         = "note"
	ActionTodo         = "todo"
)

func NewCommand() *Command {
	return new(Command)
}

func (com *Command) Run() error {
	if com.DataPath == "" {
		return errgo.New("the datapath can not be empty")
	}

	switch com.Action {
	case ActionDone:
		return com.runDone()
	case ActionNote:
		return com.runNote()
	case ActionListDates:
		return com.runListDates()
	case ActionList:
		return com.runList()
	case ActionListNotes:
		return com.runListNotes()
	case ActionListProjects:
		return com.runListProjects()
	case ActionListTodos:
		return com.runListTodos()
	case ActionTodo:
		return com.runTodo()
	default:
		return errgo.New("Do not recognize the action: " + com.Action)
	}
}

func (com *Command) runDone() error {
	l := logger.New(Name, "Command", "run", "Done")

	l.Trace("Args length: ", len(com.Args))
	if com.Value == "" {
		return errgo.New("todo command needs a value")
	}
	l.Trace("Project: ", com.Project)
	if com.Project == "" {
		return errgo.New("todo command needs an project")
	}

	done := new(Todo)
	done.Project = com.Project
	done.TimeStamp = com.TimeStamp
	done.Value = com.Value
	done.Done = true
	l.Trace("Done: ", fmt.Sprintf("%+v", done))

	return com.Write(done)
}

func (com *Command) runNote() error {
	l := logger.New(Name, "Command", "run", "Note")

	l.Trace("Args length: ", len(com.Args))
	if com.Value == "" {
		return errgo.New("note command needs a value")
	}
	l.Trace("Project: ", com.Project)
	if com.Project == "" {
		return errgo.New("note command needs an project")
	}

	note := new(Note)
	note.Project = com.Project
	note.TimeStamp = com.TimeStamp
	note.Value = com.Value
	l.Trace("Note: ", fmt.Sprintf("%+v", note))

	return com.Write(note)
}

func (com *Command) runTodo() error {
	l := logger.New(Name, "Command", "run", "Todo")

	l.Trace("Args length: ", len(com.Args))
	if com.Value == "" {
		return errgo.New("todo command needs a value")
	}
	l.Trace("Project: ", com.Project)
	if com.Project == "" {
		return errgo.New("todo command needs an project")
	}

	todo := new(Todo)
	todo.Project = com.Project
	todo.TimeStamp = com.TimeStamp
	todo.Value = com.Value
	todo.Done = false
	l.Trace("Todo: ", fmt.Sprintf("%+v", todo))

	return com.Write(todo)
}

func (com *Command) runList() error {
	if com.Project == "" {
		return com.runListProjects()
	} else {
		return com.runListProjectNotes(com.Project)
	}
}

func (com *Command) runListNotes() error {
	if com.Project != "" {
		return com.runListProjectNotes(com.Project)
	}

	projects, err := com.getProjects()
	if err != nil {
		return err
	}

	for _, project := range projects {
		err := com.runListProjectNotes(project)
		if err != nil {
			return err
		}
	}

	return nil
}

func (com *Command) runListTodos() error {
	if com.Project != "" {
		return com.runListProjectTodos(com.Project)
	}

	projects, err := com.getProjects()
	if err != nil {
		return err
	}

	for _, project := range projects {
		err := com.runListProjectTodos(project)
		if err != nil {
			return err
		}
	}

	return nil
}

func (com *Command) runListProjects() error {
	projects, err := com.getProjects()
	if err != nil {
		return err
	}

	for _, project := range projects {
		fmt.Println(project)
	}

	return nil
}

func (com *Command) runListDates() error {
	l := logger.New(Name, "Command", "run", "ListDates")

	var dates []string
	var err error

	if com.Project == "" {
		dates, err = com.getDates()
	} else {
		dates, err = com.getProjectDates(com.Project)
	}

	if err != nil {
		return err
	}

	sort.Strings(dates)
	for _, date := range dates {
		timestamp, err := now.Parse(date)
		if err != nil {
			l.Warning("Can not parse timestamp: ", errgo.Details(err))
			continue
		}

		if timestamp.Before(com.StartTime) {
			continue
		}

		if timestamp.After(com.EndTime) {
			continue
		}

		fmt.Println(date)
	}

	return nil
}

func (com *Command) getDates() ([]string, error) {
	projects, err := com.getProjects()
	if err != nil {
		return nil, err
	}

	datemap := make(map[string]struct{})
	for _, project := range projects {
		dates, err := com.getProjectDates(project)
		if err != nil {
			return nil, err
		}

		for _, date := range dates {
			datemap[date] = struct{}{}
		}
	}

	var out []string
	for date := range datemap {
		out = append(out, date)
	}

	return out, nil
}

func (com *Command) getProjectDates(project string) ([]string, error) {
	if com.DataPath == "" {
		return nil, errgo.New("datapath can not be empty")
	}
	if project == "" {
		return nil, errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return nil, errgo.New("project does not exist")
	}

	var out []string

	records, err := com.getProjectNotes(project)
	if err != nil {
		return nil, err
	}

	datemap := make(map[string]struct{})

	for _, record := range records {
		date, err := time.Parse(RecordTimeStampFormat, record.GetTimeStamp())
		if err != nil {
			return nil, err
		}

		datemap[date.Format(DateFormat)] = struct{}{}
	}

	for date := range datemap {
		out = append(out, date)
	}

	return out, nil
}

func (com *Command) getProjects() ([]string, error) {
	dir, err := ioutil.ReadDir(com.DataPath)
	if err != nil {
		return nil, err
	}

	var out []string
	for _, d := range dir {
		file := d.Name()

		// Skip dotfiles
		if strings.HasPrefix(file, ".") {
			continue
		}

		ext := filepath.Ext(file)
		name := file[0 : len(file)-len(ext)]

		out = append(out, name)
	}

	sort.Strings(out)
	return out, nil
}

func (com *Command) runListProjectTodos(project string) error {
	if project == "" {
		return errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return errgo.New("no notes for this project")
	}

	todos, err := com.getProjectTodos(project)
	if err != nil {
		return err
	}
	todos = com.filterTodos(todos)

	if len(todos) == 0 {
		return nil
	}

	fmt.Println("#", project, "- Todos")

	sort.Sort(TodoByDate(todos))
	for _, todo := range todos {
		fmt.Println("  *", todo.GetValue())
	}
	fmt.Println("")

	return nil
}

func (com *Command) filterTodos(todos []Todo) []Todo {
	filter := make(map[string]Todo)

	sort.Sort(TodoByDate(todos))
	for _, todo := range todos {
		filter[todo.Value] = todo
	}

	var out []Todo
	for _, todo := range filter {
		if todo.Done {
			continue
		}

		out = append(out, todo)
	}

	return out
}

func (com *Command) runListProjectNotes(project string) error {
	if project == "" {
		return errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return errgo.New("no notes for this project")
	}

	notes, err := com.getProjectNotes(project)
	if err != nil {
		return err
	}

	if len(notes) == 0 {
		return nil
	}

	fmt.Println("#", project)
	sort.Sort(NotesByDate(notes))
	for _, note := range notes {
		fmt.Println("##", note.GetTimeStamp())
		fmt.Println(note.GetValue())
		fmt.Println("")
	}

	return nil
}

func (com *Command) checkProjectExists(project string) bool {
	projects, err := com.getProjects()
	if err != nil {
		return false
	}

	for _, d := range projects {
		if d == project {
			return true
		}
	}

	return false
}

func (com *Command) getProjectNotes(project string) ([]Note, error) {
	l := logger.New(Name, "Command", "get", "ProjectRecords")
	l.SetLevel(logger.Debug)

	if com.DataPath == "" {
		return nil, errgo.New("datapath can not be empty")
	}
	if project == "" {
		return nil, errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return nil, errgo.New("project does not exist")
	}

	filepath := filepath.Join(com.DataPath, project+".csv")
	file, err := os.OpenFile(filepath, os.O_RDONLY, 0640)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	reader.FieldsPerRecord = 3

	var out []Note
	for {
		csv, err := reader.Read()
		if err != nil {
			if err == io.EOF {
				break
			}

			continue
		}

		note, err := NoteFromCSV(csv)
		if err != nil {
			continue
		}
		note.SetProject(project)

		if note.TimeStamp.Before(com.StartTime) {
			continue
		}

		if note.TimeStamp.After(com.EndTime) {
			continue
		}

		out = append(out, note)
	}

	return out, err
}

func (com *Command) getProjectTodos(project string) ([]Todo, error) {
	l := logger.New(Name, "Command", "get", "ProjectRecords")
	l.SetLevel(logger.Debug)

	if com.DataPath == "" {
		return nil, errgo.New("datapath can not be empty")
	}
	if project == "" {
		return nil, errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return nil, errgo.New("project does not exist")
	}

	filepath := filepath.Join(com.DataPath, project+".csv")
	file, err := os.OpenFile(filepath, os.O_RDONLY, 0640)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	reader.FieldsPerRecord = 4

	var out []Todo
	for {
		csv, err := reader.Read()
		if err != nil {
			if err == io.EOF {
				break
			}

			continue
		}

		todo, err := TodoFromCSV(csv)
		if err != nil {
			continue
		}

		out = append(out, todo)
	}

	return out, err
}

func (com *Command) Write(record Record) error {
	if com.DataPath == "" {
		return errgo.New("datapath can not be empty")
	}

	if com.Project == "" {
		return errgo.New("project name can not be empty")
	}

	path := com.DataPath
	project := com.Project

	err := os.MkdirAll(path, 0750)
	if err != nil {
		return err
	}

	filepath := filepath.Join(path, project+".csv")
	file, err := os.OpenFile(filepath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0640)
	if err != nil {
		return err
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	err = writer.Write(record.CSV())
	if err != nil {
		return err
	}
	writer.Flush()

	err = com.Commit(record)
	if err != nil {
		return err
	}

	return nil
}

func (com *Command) Commit(record Record) error {
	if !com.SCMAutoCommit {
		return nil
	}

	if com.SCM == "" {
		return errgo.New("Can not use an empty scm for commiting")
	}

	filename := record.GetProject() + ".csv"
	err := scmAdd(com.SCM, com.DataPath, filename)
	if err != nil {
		return err
	}

	message := com.Project + " - "
	message += record.GetAction() + " - "
	message += com.TimeStamp.Format(CommitMessageTimeStampFormat)
	err = scmCommit(com.SCM, com.DataPath, message)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>added filtering of projects by timerange.<commit_after>package main

import (
	"encoding/csv"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/AlexanderThaller/logger"
	"github.com/jinzhu/now"
	"github.com/juju/errgo"
)

type Command struct {
	Action        string
	Args          []string
	DataPath      string
	EndTime       time.Time
	Project       string
	SCM           string
	SCMAutoCommit bool
	SCMAutoPush   bool
	StartTime     time.Time
	TimeStamp     time.Time
	Value         string
}

const (
	CommitMessageTimeStampFormat = RecordTimeStampFormat
	DateFormat                   = "2006-01-02"
)

const (
	ActionDone         = "done"
	ActionListDates    = "listdates"
	ActionList         = "list"
	ActionListNotes    = "listnotes"
	ActionListProjects = "listprojects"
	ActionListTodos    = "listtodos"
	ActionNote         = "note"
	ActionTodo         = "todo"
)

func NewCommand() *Command {
	return new(Command)
}

func (com *Command) Run() error {
	if com.DataPath == "" {
		return errgo.New("the datapath can not be empty")
	}

	switch com.Action {
	case ActionDone:
		return com.runDone()
	case ActionNote:
		return com.runNote()
	case ActionListDates:
		return com.runListDates()
	case ActionList:
		return com.runList()
	case ActionListNotes:
		return com.runListNotes()
	case ActionListProjects:
		return com.runListProjects()
	case ActionListTodos:
		return com.runListTodos()
	case ActionTodo:
		return com.runTodo()
	default:
		return errgo.New("Do not recognize the action: " + com.Action)
	}
}

func (com *Command) runDone() error {
	l := logger.New(Name, "Command", "run", "Done")

	l.Trace("Args length: ", len(com.Args))
	if com.Value == "" {
		return errgo.New("todo command needs a value")
	}
	l.Trace("Project: ", com.Project)
	if com.Project == "" {
		return errgo.New("todo command needs an project")
	}

	done := new(Todo)
	done.Project = com.Project
	done.TimeStamp = com.TimeStamp
	done.Value = com.Value
	done.Done = true
	l.Trace("Done: ", fmt.Sprintf("%+v", done))

	return com.Write(done)
}

func (com *Command) runNote() error {
	l := logger.New(Name, "Command", "run", "Note")

	l.Trace("Args length: ", len(com.Args))
	if com.Value == "" {
		return errgo.New("note command needs a value")
	}
	l.Trace("Project: ", com.Project)
	if com.Project == "" {
		return errgo.New("note command needs an project")
	}

	note := new(Note)
	note.Project = com.Project
	note.TimeStamp = com.TimeStamp
	note.Value = com.Value
	l.Trace("Note: ", fmt.Sprintf("%+v", note))

	return com.Write(note)
}

func (com *Command) runTodo() error {
	l := logger.New(Name, "Command", "run", "Todo")

	l.Trace("Args length: ", len(com.Args))
	if com.Value == "" {
		return errgo.New("todo command needs a value")
	}
	l.Trace("Project: ", com.Project)
	if com.Project == "" {
		return errgo.New("todo command needs an project")
	}

	todo := new(Todo)
	todo.Project = com.Project
	todo.TimeStamp = com.TimeStamp
	todo.Value = com.Value
	todo.Done = false
	l.Trace("Todo: ", fmt.Sprintf("%+v", todo))

	return com.Write(todo)
}

func (com *Command) runList() error {
	if com.Project == "" {
		return com.runListProjects()
	} else {
		return com.runListProjectNotes(com.Project)
	}
}

func (com *Command) runListNotes() error {
	if com.Project != "" {
		return com.runListProjectNotes(com.Project)
	}

	projects, err := com.getProjects()
	if err != nil {
		return err
	}

	for _, project := range projects {
		err := com.runListProjectNotes(project)
		if err != nil {
			return err
		}
	}

	return nil
}

func (com *Command) runListTodos() error {
	if com.Project != "" {
		return com.runListProjectTodos(com.Project)
	}

	projects, err := com.getProjects()
	if err != nil {
		return err
	}

	for _, project := range projects {
		err := com.runListProjectTodos(project)
		if err != nil {
			return err
		}
	}

	return nil
}

func (com *Command) runListProjects() error {
	projects, err := com.getProjects()
	if err != nil {
		return err
	}

	for _, project := range projects {
		notes, err := com.getProjectNotes(project)
		if err != nil {
			return err
		}

		if len(notes) == 0 {
			continue
		}

		fmt.Println(project)
	}

	return nil
}

func (com *Command) runListDates() error {
	l := logger.New(Name, "Command", "run", "ListDates")

	var dates []string
	var err error

	if com.Project == "" {
		dates, err = com.getDates()
	} else {
		dates, err = com.getProjectDates(com.Project)
	}

	if err != nil {
		return err
	}

	sort.Strings(dates)
	for _, date := range dates {
		timestamp, err := now.Parse(date)
		if err != nil {
			l.Warning("Can not parse timestamp: ", errgo.Details(err))
			continue
		}

		if timestamp.Before(com.StartTime) {
			continue
		}

		if timestamp.After(com.EndTime) {
			continue
		}

		fmt.Println(date)
	}

	return nil
}

func (com *Command) getDates() ([]string, error) {
	projects, err := com.getProjects()
	if err != nil {
		return nil, err
	}

	datemap := make(map[string]struct{})
	for _, project := range projects {
		dates, err := com.getProjectDates(project)
		if err != nil {
			return nil, err
		}

		for _, date := range dates {
			datemap[date] = struct{}{}
		}
	}

	var out []string
	for date := range datemap {
		out = append(out, date)
	}

	return out, nil
}

func (com *Command) getProjectDates(project string) ([]string, error) {
	if com.DataPath == "" {
		return nil, errgo.New("datapath can not be empty")
	}
	if project == "" {
		return nil, errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return nil, errgo.New("project does not exist")
	}

	var out []string

	records, err := com.getProjectNotes(project)
	if err != nil {
		return nil, err
	}

	datemap := make(map[string]struct{})

	for _, record := range records {
		date, err := time.Parse(RecordTimeStampFormat, record.GetTimeStamp())
		if err != nil {
			return nil, err
		}

		datemap[date.Format(DateFormat)] = struct{}{}
	}

	for date := range datemap {
		out = append(out, date)
	}

	return out, nil
}

func (com *Command) getProjects() ([]string, error) {
	dir, err := ioutil.ReadDir(com.DataPath)
	if err != nil {
		return nil, err
	}

	var out []string
	for _, d := range dir {
		file := d.Name()

		// Skip dotfiles
		if strings.HasPrefix(file, ".") {
			continue
		}

		ext := filepath.Ext(file)
		name := file[0 : len(file)-len(ext)]

		out = append(out, name)
	}

	sort.Strings(out)
	return out, nil
}

func (com *Command) runListProjectTodos(project string) error {
	if project == "" {
		return errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return errgo.New("no notes for this project")
	}

	todos, err := com.getProjectTodos(project)
	if err != nil {
		return err
	}
	todos = com.filterTodos(todos)

	if len(todos) == 0 {
		return nil
	}

	fmt.Println("#", project, "- Todos")

	sort.Sort(TodoByDate(todos))
	for _, todo := range todos {
		fmt.Println("  *", todo.GetValue())
	}
	fmt.Println("")

	return nil
}

func (com *Command) filterTodos(todos []Todo) []Todo {
	filter := make(map[string]Todo)

	sort.Sort(TodoByDate(todos))
	for _, todo := range todos {
		filter[todo.Value] = todo
	}

	var out []Todo
	for _, todo := range filter {
		if todo.Done {
			continue
		}

		out = append(out, todo)
	}

	return out
}

func (com *Command) runListProjectNotes(project string) error {
	if project == "" {
		return errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return errgo.New("no notes for this project")
	}

	notes, err := com.getProjectNotes(project)
	if err != nil {
		return err
	}

	if len(notes) == 0 {
		return nil
	}

	fmt.Println("#", project)
	sort.Sort(NotesByDate(notes))
	for _, note := range notes {
		fmt.Println("##", note.GetTimeStamp())
		fmt.Println(note.GetValue())
		fmt.Println("")
	}

	return nil
}

func (com *Command) checkProjectExists(project string) bool {
	projects, err := com.getProjects()
	if err != nil {
		return false
	}

	for _, d := range projects {
		if d == project {
			return true
		}
	}

	return false
}

func (com *Command) getProjectNotes(project string) ([]Note, error) {
	l := logger.New(Name, "Command", "get", "ProjectRecords")
	l.SetLevel(logger.Debug)

	if com.DataPath == "" {
		return nil, errgo.New("datapath can not be empty")
	}
	if project == "" {
		return nil, errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return nil, errgo.New("project does not exist")
	}

	filepath := filepath.Join(com.DataPath, project+".csv")
	file, err := os.OpenFile(filepath, os.O_RDONLY, 0640)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	reader.FieldsPerRecord = 3

	var out []Note
	for {
		csv, err := reader.Read()
		if err != nil {
			if err == io.EOF {
				break
			}

			continue
		}

		note, err := NoteFromCSV(csv)
		if err != nil {
			continue
		}
		note.SetProject(project)

		if note.TimeStamp.Before(com.StartTime) {
			continue
		}

		if note.TimeStamp.After(com.EndTime) {
			continue
		}

		out = append(out, note)
	}

	return out, err
}

func (com *Command) getProjectTodos(project string) ([]Todo, error) {
	l := logger.New(Name, "Command", "get", "ProjectRecords")
	l.SetLevel(logger.Debug)

	if com.DataPath == "" {
		return nil, errgo.New("datapath can not be empty")
	}
	if project == "" {
		return nil, errgo.New("project name can not be empty")
	}
	if !com.checkProjectExists(project) {
		return nil, errgo.New("project does not exist")
	}

	filepath := filepath.Join(com.DataPath, project+".csv")
	file, err := os.OpenFile(filepath, os.O_RDONLY, 0640)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	reader.FieldsPerRecord = 4

	var out []Todo
	for {
		csv, err := reader.Read()
		if err != nil {
			if err == io.EOF {
				break
			}

			continue
		}

		todo, err := TodoFromCSV(csv)
		if err != nil {
			continue
		}

		out = append(out, todo)
	}

	return out, err
}

func (com *Command) Write(record Record) error {
	if com.DataPath == "" {
		return errgo.New("datapath can not be empty")
	}

	if com.Project == "" {
		return errgo.New("project name can not be empty")
	}

	path := com.DataPath
	project := com.Project

	err := os.MkdirAll(path, 0750)
	if err != nil {
		return err
	}

	filepath := filepath.Join(path, project+".csv")
	file, err := os.OpenFile(filepath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0640)
	if err != nil {
		return err
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	err = writer.Write(record.CSV())
	if err != nil {
		return err
	}
	writer.Flush()

	err = com.Commit(record)
	if err != nil {
		return err
	}

	return nil
}

func (com *Command) Commit(record Record) error {
	if !com.SCMAutoCommit {
		return nil
	}

	if com.SCM == "" {
		return errgo.New("Can not use an empty scm for commiting")
	}

	filename := record.GetProject() + ".csv"
	err := scmAdd(com.SCM, com.DataPath, filename)
	if err != nil {
		return err
	}

	message := com.Project + " - "
	message += record.GetAction() + " - "
	message += com.TimeStamp.Format(CommitMessageTimeStampFormat)
	err = scmCommit(com.SCM, com.DataPath, message)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	"github.com/CiscoCloud/consul-cli/command"
	"github.com/mitchellh/cli"
)

var Commands map[string]cli.CommandFactory

func init() {
	metaPtr := new(command.Meta)
	meta := *metaPtr
	meta.UI = &cli.BasicUi{
		Writer:		os.Stdout,
		ErrorWriter:	os.Stderr,
	}

	Commands = map[string]cli.CommandFactory{
		"acl-clone": func() (cli.Command, error) {
			return &command.ACLCloneCommand{
				Meta:	meta,
			}, nil
		},
		"acl-create": func() (cli.Command, error) {
			return &command.ACLCreateCommand{
				Meta:	meta,
			}, nil
		},
		"acl-destroy": func() (cli.Command, error) {
			return &command.ACLDestroyCommand{
				Meta:	meta,
			}, nil
		},
		"acl-info": func() (cli.Command, error) {
			return &command.ACLInfoCommand{
				Meta:	meta,
			}, nil
		},
		"acl-list": func() (cli.Command, error) {
			return &command.ACLListCommand{
				Meta:	meta,
			}, nil
		},
		"acl-update": func() (cli.Command, error) {
			return &command.ACLUpdateCommand{
				Meta:	meta,
			}, nil
		},
		"kv-delete": func() (cli.Command, error) {
			return &command.KVDeleteCommand{
				Meta:	meta,
			}, nil
		},

		"kv-read": func() (cli.Command, error) {
			return &command.KVReadCommand{
				Meta:	meta,
			}, nil
		},

		"kv-write": func() (cli.Command, error) {
			return &command.KVWriteCommand{
				Meta:	meta,
			}, nil
		},

		"kv-lock": func() (cli.Command, error) {
			return &command.KVLockCommand{
				Meta:	meta,
			}, nil
		},

		"kv-unlock": func() (cli.Command, error) {
			return &command.KVUnlockCommand{
				Meta:	meta,
			}, nil
		},
		"session-list": func() (cli.Command, error) {
			return &command.SessionListCommand{
				Meta:	meta,
			}, nil
		},
	}
}
<commit_msg>Add /v1/session endpoint<commit_after>package main

import (
	"os"

	"github.com/CiscoCloud/consul-cli/command"
	"github.com/mitchellh/cli"
)

var Commands map[string]cli.CommandFactory

func init() {
	metaPtr := new(command.Meta)
	meta := *metaPtr
	meta.UI = &cli.BasicUi{
		Writer:		os.Stdout,
		ErrorWriter:	os.Stderr,
	}

	Commands = map[string]cli.CommandFactory{
		"acl-clone": func() (cli.Command, error) {
			return &command.ACLCloneCommand{
				Meta:	meta,
			}, nil
		},
		"acl-create": func() (cli.Command, error) {
			return &command.ACLCreateCommand{
				Meta:	meta,
			}, nil
		},
		"acl-destroy": func() (cli.Command, error) {
			return &command.ACLDestroyCommand{
				Meta:	meta,
			}, nil
		},
		"acl-info": func() (cli.Command, error) {
			return &command.ACLInfoCommand{
				Meta:	meta,
			}, nil
		},
		"acl-list": func() (cli.Command, error) {
			return &command.ACLListCommand{
				Meta:	meta,
			}, nil
		},
		"acl-update": func() (cli.Command, error) {
			return &command.ACLUpdateCommand{
				Meta:	meta,
			}, nil
		},
		"kv-delete": func() (cli.Command, error) {
			return &command.KVDeleteCommand{
				Meta:	meta,
			}, nil
		},

		"kv-read": func() (cli.Command, error) {
			return &command.KVReadCommand{
				Meta:	meta,
			}, nil
		},

		"kv-write": func() (cli.Command, error) {
			return &command.KVWriteCommand{
				Meta:	meta,
			}, nil
		},

		"kv-lock": func() (cli.Command, error) {
			return &command.KVLockCommand{
				Meta:	meta,
			}, nil
		},

		"kv-unlock": func() (cli.Command, error) {
			return &command.KVUnlockCommand{
				Meta:	meta,
			}, nil
		},
		"session-create": func() (cli.Command, error) {
			return &command.SessionCreateCommand{
				Meta:	meta,
			}, nil
		},
		"session-destroy": func() (cli.Command, error) {
			return &command.SessionDestroyCommand{
				Meta:	meta,
			}, nil
		},
		"session-info": func() (cli.Command, error) {
			return &command.SessionInfoCommand{
				Meta:	meta,
			}, nil
		},
		"session-list": func() (cli.Command, error) {
			return &command.SessionListCommand{
				Meta:	meta,
			}, nil
		},
		"session-node": func() (cli.Command, error) {
			return &command.SessionNodeCommand{
				Meta:	meta,
			}, nil
		},
		"session-renew": func() (cli.Command, error) {
			return &command.SessionRenewCommand{
				Meta:	meta,
			}, nil
		},
	}
}
<|endoftext|>


<commit_before><commit_msg>Remove crappy ReaderFromStatusResponseWriter<commit_after><|endoftext|>


<commit_before>/*
Terse is an html templating language inspired by slim, but
written for the features of multitemplate. The minimal logic
marks were inspired by mustache, but I'm not going to pretend
they're not template logic. Note that the logic syntax is the
same as the template logic syntax in the Go standard library.
Terse is not an acronym, it is a description of the design of
the language.

Features

- Significant Whitespace

- Auto-closing tags and functions

- Integrates with other multitemplate languages

- Uses same function calling syntax as stdlib templates

Using Terse

Active terse as a multitemplate languages by importing the
terse library like

    import _ "github.com/acsellers/multitemplate/terse"

At that point, Parse will now take a "terse" parser for templates
and ParseFiles/ParseGlob will detect files with a .terse extension
as terse formatted files.

An example terse file:

    #content
      .title
        h1= .Title
        = link_to "Home" (url_for "home")

The resulting HTML would look like:

Welcome

      Home

Security and XSS Protection

Terse uses the built-in protection of html/template via multitemplate.

Simple HTML

Most of HTML documents is static text. Terse makes writing this static
text as simple as possible. There are three ways to write HTML tags, the
automatic way, the manual way, and the verbatim way. The automatic way
will detect tags by name, and will do a case sensitive search on the
elements listed in the ValidElements map. The manual way will detect
elements by a percentage sign before the element name, which is the same
behavior as Haml and Bham. Finally, you can just add verbatim HTML code,
and it wil be passed through to the final output. This is why the character
'<' has no special meaning in terse.

  // Source
  article

My Title

  // Output

My Title

Collapsing Tags

If you have lines that are simply declaring elements that are nesting,
you can collapse the lines using the '>' character. Note that you cannot
add attributes or content to the tags. At the moment, you can't use the
id/class shorthand here.

  // Source
  table > tr > td > %custom_element
    Gopher Freeman

  // Output

<custom_element>
    Gopher Freeman
  </custom_element>

Class and Id Shorthand

Id's and Class attributes may be specified in shorthand. If you do
not specify an html element, terse will assume that you want to use
the div element. Multiple class attributes will be combined into the
class attribute, while multiple id attributes will take the final id
attribute. Classes are similar to css declarations, where '.'s precede
class attributes and '#'s precede id attributes. You can still provide
id and class attributes using the standard HTML attributes.

  // Source
  #sidebar
    ul#sidebar_list.vertical_list
      li.sidebar_element.active Thing

  // Output

Thing

HTML Attributes

There are two ways to specify attributes, one which allows for multiple
lines with attributes and another that requires all attributes to be
specied on the same line. By specifying the attribute name, then an
equals sign, then either a string, a $variable, a RenderArg which is
a . followed by words. Finally, if you just specify a helper function.
Note that if the helper function needs arguments, you should wrap the
function and arguments in parentheses. Boolean HTML attributes, that is
attributes that have no value, either require parentheses or can have
the value 'true' or 'false'.

  // Single Line
  div id="special_thing" class=$DivIds special thing content
  // Output

    special thing content

  // Multi Line
  input(
    name="is_active"
    type="checkbox"
    value="yes"
    checked=.User.Active
  )
  // Output
  <input type="is_active" type="checkbox" value="yes" checkbox="{{.User.Active}}" />

  // All the ways to specify attributes
  input(
    name=$inputName
    type="checkbox"
    value=.User.Name
    checked=(if_print "checked" .User.ShowName)
    class=.Check.User.Class
  )

  // Boolean Attribute Option 1
  .content ng-view=true
  // Boolean Attribute Option 2
  .content(ng-view)
  // Output

Doctype

Doctype lines are prefixed with two exclamation points (!!). There is
the default doctype, which is a line with just !!, terse will assume
you want to use the HTML5 Doctype (<!DOCTYPE html>). You can modify
the terse.Doctypes variable to change to another default by changing
the empty string key. Terse includes 8 different Doctypes by default,
which are Transitional (XHTML 1.0 Transitional), Strict (XHTML 1.0
Strict), Frameset (XHTML 1.0 Frameset), 5 (HTML5), 1.1 (XHTML 1.1),
Basic (XHTML 1.1 Basic), Mobile (XHTML Mobile 1.2), and RDFa
(XHTML+RDFa 1.0). These are the same as haml's Doctypes, except
that we don't worry about a Format option.

  // Source
  !!
  // Output
  <!DOCTYPE html>

Comments

By prefixing lines with a double slash (//), that line and any nested
lines will be marked as a comment. Comment lines do not show up in the
rendered template in any way.

Executing Code

There are preferred methods for if, range, with, and block functions,
but for template functions, there is a generic way to use code in
your templates. By adding an = at the start of the line, you can
mark that line as executable, for multiple lines there is a continuation
marker (/=) which will collapse multiple lines into a single line of code,
so long as the continuation lines are nested in the first code line. Note
that the syntax is the same as text/template in the standard library,
and the behavior about inserting code results into the output is same as
the text/template code as well.

  // Source
  = print .User
  // Output
  {{ print .User }}

  // Source
  = form_tag (url_for "user.new") (attrs
    /= "enctype" "multipart/form-data"
    /= "method" "PUT")
  // Output
  <form action="{{ url_for "user.new" }}" enctype="multipart/form-data" method="PUT">

Auto Closing Functions

When lines are nested, terse will look a function another function
that has the same name as the function that has the nested name, but
with the prefix 'end_'.

  // Source
  = fieldset_tag "Visibility"
    = checkbox_tag "admins"
    = checkbox_tag "users"
    = checkbox_tag "visitors"

  // Output
  {{ fieldset_tag "Visibility" }} {{ checkbox_tag "admins" }}
    {{ checkbox_tag "users" }}
    {{ checkbox_tag "visitors" }}
  {{ end_fieldset_tag }}

If and Else Statements

If lines begin with a question mark (?), then a statement to be evaluated. The
method of evaluation is the same as text/template, where if the statement
evaluates to false, 0, nil, or anything with a length of 0, the statement
is not executed. Else is an exclamation mark followed by a question mark,
or "not the question".

  // Source
  ?.User
    Welcome
    = .User.Name
  !?
    Please Login

  // Output
  {{ if .User }}
    Welcome
  {{ else }}
    Please Login
  {{ end }}

Range Statements

Range statements begin with an ampersand (&). Else statements for ranges
are an exclamation mark followed by an ampersand (!&). Variables for the
range statement are specified using colons after the statement you will
be ranging over.

  // Source
  &.Users:$user:$index
    {{ $index }}. {{ $user.Name }}
  !&
    No matching users.

  // Output
  {{ range $user, $index := .Users }}
    {{ $index }}. {{ $user.Name }}
  {{ else }}
    No matching users.
  {{ end }}

Block Statements

Blocks are central to multitemplate and terse features
them in a first-class manner. The simple block call is [name] where
name is the name you are using for that block. If you a special
form of the block statement, just replace the opening '['. For an
exec block you use a $, while define block uses a ^. The mnemonic
for those is that exec is the end of a block, so it is a regex endline
symbol ($), while define-block is more a beginning statement, and the
regex character for the beginning of line is ^. Blocks are automatically
closed in terse.

  // Source
  [content]
    h1
      Welcome
      = .User.Name

  // Output
  {{ block "content" }}

Welcome {{.User.Name }}

  {{ end_block }}

  // Source
  ^content]
    I'm defining this block and not outputting here.
  // Output
  {{ define_block "content" }}
    I'm defining this block and not outputting here.
  {{ end_block }}

  // Source
  $content]
    We will definitely put some block content here.
  // Output
  {{ exec_block "content" }}
    We will definitely put some block content here.
  {{ end_block }}

Yield Statements

Yield statements (for blocks or templates) are written as @name,
where name is the name you are using for that block or template.

  // Source
  #content
    @content

  // Output

    {{ yield "content" }}

Extend Statements

Extend statements (for inheriting from other templates) are written
using @@ followed by the template name (without quotation marks).

  // Source
  @@layouts/app.html

  // Output
  {{ extend "layouts/app.html" }}

With Statements

A with statement takes a pipeline and either sets the dot or a variable
to the value. With statments will only execute if the value is not a
falsey value (0, false, nil, empty string, array, slice or map). With
statements can have else statements, which are signified by a !>.

  // Source
  >.Current.User
    Welcome
    = .Name
    ?time_gt .LastLogin "3w"
      It's been a while
  !>
    Please login

  // Output
  {{ with .Current.User }}
    Welcome
    {{ .Name }}
    {{ if time_gt .LastLogin "3w" }}
      It's been a while
    {{ end }}
  {{ end }}

  // Source
  >.Current.Location:$loc
    = $loc.Name

  // Output
  {{ with $loc := .Current.Location }}
    {{ $loc.Name }}
  {{ end }}
Interpolation

In addition to the function lines, you can also embed functions into
regular lines, using the Delimeters set on terse (same as the standard
library delimeters by default).

  // Source
  Welcome {{ .User.Name }}.

  // Output (same)
  Welcome {{ .User.Name}}.

Filters

Filters are started with a :, then the name of a registered filter.
Filters can have interpolated code within them, if the filter may
have interpolated code, it needs to return true, else if it returns
false, the filtered text will not be checked for code. Note that the
processed text will be inserted into the template and any interpolated
code will be processed by html/template and escaped in a manner
consistent with the surrounding text.

  // Source
  :js
    $(document).ready(function{ setup_ajax(); });

  // Output

Notes

The format of the documentation and some examples were inspired by
Haml's REFERENCE file.

*/
package terse
<commit_msg>Add more documentation about filters<commit_after>/*
Terse is an html templating language inspired by slim, but
written for the features of multitemplate. The minimal logic
marks were inspired by mustache, but I'm not going to pretend
they're not template logic. Note that the logic syntax is the
same as the template logic syntax in the Go standard library.
Terse is not an acronym, it is a description of the design of
the language.

Features

- Significant Whitespace

- Auto-closing tags and functions

- Integrates with other multitemplate languages

- Uses same function calling syntax as stdlib templates

Using Terse

Active terse as a multitemplate languages by importing the
terse library like

    import _ "github.com/acsellers/multitemplate/terse"

At that point, Parse will now take a "terse" parser for templates
and ParseFiles/ParseGlob will detect files with a .terse extension
as terse formatted files.

An example terse file:

    #content
      .title
        h1= .Title
        = link_to "Home" (url_for "home")

The resulting HTML would look like:

Welcome

      Home

Security and XSS Protection

Terse uses the built-in protection of html/template via multitemplate.

Simple HTML

Most of HTML documents is static text. Terse makes writing this static
text as simple as possible. There are three ways to write HTML tags, the
automatic way, the manual way, and the verbatim way. The automatic way
will detect tags by name, and will do a case sensitive search on the
elements listed in the ValidElements map. The manual way will detect
elements by a percentage sign before the element name, which is the same
behavior as Haml and Bham. Finally, you can just add verbatim HTML code,
and it wil be passed through to the final output. This is why the character
'<' has no special meaning in terse.

  // Source
  article

My Title

  // Output

My Title

Collapsing Tags

If you have lines that are simply declaring elements that are nesting,
you can collapse the lines using the '>' character. Note that you cannot
add attributes or content to the tags. At the moment, you can't use the
id/class shorthand here.

  // Source
  table > tr > td > %custom_element
    Gopher Freeman

  // Output

<custom_element>
    Gopher Freeman
  </custom_element>

Class and Id Shorthand

Id's and Class attributes may be specified in shorthand. If you do
not specify an html element, terse will assume that you want to use
the div element. Multiple class attributes will be combined into the
class attribute, while multiple id attributes will take the final id
attribute. Classes are similar to css declarations, where '.'s precede
class attributes and '#'s precede id attributes. You can still provide
id and class attributes using the standard HTML attributes.

  // Source
  #sidebar
    ul#sidebar_list.vertical_list
      li.sidebar_element.active Thing

  // Output

Thing

HTML Attributes

There are two ways to specify attributes, one which allows for multiple
lines with attributes and another that requires all attributes to be
specied on the same line. By specifying the attribute name, then an
equals sign, then either a string, a $variable, a RenderArg which is
a . followed by words. Finally, if you just specify a helper function.
Note that if the helper function needs arguments, you should wrap the
function and arguments in parentheses. Boolean HTML attributes, that is
attributes that have no value, either require parentheses or can have
the value 'true' or 'false'.

  // Single Line
  div id="special_thing" class=$DivIds special thing content
  // Output

    special thing content

  // Multi Line
  input(
    name="is_active"
    type="checkbox"
    value="yes"
    checked=.User.Active
  )
  // Output
  <input type="is_active" type="checkbox" value="yes" checkbox="{{.User.Active}}" />

  // All the ways to specify attributes
  input(
    name=$inputName
    type="checkbox"
    value=.User.Name
    checked=(if_print "checked" .User.ShowName)
    class=.Check.User.Class
  )

  // Boolean Attribute Option 1
  .content ng-view=true
  // Boolean Attribute Option 2
  .content(ng-view)
  // Output

Doctype

Doctype lines are prefixed with two exclamation points (!!). There is
the default doctype, which is a line with just !!, terse will assume
you want to use the HTML5 Doctype (<!DOCTYPE html>). You can modify
the terse.Doctypes variable to change to another default by changing
the empty string key. Terse includes 8 different Doctypes by default,
which are Transitional (XHTML 1.0 Transitional), Strict (XHTML 1.0
Strict), Frameset (XHTML 1.0 Frameset), 5 (HTML5), 1.1 (XHTML 1.1),
Basic (XHTML 1.1 Basic), Mobile (XHTML Mobile 1.2), and RDFa
(XHTML+RDFa 1.0). These are the same as haml's Doctypes, except
that we don't worry about a Format option.

  // Source
  !!
  // Output
  <!DOCTYPE html>

Comments

By prefixing lines with a double slash (//), that line and any nested
lines will be marked as a comment. Comment lines do not show up in the
rendered template in any way.

Executing Code

There are preferred methods for if, range, with, and block functions,
but for template functions, there is a generic way to use code in
your templates. By adding an = at the start of the line, you can
mark that line as executable, for multiple lines there is a continuation
marker (/=) which will collapse multiple lines into a single line of code,
so long as the continuation lines are nested in the first code line. Note
that the syntax is the same as text/template in the standard library,
and the behavior about inserting code results into the output is same as
the text/template code as well.

  // Source
  = print .User
  // Output
  {{ print .User }}

  // Source
  = form_tag (url_for "user.new") (attrs
    /= "enctype" "multipart/form-data"
    /= "method" "PUT")
  // Output
  <form action="{{ url_for "user.new" }}" enctype="multipart/form-data" method="PUT">

Auto Closing Functions

When lines are nested, terse will look a function another function
that has the same name as the function that has the nested name, but
with the prefix 'end_'.

  // Source
  = fieldset_tag "Visibility"
    = checkbox_tag "admins"
    = checkbox_tag "users"
    = checkbox_tag "visitors"

  // Output
  {{ fieldset_tag "Visibility" }} {{ checkbox_tag "admins" }}
    {{ checkbox_tag "users" }}
    {{ checkbox_tag "visitors" }}
  {{ end_fieldset_tag }}

If and Else Statements

If lines begin with a question mark (?), then a statement to be evaluated. The
method of evaluation is the same as text/template, where if the statement
evaluates to false, 0, nil, or anything with a length of 0, the statement
is not executed. Else is an exclamation mark followed by a question mark,
or "not the question".

  // Source
  ?.User
    Welcome
    = .User.Name
  !?
    Please Login

  // Output
  {{ if .User }}
    Welcome
  {{ else }}
    Please Login
  {{ end }}

Range Statements

Range statements begin with an ampersand (&). Else statements for ranges
are an exclamation mark followed by an ampersand (!&). Variables for the
range statement are specified using colons after the statement you will
be ranging over.

  // Source
  &.Users:$user:$index
    {{ $index }}. {{ $user.Name }}
  !&
    No matching users.

  // Output
  {{ range $user, $index := .Users }}
    {{ $index }}. {{ $user.Name }}
  {{ else }}
    No matching users.
  {{ end }}

Block Statements

Blocks are central to multitemplate and terse features
them in a first-class manner. The simple block call is [name] where
name is the name you are using for that block. If you a special
form of the block statement, just replace the opening '['. For an
exec block you use a $, while define block uses a ^. The mnemonic
for those is that exec is the end of a block, so it is a regex endline
symbol ($), while define-block is more a beginning statement, and the
regex character for the beginning of line is ^. Blocks are automatically
closed in terse.

  // Source
  [content]
    h1
      Welcome
      = .User.Name

  // Output
  {{ block "content" }}

Welcome {{.User.Name }}

  {{ end_block }}

  // Source
  ^content]
    I'm defining this block and not outputting here.
  // Output
  {{ define_block "content" }}
    I'm defining this block and not outputting here.
  {{ end_block }}

  // Source
  $content]
    We will definitely put some block content here.
  // Output
  {{ exec_block "content" }}
    We will definitely put some block content here.
  {{ end_block }}

Yield Statements

Yield statements (for blocks or templates) are written as @name,
where name is the name you are using for that block or template.

  // Source
  #content
    @content

  // Output

    {{ yield "content" }}

Extend Statements

Extend statements (for inheriting from other templates) are written
using @@ followed by the template name (without quotation marks).

  // Source
  @@layouts/app.html

  // Output
  {{ extend "layouts/app.html" }}

With Statements

A with statement takes a pipeline and either sets the dot or a variable
to the value. With statments will only execute if the value is not a
falsey value (0, false, nil, empty string, array, slice or map). With
statements can have else statements, which are signified by a !>.

  // Source
  >.Current.User
    Welcome
    = .Name
    ?time_gt .LastLogin "3w"
      It's been a while
  !>
    Please login

  // Output
  {{ with .Current.User }}
    Welcome
    {{ .Name }}
    {{ if time_gt .LastLogin "3w" }}
      It's been a while
    {{ end }}
  {{ end }}

  // Source
  >.Current.Location:$loc
    = $loc.Name

  // Output
  {{ with $loc := .Current.Location }}
    {{ $loc.Name }}
  {{ end }}
Interpolation

In addition to the function lines, you can also embed functions into
regular lines, using the Delimeters set on terse (same as the standard
library delimeters by default).

  // Source
  Welcome {{ .User.Name }}.

  // Output (same)
  Welcome {{ .User.Name}}.

Filters

Filters are started with a :, then the name of a registered filter.
Filters can have interpolated code within them, if the filter may
have interpolated code, it needs to return true, else if it returns
false, the filtered text will not be checked for code. Note that the
processed text will be inserted into the template and any interpolated
code will be processed by html/template and escaped in a manner
consistent with the surrounding text.

  // Source
  :js
    $(document).ready(function{ setup_ajax(); });

  // Output

Currently there are 3 built-in filters, a plain filter, a javascript filter
(also aliased as js), and a css filter. The Plain filter will take any nested
lines and push the straight out to the the text/template parser. The javascript
filter will wrap the text in a script tag, then send it to be processed by the
text/template parser. Similarly, the css filter will wrap the text in a style
tag, then send it to the parser.

Notes

The format of the documentation and some examples were inspired by
Haml's REFERENCE file.

*/
package terse
<|endoftext|>


<commit_before>/*
Copyright 2021 The Kubernetes 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 generators

import (
	"fmt"
	"path"
	"path/filepath"
	"sort"
	"strings"

	"k8s.io/gengo/args"
	"k8s.io/gengo/generator"
	"k8s.io/gengo/namer"
	"k8s.io/gengo/types"
	"k8s.io/klog/v2"

	applygenargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args"
	clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
)

const (
	// ApplyConfigurationTypeSuffix is the suffix of generated apply configuration types.
	ApplyConfigurationTypeSuffix = "ApplyConfiguration"
)

// NameSystems returns the name system used by the generators in this package.
func NameSystems() namer.NameSystems {
	return namer.NameSystems{
		"public":  namer.NewPublicNamer(0),
		"private": namer.NewPrivateNamer(0),
		"raw":     namer.NewRawNamer("", nil),
	}
}

// DefaultNameSystem returns the default name system for ordering the types to be
// processed by the generators in this package.
func DefaultNameSystem() string {
	return "public"
}

// Packages makes the client package definition.
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
	boilerplate, err := arguments.LoadGoBoilerplate()
	if err != nil {
		klog.Fatalf("Failed loading boilerplate: %v", err)
	}

	pkgTypes := packageTypesForInputDirs(context, arguments.InputDirs, arguments.OutputPackagePath)
	customArgs := arguments.CustomArgs.(*applygenargs.CustomArgs)
	initialTypes := customArgs.ExternalApplyConfigurations
	refs := refGraphForReachableTypes(context.Universe, pkgTypes, initialTypes)
	typeModels, err := newTypeModels(customArgs.OpenAPISchemaFilePath, pkgTypes)
	if err != nil {
		klog.Fatalf("Failed build type models from typeModels %s: %v", customArgs.OpenAPISchemaFilePath, err)
	}

	groupVersions := make(map[string]clientgentypes.GroupVersions)
	groupGoNames := make(map[string]string)
	applyConfigsForGroupVersion := make(map[clientgentypes.GroupVersion][]applyConfig)

	var packageList generator.Packages
	for pkg, p := range pkgTypes {
		gv := groupVersion(p)

		pkgType := types.Name{Name: gv.Group.PackageName(), Package: pkg}

		var toGenerate []applyConfig
		for _, t := range p.Types {
			if typePkg, ok := refs[t.Name]; ok {
				toGenerate = append(toGenerate, applyConfig{
					Type:               t,
					ApplyConfiguration: types.Ref(typePkg, t.Name.Name+ApplyConfigurationTypeSuffix),
				})
			}
		}
		if len(toGenerate) == 0 {
			continue // Don't generate empty packages
		}
		sort.Sort(applyConfigSort(toGenerate))

		// generate the apply configurations
		packageList = append(packageList, generatorForApplyConfigurationsPackage(arguments.OutputPackagePath, boilerplate, pkgType, gv, toGenerate, refs, typeModels))

		// group all the generated apply configurations by gv so ForKind() can be generated
		groupPackageName := gv.Group.NonEmpty()
		groupVersionsEntry, ok := groupVersions[groupPackageName]
		if !ok {
			groupVersionsEntry = clientgentypes.GroupVersions{
				PackageName: groupPackageName,
				Group:       gv.Group,
			}
		}
		groupVersionsEntry.Versions = append(groupVersionsEntry.Versions, clientgentypes.PackageVersion{
			Version: gv.Version,
			Package: path.Clean(p.Path),
		})

		groupGoNames[groupPackageName] = goName(gv, p)
		applyConfigsForGroupVersion[gv] = toGenerate
		groupVersions[groupPackageName] = groupVersionsEntry
	}

	// generate ForKind() utility function
	packageList = append(packageList, generatorForUtils(arguments.OutputPackagePath, boilerplate, groupVersions, applyConfigsForGroupVersion, groupGoNames))
	// generate internal embedded schema, required for generated Extract functions
	packageList = append(packageList, generatorForInternal(filepath.Join(arguments.OutputPackagePath, "internal"), boilerplate, typeModels))

	return packageList
}

func friendlyName(name string) string {
	nameParts := strings.Split(name, "/")
	// Reverse first part. e.g., io.k8s... instead of k8s.io...
	if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
		parts := strings.Split(nameParts[0], ".")
		for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
			parts[i], parts[j] = parts[j], parts[i]
		}
		nameParts[0] = strings.Join(parts, ".")
	}
	return strings.Join(nameParts, ".")
}

func typeName(t *types.Type) string {
	typePackage := t.Name.Package
	if strings.Contains(typePackage, "/vendor/") {
		typePackage = typePackage[strings.Index(typePackage, "/vendor/")+len("/vendor/"):]
	}
	return fmt.Sprintf("%s.%s", typePackage, t.Name.Name)
}

func generatorForApplyConfigurationsPackage(outputPackagePath string, boilerplate []byte, packageName types.Name, gv clientgentypes.GroupVersion, typesToGenerate []applyConfig, refs refGraph, models *typeModels) *generator.DefaultPackage {
	return &generator.DefaultPackage{
		PackageName: gv.Version.PackageName(),
		PackagePath: packageName.Package,
		HeaderText:  boilerplate,
		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
			for _, toGenerate := range typesToGenerate {
				var openAPIType *string
				gvk := gvk{
					group:   gv.Group.String(),
					version: gv.Version.String(),
					kind:    toGenerate.Type.Name.Name,
				}
				if v, ok := models.gvkToOpenAPIType[gvk]; ok {
					openAPIType = &v
				}

				generators = append(generators, &applyConfigurationGenerator{
					DefaultGen: generator.DefaultGen{
						OptionalName: strings.ToLower(toGenerate.Type.Name.Name),
					},
					outputPackage: outputPackagePath,
					localPackage:  packageName,
					groupVersion:  gv,
					applyConfig:   toGenerate,
					imports:       generator.NewImportTracker(),
					refGraph:      refs,
					openAPIType:   openAPIType,
				})
			}
			return generators
		},
	}
}

func generatorForUtils(outPackagePath string, boilerplate []byte, groupVersions map[string]clientgentypes.GroupVersions, applyConfigsForGroupVersion map[clientgentypes.GroupVersion][]applyConfig, groupGoNames map[string]string) *generator.DefaultPackage {
	return &generator.DefaultPackage{
		PackageName: filepath.Base(outPackagePath),
		PackagePath: outPackagePath,
		HeaderText:  boilerplate,
		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
			generators = append(generators, &utilGenerator{
				DefaultGen: generator.DefaultGen{
					OptionalName: "utils",
				},
				outputPackage:        outPackagePath,
				imports:              generator.NewImportTracker(),
				groupVersions:        groupVersions,
				typesForGroupVersion: applyConfigsForGroupVersion,
				groupGoNames:         groupGoNames,
			})
			return generators
		},
	}
}

func generatorForInternal(outPackagePath string, boilerplate []byte, models *typeModels) *generator.DefaultPackage {
	return &generator.DefaultPackage{
		PackageName: filepath.Base(outPackagePath),
		PackagePath: outPackagePath,
		HeaderText:  boilerplate,
		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
			generators = append(generators, &internalGenerator{
				DefaultGen: generator.DefaultGen{
					OptionalName: "internal",
				},
				outputPackage: outPackagePath,
				imports:       generator.NewImportTracker(),
				typeModels:    models,
			})
			return generators
		},
	}
}

func goName(gv clientgentypes.GroupVersion, p *types.Package) string {
	goName := namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0])
	if override := types.ExtractCommentTags("+", p.Comments)["groupGoName"]; override != nil {
		goName = namer.IC(override[0])
	}
	return goName
}

func packageTypesForInputDirs(context *generator.Context, inputDirs []string, outputPath string) map[string]*types.Package {
	pkgTypes := map[string]*types.Package{}
	for _, inputDir := range inputDirs {
		p := context.Universe.Package(inputDir)
		internal := isInternalPackage(p)
		if internal {
			klog.Warningf("Skipping internal package: %s", p.Path)
			continue
		}
		gv := groupVersion(p)
		pkg := filepath.Join(outputPath, gv.Group.PackageName(), strings.ToLower(gv.Version.NonEmpty()))
		pkgTypes[pkg] = p
	}
	return pkgTypes
}

func groupVersion(p *types.Package) (gv clientgentypes.GroupVersion) {
	parts := strings.Split(p.Path, "/")
	gv.Group = clientgentypes.Group(parts[len(parts)-2])
	gv.Version = clientgentypes.Version(parts[len(parts)-1])

	// If there's a comment of the form "// +groupName=somegroup" or
	// "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the
	// group when generating.
	if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil {
		gv.Group = clientgentypes.Group(override[0])
	}
	return gv
}

// isInternalPackage returns true if the package is an internal package
func isInternalPackage(p *types.Package) bool {
	for _, t := range p.Types {
		for _, member := range t.Members {
			if member.Name == "ObjectMeta" {
				return isInternal(member)
			}
		}
	}
	return false
}

// isInternal returns true if the tags for a member do not contain a json tag
func isInternal(m types.Member) bool {
	_, ok := lookupJSONTags(m)
	return !ok
}
<commit_msg>update the apply generator to use the same package the the client generator expects<commit_after>/*
Copyright 2021 The Kubernetes 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 generators

import (
	"fmt"
	"path"
	"path/filepath"
	"sort"
	"strings"

	"k8s.io/gengo/args"
	"k8s.io/gengo/generator"
	"k8s.io/gengo/namer"
	"k8s.io/gengo/types"
	"k8s.io/klog/v2"

	applygenargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args"
	"k8s.io/code-generator/cmd/client-gen/generators/util"
	clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
)

const (
	// ApplyConfigurationTypeSuffix is the suffix of generated apply configuration types.
	ApplyConfigurationTypeSuffix = "ApplyConfiguration"
)

// NameSystems returns the name system used by the generators in this package.
func NameSystems() namer.NameSystems {
	return namer.NameSystems{
		"public":  namer.NewPublicNamer(0),
		"private": namer.NewPrivateNamer(0),
		"raw":     namer.NewRawNamer("", nil),
	}
}

// DefaultNameSystem returns the default name system for ordering the types to be
// processed by the generators in this package.
func DefaultNameSystem() string {
	return "public"
}

// Packages makes the client package definition.
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
	boilerplate, err := arguments.LoadGoBoilerplate()
	if err != nil {
		klog.Fatalf("Failed loading boilerplate: %v", err)
	}

	pkgTypes := packageTypesForInputDirs(context, arguments.InputDirs, arguments.OutputPackagePath)
	customArgs := arguments.CustomArgs.(*applygenargs.CustomArgs)
	initialTypes := customArgs.ExternalApplyConfigurations
	refs := refGraphForReachableTypes(context.Universe, pkgTypes, initialTypes)
	typeModels, err := newTypeModels(customArgs.OpenAPISchemaFilePath, pkgTypes)
	if err != nil {
		klog.Fatalf("Failed build type models from typeModels %s: %v", customArgs.OpenAPISchemaFilePath, err)
	}

	groupVersions := make(map[string]clientgentypes.GroupVersions)
	groupGoNames := make(map[string]string)
	applyConfigsForGroupVersion := make(map[clientgentypes.GroupVersion][]applyConfig)

	var packageList generator.Packages
	for pkg, p := range pkgTypes {
		gv := groupVersion(p)

		pkgType := types.Name{Name: gv.Group.PackageName(), Package: pkg}

		var toGenerate []applyConfig
		for _, t := range p.Types {
			if typePkg, ok := refs[t.Name]; ok {
				toGenerate = append(toGenerate, applyConfig{
					Type:               t,
					ApplyConfiguration: types.Ref(typePkg, t.Name.Name+ApplyConfigurationTypeSuffix),
				})
			}
		}
		if len(toGenerate) == 0 {
			continue // Don't generate empty packages
		}
		sort.Sort(applyConfigSort(toGenerate))

		// generate the apply configurations
		packageList = append(packageList, generatorForApplyConfigurationsPackage(arguments.OutputPackagePath, boilerplate, pkgType, gv, toGenerate, refs, typeModels))

		// group all the generated apply configurations by gv so ForKind() can be generated
		groupPackageName := gv.Group.NonEmpty()
		groupVersionsEntry, ok := groupVersions[groupPackageName]
		if !ok {
			groupVersionsEntry = clientgentypes.GroupVersions{
				PackageName: groupPackageName,
				Group:       gv.Group,
			}
		}
		groupVersionsEntry.Versions = append(groupVersionsEntry.Versions, clientgentypes.PackageVersion{
			Version: gv.Version,
			Package: path.Clean(p.Path),
		})

		groupGoNames[groupPackageName] = goName(gv, p)
		applyConfigsForGroupVersion[gv] = toGenerate
		groupVersions[groupPackageName] = groupVersionsEntry
	}

	// generate ForKind() utility function
	packageList = append(packageList, generatorForUtils(arguments.OutputPackagePath, boilerplate, groupVersions, applyConfigsForGroupVersion, groupGoNames))
	// generate internal embedded schema, required for generated Extract functions
	packageList = append(packageList, generatorForInternal(filepath.Join(arguments.OutputPackagePath, "internal"), boilerplate, typeModels))

	return packageList
}

func friendlyName(name string) string {
	nameParts := strings.Split(name, "/")
	// Reverse first part. e.g., io.k8s... instead of k8s.io...
	if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
		parts := strings.Split(nameParts[0], ".")
		for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
			parts[i], parts[j] = parts[j], parts[i]
		}
		nameParts[0] = strings.Join(parts, ".")
	}
	return strings.Join(nameParts, ".")
}

func typeName(t *types.Type) string {
	typePackage := t.Name.Package
	if strings.Contains(typePackage, "/vendor/") {
		typePackage = typePackage[strings.Index(typePackage, "/vendor/")+len("/vendor/"):]
	}
	return fmt.Sprintf("%s.%s", typePackage, t.Name.Name)
}

func generatorForApplyConfigurationsPackage(outputPackagePath string, boilerplate []byte, packageName types.Name, gv clientgentypes.GroupVersion, typesToGenerate []applyConfig, refs refGraph, models *typeModels) *generator.DefaultPackage {
	return &generator.DefaultPackage{
		PackageName: gv.Version.PackageName(),
		PackagePath: packageName.Package,
		HeaderText:  boilerplate,
		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
			for _, toGenerate := range typesToGenerate {
				var openAPIType *string
				gvk := gvk{
					group:   gv.Group.String(),
					version: gv.Version.String(),
					kind:    toGenerate.Type.Name.Name,
				}
				if v, ok := models.gvkToOpenAPIType[gvk]; ok {
					openAPIType = &v
				}

				generators = append(generators, &applyConfigurationGenerator{
					DefaultGen: generator.DefaultGen{
						OptionalName: strings.ToLower(toGenerate.Type.Name.Name),
					},
					outputPackage: outputPackagePath,
					localPackage:  packageName,
					groupVersion:  gv,
					applyConfig:   toGenerate,
					imports:       generator.NewImportTracker(),
					refGraph:      refs,
					openAPIType:   openAPIType,
				})
			}
			return generators
		},
	}
}

func generatorForUtils(outPackagePath string, boilerplate []byte, groupVersions map[string]clientgentypes.GroupVersions, applyConfigsForGroupVersion map[clientgentypes.GroupVersion][]applyConfig, groupGoNames map[string]string) *generator.DefaultPackage {
	return &generator.DefaultPackage{
		PackageName: filepath.Base(outPackagePath),
		PackagePath: outPackagePath,
		HeaderText:  boilerplate,
		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
			generators = append(generators, &utilGenerator{
				DefaultGen: generator.DefaultGen{
					OptionalName: "utils",
				},
				outputPackage:        outPackagePath,
				imports:              generator.NewImportTracker(),
				groupVersions:        groupVersions,
				typesForGroupVersion: applyConfigsForGroupVersion,
				groupGoNames:         groupGoNames,
			})
			return generators
		},
	}
}

func generatorForInternal(outPackagePath string, boilerplate []byte, models *typeModels) *generator.DefaultPackage {
	return &generator.DefaultPackage{
		PackageName: filepath.Base(outPackagePath),
		PackagePath: outPackagePath,
		HeaderText:  boilerplate,
		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
			generators = append(generators, &internalGenerator{
				DefaultGen: generator.DefaultGen{
					OptionalName: "internal",
				},
				outputPackage: outPackagePath,
				imports:       generator.NewImportTracker(),
				typeModels:    models,
			})
			return generators
		},
	}
}

func goName(gv clientgentypes.GroupVersion, p *types.Package) string {
	goName := namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0])
	if override := types.ExtractCommentTags("+", p.Comments)["groupGoName"]; override != nil {
		goName = namer.IC(override[0])
	}
	return goName
}

func packageTypesForInputDirs(context *generator.Context, inputDirs []string, outputPath string) map[string]*types.Package {
	pkgTypes := map[string]*types.Package{}
	for _, inputDir := range inputDirs {
		p := context.Universe.Package(inputDir)
		internal := isInternalPackage(p)
		if internal {
			klog.Warningf("Skipping internal package: %s", p.Path)
			continue
		}
		// this is how the client generator finds the package we are creating. It uses the API package name, not the group name.
		_, gvPackageString := util.ParsePathGroupVersion(p.Path)

		pkg := filepath.Join(outputPath, strings.ToLower(gvPackageString))
		pkgTypes[pkg] = p
	}
	return pkgTypes
}

func groupVersion(p *types.Package) (gv clientgentypes.GroupVersion) {
	parts := strings.Split(p.Path, "/")
	gv.Group = clientgentypes.Group(parts[len(parts)-2])
	gv.Version = clientgentypes.Version(parts[len(parts)-1])

	// If there's a comment of the form "// +groupName=somegroup" or
	// "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the
	// group when generating.
	if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil {
		gv.Group = clientgentypes.Group(override[0])
	}
	return gv
}

// isInternalPackage returns true if the package is an internal package
func isInternalPackage(p *types.Package) bool {
	for _, t := range p.Types {
		for _, member := range t.Members {
			if member.Name == "ObjectMeta" {
				return isInternal(member)
			}
		}
	}
	return false
}

// isInternal returns true if the tags for a member do not contain a json tag
func isInternal(m types.Member) bool {
	_, ok := lookupJSONTags(m)
	return !ok
}
<|endoftext|>