<commit_before>package md

import (
	"testing"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

func TestIsHeader(t *testing.T) {
	tests := []struct {
		name string
		in   *html.Node
		out  bool
	}{
		{
			name: "LabTitle",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H1,
				Data:     "h1",
			},
		},
		{
			name: "StepTitle",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H2,
				Data:     "h2",
			},
		},
		{
			name: "FirstLevel",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H3,
				Data:     "h3",
			},
			out: true,
		},
		{
			name: "SecondLevel",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H4,
				Data:     "h4",
			},
			out: true,
		},
		{
			name: "ThirdLevel",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H5,
				Data:     "h5",
			},
			out: true,
		},
		{
			name: "FourthLevel",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H6,
				Data:     "h6",
			},
			out: true,
		},
		{
			name: "NotAHeader",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.Blink,
				Data:     "blink",
			},
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if out := isHeader(tc.in); out != tc.out {
				t.Errorf("isHeader(%v) = %t, want %t", tc.in, out, tc.out)
			}
		})
	}
}

// TODO TestIsMeta

func TestIsBold(t *testing.T) {
	// foobar
	a1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Strong,
		Data:     "strong",
	}
	a2 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	a1.AppendChild(a2)

	// foobar
	b1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.B,
		Data:     "b",
	}
	b2 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	b1.AppendChild(b2)

	// foobar
	c1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Strong,
		Data:     "strong",
	}
	c2 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Code,
		Data:     "code",
	}
	c3 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	c1.AppendChild(c2)
	c2.AppendChild(c3)

	// foobar
	d1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.B,
		Data:     "b",
	}
	d2 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Code,
		Data:     "code",
	}
	d3 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	d1.AppendChild(d2)
	d2.AppendChild(d3)

	//
foobar

	e1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.P,
		Data:     "p",
	}
	e2 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	e1.AppendChild(e2)

	tests := []struct {
		name string
		in   *html.Node
		out  bool
	}{
		{
			name: "StrongText_Strong",
			in:   a1,
			out:  true,
		},
		{
			name: "StrongText_Strong",
			in:   a2,
			out:  true,
		},
		{
			name: "BText_B",
			in:   b1,
			out:  true,
		},
		{
			name: "BText_Text",
			in:   b2,
			out:  true,
		},
		{
			name: "StrongCodeText_Strong",
			in:   c1,
			out:  true,
		},
		{
			name: "StrongCodeText_Code",
			in:   c2,
			out:  true,
		},
		/*
			// TODO: I think this should work but it doesn't.
			{
				name: "StrongCodeText_Text",
				in:   c3,
				out:  true,
			},
		*/
		{
			name: "BCodeText_B",
			in:   d1,
			out:  true,
		},
		{
			name: "BCodeText_Code",
			in:   d2,
			out:  true,
		},
		/*
			// TODO: I think this should work but it doesn't
			{
				name: "BCodeText_Text",
				in:   d3,
				out:  true,
			},
		*/
		{
			name: "PText_P",
			in:   e1,
		},
		{
			name: "PText_Text",
			in:   e2,
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if out := isBold(tc.in); out != tc.out {
				t.Errorf("isBold(%v) = %t, want %t", tc.in, out, tc.out)
			}
		})
	}
}

func TestIsItalic(t *testing.T) {
	// foobar
	a1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Em,
		Data:     "em",
	}
	a2 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	a1.AppendChild(a2)

	// foobar
	b1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.I,
		Data:     "i",
	}
	b2 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	b1.AppendChild(b2)

	// foobar
	c1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Em,
		Data:     "em",
	}
	c2 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Code,
		Data:     "code",
	}
	c3 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	c1.AppendChild(c2)
	c2.AppendChild(c3)

	// foobar
	d1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.I,
		Data:     "i",
	}
	d2 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Code,
		Data:     "code",
	}
	d3 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	d1.AppendChild(d2)
	d2.AppendChild(d3)

	//
foobar

	e1 := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.P,
		Data:     "p",
	}
	e2 := &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
	e1.AppendChild(e2)

	tests := []struct {
		name string
		in   *html.Node
		out  bool
	}{
		{
			name: "EmText_Em",
			in:   a1,
			out:  true,
		},
		{
			name: "EmText_Em",
			in:   a2,
			out:  true,
		},
		{
			name: "IText_I",
			in:   b1,
			out:  true,
		},
		{
			name: "IText_Text",
			in:   b2,
			out:  true,
		},
		{
			name: "EmCodeText_Em",
			in:   c1,
			out:  true,
		},
		{
			name: "EmCodeText_Code",
			in:   c2,
			out:  true,
		},
		/*
			// TODO: I think this should work but it doesn't.
			{
				name: "EmCodeText_Text",
				in:   c3,
				out:  true,
			},
		*/
		{
			name: "ICodeText_I",
			in:   d1,
			out:  true,
		},
		{
			name: "ICodeText_Code",
			in:   d2,
			out:  true,
		},
		/*
			// TODO: I think this should work but it doesn't
			{
				name: "ICodeText_Text",
				in:   d3,
				out:  true,
			},
		*/
		{
			name: "PText_P",
			in:   e1,
		},
		{
			name: "PText_Text",
			in:   e2,
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if out := isItalic(tc.in); out != tc.out {
				t.Errorf("isItalic(%v) = %t, want %t", tc.in, out, tc.out)
			}
		})
	}
}
<commit_msg>Add test helper functions for node creation.<commit_after>package md

import (
	"testing"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

func TestIsHeader(t *testing.T) {
	tests := []struct {
		name string
		in   *html.Node
		out  bool
	}{
		{
			name: "LabTitle",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H1,
				Data:     "h1",
			},
		},
		{
			name: "StepTitle",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H2,
				Data:     "h2",
			},
		},
		{
			name: "FirstLevel",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H3,
				Data:     "h3",
			},
			out: true,
		},
		{
			name: "SecondLevel",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H4,
				Data:     "h4",
			},
			out: true,
		},
		{
			name: "ThirdLevel",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H5,
				Data:     "h5",
			},
			out: true,
		},
		{
			name: "FourthLevel",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.H6,
				Data:     "h6",
			},
			out: true,
		},
		{
			name: "NotAHeader",
			in: &html.Node{
				Type:     html.ElementNode,
				DataAtom: atom.Blink,
				Data:     "blink",
			},
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if out := isHeader(tc.in); out != tc.out {
				t.Errorf("isHeader(%v) = %t, want %t", tc.in, out, tc.out)
			}
		})
	}
}

// TODO TestIsMeta

func makeStrongNode() *html.Node {
	return &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Strong,
		Data:     "strong",
	}
}

func makeBNode() *html.Node {
	return &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.B,
		Data:     "b",
	}
}

func makeEmNode() *html.Node {
	return &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Em,
		Data:     "em",
	}
}

// , not the filesystem abstraction.
func makeINode() *html.Node {
	return &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.I,
		Data:     "i",
	}
}

func makeTextNode() *html.Node {
	return &html.Node{
		Type: html.TextNode,
		Data: "foobar",
	}
}

func makeCodeNode() *html.Node {
	return &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Code,
		Data:     "code",
	}
}

func makePNode() *html.Node {
	return &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.P,
		Data:     "p",
	}
}

func TestIsBold(t *testing.T) {
	// foobar
	a1 := makeStrongNode()
	a2 := makeTextNode()
	a1.AppendChild(a2)

	// foobar
	b1 := makeBNode()
	b2 := makeTextNode()
	b1.AppendChild(b2)

	// foobar
	c1 := makeStrongNode()
	c2 := makeCodeNode()
	c3 := makeTextNode()
	c1.AppendChild(c2)
	c2.AppendChild(c3)

	// foobar
	d1 := makeBNode()
	d2 := makeCodeNode()
	d3 := makeTextNode()
	d1.AppendChild(d2)
	d2.AppendChild(d3)

	//
foobar

	e1 := makePNode()
	e2 := makeTextNode()
	e1.AppendChild(e2)

	tests := []struct {
		name string
		in   *html.Node
		out  bool
	}{
		{
			name: "StrongText_Strong",
			in:   a1,
			out:  true,
		},
		{
			name: "StrongText_Strong",
			in:   a2,
			out:  true,
		},
		{
			name: "BText_B",
			in:   b1,
			out:  true,
		},
		{
			name: "BText_Text",
			in:   b2,
			out:  true,
		},
		{
			name: "StrongCodeText_Strong",
			in:   c1,
			out:  true,
		},
		{
			name: "StrongCodeText_Code",
			in:   c2,
			out:  true,
		},
		/*
			// TODO: I think this should work but it doesn't.
			{
				name: "StrongCodeText_Text",
				in:   c3,
				out:  true,
			},
		*/
		{
			name: "BCodeText_B",
			in:   d1,
			out:  true,
		},
		{
			name: "BCodeText_Code",
			in:   d2,
			out:  true,
		},
		/*
			// TODO: I think this should work but it doesn't
			{
				name: "BCodeText_Text",
				in:   d3,
				out:  true,
			},
		*/
		{
			name: "PText_P",
			in:   e1,
		},
		{
			name: "PText_Text",
			in:   e2,
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if out := isBold(tc.in); out != tc.out {
				t.Errorf("isBold(%v) = %t, want %t", tc.in, out, tc.out)
			}
		})
	}
}

func TestIsItalic(t *testing.T) {
	// foobar
	a1 := makeEmNode()
	a2 := makeTextNode()
	a1.AppendChild(a2)

	// foobar
	b1 := makeINode()
	b2 := makeTextNode()
	b1.AppendChild(b2)

	// foobar
	c1 := makeEmNode()
	c2 := makeCodeNode()
	c3 := makeTextNode()
	c1.AppendChild(c2)
	c2.AppendChild(c3)

	// foobar
	d1 := makeINode()
	d2 := makeCodeNode()
	d3 := makeTextNode()
	d1.AppendChild(d2)
	d2.AppendChild(d3)

	//
foobar

	e1 := makePNode()
	e2 := makeTextNode()
	e1.AppendChild(e2)

	tests := []struct {
		name string
		in   *html.Node
		out  bool
	}{
		{
			name: "EmText_Em",
			in:   a1,
			out:  true,
		},
		{
			name: "EmText_Em",
			in:   a2,
			out:  true,
		},
		{
			name: "IText_I",
			in:   b1,
			out:  true,
		},
		{
			name: "IText_Text",
			in:   b2,
			out:  true,
		},
		{
			name: "EmCodeText_Em",
			in:   c1,
			out:  true,
		},
		{
			name: "EmCodeText_Code",
			in:   c2,
			out:  true,
		},
		/*
			// TODO: I think this should work but it doesn't.
			{
				name: "EmCodeText_Text",
				in:   c3,
				out:  true,
			},
		*/
		{
			name: "ICodeText_I",
			in:   d1,
			out:  true,
		},
		{
			name: "ICodeText_Code",
			in:   d2,
			out:  true,
		},
		/*
			// TODO: I think this should work but it doesn't
			{
				name: "ICodeText_Text",
				in:   d3,
				out:  true,
			},
		*/
		{
			name: "PText_P",
			in:   e1,
		},
		{
			name: "PText_Text",
			in:   e2,
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if out := isItalic(tc.in); out != tc.out {
				t.Errorf("isItalic(%v) = %t, want %t", tc.in, out, tc.out)
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package factom

import (
	"encoding/json"
	"fmt"
)

// BackupWallet returns a formatted string with the wallet seed and the secret
// keys for all of the wallet addresses.
func BackupWallet() (string, error) {
	type walletBackupResponse struct {
		Seed      string             `json:"wallet-seed"`
		Addresses []*addressResponse `json:"addresses"`
	}

	req := NewJSON2Request("wallet-backup", APICounter(), nil)
	resp, err := walletRequest(req)
	if err != nil {
		return "", err
	}
	if resp.Error != nil {
		return "", resp.Error
	}

	w := new(walletBackupResponse)
	if err := json.Unmarshal(resp.JSONResult(), w); err != nil {
		return "", err
	}

	s := fmt.Sprintln("Seed:")
	s += fmt.Sprintln(w.Seed)
	s += fmt.Sprintln("Addresses:")
	for _, adr := range w.Addresses {
		s += fmt.Sprintln(adr.Secret)
	}
	return s, nil
}

func GenerateFactoidAddress() (*FactoidAddress, error) {
	req := NewJSON2Request("generate-factoid-address", APICounter(), nil)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	a := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), a); err != nil {
		return nil, err
	}
	f, err := GetFactoidAddress(a.Secret)
	if err != nil {
		return nil, err
	}

	return f, nil
}

func GenerateECAddress() (*ECAddress, error) {
	req := NewJSON2Request("generate-ec-address", APICounter(), nil)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	a := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), a); err != nil {
		return nil, err
	}
	e, err := GetECAddress(a.Secret)
	if err != nil {
		return nil, err
	}

	return e, nil
}

func ImportAddresses(addrs ...string) (
	[]*FactoidAddress,
	[]*ECAddress,
	error) {

	params := new(importRequest)
	for _, addr := range addrs {
		s := secretRequest{Secret: addr}
		params.Addresses = append(params.Addresses, s)
	}
	req := NewJSON2Request("import-addresses", APICounter(), params)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, nil, err
	}
	if resp.Error != nil {
		return nil, nil, resp.Error
	}

	r := new(multiAddressResponse)
	if err := json.Unmarshal(resp.JSONResult(), r); err != nil {
		return nil, nil, err
	}
	fs := make([]*FactoidAddress, 0)
	es := make([]*ECAddress, 0)
	for _, address := range r.Addresses {
		switch AddressStringType(address.Secret) {
		case FactoidSec:
			f, err := GetFactoidAddress(address.Secret)
			if err != nil {
				return nil, nil, err
			}
			fs = append(fs, f)
		case ECSec:
			e, err := GetECAddress(address.Secret)
			if err != nil {
				return nil, nil, err
			}
			es = append(es, e)
		default:
			return nil, nil, fmt.Errorf("Unrecognized address type")
		}
	}

	return fs, es, nil
}

func ImportMnemonic(mnemonic string) (*FactoidAddress, error) {
	params := new(importMnemonicRequest)
	params.Words = mnemonic

	req := NewJSON2Request("import-mnemonic", APICounter(), params)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	r := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), r); err != nil {
		return nil, err
	}
	f, err := GetFactoidAddress(r.Secret)
	if err != nil {
		return nil, err
	}

	return f, nil
}

func FetchAddresses() ([]*FactoidAddress, []*ECAddress, error) {
	req := NewJSON2Request("all-addresses", APICounter(), nil)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, nil, err
	}
	if resp.Error != nil {
		return nil, nil, resp.Error
	}

	fs := make([]*FactoidAddress, 0)
	es := make([]*ECAddress, 0)

	as := new(multiAddressResponse)
	if err := json.Unmarshal(resp.JSONResult(), as); err != nil {
		return nil, nil, err
	}

	for _, adr := range as.Addresses {
		switch AddressStringType(adr.Public) {
		case FactoidPub:
			f, err := GetFactoidAddress(adr.Secret)
			if err != nil {
				return nil, nil, err
			}
			fs = append(fs, f)
		case ECPub:
			e, err := GetECAddress(adr.Secret)
			if err != nil {
				return nil, nil, err
			}
			es = append(es, e)
		default:
			return nil, nil, fmt.Errorf("%s is not a valid address", adr.Public)
		}
	}

	return fs, es, nil
}

func FetchECAddress(ecpub string) (*ECAddress, error) {
	if AddressStringType(ecpub) != ECPub {
		return nil, fmt.Errorf(
			"%s is not an Entry Credit Public Address", ecpub)
	}
	params := new(addressRequest)
	params.Address = ecpub

	req := NewJSON2Request("address", APICounter(), params)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	r := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), r); err != nil {
		return nil, err
	}

	return GetECAddress(r.Secret)
}

func FetchFactoidAddress(fctpub string) (*FactoidAddress, error) {
	if AddressStringType(fctpub) != FactoidPub {
		return nil, fmt.Errorf("%s is not a Factoid Address", fctpub)
	}
	params := new(addressRequest)
	params.Address = fctpub

	req := NewJSON2Request("address", APICounter(), params)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	r := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), r); err != nil {
		return nil, err
	}

	return GetFactoidAddress(r.Secret)
}

type addressResponse struct {
	Public string `json:"public"`
	Secret string `json:"secret"`
}

type multiAddressResponse struct {
	Addresses []*addressResponse `json:"addresses"`
}

<commit_msg>go fmt<commit_after>// Copyright 2016 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package factom

import (
	"encoding/json"
	"fmt"
)

// BackupWallet returns a formatted string with the wallet seed and the secret
// keys for all of the wallet addresses.
func BackupWallet() (string, error) {
	type walletBackupResponse struct {
		Seed      string             `json:"wallet-seed"`
		Addresses []*addressResponse `json:"addresses"`
	}

	req := NewJSON2Request("wallet-backup", APICounter(), nil)
	resp, err := walletRequest(req)
	if err != nil {
		return "", err
	}
	if resp.Error != nil {
		return "", resp.Error
	}

	w := new(walletBackupResponse)
	if err := json.Unmarshal(resp.JSONResult(), w); err != nil {
		return "", err
	}

	s := fmt.Sprintln("Seed:")
	s += fmt.Sprintln(w.Seed)
	s += fmt.Sprintln("Addresses:")
	for _, adr := range w.Addresses {
		s += fmt.Sprintln(adr.Secret)
	}
	return s, nil
}

func GenerateFactoidAddress() (*FactoidAddress, error) {
	req := NewJSON2Request("generate-factoid-address", APICounter(), nil)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	a := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), a); err != nil {
		return nil, err
	}
	f, err := GetFactoidAddress(a.Secret)
	if err != nil {
		return nil, err
	}

	return f, nil
}

func GenerateECAddress() (*ECAddress, error) {
	req := NewJSON2Request("generate-ec-address", APICounter(), nil)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	a := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), a); err != nil {
		return nil, err
	}
	e, err := GetECAddress(a.Secret)
	if err != nil {
		return nil, err
	}

	return e, nil
}

func ImportAddresses(addrs ...string) (
	[]*FactoidAddress,
	[]*ECAddress,
	error) {

	params := new(importRequest)
	for _, addr := range addrs {
		s := secretRequest{Secret: addr}
		params.Addresses = append(params.Addresses, s)
	}
	req := NewJSON2Request("import-addresses", APICounter(), params)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, nil, err
	}
	if resp.Error != nil {
		return nil, nil, resp.Error
	}

	r := new(multiAddressResponse)
	if err := json.Unmarshal(resp.JSONResult(), r); err != nil {
		return nil, nil, err
	}
	fs := make([]*FactoidAddress, 0)
	es := make([]*ECAddress, 0)
	for _, address := range r.Addresses {
		switch AddressStringType(address.Secret) {
		case FactoidSec:
			f, err := GetFactoidAddress(address.Secret)
			if err != nil {
				return nil, nil, err
			}
			fs = append(fs, f)
		case ECSec:
			e, err := GetECAddress(address.Secret)
			if err != nil {
				return nil, nil, err
			}
			es = append(es, e)
		default:
			return nil, nil, fmt.Errorf("Unrecognized address type")
		}
	}

	return fs, es, nil
}

func ImportMnemonic(mnemonic string) (*FactoidAddress, error) {
	params := new(importMnemonicRequest)
	params.Words = mnemonic

	req := NewJSON2Request("import-mnemonic", APICounter(), params)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	r := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), r); err != nil {
		return nil, err
	}
	f, err := GetFactoidAddress(r.Secret)
	if err != nil {
		return nil, err
	}

	return f, nil
}

func FetchAddresses() ([]*FactoidAddress, []*ECAddress, error) {
	req := NewJSON2Request("all-addresses", APICounter(), nil)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, nil, err
	}
	if resp.Error != nil {
		return nil, nil, resp.Error
	}

	fs := make([]*FactoidAddress, 0)
	es := make([]*ECAddress, 0)

	as := new(multiAddressResponse)
	if err := json.Unmarshal(resp.JSONResult(), as); err != nil {
		return nil, nil, err
	}

	for _, adr := range as.Addresses {
		switch AddressStringType(adr.Public) {
		case FactoidPub:
			f, err := GetFactoidAddress(adr.Secret)
			if err != nil {
				return nil, nil, err
			}
			fs = append(fs, f)
		case ECPub:
			e, err := GetECAddress(adr.Secret)
			if err != nil {
				return nil, nil, err
			}
			es = append(es, e)
		default:
			return nil, nil, fmt.Errorf("%s is not a valid address", adr.Public)
		}
	}

	return fs, es, nil
}

func FetchECAddress(ecpub string) (*ECAddress, error) {
	if AddressStringType(ecpub) != ECPub {
		return nil, fmt.Errorf(
			"%s is not an Entry Credit Public Address", ecpub)
	}
	params := new(addressRequest)
	params.Address = ecpub

	req := NewJSON2Request("address", APICounter(), params)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	r := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), r); err != nil {
		return nil, err
	}

	return GetECAddress(r.Secret)
}

func FetchFactoidAddress(fctpub string) (*FactoidAddress, error) {
	if AddressStringType(fctpub) != FactoidPub {
		return nil, fmt.Errorf("%s is not a Factoid Address", fctpub)
	}
	params := new(addressRequest)
	params.Address = fctpub

	req := NewJSON2Request("address", APICounter(), params)
	resp, err := walletRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	r := new(addressResponse)
	if err := json.Unmarshal(resp.JSONResult(), r); err != nil {
		return nil, err
	}

	return GetFactoidAddress(r.Secret)
}

type addressResponse struct {
	Public string `json:"public"`
	Secret string `json:"secret"`
}

type multiAddressResponse struct {
	Addresses []*addressResponse `json:"addresses"`
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"context"
	"encoding/csv"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/ovh/cds/cli"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/cdsclient"

	"github.com/spf13/cobra"
)

var adminMetadataCmd = cli.Command{
	Name:  "metadata",
	Short: "Manage CDS Metadata",
	Long: `Metadata a key/value stored on project / application / workflow.

This allows CDS administrators and/or users to make some statistics and charts in a proper tool.
	`,
}
var adminMetadataProjectCmd = cli.Command{
	Name:  "project",
	Short: "Manage CDS Project Metadata",
}
var adminMetadataApplicationCmd = cli.Command{
	Name:  "application",
	Short: "Manage CDS Application Metadata",
}
var adminMetadataWorkflowCmd = cli.Command{
	Name:  "workflow",
	Short: "Manage CDS Workflow Metadata",
}
var adminMetadataProjectExportCmd = cli.Command{
	Name:  "export",
	Short: "export CDS Project Metadata",
	Flags: []cli.Flag{
		{
			Name:    "export-file",
			Usage:   "Filename of file created",
			Default: "export_metadata_projects.csv",
		},
	},
}
var adminMetadataApplicationExportCmd = cli.Command{
	Name:  "export",
	Short: "export CDS Application Metadata",
	Flags: []cli.Flag{
		{
			Name:    "export-file",
			Usage:   "Filename of file created",
			Default: "export_metadata_applications.csv",
		},
	},
}
var adminMetadataWorkflowExportCmd = cli.Command{
	Name:  "export",
	Short: "export CDS Workflow Metadata",
	Flags: []cli.Flag{
		{
			Name:    "export-file",
			Usage:   "Filename of file created",
			Default: "export_metadata_workflows.csv",
		},
	},
}
var adminMetadataProjectImportCmd = cli.Command{
	Name:  "import",
	Short: "import CDS Project Metadata",
	Args: []cli.Arg{
		{Name: "path"},
	},
	Long: `Metadata are represented with key:value

Example of a csv file for a CDS Project

	project_key;project_name;last_modified;ou1;ou2
	YOUR_PROJECT_KEY;Your Project Name;2020-01-01T00:00:00;OU_1_VALUE;OU_2_VALUE

You can enter as many metadata as desired, the key name is on the first line of the csv file.
`,
}
var adminMetadataApplicationImportCmd = cli.Command{
	Name:  "import",
	Short: "import CDS Application Metadata",
	Args: []cli.Arg{
		{Name: "path"},
	},
	Long: `Metadata are represented with key:value

Example of a csv file for a CDS Application

	project_key;application_name;last_modified;vcs_repofullname;ou1;ou2
	YOUR_PROJECT_KEY;Your Application Name;2020-01-01T00:00:00;repo_of_application;OU_1_VALUE;OU_2_VALUE

You can enter as many metadata as desired, the key name is on the first line of the csv file.
`,
}

func adminMetadata() *cobra.Command {
	return cli.NewCommand(adminMetadataCmd, nil, []*cobra.Command{
		cli.NewCommand(adminMetadataProjectCmd, nil, []*cobra.Command{
			cli.NewCommand(adminMetadataProjectExportCmd, adminMetadataProjectExportRun, nil),
			cli.NewCommand(adminMetadataProjectImportCmd, adminMetadataProjectImportRun, nil),
		}),
		cli.NewCommand(adminMetadataApplicationCmd, nil, []*cobra.Command{
			cli.NewCommand(adminMetadataApplicationExportCmd, adminMetadataApplicationExportRun, nil),
			cli.NewCommand(adminMetadataApplicationImportCmd, adminMetadataApplicationImportRun, nil),
		}),
		cli.NewCommand(adminMetadataWorkflowCmd, nil, []*cobra.Command{
			cli.NewCommand(adminMetadataWorkflowExportCmd, adminMetadataWorkflowExportRun, nil),
		}),
	})
}

type lineMetadata struct {
	key             string
	name            string
	lastModified    time.Time
	additionalInfos sdk.Metadata
	metadata        sdk.Metadata
}

func adminMetadataProjectExportRun(c cli.Values) error {
	var currentDisplay = new(cli.Display)
	currentDisplay.Printf("Gettings projects list...")
	currentDisplay.Do(context.Background())

	projects, err := client.ProjectList(false, false)
	if err != nil {
		return err
	}

	lines := make([]lineMetadata, len(projects))
	for i, p := range projects {
		lines[i] = lineMetadata{
			key:          p.Key,
			name:         p.Name,
			lastModified: p.LastModified,
			metadata:     p.Metadata,
		}
	}

	titles := []string{"project_key", "project_name", "last_modified"}
	adminMetadataExport(titles, nil, lines, c.GetString("export-file"), currentDisplay)
	return nil
}

func adminMetadataProjectImportRun(c cli.Values) error {
	path := c.GetString("path")

	updateFunc := func(key, name string, metadata map[string]string) error {
		prj, err := client.ProjectGet(key)
		if err != nil {
			return err
		}
		prj.Metadata = metadata
		if err := client.ProjectUpdate(key, prj); err != nil {
			return err
		}
		fmt.Printf("project %s updated\n", prj.Key)
		return nil
	}

	// 3 columns to ignore: "project_key", "project_name", "last_modified", "nb_workflows"
	return processMetadata(path, 3, updateFunc)
}

func adminMetadataApplicationExportRun(c cli.Values) error {
	var currentDisplay = new(cli.Display)
	currentDisplay.Printf("Gettings projects list...")
	currentDisplay.Do(context.Background())

	projects, err := client.ProjectList(false, false)
	if err != nil {
		return err
	}

	lines := []lineMetadata{}
	for i, p := range projects {
		currentDisplay.Printf("%d/%d - fetching applications on project %s...", i, len(projects), p.Key)
		applications, err := client.ApplicationList(p.Key)
		if err != nil {
			return err
		}

		for _, a := range applications {
			m := sdk.Metadata{}
			// take all metadata from projects
			for k, v := range p.Metadata {
				m[k] = v
			}
			// then add application metadata
			for k, v := range a.Metadata {
				if _, alreadyExists := m[k]; !alreadyExists {
					m[k] = v
				}
			}

			lines = append(lines, lineMetadata{
				key:          a.ProjectKey,
				name:         a.Name,
				lastModified: a.LastModified,
				metadata:     m,
				additionalInfos: sdk.Metadata{
					"vcs_repofullname": a.RepositoryFullname,
				},
			})
		}
	}
	titles := []string{"project_key", "application_name", "last_modified"}
	titlesAdd := []string{"vcs_repofullname"}
	adminMetadataExport(titles, titlesAdd, lines, c.GetString("export-file"), currentDisplay)
	return nil
}

func adminMetadataApplicationImportRun(c cli.Values) error {
	path := c.GetString("path")

	updateFunc := func(key string, name string, metadata map[string]string) error {
		// get metadata from project
		prj, err := client.ProjectGet(key)
		if err != nil {
			return err
		}
		// remove from application metadata list, the metadata from project
		// this avoid to have the same metadata name on app and on project
		fileteredMetadata := sdk.Metadata{}
		for k, v := range metadata {
			if _, exist := prj.Metadata[k]; !exist {
				fileteredMetadata[k] = v
			}
		}
		app, err := client.ApplicationGet(key, name)
		if err != nil {
			return err
		}
		app.Metadata = fileteredMetadata
		if err := client.ApplicationUpdate(key, name, app); err != nil {
			fmt.Printf("ERROR on application %s/%s: %v\n", key, app.Name, err)
		} else {
			fmt.Printf("application %s/%s updated with metadata:%v \n", key, app.Name, app.Metadata)
		}

		return nil
	}

	return processMetadata(path, 4, updateFunc)
}

func adminMetadataWorkflowExportRun(c cli.Values) error {
	projects, err := client.ProjectList(false, false)
	if err != nil {
		return err
	}

	var currentDisplay = new(cli.Display)
	currentDisplay.Printf("Gettings projects list...")
	currentDisplay.Do(context.Background())

	modsWfs := []cdsclient.RequestModifier{}
	modsWfs = append(modsWfs, func(r *http.Request) {
		q := r.URL.Query()
		q.Set("minimal", "true")
		r.URL.RawQuery = q.Encode()
	})

	modsProjects := []cdsclient.RequestModifier{}
	modsProjects = append(modsProjects, func(r *http.Request) {
		q := r.URL.Query()
		q.Set("withWorkflowNames", "true")
		r.URL.RawQuery = q.Encode()
	})

	lines := []lineMetadata{}
	for i, p := range projects {
		currentDisplay.Printf("%d/%d - fetching project %s...", i, len(projects), p.Key)
		proj, err := client.ProjectGet(p.Key, modsProjects...)
		if err != nil {
			return err
		}

		for j, name := range proj.WorkflowNames {
			currentDisplay.Printf("%d/%d - %d/%d - fetching workflow %s/%s...", i, len(projects), j, len(proj.WorkflowNames), proj.Key, name.Name)
			w, err := client.WorkflowGet(proj.Key, name.Name, modsWfs...)
			if err != nil {
				return fmt.Errorf("Error while getting %s/%s", proj.Key, name.Name)
			}

			m := sdk.Metadata{}
			// take all metadata from projects
			for k, v := range p.Metadata {
				m[k] = v
			}
			// then add workflow metadata
			for k, v := range w.Metadata {
				if _, alreadyExists := m[k]; !alreadyExists {
					m[k] = v
				}
			}

			lines = append(lines, lineMetadata{
				key:          w.ProjectKey,
				name:         w.Name,
				lastModified: w.LastModified,
				metadata:     m,
			})
		}
	}
	titles := []string{"project_key", "workflow_name", "last_modified"}
	adminMetadataExport(titles, nil, lines, c.GetString("export-file"), currentDisplay)
	return nil
}

func adminMetadataExport(firstTitles, addTitles []string, lines []lineMetadata, filename string, currentDisplay *cli.Display) {
	keysTitle := map[string]string{}
	for _, l := range lines {
		for k := range l.metadata {
			keysTitle[k] = ""
		}
	}

	// sort the title keys
	keys := make([]string, 0, len(keysTitle))
	for k := range keysTitle {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// sort the add title keys
	sort.Strings(addTitles)

	currentDisplay.Printf("Generating file %s...", filename)

	f, err := os.Create(filename)
	if err != nil {
		fmt.Fprintf(os.Stdout, "Error while creating file %s: %v", filename, err)
		return
	}
	defer f.Close()

	// prepare header
	writeLine(f, strings.Join(firstTitles, ";"))
	for _, k := range addTitles {
		writeLine(f, fmt.Sprintf(";%s", k))
	}
	for _, k := range keys {
		writeLine(f, fmt.Sprintf(";%s", k))
	}
	writeLine(f, fmt.Sprintf("\n"))

	for _, l := range lines {
		ptime := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d",
			l.lastModified.Year(), l.lastModified.Month(), l.lastModified.Day(),
			l.lastModified.Hour(), l.lastModified.Minute(), l.lastModified.Second())

		writeLine(f, fmt.Sprintf("%s;%s;%s", l.key, l.name, ptime))

		for _, k := range addTitles {
			writeLine(f, fmt.Sprintf(";%s", l.additionalInfos[k]))
		}

		// if metadata key exists, print it
		nMetadataWrite := 0
		for _, k := range keys {
			if v, exists := l.metadata[k]; exists {
				nMetadataWrite++
				writeLine(f, fmt.Sprintf(";%s", v))
			}
		}
		// write empty ";"
		writeLine(f, fmt.Sprintf("%s", strings.Repeat(";", len(keys)-nMetadataWrite)))
		writeLine(f, fmt.Sprintf("\n"))
		fmt.Printf("")
	}
	currentDisplay.Printf("file %s created...\n", filename)
	// sleep 2s to let display the currentDisplay
	time.Sleep(2 * time.Second)
}

func writeLine(fi *os.File, s string) {
	if _, err := io.WriteString(fi, s); err != nil {
		fmt.Fprintf(os.Stdout, "error while writing file: %v\n", err)
	}
}

func processMetadata(path string, nbColumnsToIgnore int, updateFunc func(key, name string, metadata map[string]string) error) error {
	csvFile, err := os.Open(path)
	if err != nil {
		return err
	}
	defer csvFile.Close() //nolint

	reader := csv.NewReader(bufio.NewReader(csvFile))

	metadataKeys := map[int]string{}

	for {
		line, err := reader.Read()
		if err == io.EOF {
			break
		} else if err != nil {
			log.Fatal(err)
		}

		columns := strings.Split(line[0], ";")
		pkey := columns[0]
		name := columns[1]

		// first line: title
		if len(metadataKeys) == 0 {
			// project_key, name, last_update. Metadata begins at 3th position
			for index := nbColumnsToIgnore; index < len(columns); index++ {
				metadataKeys[index-nbColumnsToIgnore] = columns[index]
			}
			continue
		}

		metadata := make(map[string]string, len(metadataKeys))

		for index := 0; index < len(metadataKeys); index++ {
			if index > len(metadataKeys) || nbColumnsToIgnore+index >= len(columns) {
				return fmt.Errorf("CSV File invalid. Please check number of columns on %s;%s", pkey, name)
			}
			metadata[metadataKeys[index]] = columns[nbColumnsToIgnore+index]
		}
		if err := updateFunc(pkey, name, metadata); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>fix(cdsctl): manage empty values on metadata<commit_after>package main

import (
	"bufio"
	"context"
	"encoding/csv"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/ovh/cds/cli"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/cdsclient"

	"github.com/spf13/cobra"
)

var adminMetadataCmd = cli.Command{
	Name:  "metadata",
	Short: "Manage CDS Metadata",
	Long: `Metadata a key/value stored on project / application / workflow.

This allows CDS administrators and/or users to make some statistics and charts in a proper tool.
	`,
}
var adminMetadataProjectCmd = cli.Command{
	Name:  "project",
	Short: "Manage CDS Project Metadata",
}
var adminMetadataApplicationCmd = cli.Command{
	Name:  "application",
	Short: "Manage CDS Application Metadata",
}
var adminMetadataWorkflowCmd = cli.Command{
	Name:  "workflow",
	Short: "Manage CDS Workflow Metadata",
}
var adminMetadataProjectExportCmd = cli.Command{
	Name:  "export",
	Short: "export CDS Project Metadata",
	Flags: []cli.Flag{
		{
			Name:    "export-file",
			Usage:   "Filename of file created",
			Default: "export_metadata_projects.csv",
		},
	},
}
var adminMetadataApplicationExportCmd = cli.Command{
	Name:  "export",
	Short: "export CDS Application Metadata",
	Flags: []cli.Flag{
		{
			Name:    "export-file",
			Usage:   "Filename of file created",
			Default: "export_metadata_applications.csv",
		},
	},
}
var adminMetadataWorkflowExportCmd = cli.Command{
	Name:  "export",
	Short: "export CDS Workflow Metadata",
	Flags: []cli.Flag{
		{
			Name:    "export-file",
			Usage:   "Filename of file created",
			Default: "export_metadata_workflows.csv",
		},
	},
}
var adminMetadataProjectImportCmd = cli.Command{
	Name:  "import",
	Short: "import CDS Project Metadata",
	Args: []cli.Arg{
		{Name: "path"},
	},
	Long: `Metadata are represented with key:value

Example of a csv file for a CDS Project

	project_key;project_name;last_modified;ou1;ou2
	YOUR_PROJECT_KEY;Your Project Name;2020-01-01T00:00:00;OU_1_VALUE;OU_2_VALUE

You can enter as many metadata as desired, the key name is on the first line of the csv file.
`,
}
var adminMetadataApplicationImportCmd = cli.Command{
	Name:  "import",
	Short: "import CDS Application Metadata",
	Args: []cli.Arg{
		{Name: "path"},
	},
	Long: `Metadata are represented with key:value

Example of a csv file for a CDS Application

	project_key;application_name;last_modified;vcs_repofullname;ou1;ou2
	YOUR_PROJECT_KEY;Your Application Name;2020-01-01T00:00:00;repo_of_application;OU_1_VALUE;OU_2_VALUE

You can enter as many metadata as desired, the key name is on the first line of the csv file.
`,
}

func adminMetadata() *cobra.Command {
	return cli.NewCommand(adminMetadataCmd, nil, []*cobra.Command{
		cli.NewCommand(adminMetadataProjectCmd, nil, []*cobra.Command{
			cli.NewCommand(adminMetadataProjectExportCmd, adminMetadataProjectExportRun, nil),
			cli.NewCommand(adminMetadataProjectImportCmd, adminMetadataProjectImportRun, nil),
		}),
		cli.NewCommand(adminMetadataApplicationCmd, nil, []*cobra.Command{
			cli.NewCommand(adminMetadataApplicationExportCmd, adminMetadataApplicationExportRun, nil),
			cli.NewCommand(adminMetadataApplicationImportCmd, adminMetadataApplicationImportRun, nil),
		}),
		cli.NewCommand(adminMetadataWorkflowCmd, nil, []*cobra.Command{
			cli.NewCommand(adminMetadataWorkflowExportCmd, adminMetadataWorkflowExportRun, nil),
		}),
	})
}

type lineMetadata struct {
	key             string
	name            string
	lastModified    time.Time
	additionalInfos sdk.Metadata
	metadata        sdk.Metadata
}

func adminMetadataProjectExportRun(c cli.Values) error {
	var currentDisplay = new(cli.Display)
	currentDisplay.Printf("Gettings projects list...")
	currentDisplay.Do(context.Background())

	projects, err := client.ProjectList(false, false)
	if err != nil {
		return err
	}

	lines := make([]lineMetadata, len(projects))
	for i, p := range projects {
		lines[i] = lineMetadata{
			key:          p.Key,
			name:         p.Name,
			lastModified: p.LastModified,
			metadata:     p.Metadata,
		}
	}

	titles := []string{"project_key", "project_name", "last_modified"}
	adminMetadataExport(titles, nil, lines, c.GetString("export-file"), currentDisplay)
	return nil
}

func adminMetadataProjectImportRun(c cli.Values) error {
	path := c.GetString("path")

	updateFunc := func(key, name string, metadata map[string]string) error {
		prj, err := client.ProjectGet(key)
		if err != nil {
			return err
		}
		prj.Metadata = metadata
		if err := client.ProjectUpdate(key, prj); err != nil {
			return err
		}
		fmt.Printf("project %s updated\n", prj.Key)
		return nil
	}

	// 3 columns to ignore: "project_key", "project_name", "last_modified", "nb_workflows"
	return processMetadata(path, 3, updateFunc)
}

func adminMetadataApplicationExportRun(c cli.Values) error {
	var currentDisplay = new(cli.Display)
	currentDisplay.Printf("Gettings projects list...")
	currentDisplay.Do(context.Background())

	projects, err := client.ProjectList(false, false)
	if err != nil {
		return err
	}

	lines := []lineMetadata{}
	for i, p := range projects {
		currentDisplay.Printf("%d/%d - fetching applications on project %s...", i, len(projects), p.Key)
		applications, err := client.ApplicationList(p.Key)
		if err != nil {
			return err
		}

		for _, a := range applications {
			m := sdk.Metadata{}
			// take all metadata from projects
			for k, v := range p.Metadata {
				m[k] = v
			}
			// then add application metadata
			for k, v := range a.Metadata {
				if _, alreadyExists := m[k]; !alreadyExists {
					m[k] = v
				}
			}

			lines = append(lines, lineMetadata{
				key:          a.ProjectKey,
				name:         a.Name,
				lastModified: a.LastModified,
				metadata:     m,
				additionalInfos: sdk.Metadata{
					"vcs_repofullname": a.RepositoryFullname,
				},
			})
		}
	}
	titles := []string{"project_key", "application_name", "last_modified"}
	titlesAdd := []string{"vcs_repofullname"}
	adminMetadataExport(titles, titlesAdd, lines, c.GetString("export-file"), currentDisplay)
	return nil
}

func adminMetadataApplicationImportRun(c cli.Values) error {
	path := c.GetString("path")

	updateFunc := func(key string, name string, metadata map[string]string) error {
		// get metadata from project
		prj, err := client.ProjectGet(key)
		if err != nil {
			return err
		}
		// remove from application metadata list, the metadata from project
		// this avoid to have the same metadata name on app and on project
		fileteredMetadata := sdk.Metadata{}
		for k, v := range metadata {
			if _, exist := prj.Metadata[k]; !exist {
				fileteredMetadata[k] = v
			}
		}
		app, err := client.ApplicationGet(key, name)
		if err != nil {
			return err
		}
		app.Metadata = fileteredMetadata
		if err := client.ApplicationUpdate(key, name, app); err != nil {
			fmt.Printf("ERROR on application %s/%s: %v\n", key, app.Name, err)
		} else {
			fmt.Printf("application %s/%s updated with metadata:%v \n", key, app.Name, app.Metadata)
		}

		return nil
	}

	return processMetadata(path, 4, updateFunc)
}

func adminMetadataWorkflowExportRun(c cli.Values) error {
	projects, err := client.ProjectList(false, false)
	if err != nil {
		return err
	}

	var currentDisplay = new(cli.Display)
	currentDisplay.Printf("Gettings projects list...")
	currentDisplay.Do(context.Background())

	modsWfs := []cdsclient.RequestModifier{}
	modsWfs = append(modsWfs, func(r *http.Request) {
		q := r.URL.Query()
		q.Set("minimal", "true")
		r.URL.RawQuery = q.Encode()
	})

	modsProjects := []cdsclient.RequestModifier{}
	modsProjects = append(modsProjects, func(r *http.Request) {
		q := r.URL.Query()
		q.Set("withWorkflowNames", "true")
		r.URL.RawQuery = q.Encode()
	})

	lines := []lineMetadata{}
	for i, p := range projects {
		currentDisplay.Printf("%d/%d - fetching project %s...", i, len(projects), p.Key)
		proj, err := client.ProjectGet(p.Key, modsProjects...)
		if err != nil {
			return err
		}

		for j, name := range proj.WorkflowNames {
			currentDisplay.Printf("%d/%d - %d/%d - fetching workflow %s/%s...", i, len(projects), j, len(proj.WorkflowNames), proj.Key, name.Name)
			w, err := client.WorkflowGet(proj.Key, name.Name, modsWfs...)
			if err != nil {
				return fmt.Errorf("Error while getting %s/%s", proj.Key, name.Name)
			}

			m := sdk.Metadata{}
			// take all metadata from projects
			for k, v := range p.Metadata {
				m[k] = v
			}
			// then add workflow metadata
			for k, v := range w.Metadata {
				if _, alreadyExists := m[k]; !alreadyExists {
					m[k] = v
				}
			}

			lines = append(lines, lineMetadata{
				key:          w.ProjectKey,
				name:         w.Name,
				lastModified: w.LastModified,
				metadata:     m,
			})
		}
	}
	titles := []string{"project_key", "workflow_name", "last_modified"}
	adminMetadataExport(titles, nil, lines, c.GetString("export-file"), currentDisplay)
	return nil
}

func adminMetadataExport(firstTitles, addTitles []string, lines []lineMetadata, filename string, currentDisplay *cli.Display) {
	keysTitle := map[string]string{}
	for _, l := range lines {
		for k := range l.metadata {
			keysTitle[k] = ""
		}
	}

	// sort the title keys
	keys := make([]string, 0, len(keysTitle))
	for k := range keysTitle {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// sort the add title keys
	sort.Strings(addTitles)

	currentDisplay.Printf("Generating file %s...", filename)

	f, err := os.Create(filename)
	if err != nil {
		fmt.Fprintf(os.Stdout, "Error while creating file %s: %v", filename, err)
		return
	}
	defer f.Close()

	// prepare header
	writeLine(f, strings.Join(firstTitles, ";"))
	for _, k := range addTitles {
		writeLine(f, fmt.Sprintf(";%s", k))
	}
	for _, k := range keys {
		writeLine(f, fmt.Sprintf(";%s", k))
	}
	writeLine(f, fmt.Sprintf("\n"))

	for _, l := range lines {
		ptime := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d",
			l.lastModified.Year(), l.lastModified.Month(), l.lastModified.Day(),
			l.lastModified.Hour(), l.lastModified.Minute(), l.lastModified.Second())

		writeLine(f, fmt.Sprintf("%s;%s;%s", l.key, l.name, ptime))

		for _, k := range addTitles {
			writeLine(f, fmt.Sprintf(";%s", l.additionalInfos[k]))
		}

		// if metadata key exists, print it
		nMetadataWrite := 0
		for _, k := range keys {
			if v, exists := l.metadata[k]; exists {
				nMetadataWrite++
				writeLine(f, fmt.Sprintf(";%s", v))
			} else {
				writeLine(f, ";")
			}
		}
		writeLine(f, fmt.Sprintf("\n"))
		fmt.Printf("")
	}
	currentDisplay.Printf("file %s created...\n", filename)
	// sleep 2s to let display the currentDisplay
	time.Sleep(2 * time.Second)
}

func writeLine(fi *os.File, s string) {
	if _, err := io.WriteString(fi, s); err != nil {
		fmt.Fprintf(os.Stdout, "error while writing file: %v\n", err)
	}
}

func processMetadata(path string, nbColumnsToIgnore int, updateFunc func(key, name string, metadata map[string]string) error) error {
	csvFile, err := os.Open(path)
	if err != nil {
		return err
	}
	defer csvFile.Close() //nolint

	reader := csv.NewReader(bufio.NewReader(csvFile))

	metadataKeys := map[int]string{}

	for {
		line, err := reader.Read()
		if err == io.EOF {
			break
		} else if err != nil {
			log.Fatal(err)
		}

		columns := strings.Split(line[0], ";")
		pkey := columns[0]
		name := columns[1]

		// first line: title
		if len(metadataKeys) == 0 {
			// project_key, name, last_update. Metadata begins at 3th position
			for index := nbColumnsToIgnore; index < len(columns); index++ {
				metadataKeys[index-nbColumnsToIgnore] = columns[index]
			}
			continue
		}

		metadata := make(map[string]string, len(metadataKeys))

		for index := 0; index < len(metadataKeys); index++ {
			if index > len(metadataKeys) || nbColumnsToIgnore+index >= len(columns) {
				return fmt.Errorf("CSV File invalid. Please check number of columns on %s;%s", pkey, name)
			}
			metadata[metadataKeys[index]] = columns[nbColumnsToIgnore+index]
		}
		if err := updateFunc(pkey, name, metadata); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package container

import (
	"context"
	"fmt"
	"io"
	"os"
	"runtime"
	"strings"
	"syscall"

	"github.com/docker/cli/cli"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/opts"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/pkg/signal"
	"github.com/moby/term"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

type runOptions struct {
	createOptions
	detach     bool
	sigProxy   bool
	detachKeys string
}

// NewRunCommand create a new `docker run` command
func NewRunCommand(dockerCli command.Cli) *cobra.Command {
	var opts runOptions
	var copts *containerOptions

	cmd := &cobra.Command{
		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
		Short: "Run a command in a new container",
		Args:  cli.RequiresMinArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			copts.Image = args[0]
			if len(args) > 1 {
				copts.Args = args[1:]
			}
			return runRun(dockerCli, cmd.Flags(), &opts, copts)
		},
	}

	flags := cmd.Flags()
	flags.SetInterspersed(false)

	// These are flags not stored in Config/HostConfig
	flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID")
	flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
	flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
	flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
	flags.StringVar(&opts.createOptions.pull, "pull", PullImageMissing,
		`Pull image before running ("`+PullImageAlways+`"|"`+PullImageMissing+`"|"`+PullImageNever+`")`)

	// Add an explicit help that doesn't have a `-h` to prevent the conflict
	// with hostname
	flags.Bool("help", false, "Print usage")

	command.AddPlatformFlag(flags, &opts.platform)
	command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
	copts = addFlags(flags)
	return cmd
}

func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
	proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll()))
	newEnv := []string{}
	for k, v := range proxyConfig {
		if v == nil {
			newEnv = append(newEnv, k)
		} else {
			newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
		}
	}
	copts.env = *opts.NewListOptsRef(&newEnv, nil)
	containerConfig, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
	// just in case the parse does not exit
	if err != nil {
		reportError(dockerCli.Err(), "run", err.Error(), true)
		return cli.StatusError{StatusCode: 125}
	}
	if err = validateAPIVersion(containerConfig, dockerCli.Client().ClientVersion()); err != nil {
		reportError(dockerCli.Err(), "run", err.Error(), true)
		return cli.StatusError{StatusCode: 125}
	}
	return runContainer(dockerCli, ropts, copts, containerConfig)
}

// nolint: gocyclo
func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
	config := containerConfig.Config
	hostConfig := containerConfig.HostConfig
	stdout, stderr := dockerCli.Out(), dockerCli.Err()
	client := dockerCli.Client()

	config.ArgsEscaped = false

	if !opts.detach {
		if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
			return err
		}
	} else {
		if copts.attach.Len() != 0 {
			return errors.New("Conflicting options: -a and -d")
		}

		config.AttachStdin = false
		config.AttachStdout = false
		config.AttachStderr = false
		config.StdinOnce = false
	}

	// Telling the Windows daemon the initial size of the tty during start makes
	// a far better user experience rather than relying on subsequent resizes
	// to cause things to catch up.
	if runtime.GOOS == "windows" {
		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
	}

	ctx, cancelFun := context.WithCancel(context.Background())
	defer cancelFun()

	createResponse, err := createContainer(ctx, dockerCli, containerConfig, &opts.createOptions)
	if err != nil {
		reportError(stderr, "run", err.Error(), true)
		return runStartContainerErr(err)
	}
	if opts.sigProxy {
		sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID)
		defer signal.StopCatch(sigc)
	}

	var (
		waitDisplayID chan struct{}
		errCh         chan error
	)
	if !config.AttachStdout && !config.AttachStderr {
		// Make this asynchronous to allow the client to write to stdin before having to read the ID
		waitDisplayID = make(chan struct{})
		go func() {
			defer close(waitDisplayID)
			fmt.Fprintln(stdout, createResponse.ID)
		}()
	}
	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
	if attach {
		if opts.detachKeys != "" {
			dockerCli.ConfigFile().DetachKeys = opts.detachKeys
		}

		close, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID)

		if err != nil {
			return err
		}
		defer close()
	}

	statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)

	// start the container
	if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
		// If we have hijackedIOStreamer, we should notify
		// hijackedIOStreamer we are going to exit and wait
		// to avoid the terminal are not restored.
		if attach {
			cancelFun()
			<-errCh
		}

		reportError(stderr, "run", err.Error(), false)
		if copts.autoRemove {
			// wait container to be removed
			<-statusChan
		}
		return runStartContainerErr(err)
	}

	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
		if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
			fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
		}
	}

	if errCh != nil {
		if err := <-errCh; err != nil {
			if _, ok := err.(term.EscapeError); ok {
				// The user entered the detach escape sequence.
				return nil
			}

			logrus.Debugf("Error hijack: %s", err)
			return err
		}
	}

	// Detached mode: wait for the id to be displayed and return.
	if !config.AttachStdout && !config.AttachStderr {
		// Detached mode
		<-waitDisplayID
		return nil
	}

	status := <-statusChan
	if status != 0 {
		return cli.StatusError{StatusCode: status}
	}
	return nil
}

func attachContainer(
	ctx context.Context,
	dockerCli command.Cli,
	errCh *chan error,
	config *container.Config,
	containerID string,
) (func(), error) {
	stdout, stderr := dockerCli.Out(), dockerCli.Err()
	var (
		out, cerr io.Writer
		in        io.ReadCloser
	)
	if config.AttachStdin {
		in = dockerCli.In()
	}
	if config.AttachStdout {
		out = stdout
	}
	if config.AttachStderr {
		if config.Tty {
			cerr = stdout
		} else {
			cerr = stderr
		}
	}

	options := types.ContainerAttachOptions{
		Stream:     true,
		Stdin:      config.AttachStdin,
		Stdout:     config.AttachStdout,
		Stderr:     config.AttachStderr,
		DetachKeys: dockerCli.ConfigFile().DetachKeys,
	}

	resp, errAttach := dockerCli.Client().ContainerAttach(ctx, containerID, options)
	if errAttach != nil {
		return nil, errAttach
	}

	ch := make(chan error, 1)
	*errCh = ch

	go func() {
		ch <- func() error {
			streamer := hijackedIOStreamer{
				streams:      dockerCli,
				inputStream:  in,
				outputStream: out,
				errorStream:  cerr,
				resp:         resp,
				tty:          config.Tty,
				detachKeys:   options.DetachKeys,
			}

			if errHijack := streamer.stream(ctx); errHijack != nil {
				return errHijack
			}
			return errAttach
		}()
	}()
	return resp.Close, nil
}

// reportError is a utility method that prints a user-friendly message
// containing the error that occurred during parsing and a suggestion to get help
func reportError(stderr io.Writer, name string, str string, withHelp bool) {
	str = strings.TrimSuffix(str, ".") + "."
	if withHelp {
		str += "\nSee '" + os.Args[0] + " " + name + " --help'."
	}
	fmt.Fprintf(stderr, "%s: %s\n", os.Args[0], str)
}

// if container start fails with 'not found'/'no such' error, return 127
// if container start fails with 'permission denied' error, return 126
// return 125 for generic docker daemon failures
func runStartContainerErr(err error) error {
	trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
	statusError := cli.StatusError{StatusCode: 125}
	if strings.Contains(trimmedErr, "executable file not found") ||
		strings.Contains(trimmedErr, "no such file or directory") ||
		strings.Contains(trimmedErr, "system cannot find the file specified") {
		statusError = cli.StatusError{StatusCode: 127}
	} else if strings.Contains(trimmedErr, syscall.EACCES.Error()) {
		statusError = cli.StatusError{StatusCode: 126}
	}

	return statusError
}
<commit_msg>Use consistent name for errors<commit_after>package container

import (
	"context"
	"fmt"
	"io"
	"runtime"
	"strings"
	"syscall"

	"github.com/docker/cli/cli"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/opts"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/pkg/signal"
	"github.com/moby/term"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

type runOptions struct {
	createOptions
	detach     bool
	sigProxy   bool
	detachKeys string
}

// NewRunCommand create a new `docker run` command
func NewRunCommand(dockerCli command.Cli) *cobra.Command {
	var opts runOptions
	var copts *containerOptions

	cmd := &cobra.Command{
		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
		Short: "Run a command in a new container",
		Args:  cli.RequiresMinArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			copts.Image = args[0]
			if len(args) > 1 {
				copts.Args = args[1:]
			}
			return runRun(dockerCli, cmd.Flags(), &opts, copts)
		},
	}

	flags := cmd.Flags()
	flags.SetInterspersed(false)

	// These are flags not stored in Config/HostConfig
	flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID")
	flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
	flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
	flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
	flags.StringVar(&opts.createOptions.pull, "pull", PullImageMissing,
		`Pull image before running ("`+PullImageAlways+`"|"`+PullImageMissing+`"|"`+PullImageNever+`")`)

	// Add an explicit help that doesn't have a `-h` to prevent the conflict
	// with hostname
	flags.Bool("help", false, "Print usage")

	command.AddPlatformFlag(flags, &opts.platform)
	command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
	copts = addFlags(flags)
	return cmd
}

func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
	proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll()))
	newEnv := []string{}
	for k, v := range proxyConfig {
		if v == nil {
			newEnv = append(newEnv, k)
		} else {
			newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
		}
	}
	copts.env = *opts.NewListOptsRef(&newEnv, nil)
	containerConfig, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
	// just in case the parse does not exit
	if err != nil {
		reportError(dockerCli.Err(), "run", err.Error(), true)
		return cli.StatusError{StatusCode: 125}
	}
	if err = validateAPIVersion(containerConfig, dockerCli.Client().ClientVersion()); err != nil {
		reportError(dockerCli.Err(), "run", err.Error(), true)
		return cli.StatusError{StatusCode: 125}
	}
	return runContainer(dockerCli, ropts, copts, containerConfig)
}

// nolint: gocyclo
func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
	config := containerConfig.Config
	hostConfig := containerConfig.HostConfig
	stdout, stderr := dockerCli.Out(), dockerCli.Err()
	client := dockerCli.Client()

	config.ArgsEscaped = false

	if !opts.detach {
		if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
			return err
		}
	} else {
		if copts.attach.Len() != 0 {
			return errors.New("Conflicting options: -a and -d")
		}

		config.AttachStdin = false
		config.AttachStdout = false
		config.AttachStderr = false
		config.StdinOnce = false
	}

	// Telling the Windows daemon the initial size of the tty during start makes
	// a far better user experience rather than relying on subsequent resizes
	// to cause things to catch up.
	if runtime.GOOS == "windows" {
		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
	}

	ctx, cancelFun := context.WithCancel(context.Background())
	defer cancelFun()

	createResponse, err := createContainer(ctx, dockerCli, containerConfig, &opts.createOptions)
	if err != nil {
		reportError(stderr, "run", err.Error(), true)
		return runStartContainerErr(err)
	}
	if opts.sigProxy {
		sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID)
		defer signal.StopCatch(sigc)
	}

	var (
		waitDisplayID chan struct{}
		errCh         chan error
	)
	if !config.AttachStdout && !config.AttachStderr {
		// Make this asynchronous to allow the client to write to stdin before having to read the ID
		waitDisplayID = make(chan struct{})
		go func() {
			defer close(waitDisplayID)
			fmt.Fprintln(stdout, createResponse.ID)
		}()
	}
	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
	if attach {
		if opts.detachKeys != "" {
			dockerCli.ConfigFile().DetachKeys = opts.detachKeys
		}

		close, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID)

		if err != nil {
			return err
		}
		defer close()
	}

	statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)

	// start the container
	if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
		// If we have hijackedIOStreamer, we should notify
		// hijackedIOStreamer we are going to exit and wait
		// to avoid the terminal are not restored.
		if attach {
			cancelFun()
			<-errCh
		}

		reportError(stderr, "run", err.Error(), false)
		if copts.autoRemove {
			// wait container to be removed
			<-statusChan
		}
		return runStartContainerErr(err)
	}

	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
		if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
			fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
		}
	}

	if errCh != nil {
		if err := <-errCh; err != nil {
			if _, ok := err.(term.EscapeError); ok {
				// The user entered the detach escape sequence.
				return nil
			}

			logrus.Debugf("Error hijack: %s", err)
			return err
		}
	}

	// Detached mode: wait for the id to be displayed and return.
	if !config.AttachStdout && !config.AttachStderr {
		// Detached mode
		<-waitDisplayID
		return nil
	}

	status := <-statusChan
	if status != 0 {
		return cli.StatusError{StatusCode: status}
	}
	return nil
}

func attachContainer(
	ctx context.Context,
	dockerCli command.Cli,
	errCh *chan error,
	config *container.Config,
	containerID string,
) (func(), error) {
	stdout, stderr := dockerCli.Out(), dockerCli.Err()
	var (
		out, cerr io.Writer
		in        io.ReadCloser
	)
	if config.AttachStdin {
		in = dockerCli.In()
	}
	if config.AttachStdout {
		out = stdout
	}
	if config.AttachStderr {
		if config.Tty {
			cerr = stdout
		} else {
			cerr = stderr
		}
	}

	options := types.ContainerAttachOptions{
		Stream:     true,
		Stdin:      config.AttachStdin,
		Stdout:     config.AttachStdout,
		Stderr:     config.AttachStderr,
		DetachKeys: dockerCli.ConfigFile().DetachKeys,
	}

	resp, errAttach := dockerCli.Client().ContainerAttach(ctx, containerID, options)
	if errAttach != nil {
		return nil, errAttach
	}

	ch := make(chan error, 1)
	*errCh = ch

	go func() {
		ch <- func() error {
			streamer := hijackedIOStreamer{
				streams:      dockerCli,
				inputStream:  in,
				outputStream: out,
				errorStream:  cerr,
				resp:         resp,
				tty:          config.Tty,
				detachKeys:   options.DetachKeys,
			}

			if errHijack := streamer.stream(ctx); errHijack != nil {
				return errHijack
			}
			return errAttach
		}()
	}()
	return resp.Close, nil
}

// reportError is a utility method that prints a user-friendly message
// containing the error that occurred during parsing and a suggestion to get help
func reportError(stderr io.Writer, name string, str string, withHelp bool) {
	str = strings.TrimSuffix(str, ".") + "."
	if withHelp {
		str += "\nSee 'docker " + name + " --help'."
	}
	fmt.Fprintln(stderr, "docker:", str)
}

// if container start fails with 'not found'/'no such' error, return 127
// if container start fails with 'permission denied' error, return 126
// return 125 for generic docker daemon failures
func runStartContainerErr(err error) error {
	trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
	statusError := cli.StatusError{StatusCode: 125}
	if strings.Contains(trimmedErr, "executable file not found") ||
		strings.Contains(trimmedErr, "no such file or directory") ||
		strings.Contains(trimmedErr, "system cannot find the file specified") {
		statusError = cli.StatusError{StatusCode: 127}
	} else if strings.Contains(trimmedErr, syscall.EACCES.Error()) {
		statusError = cli.StatusError{StatusCode: 126}
	}

	return statusError
}
<|endoftext|>


<commit_before>package ssh

import (
	"testing"

	"github.com/dorzheh/infra/comm/gssh/common"
)

func TestRun(t *testing.T) {
	conf := &common.Config{
		Host:        "127.0.0.1",
		Port:        "22",
		User:        "root",
		Password:    "d",
		PrvtKeyFile: "",
	}
	c := NewClient(conf)
	if err := c.Run("ls /tmp"); err != nil {
		t.Fatal(err)
	}
	if err := c.Upload("/etc/hosts", "/tmp/hosts"); err != nil {
		t.Fatal(err)
	}
}
<commit_msg>Update ssh_client_test.go<commit_after>package ssh

import (
	"testing"

	"github.com/dorzheh/infra/comm/gssh/common"
)

func TestRun(t *testing.T) {
	conf := &common.Config{
		Host:        "127.0.0.1",
		Port:        "22",
		User:        "user",
		Password:    "password",
		PrvtKeyFile: "",
	}
	c := NewClient(conf)
	if err := c.Run("ls /tmp"); err != nil {
		t.Fatal(err)
	}
	if err := c.Upload("/etc/hosts", "/tmp/hosts"); err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package spotify

import (
	"github.com/fabiofalci/sconsify/infrastructure"
	"github.com/fabiofalci/sconsify/sconsify"
	sp "github.com/op/go-libspotify/spotify"
	webspotify "github.com/zmb3/spotify"
	"time"
	"strings"
)

func (spotify *Spotify) shutdownSpotify() {
	spotify.session.Logout()
	spotify.initCache()
	spotify.events.ShutdownEngine()
}

func (spotify *Spotify) play(trackUri *sconsify.Track) {
	player := spotify.session.Player()
	if (!spotify.paused) {
		link, err := spotify.session.ParseLink(trackUri.URI)
		if err != nil {
			return
		}

		track, err := link.Track()
		if err != nil {
			return
		}

		if trackUri.IsPartial() {
			trackUri = sconsify.ToSconsifyTrack(track)
		}

		if !spotify.isTrackAvailable(track) {
			if trackUri.IsFromWebApi() {
				retry := trackUri.RetryLoading()
				if retry < 4 {
					go func() {
						time.Sleep(100 * time.Millisecond)
						spotify.events.Play(trackUri)
					}()
					return
				}
			}
			spotify.events.TrackNotAvailable(trackUri)
			return
		}
		if err := player.Load(track); err != nil {
			return
		}

	}
	player.Play()

	spotify.events.TrackPlaying(trackUri)
	spotify.currentTrack = trackUri
	spotify.paused = false
	return
}

func (spotify *Spotify) isTrackAvailable(track *sp.Track) bool {
	return track.Availability() == sp.TrackAvailabilityAvailable
}

func (spotify *Spotify) search(query string) {
	playlists := sconsify.InitPlaylists()

	query = checkAlias(query)
	name := " " + query

	playlist := sconsify.InitSearchPlaylist(name, name, func(playlist *sconsify.Playlist) {
		options := createWebSpotifyOptions(50, playlist.Tracks())
		if searchResult, err := spotify.getWebApiClient().SearchOpt(query, webspotify.SearchTypeTrack, options); err == nil {
			numberOfTracks := len(searchResult.Tracks.Tracks)
			infrastructure.Debugf("Search '%v' returned %v track(s)", query, numberOfTracks)
			for _, track := range searchResult.Tracks.Tracks {
				webArtist := track.Artists[0]
				artist := sconsify.InitArtist(string(webArtist.URI), webArtist.Name)
				playlist.AddTrack(sconsify.InitWebApiTrack(string(track.URI), artist, track.Name, track.TimeDuration().String()))
				infrastructure.Debugf("\tTrack '%v' (%v)", track.URI, track.Name)
			}
		} else {
			infrastructure.Debugf("Spotify search returning error: %v", err)
		}
	})
	playlist.ExecuteLoad()
	playlists.AddPlaylist(playlist)

	spotify.events.NewPlaylist(playlists)
}

func checkAlias(query string) string {
	if strings.HasPrefix(query, "ar:") {
		return strings.Replace(query, "ar:", "artist:", 1)
	} else if strings.HasPrefix(query, "al:") {
		return strings.Replace(query, "al:", "album:", 1)
	} else if strings.HasPrefix(query, "tr:") {
		return strings.Replace(query, "tr:", "track:", 1)
	}
	return query
}

func (spotify *Spotify) getWebApiClient() *webspotify.Client {
	if spotify.client != nil {
		return spotify.client
	}
	return webspotify.DefaultClient
}

func (spotify *Spotify) pause() {
	if spotify.currentTrack != nil {
		if spotify.paused {
			spotify.play(spotify.currentTrack)
		} else {
			spotify.pauseCurrentTrack()
		}
	}
}

func (spotify *Spotify) pauseCurrentTrack() {
	player := spotify.session.Player()
	player.Pause()
	spotify.events.TrackPaused(spotify.currentTrack)
	spotify.paused = true
}

func (spotify *Spotify) artistAlbums(artist *sconsify.Artist) {
	if simpleAlbumPage, err := spotify.client.GetArtistAlbums(webspotify.ID(artist.GetSpotifyID())); err == nil {
		folder := sconsify.InitFolder(artist.URI, "*"+artist.Name, make([]*sconsify.Playlist, 0))

		if fullTracks, err := spotify.client.GetArtistsTopTracks(webspotify.ID(artist.GetSpotifyID()), "GB"); err == nil {
			tracks := make([]*sconsify.Track, len(fullTracks))
			for i, track := range fullTracks {
				tracks[i] = sconsify.InitWebApiTrack(string(track.URI), artist, track.Name, track.TimeDuration().String())
			}

			folder.AddPlaylist(sconsify.InitPlaylist(artist.URI, " "+artist.Name+" Top Tracks", tracks))
		}

		infrastructure.Debugf("# of albums %v", len(simpleAlbumPage.Albums))
		for _, simpleAlbum := range simpleAlbumPage.Albums {
			infrastructure.Debugf("AlbumsID %v = %v", simpleAlbum.URI, simpleAlbum.Name)
			playlist := sconsify.InitOnDemandPlaylist(string(simpleAlbum.URI), " "+simpleAlbum.Name, true, func(playlist *sconsify.Playlist) {
				infrastructure.Debugf("Album id %v", playlist.ToSpotifyID())
				if simpleTrackPage, err := spotify.client.GetAlbumTracks(webspotify.ID(playlist.ToSpotifyID())); err == nil {
					infrastructure.Debugf("# of tracks %v", len(simpleTrackPage.Tracks))
					for _, track := range simpleTrackPage.Tracks {
						playlist.AddTrack(sconsify.InitWebApiTrack(string(track.URI), artist, track.Name, track.TimeDuration().String()))
					}
				}
			})
			folder.AddPlaylist(playlist)
		}

		spotify.events.ArtistAlbums(folder)
	}
}
<commit_msg>UI: BUG: Fix pressing play after pausing<commit_after>package spotify

import (
	"github.com/fabiofalci/sconsify/infrastructure"
	"github.com/fabiofalci/sconsify/sconsify"
	sp "github.com/op/go-libspotify/spotify"
	webspotify "github.com/zmb3/spotify"
	"strings"
	"time"
)

func (spotify *Spotify) shutdownSpotify() {
	spotify.session.Logout()
	spotify.initCache()
	spotify.events.ShutdownEngine()
}

func (spotify *Spotify) play(trackUri *sconsify.Track) {
	player := spotify.session.Player()
	if !spotify.paused || spotify.currentTrack != trackUri {
		link, err := spotify.session.ParseLink(trackUri.URI)
		if err != nil {
			return
		}

		track, err := link.Track()
		if err != nil {
			return
		}

		if trackUri.IsPartial() {
			trackUri = sconsify.ToSconsifyTrack(track)
		}

		if !spotify.isTrackAvailable(track) {
			if trackUri.IsFromWebApi() {
				retry := trackUri.RetryLoading()
				if retry < 4 {
					go func() {
						time.Sleep(100 * time.Millisecond)
						spotify.events.Play(trackUri)
					}()
					return
				}
			}
			spotify.events.TrackNotAvailable(trackUri)
			return
		}
		if err := player.Load(track); err != nil {
			return
		}

	}
	player.Play()

	spotify.events.TrackPlaying(trackUri)
	spotify.currentTrack = trackUri
	spotify.paused = false
	return
}

func (spotify *Spotify) isTrackAvailable(track *sp.Track) bool {
	return track.Availability() == sp.TrackAvailabilityAvailable
}

func (spotify *Spotify) search(query string) {
	playlists := sconsify.InitPlaylists()

	query = checkAlias(query)
	name := " " + query

	playlist := sconsify.InitSearchPlaylist(name, name, func(playlist *sconsify.Playlist) {
		options := createWebSpotifyOptions(50, playlist.Tracks())
		if searchResult, err := spotify.getWebApiClient().SearchOpt(query, webspotify.SearchTypeTrack, options); err == nil {
			numberOfTracks := len(searchResult.Tracks.Tracks)
			infrastructure.Debugf("Search '%v' returned %v track(s)", query, numberOfTracks)
			for _, track := range searchResult.Tracks.Tracks {
				webArtist := track.Artists[0]
				artist := sconsify.InitArtist(string(webArtist.URI), webArtist.Name)
				playlist.AddTrack(sconsify.InitWebApiTrack(string(track.URI), artist, track.Name, track.TimeDuration().String()))
				infrastructure.Debugf("\tTrack '%v' (%v)", track.URI, track.Name)
			}
		} else {
			infrastructure.Debugf("Spotify search returning error: %v", err)
		}
	})
	playlist.ExecuteLoad()
	playlists.AddPlaylist(playlist)

	spotify.events.NewPlaylist(playlists)
}

func checkAlias(query string) string {
	if strings.HasPrefix(query, "ar:") {
		return strings.Replace(query, "ar:", "artist:", 1)
	} else if strings.HasPrefix(query, "al:") {
		return strings.Replace(query, "al:", "album:", 1)
	} else if strings.HasPrefix(query, "tr:") {
		return strings.Replace(query, "tr:", "track:", 1)
	}
	return query
}

func (spotify *Spotify) getWebApiClient() *webspotify.Client {
	if spotify.client != nil {
		return spotify.client
	}
	return webspotify.DefaultClient
}

func (spotify *Spotify) pause() {
	if spotify.currentTrack != nil {
		if spotify.paused {
			spotify.play(spotify.currentTrack)
		} else {
			spotify.pauseCurrentTrack()
		}
	}
}

func (spotify *Spotify) pauseCurrentTrack() {
	player := spotify.session.Player()
	player.Pause()
	spotify.events.TrackPaused(spotify.currentTrack)
	spotify.paused = true
}

func (spotify *Spotify) artistAlbums(artist *sconsify.Artist) {
	if simpleAlbumPage, err := spotify.client.GetArtistAlbums(webspotify.ID(artist.GetSpotifyID())); err == nil {
		folder := sconsify.InitFolder(artist.URI, "*"+artist.Name, make([]*sconsify.Playlist, 0))

		if fullTracks, err := spotify.client.GetArtistsTopTracks(webspotify.ID(artist.GetSpotifyID()), "GB"); err == nil {
			tracks := make([]*sconsify.Track, len(fullTracks))
			for i, track := range fullTracks {
				tracks[i] = sconsify.InitWebApiTrack(string(track.URI), artist, track.Name, track.TimeDuration().String())
			}

			folder.AddPlaylist(sconsify.InitPlaylist(artist.URI, " "+artist.Name+" Top Tracks", tracks))
		}

		infrastructure.Debugf("# of albums %v", len(simpleAlbumPage.Albums))
		for _, simpleAlbum := range simpleAlbumPage.Albums {
			infrastructure.Debugf("AlbumsID %v = %v", simpleAlbum.URI, simpleAlbum.Name)
			playlist := sconsify.InitOnDemandPlaylist(string(simpleAlbum.URI), " "+simpleAlbum.Name, true, func(playlist *sconsify.Playlist) {
				infrastructure.Debugf("Album id %v", playlist.ToSpotifyID())
				if simpleTrackPage, err := spotify.client.GetAlbumTracks(webspotify.ID(playlist.ToSpotifyID())); err == nil {
					infrastructure.Debugf("# of tracks %v", len(simpleTrackPage.Tracks))
					for _, track := range simpleTrackPage.Tracks {
						playlist.AddTrack(sconsify.InitWebApiTrack(string(track.URI), artist, track.Name, track.TimeDuration().String()))
					}
				}
			})
			folder.AddPlaylist(playlist)
		}

		spotify.events.ArtistAlbums(folder)
	}
}
<|endoftext|>


<commit_before>// This worker does not model the way qless does it.
// I more or less modeled it after my own needs.
package goqless

import (
	// "encoding/json"
	"fmt"
	// "github.com/garyburd/redigo/redis"
	"errors"
	"log"
	"reflect"
	"strconv"
	"sync"
	"time"
)

func init() {
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}

type JobFunc func(*Job) error
type JobCallback func(*Job) error

type Worker struct {
	Interval int // in time.Duration

	funcs map[string]JobFunc
	queue *Queue
	// events *Events

	cli *Client
}

func NewWorker(cli *Client, queueName string, interval int) *Worker {
	w := &Worker{
		Interval: interval,
		funcs:    make(map[string]JobFunc),
		// events: c.Events(),
		cli: cli,
	}

	w.queue = cli.Queue(queueName)

	return w
}

func heartbeatStart(job *Job, done chan bool, heartbeat int, clientLock sync.Mutex) {
	tick := time.NewTicker(time.Duration(heartbeat) * time.Duration(time.Second))
	for {
		select {
		case <-done:
			tick.Stop()
			return
		case <-tick.C:
			clientLock.Lock()
			success, err := job.Heartbeat()
			clientLock.Unlock()
			if err != nil {
				log.Printf("warning, slow, send heartbeat***jid:%v, queue:%v, success:%v, error:%v",
					job.Jid, job.Queue, success, err)
			} else {
				log.Printf("warning, slow, send heartbeat***jid:%v, queue:%v, success:%v",
					job.Jid, job.Queue, success)
			}
		}
	}
}

func (w *Worker) Start() error {
	// log.Println("worker Start")
	var clientLock sync.Mutex

	heartbeatStr, err := w.cli.GetConfig("heartbeat")
	heartbeat, err := strconv.Atoi(heartbeatStr)
	//log.Println("heartbeatStr:", heartbeat)
	if err != nil {
		heartbeat = 60
		log.Println(err)
	}

	heartbeat /= 2

	err = func(q *Queue) error {
		for {
			clientLock.Lock()
			jobs, err := q.Pop(1) //we may pop more if fast enough
			clientLock.Unlock()

			if err != nil {
				log.Println(err)
				return err
			}

			if len(jobs) == 0 {
				time.Sleep(time.Duration(w.Interval) * time.Millisecond)
				continue
			}

			for i := 0; i < len(jobs); i++ {
				if len(jobs[i].History) > 2 {
					log.Printf("warning, multiple processed exist %+v\n", jobs[i])
				}
				done := make(chan bool)
				//todo: using seprate connection to send heartbeat
				go heartbeatStart(jobs[i], done, heartbeat, clientLock)
				f, ok := w.funcs[jobs[i].Klass]
				if !ok { //we got a job that not belongs to us
					done <- false
					log.Fatalln("got a message not belongs to us, queue", q.Name, jobs[i])
					continue
				}

				err := f(jobs[i])
				if err != nil {
					// TODO: probably do something with this
					log.Println("error: job failed, id", jobs[i].Jid, "queue", jobs[i].Queue, err.Error())
					clientLock.Lock()
					success, err := jobs[i].Fail("fail", err.Error())
					clientLock.Unlock()
					done <- false
					if err != nil {
						log.Printf("fail job:%+v success:%v, error:%v",
							jobs[i], success, err)
						return err
					}
				} else {
					clientLock.Lock()
					success, err := jobs[i].Complete()
					clientLock.Unlock()
					done <- true
					if err != nil {
						log.Printf("complete job:%+v success:%v, error:%v",
							jobs[i], success, err)
						return err
					}
					//log.Printf("===job:%+v", jobs[0])
				}
			}
		}
		return nil
	}(w.queue)

	return err
}

func (w *Worker) AddFunc(name string, f JobFunc) error {
	if _, ok := w.funcs[name]; ok {
		return fmt.Errorf("function \"%s\" already exists", name)
	}

	w.funcs[name] = f
	return nil
}

// Adds all the methods in the passed interface as job functions.
// Job names are in the form of: name.methodname
func (w *Worker) AddService(name string, rcvr interface{}) error {
	typ := reflect.TypeOf(rcvr)
	val := reflect.ValueOf(rcvr)
	for i := 0; i < typ.NumMethod(); i++ {
		method := typ.Method(i)
		w.AddFunc(name+"."+method.Name, func(job *Job) error {
			ret := method.Func.Call([]reflect.Value{val, reflect.ValueOf(job)})
			if len(ret) > 0 {
				if err, ok := ret[0].Interface().(error); ok {
					return err
				}
			} else {
				errStr := "reflect len less than zero." + strconv.Itoa(len(ret))
				return errors.New(errStr)
			}

			return nil
		})
	}

	return nil
}
<commit_msg>only log jid<commit_after>// This worker does not model the way qless does it.
// I more or less modeled it after my own needs.
package goqless

import (
	// "encoding/json"
	"fmt"
	// "github.com/garyburd/redigo/redis"
	"errors"
	"log"
	"reflect"
	"strconv"
	"sync"
	"time"
)

func init() {
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}

type JobFunc func(*Job) error
type JobCallback func(*Job) error

type Worker struct {
	Interval int // in time.Duration

	funcs map[string]JobFunc
	queue *Queue
	// events *Events

	cli *Client
}

func NewWorker(cli *Client, queueName string, interval int) *Worker {
	w := &Worker{
		Interval: interval,
		funcs:    make(map[string]JobFunc),
		// events: c.Events(),
		cli: cli,
	}

	w.queue = cli.Queue(queueName)

	return w
}

func heartbeatStart(job *Job, done chan bool, heartbeat int, clientLock sync.Mutex) {
	tick := time.NewTicker(time.Duration(heartbeat) * time.Duration(time.Second))
	for {
		select {
		case <-done:
			tick.Stop()
			return
		case <-tick.C:
			clientLock.Lock()
			success, err := job.Heartbeat()
			clientLock.Unlock()
			if err != nil {
				log.Printf("warning, slow, send heartbeat***jid:%v, queue:%v, success:%v, error:%v",
					job.Jid, job.Queue, success, err)
			} else {
				log.Printf("warning, slow, send heartbeat***jid:%v, queue:%v, success:%v",
					job.Jid, job.Queue, success)
			}
		}
	}
}

func (w *Worker) Start() error {
	// log.Println("worker Start")
	var clientLock sync.Mutex

	heartbeatStr, err := w.cli.GetConfig("heartbeat")
	heartbeat, err := strconv.Atoi(heartbeatStr)
	//log.Println("heartbeatStr:", heartbeat)
	if err != nil {
		heartbeat = 60
		log.Println(err)
	}

	heartbeat /= 2

	err = func(q *Queue) error {
		for {
			clientLock.Lock()
			jobs, err := q.Pop(1) //we may pop more if fast enough
			clientLock.Unlock()

			if err != nil {
				log.Println(err)
				return err
			}

			if len(jobs) == 0 {
				time.Sleep(time.Duration(w.Interval) * time.Millisecond)
				continue
			}

			for i := 0; i < len(jobs); i++ {
				if len(jobs[i].History) > 2 {
					log.Printf("warning, multiple processed exist %+v\n", jobs[i])
				}
				done := make(chan bool)
				//todo: using seprate connection to send heartbeat
				go heartbeatStart(jobs[i], done, heartbeat, clientLock)
				f, ok := w.funcs[jobs[i].Klass]
				if !ok { //we got a job that not belongs to us
					done <- false
					log.Fatalln("got a message not belongs to us, queue", q.Name, jobs[i])
					continue
				}

				err := f(jobs[i])
				if err != nil {
					// TODO: probably do something with this
					log.Println("error: job failed, id", jobs[i].Jid, "queue", jobs[i].Queue, err.Error())
					clientLock.Lock()
					success, err := jobs[i].Fail("fail", err.Error())
					clientLock.Unlock()
					done <- false
					if err != nil {
						log.Printf("fail job:%+v success:%v, error:%v",
							jobs[i], success, err)
						return err
					}
				} else {
					clientLock.Lock()
					success, err := jobs[i].Complete()
					clientLock.Unlock()
					done <- true
					if err != nil {
						log.Printf("complete job:%+v success:%v, error:%v",
							jobs[i].Jid, success, err)
						return err
					}
					//log.Printf("===job:%+v", jobs[0])
				}
			}
		}
		return nil
	}(w.queue)

	return err
}

func (w *Worker) AddFunc(name string, f JobFunc) error {
	if _, ok := w.funcs[name]; ok {
		return fmt.Errorf("function \"%s\" already exists", name)
	}

	w.funcs[name] = f
	return nil
}

// Adds all the methods in the passed interface as job functions.
// Job names are in the form of: name.methodname
func (w *Worker) AddService(name string, rcvr interface{}) error {
	typ := reflect.TypeOf(rcvr)
	val := reflect.ValueOf(rcvr)
	for i := 0; i < typ.NumMethod(); i++ {
		method := typ.Method(i)
		w.AddFunc(name+"."+method.Name, func(job *Job) error {
			ret := method.Func.Call([]reflect.Value{val, reflect.ValueOf(job)})
			if len(ret) > 0 {
				if err, ok := ret[0].Interface().(error); ok {
					return err
				}
			} else {
				errStr := "reflect len less than zero." + strconv.Itoa(len(ret))
				return errors.New(errStr)
			}

			return nil
		})
	}

	return nil
}
<|endoftext|>