<commit_before>// +build linux darwin freebsd solaris

package driver

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sort"

	"github.com/containerd/continuity/devices"
	"github.com/containerd/continuity/sysx"
	"golang.org/x/sys/unix"
)

func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
	return devices.Mknod(path, mode, major, minor)
}

func (d *driver) Mkfifo(path string, mode os.FileMode) error {
	if mode&os.ModeNamedPipe == 0 {
		return errors.New("mode passed to Mkfifo does not have the named pipe bit set")
	}
	// mknod with a mode that has ModeNamedPipe set creates a fifo, not a
	// device.
	return devices.Mknod(path, mode, 0, 0)
}

// Lchmod changes the mode of an file not following symlinks.
func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
	if !filepath.IsAbs(path) {
		path, err = filepath.Abs(path)
		if err != nil {
			return
		}
	}

	return unix.Fchmodat(0, path, uint32(mode), unix.AT_SYMLINK_NOFOLLOW)
}

// Getxattr returns all of the extended attributes for the file at path p.
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
	xattrs, err := sysx.Listxattr(p)
	if err != nil {
		return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
	}

	sort.Strings(xattrs)
	m := make(map[string][]byte, len(xattrs))

	for _, attr := range xattrs {
		value, err := sysx.Getxattr(p, attr)
		if err != nil {
			return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
		}

		// NOTE(stevvooe): This append/copy tricky relies on unique
		// xattrs. Break this out into an alloc/copy if xattrs are no
		// longer unique.
		m[attr] = append(m[attr], value...)
	}

	return m, nil
}

// Setxattr sets all of the extended attributes on file at path, following
// any symbolic links, if necessary. All attributes on the target are
// replaced by the values from attr. If the operation fails to set any
// attribute, those already applied will not be rolled back.
func (d *driver) Setxattr(path string, attrMap map[string][]byte) error {
	for attr, value := range attrMap {
		if err := sysx.Setxattr(path, attr, value, 0); err != nil {
			return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
		}
	}

	return nil
}

// LGetxattr returns all of the extended attributes for the file at path p
// not following symbolic links.
func (d *driver) LGetxattr(p string) (map[string][]byte, error) {
	xattrs, err := sysx.LListxattr(p)
	if err != nil {
		return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
	}

	sort.Strings(xattrs)
	m := make(map[string][]byte, len(xattrs))

	for _, attr := range xattrs {
		value, err := sysx.LGetxattr(p, attr)
		if err != nil {
			return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
		}

		// NOTE(stevvooe): This append/copy tricky relies on unique
		// xattrs. Break this out into an alloc/copy if xattrs are no
		// longer unique.
		m[attr] = append(m[attr], value...)
	}

	return m, nil
}

// LSetxattr sets all of the extended attributes on file at path, not
// following any symbolic links. All attributes on the target are
// replaced by the values from attr. If the operation fails to set any
// attribute, those already applied will not be rolled back.
func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error {
	for attr, value := range attrMap {
		if err := sysx.LSetxattr(path, attr, value, 0); err != nil {
			return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
		}
	}

	return nil
}

func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) {
	return devices.DeviceInfo(fi)
}

// Readlink was forked on Windows to fix a Golang bug, use the "os" package here
func (d *driver) Readlink(p string) (string, error) {
	return os.Readlink(p)
}
<commit_msg>Lchmod(): simplify and optimize<commit_after>// +build linux darwin freebsd solaris

package driver

import (
	"errors"
	"fmt"
	"os"
	"sort"

	"github.com/containerd/continuity/devices"
	"github.com/containerd/continuity/sysx"
	"golang.org/x/sys/unix"
)

func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
	return devices.Mknod(path, mode, major, minor)
}

func (d *driver) Mkfifo(path string, mode os.FileMode) error {
	if mode&os.ModeNamedPipe == 0 {
		return errors.New("mode passed to Mkfifo does not have the named pipe bit set")
	}
	// mknod with a mode that has ModeNamedPipe set creates a fifo, not a
	// device.
	return devices.Mknod(path, mode, 0, 0)
}

// Lchmod changes the mode of a file not following symlinks.
func (d *driver) Lchmod(path string, mode os.FileMode) error {
	return unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), unix.AT_SYMLINK_NOFOLLOW)
}

// Getxattr returns all of the extended attributes for the file at path p.
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
	xattrs, err := sysx.Listxattr(p)
	if err != nil {
		return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
	}

	sort.Strings(xattrs)
	m := make(map[string][]byte, len(xattrs))

	for _, attr := range xattrs {
		value, err := sysx.Getxattr(p, attr)
		if err != nil {
			return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
		}

		// NOTE(stevvooe): This append/copy tricky relies on unique
		// xattrs. Break this out into an alloc/copy if xattrs are no
		// longer unique.
		m[attr] = append(m[attr], value...)
	}

	return m, nil
}

// Setxattr sets all of the extended attributes on file at path, following
// any symbolic links, if necessary. All attributes on the target are
// replaced by the values from attr. If the operation fails to set any
// attribute, those already applied will not be rolled back.
func (d *driver) Setxattr(path string, attrMap map[string][]byte) error {
	for attr, value := range attrMap {
		if err := sysx.Setxattr(path, attr, value, 0); err != nil {
			return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
		}
	}

	return nil
}

// LGetxattr returns all of the extended attributes for the file at path p
// not following symbolic links.
func (d *driver) LGetxattr(p string) (map[string][]byte, error) {
	xattrs, err := sysx.LListxattr(p)
	if err != nil {
		return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
	}

	sort.Strings(xattrs)
	m := make(map[string][]byte, len(xattrs))

	for _, attr := range xattrs {
		value, err := sysx.LGetxattr(p, attr)
		if err != nil {
			return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
		}

		// NOTE(stevvooe): This append/copy tricky relies on unique
		// xattrs. Break this out into an alloc/copy if xattrs are no
		// longer unique.
		m[attr] = append(m[attr], value...)
	}

	return m, nil
}

// LSetxattr sets all of the extended attributes on file at path, not
// following any symbolic links. All attributes on the target are
// replaced by the values from attr. If the operation fails to set any
// attribute, those already applied will not be rolled back.
func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error {
	for attr, value := range attrMap {
		if err := sysx.LSetxattr(path, attr, value, 0); err != nil {
			return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
		}
	}

	return nil
}

func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) {
	return devices.DeviceInfo(fi)
}

// Readlink was forked on Windows to fix a Golang bug, use the "os" package here
func (d *driver) Readlink(p string) (string, error) {
	return os.Readlink(p)
}
<|endoftext|>


<commit_before>package orm

import (
	"bytes"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/go-pg/pg/internal/parser"
	"github.com/go-pg/pg/types"
)

var formatter Formatter

type FormatAppender interface {
	AppendFormat([]byte, QueryFormatter) []byte
}

type sepFormatAppender interface {
	FormatAppender
	AppendSep([]byte) []byte
}

//------------------------------------------------------------------------------

type queryParamsAppender struct {
	query  string
	params []interface{}
}

var _ FormatAppender = (*queryParamsAppender)(nil)
var _ types.ValueAppender = (*queryParamsAppender)(nil)

func Q(query string, params ...interface{}) *queryParamsAppender {
	return &queryParamsAppender{query, params}
}

func (q *queryParamsAppender) AppendFormat(b []byte, f QueryFormatter) []byte {
	return f.FormatQuery(b, q.query, q.params...)
}

func (q *queryParamsAppender) AppendValue(b []byte, quote int) []byte {
	return q.AppendFormat(b, formatter)
}

//------------------------------------------------------------------------------

type condGroupAppender struct {
	sep  string
	cond []sepFormatAppender
}

var _ FormatAppender = (*condAppender)(nil)
var _ sepFormatAppender = (*condAppender)(nil)

func (q *condGroupAppender) AppendSep(b []byte) []byte {
	return append(b, q.sep...)
}

func (q *condGroupAppender) AppendFormat(b []byte, f QueryFormatter) []byte {
	b = append(b, '(')
	for i, app := range q.cond {
		if i > 0 {
			b = app.AppendSep(b)
		}
		b = app.AppendFormat(b, f)
	}
	b = append(b, ')')
	return b
}

//------------------------------------------------------------------------------

type condAppender struct {
	sep    string
	cond   string
	params []interface{}
}

var _ FormatAppender = (*condAppender)(nil)
var _ sepFormatAppender = (*condAppender)(nil)

func (q *condAppender) AppendSep(b []byte) []byte {
	return append(b, q.sep...)
}

func (q *condAppender) AppendFormat(b []byte, f QueryFormatter) []byte {
	b = append(b, '(')
	b = f.FormatQuery(b, q.cond, q.params...)
	b = append(b, ')')
	return b
}

//------------------------------------------------------------------------------

type fieldAppender struct {
	field string
}

var _ FormatAppender = (*fieldAppender)(nil)

func (a fieldAppender) AppendFormat(b []byte, f QueryFormatter) []byte {
	return types.AppendField(b, a.field, 1)
}

//------------------------------------------------------------------------------

type Formatter struct {
	namedParams map[string]interface{}
}

func (f Formatter) String() string {
	if len(f.namedParams) == 0 {
		return ""
	}

	var keys []string
	for k, _ := range f.namedParams {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var ss []string
	for _, k := range keys {
		ss = append(ss, fmt.Sprintf("%s=%v", k, f.namedParams[k]))
	}
	return " " + strings.Join(ss, " ")
}

func (f Formatter) copy() Formatter {
	var cp Formatter
	for param, value := range f.namedParams {
		cp.SetParam(param, value)
	}
	return cp
}

func (f *Formatter) SetParam(param string, value interface{}) {
	if f.namedParams == nil {
		f.namedParams = make(map[string]interface{})
	}
	f.namedParams[param] = value
}

func (f *Formatter) WithParam(param string, value interface{}) Formatter {
	cp := f.copy()
	cp.SetParam(param, value)
	return cp
}

func (f Formatter) Param(param string) (interface{}, bool) {
	v, ok := f.namedParams[param]
	return v, ok
}

func (f Formatter) Append(dst []byte, src string, params ...interface{}) []byte {
	if (params == nil && f.namedParams == nil) || strings.IndexByte(src, '?') == -1 {
		return append(dst, src...)
	}
	return f.append(dst, parser.NewString(src), params)
}

func (f Formatter) AppendBytes(dst, src []byte, params ...interface{}) []byte {
	if (params == nil && f.namedParams == nil) || bytes.IndexByte(src, '?') == -1 {
		return append(dst, src...)
	}
	return f.append(dst, parser.New(src), params)
}

func (f Formatter) FormatQuery(dst []byte, query string, params ...interface{}) []byte {
	return f.Append(dst, query, params...)
}

func (f Formatter) append(dst []byte, p *parser.Parser, params []interface{}) []byte {
	var paramsIndex int
	var namedParamsOnce bool
	var tableParams *tableParams
	var model tableModel

	if len(params) > 0 {
		var ok bool
		model, ok = params[len(params)-1].(tableModel)
		if ok {
			params = params[:len(params)-1]
		}
	}

	for p.Valid() {
		b, ok := p.ReadSep('?')
		if !ok {
			dst = append(dst, b...)
			continue
		}
		if len(b) > 0 && b[len(b)-1] == '\\' {
			dst = append(dst, b[:len(b)-1]...)
			dst = append(dst, '?')
			continue
		}
		dst = append(dst, b...)

		if id, numeric := p.ReadIdentifier(); id != "" {
			if numeric {
				idx, err := strconv.Atoi(id)
				if err != nil {
					goto restore_param
				}

				if idx >= len(params) {
					goto restore_param
				}

				dst = f.appendParam(dst, params[idx])
				continue
			}

			if f.namedParams != nil {
				param, paramOK := f.Param(id)
				if paramOK {
					dst = f.appendParam(dst, param)
					continue
				}
			}

			if !namedParamsOnce && len(params) > 0 {
				namedParamsOnce = true
				if len(params) > 0 {
					tableParams, ok = newTableParams(params[len(params)-1])
					if ok {
						params = params[:len(params)-1]
					}
				}
			}

			if tableParams != nil {
				dst, ok = tableParams.AppendParam(dst, f, id)
				if ok {
					continue
				}
			}

			if model != nil {
				dst, ok = model.AppendParam(dst, f, id)
				if ok {
					continue
				}
			}

		restore_param:
			dst = append(dst, '?')
			dst = append(dst, id...)
			continue
		}

		if paramsIndex >= len(params) {
			dst = append(dst, '?')
			continue
		}

		param := params[paramsIndex]
		paramsIndex++

		dst = f.appendParam(dst, param)
	}

	return dst
}

type queryAppender interface {
	AppendQuery(dst []byte) ([]byte, error)
}

func (f Formatter) appendParam(b []byte, param interface{}) []byte {
	switch param := param.(type) {
	case queryAppender:
		bb, err := param.AppendQuery(b)
		if err != nil {
			return types.AppendError(b, err)
		}
		return bb
	case FormatAppender:
		return param.AppendFormat(b, f)
	default:
		return types.Append(b, param, 1)
	}
}
<commit_msg>orm: add Value method to queryParamsAppender<commit_after>package orm

import (
	"bytes"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/go-pg/pg/internal"
	"github.com/go-pg/pg/internal/parser"
	"github.com/go-pg/pg/types"
)

var formatter Formatter

type FormatAppender interface {
	AppendFormat([]byte, QueryFormatter) []byte
}

type sepFormatAppender interface {
	FormatAppender
	AppendSep([]byte) []byte
}

//------------------------------------------------------------------------------

type queryParamsAppender struct {
	query  string
	params []interface{}
}

var _ FormatAppender = (*queryParamsAppender)(nil)
var _ types.ValueAppender = (*queryParamsAppender)(nil)

func Q(query string, params ...interface{}) *queryParamsAppender {
	return &queryParamsAppender{query, params}
}

func (q *queryParamsAppender) AppendFormat(b []byte, f QueryFormatter) []byte {
	return f.FormatQuery(b, q.query, q.params...)
}

func (q *queryParamsAppender) AppendValue(b []byte, quote int) []byte {
	return q.AppendFormat(b, formatter)
}

func (q *queryParamsAppender) Value() types.Q {
	b := q.AppendValue(nil, 1)
	return types.Q(internal.BytesToString(b))
}

//------------------------------------------------------------------------------

type condGroupAppender struct {
	sep  string
	cond []sepFormatAppender
}

var _ FormatAppender = (*condAppender)(nil)
var _ sepFormatAppender = (*condAppender)(nil)

func (q *condGroupAppender) AppendSep(b []byte) []byte {
	return append(b, q.sep...)
}

func (q *condGroupAppender) AppendFormat(b []byte, f QueryFormatter) []byte {
	b = append(b, '(')
	for i, app := range q.cond {
		if i > 0 {
			b = app.AppendSep(b)
		}
		b = app.AppendFormat(b, f)
	}
	b = append(b, ')')
	return b
}

//------------------------------------------------------------------------------

type condAppender struct {
	sep    string
	cond   string
	params []interface{}
}

var _ FormatAppender = (*condAppender)(nil)
var _ sepFormatAppender = (*condAppender)(nil)

func (q *condAppender) AppendSep(b []byte) []byte {
	return append(b, q.sep...)
}

func (q *condAppender) AppendFormat(b []byte, f QueryFormatter) []byte {
	b = append(b, '(')
	b = f.FormatQuery(b, q.cond, q.params...)
	b = append(b, ')')
	return b
}

//------------------------------------------------------------------------------

type fieldAppender struct {
	field string
}

var _ FormatAppender = (*fieldAppender)(nil)

func (a fieldAppender) AppendFormat(b []byte, f QueryFormatter) []byte {
	return types.AppendField(b, a.field, 1)
}

//------------------------------------------------------------------------------

type Formatter struct {
	namedParams map[string]interface{}
}

func (f Formatter) String() string {
	if len(f.namedParams) == 0 {
		return ""
	}

	var keys []string
	for k, _ := range f.namedParams {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var ss []string
	for _, k := range keys {
		ss = append(ss, fmt.Sprintf("%s=%v", k, f.namedParams[k]))
	}
	return " " + strings.Join(ss, " ")
}

func (f Formatter) copy() Formatter {
	var cp Formatter
	for param, value := range f.namedParams {
		cp.SetParam(param, value)
	}
	return cp
}

func (f *Formatter) SetParam(param string, value interface{}) {
	if f.namedParams == nil {
		f.namedParams = make(map[string]interface{})
	}
	f.namedParams[param] = value
}

func (f *Formatter) WithParam(param string, value interface{}) Formatter {
	cp := f.copy()
	cp.SetParam(param, value)
	return cp
}

func (f Formatter) Param(param string) (interface{}, bool) {
	v, ok := f.namedParams[param]
	return v, ok
}

func (f Formatter) Append(dst []byte, src string, params ...interface{}) []byte {
	if (params == nil && f.namedParams == nil) || strings.IndexByte(src, '?') == -1 {
		return append(dst, src...)
	}
	return f.append(dst, parser.NewString(src), params)
}

func (f Formatter) AppendBytes(dst, src []byte, params ...interface{}) []byte {
	if (params == nil && f.namedParams == nil) || bytes.IndexByte(src, '?') == -1 {
		return append(dst, src...)
	}
	return f.append(dst, parser.New(src), params)
}

func (f Formatter) FormatQuery(dst []byte, query string, params ...interface{}) []byte {
	return f.Append(dst, query, params...)
}

func (f Formatter) append(dst []byte, p *parser.Parser, params []interface{}) []byte {
	var paramsIndex int
	var namedParamsOnce bool
	var tableParams *tableParams
	var model tableModel

	if len(params) > 0 {
		var ok bool
		model, ok = params[len(params)-1].(tableModel)
		if ok {
			params = params[:len(params)-1]
		}
	}

	for p.Valid() {
		b, ok := p.ReadSep('?')
		if !ok {
			dst = append(dst, b...)
			continue
		}
		if len(b) > 0 && b[len(b)-1] == '\\' {
			dst = append(dst, b[:len(b)-1]...)
			dst = append(dst, '?')
			continue
		}
		dst = append(dst, b...)

		if id, numeric := p.ReadIdentifier(); id != "" {
			if numeric {
				idx, err := strconv.Atoi(id)
				if err != nil {
					goto restore_param
				}

				if idx >= len(params) {
					goto restore_param
				}

				dst = f.appendParam(dst, params[idx])
				continue
			}

			if f.namedParams != nil {
				param, paramOK := f.Param(id)
				if paramOK {
					dst = f.appendParam(dst, param)
					continue
				}
			}

			if !namedParamsOnce && len(params) > 0 {
				namedParamsOnce = true
				if len(params) > 0 {
					tableParams, ok = newTableParams(params[len(params)-1])
					if ok {
						params = params[:len(params)-1]
					}
				}
			}

			if tableParams != nil {
				dst, ok = tableParams.AppendParam(dst, f, id)
				if ok {
					continue
				}
			}

			if model != nil {
				dst, ok = model.AppendParam(dst, f, id)
				if ok {
					continue
				}
			}

		restore_param:
			dst = append(dst, '?')
			dst = append(dst, id...)
			continue
		}

		if paramsIndex >= len(params) {
			dst = append(dst, '?')
			continue
		}

		param := params[paramsIndex]
		paramsIndex++

		dst = f.appendParam(dst, param)
	}

	return dst
}

type queryAppender interface {
	AppendQuery(dst []byte) ([]byte, error)
}

func (f Formatter) appendParam(b []byte, param interface{}) []byte {
	switch param := param.(type) {
	case queryAppender:
		bb, err := param.AppendQuery(b)
		if err != nil {
			return types.AppendError(b, err)
		}
		return bb
	case FormatAppender:
		return param.AppendFormat(b, f)
	default:
		return types.Append(b, param, 1)
	}
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// 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
//
//     https://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 generator contains the command for generating Go code from YANG modules.
package generator

import (
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/openconfig/goyang/pkg/yang"
	"github.com/openconfig/ygnmi/pathgen"
	"github.com/openconfig/ygot/genutil"
	"github.com/openconfig/ygot/ygen"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	log "github.com/golang/glog"
)

// New returns a new generator command.
//nolint:errcheck
func New() *cobra.Command {
	generator := &cobra.Command{
		Use:   "generator",
		RunE:  generate,
		Short: "Generates Go code for gNMI from a YANG schema.",
		Args:  cobra.MinimumNArgs(1),
	}

	generator.Flags().String("schema_struct_path", "", "The Go import path for the schema structs package.")
	generator.Flags().String("ygot_path", "github.com/openconfig/ygot/ygot", "The import path to use for ygot.")
	generator.Flags().String("ygnmi_path", "github.com/openconfig/ygnmi/ygnmi", "The import path to use for ygnmi.")
	generator.Flags().String("ytypes_path", "github.com/openconfig/ygot/ytypes", "The import path to use for ytypes.")
	generator.Flags().String("goyang_path", "github.com/openconfig/goyang/pkg/yang", "The import path to use for goyang.")
	generator.Flags().String("base_import_path", "", "This needs to be set to the import path of the output_dir.")
	generator.Flags().StringSlice("path", nil, "Comma-separated list of paths to be recursively searched for included modules or submodules within the defined YANG modules.")
	generator.Flags().String("output_dir", "", "The directory that the generated Go code should be written to. This directory is the base of the generated module packages. default (working dir)")
	generator.Flags().Bool("generate_structs", true, "Generate structs and schema for YANG modules.")
	generator.Flags().Int("structs_split_files_count", 1, "The number of files to split the generated schema structs into.")

	generator.MarkFlagRequired("base_import_path")

	return generator
}

const (
	packageName = "root"
)

func generate(cmd *cobra.Command, args []string) error {
	schema_struct_path := viper.GetString("schema_struct_path")
	if viper.GetBool("generate_structs") {
		if schema_struct_path != "" {
			log.Warningf("schema_struct_path is set but unused because struct generation is enabled.")
		}
		schema_struct_path = viper.GetString("base_import_path")
	}
	version := "ygnmi version: " + cmd.Root().Version

	pcg := pathgen.GenConfig{
		PackageName: packageName,
		GoImports: pathgen.GoImports{
			SchemaStructPkgPath: schema_struct_path,
			YgotImportPath:      viper.GetString("ygot_path"),
			YgnmiImportPath:     viper.GetString("ygnmi_path"),
			YtypesImportPath:    viper.GetString("ytypes_path"),
		},
		PreferOperationalState:               true,
		ExcludeState:                         false,
		SkipEnumDeduplication:                false,
		ShortenEnumLeafNames:                 true,
		EnumOrgPrefixesToTrim:                []string{"openconfig"},
		UseDefiningModuleForTypedefEnumNames: true,
		AppendEnumSuffixForSimpleUnionEnums:  true,
		FakeRootName:                         "root",
		PathStructSuffix:                     "Path",
		ExcludeModules:                       nil,
		YANGParseOptions: yang.Options{
			IgnoreSubmoduleCircularDependencies: false,
		},
		GeneratingBinary:        version,
		ListBuilderKeyThreshold: 2,
		GenerateWildcardPaths:   true,
		SimplifyWildcardPaths:   false,
		TrimOCPackage:           true,
		SplitByModule:           true,
		BaseImportPath:          viper.GetString("base_import_path"),
		PackageSuffix:           "",
		UnifyPathStructs:        true,
		ExtraGenerators:         []pathgen.Generator{pathgen.GNMIGenerator},
	}

	pathCode, _, errs := pcg.GeneratePathCode(args, viper.GetStringSlice("paths"))
	if errs != nil {
		return errs
	}

	for packageName, code := range pathCode {
		path := filepath.Join(viper.GetString("output_dir"), packageName, fmt.Sprintf("%s.go", packageName))
		if err := os.MkdirAll(filepath.Join(viper.GetString("output_dir"), packageName), 0755); err != nil {
			return fmt.Errorf("failed to create directory for package %q: %w", packageName, err)
		}
		if err := ioutil.WriteFile(path, []byte(code.String()), 0644); err != nil {
			return err
		}
	}
	if !viper.GetBool("generate_structs") {
		return nil
	}

	return generateStructs(args, schema_struct_path, version)
}

func generateStructs(modules []string, schemaPath, version string) error {
	cmp, err := genutil.TranslateToCompressBehaviour(true, false, true)
	if err != nil {
		return err
	}

	// Perform the code generation.
	cg := ygen.NewYANGCodeGenerator(&ygen.GeneratorConfig{
		ParseOptions: ygen.ParseOpts{
			ExcludeModules:        nil,
			SkipEnumDeduplication: false,
			YANGParseOptions: yang.Options{
				IgnoreSubmoduleCircularDependencies: false,
			},
		},
		TransformationOptions: ygen.TransformationOpts{
			CompressBehaviour:                    cmp,
			IgnoreShadowSchemaPaths:              true,
			GenerateFakeRoot:                     true,
			FakeRootName:                         "root",
			ShortenEnumLeafNames:                 true,
			EnumOrgPrefixesToTrim:                []string{"openconfig"},
			UseDefiningModuleForTypedefEnumNames: false,
			EnumerationsUseUnderscores:           true,
		},
		Caller:              version,
		PackageName:         path.Base(schemaPath),
		GenerateJSONSchema:  true,
		IncludeDescriptions: false,
		GoOptions: ygen.GoOpts{
			YgotImportPath:                      viper.GetString("ygot_path"),
			YtypesImportPath:                    viper.GetString("ytypes_path"),
			GoyangImportPath:                    viper.GetString("goyang_path"),
			GenerateRenameMethod:                false,
			AddAnnotationFields:                 false,
			AnnotationPrefix:                    "Λ",
			AddYangPresence:                     false,
			GenerateGetters:                     true,
			GenerateDeleteMethod:                true,
			GenerateAppendMethod:                true,
			GenerateLeafGetters:                 true,
			GeneratePopulateDefault:             true,
			ValidateFunctionName:                "Validate",
			GenerateSimpleUnions:                true,
			IncludeModelData:                    false,
			AppendEnumSuffixForSimpleUnionEnums: true,
		},
	})
	generatedGoCode, errs := cg.GenerateGoCode(modules, viper.GetStringSlice("paths"))
	if errs != nil {
		return fmt.Errorf("error generating GoStruct Code: %v\n", errs)
	}
	out, err := splitCodeByFileN(generatedGoCode, viper.GetInt("structs_split_files_count"))
	if err != nil {
		return fmt.Errorf("error splitting GoStruct Code: %w\n", err)
	}
	if err := writeFiles(viper.GetString("output_dir"), out); err != nil {
		return fmt.Errorf("error while writing schema struct files: %w", err)
	}
	return nil
}

const (
	// enumMapFn is the filename to be used for the enum map when Go code is output to a directory.
	enumMapFn = "enum_map.go"
	// enumFn is the filename to be used for the enum code when Go code is output to a directory.
	enumFn = "enum.go"
	// schemaFn is the filename to be used for the schema code when outputting to a directory.
	schemaFn = "schema.go"
	// interfaceFn is the filename to be used for interface code when outputting to a directory.
	interfaceFn = "union.go"
	// structsFileFmt is the format string filename (missing index) to be
	// used for files containing structs when outputting to a directory.
	structsFileFmt = "structs-%d.go"
)

// writeFiles creates or truncates files in a given base directory and writes
// to them. Keys of the contents map are file names, and values are the
// contents to be written. An error is returned if the base directory does not
// exist. If a file cannot be written, the function aborts with the error,
// leaving an unspecified set of the other input files written with their given
// contents.
func writeFiles(dir string, out map[string]string) error {
	for filename, contents := range out {
		if len(contents) == 0 {
			continue
		}
		fh := genutil.OpenFile(filepath.Join(dir, filename))
		if fh == nil {
			return fmt.Errorf("could not open file %q", filename)
		}
		if _, err := fh.WriteString(contents); err != nil {
			return fmt.Errorf("failed to write to file: %w", err)
		}
		// flush & close written files before function finishes.
		defer genutil.SyncFile(fh)
	}

	return nil
}

func splitCodeByFileN(goCode *ygen.GeneratedGoCode, fileN int) (map[string]string, error) {
	structN := len(goCode.Structs)
	if fileN < 1 || fileN > structN {
		return nil, fmt.Errorf("requested %d files, but must be between 1 and %d (number of schema structs)", fileN, structN)
	}

	out := map[string]string{
		schemaFn: goCode.JSONSchemaCode,
		enumFn:   strings.Join(goCode.Enums, "\n"),
	}

	var structFiles []string
	var code, interfaceCode strings.Builder
	structsPerFile := int(math.Ceil(float64(structN) / float64(fileN)))
	// Empty files could appear with certain structN/fileN combinations due
	// to the ceiling numbers being used for structsPerFile.
	// e.g. 4/3 gives two files of two structs.
	// This is a little more complex, but spreads out the structs more evenly.
	// If we instead use the floor number, and put all remainder structs in
	// the last file, we might double the last file's number of structs if we get unlucky.
	// e.g. 99/10 assigns 18 structs to the last file.
	emptyFiles := fileN - int(math.Ceil(float64(structN)/float64(structsPerFile)))
	code.WriteString(goCode.OneOffHeader)
	for i, s := range goCode.Structs {
		code.WriteString(s.StructDef)
		code.WriteString(s.ListKeys)
		code.WriteString("\n")
		code.WriteString(s.Methods)
		if s.Methods != "" {
			code.WriteString("\n")
		}
		interfaceCode.WriteString(s.Interfaces)
		if s.Interfaces != "" {
			interfaceCode.WriteString("\n")
		}
		// The last file contains the remainder of the structs.
		if i == structN-1 || (i+1)%structsPerFile == 0 {
			structFiles = append(structFiles, code.String())
			code.Reset()
		}
	}
	for i := 0; i != emptyFiles; i++ {
		structFiles = append(structFiles, "")
	}

	for i, structFile := range structFiles {
		out[fmt.Sprintf(structsFileFmt, i)] = structFile
	}

	code.Reset()
	code.WriteString(goCode.EnumMap)
	if code.Len() != 0 {
		code.WriteString("\n")
	}
	code.WriteString(goCode.EnumTypeMap)

	out[enumMapFn] = code.String()
	out[interfaceFn] = interfaceCode.String()

	for name, code := range out {
		out[name] = goCode.CommonHeader + code
	}

	return out, nil
}
<commit_msg>Allow setting schema_struct_path with good default (#26)<commit_after>// Copyright 2022 Google LLC
//
// 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
//
//     https://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 generator contains the command for generating Go code from YANG modules.
package generator

import (
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/openconfig/goyang/pkg/yang"
	"github.com/openconfig/ygnmi/pathgen"
	"github.com/openconfig/ygot/genutil"
	"github.com/openconfig/ygot/ygen"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	log "github.com/golang/glog"
)

// New returns a new generator command.
//nolint:errcheck
func New() *cobra.Command {
	generator := &cobra.Command{
		Use:   "generator",
		RunE:  generate,
		Short: "Generates Go code for gNMI from a YANG schema.",
		Args:  cobra.MinimumNArgs(1),
	}

	generator.Flags().String("schema_struct_path", "", "The Go import path for the schema structs package. If struct generation is enabled, this defaults to base_import_path.")
	generator.Flags().String("ygot_path", "github.com/openconfig/ygot/ygot", "The import path to use for ygot.")
	generator.Flags().String("ygnmi_path", "github.com/openconfig/ygnmi/ygnmi", "The import path to use for ygnmi.")
	generator.Flags().String("ytypes_path", "github.com/openconfig/ygot/ytypes", "The import path to use for ytypes.")
	generator.Flags().String("goyang_path", "github.com/openconfig/goyang/pkg/yang", "The import path to use for goyang.")
	generator.Flags().String("base_import_path", "", "This needs to be set to the import path of the output_dir.")
	generator.Flags().StringSlice("path", nil, "Comma-separated list of paths to be recursively searched for included modules or submodules within the defined YANG modules.")
	generator.Flags().String("output_dir", "", "The directory that the generated Go code should be written to. This directory is the base of the generated module packages. default (working dir)")
	generator.Flags().Bool("generate_structs", true, "Generate structs and schema for YANG modules.")
	generator.Flags().Int("structs_split_files_count", 1, "The number of files to split the generated schema structs into.")

	generator.MarkFlagRequired("base_import_path")

	return generator
}

const (
	packageName = "root"
)

func generate(cmd *cobra.Command, args []string) error {
	schemaStructPath := viper.GetString("schema_struct_path")
	if viper.GetBool("generate_structs") {
		if schemaStructPath == "" {
			log.Info("schema_struct_path is unset, defaulting to base import path")
			schemaStructPath = viper.GetString("base_import_path")
		}
	}
	version := "ygnmi version: " + cmd.Root().Version

	pcg := pathgen.GenConfig{
		PackageName: packageName,
		GoImports: pathgen.GoImports{
			SchemaStructPkgPath: schemaStructPath,
			YgotImportPath:      viper.GetString("ygot_path"),
			YgnmiImportPath:     viper.GetString("ygnmi_path"),
			YtypesImportPath:    viper.GetString("ytypes_path"),
		},
		PreferOperationalState:               true,
		ExcludeState:                         false,
		SkipEnumDeduplication:                false,
		ShortenEnumLeafNames:                 true,
		EnumOrgPrefixesToTrim:                []string{"openconfig"},
		UseDefiningModuleForTypedefEnumNames: true,
		AppendEnumSuffixForSimpleUnionEnums:  true,
		FakeRootName:                         "root",
		PathStructSuffix:                     "Path",
		ExcludeModules:                       nil,
		YANGParseOptions: yang.Options{
			IgnoreSubmoduleCircularDependencies: false,
		},
		GeneratingBinary:        version,
		ListBuilderKeyThreshold: 2,
		GenerateWildcardPaths:   true,
		SimplifyWildcardPaths:   false,
		TrimOCPackage:           true,
		SplitByModule:           true,
		BaseImportPath:          viper.GetString("base_import_path"),
		PackageSuffix:           "",
		UnifyPathStructs:        true,
		ExtraGenerators:         []pathgen.Generator{pathgen.GNMIGenerator},
	}

	pathCode, _, errs := pcg.GeneratePathCode(args, viper.GetStringSlice("paths"))
	if errs != nil {
		return errs
	}

	for packageName, code := range pathCode {
		path := filepath.Join(viper.GetString("output_dir"), packageName, fmt.Sprintf("%s.go", packageName))
		if err := os.MkdirAll(filepath.Join(viper.GetString("output_dir"), packageName), 0755); err != nil {
			return fmt.Errorf("failed to create directory for package %q: %w", packageName, err)
		}
		if err := ioutil.WriteFile(path, []byte(code.String()), 0644); err != nil {
			return err
		}
	}
	if !viper.GetBool("generate_structs") {
		return nil
	}

	return generateStructs(args, schemaStructPath, version)
}

func generateStructs(modules []string, schemaPath, version string) error {
	cmp, err := genutil.TranslateToCompressBehaviour(true, false, true)
	if err != nil {
		return err
	}

	// Perform the code generation.
	cg := ygen.NewYANGCodeGenerator(&ygen.GeneratorConfig{
		ParseOptions: ygen.ParseOpts{
			ExcludeModules:        nil,
			SkipEnumDeduplication: false,
			YANGParseOptions: yang.Options{
				IgnoreSubmoduleCircularDependencies: false,
			},
		},
		TransformationOptions: ygen.TransformationOpts{
			CompressBehaviour:                    cmp,
			IgnoreShadowSchemaPaths:              true,
			GenerateFakeRoot:                     true,
			FakeRootName:                         "root",
			ShortenEnumLeafNames:                 true,
			EnumOrgPrefixesToTrim:                []string{"openconfig"},
			UseDefiningModuleForTypedefEnumNames: false,
			EnumerationsUseUnderscores:           true,
		},
		Caller:              version,
		PackageName:         path.Base(schemaPath),
		GenerateJSONSchema:  true,
		IncludeDescriptions: false,
		GoOptions: ygen.GoOpts{
			YgotImportPath:                      viper.GetString("ygot_path"),
			YtypesImportPath:                    viper.GetString("ytypes_path"),
			GoyangImportPath:                    viper.GetString("goyang_path"),
			GenerateRenameMethod:                false,
			AddAnnotationFields:                 false,
			AnnotationPrefix:                    "Λ",
			AddYangPresence:                     false,
			GenerateGetters:                     true,
			GenerateDeleteMethod:                true,
			GenerateAppendMethod:                true,
			GenerateLeafGetters:                 true,
			GeneratePopulateDefault:             true,
			ValidateFunctionName:                "Validate",
			GenerateSimpleUnions:                true,
			IncludeModelData:                    false,
			AppendEnumSuffixForSimpleUnionEnums: true,
		},
	})
	generatedGoCode, errs := cg.GenerateGoCode(modules, viper.GetStringSlice("paths"))
	if errs != nil {
		return fmt.Errorf("error generating GoStruct Code: %v\n", errs)
	}
	out, err := splitCodeByFileN(generatedGoCode, viper.GetInt("structs_split_files_count"))
	if err != nil {
		return fmt.Errorf("error splitting GoStruct Code: %w\n", err)
	}
	if err := writeFiles(viper.GetString("output_dir"), out); err != nil {
		return fmt.Errorf("error while writing schema struct files: %w", err)
	}
	return nil
}

const (
	// enumMapFn is the filename to be used for the enum map when Go code is output to a directory.
	enumMapFn = "enum_map.go"
	// enumFn is the filename to be used for the enum code when Go code is output to a directory.
	enumFn = "enum.go"
	// schemaFn is the filename to be used for the schema code when outputting to a directory.
	schemaFn = "schema.go"
	// interfaceFn is the filename to be used for interface code when outputting to a directory.
	interfaceFn = "union.go"
	// structsFileFmt is the format string filename (missing index) to be
	// used for files containing structs when outputting to a directory.
	structsFileFmt = "structs-%d.go"
)

// writeFiles creates or truncates files in a given base directory and writes
// to them. Keys of the contents map are file names, and values are the
// contents to be written. An error is returned if the base directory does not
// exist. If a file cannot be written, the function aborts with the error,
// leaving an unspecified set of the other input files written with their given
// contents.
func writeFiles(dir string, out map[string]string) error {
	for filename, contents := range out {
		if len(contents) == 0 {
			continue
		}
		fh := genutil.OpenFile(filepath.Join(dir, filename))
		if fh == nil {
			return fmt.Errorf("could not open file %q", filename)
		}
		if _, err := fh.WriteString(contents); err != nil {
			return fmt.Errorf("failed to write to file: %w", err)
		}
		// flush & close written files before function finishes.
		defer genutil.SyncFile(fh)
	}

	return nil
}

func splitCodeByFileN(goCode *ygen.GeneratedGoCode, fileN int) (map[string]string, error) {
	structN := len(goCode.Structs)
	if fileN < 1 || fileN > structN {
		return nil, fmt.Errorf("requested %d files, but must be between 1 and %d (number of schema structs)", fileN, structN)
	}

	out := map[string]string{
		schemaFn: goCode.JSONSchemaCode,
		enumFn:   strings.Join(goCode.Enums, "\n"),
	}

	var structFiles []string
	var code, interfaceCode strings.Builder
	structsPerFile := int(math.Ceil(float64(structN) / float64(fileN)))
	// Empty files could appear with certain structN/fileN combinations due
	// to the ceiling numbers being used for structsPerFile.
	// e.g. 4/3 gives two files of two structs.
	// This is a little more complex, but spreads out the structs more evenly.
	// If we instead use the floor number, and put all remainder structs in
	// the last file, we might double the last file's number of structs if we get unlucky.
	// e.g. 99/10 assigns 18 structs to the last file.
	emptyFiles := fileN - int(math.Ceil(float64(structN)/float64(structsPerFile)))
	code.WriteString(goCode.OneOffHeader)
	for i, s := range goCode.Structs {
		code.WriteString(s.StructDef)
		code.WriteString(s.ListKeys)
		code.WriteString("\n")
		code.WriteString(s.Methods)
		if s.Methods != "" {
			code.WriteString("\n")
		}
		interfaceCode.WriteString(s.Interfaces)
		if s.Interfaces != "" {
			interfaceCode.WriteString("\n")
		}
		// The last file contains the remainder of the structs.
		if i == structN-1 || (i+1)%structsPerFile == 0 {
			structFiles = append(structFiles, code.String())
			code.Reset()
		}
	}
	for i := 0; i != emptyFiles; i++ {
		structFiles = append(structFiles, "")
	}

	for i, structFile := range structFiles {
		out[fmt.Sprintf(structsFileFmt, i)] = structFile
	}

	code.Reset()
	code.WriteString(goCode.EnumMap)
	if code.Len() != 0 {
		code.WriteString("\n")
	}
	code.WriteString(goCode.EnumTypeMap)

	out[enumMapFn] = code.String()
	out[interfaceFn] = interfaceCode.String()

	for name, code := range out {
		out[name] = goCode.CommonHeader + code
	}

	return out, nil
}
<|endoftext|>


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

package swarming

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/luci/luci-go/appengine/cmd/milo/logdog"
	"github.com/luci/luci-go/appengine/cmd/milo/resp"
	"github.com/luci/luci-go/appengine/gaeauth/client"
	"github.com/luci/luci-go/client/logdog/annotee"
	swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1"
	"github.com/luci/luci-go/common/logdog/types"
	"github.com/luci/luci-go/common/logging"
	miloProto "github.com/luci/luci-go/common/proto/milo"
	"github.com/luci/luci-go/common/transport"
	"golang.org/x/net/context"
)

func resolveServer(server string) string {
	// TODO(hinoka): configure this map in luci-config
	if server == "" || server == "default" || server == "dev" {
		return "chromium-swarm-dev.appspot.com"
	} else if server == "prod" {
		return "chromium-swarm.appspot.com"
	} else {
		return server
	}
}

func getSwarmingClient(c context.Context, server string) (*swarming.Service, error) {
	client := transport.GetClient(client.UseServiceAccountTransport(
		c, []string{"https://www.googleapis.com/auth/userinfo.email"}, nil))
	sc, err := swarming.New(client)
	if err != nil {
		return nil, err
	}
	sc.BasePath = fmt.Sprintf("https://%s/_ah/api/swarming/v1/", resolveServer(server))
	return sc, nil
}

func getSwarmingLog(sc *swarming.Service, taskID string) ([]byte, error) {
	// Fetch the debug file instead.
	if strings.HasPrefix(taskID, "debug:") {
		logFilename := filepath.Join("testdata", taskID[6:])
		b, err := ioutil.ReadFile(logFilename)
		if err != nil {
			return nil, err
		}
		return b, nil
	}
	tsc := sc.Task.Stdout(taskID)
	tsco, err := tsc.Do()
	if err != nil {
		return nil, err
	}
	// tsc.Do() should return an error if the http status code is not okay.
	return []byte(tsco.Output), nil
}

func getSwarmingResult(
	sc *swarming.Service, taskID string) (*swarming.SwarmingRpcsTaskResult, error) {
	if strings.HasPrefix(taskID, "debug:") {
		// Fetch the debug file instead.
		logFilename := filepath.Join("testdata", taskID[6:])
		swarmFilename := fmt.Sprintf("%s.swarm", logFilename)
		s, err := ioutil.ReadFile(swarmFilename)
		if err != nil {
			return nil, err
		}
		sr := &swarming.SwarmingRpcsTaskResult{}
		if err := json.Unmarshal(s, sr); err != nil {
			return nil, err
		}
		return sr, nil
	}
	trc := sc.Task.Result(taskID)
	srtr, err := trc.Do()
	if err != nil {
		return nil, err
	}
	return srtr, nil
}

func getSwarming(c context.Context, server string, taskID string) (
	*swarming.SwarmingRpcsTaskResult, []byte, error) {

	var log []byte
	var sr *swarming.SwarmingRpcsTaskResult
	var errLog, errRes error
	var wg sync.WaitGroup
	sc, err := func(debug bool) (*swarming.Service, error) {
		if debug {
			return nil, nil
		}
		return getSwarmingClient(c, server)
	}(strings.HasPrefix(taskID, "debug:"))
	if err != nil {
		return nil, nil, err
	}
	wg.Add(2)
	go func() {
		defer wg.Done()
		log, errLog = getSwarmingLog(sc, taskID)
	}()
	go func() {
		defer wg.Done()
		sr, errRes = getSwarmingResult(sc, taskID)
	}()
	wg.Wait()
	if errRes == nil {
		return sr, log, errRes
	}
	return sr, log, errLog
}

// TODO(hinoka): This should go in a more generic file, when milo has more
// than one page.
func getNavi(taskID string, URL string) *resp.Navigation {
	navi := &resp.Navigation{}
	navi.PageTitle = &resp.Link{
		Label: taskID,
		URL:   URL,
	}
	navi.SiteTitle = &resp.Link{
		Label: "Milo",
		URL:   "/",
	}
	return navi
}

// Given a logdog/milo step, translate it to a BuildComponent struct.
func miloBuildStep(
	c context.Context, url string, anno *miloProto.Step, name string) *resp.BuildComponent {
	comp := &resp.BuildComponent{}
	asc := anno.GetStepComponent()
	comp.Label = asc.Name
	switch asc.Status {
	case miloProto.Status_RUNNING:
		comp.Status = resp.Running

	case miloProto.Status_SUCCESS:
		comp.Status = resp.Success

	case miloProto.Status_FAILURE:
		if anno.GetFailureDetails() != nil {
			switch anno.GetFailureDetails().Type {
			case miloProto.FailureDetails_INFRA:
				comp.Status = resp.InfraFailure

			case miloProto.FailureDetails_DM_DEPENDENCY_FAILED:
				comp.Status = resp.DependencyFailure

			default:
				comp.Status = resp.Failure
			}
		} else {
			comp.Status = resp.Failure
		}

	case miloProto.Status_EXCEPTION:
		comp.Status = resp.InfraFailure

		// Missing the case of waiting on unfinished dependency...
	default:
		comp.Status = resp.NotRun
	}
	// Sub link is for one link per log that isn't stdio.
	for _, link := range asc.GetOtherLinks() {
		lds := link.GetLogdogStream()
		if lds == nil {
			logging.Warningf(c, "Warning: %v of %v has an empty logdog stream.", link, asc)
			continue // DNE???
		}
		shortName := lds.Name[5 : len(lds.Name)-2]
		if strings.HasSuffix(lds.Name, "annotations") || strings.HasSuffix(lds.Name, "stdio") {
			// Skip the special ones.
			continue
		}
		newLink := &resp.Link{
			Label: shortName,
			URL:   strings.Join([]string{url, lds.Name}, "/"),
		}
		comp.SubLink = append(comp.SubLink, newLink)
	}

	// Main link is a link to the stdio.
	comp.MainLink = &resp.Link{
		Label: "stdio",
		URL:   strings.Join([]string{url, name, "stdio"}, "/"),
	}

	// This should always be a step.
	comp.Type = resp.Step

	// This should always be 0
	comp.LevelsDeep = 0

	// Timeswamapts
	comp.Started = asc.Started.Time().Format(time.RFC3339)

	// This should be the exact same thing.
	comp.Text = asc.Text

	return comp
}

func swarmingProperties(sr *swarming.SwarmingRpcsTaskResult) *resp.PropertyGroup {
	props := &resp.PropertyGroup{GroupName: "Swarming"}
	if len(sr.CostsUsd) == 1 {
		props.Property = append(props.Property, &resp.Property{
			Key:   "Cost of job (USD)",
			Value: fmt.Sprintf("$%.2f", sr.CostsUsd[0]),
		})
	}
	props.Property = append(props.Property, &resp.Property{
		Key:   "Exit Code",
		Value: fmt.Sprintf("%d", sr.ExitCode),
	})
	return props
}

func swarmingTags(sr *swarming.SwarmingRpcsTaskResult) *resp.PropertyGroup {
	props := &resp.PropertyGroup{GroupName: "Swarming Tags"}
	for _, s := range sr.Tags {
		sp := strings.SplitN(s, ":", 2)
		var k, v string
		k = sp[0]
		if len(sp) == 2 {
			v = sp[1]
		}
		props.Property = append(props.Property, &resp.Property{
			Key:   k,
			Value: v,
		})
	}
	return props
}

func addSwarmingToBuild(
	c context.Context, sr *swarming.SwarmingRpcsTaskResult, build *resp.MiloBuild) {
	// Specify the result.
	if sr.State == "RUNNING" {
		build.Summary.Status = resp.Running
	} else if sr.State == "PENDING" {
		build.Summary.Status = resp.NotRun
	} else if sr.InternalFailure == true || sr.State == "BOT_DIED" || sr.State == "EXPIRED" || sr.State == "TIMED_OUT" {
		build.Summary.Status = resp.InfraFailure
	} else if sr.Failure == true || sr.State == "CANCELLED" {
		// Cancelled build is user action, so it is not an infra failure.
		build.Summary.Status = resp.Failure
	} else {
		build.Summary.Status = resp.Success
	}

	// Extract more swarming specific information into the properties.
	build.PropertyGroup = append(build.PropertyGroup, swarmingProperties(sr))
	build.PropertyGroup = append(build.PropertyGroup, swarmingTags(sr))

	// Build times.  Swarming timestamps are RFC3339Nano without the timezone
	// information, which is assumed to be UTC, so we fix it here.
	build.Summary.Started = fmt.Sprintf("%sZ", sr.StartedTs)
	build.Summary.Finished = fmt.Sprintf("%sZ", sr.CompletedTs)
	build.Summary.Duration = uint64(sr.Duration)
}

// Takes in an annotated log and returns a fully populated set of logdog streams
func streamsFromAnnotatedLog(ctx context.Context, log []byte) (*logdog.Streams, error) {
	c := &memoryClient{}
	p := annotee.New(ctx, annotee.Options{
		Client:                 c,
		MetadataUpdateInterval: -1, // Neverrrrrr send incr updates.
	})
	defer p.Finish()

	is := annotee.Stream{
		Reader:           bytes.NewBuffer(log),
		Name:             types.StreamName("stdout"),
		Annotate:         true,
		StripAnnotations: true,
	}
	// If this ever has more than one stream then memoryClient needs to become
	// goroutine safe
	if err := p.RunStreams([]*annotee.Stream{&is}); err != nil {
		return nil, err
	}
	p.Finish()
	return c.ToLogDogStreams()
}

func swarmingBuildImpl(c context.Context, URL string, server string, taskID string) (*resp.MiloBuild, error) {
	// Fetch the data from Swarming
	sr, body, err := getSwarming(c, server, taskID)
	if err != nil {
		return nil, err
	}

	// Decode the data using annotee.  The logdog stream returned here is assumed
	// to be consistent, which is why the following block of code are not
	// expected to ever err out.
	lds, err := streamsFromAnnotatedLog(c, body)
	if err != nil {
		return nil, err
	}

	build := &resp.MiloBuild{}
	logdog.AddLogDogToBuild(c, URL, lds, build)
	addSwarmingToBuild(c, sr, build)
	return build, nil
}
<commit_msg>Increase swarming log fetch timeout from 5 seconds to 60 seconds<commit_after>// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package swarming

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/luci/luci-go/appengine/cmd/milo/logdog"
	"github.com/luci/luci-go/appengine/cmd/milo/resp"
	"github.com/luci/luci-go/appengine/gaeauth/client"
	"github.com/luci/luci-go/client/logdog/annotee"
	swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1"
	"github.com/luci/luci-go/common/logdog/types"
	"github.com/luci/luci-go/common/logging"
	miloProto "github.com/luci/luci-go/common/proto/milo"
	"github.com/luci/luci-go/common/transport"
	"golang.org/x/net/context"
)

func resolveServer(server string) string {
	// TODO(hinoka): configure this map in luci-config
	if server == "" || server == "default" || server == "dev" {
		return "chromium-swarm-dev.appspot.com"
	} else if server == "prod" {
		return "chromium-swarm.appspot.com"
	} else {
		return server
	}
}

func getSwarmingClient(c context.Context, server string) (*swarming.Service, error) {
	c, _ = context.WithTimeout(c, 60*time.Second)
	client := transport.GetClient(client.UseServiceAccountTransport(
		c, []string{"https://www.googleapis.com/auth/userinfo.email"}, nil))
	sc, err := swarming.New(client)
	if err != nil {
		return nil, err
	}
	sc.BasePath = fmt.Sprintf("https://%s/_ah/api/swarming/v1/", resolveServer(server))
	return sc, nil
}

func getSwarmingLog(sc *swarming.Service, taskID string) ([]byte, error) {
	// Fetch the debug file instead.
	if strings.HasPrefix(taskID, "debug:") {
		logFilename := filepath.Join("testdata", taskID[6:])
		b, err := ioutil.ReadFile(logFilename)
		if err != nil {
			return nil, err
		}
		return b, nil
	}
	tsc := sc.Task.Stdout(taskID)
	tsco, err := tsc.Do()
	if err != nil {
		return nil, err
	}
	// tsc.Do() should return an error if the http status code is not okay.
	return []byte(tsco.Output), nil
}

func getSwarmingResult(
	sc *swarming.Service, taskID string) (*swarming.SwarmingRpcsTaskResult, error) {
	if strings.HasPrefix(taskID, "debug:") {
		// Fetch the debug file instead.
		logFilename := filepath.Join("testdata", taskID[6:])
		swarmFilename := fmt.Sprintf("%s.swarm", logFilename)
		s, err := ioutil.ReadFile(swarmFilename)
		if err != nil {
			return nil, err
		}
		sr := &swarming.SwarmingRpcsTaskResult{}
		if err := json.Unmarshal(s, sr); err != nil {
			return nil, err
		}
		return sr, nil
	}
	trc := sc.Task.Result(taskID)
	srtr, err := trc.Do()
	if err != nil {
		return nil, err
	}
	return srtr, nil
}

func getSwarming(c context.Context, server string, taskID string) (
	*swarming.SwarmingRpcsTaskResult, []byte, error) {

	var log []byte
	var sr *swarming.SwarmingRpcsTaskResult
	var errLog, errRes error
	var wg sync.WaitGroup
	sc, err := func(debug bool) (*swarming.Service, error) {
		if debug {
			return nil, nil
		}
		return getSwarmingClient(c, server)
	}(strings.HasPrefix(taskID, "debug:"))
	if err != nil {
		return nil, nil, err
	}
	wg.Add(2)
	go func() {
		defer wg.Done()
		log, errLog = getSwarmingLog(sc, taskID)
	}()
	go func() {
		defer wg.Done()
		sr, errRes = getSwarmingResult(sc, taskID)
	}()
	wg.Wait()
	if errRes == nil {
		return sr, log, errRes
	}
	return sr, log, errLog
}

// TODO(hinoka): This should go in a more generic file, when milo has more
// than one page.
func getNavi(taskID string, URL string) *resp.Navigation {
	navi := &resp.Navigation{}
	navi.PageTitle = &resp.Link{
		Label: taskID,
		URL:   URL,
	}
	navi.SiteTitle = &resp.Link{
		Label: "Milo",
		URL:   "/",
	}
	return navi
}

// Given a logdog/milo step, translate it to a BuildComponent struct.
func miloBuildStep(
	c context.Context, url string, anno *miloProto.Step, name string) *resp.BuildComponent {
	comp := &resp.BuildComponent{}
	asc := anno.GetStepComponent()
	comp.Label = asc.Name
	switch asc.Status {
	case miloProto.Status_RUNNING:
		comp.Status = resp.Running

	case miloProto.Status_SUCCESS:
		comp.Status = resp.Success

	case miloProto.Status_FAILURE:
		if anno.GetFailureDetails() != nil {
			switch anno.GetFailureDetails().Type {
			case miloProto.FailureDetails_INFRA:
				comp.Status = resp.InfraFailure

			case miloProto.FailureDetails_DM_DEPENDENCY_FAILED:
				comp.Status = resp.DependencyFailure

			default:
				comp.Status = resp.Failure
			}
		} else {
			comp.Status = resp.Failure
		}

	case miloProto.Status_EXCEPTION:
		comp.Status = resp.InfraFailure

		// Missing the case of waiting on unfinished dependency...
	default:
		comp.Status = resp.NotRun
	}
	// Sub link is for one link per log that isn't stdio.
	for _, link := range asc.GetOtherLinks() {
		lds := link.GetLogdogStream()
		if lds == nil {
			logging.Warningf(c, "Warning: %v of %v has an empty logdog stream.", link, asc)
			continue // DNE???
		}
		shortName := lds.Name[5 : len(lds.Name)-2]
		if strings.HasSuffix(lds.Name, "annotations") || strings.HasSuffix(lds.Name, "stdio") {
			// Skip the special ones.
			continue
		}
		newLink := &resp.Link{
			Label: shortName,
			URL:   strings.Join([]string{url, lds.Name}, "/"),
		}
		comp.SubLink = append(comp.SubLink, newLink)
	}

	// Main link is a link to the stdio.
	comp.MainLink = &resp.Link{
		Label: "stdio",
		URL:   strings.Join([]string{url, name, "stdio"}, "/"),
	}

	// This should always be a step.
	comp.Type = resp.Step

	// This should always be 0
	comp.LevelsDeep = 0

	// Timeswamapts
	comp.Started = asc.Started.Time().Format(time.RFC3339)

	// This should be the exact same thing.
	comp.Text = asc.Text

	return comp
}

func swarmingProperties(sr *swarming.SwarmingRpcsTaskResult) *resp.PropertyGroup {
	props := &resp.PropertyGroup{GroupName: "Swarming"}
	if len(sr.CostsUsd) == 1 {
		props.Property = append(props.Property, &resp.Property{
			Key:   "Cost of job (USD)",
			Value: fmt.Sprintf("$%.2f", sr.CostsUsd[0]),
		})
	}
	props.Property = append(props.Property, &resp.Property{
		Key:   "Exit Code",
		Value: fmt.Sprintf("%d", sr.ExitCode),
	})
	return props
}

func swarmingTags(sr *swarming.SwarmingRpcsTaskResult) *resp.PropertyGroup {
	props := &resp.PropertyGroup{GroupName: "Swarming Tags"}
	for _, s := range sr.Tags {
		sp := strings.SplitN(s, ":", 2)
		var k, v string
		k = sp[0]
		if len(sp) == 2 {
			v = sp[1]
		}
		props.Property = append(props.Property, &resp.Property{
			Key:   k,
			Value: v,
		})
	}
	return props
}

func addSwarmingToBuild(
	c context.Context, sr *swarming.SwarmingRpcsTaskResult, build *resp.MiloBuild) {
	// Specify the result.
	if sr.State == "RUNNING" {
		build.Summary.Status = resp.Running
	} else if sr.State == "PENDING" {
		build.Summary.Status = resp.NotRun
	} else if sr.InternalFailure == true || sr.State == "BOT_DIED" || sr.State == "EXPIRED" || sr.State == "TIMED_OUT" {
		build.Summary.Status = resp.InfraFailure
	} else if sr.Failure == true || sr.State == "CANCELLED" {
		// Cancelled build is user action, so it is not an infra failure.
		build.Summary.Status = resp.Failure
	} else {
		build.Summary.Status = resp.Success
	}

	// Extract more swarming specific information into the properties.
	build.PropertyGroup = append(build.PropertyGroup, swarmingProperties(sr))
	build.PropertyGroup = append(build.PropertyGroup, swarmingTags(sr))

	// Build times.  Swarming timestamps are RFC3339Nano without the timezone
	// information, which is assumed to be UTC, so we fix it here.
	build.Summary.Started = fmt.Sprintf("%sZ", sr.StartedTs)
	build.Summary.Finished = fmt.Sprintf("%sZ", sr.CompletedTs)
	build.Summary.Duration = uint64(sr.Duration)
}

// Takes in an annotated log and returns a fully populated set of logdog streams
func streamsFromAnnotatedLog(ctx context.Context, log []byte) (*logdog.Streams, error) {
	c := &memoryClient{}
	p := annotee.New(ctx, annotee.Options{
		Client:                 c,
		MetadataUpdateInterval: -1, // Neverrrrrr send incr updates.
	})
	defer p.Finish()

	is := annotee.Stream{
		Reader:           bytes.NewBuffer(log),
		Name:             types.StreamName("stdout"),
		Annotate:         true,
		StripAnnotations: true,
	}
	// If this ever has more than one stream then memoryClient needs to become
	// goroutine safe
	if err := p.RunStreams([]*annotee.Stream{&is}); err != nil {
		return nil, err
	}
	p.Finish()
	return c.ToLogDogStreams()
}

func swarmingBuildImpl(c context.Context, URL string, server string, taskID string) (*resp.MiloBuild, error) {
	// Fetch the data from Swarming
	sr, body, err := getSwarming(c, server, taskID)
	if err != nil {
		return nil, err
	}

	// Decode the data using annotee.  The logdog stream returned here is assumed
	// to be consistent, which is why the following block of code are not
	// expected to ever err out.
	lds, err := streamsFromAnnotatedLog(c, body)
	if err != nil {
		return nil, err
	}

	build := &resp.MiloBuild{}
	logdog.AddLogDogToBuild(c, URL, lds, build)
	addSwarmingToBuild(c, sr, build)
	return build, nil
}
<|endoftext|>


<commit_before>package dsl

import (
	"errors"
	"fmt"
	"regexp"
	"strings"

	"github.com/emicklei/melrose/control"
	"github.com/emicklei/melrose/core"
	"github.com/emicklei/melrose/notify"

	"github.com/antonmedv/expr"
)

type Evaluator struct {
	context core.Context
	funcs   map[string]Function
}

func NewEvaluator(ctx core.Context) *Evaluator {
	return &Evaluator{
		context: ctx,
		funcs:   EvalFunctions(ctx),
	}
}

const fourSpaces = "    "

// Statements are separated by newlines.
// If a line is prefixed by one or more TABs then that line is appended to the previous.
// If a line is prefixed by 4 SPACES then that line is appended to the previous.
// Return the result of the last expression or statement.
func (e *Evaluator) EvaluateProgram(source string) (interface{}, error) {
	lines := []string{}
	splitted := strings.Split(source, "\n")
	nrOfLastExpression := -1
	for lineNr, each := range splitted {
		if strings.HasPrefix(each, "\t") || strings.HasPrefix(each, fourSpaces) { // append to previous
			if len(lines) == 0 {
				return nil, errors.New("syntax error, first line cannot start with TAB")
			}
			if nrOfLastExpression+1 != lineNr {
				return nil, fmt.Errorf("syntax error, line with TAB [%d] must be part of expression", lineNr+1)
			}
			lines[len(lines)-1] = withoutTrailingComment(lines[len(lines)-1]) + each // with TAB TODO
			nrOfLastExpression = lineNr
			continue
		}
		lines = append(lines, each)
		nrOfLastExpression = lineNr
	}
	var lastResult interface{}
	for _, each := range lines {
		result, err := e.evaluateCleanStatement(each)
		if err != nil {
			return nil, err
		}
		if result != nil {
			lastResult = result
		}
	}
	return lastResult, nil
}

func (e *Evaluator) EvaluateStatement(entry string) (interface{}, error) {
	// flatten multiline ; expr does not support multiline strings
	entry = strings.Replace(entry, "\n", " ", -1)

	return e.evaluateCleanStatement(entry)
}

func (e *Evaluator) evaluateCleanStatement(entry string) (interface{}, error) {
	// replace all TABs
	entry = strings.Replace(entry, "\t", " ", -1)

	// whitespaces
	entry = strings.TrimSpace(entry)

	// check comment line
	if strings.HasPrefix(entry, "//") {
		return nil, nil
	}
	// remove trailing inline comment
	entry = withoutTrailingComment(entry)

	if len(entry) == 0 {
		return nil, nil
	}
	if value, ok := e.context.Variables().Get(entry); ok {
		return value, nil
	}
	if variable, expression, ok := IsAssignment(entry); ok {
		// variable cannot be named after function
		if _, conflict := e.funcs[variable]; conflict {
			return nil, fmt.Errorf("cannot use variable [%s] because it is a defined function", variable)
		}

		r, err := e.EvaluateExpression(expression)
		if err != nil {
			return nil, err
		}
		// check delete
		if r == nil {
			e.context.Variables().Delete(variable)
		} else {
			// special case for Loop
			// if the value is a Loop
			// then if the variable refers to an existing loop
			// 		then change to Target of that loop
			//		else store the loop
			// else store the result
			if theLoop, ok := r.(*core.Loop); ok {
				if storedValue, present := e.context.Variables().Get(variable); present {
					if otherLoop, replaceme := storedValue.(*core.Loop); replaceme {
						otherLoop.SetTarget(theLoop.Target())
						r = otherLoop
					} else {
						// existing variable but not a Loop
						e.context.Variables().Put(variable, theLoop)
					}
				} else {
					// new variable for theLoop
					e.context.Variables().Put(variable, theLoop)
				}
				return r, nil
			}
			// special case for Listen
			// if the value is a Listen
			// then if the variable refers to an existing listen
			// 		then change to Target of that listen
			//		else store the listen
			// else store the result
			if theListen, ok := r.(*control.Listen); ok {
				if storedValue, present := e.context.Variables().Get(variable); present {
					if otherListen, replaceme := storedValue.(*control.Listen); replaceme {
						otherListen.SetTarget(theListen.Target())
						r = otherListen
					} else {
						// existing variable but not a Listen
						e.context.Variables().Put(variable, theListen)
					}
				} else {
					// new variable for theLoop
					e.context.Variables().Put(variable, theListen)
				}
				return r, nil
			}

			// not a Loop or Listen
			e.context.Variables().Put(variable, r)
		}
		return r, nil
	}
	// evaluate and print
	r, err := e.EvaluateExpression(entry)
	if err != nil {
		return nil, err
	}

	// special case for Loop
	if theLoop, ok := r.(*core.Loop); ok {
		return nil, fmt.Errorf("cannot have an unnamed Loop, use e.g. myLoop = %s", theLoop.Storex())
	}

	// special case for Listen
	if theListen, ok := r.(*control.Listen); ok {
		return nil, fmt.Errorf("cannot have an unnamed Listen, use e.g. myListen = %s", theListen.Storex())
	}

	// special case for Evals, put last because Loop is also Evaluatable
	if theEval, ok := r.(core.Evaluatable); ok {
		if err := theEval.Evaluate(e.context); err != nil { // no condition
			return nil, err
		}
	}

	return r, nil
}

// EvaluateExpression returns the result of an expression (entry) using a given store of variables.
// The result is either FunctionResult or a "raw" Go object.
func (e *Evaluator) EvaluateExpression(entry string) (interface{}, error) {
	env := envMap{}
	for k, f := range e.funcs {
		env[k] = f.Func
	}
	for k := range e.context.Variables().Variables() {
		env[k] = variable{Name: k, store: e.context.Variables()}
	}
	options := []expr.Option{expr.Env(env), expr.Patch(new(indexedAccessPatcher))}
	program, err := expr.Compile(entry, append(options, env.exprOperators()...)...)
	if err != nil {
		// try parsing the entry as a sequence
		// this can be requested from the editor to listen to a part of a sequence,chord,note,progression
		if subseq, suberr := core.ParseSequence(entry); suberr == nil {
			if core.IsDebug() {
				notify.Debugf("dsl.evaluate:%s", subseq.Storex())
			}
			return subseq, nil
		}
		// give up
		return nil, err
	}
	return expr.Run(program, env)
}

// https://regex101.com/
var assignmentRegex = regexp.MustCompile(`^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$`)

// [ ]a[]=[]note('c')
func IsAssignment(entry string) (varname string, expression string, ok bool) {
	sanitized := strings.TrimSpace(entry)
	res := assignmentRegex.FindAllStringSubmatch(sanitized, -1)
	if len(res) != 1 {
		return "", "", false
	}
	if len(res[0]) != 3 {
		return "", "", false
	}
	return res[0][1], res[0][2], true
}

func (e *Evaluator) LookupFunction(fn string) (Function, bool) {
	for name, each := range e.funcs {
		if name == fn {
			return each, true
		}
	}
	return Function{}, false
}

func withoutTrailingComment(s string) string {
	if slashes := strings.Index(s, "//"); slashes != -1 {
		return s[0:slashes]
	}
	return s
}
<commit_msg>rename<commit_after>package dsl

import (
	"errors"
	"fmt"
	"regexp"
	"strings"

	"github.com/emicklei/melrose/control"
	"github.com/emicklei/melrose/core"
	"github.com/emicklei/melrose/notify"

	"github.com/antonmedv/expr"
)

type Evaluator struct {
	context core.Context
	funcs   map[string]Function
}

func NewEvaluator(ctx core.Context) *Evaluator {
	return &Evaluator{
		context: ctx,
		funcs:   EvalFunctions(ctx),
	}
}

const fourSpaces = "    "

// Statements are separated by newlines.
// If a line is prefixed by one or more TABs then that line is appended to the previous.
// If a line is prefixed by 4 SPACES then that line is appended to the previous.
// Return the result of the last expression or statement.
func (e *Evaluator) EvaluateProgram(source string) (interface{}, error) {
	lines := []string{}
	splitted := strings.Split(source, "\n")
	nrOfLastExpression := -1
	for lineNr, each := range splitted {
		if strings.HasPrefix(each, "\t") || strings.HasPrefix(each, fourSpaces) { // append to previous
			if len(lines) == 0 {
				return nil, errors.New("syntax error, first line cannot start with TAB")
			}
			if nrOfLastExpression+1 != lineNr {
				return nil, fmt.Errorf("syntax error, line with TAB [%d] must be part of expression", lineNr+1)
			}
			lines[len(lines)-1] = withoutTrailingComment(lines[len(lines)-1]) + each // with TAB TODO
			nrOfLastExpression = lineNr
			continue
		}
		lines = append(lines, each)
		nrOfLastExpression = lineNr
	}
	var lastResult interface{}
	for _, each := range lines {
		result, err := e.evaluateCleanStatement(each)
		if err != nil {
			return nil, err
		}
		if result != nil {
			lastResult = result
		}
	}
	return lastResult, nil
}

func (e *Evaluator) EvaluateStatement(entry string) (interface{}, error) {
	// flatten multiline ; expr does not support multiline strings
	entry = strings.Replace(entry, "\n", " ", -1)

	return e.evaluateCleanStatement(entry)
}

func (e *Evaluator) evaluateCleanStatement(entry string) (interface{}, error) {
	// replace all TABs
	entry = strings.Replace(entry, "\t", " ", -1)

	// whitespaces
	entry = strings.TrimSpace(entry)

	// check comment line
	if strings.HasPrefix(entry, "//") {
		return nil, nil
	}
	// remove trailing inline comment
	entry = withoutTrailingComment(entry)

	if len(entry) == 0 {
		return nil, nil
	}
	if value, ok := e.context.Variables().Get(entry); ok {
		return value, nil
	}
	if varName, expression, ok := IsAssignment(entry); ok {
		// variable cannot be named after function
		if _, conflict := e.funcs[varName]; conflict {
			return nil, fmt.Errorf("cannot use variable [%s] because it is a defined function", varName)
		}

		r, err := e.EvaluateExpression(expression)
		if err != nil {
			return nil, err
		}
		// check delete
		if r == nil {
			e.context.Variables().Delete(varName)
		} else {
			// special case for Loop
			// if the value is a Loop
			// then if the variable refers to an existing loop
			// 		then change to Target of that loop
			//		else store the loop
			// else store the result
			if theLoop, ok := r.(*core.Loop); ok {
				if storedValue, present := e.context.Variables().Get(varName); present {
					if otherLoop, replaceme := storedValue.(*core.Loop); replaceme {
						otherLoop.SetTarget(theLoop.Target())
						r = otherLoop
					} else {
						// existing variable but not a Loop
						e.context.Variables().Put(varName, theLoop)
					}
				} else {
					// new variable for theLoop
					e.context.Variables().Put(varName, theLoop)
				}
				return r, nil
			}
			// special case for Listen
			// if the value is a Listen
			// then if the variable refers to an existing listen
			// 		then change to Target of that listen
			//		else store the listen
			// else store the result
			if theListen, ok := r.(*control.Listen); ok {
				if storedValue, present := e.context.Variables().Get(varName); present {
					if otherListen, replaceme := storedValue.(*control.Listen); replaceme {
						otherListen.SetTarget(theListen.Target())
						r = otherListen
					} else {
						// existing variable but not a Listen
						e.context.Variables().Put(varName, theListen)
					}
				} else {
					// new variable for theLoop
					e.context.Variables().Put(varName, theListen)
				}
				return r, nil
			}

			// not a Loop or Listen
			e.context.Variables().Put(varName, r)
			if aware, ok := r.(core.NameAware); ok {
				aware.VariableName(varName)
			}
		}
		return r, nil
	}
	// evaluate and print
	r, err := e.EvaluateExpression(entry)
	if err != nil {
		return nil, err
	}

	// special case for Loop
	if theLoop, ok := r.(*core.Loop); ok {
		return nil, fmt.Errorf("cannot have an unnamed Loop, use e.g. myLoop = %s", theLoop.Storex())
	}

	// special case for Listen
	if theListen, ok := r.(*control.Listen); ok {
		return nil, fmt.Errorf("cannot have an unnamed Listen, use e.g. myListen = %s", theListen.Storex())
	}

	// special case for Evals, put last because Loop is also Evaluatable
	if theEval, ok := r.(core.Evaluatable); ok {
		if err := theEval.Evaluate(e.context); err != nil { // no condition
			return nil, err
		}
	}

	return r, nil
}

// EvaluateExpression returns the result of an expression (entry) using a given store of variables.
// The result is either FunctionResult or a "raw" Go object.
func (e *Evaluator) EvaluateExpression(entry string) (interface{}, error) {
	env := envMap{}
	for k, f := range e.funcs {
		env[k] = f.Func
	}
	for k := range e.context.Variables().Variables() {
		env[k] = variable{Name: k, store: e.context.Variables()}
	}
	options := []expr.Option{expr.Env(env), expr.Patch(new(indexedAccessPatcher))}
	program, err := expr.Compile(entry, append(options, env.exprOperators()...)...)
	if err != nil {
		// try parsing the entry as a sequence
		// this can be requested from the editor to listen to a part of a sequence,chord,note,progression
		if subseq, suberr := core.ParseSequence(entry); suberr == nil {
			if core.IsDebug() {
				notify.Debugf("dsl.evaluate:%s", subseq.Storex())
			}
			return subseq, nil
		}
		// give up
		return nil, err
	}
	return expr.Run(program, env)
}

// https://regex101.com/
var assignmentRegex = regexp.MustCompile(`^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$`)

// [ ]a[]=[]note('c')
func IsAssignment(entry string) (varname string, expression string, ok bool) {
	sanitized := strings.TrimSpace(entry)
	res := assignmentRegex.FindAllStringSubmatch(sanitized, -1)
	if len(res) != 1 {
		return "", "", false
	}
	if len(res[0]) != 3 {
		return "", "", false
	}
	return res[0][1], res[0][2], true
}

func (e *Evaluator) LookupFunction(fn string) (Function, bool) {
	for name, each := range e.funcs {
		if name == fn {
			return each, true
		}
	}
	return Function{}, false
}

func withoutTrailingComment(s string) string {
	if slashes := strings.Index(s, "//"); slashes != -1 {
		return s[0:slashes]
	}
	return s
}
<|endoftext|>


<commit_before>package jpush

import (
	"encoding/json"
	"errors"
	"github.com/wuyongzhi/gopush/utils"
	"io/ioutil"
	"net/http"
	_ "net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
	"crypto/md5"
	"encoding/hex"
)

const JPushServerUrl string = "http://api.jpush.cn:8800/v2/push"
const JPushServerUrlSsl string = "https://api.jpush.cn:443/v2/push"

type Request struct {
	url.Values
}

type Message struct {
	BuilderId int		`json:"n_builder_id"`
	Title string		`json:"n_title"`
	Content string		`json:"n_content"`
	Extras string		`json:"n_extras"`
}

type Response struct {
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
	MsgId   int64 `json:"msg_id"`
}

func NewRequest() *Request {
	m := Request{}
	m.Values = make(map[string][]string, 8)
	return &m
}

func (r *Response) IsOk() bool {
	return r.ErrCode == 0
}

func (r *Response) IsFailed() bool {
	return r.ErrCode != 0
}

func (m *Request) Set(key, value string){
	m.Values.Set(key, value)
}

func (m *Request) SetInt(key string, value int){
	m.Set(key, strconv.Itoa(value))
}

func (m *Request) SendNo(sendno int){
	m.SetInt("sendno", sendno)
}

func (m *Request) AppKey(app_key string) {
	m.Set("app_key", app_key)
}

const (
	ReceiverTypeTag            int = 2
	ReceiverTypeAlias              = 3
	ReceiverTypeBoardcast          = 4
	ReceiverTypeRegistrationID     = 5
)

const (
	MsgTypeNotify = 1
	MsgTypeCustom = 2
)

//	可以是以下值:
//		ReceiverTypeAlias
// 		ReceiverTypeTag
// 		ReceiverTypeBoardcast
// 		ReceiverTypeRegistrationID
func (m *Request) ReceiverType(receiver_type int)  {
	m.SetInt("receiver_type", receiver_type)
}

func (m *Request) ReceiverValue(receiver_values ...string) {
	m.Set("receiver_value", strings.Join(receiver_values, ","))
}

//允许传递认证码自行认证，也可以在调用Send 时，传递有效的 master_secret 参数来生成认证码
func (m *Request) VerificationCode(verification_code string)  {
	m.Set("verification_code", verification_code)
}

//可以是以下值：
//
// 	MsgTypeNotify
// 	MsgTypeCustom
func (m *Request) MsgType(msg_type int)  {
	m.SetInt("msg_type", msg_type)
}

func (m *Request) MsgContent(n_builder_id int, n_title, n_content, n_extras string) {
	msg := Message{n_builder_id, n_title, n_content, n_extras}
	bytes, _ := json.Marshal(msg)
	m.Set("msg_content", string(bytes))
}

func (m *Request) SendDescription(send_description string)  {
	m.Set("send_description", send_description)
}

//按可变参数，挨个传递“平台”，方法会用逗号将它们拼起来
func (m *Request) Platform(platforms ...string)  {
	m.Set("platform", strings.Join(platforms, ","))
}

// 仅IOS  适用 0 开发环境；1 生产环境
func (m *Request) APNSProduction(apns_production int)  {
	m.SetInt("apns_production", apns_production)
}

func (m *Request) TimeToLive(time_to_live int)  {
	m.SetInt("time_to_live", time_to_live)
}

func (m *Request) OverrideMsgId(override_msg_id string)  {
	m.Set("override_msg_id", override_msg_id)
}

func (m *Request) Sign(master_secret string)  {
	src := m.Values.Get("sendno") + m.Values.Get("receiver_type") + m.Values.Get("receiver_value") + master_secret
//	fmt.Println(src)
	sum := md5.Sum([]byte(src))
	verification_code := hex.EncodeToString(sum[:])
	m.Values.Set("verification_code", verification_code)

}

func (m *Request) send(url string) (*Response, error) {

	resp, err := defaultHttpClient.PostForm(url, m.Values)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(strconv.Itoa(resp.StatusCode) + resp.Status)
	}

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var jpushResponse Response

	//	responseContent := string(bytes)
	err = json.Unmarshal(bytes, &jpushResponse)
	if err != nil {
		return nil, errors.New(err.Error() + " response: \n" + string(bytes))
	}

	// 如果失败，转换为 go 的 error
	if jpushResponse.IsFailed() {
		return &jpushResponse, errors.New(strconv.Itoa(jpushResponse.ErrCode) + ", " + jpushResponse.ErrMsg)
	}

	return &jpushResponse, nil

}

// 使用 http 协议
func (m *Request) Send() (*Response, error) {
	return m.send(JPushServerUrl)
}

// 使用 https 协议
func (m *Request) SendSecure() (*Response, error) {
	return m.send(JPushServerUrlSsl)
}

var defaultHttpClient *utils.HttpClient

func init() {
	timeout, _ := time.ParseDuration("10s")
	defaultHttpClient = utils.NewHttpClient(20, timeout, timeout, false)
	//defaultHttpClient.
}
<commit_msg>msg_id 类型改为 int64<commit_after>package jpush

import (
	"encoding/json"
	"errors"
	"github.com/wuyongzhi/gopush/utils"
	"io/ioutil"
	"net/http"
	_ "net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
	"crypto/md5"
	"encoding/hex"
)

const JPushServerUrl string = "http://api.jpush.cn:8800/v2/push"
const JPushServerUrlSsl string = "https://api.jpush.cn:443/v2/push"

type Request struct {
	url.Values
}

type Message struct {
	BuilderId int		`json:"n_builder_id"`
	Title string		`json:"n_title"`
	Content string		`json:"n_content"`
	Extras string		`json:"n_extras"`
}

type JPushMsgId interface {}

var InvalidMsgId JPushMsgId = nil

type Response struct {
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
	MsgId   JPushMsgId `json:"msg_id"`
}

type Response2 struct {
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`

}

func NewRequest() *Request {
	m := Request{}
	m.Values = make(map[string][]string, 8)
	return &m
}

func (r *Response) IsOk() bool {
	return r.ErrCode == 0
}

func (r *Response) IsFailed() bool {
	return r.ErrCode != 0
}

func (m *Request) Set(key, value string){
	m.Values.Set(key, value)
}

func (m *Request) SetInt(key string, value int){
	m.Set(key, strconv.Itoa(value))
}

func (m *Request) SendNo(sendno int){
	m.SetInt("sendno", sendno)
}

func (m *Request) AppKey(app_key string) {
	m.Set("app_key", app_key)
}

const (
	ReceiverTypeTag            int = 2
	ReceiverTypeAlias              = 3
	ReceiverTypeBoardcast          = 4
	ReceiverTypeRegistrationID     = 5
)

const (
	MsgTypeNotify = 1
	MsgTypeCustom = 2
)

//	可以是以下值:
//		ReceiverTypeAlias
// 		ReceiverTypeTag
// 		ReceiverTypeBoardcast
// 		ReceiverTypeRegistrationID
func (m *Request) ReceiverType(receiver_type int)  {
	m.SetInt("receiver_type", receiver_type)
}

func (m *Request) ReceiverValue(receiver_values ...string) {
	m.Set("receiver_value", strings.Join(receiver_values, ","))
}

//允许传递认证码自行认证，也可以在调用Send 时，传递有效的 master_secret 参数来生成认证码
func (m *Request) VerificationCode(verification_code string)  {
	m.Set("verification_code", verification_code)
}

//可以是以下值：
//
// 	MsgTypeNotify
// 	MsgTypeCustom
func (m *Request) MsgType(msg_type int)  {
	m.SetInt("msg_type", msg_type)
}

func (m *Request) MsgContent(n_builder_id int, n_title, n_content, n_extras string) {
	msg := Message{n_builder_id, n_title, n_content, n_extras}
	bytes, _ := json.Marshal(msg)
	m.Set("msg_content", string(bytes))
}

func (m *Request) SendDescription(send_description string)  {
	m.Set("send_description", send_description)
}

//按可变参数，挨个传递“平台”，方法会用逗号将它们拼起来
func (m *Request) Platform(platforms ...string)  {
	m.Set("platform", strings.Join(platforms, ","))
}

// 仅IOS  适用 0 开发环境；1 生产环境
func (m *Request) APNSProduction(apns_production int)  {
	m.SetInt("apns_production", apns_production)
}

func (m *Request) TimeToLive(time_to_live int)  {
	m.SetInt("time_to_live", time_to_live)
}

func (m *Request) OverrideMsgId(override_msg_id string)  {
	m.Set("override_msg_id", override_msg_id)
}

func (m *Request) Sign(master_secret string)  {
	src := m.Values.Get("sendno") + m.Values.Get("receiver_type") + m.Values.Get("receiver_value") + master_secret
//	fmt.Println(src)
	sum := md5.Sum([]byte(src))
	verification_code := hex.EncodeToString(sum[:])
	m.Values.Set("verification_code", verification_code)

}

func (m *Request) send(url string) (*Response, error) {

	resp, err := defaultHttpClient.PostForm(url, m.Values)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(strconv.Itoa(resp.StatusCode) + resp.Status)
	}

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var jpushResponse Response

	//	responseContent := string(bytes)
	err = json.Unmarshal(bytes, &jpushResponse)
	if err != nil {
		return nil, errors.New(err.Error() + " response: \n" + string(bytes))
	}

	// 如果失败，转换为 go 的 error
	if jpushResponse.IsFailed() {
		return &jpushResponse, errors.New(strconv.Itoa(jpushResponse.ErrCode) + ", " + jpushResponse.ErrMsg)
	}

	return &jpushResponse, nil

}

// 使用 http 协议
func (m *Request) Send() (*Response, error) {
	return m.send(JPushServerUrl)
}

// 使用 https 协议
func (m *Request) SendSecure() (*Response, error) {
	return m.send(JPushServerUrlSsl)
}

var defaultHttpClient *utils.HttpClient

func init() {
	timeout, _ := time.ParseDuration("10s")
	defaultHttpClient = utils.NewHttpClient(20, timeout, timeout, false)
	//defaultHttpClient.
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/mitchellh/cli"
)

func MountsCommandFactory() (cli.Command, error) {
	return &MountsCommand{}, nil
}

type MountsCommand struct{}

func (c *MountsCommand) Run(_ []string) int {
	k, err := CreateKlientClient(NewKlientOptions())
	if err != nil {
		// TODO: Print UX friendly err
		fmt.Println("Error:", err)
		return 1
	}

	if err := k.Dial(); err != nil {
		// TODO: Print UX friendly err
		fmt.Println("Error:", err)
		return 1
	}

	res, err := k.Tell("remote.mounts")
	if err != nil {
		// TODO: Print UX friendly err
		fmt.Println("Error:", err)
		return 1
	}

	type kiteMounts struct {
		Ip         string `json:"ip"`
		RemotePath string `json:"remotePath"`
		LocalPath  string `json:"localPath"`
		MountName  string `json:"mountName"`
	}

	var mounts []kiteMounts
	res.Unmarshal(&mounts)

	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	fmt.Fprintf(w, "\tNAME\tLOCAL PATH\tREMOTE PATH\tMACHINE IP\n")
	for i, mount := range mounts {
		fmt.Fprintf(w, "  %d.\t%s\t%s\t%s\t%s\n",
			i+1,
			mount.MountName,
			mount.LocalPath,
			mount.RemotePath,
			mount.Ip,
		)
	}
	w.Flush()

	return 0
}

func (*MountsCommand) Help() string {
	helpText := `
Usage: %s mounts

	List the mounted folders on this machine.
`
	return fmt.Sprintf(helpText, Name, KlientName)
}

func (*MountsCommand) Synopsis() string {
	return fmt.Sprintf("List mounted folders on this machine")
}
<commit_msg>return machine ip next to name in `kd mounts`<commit_after>package main

import (
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/mitchellh/cli"
)

func MountsCommandFactory() (cli.Command, error) {
	return &MountsCommand{}, nil
}

type MountsCommand struct{}

func (c *MountsCommand) Run(_ []string) int {
	k, err := CreateKlientClient(NewKlientOptions())
	if err != nil {
		// TODO: Print UX friendly err
		fmt.Println("Error:", err)
		return 1
	}

	if err := k.Dial(); err != nil {
		// TODO: Print UX friendly err
		fmt.Println("Error:", err)
		return 1
	}

	res, err := k.Tell("remote.mounts")
	if err != nil {
		// TODO: Print UX friendly err
		fmt.Println("Error:", err)
		return 1
	}

	type kiteMounts struct {
		Ip         string `json:"ip"`
		RemotePath string `json:"remotePath"`
		LocalPath  string `json:"localPath"`
		MountName  string `json:"mountName"`
	}

	var mounts []kiteMounts
	res.Unmarshal(&mounts)

	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	fmt.Fprintf(w, "\tNAME\tMACHINE IP\tLOCAL PATH\tREMOTE PATH\n")
	for i, mount := range mounts {
		fmt.Fprintf(w, "  %d.\t%s\t%s\t%s\t%s\n",
			i+1,
			mount.MountName,
			mount.Ip,
			mount.LocalPath,
			mount.RemotePath,
		)
	}
	w.Flush()

	return 0
}

func (*MountsCommand) Help() string {
	helpText := `
Usage: %s mounts

	List the mounted folders on this machine.
`
	return fmt.Sprintf(helpText, Name, KlientName)
}

func (*MountsCommand) Synopsis() string {
	return fmt.Sprintf("List mounted folders on this machine")
}
<|endoftext|>


<commit_before>package edit

import (
	"bytes"
	"fmt"
	"math"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/elves/elvish/edit/ui"
	"github.com/elves/elvish/eval"
	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/store/storedefs"
	"github.com/elves/elvish/util"
)

// Location mode.

var _ = registerBuiltins(modeLocation, map[string]func(*Editor){
	"start": locStart,
})

func init() {
	registerBindings(modeLocation, modeLocation, map[ui.Key]string{})
}

// PinnedScore is a special value of Score in storedefs.Dir to represent that the
// directory is pinned.
var PinnedScore = math.Inf(1)

type location struct {
	home     string // The home directory; leave empty if unknown.
	all      []storedefs.Dir
	filtered []storedefs.Dir
}

func newLocation(dirs []storedefs.Dir, home string) *listing {
	l := newListing(modeLocation, &location{all: dirs, home: home})
	return &l
}

func (loc *location) ModeTitle(i int) string {
	return " LOCATION "
}

func (*location) CursorOnModeLine() bool {
	return true
}

func (loc *location) Len() int {
	return len(loc.filtered)
}

func (loc *location) Show(i int) (string, ui.Styled) {
	var header string
	score := loc.filtered[i].Score
	if score == PinnedScore {
		header = "*"
	} else {
		header = fmt.Sprintf("%.0f", score)
	}
	return header, ui.Unstyled(showPath(loc.filtered[i].Path, loc.home))
}

func (loc *location) Filter(filter string) int {
	loc.filtered = nil
	pattern := makeLocationFilterPattern(filter)
	for _, item := range loc.all {
		if pattern.MatchString(showPath(item.Path, loc.home)) {
			loc.filtered = append(loc.filtered, item)
		}
	}

	if len(loc.filtered) == 0 {
		return -1
	}
	return 0
}

func showPath(path, home string) string {
	if home != "" && path == home {
		return "~"
	} else if home != "" && strings.HasPrefix(path, home+"/") {
		return "~/" + parse.Quote(path[len(home)+1:])
	} else {
		return parse.Quote(path)
	}
}

var emptyRegexp = regexp.MustCompile("")

func makeLocationFilterPattern(s string) *regexp.Regexp {
	var b bytes.Buffer
	b.WriteString(".*")
	segs := strings.Split(s, "/")
	for i, seg := range segs {
		if i > 0 {
			b.WriteString(".*/.*")
		}
		b.WriteString(regexp.QuoteMeta(seg))
	}
	b.WriteString(".*")
	p, err := regexp.Compile(b.String())
	if err != nil {
		logger.Printf("failed to compile regexp %q: %v", b.String(), err)
		return emptyRegexp
	}
	return p
}

func (ed *Editor) chdir(dir string) error {
	dir, err := filepath.Abs(dir)
	if err != nil {
		return err
	}
	err = os.Chdir(dir)
	if err == nil {
		store := ed.daemon
		store.Waits().Add(1)
		go func() {
			// XXX Error ignored.
			store.AddDir(dir, 1)
			store.Waits().Done()
			logger.Println("added dir to store:", dir)
		}()
	}
	return err
}

// Editor interface.

func (loc *location) Accept(i int, ed *Editor) {
	err := ed.chdir(loc.filtered[i].Path)
	if err != nil {
		ed.Notify("%v", err)
	}
	ed.mode = &ed.insert
}

func locStart(ed *Editor) {
	if ed.daemon == nil {
		ed.Notify("%v", ErrStoreOffline)
		return
	}

	pinnedValue := ed.locPinned()
	pinned := convertListToDirs(pinnedValue)
	black := convertListToSet(ed.locHidden(), pinnedValue)
	stored, err := ed.daemon.Dirs(black)
	if err != nil {
		ed.Notify("store error: %v", err)
		return
	}

	// concatenate pinned and stored dirs, pinned first
	dirs := make([]storedefs.Dir, len(pinned)+len(stored))
	copy(dirs, pinned)
	copy(dirs[len(pinned):], stored)

	// Drop the error. When there is an error, home is "", which is used to
	// signify "no home known" in location.
	home, _ := util.GetHome("")
	ed.mode = newLocation(dirs, home)
}

// convertListToDirs converts a list of strings to []storedefs.Dir. It uses the
// special score of PinnedScore to signify that the directory is pinned.
func convertListToDirs(li eval.List) []storedefs.Dir {
	pinned := make([]storedefs.Dir, 0, li.Len())
	// XXX(xiaq): silently drops non-string items.
	li.Iterate(func(v eval.Value) bool {
		if s, ok := v.(eval.String); ok {
			pinned = append(pinned, storedefs.Dir{string(s), PinnedScore})
		}
		return true
	})
	return pinned
}

func convertListToSet(lis ...eval.List) map[string]struct{} {
	set := make(map[string]struct{})
	// XXX(xiaq): silently drops non-string items.
	for _, li := range lis {
		li.Iterate(func(v eval.Value) bool {
			if s, ok := v.(eval.String); ok {
				set[string(s)] = struct{}{}
			}
			return true
		})
	}
	return set
}

// Variables.

var _ = RegisterVariable("loc-hidden", func() eval.Variable {
	return eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList)
})

func (ed *Editor) locHidden() eval.List {
	return ed.variables["loc-hidden"].Get().(eval.List)
}

var _ = RegisterVariable("loc-pinned", func() eval.Variable {
	return eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList)
})

func (ed *Editor) locPinned() eval.List {
	return ed.variables["loc-pinned"].Get().(eval.List)
}
<commit_msg>Some trivial cleanup.<commit_after>package edit

import (
	"bytes"
	"fmt"
	"math"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/elves/elvish/edit/ui"
	"github.com/elves/elvish/eval"
	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/store/storedefs"
	"github.com/elves/elvish/util"
)

// Location mode.

var _ = registerBuiltins(modeLocation, map[string]func(*Editor){
	"start": locStart,
})

func init() {
	registerBindings(modeLocation, modeLocation, map[ui.Key]string{})
}

// PinnedScore is a special value of Score in storedefs.Dir to represent that the
// directory is pinned.
var PinnedScore = math.Inf(1)

type location struct {
	home     string // The home directory; leave empty if unknown.
	all      []storedefs.Dir
	filtered []storedefs.Dir
}

func newLocation(dirs []storedefs.Dir, home string) *listing {
	l := newListing(modeLocation, &location{all: dirs, home: home})
	return &l
}

func (loc *location) ModeTitle(i int) string {
	return " LOCATION "
}

func (*location) CursorOnModeLine() bool {
	return true
}

func (loc *location) Len() int {
	return len(loc.filtered)
}

func (loc *location) Show(i int) (string, ui.Styled) {
	var header string
	score := loc.filtered[i].Score
	if score == PinnedScore {
		header = "*"
	} else {
		header = fmt.Sprintf("%.0f", score)
	}
	return header, ui.Unstyled(showPath(loc.filtered[i].Path, loc.home))
}

func (loc *location) Filter(filter string) int {
	loc.filtered = nil
	pattern := makeLocationFilterPattern(filter)
	for _, item := range loc.all {
		if pattern.MatchString(showPath(item.Path, loc.home)) {
			loc.filtered = append(loc.filtered, item)
		}
	}

	if len(loc.filtered) == 0 {
		return -1
	}
	return 0
}

func showPath(path, home string) string {
	if home != "" && path == home {
		return "~"
	} else if home != "" && strings.HasPrefix(path, home+"/") {
		return "~/" + parse.Quote(path[len(home)+1:])
	} else {
		return parse.Quote(path)
	}
}

var emptyRegexp = regexp.MustCompile("")

func makeLocationFilterPattern(s string) *regexp.Regexp {
	var b bytes.Buffer
	b.WriteString(".*")
	segs := strings.Split(s, "/")
	for i, seg := range segs {
		if i > 0 {
			b.WriteString(".*/.*")
		}
		b.WriteString(regexp.QuoteMeta(seg))
	}
	b.WriteString(".*")
	p, err := regexp.Compile(b.String())
	if err != nil {
		logger.Printf("failed to compile regexp %q: %v", b.String(), err)
		return emptyRegexp
	}
	return p
}

func (ed *Editor) chdir(dir string) error {
	dir, err := filepath.Abs(dir)
	if err != nil {
		return err
	}
	err = os.Chdir(dir)
	if err == nil {
		store := ed.daemon
		store.Waits().Add(1)
		go func() {
			// XXX Error ignored.
			store.AddDir(dir, 1)
			store.Waits().Done()
			logger.Println("added dir to store:", dir)
		}()
	}
	return err
}

// Editor interface.

func (loc *location) Accept(i int, ed *Editor) {
	err := ed.chdir(loc.filtered[i].Path)
	if err != nil {
		ed.Notify("%v", err)
	}
	ed.mode = &ed.insert
}

func locStart(ed *Editor) {
	if ed.daemon == nil {
		ed.Notify("%v", ErrStoreOffline)
		return
	}

	// Pinned directories are also blacklisted to prevent them from showing up
	// twice.
	black := convertListsToSet(ed.locHidden(), ed.locPinned())
	stored, err := ed.daemon.Dirs(black)
	if err != nil {
		ed.Notify("store error: %v", err)
		return
	}

	// Concatenate pinned and stored dirs, pinned first.
	pinned := convertListToDirs(ed.locPinned())
	dirs := make([]storedefs.Dir, len(pinned)+len(stored))
	copy(dirs, pinned)
	copy(dirs[len(pinned):], stored)

	// Drop the error. When there is an error, home is "", which is used to
	// signify "no home known" in location.
	home, _ := util.GetHome("")
	ed.mode = newLocation(dirs, home)
}

// convertListToDirs converts a list of strings to []storedefs.Dir. It uses the
// special score of PinnedScore to signify that the directory is pinned.
func convertListToDirs(li eval.List) []storedefs.Dir {
	pinned := make([]storedefs.Dir, 0, li.Len())
	// XXX(xiaq): silently drops non-string items.
	li.Iterate(func(v eval.Value) bool {
		if s, ok := v.(eval.String); ok {
			pinned = append(pinned, storedefs.Dir{string(s), PinnedScore})
		}
		return true
	})
	return pinned
}

func convertListsToSet(lis ...eval.List) map[string]struct{} {
	set := make(map[string]struct{})
	// XXX(xiaq): silently drops non-string items.
	for _, li := range lis {
		li.Iterate(func(v eval.Value) bool {
			if s, ok := v.(eval.String); ok {
				set[string(s)] = struct{}{}
			}
			return true
		})
	}
	return set
}

// Variables.

var _ = RegisterVariable("loc-hidden", func() eval.Variable {
	return eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList)
})

func (ed *Editor) locHidden() eval.List {
	return ed.variables["loc-hidden"].Get().(eval.List)
}

var _ = RegisterVariable("loc-pinned", func() eval.Variable {
	return eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList)
})

func (ed *Editor) locPinned() eval.List {
	return ed.variables["loc-pinned"].Get().(eval.List)
}
<|endoftext|>