<commit_before>package main

import (
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/gonuts/commander"
	"github.com/gonuts/flag"
)

func hwaf_make_cmd_pkg_create() *commander.Command {
	cmd := &commander.Command{
		Run:       hwaf_run_cmd_pkg_create,
		UsageLine: "create [options] <pkg-full-path>",
		Short:     "create a new package in the current workarea",
		Long: `
create creates a new package in the current workarea.

ex:
 $ hwaf pkg create MyPath/MyPackage
 $ hwaf pkg create -script=yml MyPath/MyPackage
 $ hwaf pkg create -script=py  MyPath/MyPackage
`,
		Flag: *flag.NewFlagSet("hwaf-pkg-create", flag.ExitOnError),
	}
	cmd.Flag.Bool("v", false, "enable verbose output")
	cmd.Flag.String("script", "yml", "type of the hwaf script to use (yml|py)")
	cmd.Flag.String("authors", "", "comma-separated list of authors for the new package")
	return cmd
}

func hwaf_run_cmd_pkg_create(cmd *commander.Command, args []string) error {
	var err error
	n := "hwaf-pkg-" + cmd.Name()
	pkgpath := ""
	switch len(args) {
	case 1:
		pkgpath = args[0]
	default:
		return fmt.Errorf("%s: you need to give a package (full) path", n)
	}

	script := cmd.Flag.Lookup("script").Value.Get().(string)
	switch script {
	case "yml", "py":
		// ok
	default:
		return fmt.Errorf("%s: script type is either 'yml' or 'py' (got: %q)", n, script)
	}

	verbose := cmd.Flag.Lookup("v").Value.Get().(bool)
	authors := func() []string {
		authors := cmd.Flag.Lookup("authors").Value.Get().(string)
		out := make([]string, 0, 1)
		for _, s := range strings.Split(authors, ",") {
			s = strings.Trim(s, " ")
			if s == "" {
				continue
			}
			out = append(out, s)
		}
		return out
	}()

	if len(authors) == 0 {
		usr, err := user.Current()
		if err != nil {
			return err
		}
		//fmt.Printf(">>>>> %v\n", usr)
		usrname := usr.Name
		if usrname == "" {
			usrname = usr.Username
		}
		authors = []string{usrname}
	}

	if verbose {
		fmt.Printf("%s: create package [%s]...\n", n, pkgpath)
	}

	cfg, err := g_ctx.LocalCfg()
	if err != nil {
		return err
	}

	pkgdir := "src"
	if cfg.HasOption("hwaf-cfg", "pkgdir") {
		pkgdir, err = cfg.String("hwaf-cfg", "pkgdir")
		if err != nil {
			return err
		}
	}

	dir := filepath.Join(pkgdir, pkgpath)
	if path_exists(dir) {
		err = fmt.Errorf("%s: directory [%s] already exists on filesystem", n, dir)
		if err != nil {
			return err
		}
	}

	err = os.MkdirAll(dir, 0755)
	if err != nil {
		return err
	}

	if g_ctx.PkgDb.HasPkg(dir) {
		return fmt.Errorf("%s: a package with name [%s] already exists", n, dir)
	}

	pkgname := filepath.Base(pkgpath)

	const w_txt = `# -*- python -*-
# automatically generated wscript

import waflib.Logs as msg

PACKAGE = {
    'name': '{{.FullName}}',
    'author': [{{.Authors | printlst }}],
}

def pkg_deps(ctx):
    # put your package dependencies here.
    # e.g.:
    # ctx.use_pkg('AtlasPolicy')
    return

def configure(ctx):
    msg.debug('[configure] package name: '+PACKAGE['name'])
    return

def build(ctx):
    # build artifacts
    # e.g.:
    # ctx.build_complib(
    #    name = '{{.Name}}',
    #    source = 'src/*.cxx src/components/*.cxx',
    #    use = ['lib1', 'lib2', 'ROOT', 'boost', ...],
    # )
    # ctx.install_headers()
    # ctx.build_pymodule(source=['python/*.py'])
    # ctx.install_joboptions(source=['share/*.py'])
    return
`

	const h_txt = `# -*- yaml -*-
# automatically generated hscript

package: {
  name: "{{.FullName}}",
  authors: [{{.Authors | printlst}}],

  ## dependencies of this package
  deps: {
    public: [
    ],

    private: [
    ],

    # specify runtime dependencies
    # e.g: python modules for scripts installed by this package
    #      binaries used by scripts installed by this package
    runtime: [
    ],
  },
}

options: {}
configure: {}

build: {
  # build artifacts
  # e.g.:
  # {{.Name}}: {
  #    source: "src/*.cxx src/components/*.cxx",
  #    use: ["lib1", "lib2", "ROOT", "boost", ...],
  # }
}

## EOF ##
`
	txt := h_txt
	fname := "hscript.yml"

	switch script {
	case "yml":
		txt = h_txt
		fname = "hscript.yml"
	case "py":
		txt = w_txt
		fname = "hscript.py"
	}

	// create generic structure...
	for _, d := range []string{
		//"cmt",
		pkgname,
		"src",
	} {
		err = os.MkdirAll(filepath.Join(dir, d), 0755)
		if err != nil {
			return err
		}
	}

	wscript, err := os.Create(filepath.Join(dir, fname))
	if err != nil {
		return err
	}
	defer func(err *error) {
		*err = wscript.Sync()
		if *err != nil {
			return
		}
		*err = wscript.Close()
	}(&err)

	/* fill the template */
	pkg := struct {
		FullName string
		Name     string
		Authors  []string
	}{
		FullName: pkgpath,
		Name:     pkgname,
		Authors:  authors,
	}
	tmpl := template.New(script).Funcs(template.FuncMap{
		"printlst": func(lst []string) string {
			out := []string{}
			for idx, s := range lst {
				s = strings.Trim(s, " ")
				if s == "" {
					continue
				}
				comma := ","
				if idx+1 == len(lst) {
					comma = ""
				}
				out = append(out, fmt.Sprintf("%q%s", s, comma))
			}
			return strings.Join(out, " ")
		},
	})
	tmpl, err = tmpl.Parse(txt)
	if err != nil {
		return err
	}
	err = tmpl.Execute(wscript, &pkg)
	if err != nil {
		return err
	}

	err = g_ctx.PkgDb.Add("local", "", pkgdir, dir)
	if err != nil {
		return err
	}

	if verbose {
		fmt.Printf("%s: create package [%s]... [ok]\n", n, pkgpath)
	}

	return err
}

// EOF
<commit_msg>pkg-create: more doc to the generated stubs<commit_after>package main

import (
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/gonuts/commander"
	"github.com/gonuts/flag"
)

func hwaf_make_cmd_pkg_create() *commander.Command {
	cmd := &commander.Command{
		Run:       hwaf_run_cmd_pkg_create,
		UsageLine: "create [options] <pkg-full-path>",
		Short:     "create a new package in the current workarea",
		Long: `
create creates a new package in the current workarea.

ex:
 $ hwaf pkg create MyPath/MyPackage
 $ hwaf pkg create -script=yml MyPath/MyPackage
 $ hwaf pkg create -script=py  MyPath/MyPackage
`,
		Flag: *flag.NewFlagSet("hwaf-pkg-create", flag.ExitOnError),
	}
	cmd.Flag.Bool("v", false, "enable verbose output")
	cmd.Flag.String("script", "yml", "type of the hwaf script to use (yml|py)")
	cmd.Flag.String("authors", "", "comma-separated list of authors for the new package")
	return cmd
}

func hwaf_run_cmd_pkg_create(cmd *commander.Command, args []string) error {
	var err error
	n := "hwaf-pkg-" + cmd.Name()
	pkgpath := ""
	switch len(args) {
	case 1:
		pkgpath = args[0]
	default:
		return fmt.Errorf("%s: you need to give a package (full) path", n)
	}

	script := cmd.Flag.Lookup("script").Value.Get().(string)
	switch script {
	case "yml", "py":
		// ok
	default:
		return fmt.Errorf("%s: script type is either 'yml' or 'py' (got: %q)", n, script)
	}

	verbose := cmd.Flag.Lookup("v").Value.Get().(bool)
	authors := func() []string {
		authors := cmd.Flag.Lookup("authors").Value.Get().(string)
		out := make([]string, 0, 1)
		for _, s := range strings.Split(authors, ",") {
			s = strings.Trim(s, " ")
			if s == "" {
				continue
			}
			out = append(out, s)
		}
		return out
	}()

	if len(authors) == 0 {
		usr, err := user.Current()
		if err != nil {
			return err
		}
		//fmt.Printf(">>>>> %v\n", usr)
		usrname := usr.Name
		if usrname == "" {
			usrname = usr.Username
		}
		authors = []string{usrname}
	}

	if verbose {
		fmt.Printf("%s: create package [%s]...\n", n, pkgpath)
	}

	cfg, err := g_ctx.LocalCfg()
	if err != nil {
		return err
	}

	pkgdir := "src"
	if cfg.HasOption("hwaf-cfg", "pkgdir") {
		pkgdir, err = cfg.String("hwaf-cfg", "pkgdir")
		if err != nil {
			return err
		}
	}

	dir := filepath.Join(pkgdir, pkgpath)
	if path_exists(dir) {
		err = fmt.Errorf("%s: directory [%s] already exists on filesystem", n, dir)
		if err != nil {
			return err
		}
	}

	err = os.MkdirAll(dir, 0755)
	if err != nil {
		return err
	}

	if g_ctx.PkgDb.HasPkg(dir) {
		return fmt.Errorf("%s: a package with name [%s] already exists", n, dir)
	}

	pkgname := filepath.Base(pkgpath)

	const w_txt = `# -*- python -*-
# automatically generated wscript

import waflib.Logs as msg

PACKAGE = {
    'name': '{{.FullName}}',
    'author': [{{.Authors | printlst }}],
}

def pkg_deps(ctx):
    # put your package dependencies here.
    # e.g.:
    # ctx.use_pkg('AtlasPolicy')
    return

def configure(ctx):
    msg.debug('[configure] package name: '+PACKAGE['name'])
    return

def build(ctx):
    # build artifacts
    # e.g.:
    # ctx(
    #    features = 'atlas_component',
    #    name = '{{.Name}}',
    #    source = 'src/*.cxx src/components/*.cxx',
    #    use = ['lib1', 'lib2', 'ROOT', 'boost', ...],
    # )
    # ctx(
    #    features = 'atlas_install_headers',
    #    name = '{{.Name}}-install-headers',
    # )
    # ctx(
    #    features = 'atlas_install_python_modules',
    #    name = '{{.Name}}-install-py-modules',
    # )
    # ctx(
    #    features = 'atlas_install_joboptions',
    #    name = '{{.Name}}-install-jobos',
    # )
    return
`

	const h_txt = `# -*- yaml -*-
# automatically generated hscript

package: {
  name: "{{.FullName}}",
  authors: [{{.Authors | printlst}}],

  ## dependencies of this package
  deps: {
    public: [
    ],

    private: [
    ],

    # specify runtime dependencies
    # e.g: python modules for scripts installed by this package
    #      binaries used by scripts installed by this package
    runtime: [
    ],
  },
}

options: {}
configure: {}

build: {
  # build artifacts
  # e.g.:
  # {{.Name}}: {
  #    features: "atlas_component",
  #    source:   "src/*.cxx src/components/*.cxx",
  #    use:      ["lib1", "lib2", "ROOT", "boost", ...],
  # }
}

## EOF ##
`
	txt := h_txt
	fname := "hscript.yml"

	switch script {
	case "yml":
		txt = h_txt
		fname = "hscript.yml"
	case "py":
		txt = w_txt
		fname = "hscript.py"
	}

	// create generic structure...
	for _, d := range []string{
		//"cmt",
		pkgname,
		"src",
	} {
		err = os.MkdirAll(filepath.Join(dir, d), 0755)
		if err != nil {
			return err
		}
	}

	wscript, err := os.Create(filepath.Join(dir, fname))
	if err != nil {
		return err
	}
	defer func(err *error) {
		*err = wscript.Sync()
		if *err != nil {
			return
		}
		*err = wscript.Close()
	}(&err)

	/* fill the template */
	pkg := struct {
		FullName string
		Name     string
		Authors  []string
	}{
		FullName: pkgpath,
		Name:     pkgname,
		Authors:  authors,
	}
	tmpl := template.New(script).Funcs(template.FuncMap{
		"printlst": func(lst []string) string {
			out := []string{}
			for idx, s := range lst {
				s = strings.Trim(s, " ")
				if s == "" {
					continue
				}
				comma := ","
				if idx+1 == len(lst) {
					comma = ""
				}
				out = append(out, fmt.Sprintf("%q%s", s, comma))
			}
			return strings.Join(out, " ")
		},
	})
	tmpl, err = tmpl.Parse(txt)
	if err != nil {
		return err
	}
	err = tmpl.Execute(wscript, &pkg)
	if err != nil {
		return err
	}

	err = g_ctx.PkgDb.Add("local", "", pkgdir, dir)
	if err != nil {
		return err
	}

	if verbose {
		fmt.Printf("%s: create package [%s]... [ok]\n", n, pkgpath)
	}

	return err
}

// EOF
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

// Headerer is an interface for getting additional HTTP headers to use
// when downloading data (images, signatures).
type Headerer interface {
	Header() http.Header
}

// Config is a single place where configuration for rkt frontend needs
// resides.
type Config struct {
	AuthPerHost map[string]Headerer
}

type configParser interface {
	parse(config *Config, raw []byte) error
}

var (
	// configSubDirs is a map saying what kinds of configuration
	// (values) are acceptable in a config subdirectory (key)
	configSubDirs  = make(map[string][]string)
	parsersForKind = make(map[string]map[string]configParser)
)

// Default paths for vendor and custom configuration
const (
	DefaultVendorPath = "/usr/lib/rkt"
	DefaultCustomPath = "/etc/rkt"
)

func addParser(kind, version string, parser configParser) {
	if len(kind) == 0 {
		panic("empty kind string when registering a config parser")
	}
	if len(version) == 0 {
		panic("empty version string when registering a config parser")
	}
	if parser == nil {
		panic("trying to register a nil parser")
	}
	if _, err := getParser(kind, version); err == nil {
		panic(fmt.Sprintf("A parser for kind %q and version %q already exist", kind, version))
	}
	if _, ok := parsersForKind[kind]; !ok {
		parsersForKind[kind] = make(map[string]configParser)
	}
	parsersForKind[kind][version] = parser
}

func registerSubDir(dir string, kinds []string) {
	if len(dir) == 0 {
		panic("trying to register empty config subdirectory")
	}
	if len(kinds) == 0 {
		panic("kinds array cannot be empty when registering config subdir")
	}
	allKinds := toArray(toSet(append(configSubDirs[dir], kinds...)))
	sort.Strings(allKinds)
	configSubDirs[dir] = allKinds
}

func toSet(a []string) map[string]struct{} {
	s := make(map[string]struct{})
	for _, v := range a {
		s[v] = struct{}{}
	}
	return s
}

func toArray(s map[string]struct{}) []string {
	a := make([]string, len(s))
	for k := range s {
		a = append(a, k)
	}
	sort.Strings(a)
	return a
}

// GetConfig gets the Config instance with configuration taken from
// default vendor path (see DefaultVendorPath) overridden with
// configuration from default custom path (see DefaultCustomPath).
func GetConfig() (*Config, error) {
	return GetConfigFrom(DefaultVendorPath, DefaultCustomPath)
}

// GetConfigFrom gets the Config instance with configuration taken from
// given vendor path overridden with configuration from given custom
// path.
func GetConfigFrom(vendor, custom string) (*Config, error) {
	cfg := newConfig()
	for _, cd := range []string{vendor, custom} {
		subcfg, err := GetConfigFromDir(cd)
		if err != nil {
			return nil, err
		}
		mergeConfigs(cfg, subcfg)
	}
	return cfg, nil
}

// GetConfigFromDir gets the Config instance with configuration taken
// from given directory.
func GetConfigFromDir(dir string) (*Config, error) {
	subcfg := newConfig()
	if valid, err := validDir(dir); err != nil {
		return nil, err
	} else if !valid {
		return subcfg, nil
	}
	if err := readConfigDir(subcfg, dir); err != nil {
		return nil, err
	}
	return subcfg, nil
}

func newConfig() *Config {
	return &Config{
		AuthPerHost: make(map[string]Headerer),
	}
}

func readConfigDir(config *Config, dir string) error {
	for csd, kinds := range configSubDirs {
		d := filepath.Join(dir, csd)
		if valid, err := validDir(d); err != nil {
			return err
		} else if !valid {
			continue
		}
		configWalker := getConfigWalker(config, kinds, d)
		if err := filepath.Walk(d, configWalker); err != nil {
			return err
		}
	}
	return nil
}

func validDir(path string) (bool, error) {
	fi, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	if !fi.IsDir() {
		return false, fmt.Errorf("expected %q to be a directory", path)
	}
	return true, nil
}

func getConfigWalker(config *Config, kinds []string, root string) filepath.WalkFunc {
	return func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if path == root {
			return nil
		}
		return readFile(config, info, path, kinds)
	}
}

func readFile(config *Config, info os.FileInfo, path string, kinds []string) error {
	if valid, err := validConfigFile(info); err != nil {
		return err
	} else if !valid {
		return nil
	}
	if err := parseConfigFile(config, path, kinds); err != nil {
		return err
	}
	return nil
}

func validConfigFile(info os.FileInfo) (bool, error) {
	mode := info.Mode()
	switch {
	case mode.IsDir():
		return false, filepath.SkipDir
	case mode.IsRegular():
		return filepath.Ext(info.Name()) == ".json", nil
	case mode&os.ModeSymlink == os.ModeSymlink:
		// TODO: support symlinks?
		return false, nil
	default:
		return false, nil
	}
}

type configHeader struct {
	RktVersion string `json:"rktVersion"`
	RktKind    string `json:"rktKind"`
}

func parseConfigFile(config *Config, path string, kinds []string) error {
	raw, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	var header configHeader
	if err := json.Unmarshal(raw, &header); err != nil {
		return err
	}
	if len(header.RktKind) == 0 {
		return fmt.Errorf("no rktKind specified in %q", path)
	}
	if len(header.RktVersion) == 0 {
		return fmt.Errorf("no rktVersion specified in %q", path)
	}
	kindOk := false
	for _, kind := range kinds {
		if header.RktKind == kind {
			kindOk = true
			break
		}
	}
	if !kindOk {
		dir := filepath.Dir(path)
		base := filepath.Base(path)
		kindsStr := strings.Join(kinds, `", "`)
		return fmt.Errorf("the configuration directory %q expects to have configuration files of kinds %q, but %q has kind of %q", dir, kindsStr, base, header.RktKind)
	}
	parser, err := getParser(header.RktKind, header.RktVersion)
	if err != nil {
		return err
	}
	if err := parser.parse(config, raw); err != nil {
		return fmt.Errorf("failed to parse %q: %v", path, err)
	}
	return nil
}

func getParser(kind, version string) (configParser, error) {
	parsers, ok := parsersForKind[kind]
	if !ok {
		return nil, fmt.Errorf("no parser available for configuration of kind %q", kind)
	}
	parser, ok := parsers[version]
	if !ok {
		return nil, fmt.Errorf("no parser available for configuration of kind %q and version %q", kind, version)
	}
	return parser, nil
}

func mergeConfigs(config *Config, subconfig *Config) {
	for host, headerer := range subconfig.AuthPerHost {
		config.AuthPerHost[host] = headerer
	}
}
<commit_msg>rkt: Remove redundant sorting<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

// Headerer is an interface for getting additional HTTP headers to use
// when downloading data (images, signatures).
type Headerer interface {
	Header() http.Header
}

// Config is a single place where configuration for rkt frontend needs
// resides.
type Config struct {
	AuthPerHost map[string]Headerer
}

type configParser interface {
	parse(config *Config, raw []byte) error
}

var (
	// configSubDirs is a map saying what kinds of configuration
	// (values) are acceptable in a config subdirectory (key)
	configSubDirs  = make(map[string][]string)
	parsersForKind = make(map[string]map[string]configParser)
)

// Default paths for vendor and custom configuration
const (
	DefaultVendorPath = "/usr/lib/rkt"
	DefaultCustomPath = "/etc/rkt"
)

func addParser(kind, version string, parser configParser) {
	if len(kind) == 0 {
		panic("empty kind string when registering a config parser")
	}
	if len(version) == 0 {
		panic("empty version string when registering a config parser")
	}
	if parser == nil {
		panic("trying to register a nil parser")
	}
	if _, err := getParser(kind, version); err == nil {
		panic(fmt.Sprintf("A parser for kind %q and version %q already exist", kind, version))
	}
	if _, ok := parsersForKind[kind]; !ok {
		parsersForKind[kind] = make(map[string]configParser)
	}
	parsersForKind[kind][version] = parser
}

func registerSubDir(dir string, kinds []string) {
	if len(dir) == 0 {
		panic("trying to register empty config subdirectory")
	}
	if len(kinds) == 0 {
		panic("kinds array cannot be empty when registering config subdir")
	}
	allKinds := toArray(toSet(append(configSubDirs[dir], kinds...)))
	sort.Strings(allKinds)
	configSubDirs[dir] = allKinds
}

func toSet(a []string) map[string]struct{} {
	s := make(map[string]struct{})
	for _, v := range a {
		s[v] = struct{}{}
	}
	return s
}

func toArray(s map[string]struct{}) []string {
	a := make([]string, len(s))
	for k := range s {
		a = append(a, k)
	}
	return a
}

// GetConfig gets the Config instance with configuration taken from
// default vendor path (see DefaultVendorPath) overridden with
// configuration from default custom path (see DefaultCustomPath).
func GetConfig() (*Config, error) {
	return GetConfigFrom(DefaultVendorPath, DefaultCustomPath)
}

// GetConfigFrom gets the Config instance with configuration taken from
// given vendor path overridden with configuration from given custom
// path.
func GetConfigFrom(vendor, custom string) (*Config, error) {
	cfg := newConfig()
	for _, cd := range []string{vendor, custom} {
		subcfg, err := GetConfigFromDir(cd)
		if err != nil {
			return nil, err
		}
		mergeConfigs(cfg, subcfg)
	}
	return cfg, nil
}

// GetConfigFromDir gets the Config instance with configuration taken
// from given directory.
func GetConfigFromDir(dir string) (*Config, error) {
	subcfg := newConfig()
	if valid, err := validDir(dir); err != nil {
		return nil, err
	} else if !valid {
		return subcfg, nil
	}
	if err := readConfigDir(subcfg, dir); err != nil {
		return nil, err
	}
	return subcfg, nil
}

func newConfig() *Config {
	return &Config{
		AuthPerHost: make(map[string]Headerer),
	}
}

func readConfigDir(config *Config, dir string) error {
	for csd, kinds := range configSubDirs {
		d := filepath.Join(dir, csd)
		if valid, err := validDir(d); err != nil {
			return err
		} else if !valid {
			continue
		}
		configWalker := getConfigWalker(config, kinds, d)
		if err := filepath.Walk(d, configWalker); err != nil {
			return err
		}
	}
	return nil
}

func validDir(path string) (bool, error) {
	fi, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	if !fi.IsDir() {
		return false, fmt.Errorf("expected %q to be a directory", path)
	}
	return true, nil
}

func getConfigWalker(config *Config, kinds []string, root string) filepath.WalkFunc {
	return func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if path == root {
			return nil
		}
		return readFile(config, info, path, kinds)
	}
}

func readFile(config *Config, info os.FileInfo, path string, kinds []string) error {
	if valid, err := validConfigFile(info); err != nil {
		return err
	} else if !valid {
		return nil
	}
	if err := parseConfigFile(config, path, kinds); err != nil {
		return err
	}
	return nil
}

func validConfigFile(info os.FileInfo) (bool, error) {
	mode := info.Mode()
	switch {
	case mode.IsDir():
		return false, filepath.SkipDir
	case mode.IsRegular():
		return filepath.Ext(info.Name()) == ".json", nil
	case mode&os.ModeSymlink == os.ModeSymlink:
		// TODO: support symlinks?
		return false, nil
	default:
		return false, nil
	}
}

type configHeader struct {
	RktVersion string `json:"rktVersion"`
	RktKind    string `json:"rktKind"`
}

func parseConfigFile(config *Config, path string, kinds []string) error {
	raw, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	var header configHeader
	if err := json.Unmarshal(raw, &header); err != nil {
		return err
	}
	if len(header.RktKind) == 0 {
		return fmt.Errorf("no rktKind specified in %q", path)
	}
	if len(header.RktVersion) == 0 {
		return fmt.Errorf("no rktVersion specified in %q", path)
	}
	kindOk := false
	for _, kind := range kinds {
		if header.RktKind == kind {
			kindOk = true
			break
		}
	}
	if !kindOk {
		dir := filepath.Dir(path)
		base := filepath.Base(path)
		kindsStr := strings.Join(kinds, `", "`)
		return fmt.Errorf("the configuration directory %q expects to have configuration files of kinds %q, but %q has kind of %q", dir, kindsStr, base, header.RktKind)
	}
	parser, err := getParser(header.RktKind, header.RktVersion)
	if err != nil {
		return err
	}
	if err := parser.parse(config, raw); err != nil {
		return fmt.Errorf("failed to parse %q: %v", path, err)
	}
	return nil
}

func getParser(kind, version string) (configParser, error) {
	parsers, ok := parsersForKind[kind]
	if !ok {
		return nil, fmt.Errorf("no parser available for configuration of kind %q", kind)
	}
	parser, ok := parsers[version]
	if !ok {
		return nil, fmt.Errorf("no parser available for configuration of kind %q and version %q", kind, version)
	}
	return parser, nil
}

func mergeConfigs(config *Config, subconfig *Config) {
	for host, headerer := range subconfig.AuthPerHost {
		config.AuthPerHost[host] = headerer
	}
}
<|endoftext|>


<commit_before>// Package igo implements the machinery necessary to run a Go kernel for IPython.
// It should be installed with an "igo" command to launch the kernel.
package igo

import (
    "fmt"
    "log"
    "io"
    "io/ioutil"
    "os"
    "encoding/json"
    "encoding/hex"
    "crypto/sha256"
    "crypto/hmac"
    zmq "github.com/alecthomas/gozmq"
    uuid "github.com/nu7hatch/gouuid"
    "go/token"
    "github.com/sbinet/go-eval/pkg/eval"

)

var logger *log.Logger

type MsgHeader struct  {
    Msg_id string `json:"msg_id"`
    Username string `json:"username"`
    Session string `json:"session"`
    Msg_type string `json:"msg_type"`
}

// ComposedMsg represents an entire message in a high-level structure.
type ComposedMsg struct {
    Header MsgHeader
    Parent_header MsgHeader
    Metadata map[string]interface{}
    Content interface{}
}

// ConnectionInfo stores the contents of the kernel connection file created by IPython.
type ConnectionInfo struct {
    Signature_scheme string
    Transport string
    Stdin_port int
    Control_port int
    IOPub_port int
    HB_port int
    Shell_port int
    Key string
    IP string
}

// SocketGroup holds the sockets the kernel needs to communicate with the kernel, and
// the key for message signing.
type SocketGroup struct {
    Shell_socket *zmq.Socket
    Stdin_socket *zmq.Socket
    IOPub_socket *zmq.Socket
    Key []byte
}

// PrepareSockets sets up the ZMQ sockets through which the kernel will communicate.
func PrepareSockets(conn_info ConnectionInfo) (sg SocketGroup) {
    context, _ := zmq.NewContext()
    sg.Shell_socket, _ = context.NewSocket(zmq.ROUTER)
    sg.Stdin_socket, _ = context.NewSocket(zmq.ROUTER)
    sg.IOPub_socket, _ = context.NewSocket(zmq.PUB)

    address := fmt.Sprintf("%v://%v:%%v", conn_info.Transport, conn_info.IP)

    sg.Shell_socket.Bind(fmt.Sprintf(address, conn_info.Shell_port))
    sg.Stdin_socket.Bind(fmt.Sprintf(address, conn_info.Stdin_port))
    sg.IOPub_socket.Bind(fmt.Sprintf(address, conn_info.IOPub_port))

    // Message signing key
    sg.Key = []byte(conn_info.Key)

    // Start the heartbeat device
    HB_socket, _ := context.NewSocket(zmq.REP)
    HB_socket.Bind(fmt.Sprintf(address, conn_info.HB_port))
    go zmq.Device(zmq.FORWARDER, HB_socket, HB_socket)
    return
}

// InvalidSignatureError is returned when the signature on a received message does not
// validate.
type InvalidSignatureError struct {}
func (e *InvalidSignatureError) Error() string {
    return "A message had an invalid signature"
}

// WireMsgToComposedMsg translates a multipart ZMQ messages received from a socket into
// a ComposedMsg struct and a slice of return identities. This includes verifying the
// message signature.
func WireMsgToComposedMsg(msgparts [][]byte, signkey []byte) (msg ComposedMsg,
                            identities [][]byte, err error) {
    i := 0
    for string(msgparts[i]) != "<IDS|MSG>" {
        i++
    }
    identities = msgparts[:i]
    // msgparts[i] is the delimiter

    // Validate signature
    if len(signkey) != 0 {
        mac := hmac.New(sha256.New, signkey)
        for _, msgpart := range msgparts[i+2:i+6] {
            mac.Write(msgpart)
        }
        signature := make([]byte, hex.DecodedLen(len(msgparts[i+1])))
        hex.Decode(signature, msgparts[i+1])
        if !hmac.Equal(mac.Sum(nil), signature) {
            return msg, nil, &InvalidSignatureError{}
        }
    }
    json.Unmarshal(msgparts[i+2], &msg.Header)
    json.Unmarshal(msgparts[i+3], &msg.Parent_header)
    json.Unmarshal(msgparts[i+4], &msg.Metadata)
    json.Unmarshal(msgparts[i+5], &msg.Content)
    return
}

// ToWireMsg translates a ComposedMsg into a multipart ZMQ message ready to send, and
// signs it. This does not add the return identities or the delimiter.
func (msg ComposedMsg) ToWireMsg(signkey []byte) (msgparts [][]byte) {
    msgparts = make([][]byte, 5)
    header, _ := json.Marshal(msg.Header)
    msgparts[1] = header
    parent_header, _ := json.Marshal(msg.Parent_header)
    msgparts[2] = parent_header
    if msg.Metadata == nil {
        msg.Metadata = make(map[string]interface{})
    }
    metadata, _ := json.Marshal(msg.Metadata)
    msgparts[3] = metadata
    content, _ := json.Marshal(msg.Content)
    msgparts[4] = content

    // Sign the message
    if len(signkey) != 0 {
        mac := hmac.New(sha256.New, signkey)
        for _, msgpart := range msgparts[1:] {
            mac.Write(msgpart)
        }
        msgparts[0] = make([]byte, hex.EncodedLen(mac.Size()))
        hex.Encode(msgparts[0], mac.Sum(nil))
    }
    return
}

// MsgReceipt represents a received message, its return identities, and the sockets for
// communication.
type MsgReceipt struct {
    Msg ComposedMsg
    Identities [][]byte
    Sockets SocketGroup
}

// SendResponse sends a message back to return identites of the received message.
func (receipt *MsgReceipt) SendResponse(socket *zmq.Socket, msg ComposedMsg) {
    socket.SendMultipart(receipt.Identities, zmq.SNDMORE)
    socket.Send([]byte("<IDS|MSG>"), zmq.SNDMORE)
    socket.SendMultipart(msg.ToWireMsg(receipt.Sockets.Key), 0)
    logger.Println("<--", msg.Header.Msg_type)
    logger.Printf("%+v\n", msg.Content)
}

// HandleShellMsg responds to a message on the shell ROUTER socket.
func HandleShellMsg(receipt MsgReceipt) {
    switch receipt.Msg.Header.Msg_type {
        case "kernel_info_request":
            SendKernelInfo(receipt)
        case "execute_request":
            HandleExecuteRequest(receipt)
        case "shutdown_request":
            HandleShutdownRequest(receipt)
        default: logger.Println("Unhandled shell message:", receipt.Msg.Header.Msg_type)
    }
}

// NewMsg creates a new ComposedMsg to respond to a parent message. This includes setting
// up its headers.
func NewMsg(msg_type string, parent ComposedMsg) (msg ComposedMsg) {
    msg.Parent_header = parent.Header
    msg.Header.Session = parent.Header.Session
    msg.Header.Username = parent.Header.Username
    msg.Header.Msg_type = msg_type
    u, _ := uuid.NewV4()
    msg.Header.Msg_id = u.String()
    return
}

// KernelInfo holds information about the igo kernel, for kernel_info_reply messages.
type KernelInfo struct {
    Protocol_version []int `json:"protocol_version"`
    Language string `json:"language"`
}

// KernelStatus holds a kernel state, for status broadcast messages.
type KernelStatus struct {
    ExecutionState string `json:"execution_state"`
}

//SendKernelInfo sends a kernel_info_reply message.
func SendKernelInfo(receipt MsgReceipt) {
    reply := NewMsg("kernel_info_reply", receipt.Msg)
    reply.Content = KernelInfo{[]int{4, 0}, "go"}
    receipt.SendResponse(receipt.Sockets.Shell_socket, reply)
}

type ShutdownReply struct {
    Restart bool `json:"restart"`
}

func HandleShutdownRequest(receipt MsgReceipt) {
    reply := NewMsg("shutdown_reply", receipt.Msg)
    restart := receipt.Msg.Content.(map[string]bool)["restart"]
    reply.Content = ShutdownReply{restart}
    receipt.SendResponse(receipt.Sockets.Shell_socket, reply)
    os.Exit(0)
}

// World holds the user namespace for the REPL.
var World *eval.World
var fset *token.FileSet
// ExecCounter is incremented each time we run user code.
var ExecCounter int = 0

// RunCode runs the given user code, returning the expression value and/or an error.
func RunCode(text string) (val interface{}, err error) {
    var code eval.Code
    code, err = World.Compile(fset, text)
    if err != nil {
        return nil, err
    }
    val, err = code.Run()
    return
}

// OutputMsg holds the data for a pyout message.
type OutputMsg struct {
    Execcount int `json:"execution_count"`
    Data map[string]string `json:"data"`
    Metadata map[string]interface{} `json:"metadata"`
}

type ErrMsg struct {
    EName string `json:"ename"`
    EValue string `json:"evalue"`
    Traceback []string `json:"traceback"`
}

// HandleExecuteRequest runs code from an execute_request method, and sends the various
// reply messages.
func HandleExecuteRequest(receipt MsgReceipt) {
    reply := NewMsg("execute_reply", receipt.Msg)
    content := make(map[string]interface{})
    reqcontent := receipt.Msg.Content.(map[string]interface{})
    code := reqcontent["code"].(string)
    silent := reqcontent["silent"].(bool)
    if !silent {
        ExecCounter++
    }
    content["execution_count"] = ExecCounter
    val, err := RunCode(code)
    if err == nil {
        content["status"] = "ok"
        content["payload"] = make([]map[string]interface{}, 0)
        content["user_variables"] = make(map[string]string)
        content["user_expressions"] = make(map[string]string)
        if (val != nil) && !silent {
            var out_content OutputMsg
            out := NewMsg("pyout", receipt.Msg)
            out_content.Execcount = ExecCounter
            out_content.Data = make(map[string]string)
            out_content.Data["text/plain"] = fmt.Sprint(val)
            out_content.Metadata = make(map[string]interface{})
            out.Content = out_content
            receipt.SendResponse(receipt.Sockets.IOPub_socket, out)
        }
    } else {
        content["status"] = "error"
        content["ename"] = "ERROR"
        content["evalue"] = err.Error()
        content["traceback"] = []string{err.Error()}
        errormsg := NewMsg("pyerr", receipt.Msg)
        errormsg.Content = ErrMsg{"Error", err.Error(), []string{err.Error()}}
        receipt.SendResponse(receipt.Sockets.IOPub_socket, errormsg)
    }
    reply.Content = content
    receipt.SendResponse(receipt.Sockets.Shell_socket, reply)
    idle := NewMsg("status", receipt.Msg)
    idle.Content = KernelStatus{"idle"}
    receipt.SendResponse(receipt.Sockets.IOPub_socket, idle)
}

// RunKernel is the main entry point to start the kernel. This is what is called by the
// igo executable.
func RunKernel(connection_file string, logwriter io.Writer) {
    logger = log.New(logwriter, "igopkg ", log.LstdFlags)
    World = eval.NewWorld()
    fset = token.NewFileSet()
    var conn_info ConnectionInfo
    bs, err := ioutil.ReadFile(connection_file)
    if err != nil {
        log.Fatalln(err)
    }
    err = json.Unmarshal(bs, &conn_info)
    if err != nil {
        log.Fatalln(err)
    }
    logger.Printf("%+v\n", conn_info)
    sockets := PrepareSockets(conn_info)

    pi := zmq.PollItems{
        zmq.PollItem{Socket: sockets.Shell_socket, Events: zmq.POLLIN},
        zmq.PollItem{Socket: sockets.Stdin_socket, Events: zmq.POLLIN},
    }
    var msgparts [][]byte
    // Message receiving loop:
    for {
        _, err = zmq.Poll(pi, -1)
        if err != nil {
            log.Fatalln(err)
        }
        switch {
        case pi[0].REvents&zmq.POLLIN != 0:
            msgparts, _ = pi[0].Socket.RecvMultipart(0)
            msg, ids, err := WireMsgToComposedMsg(msgparts, sockets.Key)
            if err != nil {
                fmt.Println(err)
                return
            }
            HandleShellMsg(MsgReceipt{msg, ids, sockets})
        case pi[1].REvents&zmq.POLLIN != 0:
            pi[1].Socket.RecvMultipart(0)
        }
    }
}
<commit_msg>Listen on control socket, so we see shutdown request<commit_after>// Package igo implements the machinery necessary to run a Go kernel for IPython.
// It should be installed with an "igo" command to launch the kernel.
package igo

import (
    "fmt"
    "log"
    "io"
    "io/ioutil"
    "os"
    "encoding/json"
    "encoding/hex"
    "crypto/sha256"
    "crypto/hmac"
    zmq "github.com/alecthomas/gozmq"
    uuid "github.com/nu7hatch/gouuid"
    "go/token"
    "github.com/sbinet/go-eval/pkg/eval"

)

var logger *log.Logger

type MsgHeader struct  {
    Msg_id string `json:"msg_id"`
    Username string `json:"username"`
    Session string `json:"session"`
    Msg_type string `json:"msg_type"`
}

// ComposedMsg represents an entire message in a high-level structure.
type ComposedMsg struct {
    Header MsgHeader
    Parent_header MsgHeader
    Metadata map[string]interface{}
    Content interface{}
}

// ConnectionInfo stores the contents of the kernel connection file created by IPython.
type ConnectionInfo struct {
    Signature_scheme string
    Transport string
    Stdin_port int
    Control_port int
    IOPub_port int
    HB_port int
    Shell_port int
    Key string
    IP string
}

// SocketGroup holds the sockets the kernel needs to communicate with the kernel, and
// the key for message signing.
type SocketGroup struct {
    Shell_socket *zmq.Socket
    Control_socket *zmq.Socket
    Stdin_socket *zmq.Socket
    IOPub_socket *zmq.Socket
    Key []byte
}

// PrepareSockets sets up the ZMQ sockets through which the kernel will communicate.
func PrepareSockets(conn_info ConnectionInfo) (sg SocketGroup) {
    context, _ := zmq.NewContext()
    sg.Shell_socket, _ = context.NewSocket(zmq.ROUTER)
    sg.Control_socket, _ = context.NewSocket(zmq.ROUTER)
    sg.Stdin_socket, _ = context.NewSocket(zmq.ROUTER)
    sg.IOPub_socket, _ = context.NewSocket(zmq.PUB)

    address := fmt.Sprintf("%v://%v:%%v", conn_info.Transport, conn_info.IP)

    sg.Shell_socket.Bind(fmt.Sprintf(address, conn_info.Shell_port))
    sg.Control_socket.Bind(fmt.Sprintf(address, conn_info.Control_port))
    sg.Stdin_socket.Bind(fmt.Sprintf(address, conn_info.Stdin_port))
    sg.IOPub_socket.Bind(fmt.Sprintf(address, conn_info.IOPub_port))

    // Message signing key
    sg.Key = []byte(conn_info.Key)

    // Start the heartbeat device
    HB_socket, _ := context.NewSocket(zmq.REP)
    HB_socket.Bind(fmt.Sprintf(address, conn_info.HB_port))
    go zmq.Device(zmq.FORWARDER, HB_socket, HB_socket)
    return
}

// InvalidSignatureError is returned when the signature on a received message does not
// validate.
type InvalidSignatureError struct {}
func (e *InvalidSignatureError) Error() string {
    return "A message had an invalid signature"
}

// WireMsgToComposedMsg translates a multipart ZMQ messages received from a socket into
// a ComposedMsg struct and a slice of return identities. This includes verifying the
// message signature.
func WireMsgToComposedMsg(msgparts [][]byte, signkey []byte) (msg ComposedMsg,
                            identities [][]byte, err error) {
    i := 0
    for string(msgparts[i]) != "<IDS|MSG>" {
        i++
    }
    identities = msgparts[:i]
    // msgparts[i] is the delimiter

    // Validate signature
    if len(signkey) != 0 {
        mac := hmac.New(sha256.New, signkey)
        for _, msgpart := range msgparts[i+2:i+6] {
            mac.Write(msgpart)
        }
        signature := make([]byte, hex.DecodedLen(len(msgparts[i+1])))
        hex.Decode(signature, msgparts[i+1])
        if !hmac.Equal(mac.Sum(nil), signature) {
            return msg, nil, &InvalidSignatureError{}
        }
    }
    json.Unmarshal(msgparts[i+2], &msg.Header)
    json.Unmarshal(msgparts[i+3], &msg.Parent_header)
    json.Unmarshal(msgparts[i+4], &msg.Metadata)
    json.Unmarshal(msgparts[i+5], &msg.Content)
    return
}

// ToWireMsg translates a ComposedMsg into a multipart ZMQ message ready to send, and
// signs it. This does not add the return identities or the delimiter.
func (msg ComposedMsg) ToWireMsg(signkey []byte) (msgparts [][]byte) {
    msgparts = make([][]byte, 5)
    header, _ := json.Marshal(msg.Header)
    msgparts[1] = header
    parent_header, _ := json.Marshal(msg.Parent_header)
    msgparts[2] = parent_header
    if msg.Metadata == nil {
        msg.Metadata = make(map[string]interface{})
    }
    metadata, _ := json.Marshal(msg.Metadata)
    msgparts[3] = metadata
    content, _ := json.Marshal(msg.Content)
    msgparts[4] = content

    // Sign the message
    if len(signkey) != 0 {
        mac := hmac.New(sha256.New, signkey)
        for _, msgpart := range msgparts[1:] {
            mac.Write(msgpart)
        }
        msgparts[0] = make([]byte, hex.EncodedLen(mac.Size()))
        hex.Encode(msgparts[0], mac.Sum(nil))
    }
    return
}

// MsgReceipt represents a received message, its return identities, and the sockets for
// communication.
type MsgReceipt struct {
    Msg ComposedMsg
    Identities [][]byte
    Sockets SocketGroup
}

// SendResponse sends a message back to return identites of the received message.
func (receipt *MsgReceipt) SendResponse(socket *zmq.Socket, msg ComposedMsg) {
    socket.SendMultipart(receipt.Identities, zmq.SNDMORE)
    socket.Send([]byte("<IDS|MSG>"), zmq.SNDMORE)
    socket.SendMultipart(msg.ToWireMsg(receipt.Sockets.Key), 0)
    logger.Println("<--", msg.Header.Msg_type)
    logger.Printf("%+v\n", msg.Content)
}

// HandleShellMsg responds to a message on the shell ROUTER socket.
func HandleShellMsg(receipt MsgReceipt) {
    switch receipt.Msg.Header.Msg_type {
        case "kernel_info_request":
            SendKernelInfo(receipt)
        case "execute_request":
            HandleExecuteRequest(receipt)
        case "shutdown_request":
            HandleShutdownRequest(receipt)
        default: logger.Println("Unhandled shell message:", receipt.Msg.Header.Msg_type)
    }
}

// NewMsg creates a new ComposedMsg to respond to a parent message. This includes setting
// up its headers.
func NewMsg(msg_type string, parent ComposedMsg) (msg ComposedMsg) {
    msg.Parent_header = parent.Header
    msg.Header.Session = parent.Header.Session
    msg.Header.Username = parent.Header.Username
    msg.Header.Msg_type = msg_type
    u, _ := uuid.NewV4()
    msg.Header.Msg_id = u.String()
    return
}

// KernelInfo holds information about the igo kernel, for kernel_info_reply messages.
type KernelInfo struct {
    Protocol_version []int `json:"protocol_version"`
    Language string `json:"language"`
}

// KernelStatus holds a kernel state, for status broadcast messages.
type KernelStatus struct {
    ExecutionState string `json:"execution_state"`
}

//SendKernelInfo sends a kernel_info_reply message.
func SendKernelInfo(receipt MsgReceipt) {
    reply := NewMsg("kernel_info_reply", receipt.Msg)
    reply.Content = KernelInfo{[]int{4, 0}, "go"}
    receipt.SendResponse(receipt.Sockets.Shell_socket, reply)
}

type ShutdownReply struct {
    Restart bool `json:"restart"`
}

func HandleShutdownRequest(receipt MsgReceipt) {
    reply := NewMsg("shutdown_reply", receipt.Msg)
    content := receipt.Msg.Content.(map[string]interface{})
    restart := content["restart"].(bool)
    reply.Content = ShutdownReply{restart}
    receipt.SendResponse(receipt.Sockets.Shell_socket, reply)
    logger.Println("Shutting down in response to shutdown_request")
    os.Exit(0)
}

// World holds the user namespace for the REPL.
var World *eval.World
var fset *token.FileSet
// ExecCounter is incremented each time we run user code.
var ExecCounter int = 0

// RunCode runs the given user code, returning the expression value and/or an error.
func RunCode(text string) (val interface{}, err error) {
    var code eval.Code
    code, err = World.Compile(fset, text)
    if err != nil {
        return nil, err
    }
    val, err = code.Run()
    return
}

// OutputMsg holds the data for a pyout message.
type OutputMsg struct {
    Execcount int `json:"execution_count"`
    Data map[string]string `json:"data"`
    Metadata map[string]interface{} `json:"metadata"`
}

type ErrMsg struct {
    EName string `json:"ename"`
    EValue string `json:"evalue"`
    Traceback []string `json:"traceback"`
}

// HandleExecuteRequest runs code from an execute_request method, and sends the various
// reply messages.
func HandleExecuteRequest(receipt MsgReceipt) {
    reply := NewMsg("execute_reply", receipt.Msg)
    content := make(map[string]interface{})
    reqcontent := receipt.Msg.Content.(map[string]interface{})
    code := reqcontent["code"].(string)
    silent := reqcontent["silent"].(bool)
    if !silent {
        ExecCounter++
    }
    content["execution_count"] = ExecCounter
    val, err := RunCode(code)
    if err == nil {
        content["status"] = "ok"
        content["payload"] = make([]map[string]interface{}, 0)
        content["user_variables"] = make(map[string]string)
        content["user_expressions"] = make(map[string]string)
        if (val != nil) && !silent {
            var out_content OutputMsg
            out := NewMsg("pyout", receipt.Msg)
            out_content.Execcount = ExecCounter
            out_content.Data = make(map[string]string)
            out_content.Data["text/plain"] = fmt.Sprint(val)
            out_content.Metadata = make(map[string]interface{})
            out.Content = out_content
            receipt.SendResponse(receipt.Sockets.IOPub_socket, out)
        }
    } else {
        content["status"] = "error"
        content["ename"] = "ERROR"
        content["evalue"] = err.Error()
        content["traceback"] = []string{err.Error()}
        errormsg := NewMsg("pyerr", receipt.Msg)
        errormsg.Content = ErrMsg{"Error", err.Error(), []string{err.Error()}}
        receipt.SendResponse(receipt.Sockets.IOPub_socket, errormsg)
    }
    reply.Content = content
    receipt.SendResponse(receipt.Sockets.Shell_socket, reply)
    idle := NewMsg("status", receipt.Msg)
    idle.Content = KernelStatus{"idle"}
    receipt.SendResponse(receipt.Sockets.IOPub_socket, idle)
}

// RunKernel is the main entry point to start the kernel. This is what is called by the
// igo executable.
func RunKernel(connection_file string, logwriter io.Writer) {
    logger = log.New(logwriter, "igopkg ", log.LstdFlags)
    World = eval.NewWorld()
    fset = token.NewFileSet()
    var conn_info ConnectionInfo
    bs, err := ioutil.ReadFile(connection_file)
    if err != nil {
        log.Fatalln(err)
    }
    err = json.Unmarshal(bs, &conn_info)
    if err != nil {
        log.Fatalln(err)
    }
    logger.Printf("%+v\n", conn_info)
    sockets := PrepareSockets(conn_info)

    pi := zmq.PollItems{
        zmq.PollItem{Socket: sockets.Shell_socket, Events: zmq.POLLIN},
        zmq.PollItem{Socket: sockets.Stdin_socket, Events: zmq.POLLIN},
        zmq.PollItem{Socket: sockets.Control_socket, Events: zmq.POLLIN},
    }
    var msgparts [][]byte
    // Message receiving loop:
    for {
        _, err = zmq.Poll(pi, -1)
        if err != nil {
            log.Fatalln(err)
        }
        switch {
        case pi[0].REvents&zmq.POLLIN != 0:  // shell socket
            msgparts, _ = pi[0].Socket.RecvMultipart(0)
            msg, ids, err := WireMsgToComposedMsg(msgparts, sockets.Key)
            if err != nil {
                fmt.Println(err)
                return
            }
            HandleShellMsg(MsgReceipt{msg, ids, sockets})
        case pi[1].REvents&zmq.POLLIN != 0:  // stdin socket - not implemented.
            pi[1].Socket.RecvMultipart(0)
        case pi[2].REvents&zmq.POLLIN != 0:  // control socket - treat like shell socket.
            msgparts, _ = pi[2].Socket.RecvMultipart(0)
            msg, ids, err := WireMsgToComposedMsg(msgparts, sockets.Key)
            if err != nil {
                fmt.Println(err)
                return
            }
            HandleShellMsg(MsgReceipt{msg, ids, sockets})
        }
    }
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/pmylund/go-cache"
)

const (
	dataFileExt = ".data"
)

type Sample struct {
	ts int64
	v  float64
}

type perfDB struct {
	baseDir string
	mu      sync.Mutex
}

var timestampCache *cache.Cache

func newPerfDB(baseDir string) (*perfDB, error) {
	if err := os.MkdirAll(baseDir, 0755); err != nil {
		logger.Critical("Failed to initialize datastore: %s", err)
		return nil, err
	}
	timestampCache = cache.New(cache.NoExpiration, cache.NoExpiration)
	return &perfDB{baseDir, sync.Mutex{}}, nil
}

func (pdb *perfDB) getDirPath(dbname string) string {
	return filepath.Join(pdb.baseDir, dbname)
}

func (pdb *perfDB) getFilePath(dbname, metric string) string {
	dataDir := pdb.getDirPath(dbname)

	return filepath.Join(dataDir, metric+dataFileExt)
}

func (pdb *perfDB) checkDbExists(dbname string) error {
	dataDir := pdb.getDirPath(dbname)

	_, err := os.Stat(dataDir)
	return err
}

func (pdb *perfDB) checkMetricExists(dbname string, metric string) error {
	dataFile := pdb.getFilePath(dbname, metric)

	_, err := os.Stat(dataFile)
	return err
}

func parseRecord(record string) (Sample, error) {
	fields := strings.Fields(record)

	ts, err := strconv.ParseInt(fields[0], 10, 64)
	if err != nil {
		return Sample{}, err
	}

	value, err := strconv.ParseFloat(fields[1], 64)
	if err != nil {
		return Sample{}, err
	}
	return Sample{ts, value}, nil
}

func readTimestamp(dataFile string) (int64, error) {
	if cachedData, found := timestampCache.Get(dataFile); found {
		return cachedData.(int64), nil
	}

	record, err := ioutil.ReadFile(dataFile)
	if err != nil {
		return 0, err
	}

	return strconv.ParseInt(string(record), 10, 64)
}

func storeTimestamp(dataFile string, timestamp int64) error {
	file, err := os.OpenFile(dataFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	if _, err = fmt.Fprint(file, timestamp); err != nil {
		return err
	}
	timestampCache.Set(dataFile, timestamp, time.Minute)
	return nil
}

func storeSample(dataFile string, sample Sample) error {
	file, err := os.OpenFile(dataFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = fmt.Fprintf(file, "%d %v\n", sample.ts, sample.v)
	return err
}

func initStore(dataFile string, sample Sample) error {
	if err := storeTimestamp(dataFile+".1", sample.ts); err != nil {
		return err
	}
	if err := storeTimestamp(dataFile+".n", sample.ts); err != nil {
		return err
	}

	sample.ts = 0
	return storeSample(dataFile, sample)
}

func appendSample(dataFile string, sample Sample) error {
	ts, err := readTimestamp(dataFile + ".n")
	if err != nil {
		return err
	}
	if err := storeTimestamp(dataFile+".n", sample.ts); err != nil {
		return err
	}
	sample.ts -= ts
	return storeSample(dataFile, sample)
}

func (pdb *perfDB) addSample(dbname, metric string, sample Sample) error {
	dataDir := pdb.getDirPath(dbname)
	if err := os.MkdirAll(dataDir, 0775); err != nil {
		return err
	}
	dataFile := pdb.getFilePath(dbname, metric)

	pdb.mu.Lock()
	defer pdb.mu.Unlock()

	_, err := os.Stat(dataFile + ".n")
	if err == nil { // Append delta
		return appendSample(dataFile, sample)
	} else if os.IsNotExist(err) {
		return initStore(dataFile, sample)
	}
	return err
}

func (pdb *perfDB) listDatabases() ([]string, error) {
	files, err := ioutil.ReadDir(pdb.baseDir)
	if err != nil {
		return nil, err
	}

	databases := []string{}
	for _, f := range files {
		databases = append(databases, f.Name())
	}
	return databases, nil
}

func (pdb *perfDB) listMetrics(dbname string) []string {
	dataDir := pdb.getDirPath(dbname)

	matches, _ := filepath.Glob(dataDir + "/*.data") // Glob ignores file system errors

	metrics := []string{}
	for _, match := range matches {
		name := strings.TrimPrefix(match, dataDir+"/")
		name = strings.TrimRight(name, dataFileExt)
		metrics = append(metrics, name)
	}
	return metrics
}

const bufferSize = 1000

func readDeltas(fileName string, done <-chan struct{}) (<-chan string, <-chan error) {
	samples := make(chan string, bufferSize)
	errc := make(chan error, 1)

	go func() {
		defer close(samples)
		defer close(errc)

		file, err := os.Open(fileName)
		if err != nil {
			errc <- err
			return
		}
		defer file.Close()

		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			select {
			case samples <- scanner.Text():
			case <-done:
				return
			}
		}

		if err := scanner.Err(); err != nil {
			errc <- err
		}
	}()
	return samples, errc
}

func parseSamples(records <-chan string, ts int64) (<-chan Sample, <-chan error) {
	samples := make(chan Sample, bufferSize)
	errc := make(chan error, 1)

	go func() {
		defer close(samples)
		defer close(errc)

		for record := range records {
			sample, err := parseRecord(record)
			if err != nil {
				errc <- err
			} else {
				sample.ts += ts
				ts = sample.ts
				samples <- sample
			}
		}
	}()
	return samples, errc
}

func mergeErrors(errcs ...<-chan error) error {
	for _, errc := range errcs {
		if err := <-errc; err != nil {
			return err
		}
	}
	return nil
}

func (pdb *perfDB) getRawValues(dbname, metric string) ([][]interface{}, error) {
	dataFile := pdb.getFilePath(dbname, metric)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	done := make(chan struct{}, 1)
	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	values := [][]interface{}{}
	for sample := range parsedSamples {
		values = append(values, []interface{}{sample.ts, sample.v})
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}
	return values, nil
}

func (pdb *perfDB) getSummary(dbname, metric string) (map[string]interface{}, error) {
	var summary map[string]interface{}

	dataFile := pdb.getFilePath(dbname, metric)

	done := make(chan struct{}, 1)
	defer close(done)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	values := []float64{}
	sum := 0.0
	for sample := range parsedSamples {
		sum += sample.v
		values = append(values, sample.v)
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}

	count := len(values)
	sort.Float64s(values)

	summary = map[string]interface{}{
		"max":   values[count-1],
		"min":   values[0],
		"count": count,
		"avg":   sum / float64(count),
	}

	for _, percentile := range []float64{0.5, 0.8, 0.9, 0.95, 0.99, 0.999} {
		var pIdx int
		if count > 1 {
			pIdx = int(float64(count)*percentile) - 1
		}
		p := fmt.Sprintf("p%v", percentile*100)
		summary[p] = values[pIdx]
	}

	return summary, nil
}

func (pdb *perfDB) getHeatMap(dbname, metric string) (*heatMap, error) {
	dataFile := pdb.getFilePath(dbname, metric)

	hm := newHeatMap()
	hm.MinTS = int64(^uint64(0) >> 1)

	done := make(chan struct{}, 1)
	defer close(done)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	samples := []Sample{}
	for sample := range parsedSamples {
		hm.MaxValue = math.Max(hm.MaxValue, sample.v)
		if sample.ts < hm.MinTS {
			hm.MinTS = sample.ts
		} else if sample.ts > hm.MaxTS {
			hm.MaxTS = sample.ts
		}
		samples = append(samples, sample)
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}

	for _, sample := range samples {
		x := math.Floor(heatMapWidth * float64(sample.ts-hm.MinTS) / float64(hm.MaxTS-hm.MinTS))
		y := math.Floor(heatMapHeight * sample.v / hm.MaxValue)
		if x == heatMapWidth {
			x--
		}
		if y == heatMapHeight {
			y--
		}
		hm.Map[int(y)][int(x)]++
		if hm.Map[int(y)][int(x)] > hm.maxDensity {
			hm.maxDensity = hm.Map[int(y)][int(x)]
		}
	}
	return hm, nil
}
<commit_msg>Remove redundant comment<commit_after>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/pmylund/go-cache"
)

const (
	dataFileExt = ".data"
)

type Sample struct {
	ts int64
	v  float64
}

type perfDB struct {
	baseDir string
	mu      sync.Mutex
}

var timestampCache *cache.Cache

func newPerfDB(baseDir string) (*perfDB, error) {
	if err := os.MkdirAll(baseDir, 0755); err != nil {
		logger.Critical("Failed to initialize datastore: %s", err)
		return nil, err
	}
	timestampCache = cache.New(cache.NoExpiration, cache.NoExpiration)
	return &perfDB{baseDir, sync.Mutex{}}, nil
}

func (pdb *perfDB) getDirPath(dbname string) string {
	return filepath.Join(pdb.baseDir, dbname)
}

func (pdb *perfDB) getFilePath(dbname, metric string) string {
	dataDir := pdb.getDirPath(dbname)

	return filepath.Join(dataDir, metric+dataFileExt)
}

func (pdb *perfDB) checkDbExists(dbname string) error {
	dataDir := pdb.getDirPath(dbname)

	_, err := os.Stat(dataDir)
	return err
}

func (pdb *perfDB) checkMetricExists(dbname string, metric string) error {
	dataFile := pdb.getFilePath(dbname, metric)

	_, err := os.Stat(dataFile)
	return err
}

func parseRecord(record string) (Sample, error) {
	fields := strings.Fields(record)

	ts, err := strconv.ParseInt(fields[0], 10, 64)
	if err != nil {
		return Sample{}, err
	}

	value, err := strconv.ParseFloat(fields[1], 64)
	if err != nil {
		return Sample{}, err
	}
	return Sample{ts, value}, nil
}

func readTimestamp(dataFile string) (int64, error) {
	if cachedData, found := timestampCache.Get(dataFile); found {
		return cachedData.(int64), nil
	}

	record, err := ioutil.ReadFile(dataFile)
	if err != nil {
		return 0, err
	}

	return strconv.ParseInt(string(record), 10, 64)
}

func storeTimestamp(dataFile string, timestamp int64) error {
	file, err := os.OpenFile(dataFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	if _, err = fmt.Fprint(file, timestamp); err != nil {
		return err
	}
	timestampCache.Set(dataFile, timestamp, time.Minute)
	return nil
}

func storeSample(dataFile string, sample Sample) error {
	file, err := os.OpenFile(dataFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = fmt.Fprintf(file, "%d %v\n", sample.ts, sample.v)
	return err
}

func initStore(dataFile string, sample Sample) error {
	if err := storeTimestamp(dataFile+".1", sample.ts); err != nil {
		return err
	}
	if err := storeTimestamp(dataFile+".n", sample.ts); err != nil {
		return err
	}

	sample.ts = 0
	return storeSample(dataFile, sample)
}

func appendSample(dataFile string, sample Sample) error {
	ts, err := readTimestamp(dataFile + ".n")
	if err != nil {
		return err
	}
	if err := storeTimestamp(dataFile+".n", sample.ts); err != nil {
		return err
	}
	sample.ts -= ts
	return storeSample(dataFile, sample)
}

func (pdb *perfDB) addSample(dbname, metric string, sample Sample) error {
	dataDir := pdb.getDirPath(dbname)
	if err := os.MkdirAll(dataDir, 0775); err != nil {
		return err
	}
	dataFile := pdb.getFilePath(dbname, metric)

	pdb.mu.Lock()
	defer pdb.mu.Unlock()

	_, err := os.Stat(dataFile + ".n")
	if err == nil {
		return appendSample(dataFile, sample)
	} else if os.IsNotExist(err) {
		return initStore(dataFile, sample)
	}
	return err
}

func (pdb *perfDB) listDatabases() ([]string, error) {
	files, err := ioutil.ReadDir(pdb.baseDir)
	if err != nil {
		return nil, err
	}

	databases := []string{}
	for _, f := range files {
		databases = append(databases, f.Name())
	}
	return databases, nil
}

func (pdb *perfDB) listMetrics(dbname string) []string {
	dataDir := pdb.getDirPath(dbname)

	matches, _ := filepath.Glob(dataDir + "/*.data") // Glob ignores file system errors

	metrics := []string{}
	for _, match := range matches {
		name := strings.TrimPrefix(match, dataDir+"/")
		name = strings.TrimRight(name, dataFileExt)
		metrics = append(metrics, name)
	}
	return metrics
}

const bufferSize = 1000

func readDeltas(fileName string, done <-chan struct{}) (<-chan string, <-chan error) {
	samples := make(chan string, bufferSize)
	errc := make(chan error, 1)

	go func() {
		defer close(samples)
		defer close(errc)

		file, err := os.Open(fileName)
		if err != nil {
			errc <- err
			return
		}
		defer file.Close()

		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			select {
			case samples <- scanner.Text():
			case <-done:
				return
			}
		}

		if err := scanner.Err(); err != nil {
			errc <- err
		}
	}()
	return samples, errc
}

func parseSamples(records <-chan string, ts int64) (<-chan Sample, <-chan error) {
	samples := make(chan Sample, bufferSize)
	errc := make(chan error, 1)

	go func() {
		defer close(samples)
		defer close(errc)

		for record := range records {
			sample, err := parseRecord(record)
			if err != nil {
				errc <- err
			} else {
				sample.ts += ts
				ts = sample.ts
				samples <- sample
			}
		}
	}()
	return samples, errc
}

func mergeErrors(errcs ...<-chan error) error {
	for _, errc := range errcs {
		if err := <-errc; err != nil {
			return err
		}
	}
	return nil
}

func (pdb *perfDB) getRawValues(dbname, metric string) ([][]interface{}, error) {
	dataFile := pdb.getFilePath(dbname, metric)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	done := make(chan struct{}, 1)
	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	values := [][]interface{}{}
	for sample := range parsedSamples {
		values = append(values, []interface{}{sample.ts, sample.v})
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}
	return values, nil
}

func (pdb *perfDB) getSummary(dbname, metric string) (map[string]interface{}, error) {
	var summary map[string]interface{}

	dataFile := pdb.getFilePath(dbname, metric)

	done := make(chan struct{}, 1)
	defer close(done)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	values := []float64{}
	sum := 0.0
	for sample := range parsedSamples {
		sum += sample.v
		values = append(values, sample.v)
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}

	count := len(values)
	sort.Float64s(values)

	summary = map[string]interface{}{
		"max":   values[count-1],
		"min":   values[0],
		"count": count,
		"avg":   sum / float64(count),
	}

	for _, percentile := range []float64{0.5, 0.8, 0.9, 0.95, 0.99, 0.999} {
		var pIdx int
		if count > 1 {
			pIdx = int(float64(count)*percentile) - 1
		}
		p := fmt.Sprintf("p%v", percentile*100)
		summary[p] = values[pIdx]
	}

	return summary, nil
}

func (pdb *perfDB) getHeatMap(dbname, metric string) (*heatMap, error) {
	dataFile := pdb.getFilePath(dbname, metric)

	hm := newHeatMap()
	hm.MinTS = int64(^uint64(0) >> 1)

	done := make(chan struct{}, 1)
	defer close(done)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	samples := []Sample{}
	for sample := range parsedSamples {
		hm.MaxValue = math.Max(hm.MaxValue, sample.v)
		if sample.ts < hm.MinTS {
			hm.MinTS = sample.ts
		} else if sample.ts > hm.MaxTS {
			hm.MaxTS = sample.ts
		}
		samples = append(samples, sample)
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}

	for _, sample := range samples {
		x := math.Floor(heatMapWidth * float64(sample.ts-hm.MinTS) / float64(hm.MaxTS-hm.MinTS))
		y := math.Floor(heatMapHeight * sample.v / hm.MaxValue)
		if x == heatMapWidth {
			x--
		}
		if y == heatMapHeight {
			y--
		}
		hm.Map[int(y)][int(x)]++
		if hm.Map[int(y)][int(x)] > hm.maxDensity {
			hm.maxDensity = hm.Map[int(y)][int(x)]
		}
	}
	return hm, nil
}
<|endoftext|>


<commit_before>package images

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"sort"
	"strings"
	"text/tabwriter"
	"time"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/service/ec2"
	"github.com/fatih/color"
)

type AwsImages struct {
	svc *ec2.EC2

	images []*ec2.Image
}

func NewAwsImages(region string) *AwsImages {
	return &AwsImages{
		svc: ec2.New(&aws.Config{Region: region}),
	}
}

func (a *AwsImages) Fetch() error {
	input := &ec2.DescribeImagesInput{
		Owners: stringSlice("self"),
	}

	resp, err := a.svc.DescribeImages(input)
	if err != nil {
		return err
	}

	a.images = resp.Images

	// sort from oldest to newest
	if len(a.images) > 1 {
		sort.Sort(a)
	}

	return nil
}

func (a *AwsImages) Print() {
	if len(a.images) == 0 {
		fmt.Println("no images found")
		return
	}

	color.Green("AWS: Region: %s (%d images)\n\n", a.svc.Config.Region, len(a.images))

	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 10, 8, 0, '\t', 0)
	defer w.Flush()

	fmt.Fprintln(w, "    Name\tID\tState\tTags")

	for i, image := range a.images {
		tags := make([]string, len(image.Tags))
		for i, tag := range image.Tags {
			tags[i] = *tag.Key + ":" + *tag.Value
		}

		fmt.Fprintf(w, "[%d] %s\t%s\t%s\t%+v\n",
			i, *image.Name, *image.ImageID, *image.State, tags)
	}
}

func (a *AwsImages) Help(command string) string {
	switch command {
	case "modify":
		return `Usage: images modify --provider aws [options]

  Modify AMI properties.

Options:

  -create-tags                  Create or override tags
  -delete-tags                  Delete tags
`
	case "list":
	}

	return "no help found for command " + command
}

func (a *AwsImages) Modify(args []string) error {
	var (
		createTags string
		deleteTags string
		imageIds   string
		dryRun     bool
	)

	flagSet := flag.NewFlagSet("modify", flag.ContinueOnError)
	flagSet.StringVar(&createTags, "create-tags", "", "Create  or override tags")
	flagSet.StringVar(&deleteTags, "delete-tags", "", "Delete tags")
	flagSet.StringVar(&imageIds, "image-ids", "", "Images to be used with actions")
	flagSet.BoolVar(&dryRun, "dry-run", false, "Don't run command, but show the action")
	flagSet.Usage = func() {
		helpMsg := `Usage: images modify --provider aws [options]

  Modify AMI properties.

Options:

  -image-ids   "ami-123,..."   Images to be used with below actions

  -create-tags "key=val,..."   Create or override tags
  -delete-tags "key,..."       Delete tags
  -dry-run                     Don't run command, but show the action
`
		fmt.Fprintf(os.Stderr, helpMsg)
	}

	flagSet.SetOutput(ioutil.Discard) // don't print anything without my permission
	if err := flagSet.Parse(args); err != nil {
		return nil // we don't return error, the usage will be printed instead
	}

	if len(args) == 0 {
		flagSet.Usage()
		return errors.New("no flags are passed")
	}

	if imageIds == "" {
		return errors.New("no images are passed with [--image-ids]")
	}

	fmt.Printf("imageIds = %+v\n", imageIds)
	fmt.Printf("createTags = %+v\n", createTags)
	fmt.Printf("deleteTags = %+v\n", deleteTags)

	if createTags != "" && deleteTags != "" {
		return errors.New("not allowed to be used together: [--create-tags,--delete-tags]")
	}

	if createTags != "" {
		keyVals := make(map[string]string, 0)
		for _, keyVal := range strings.Split(createTags, ",") {
			keys := strings.Split(keyVal, "=")
			if len(keys) != 2 {
				return fmt.Errorf("malformed value passed to --create-tags: %v", keys)
			}
			keyVals[keys[0]] = keys[1]
		}

		return a.AddTags(keyVals, dryRun, strings.Split(imageIds, ",")...)
	}

	if deleteTags != "" {
		a.DeleteTags(deleteTags, dryRun, strings.Split(imageIds, ",")...)
	}

	return nil
}

// Add tags adds or overwrites all tags for the specified images.
func (a *AwsImages) AddTags(tags map[string]string, dryRun bool, images ...string) error {
	ec2Tags := make([]*ec2.Tag, 0)
	for key, val := range tags {
		ec2Tags = append(ec2Tags, &ec2.Tag{
			Key:   aws.String(key),
			Value: aws.String(val),
		})
	}

	params := &ec2.CreateTagsInput{
		Resources: stringSlice(images...),
		Tags:      ec2Tags,
		DryRun:    aws.Boolean(dryRun),
	}

	_, err := a.svc.CreateTags(params)
	return err
}

// DeleteTags deletes the given tags for the given images. Tags is in the form
// of "key1=val1,key2=val2,key3,key4="
func (a *AwsImages) DeleteTags(tags string, dryRun bool, images ...string) error {
	ec2Tags := make([]*ec2.Tag, 0)

	for _, keyVal := range strings.Split(tags, ",") {
		keys := strings.Split(keyVal, "=")
		ec2Tag := &ec2.Tag{
			Key: aws.String(keys[0]), // index 0 is always available
		}

		// means value is not omitted. We don't care if value is empty or not,
		// the AWS API takes care of it.
		if len(keys) == 2 {
			ec2Tag.Value = aws.String(keys[1])
		}

		ec2Tags = append(ec2Tags, ec2Tag)
	}

	params := &ec2.DeleteTagsInput{
		Resources: stringSlice(images...),
		Tags:      ec2Tags,
		DryRun:    aws.Boolean(dryRun),
	}

	_, err := a.svc.DeleteTags(params)
	return err
}

//
// Sort interface
//

func (a *AwsImages) Len() int {
	return len(a.images)
}

func (a *AwsImages) Less(i, j int) bool {
	it, err := time.Parse(time.RFC3339, *a.images[i].CreationDate)
	if err != nil {
		log.Println("aws: sorting err: ", err)
	}

	jt, err := time.Parse(time.RFC3339, *a.images[j].CreationDate)
	if err != nil {
		log.Println("aws: sorting err: ", err)
	}

	return it.Before(jt)
}

func (a *AwsImages) Swap(i, j int) {
	a.images[i], a.images[j] = a.images[j], a.images[i]
}

//
// Utils
//

func stringSlice(vals ...string) []*string {
	a := make([]*string, len(vals))

	for i, v := range vals {
		a[i] = aws.String(v)
	}

	return a
}
<commit_msg>images/aws: finalize delete-tags and create-tags flags<commit_after>package images

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"sort"
	"strings"
	"text/tabwriter"
	"time"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/service/ec2"
	"github.com/fatih/color"
)

type AwsImages struct {
	svc *ec2.EC2

	images []*ec2.Image
}

func NewAwsImages(region string) *AwsImages {
	return &AwsImages{
		svc: ec2.New(&aws.Config{Region: region}),
	}
}

func (a *AwsImages) Fetch() error {
	input := &ec2.DescribeImagesInput{
		Owners: stringSlice("self"),
	}

	resp, err := a.svc.DescribeImages(input)
	if err != nil {
		return err
	}

	a.images = resp.Images

	// sort from oldest to newest
	if len(a.images) > 1 {
		sort.Sort(a)
	}

	return nil
}

func (a *AwsImages) Print() {
	if len(a.images) == 0 {
		fmt.Println("no images found")
		return
	}

	color.Green("AWS: Region: %s (%d images)\n\n", a.svc.Config.Region, len(a.images))

	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 10, 8, 0, '\t', 0)
	defer w.Flush()

	fmt.Fprintln(w, "    Name\tID\tState\tTags")

	for i, image := range a.images {
		tags := make([]string, len(image.Tags))
		for i, tag := range image.Tags {
			tags[i] = *tag.Key + ":" + *tag.Value
		}

		fmt.Fprintf(w, "[%d] %s\t%s\t%s\t%+v\n",
			i, *image.Name, *image.ImageID, *image.State, tags)
	}
}

func (a *AwsImages) Help(command string) string {
	switch command {
	case "modify":
		return `Usage: images modify --provider aws [options]

  Modify AMI properties.

Options:

  -create-tags                  Create or override tags
  -delete-tags                  Delete tags
`
	case "list":
	}

	return "no help found for command " + command
}

func (a *AwsImages) Modify(args []string) error {
	var (
		createTags string
		deleteTags string
		imageIds   string
		dryRun     bool
	)

	flagSet := flag.NewFlagSet("modify", flag.ContinueOnError)
	flagSet.StringVar(&createTags, "create-tags", "", "Create  or override tags")
	flagSet.StringVar(&deleteTags, "delete-tags", "", "Delete tags")
	flagSet.StringVar(&imageIds, "image-ids", "", "Images to be used with actions")
	flagSet.BoolVar(&dryRun, "dry-run", false, "Don't run command, but show the action")
	flagSet.Usage = func() {
		helpMsg := `Usage: images modify --provider aws [options]

  Modify AMI properties.

Options:

  -image-ids   "ami-123,..."   Images to be used with below actions

  -create-tags "key=val,..."   Create or override tags
  -delete-tags "key,..."       Delete tags
  -dry-run                     Don't run command, but show the action
`
		fmt.Fprintf(os.Stderr, helpMsg)
	}

	flagSet.SetOutput(ioutil.Discard) // don't print anything without my permission
	if err := flagSet.Parse(args); err != nil {
		return nil // we don't return error, the usage will be printed instead
	}

	if len(args) == 0 {
		flagSet.Usage()
		return errors.New("no flags are passed")
	}

	if imageIds == "" {
		return errors.New("no images are passed with [--image-ids]")
	}

	fmt.Printf("imageIds = %+v\n", imageIds)
	fmt.Printf("createTags = %+v\n", createTags)
	fmt.Printf("deleteTags = %+v\n", deleteTags)

	if createTags != "" && deleteTags != "" {
		return errors.New("not allowed to be used together: [--create-tags,--delete-tags]")
	}

	if createTags != "" {
		return a.CreateTags(createTags, dryRun, strings.Split(imageIds, ",")...)
	}

	if deleteTags != "" {
		return a.DeleteTags(deleteTags, dryRun, strings.Split(imageIds, ",")...)
	}

	return nil
}

// CreateTags adds or overwrites all tags for the specified images. Tags is in
// the form of "key1=val1,key2=val2,key3,key4=".
// One or more tags. The value parameter is required, but if you don't want the
// tag to have a value, specify the parameter with no value (i.e: "key3" or
// "key4=" both works)
func (a *AwsImages) CreateTags(tags string, dryRun bool, images ...string) error {
	ec2Tags := make([]*ec2.Tag, 0)

	for _, keyVal := range strings.Split(tags, ",") {
		keys := strings.Split(keyVal, "=")
		ec2Tag := &ec2.Tag{
			Key: aws.String(keys[0]), // index 0 is always available
		}

		// It's in the form "key4". The AWS API will create the key only if the
		// value is being passed as an empty string.
		if len(keys) == 1 {
			ec2Tag.Value = aws.String("")
		}

		if len(keys) == 2 {
			ec2Tag.Value = aws.String(keys[1])
		}

		ec2Tags = append(ec2Tags, ec2Tag)
	}

	params := &ec2.CreateTagsInput{
		Resources: stringSlice(images...),
		Tags:      ec2Tags,
		DryRun:    aws.Boolean(dryRun),
	}

	_, err := a.svc.CreateTags(params)
	return err
}

// DeleteTags deletes the given tags for the given images. Tags is in the form
// of "key1=val1,key2=val2,key3,key4="
// One or more tags to delete. If you omit the value parameter(i.e "key3"), we
// delete the tag regardless of its value. If you specify this parameter with
// an empty string (i.e: "key4=" as the value, we delete the key only if its
// value is an empty string.
func (a *AwsImages) DeleteTags(tags string, dryRun bool, images ...string) error {
	ec2Tags := make([]*ec2.Tag, 0)

	for _, keyVal := range strings.Split(tags, ",") {
		keys := strings.Split(keyVal, "=")
		ec2Tag := &ec2.Tag{
			Key: aws.String(keys[0]), // index 0 is always available
		}

		// means value is not omitted. We don't care if value is empty or not,
		// the AWS API takes care of it.
		if len(keys) == 2 {
			ec2Tag.Value = aws.String(keys[1])
		}

		ec2Tags = append(ec2Tags, ec2Tag)
	}

	params := &ec2.DeleteTagsInput{
		Resources: stringSlice(images...),
		Tags:      ec2Tags,
		DryRun:    aws.Boolean(dryRun),
	}

	_, err := a.svc.DeleteTags(params)
	return err
}

//
// Sort interface
//

func (a *AwsImages) Len() int {
	return len(a.images)
}

func (a *AwsImages) Less(i, j int) bool {
	it, err := time.Parse(time.RFC3339, *a.images[i].CreationDate)
	if err != nil {
		log.Println("aws: sorting err: ", err)
	}

	jt, err := time.Parse(time.RFC3339, *a.images[j].CreationDate)
	if err != nil {
		log.Println("aws: sorting err: ", err)
	}

	return it.Before(jt)
}

func (a *AwsImages) Swap(i, j int) {
	a.images[i], a.images[j] = a.images[j], a.images[i]
}

//
// Utils
//

func stringSlice(vals ...string) []*string {
	a := make([]*string, len(vals))

	for i, v := range vals {
		a[i] = aws.String(v)
	}

	return a
}
<|endoftext|>


<commit_before>package golymer

import (
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/gopherjs/gopherjs/js"
)

func consoleError(args ...interface{}) {
	js.Global.Get("console").Call("error", args...)
}

//CustomElement the interface to create the CustomElement
type CustomElement interface {
	ConnectedCallback()
	DisconnectedCallback()
	AttributeChangedCallback(attributeName string, oldValue interface{}, newValue interface{}, namespace string)
	AdoptedCallback(oldDocument, newDocument interface{})
}

type oneWayDataBinding struct {
	Str       string
	Attribute *js.Object
	Paths     []attrPath
}

func (db oneWayDataBinding) SetAttr(obj *js.Object) {
	value := db.Str
	for _, path := range db.Paths {
		fieldValue := path.Get(obj).String()
		value = strings.Replace(value, "[["+path.String()+"]]", fieldValue, -1)
	}
	if db.Attribute.Get("value") != js.Undefined {
		db.Attribute.Set("value", value) //if it's an attribute
		return
	}
	db.Attribute.Set("data", value) //if it's an text node
}

type twoWayDataBinding struct {
	Attribute        *js.Object
	Path             attrPath
	MutationObserver *js.Object
}

func (db twoWayDataBinding) SetAttr(obj *js.Object) {
	value := db.Path.Get(obj)
	/*
		if timeValue, ok := value.Interface().(time.Time); ok {
			formatedTime := timeValue.Format(time.UnixDate)
			print(formatedTime)
			db.Attribute.Set("value", formatedTime)
			return
		}
	*/
	if db.Attribute.Get("value").String() != value.String() {
		db.Attribute.Set("value", value)
	}
}

//Element wrapper for the HTML element
type Element struct {
	*js.Object
	ObjValue           reflect.Value //custom element struct type
	Template           string
	Children           map[string]*js.Object
	oneWayDataBindings map[string][]*oneWayDataBinding
	twoWayDataBindings map[string]*twoWayDataBinding
}

//ConnectedCallback ...
func (e *Element) ConnectedCallback() {
	e.Call("attachShadow", map[string]interface{}{"mode": "open"})
	e.Get("shadowRoot").Set("innerHTML", e.Template)
	e.Children = make(map[string]*js.Object)
	e.oneWayDataBindings = make(map[string][]*oneWayDataBinding)
	e.twoWayDataBindings = make(map[string]*twoWayDataBinding)
	e.scanElement(e.Get("shadowRoot"))
}

//DisconnectedCallback ...
func (e *Element) DisconnectedCallback() {
}

//AttributeChangedCallback ...
func (e *Element) AttributeChangedCallback(attributeName string, oldValue interface{}, newValue interface{}, namespace string) {
	//if attribute didn't change don't set the field
	if oldValue != newValue {
		e.Get("__internal_object__").Set(toExportedFieldName(attributeName), newValue)
	}
}

//AdoptedCallback ...
func (e *Element) AdoptedCallback(oldDocument, newDocument interface{}) {
}

func (e *Element) scanElement(element *js.Object) {
	//find data binded attributes
	if elementAttributes := element.Get("attributes"); elementAttributes != js.Undefined {
		for i := 0; i < elementAttributes.Length(); i++ {
			attribute := elementAttributes.Index(i)
			//collect children with id
			if attribute.Get("name").String() == "id" {
				e.Children[attribute.Get("value").String()] = element
				continue
			}
			e.addOneWay(attribute, attribute.Get("value").String())
			e.addTwoWay(attribute, attribute.Get("value").String())
			if attribute.Get("name").String()[:3] == "on-" {
				e.addEventListener(attribute)
			}
		}
	}
	//find textChild with data binded value
	childNodes := element.Get("childNodes")
	for i := 0; i < childNodes.Length(); i++ {
		child := childNodes.Index(i)
		if child.Get("nodeName").String() != "#text" {
			continue
		}
		e.addOneWay(child, child.Get("data").String())
	}
	//scan children
	children := element.Get("children")
	for i := 0; i < children.Length(); i++ {
		e.scanElement(children.Index(i))
	}
}

//addOneWay gets an js Attribute object or an textNode object and its text value
//than finds all data bindings and adds it to the oneWayDataBindings map
func (e *Element) addOneWay(obj *js.Object, value string) {
	var bindedPaths []attrPath
	for _, path := range oneWayFindAll(value) {
		bindedPaths = append(bindedPaths, newAttrPath(path))
	}
	for _, bindedPath := range bindedPaths {
		e.subpropertyProxySet(bindedPath)
		db := &oneWayDataBinding{Str: value, Attribute: obj, Paths: bindedPaths}
		e.oneWayDataBindings[bindedPath.String()] = append(e.oneWayDataBindings[bindedPath.String()], db)
		db.SetAttr(e.Object)
	}
}

//addTwoWay gets an js Attribute object or an textNode object and its text value
//than finds all data bindings and adds it to the twoWayDataBindings map
func (e *Element) addTwoWay(obj *js.Object, value string) {
	//twoWayDataBinding, obj is attribute DOM object and value is in {{}}
	if obj.Get("value") == js.Undefined || value[:2] != "{{" || value[len(value)-2:] != "}}" {
		return
	}
	path := newAttrPath(value[2 : len(value)-2])
	if _, ok := e.twoWayDataBindings[path.String()]; ok {
		consoleError("data binding", path.String(), "set more than once")
	}
	e.subpropertyProxySet(path)
	mutationObserver := newMutationObserver(e.Get("__internal_object__"), obj, path)
	db := &twoWayDataBinding{Attribute: obj, Path: path, MutationObserver: mutationObserver}
	e.twoWayDataBindings[path.String()] = db
	db.SetAttr(e.Object)
}

func (e *Element) addEventListener(attr *js.Object) {
	eventName := attr.Get("name").String()[3:]
	methodName := attr.Get("value").String()
	method := e.ObjValue.MethodByName(methodName)
	if !method.IsValid() {
		consoleError("Error on event listener binding, method", methodName, "doesn't exist")
		return
	}
	e.Call("addEventListener", eventName, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		var in []reflect.Value
		for _, arg := range arguments {
			in = append(in, reflect.ValueOf(arg.Interface()))
		}
		method.Call(in)
		return nil
	}))
}

//subpropertyProxySet sets an js Proxy on an subproperty path to track get and set on its properties
func (e *Element) subpropertyProxySet(bindedPath attrPath) {
	subpropertyPath := bindedPath[:len(bindedPath)-1]
	if len(subpropertyPath) > 0 && !e.subpropertyProxyAdded(subpropertyPath) {
		proxy := newProxy(e.ObjValue, subpropertyPath)
		subpropertyPath.Set(js.InternalObject(e.ObjValue).Get("ptr"), proxy)
	}
}

func (e *Element) subpropertyProxyAdded(subpropertyPath attrPath) bool {
	for p := range e.oneWayDataBindings {
		subPath := newAttrPath(p)
		subPath = subPath[:len(subPath)-1]
		if subPath.String() == subpropertyPath.String() {
			return true
		}
	}
	for p := range e.twoWayDataBindings {
		subPath := newAttrPath(p)
		subPath = subPath[:len(subPath)-1]
		if subPath.String() == subpropertyPath.String() {
			return true
		}
	}
	return false
}

func newMutationObserver(proxy *js.Object, attr *js.Object, path attrPath) *js.Object {
	attr.Set("__attr_path__", path.String())
	mutationObserver := js.Global.Get("window").Get("MutationObserver").New(
		js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
			newValue := attr.Get("value")
			path := newAttrPath(attr.Get("__attr_path__").String())
			if path.Get(proxy) != newValue {
				path.Set(proxy, newValue)
			}
			return nil
		}))
	mutationObserver.Call(
		"observe",
		attr.Get("ownerElement"),
		map[string]interface{}{
			"attributes":      true,
			"attributeFilter": []string{attr.Get("name").String()},
		},
	)
	return mutationObserver
}

//newProxy creates an js Proxy object that can track what has been get or set to run dataBindings
func newProxy(customObject reflect.Value, pathPrefix attrPath) (proxy *js.Object) {
	internalCustomObject := js.InternalObject(customObject.Interface())
	subObj := internalCustomObject
	if len(pathPrefix) > 0 {
		subObj = pathPrefix.Get(subObj)
	}
	handler := map[string]interface{}{
		"get": js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
			attributeName := arguments[1].String()
			if attributeName == "__internal_object__" || attributeName == "$val" {
				return proxy
			}
			return subObj.Get(attributeName)
		}),
		"set": js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
			attributeName := arguments[1].String()
			path := append(pathPrefix, attributeName)
			field, ok := path.GetField(customObject.Elem().Type())
			//field doesn't exist
			if !ok {
				return true
			}
			if isDataBindingExpression(arguments[2]) {
				return true
			}
			convertedValue, err := convertJSType(field.Type, arguments[2])
			if err != nil {
				consoleError(
					"Error converting value in setting the property",
					path.String(),
					": (", arguments[2], ")",
					err,
				)
				return true
			}
			subObj.Set(attributeName, convertedValue)
			//if it's exported and isn't a subproperty set also the tag attribute
			if len(pathPrefix) == 0 && field.PkgPath == "" {
				instance := subObj.Get("Element").Get("Object")
				instance.Call("setAttribute", camelCaseToKebab(attributeName), arguments[2])
			}
			//sets binded attributes of the children in template
			elem := customObject.Elem().FieldByName("Element").Interface().(Element)
			if dbs, ok := elem.oneWayDataBindings[path.String()]; ok {
				for _, db := range dbs {
					db.SetAttr(internalCustomObject)
				}
			}
			if db, ok := elem.twoWayDataBindings[path.String()]; ok {
				db.SetAttr(internalCustomObject)
			}
			return true
		}),
	}
	proxy = js.Global.Get("Proxy").New(subObj, handler)
	return
}

func convertJSType(t reflect.Type, value *js.Object) (val interface{}, err error) {
	val = value.Interface()
	valString, ok := val.(string)
	if !ok {
		return
	}
	switch t.Kind() {
	case reflect.String:
		val = valString
		return
	case reflect.Bool:
		val, err = strconv.ParseBool(valString)
	case reflect.Float32:
		val, err = strconv.ParseFloat(valString, 32)
	case reflect.Float64:
		val, err = strconv.ParseFloat(valString, 64)
	case reflect.Int:
		val, err = strconv.ParseInt(valString, 10, 64)
	case reflect.Int16:
		val, err = strconv.ParseInt(valString, 10, 16)
	case reflect.Int32:
		val, err = strconv.ParseInt(valString, 10, 32)
	case reflect.Int64:
		val, err = strconv.ParseInt(valString, 10, 64)
	case reflect.Uint:
		val, err = strconv.ParseUint(valString, 10, 64)
	case reflect.Uint16:
		val, err = strconv.ParseUint(valString, 10, 16)
	case reflect.Uint32:
		val, err = strconv.ParseUint(valString, 10, 32)
	case reflect.Uint64:
		val, err = strconv.ParseUint(valString, 10, 64)
	case reflect.Struct:
		if t.Name() == "Time" {
			val, err = time.Parse(time.UnixDate, valString)
			return
		}
	}
	return
}

func isDataBindingExpression(val *js.Object) bool {
	valString, ok := val.Interface().(string)
	if !ok {
		return false
	}
	if len(valString) < 4 {
		return false
	}
	return (valString[:2] == "{{" && valString[len(valString)-2:] == "}}") ||
		(valString[:2] == "||" && valString[len(valString)-2:] == "||")
}
<commit_msg>addEventListener error print<commit_after>package golymer

import (
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/gopherjs/gopherjs/js"
)

func consoleError(args ...interface{}) {
	js.Global.Get("console").Call("error", args...)
}

//CustomElement the interface to create the CustomElement
type CustomElement interface {
	ConnectedCallback()
	DisconnectedCallback()
	AttributeChangedCallback(attributeName string, oldValue interface{}, newValue interface{}, namespace string)
	AdoptedCallback(oldDocument, newDocument interface{})
}

type oneWayDataBinding struct {
	Str       string
	Attribute *js.Object
	Paths     []attrPath
}

func (db oneWayDataBinding) SetAttr(obj *js.Object) {
	value := db.Str
	for _, path := range db.Paths {
		fieldValue := path.Get(obj).String()
		value = strings.Replace(value, "[["+path.String()+"]]", fieldValue, -1)
	}
	if db.Attribute.Get("value") != js.Undefined {
		db.Attribute.Set("value", value) //if it's an attribute
		return
	}
	db.Attribute.Set("data", value) //if it's an text node
}

type twoWayDataBinding struct {
	Attribute        *js.Object
	Path             attrPath
	MutationObserver *js.Object
}

func (db twoWayDataBinding) SetAttr(obj *js.Object) {
	value := db.Path.Get(obj)
	/*
		if timeValue, ok := value.Interface().(time.Time); ok {
			formatedTime := timeValue.Format(time.UnixDate)
			print(formatedTime)
			db.Attribute.Set("value", formatedTime)
			return
		}
	*/
	if db.Attribute.Get("value").String() != value.String() {
		db.Attribute.Set("value", value)
	}
}

//Element wrapper for the HTML element
type Element struct {
	*js.Object
	ObjValue           reflect.Value //custom element struct type
	Template           string
	Children           map[string]*js.Object
	oneWayDataBindings map[string][]*oneWayDataBinding
	twoWayDataBindings map[string]*twoWayDataBinding
}

//ConnectedCallback ...
func (e *Element) ConnectedCallback() {
	e.Call("attachShadow", map[string]interface{}{"mode": "open"})
	e.Get("shadowRoot").Set("innerHTML", e.Template)
	e.Children = make(map[string]*js.Object)
	e.oneWayDataBindings = make(map[string][]*oneWayDataBinding)
	e.twoWayDataBindings = make(map[string]*twoWayDataBinding)
	e.scanElement(e.Get("shadowRoot"))
}

//DisconnectedCallback ...
func (e *Element) DisconnectedCallback() {
}

//AttributeChangedCallback ...
func (e *Element) AttributeChangedCallback(attributeName string, oldValue interface{}, newValue interface{}, namespace string) {
	//if attribute didn't change don't set the field
	if oldValue != newValue {
		e.Get("__internal_object__").Set(toExportedFieldName(attributeName), newValue)
	}
}

//AdoptedCallback ...
func (e *Element) AdoptedCallback(oldDocument, newDocument interface{}) {
}

func (e *Element) scanElement(element *js.Object) {
	//find data binded attributes
	if elementAttributes := element.Get("attributes"); elementAttributes != js.Undefined {
		for i := 0; i < elementAttributes.Length(); i++ {
			attribute := elementAttributes.Index(i)
			//collect children with id
			if attribute.Get("name").String() == "id" {
				e.Children[attribute.Get("value").String()] = element
				continue
			}
			e.addOneWay(attribute, attribute.Get("value").String())
			e.addTwoWay(attribute, attribute.Get("value").String())
			if attribute.Get("name").String()[:3] == "on-" {
				e.addEventListener(attribute)
			}
		}
	}
	//find textChild with data binded value
	childNodes := element.Get("childNodes")
	for i := 0; i < childNodes.Length(); i++ {
		child := childNodes.Index(i)
		if child.Get("nodeName").String() != "#text" {
			continue
		}
		e.addOneWay(child, child.Get("data").String())
	}
	//scan children
	children := element.Get("children")
	for i := 0; i < children.Length(); i++ {
		e.scanElement(children.Index(i))
	}
}

//addOneWay gets an js Attribute object or an textNode object and its text value
//than finds all data bindings and adds it to the oneWayDataBindings map
func (e *Element) addOneWay(obj *js.Object, value string) {
	var bindedPaths []attrPath
	for _, path := range oneWayFindAll(value) {
		bindedPaths = append(bindedPaths, newAttrPath(path))
	}
	for _, bindedPath := range bindedPaths {
		e.subpropertyProxySet(bindedPath)
		db := &oneWayDataBinding{Str: value, Attribute: obj, Paths: bindedPaths}
		e.oneWayDataBindings[bindedPath.String()] = append(e.oneWayDataBindings[bindedPath.String()], db)
		db.SetAttr(e.Object)
	}
}

//addTwoWay gets an js Attribute object or an textNode object and its text value
//than finds all data bindings and adds it to the twoWayDataBindings map
func (e *Element) addTwoWay(obj *js.Object, value string) {
	//twoWayDataBinding, obj is attribute DOM object and value is in {{}}
	if obj.Get("value") == js.Undefined || value[:2] != "{{" || value[len(value)-2:] != "}}" {
		return
	}
	path := newAttrPath(value[2 : len(value)-2])
	if _, ok := e.twoWayDataBindings[path.String()]; ok {
		consoleError("data binding", path.String(), "set more than once")
	}
	e.subpropertyProxySet(path)
	mutationObserver := newMutationObserver(e.Get("__internal_object__"), obj, path)
	db := &twoWayDataBinding{Attribute: obj, Path: path, MutationObserver: mutationObserver}
	e.twoWayDataBindings[path.String()] = db
	db.SetAttr(e.Object)
}

func (e *Element) addEventListener(attr *js.Object) {
	eventName := attr.Get("name").String()[3:]
	methodName := attr.Get("value").String()
	method := e.ObjValue.MethodByName(methodName)
	if !method.IsValid() {
		consoleError("Error on event listener", eventName, "binding, method", methodName, "doesn't exist")
		return
	}
	e.Call("addEventListener", eventName, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		var in []reflect.Value
		for _, arg := range arguments {
			in = append(in, reflect.ValueOf(arg.Interface()))
		}
		method.Call(in)
		return nil
	}))
}

//subpropertyProxySet sets an js Proxy on an subproperty path to track get and set on its properties
func (e *Element) subpropertyProxySet(bindedPath attrPath) {
	subpropertyPath := bindedPath[:len(bindedPath)-1]
	if len(subpropertyPath) > 0 && !e.subpropertyProxyAdded(subpropertyPath) {
		proxy := newProxy(e.ObjValue, subpropertyPath)
		subpropertyPath.Set(js.InternalObject(e.ObjValue).Get("ptr"), proxy)
	}
}

func (e *Element) subpropertyProxyAdded(subpropertyPath attrPath) bool {
	for p := range e.oneWayDataBindings {
		subPath := newAttrPath(p)
		subPath = subPath[:len(subPath)-1]
		if subPath.String() == subpropertyPath.String() {
			return true
		}
	}
	for p := range e.twoWayDataBindings {
		subPath := newAttrPath(p)
		subPath = subPath[:len(subPath)-1]
		if subPath.String() == subpropertyPath.String() {
			return true
		}
	}
	return false
}

func newMutationObserver(proxy *js.Object, attr *js.Object, path attrPath) *js.Object {
	attr.Set("__attr_path__", path.String())
	mutationObserver := js.Global.Get("window").Get("MutationObserver").New(
		js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
			newValue := attr.Get("value")
			path := newAttrPath(attr.Get("__attr_path__").String())
			if path.Get(proxy) != newValue {
				path.Set(proxy, newValue)
			}
			return nil
		}))
	mutationObserver.Call(
		"observe",
		attr.Get("ownerElement"),
		map[string]interface{}{
			"attributes":      true,
			"attributeFilter": []string{attr.Get("name").String()},
		},
	)
	return mutationObserver
}

//newProxy creates an js Proxy object that can track what has been get or set to run dataBindings
func newProxy(customObject reflect.Value, pathPrefix attrPath) (proxy *js.Object) {
	internalCustomObject := js.InternalObject(customObject.Interface())
	subObj := internalCustomObject
	if len(pathPrefix) > 0 {
		subObj = pathPrefix.Get(subObj)
	}
	handler := map[string]interface{}{
		"get": js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
			attributeName := arguments[1].String()
			if attributeName == "__internal_object__" || attributeName == "$val" {
				return proxy
			}
			return subObj.Get(attributeName)
		}),
		"set": js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
			attributeName := arguments[1].String()
			path := append(pathPrefix, attributeName)
			field, ok := path.GetField(customObject.Elem().Type())
			//field doesn't exist
			if !ok {
				return true
			}
			if isDataBindingExpression(arguments[2]) {
				return true
			}
			convertedValue, err := convertJSType(field.Type, arguments[2])
			if err != nil {
				consoleError(
					"Error converting value in setting the property",
					path.String(),
					": (", arguments[2], ")",
					err,
				)
				return true
			}
			subObj.Set(attributeName, convertedValue)
			//if it's exported and isn't a subproperty set also the tag attribute
			if len(pathPrefix) == 0 && field.PkgPath == "" {
				instance := subObj.Get("Element").Get("Object")
				instance.Call("setAttribute", camelCaseToKebab(attributeName), arguments[2])
			}
			//sets binded attributes of the children in template
			elem := customObject.Elem().FieldByName("Element").Interface().(Element)
			if dbs, ok := elem.oneWayDataBindings[path.String()]; ok {
				for _, db := range dbs {
					db.SetAttr(internalCustomObject)
				}
			}
			if db, ok := elem.twoWayDataBindings[path.String()]; ok {
				db.SetAttr(internalCustomObject)
			}
			return true
		}),
	}
	proxy = js.Global.Get("Proxy").New(subObj, handler)
	return
}

func convertJSType(t reflect.Type, value *js.Object) (val interface{}, err error) {
	val = value.Interface()
	valString, ok := val.(string)
	if !ok {
		return
	}
	switch t.Kind() {
	case reflect.String:
		val = valString
		return
	case reflect.Bool:
		val, err = strconv.ParseBool(valString)
	case reflect.Float32:
		val, err = strconv.ParseFloat(valString, 32)
	case reflect.Float64:
		val, err = strconv.ParseFloat(valString, 64)
	case reflect.Int:
		val, err = strconv.ParseInt(valString, 10, 64)
	case reflect.Int16:
		val, err = strconv.ParseInt(valString, 10, 16)
	case reflect.Int32:
		val, err = strconv.ParseInt(valString, 10, 32)
	case reflect.Int64:
		val, err = strconv.ParseInt(valString, 10, 64)
	case reflect.Uint:
		val, err = strconv.ParseUint(valString, 10, 64)
	case reflect.Uint16:
		val, err = strconv.ParseUint(valString, 10, 16)
	case reflect.Uint32:
		val, err = strconv.ParseUint(valString, 10, 32)
	case reflect.Uint64:
		val, err = strconv.ParseUint(valString, 10, 64)
	case reflect.Struct:
		if t.Name() == "Time" {
			val, err = time.Parse(time.UnixDate, valString)
			return
		}
	}
	return
}

func isDataBindingExpression(val *js.Object) bool {
	valString, ok := val.Interface().(string)
	if !ok {
		return false
	}
	if len(valString) < 4 {
		return false
	}
	return (valString[:2] == "{{" && valString[len(valString)-2:] == "}}") ||
		(valString[:2] == "||" && valString[len(valString)-2:] == "||")
}
<|endoftext|>