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

package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"strings"

	"github.com/maruel/subcommands"
	"google.golang.org/genproto/protobuf/field_mask"
	"google.golang.org/grpc/codes"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/auth/client/authcli"
	"go.chromium.org/luci/buildbucket/protoutil"
	"go.chromium.org/luci/cipd/version"
	"go.chromium.org/luci/common/api/gerrit"
	"go.chromium.org/luci/common/lhttp"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/grpc/prpc"

	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
	gerritpb "go.chromium.org/luci/common/proto/gerrit"
)

type baseCommandRun struct {
	subcommands.CommandRunBase
	authFlags authcli.Flags
	host      string
	json      bool
	noColor   bool
}

func (r *baseCommandRun) RegisterGlobalFlags(defaultAuthOpts auth.Options) {
	r.Flags.StringVar(
		&r.host,
		"host",
		"cr-buildbucket.appspot.com",
		"Host for the buildbucket service instance.")
	r.Flags.BoolVar(
		&r.json,
		"json",
		false,
		"Print information in JSON format.")
	r.Flags.BoolVar(
		&r.noColor,
		"nocolor",
		false,
		"Disable coloration.")
	r.authFlags.Register(&r.Flags, defaultAuthOpts)
}

func (r *baseCommandRun) validateHost() error {
	if r.host == "" {
		return fmt.Errorf("a host for the buildbucket service must be provided")
	}
	if strings.ContainsRune(r.host, '/') {
		return fmt.Errorf("invalid host %q", r.host)
	}
	return nil
}

func (r *baseCommandRun) createHTTPClient(ctx context.Context) (*http.Client, error) {
	opts, err := r.authFlags.Options()
	if err != nil {
		return nil, err
	}
	return auth.NewAuthenticator(ctx, auth.SilentLogin, opts).Client()
}

func (r *baseCommandRun) newClient(ctx context.Context) (buildbucketpb.BuildsClient, error) {
	if err := r.validateHost(); err != nil {
		return nil, err
	}

	httpClient, err := r.createHTTPClient(ctx)
	if err != nil {
		return nil, err
	}

	opts := prpc.DefaultOptions()
	opts.Insecure = lhttp.IsLocalHost(r.host)

	info, err := version.GetCurrentVersion()
	if err != nil {
		return nil, err
	}
	opts.UserAgent = fmt.Sprintf("buildbucket CLI, instanceID=%q", info.InstanceID)

	return buildbucketpb.NewBuildsPRPCClient(&prpc.Client{
		C:       httpClient,
		Host:    r.host,
		Options: opts,
	}), nil
}

// batchAndDone executes req and prints the response.
func (r *baseCommandRun) batchAndDone(ctx context.Context, req *buildbucketpb.BatchRequest) int {
	client, err := r.newClient(ctx)
	if err != nil {
		return r.done(ctx, err)
	}

	res, err := client.Batch(ctx, req)
	if err != nil {
		return r.done(ctx, err)
	}

	hasErr := false
	p := newStdoutPrinter(r.noColor)
	for i, res := range res.Responses {
		var build *buildbucketpb.Build
		switch res := res.Response.(type) {

		case *buildbucketpb.BatchResponse_Response_Error:
			hasErr = true

			var requestTitle string
			switch req := req.Requests[i].Request.(type) {
			case *buildbucketpb.BatchRequest_Request_GetBuild:
				r := req.GetBuild
				if r.Id != 0 {
					requestTitle = fmt.Sprintf("build %d", r.Id)
				} else {
					requestTitle = fmt.Sprintf(`build "%s/%d"`, protoutil.FormatBuilderID(r.Builder), r.BuildNumber)
				}

			case *buildbucketpb.BatchRequest_Request_CancelBuild:
				requestTitle = fmt.Sprintf("build %d", req.CancelBuild.Id)

			default:
				requestTitle = fmt.Sprintf("request #%d", i)
			}

			fmt.Fprintf(os.Stderr, "%s: %s: %s\n", requestTitle, codes.Code(res.Error.Code), res.Error.Message)
			continue

		case *buildbucketpb.BatchResponse_Response_GetBuild:
			build = res.GetBuild
		case *buildbucketpb.BatchResponse_Response_CancelBuild:
			build = res.CancelBuild
		case *buildbucketpb.BatchResponse_Response_ScheduleBuild:
			build = res.ScheduleBuild
		default:
			panic("forgot to update batchAndDone()?")
		}

		if r.json {
			p.JSONPB(build)
		} else {
			if i > 0 {
				p.f("\n")
			}
			p.Build(build)
		}
	}
	if hasErr {
		return 1
	}
	return 0
}

func (r *baseCommandRun) done(ctx context.Context, err error) int {
	if err != nil {
		logging.Errorf(ctx, "%s", err)
		return 1
	}
	return 0
}

// retrieveCL retrieves GerritChange from a string.
// Makes a Gerrit RPC if necessary.
func (r *baseCommandRun) retrieveCL(ctx context.Context, cl string) (*buildbucketpb.GerritChange, error) {
	ret, err := parseCL(cl)
	if err != nil {
		return nil, err
	}

	if ret.Project != "" && ret.Patchset != 0 {
		return ret, nil
	}

	// Fetch CL info from Gerrit.
	httpClient, err := r.createHTTPClient(ctx)
	if err != nil {
		return nil, err
	}
	client, err := gerrit.NewRESTClient(httpClient, ret.Host, true)
	if err != nil {
		return nil, err
	}
	change, err := client.GetChange(ctx, &gerritpb.GetChangeRequest{
		Number:  ret.Change,
		Options: []gerritpb.QueryOption{gerritpb.QueryOption_CURRENT_REVISION},
	})
	if err != nil {
		return nil, fmt.Errorf("failed to fetch CL %d from %q: %s", ret.Change, ret.Host, err)
	}

	ret.Project = change.Project
	ret.Patchset = int64(change.Revisions[change.CurrentRevision].Number)
	return ret, nil
}

// retrieveBuildIDs converts build arguments to int64 build ids,
// where a build argument can be an int64 build or a
// "<project>/<bucket>/<builder>/<build_number>" string.
func (r *baseCommandRun) retrieveBuildIDs(ctx context.Context, builds []string) (buildIDs []int64, err error) {
	return retrieveBuildIDs(builds, func(req *buildbucketpb.BatchRequest) (*buildbucketpb.BatchResponse, error) {
		client, err := r.newClient(ctx)
		if err != nil {
			return nil, err
		}
		return client.Batch(ctx, req)
	})
}

func retrieveBuildIDs(builds []string, callBatch func(*buildbucketpb.BatchRequest) (*buildbucketpb.BatchResponse, error)) (buildIDs []int64, err error) {
	buildIDs = make([]int64, len(builds))
	batchReq := &buildbucketpb.BatchRequest{
		Requests: make([]*buildbucketpb.BatchRequest_Request, 0, len(builds)),
	}
	indexes := make([]int, 0, len(builds))
	idFieldMask := &field_mask.FieldMask{Paths: []string{"id"}}
	for i, b := range builds {
		getBuild, err := protoutil.ParseGetBuildRequest(b)
		if err != nil {
			return nil, fmt.Errorf("invalid build %q: %s", b, err)
		}
		if getBuild.Builder == nil {
			buildIDs[i] = getBuild.Id
		} else {
			getBuild.Fields = idFieldMask
			batchReq.Requests = append(batchReq.Requests, &buildbucketpb.BatchRequest_Request{
				Request: &buildbucketpb.BatchRequest_Request_GetBuild{GetBuild: getBuild},
			})
			indexes = append(indexes, i)
		}
	}

	if len(batchReq.Requests) == 0 {
		return buildIDs, nil
	}

	res, err := callBatch(batchReq)
	for i, res := range res.Responses {
		j := indexes[i]
		switch codes.Code(res.GetError().GetCode()) {
		case codes.OK:
			buildIDs[j] = res.GetGetBuild().Id
		case codes.NotFound:
			return nil, fmt.Errorf("build %q not found", builds[j])
		default:
			return nil, err
		}
	}
	return buildIDs, nil
}
<commit_msg>[bb] Improve request title<commit_after>// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"strings"

	"github.com/maruel/subcommands"
	"google.golang.org/genproto/protobuf/field_mask"
	"google.golang.org/grpc/codes"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/auth/client/authcli"
	"go.chromium.org/luci/buildbucket/protoutil"
	"go.chromium.org/luci/cipd/version"
	"go.chromium.org/luci/common/api/gerrit"
	"go.chromium.org/luci/common/lhttp"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/grpc/prpc"

	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
	gerritpb "go.chromium.org/luci/common/proto/gerrit"
)

type baseCommandRun struct {
	subcommands.CommandRunBase
	authFlags authcli.Flags
	host      string
	json      bool
	noColor   bool
}

func (r *baseCommandRun) RegisterGlobalFlags(defaultAuthOpts auth.Options) {
	r.Flags.StringVar(
		&r.host,
		"host",
		"cr-buildbucket.appspot.com",
		"Host for the buildbucket service instance.")
	r.Flags.BoolVar(
		&r.json,
		"json",
		false,
		"Print information in JSON format.")
	r.Flags.BoolVar(
		&r.noColor,
		"nocolor",
		false,
		"Disable coloration.")
	r.authFlags.Register(&r.Flags, defaultAuthOpts)
}

func (r *baseCommandRun) validateHost() error {
	if r.host == "" {
		return fmt.Errorf("a host for the buildbucket service must be provided")
	}
	if strings.ContainsRune(r.host, '/') {
		return fmt.Errorf("invalid host %q", r.host)
	}
	return nil
}

func (r *baseCommandRun) createHTTPClient(ctx context.Context) (*http.Client, error) {
	opts, err := r.authFlags.Options()
	if err != nil {
		return nil, err
	}
	return auth.NewAuthenticator(ctx, auth.SilentLogin, opts).Client()
}

func (r *baseCommandRun) newClient(ctx context.Context) (buildbucketpb.BuildsClient, error) {
	if err := r.validateHost(); err != nil {
		return nil, err
	}

	httpClient, err := r.createHTTPClient(ctx)
	if err != nil {
		return nil, err
	}

	opts := prpc.DefaultOptions()
	opts.Insecure = lhttp.IsLocalHost(r.host)

	info, err := version.GetCurrentVersion()
	if err != nil {
		return nil, err
	}
	opts.UserAgent = fmt.Sprintf("buildbucket CLI, instanceID=%q", info.InstanceID)

	return buildbucketpb.NewBuildsPRPCClient(&prpc.Client{
		C:       httpClient,
		Host:    r.host,
		Options: opts,
	}), nil
}

// batchAndDone executes req and prints the response.
func (r *baseCommandRun) batchAndDone(ctx context.Context, req *buildbucketpb.BatchRequest) int {
	client, err := r.newClient(ctx)
	if err != nil {
		return r.done(ctx, err)
	}

	res, err := client.Batch(ctx, req)
	if err != nil {
		return r.done(ctx, err)
	}

	stderr := func(format string, args ...interface{}) {
		fmt.Fprintf(os.Stderr, format, args...)
	}

	hasErr := false
	p := newStdoutPrinter(r.noColor)
	for i, res := range res.Responses {
		var build *buildbucketpb.Build
		switch res := res.Response.(type) {

		case *buildbucketpb.BatchResponse_Response_Error:
			hasErr = true

			// If we have multiple requests, print a request title.
			if len(req.Requests) > 1 {
				switch req := req.Requests[i].Request.(type) {
				case *buildbucketpb.BatchRequest_Request_GetBuild:
					r := req.GetBuild
					if r.Id != 0 {
						stderr("build %d", r.Id)
					} else {
						stderr(`build "%s/%d"`, protoutil.FormatBuilderID(r.Builder), r.BuildNumber)
					}

				case *buildbucketpb.BatchRequest_Request_CancelBuild:
					stderr("build %d", req.CancelBuild.Id)

				default:
					stderr("request #%d", i)
				}
				stderr(": ")
			}

			stderr("%s\n", res.Error.Message)
			continue

		case *buildbucketpb.BatchResponse_Response_GetBuild:
			build = res.GetBuild
		case *buildbucketpb.BatchResponse_Response_CancelBuild:
			build = res.CancelBuild
		case *buildbucketpb.BatchResponse_Response_ScheduleBuild:
			build = res.ScheduleBuild
		default:
			panic("forgot to update batchAndDone()?")
		}

		if r.json {
			p.JSONPB(build)
		} else {
			if i > 0 {
				p.f("\n")
			}
			p.Build(build)
		}
	}
	if hasErr {
		return 1
	}
	return 0
}

func (r *baseCommandRun) done(ctx context.Context, err error) int {
	if err != nil {
		logging.Errorf(ctx, "%s", err)
		return 1
	}
	return 0
}

// retrieveCL retrieves GerritChange from a string.
// Makes a Gerrit RPC if necessary.
func (r *baseCommandRun) retrieveCL(ctx context.Context, cl string) (*buildbucketpb.GerritChange, error) {
	ret, err := parseCL(cl)
	if err != nil {
		return nil, err
	}

	if ret.Project != "" && ret.Patchset != 0 {
		return ret, nil
	}

	// Fetch CL info from Gerrit.
	httpClient, err := r.createHTTPClient(ctx)
	if err != nil {
		return nil, err
	}
	client, err := gerrit.NewRESTClient(httpClient, ret.Host, true)
	if err != nil {
		return nil, err
	}
	change, err := client.GetChange(ctx, &gerritpb.GetChangeRequest{
		Number:  ret.Change,
		Options: []gerritpb.QueryOption{gerritpb.QueryOption_CURRENT_REVISION},
	})
	if err != nil {
		return nil, fmt.Errorf("failed to fetch CL %d from %q: %s", ret.Change, ret.Host, err)
	}

	ret.Project = change.Project
	ret.Patchset = int64(change.Revisions[change.CurrentRevision].Number)
	return ret, nil
}

// retrieveBuildIDs converts build arguments to int64 build ids,
// where a build argument can be an int64 build or a
// "<project>/<bucket>/<builder>/<build_number>" string.
func (r *baseCommandRun) retrieveBuildIDs(ctx context.Context, builds []string) (buildIDs []int64, err error) {
	return retrieveBuildIDs(builds, func(req *buildbucketpb.BatchRequest) (*buildbucketpb.BatchResponse, error) {
		client, err := r.newClient(ctx)
		if err != nil {
			return nil, err
		}
		return client.Batch(ctx, req)
	})
}

func retrieveBuildIDs(builds []string, callBatch func(*buildbucketpb.BatchRequest) (*buildbucketpb.BatchResponse, error)) (buildIDs []int64, err error) {
	buildIDs = make([]int64, len(builds))
	batchReq := &buildbucketpb.BatchRequest{
		Requests: make([]*buildbucketpb.BatchRequest_Request, 0, len(builds)),
	}
	indexes := make([]int, 0, len(builds))
	idFieldMask := &field_mask.FieldMask{Paths: []string{"id"}}
	for i, b := range builds {
		getBuild, err := protoutil.ParseGetBuildRequest(b)
		if err != nil {
			return nil, fmt.Errorf("invalid build %q: %s", b, err)
		}
		if getBuild.Builder == nil {
			buildIDs[i] = getBuild.Id
		} else {
			getBuild.Fields = idFieldMask
			batchReq.Requests = append(batchReq.Requests, &buildbucketpb.BatchRequest_Request{
				Request: &buildbucketpb.BatchRequest_Request_GetBuild{GetBuild: getBuild},
			})
			indexes = append(indexes, i)
		}
	}

	if len(batchReq.Requests) == 0 {
		return buildIDs, nil
	}

	res, err := callBatch(batchReq)
	for i, res := range res.Responses {
		j := indexes[i]
		switch codes.Code(res.GetError().GetCode()) {
		case codes.OK:
			buildIDs[j] = res.GetGetBuild().Id
		case codes.NotFound:
			return nil, fmt.Errorf("build %q not found", builds[j])
		default:
			return nil, err
		}
	}
	return buildIDs, nil
}
<|endoftext|>


<commit_before>package common

import (
	gossh "code.google.com/p/go.crypto/ssh"
	"errors"
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/communicator/ssh"
	"github.com/mitchellh/packer/packer"
	"log"
	"time"
)

// StepConnectSSH is a multistep Step implementation that waits for SSH
// to become available. It gets the connection information from a single
// configuration when creating the step.
//
// Uses:
//   ui packer.Ui
//
// Produces:
//   communicator packer.Communicator
type StepConnectSSH struct {
	// SSHAddress is a function that returns the TCP address to connect to
	// for SSH. This is a function so that you can query information
	// if necessary for this address.
	SSHAddress func(map[string]interface{}) (string, error)

	// SSHConfig is a function that returns the proper client configuration
	// for SSH access.
	SSHConfig func(map[string]interface{}) (*gossh.ClientConfig, error)

	// SSHWaitTimeout is the total timeout to wait for SSH to become available.
	SSHWaitTimeout time.Duration

	cancel bool
	comm   packer.Communicator
}

func (s *StepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
	ui := state["ui"].(packer.Ui)

	var comm packer.Communicator
	var err error

	waitDone := make(chan bool, 1)
	go func() {
		ui.Say("Waiting for SSH to become available...")
		comm, err = s.waitForSSH(state)
		waitDone <- true
	}()

	log.Printf("Waiting for SSH, up to timeout: %s", s.SSHWaitTimeout)
	timeout := time.After(s.SSHWaitTimeout)
WaitLoop:
	for {
		// Wait for either SSH to become available, a timeout to occur,
		// or an interrupt to come through.
		select {
		case <-waitDone:
			if err != nil {
				ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
				return multistep.ActionHalt
			}

			ui.Say("Connected to SSH!")
			s.comm = comm
			state["communicator"] = comm
			break WaitLoop
		case <-timeout:
			ui.Error("Timeout waiting for SSH.")
			s.cancel = true
			return multistep.ActionHalt
		case <-time.After(1 * time.Second):
			if _, ok := state[multistep.StateCancelled]; ok {
				log.Println("Interrupt detected, quitting waiting for SSH.")
				return multistep.ActionHalt
			}
		}
	}

	return multistep.ActionContinue
}

func (s *StepConnectSSH) Cleanup(map[string]interface{}) {
}

func (s *StepConnectSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
	handshakeAttempts := 0

	var comm packer.Communicator
	for {
		time.Sleep(5 * time.Second)

		if s.cancel {
			log.Println("SSH wait cancelled. Exiting loop.")
			return nil, errors.New("SSH wait cancelled")
		}

		// First we request the TCP connection information
		address, err := s.SSHAddress(state)
		if err != nil {
			log.Printf("Error getting SSH address: %s", err)
			continue
		}

		// Retrieve the SSH configuration
		sshConfig, err := s.SSHConfig(state)
		if err != nil {
			log.Printf("Error getting SSH config: %s", err)
			continue
		}

		// Attempt to connect to SSH port
		connFunc := ssh.ConnectFunc("tcp", address, 5*time.Minute)
		nc, err := connFunc()
		if err != nil {
			log.Printf("TCP connection to SSH ip/port failed: %s", err)
			continue
		}
		nc.Close()

		// Then we attempt to connect via SSH
		config := &ssh.Config{
			Connection: connFunc,
			SSHConfig:  sshConfig,
		}

		comm, err = ssh.New(config)
		if err != nil {
			log.Printf("SSH handshake err: %s", err)

			handshakeAttempts += 1
			if handshakeAttempts < 10 {
				// Try to connect via SSH a handful of times
				continue
			}

			return nil, err
		}

		break
	}

	return comm, nil
}
<commit_msg>builder/common: if cancel during SSH, cancel the attempts<commit_after>package common

import (
	gossh "code.google.com/p/go.crypto/ssh"
	"errors"
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/communicator/ssh"
	"github.com/mitchellh/packer/packer"
	"log"
	"time"
)

// StepConnectSSH is a multistep Step implementation that waits for SSH
// to become available. It gets the connection information from a single
// configuration when creating the step.
//
// Uses:
//   ui packer.Ui
//
// Produces:
//   communicator packer.Communicator
type StepConnectSSH struct {
	// SSHAddress is a function that returns the TCP address to connect to
	// for SSH. This is a function so that you can query information
	// if necessary for this address.
	SSHAddress func(map[string]interface{}) (string, error)

	// SSHConfig is a function that returns the proper client configuration
	// for SSH access.
	SSHConfig func(map[string]interface{}) (*gossh.ClientConfig, error)

	// SSHWaitTimeout is the total timeout to wait for SSH to become available.
	SSHWaitTimeout time.Duration

	cancel bool
	comm   packer.Communicator
}

func (s *StepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
	ui := state["ui"].(packer.Ui)

	var comm packer.Communicator
	var err error

	waitDone := make(chan bool, 1)
	go func() {
		ui.Say("Waiting for SSH to become available...")
		comm, err = s.waitForSSH(state)
		waitDone <- true
	}()

	log.Printf("Waiting for SSH, up to timeout: %s", s.SSHWaitTimeout)
	timeout := time.After(s.SSHWaitTimeout)
WaitLoop:
	for {
		// Wait for either SSH to become available, a timeout to occur,
		// or an interrupt to come through.
		select {
		case <-waitDone:
			if err != nil {
				ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
				return multistep.ActionHalt
			}

			ui.Say("Connected to SSH!")
			s.comm = comm
			state["communicator"] = comm
			break WaitLoop
		case <-timeout:
			ui.Error("Timeout waiting for SSH.")
			s.cancel = true
			return multistep.ActionHalt
		case <-time.After(1 * time.Second):
			if _, ok := state[multistep.StateCancelled]; ok {
				// The step sequence was cancelled, so cancel waiting for SSH
				// and just start the halting process.
				s.cancel = true
				log.Println("Interrupt detected, quitting waiting for SSH.")
				return multistep.ActionHalt
			}
		}
	}

	return multistep.ActionContinue
}

func (s *StepConnectSSH) Cleanup(map[string]interface{}) {
}

func (s *StepConnectSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
	handshakeAttempts := 0

	var comm packer.Communicator
	for {
		time.Sleep(5 * time.Second)

		if s.cancel {
			log.Println("SSH wait cancelled. Exiting loop.")
			return nil, errors.New("SSH wait cancelled")
		}

		// First we request the TCP connection information
		address, err := s.SSHAddress(state)
		if err != nil {
			log.Printf("Error getting SSH address: %s", err)
			continue
		}

		// Retrieve the SSH configuration
		sshConfig, err := s.SSHConfig(state)
		if err != nil {
			log.Printf("Error getting SSH config: %s", err)
			continue
		}

		// Attempt to connect to SSH port
		connFunc := ssh.ConnectFunc("tcp", address, 5*time.Minute)
		nc, err := connFunc()
		if err != nil {
			log.Printf("TCP connection to SSH ip/port failed: %s", err)
			continue
		}
		nc.Close()

		// Then we attempt to connect via SSH
		config := &ssh.Config{
			Connection: connFunc,
			SSHConfig:  sshConfig,
		}

		comm, err = ssh.New(config)
		if err != nil {
			log.Printf("SSH handshake err: %s", err)

			handshakeAttempts += 1
			if handshakeAttempts < 10 {
				// Try to connect via SSH a handful of times
				continue
			}

			return nil, err
		}

		break
	}

	return comm, nil
}
<|endoftext|>


<commit_before>package wallet

import (
	"github.com/NebulousLabs/Sia/consensus"
	"github.com/NebulousLabs/Sia/crypto"
)

// TimelockedCoinAddress returns an address that can only be spent after block
// `unlockHeight`.
func (w *Wallet) timelockedCoinAddress(unlockHeight consensus.BlockHeight) (coinAddress consensus.CoinAddress, spendConditions consensus.SpendConditions, err error) {
	// Create the address + spend conditions.
	sk, pk, err := crypto.GenerateSignatureKeys()
	if err != nil {
		return
	}
	spendConditions = consensus.SpendConditions{
		TimeLock:      unlockHeight,
		NumSignatures: 1,
		PublicKeys: []consensus.SiaPublicKey{
			consensus.SiaPublicKey{
				Algorithm: consensus.ED25519Identifier,
			},
		},
	}
	copy(spendConditions.PublicKeys[0].Key, pk[:])
	coinAddress = spendConditions.CoinAddress()

	// Create a spendableAddress for the keys and add it to the
	// timelockedSpendableAddresses map. If the address has already been
	// unlocked, also add it to the list of currently spendable addresses. It
	// needs to go in both though in case there is a reorganization of the
	// blockchain.
	newKey := &key{
		spendConditions: spendConditions,
		secretKey:       sk,

		outputs: make(map[consensus.OutputID]*knownOutput),
	}
	if unlockHeight <= w.state.Height() {
		newKey.spendable = true
	}
	w.keys[coinAddress] = newKey

	// Add this key to the list of addresses that get unlocked at
	// `unlockHeight`
	heightAddrs := w.timelockedKeys[unlockHeight]
	heightAddrs = append(heightAddrs, coinAddress)
	w.timelockedKeys[unlockHeight] = heightAddrs

	// Save the wallet state, which now includes the new address.
	err = w.save()
	if err != nil {
		return
	}

	return
}

// coinAddress returns a new address for receiving coins.
func (w *Wallet) coinAddress() (coinAddress consensus.CoinAddress, spendConditions consensus.SpendConditions, err error) {
	// Create the keys and address.
	sk, pk, err := crypto.GenerateSignatureKeys()
	if err != nil {
		return
	}
	spendConditions = consensus.SpendConditions{
		NumSignatures: 1,
		PublicKeys: []consensus.SiaPublicKey{
			consensus.SiaPublicKey{
				Algorithm: consensus.ED25519Identifier,
			},
		},
	}
	copy(spendConditions.PublicKeys[0].Key, pk[:])
	coinAddress = spendConditions.CoinAddress()

	// Add the address to the set of spendable addresses.
	newKey := &key{
		spendable:       true,
		spendConditions: spendConditions,
		secretKey:       sk,

		outputs: make(map[consensus.OutputID]*knownOutput),
	}
	w.keys[coinAddress] = newKey

	// Save the wallet state, which now includes the new address.
	err = w.save()
	if err != nil {
		return
	}

	return
}

// TimelockedCoinAddress returns an address that can only be spent after block
// `unlockHeight`.
func (w *Wallet) TimelockedCoinAddress(unlockHeight consensus.BlockHeight) (coinAddress consensus.CoinAddress, spendConditions consensus.SpendConditions, err error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.timelockedCoinAddress(unlockHeight)
}

// CoinAddress implements the core.Wallet interface.
func (w *Wallet) CoinAddress() (coinAddress consensus.CoinAddress, spendConditions consensus.SpendConditions, err error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.coinAddress()
}
<commit_msg>fix wallet key copy<commit_after>package wallet

import (
	"github.com/NebulousLabs/Sia/consensus"
	"github.com/NebulousLabs/Sia/crypto"
)

// TimelockedCoinAddress returns an address that can only be spent after block
// `unlockHeight`.
func (w *Wallet) timelockedCoinAddress(unlockHeight consensus.BlockHeight) (coinAddress consensus.CoinAddress, spendConditions consensus.SpendConditions, err error) {
	// Create the address + spend conditions.
	sk, pk, err := crypto.GenerateSignatureKeys()
	if err != nil {
		return
	}
	spendConditions = consensus.SpendConditions{
		TimeLock:      unlockHeight,
		NumSignatures: 1,
		PublicKeys: []consensus.SiaPublicKey{
			consensus.SiaPublicKey{
				Algorithm: consensus.ED25519Identifier,
				Key:       pk[:],
			},
		},
	}
	coinAddress = spendConditions.CoinAddress()

	// Create a spendableAddress for the keys and add it to the
	// timelockedSpendableAddresses map. If the address has already been
	// unlocked, also add it to the list of currently spendable addresses. It
	// needs to go in both though in case there is a reorganization of the
	// blockchain.
	newKey := &key{
		spendConditions: spendConditions,
		secretKey:       sk,

		outputs: make(map[consensus.OutputID]*knownOutput),
	}
	if unlockHeight <= w.state.Height() {
		newKey.spendable = true
	}
	w.keys[coinAddress] = newKey

	// Add this key to the list of addresses that get unlocked at
	// `unlockHeight`
	heightAddrs := w.timelockedKeys[unlockHeight]
	heightAddrs = append(heightAddrs, coinAddress)
	w.timelockedKeys[unlockHeight] = heightAddrs

	// Save the wallet state, which now includes the new address.
	err = w.save()
	if err != nil {
		return
	}

	return
}

// coinAddress returns a new address for receiving coins.
func (w *Wallet) coinAddress() (coinAddress consensus.CoinAddress, spendConditions consensus.SpendConditions, err error) {
	// Create the keys and address.
	sk, pk, err := crypto.GenerateSignatureKeys()
	if err != nil {
		return
	}
	spendConditions = consensus.SpendConditions{
		NumSignatures: 1,
		PublicKeys: []consensus.SiaPublicKey{
			consensus.SiaPublicKey{
				Algorithm: consensus.ED25519Identifier,
				Key:       pk[:],
			},
		},
	}
	coinAddress = spendConditions.CoinAddress()

	// Add the address to the set of spendable addresses.
	newKey := &key{
		spendable:       true,
		spendConditions: spendConditions,
		secretKey:       sk,

		outputs: make(map[consensus.OutputID]*knownOutput),
	}
	w.keys[coinAddress] = newKey

	// Save the wallet state, which now includes the new address.
	err = w.save()
	if err != nil {
		return
	}

	return
}

// TimelockedCoinAddress returns an address that can only be spent after block
// `unlockHeight`.
func (w *Wallet) TimelockedCoinAddress(unlockHeight consensus.BlockHeight) (coinAddress consensus.CoinAddress, spendConditions consensus.SpendConditions, err error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.timelockedCoinAddress(unlockHeight)
}

// CoinAddress implements the core.Wallet interface.
func (w *Wallet) CoinAddress() (coinAddress consensus.CoinAddress, spendConditions consensus.SpendConditions, err error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.coinAddress()
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package updater

import (
	"fmt"
	"time"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/logger"
	keybase1 "github.com/keybase/client/go/protocol"
	"github.com/keybase/client/go/updater/sources"
)

type UpdateChecker struct {
	updater *Updater
	ui      UI
	ticker  *time.Ticker
	log     logger.Logger
}

type UI interface {
	GetUpdateUI() (libkb.UpdateUI, error)
}

func NewUpdateChecker(updater *Updater, ui UI, log logger.Logger) UpdateChecker {
	return UpdateChecker{
		updater: updater,
		ui:      ui,
		log:     log,
	}
}

func (u *UpdateChecker) Check(force bool, requested bool) error {
	ui, _ := u.ui.GetUpdateUI()
	if ui == nil && !force {
		return fmt.Errorf("No UI for update check")
	}

	if !requested && !force {
		if lastCheckedPTime := u.updater.config.GetUpdateLastChecked(); lastCheckedPTime > 0 {
			lastChecked := keybase1.FromTime(lastCheckedPTime)
			if time.Now().Before(lastChecked.Add(checkDuration())) {
				u.log.Debug("Already checked: %s", lastChecked)
				return nil
			}
		}
	}

	_, err := u.updater.Update(ui, force, requested)
	if err != nil {
		return err
	}
	u.log.Debug("Updater checked")
	u.updater.config.SetUpdateLastChecked(keybase1.ToTime(time.Now()))
	return nil
}

func (u *UpdateChecker) Start() {
	if u.ticker != nil {
		return
	}
	u.ticker = time.NewTicker(tickDuration())
	go func() {
		for _ = range u.ticker.C {
			u.log.Info("Checking for update (ticker)")
			u.Check(false, false)
		}
	}()
}

func (u *UpdateChecker) Stop() {
	u.ticker.Stop()
	u.ticker = nil
}

// checkDuration is how often to check for updates
func checkDuration() time.Duration {
	if sources.IsPrerelease {
		return time.Hour
	}
	return 24 * time.Hour
}

// tickDuration is how often to call check (should be less than checkDuration or snooze min)
func tickDuration() time.Duration {
	if sources.IsPrerelease {
		return 15 * time.Minute
	}
	return time.Hour
}
<commit_msg>Moving ticker log entry to debug<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package updater

import (
	"fmt"
	"time"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/logger"
	keybase1 "github.com/keybase/client/go/protocol"
	"github.com/keybase/client/go/updater/sources"
)

type UpdateChecker struct {
	updater *Updater
	ui      UI
	ticker  *time.Ticker
	log     logger.Logger
}

type UI interface {
	GetUpdateUI() (libkb.UpdateUI, error)
}

func NewUpdateChecker(updater *Updater, ui UI, log logger.Logger) UpdateChecker {
	return UpdateChecker{
		updater: updater,
		ui:      ui,
		log:     log,
	}
}

func (u *UpdateChecker) Check(force bool, requested bool) error {
	ui, _ := u.ui.GetUpdateUI()
	if ui == nil && !force {
		return fmt.Errorf("No UI for update check")
	}

	if !requested && !force {
		if lastCheckedPTime := u.updater.config.GetUpdateLastChecked(); lastCheckedPTime > 0 {
			lastChecked := keybase1.FromTime(lastCheckedPTime)
			if time.Now().Before(lastChecked.Add(checkDuration())) {
				u.log.Debug("Already checked: %s", lastChecked)
				return nil
			}
		}
	}

	_, err := u.updater.Update(ui, force, requested)
	if err != nil {
		return err
	}
	u.log.Debug("Updater checked")
	u.updater.config.SetUpdateLastChecked(keybase1.ToTime(time.Now()))
	return nil
}

func (u *UpdateChecker) Start() {
	if u.ticker != nil {
		return
	}
	u.ticker = time.NewTicker(tickDuration())
	go func() {
		for _ = range u.ticker.C {
			u.log.Debug("Checking for update (ticker)")
			u.Check(false, false)
		}
	}()
}

func (u *UpdateChecker) Stop() {
	u.ticker.Stop()
	u.ticker = nil
}

// checkDuration is how often to check for updates
func checkDuration() time.Duration {
	if sources.IsPrerelease {
		return time.Hour
	}
	return 24 * time.Hour
}

// tickDuration is how often to call check (should be less than checkDuration or snooze min)
func tickDuration() time.Duration {
	if sources.IsPrerelease {
		return 15 * time.Minute
	}
	return time.Hour
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"sort"
	"strings"
)

func DisplayForceSobjects(sobjects []ForceSobject) {
	names := make([]string, len(sobjects))
	for i, sobject := range sobjects {
		names[i] = sobject["name"].(string)
	}
	sort.Strings(names)
	for _, name := range names {
		fmt.Println(name)
	}
}

func DisplayForceRecords(records []ForceRecord) {
	var keys []string
	if len(records) > 1 {
		for key, _ := range records[0] {
			if key != "attributes" {
				keys = append(keys, key)
			}
		}
		lengths := make([]int, len(keys))
		separators := make([]string, len(keys))
		for i, key := range keys {
			lengths[i] = 0
			for _, record := range records {
				l := len(record[key].(string))
				if l > lengths[i] {
					lengths[i] = l
				}
			}
			separators[i] = strings.Repeat("-", lengths[i]+2)
		}
		formatter_parts := make([]string, len(keys))
		for i, length := range lengths {
			formatter_parts[i] = fmt.Sprintf(" %%-%ds ", length)
		}
		formatter := strings.Join(formatter_parts, "|")
		fmt.Printf(formatter+"\n", StringSliceToInterfaceSlice(keys)...)
		fmt.Printf(strings.Join(separators, "+") + "\n")
		for _, record := range records {
			values := make([]string, len(keys))
			for i, key := range keys {
				values[i] = record[key].(string)
			}
			fmt.Printf(formatter+"\n", StringSliceToInterfaceSlice(values)...)
		}
		fmt.Printf(strings.Join(separators, "+") + "\n")
	}
	fmt.Printf("%d results returned\n", len(records))
}

func DisplayForceRecord(record ForceRecord) {
	DisplayInterfaceMap(record, 0)
}

func DisplayInterfaceMap(object map[string]interface{}, indent int) {
	keys := make([]string, len(object))
	i := 0
	for key, _ := range object {
		keys[i] = key
		i++
	}
	sort.Strings(keys)
	for _, key := range keys {
		for i := 0; i < indent; i++ {
			fmt.Printf("  ")
		}
		fmt.Printf("%s: ", key)
		switch v := object[key].(type) {
		case map[string]interface{}:
			fmt.Printf("\n")
			DisplayInterfaceMap(v, indent+1)
		default:
			fmt.Printf("%v\n", v)
		}
	}
}

func StringSliceToInterfaceSlice(s []string) (i []interface{}) {
	for _, str := range s {
		i = append(i, interface{}(str))
	}
	return
}
<commit_msg>off-by-one<commit_after>package main

import (
	"fmt"
	"sort"
	"strings"
)

func DisplayForceSobjects(sobjects []ForceSobject) {
	names := make([]string, len(sobjects))
	for i, sobject := range sobjects {
		names[i] = sobject["name"].(string)
	}
	sort.Strings(names)
	for _, name := range names {
		fmt.Println(name)
	}
}

func DisplayForceRecords(records []ForceRecord) {
	var keys []string
	if len(records) > 0 {
		for key, _ := range records[0] {
			if key != "attributes" {
				keys = append(keys, key)
			}
		}
		lengths := make([]int, len(keys))
		separators := make([]string, len(keys))
		for i, key := range keys {
			lengths[i] = 0
			for _, record := range records {
				l := len(record[key].(string))
				if l > lengths[i] {
					lengths[i] = l
				}
			}
			separators[i] = strings.Repeat("-", lengths[i]+2)
		}
		formatter_parts := make([]string, len(keys))
		for i, length := range lengths {
			formatter_parts[i] = fmt.Sprintf(" %%-%ds ", length)
		}
		formatter := strings.Join(formatter_parts, "|")
		fmt.Printf(formatter+"\n", StringSliceToInterfaceSlice(keys)...)
		fmt.Printf(strings.Join(separators, "+") + "\n")
		for _, record := range records {
			values := make([]string, len(keys))
			for i, key := range keys {
				values[i] = record[key].(string)
			}
			fmt.Printf(formatter+"\n", StringSliceToInterfaceSlice(values)...)
		}
		fmt.Printf(strings.Join(separators, "+") + "\n")
	}
	fmt.Printf("%d results returned\n", len(records))
}

func DisplayForceRecord(record ForceRecord) {
	DisplayInterfaceMap(record, 0)
}

func DisplayInterfaceMap(object map[string]interface{}, indent int) {
	keys := make([]string, len(object))
	i := 0
	for key, _ := range object {
		keys[i] = key
		i++
	}
	sort.Strings(keys)
	for _, key := range keys {
		for i := 0; i < indent; i++ {
			fmt.Printf("  ")
		}
		fmt.Printf("%s: ", key)
		switch v := object[key].(type) {
		case map[string]interface{}:
			fmt.Printf("\n")
			DisplayInterfaceMap(v, indent+1)
		default:
			fmt.Printf("%v\n", v)
		}
	}
}

func StringSliceToInterfaceSlice(s []string) (i []interface{}) {
	for _, str := range s {
		i = append(i, interface{}(str))
	}
	return
}
<|endoftext|>


<commit_before>package tracer

import (
	"encoding/binary"
	"io"
	"strconv"
	"strings"

	"github.com/opentracing/opentracing-go"
)

// An Extracter extracts a SpanContext from carrier.
type Extracter func(carrier interface{}) (SpanContext, error)

// An Injecter injects a SpanContext into carrier.
type Injecter func(sm SpanContext, carrier interface{}) error

var extracters = map[interface{}]Extracter{
	opentracing.TextMap: textExtracter,
	opentracing.Binary:  binaryExtracter,
}

var injecters = map[interface{}]Injecter{
	opentracing.TextMap: textInjecter,
	opentracing.Binary:  binaryInjecter,
}

// RegisterExtracter registers an Extracter.
func RegisterExtracter(format interface{}, extracter Extracter) {
	extracters[format] = extracter
}

// RegisterInjecter registers an Injecter.
func RegisterInjecter(format interface{}, injecter Injecter) {
	injecters[format] = injecter
}

// SpanContext contains the parts of a span that will be sent to
// downstream services.
type SpanContext struct {
	TraceID  uint64            `json:"trace_id"`
	ParentID uint64            `json:"parent_id"`
	SpanID   uint64            `json:"span_id"`
	Flags    uint64            `json:"flags"`
	Baggage  map[string]string `json:"baggage"`
}

// ForeachBaggageItem implements the opentracing.Tracer interface.
func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) {
	for k, v := range c.Baggage {
		if !handler(k, v) {
			return
		}
	}
}

func textInjecter(sm SpanContext, carrier interface{}) error {
	w, ok := carrier.(opentracing.TextMapWriter)
	if !ok {
		return opentracing.ErrInvalidCarrier
	}
	w.Set("Tracer-TraceId", idToHex(sm.TraceID))
	w.Set("Tracer-SpanId", idToHex(sm.SpanID))
	w.Set("Tracer-ParentSpanId", idToHex(sm.ParentID))
	w.Set("Tracer-Flags", strconv.FormatUint(sm.Flags, 10))
	for k, v := range sm.Baggage {
		w.Set("Tracer-Baggage-"+k, v)
	}
	return nil
}

func textExtracter(carrier interface{}) (SpanContext, error) {
	r, ok := carrier.(opentracing.TextMapReader)
	if !ok {
		return SpanContext{}, opentracing.ErrInvalidCarrier
	}
	ctx := SpanContext{Baggage: map[string]string{}}
	err := r.ForeachKey(func(key string, val string) error {
		lower := strings.ToLower(key)
		switch lower {
		case "tracer-traceid":
			ctx.TraceID = idFromHex(val)
		case "tracer-spanid":
			ctx.SpanID = idFromHex(val)
		case "tracer-parentspanid":
			ctx.ParentID = idFromHex(val)
		case "tracer-flags":
			ctx.Flags, _ = strconv.ParseUint(val, 10, 64)
		default:
			if strings.HasPrefix(lower, "tracer-baggage-") {
				key = key[len("Tracer-Baggage-"):]
				ctx.Baggage[key] = val
			}
		}
		return nil
	})
	if ctx.TraceID == 0 {
		return SpanContext{}, opentracing.ErrSpanContextNotFound
	}
	return ctx, err
}

func binaryInjecter(sm SpanContext, carrier interface{}) error {
	w, ok := carrier.(io.Writer)
	if !ok {
		return opentracing.ErrInvalidCarrier
	}
	b := make([]byte, 8*5)
	binary.BigEndian.PutUint64(b, sm.TraceID)
	binary.BigEndian.PutUint64(b[8:], sm.SpanID)
	binary.BigEndian.PutUint64(b[16:], sm.ParentID)
	binary.BigEndian.PutUint64(b[24:], sm.Flags)
	binary.BigEndian.PutUint64(b[32:], uint64(len(sm.Baggage)))
	for k, v := range sm.Baggage {
		b2 := make([]byte, 16+len(k)+len(v))
		binary.BigEndian.PutUint64(b2, uint64(len(k)))
		binary.BigEndian.PutUint64(b2[8:], uint64(len(v)))
		copy(b2[16:], k)
		copy(b2[16+len(k):], v)
		b = append(b, b2...)
	}
	_, err := w.Write(b)
	return err
}

func binaryExtracter(carrier interface{}) (SpanContext, error) {
	r, ok := carrier.(io.Reader)
	if !ok {
		return SpanContext{}, opentracing.ErrInvalidCarrier
	}
	ctx := SpanContext{Baggage: map[string]string{}}
	b := make([]byte, 8*5)
	if _, err := io.ReadFull(r, b); err != nil {
		if err == io.ErrUnexpectedEOF {
			return SpanContext{}, opentracing.ErrSpanContextNotFound
		}
		return SpanContext{}, err
	}
	ctx.TraceID = binary.BigEndian.Uint64(b)
	ctx.SpanID = binary.BigEndian.Uint64(b[8:])
	ctx.ParentID = binary.BigEndian.Uint64(b[16:])
	ctx.Flags = binary.BigEndian.Uint64(b[24:])
	n := binary.BigEndian.Uint64(b[32:])

	b = make([]byte, 8*2)
	for i := uint64(0); i < n; i++ {
		if _, err := io.ReadFull(r, b); err != nil {
			if err == io.ErrUnexpectedEOF {
				return SpanContext{}, opentracing.ErrSpanContextNotFound
			}
			return SpanContext{}, err
		}

		kl := int(binary.BigEndian.Uint64(b))
		vl := int(binary.BigEndian.Uint64(b[8:]))
		if kl <= 0 || vl < 0 {
			return SpanContext{}, opentracing.ErrSpanContextNotFound
		}

		b2 := make([]byte, kl+vl)
		if _, err := io.ReadFull(r, b2); err != nil {
			if err == io.ErrUnexpectedEOF {
				return SpanContext{}, opentracing.ErrSpanContextNotFound
			}
			return SpanContext{}, err
		}
		ctx.Baggage[string(b2[:kl])] = string(b2[kl:])
	}

	return ctx, nil
}
<commit_msg>Use lowercase keys in TextMap<commit_after>package tracer

import (
	"encoding/binary"
	"io"
	"strconv"
	"strings"

	"github.com/opentracing/opentracing-go"
)

// An Extracter extracts a SpanContext from carrier.
type Extracter func(carrier interface{}) (SpanContext, error)

// An Injecter injects a SpanContext into carrier.
type Injecter func(sm SpanContext, carrier interface{}) error

var extracters = map[interface{}]Extracter{
	opentracing.TextMap: textExtracter,
	opentracing.Binary:  binaryExtracter,
}

var injecters = map[interface{}]Injecter{
	opentracing.TextMap: textInjecter,
	opentracing.Binary:  binaryInjecter,
}

// RegisterExtracter registers an Extracter.
func RegisterExtracter(format interface{}, extracter Extracter) {
	extracters[format] = extracter
}

// RegisterInjecter registers an Injecter.
func RegisterInjecter(format interface{}, injecter Injecter) {
	injecters[format] = injecter
}

// SpanContext contains the parts of a span that will be sent to
// downstream services.
type SpanContext struct {
	TraceID  uint64            `json:"trace_id"`
	ParentID uint64            `json:"parent_id"`
	SpanID   uint64            `json:"span_id"`
	Flags    uint64            `json:"flags"`
	Baggage  map[string]string `json:"baggage"`
}

// ForeachBaggageItem implements the opentracing.Tracer interface.
func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) {
	for k, v := range c.Baggage {
		if !handler(k, v) {
			return
		}
	}
}

func textInjecter(sm SpanContext, carrier interface{}) error {
	w, ok := carrier.(opentracing.TextMapWriter)
	if !ok {
		return opentracing.ErrInvalidCarrier
	}
	w.Set("tracer-traceid", idToHex(sm.TraceID))
	w.Set("tracer-spanid", idToHex(sm.SpanID))
	w.Set("tracer-parentspanid", idToHex(sm.ParentID))
	w.Set("tracer-flags", strconv.FormatUint(sm.Flags, 10))
	for k, v := range sm.Baggage {
		w.Set("tracer-baggage-"+k, v)
	}
	return nil
}

func textExtracter(carrier interface{}) (SpanContext, error) {
	r, ok := carrier.(opentracing.TextMapReader)
	if !ok {
		return SpanContext{}, opentracing.ErrInvalidCarrier
	}
	ctx := SpanContext{Baggage: map[string]string{}}
	err := r.ForeachKey(func(key string, val string) error {
		lower := strings.ToLower(key)
		switch lower {
		case "tracer-traceid":
			ctx.TraceID = idFromHex(val)
		case "tracer-spanid":
			ctx.SpanID = idFromHex(val)
		case "tracer-parentspanid":
			ctx.ParentID = idFromHex(val)
		case "tracer-flags":
			ctx.Flags, _ = strconv.ParseUint(val, 10, 64)
		default:
			if strings.HasPrefix(lower, "tracer-baggage-") {
				key = key[len("Tracer-Baggage-"):]
				ctx.Baggage[key] = val
			}
		}
		return nil
	})
	if ctx.TraceID == 0 {
		return SpanContext{}, opentracing.ErrSpanContextNotFound
	}
	return ctx, err
}

func binaryInjecter(sm SpanContext, carrier interface{}) error {
	w, ok := carrier.(io.Writer)
	if !ok {
		return opentracing.ErrInvalidCarrier
	}
	b := make([]byte, 8*5)
	binary.BigEndian.PutUint64(b, sm.TraceID)
	binary.BigEndian.PutUint64(b[8:], sm.SpanID)
	binary.BigEndian.PutUint64(b[16:], sm.ParentID)
	binary.BigEndian.PutUint64(b[24:], sm.Flags)
	binary.BigEndian.PutUint64(b[32:], uint64(len(sm.Baggage)))
	for k, v := range sm.Baggage {
		b2 := make([]byte, 16+len(k)+len(v))
		binary.BigEndian.PutUint64(b2, uint64(len(k)))
		binary.BigEndian.PutUint64(b2[8:], uint64(len(v)))
		copy(b2[16:], k)
		copy(b2[16+len(k):], v)
		b = append(b, b2...)
	}
	_, err := w.Write(b)
	return err
}

func binaryExtracter(carrier interface{}) (SpanContext, error) {
	r, ok := carrier.(io.Reader)
	if !ok {
		return SpanContext{}, opentracing.ErrInvalidCarrier
	}
	ctx := SpanContext{Baggage: map[string]string{}}
	b := make([]byte, 8*5)
	if _, err := io.ReadFull(r, b); err != nil {
		if err == io.ErrUnexpectedEOF {
			return SpanContext{}, opentracing.ErrSpanContextNotFound
		}
		return SpanContext{}, err
	}
	ctx.TraceID = binary.BigEndian.Uint64(b)
	ctx.SpanID = binary.BigEndian.Uint64(b[8:])
	ctx.ParentID = binary.BigEndian.Uint64(b[16:])
	ctx.Flags = binary.BigEndian.Uint64(b[24:])
	n := binary.BigEndian.Uint64(b[32:])

	b = make([]byte, 8*2)
	for i := uint64(0); i < n; i++ {
		if _, err := io.ReadFull(r, b); err != nil {
			if err == io.ErrUnexpectedEOF {
				return SpanContext{}, opentracing.ErrSpanContextNotFound
			}
			return SpanContext{}, err
		}

		kl := int(binary.BigEndian.Uint64(b))
		vl := int(binary.BigEndian.Uint64(b[8:]))
		if kl <= 0 || vl < 0 {
			return SpanContext{}, opentracing.ErrSpanContextNotFound
		}

		b2 := make([]byte, kl+vl)
		if _, err := io.ReadFull(r, b2); err != nil {
			if err == io.ErrUnexpectedEOF {
				return SpanContext{}, opentracing.ErrSpanContextNotFound
			}
			return SpanContext{}, err
		}
		ctx.Baggage[string(b2[:kl])] = string(b2[kl:])
	}

	return ctx, nil
}
<|endoftext|>


<commit_before>// +build !ignore_autogenerated

// Copyright (c) 2018 Chef Software Inc. and/or applicable contributors
//
// 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.

// Code generated by deepcopy-gen. DO NOT EDIT.

package v1beta1

import (
	v1 "k8s.io/api/core/v1"
	runtime "k8s.io/apimachinery/pkg/runtime"
)

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Bind) DeepCopyInto(out *Bind) {
	*out = *in
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bind.
func (in *Bind) DeepCopy() *Bind {
	if in == nil {
		return nil
	}
	out := new(Bind)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Habitat) DeepCopyInto(out *Habitat) {
	*out = *in
	out.TypeMeta = in.TypeMeta
	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
	in.Spec.DeepCopyInto(&out.Spec)
	out.Status = in.Status
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Habitat.
func (in *Habitat) DeepCopy() *Habitat {
	if in == nil {
		return nil
	}
	out := new(Habitat)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Habitat) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HabitatList) DeepCopyInto(out *HabitatList) {
	*out = *in
	out.TypeMeta = in.TypeMeta
	out.ListMeta = in.ListMeta
	if in.Items != nil {
		in, out := &in.Items, &out.Items
		*out = make([]Habitat, len(*in))
		for i := range *in {
			(*in)[i].DeepCopyInto(&(*out)[i])
		}
	}
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HabitatList.
func (in *HabitatList) DeepCopy() *HabitatList {
	if in == nil {
		return nil
	}
	out := new(HabitatList)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HabitatList) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HabitatSpec) DeepCopyInto(out *HabitatSpec) {
	*out = *in
	in.Service.DeepCopyInto(&out.Service)
	if in.Env != nil {
		in, out := &in.Env, &out.Env
		*out = make([]v1.EnvVar, len(*in))
		for i := range *in {
			(*in)[i].DeepCopyInto(&(*out)[i])
		}
	}
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HabitatSpec.
func (in *HabitatSpec) DeepCopy() *HabitatSpec {
	if in == nil {
		return nil
	}
	out := new(HabitatSpec)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HabitatStatus) DeepCopyInto(out *HabitatStatus) {
	*out = *in
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HabitatStatus.
func (in *HabitatStatus) DeepCopy() *HabitatStatus {
	if in == nil {
		return nil
	}
	out := new(HabitatStatus)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Service) DeepCopyInto(out *Service) {
	*out = *in
	if in.Bind != nil {
		in, out := &in.Bind, &out.Bind
		*out = make([]Bind, len(*in))
		copy(*out, *in)
	}
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Service.
func (in *Service) DeepCopy() *Service {
	if in == nil {
		return nil
	}
	out := new(Service)
	in.DeepCopyInto(out)
	return out
}
<commit_msg>Regenerate deepcopy functions<commit_after>// +build !ignore_autogenerated

// Copyright (c) 2018 Chef Software Inc. and/or applicable contributors
//
// 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.

// Code generated by deepcopy-gen. DO NOT EDIT.

package v1beta1

import (
	v1 "k8s.io/api/core/v1"
	runtime "k8s.io/apimachinery/pkg/runtime"
)

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Bind) DeepCopyInto(out *Bind) {
	*out = *in
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bind.
func (in *Bind) DeepCopy() *Bind {
	if in == nil {
		return nil
	}
	out := new(Bind)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Habitat) DeepCopyInto(out *Habitat) {
	*out = *in
	out.TypeMeta = in.TypeMeta
	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
	in.Spec.DeepCopyInto(&out.Spec)
	out.Status = in.Status
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Habitat.
func (in *Habitat) DeepCopy() *Habitat {
	if in == nil {
		return nil
	}
	out := new(Habitat)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Habitat) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HabitatList) DeepCopyInto(out *HabitatList) {
	*out = *in
	out.TypeMeta = in.TypeMeta
	out.ListMeta = in.ListMeta
	if in.Items != nil {
		in, out := &in.Items, &out.Items
		*out = make([]Habitat, len(*in))
		for i := range *in {
			(*in)[i].DeepCopyInto(&(*out)[i])
		}
	}
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HabitatList.
func (in *HabitatList) DeepCopy() *HabitatList {
	if in == nil {
		return nil
	}
	out := new(HabitatList)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HabitatList) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HabitatSpec) DeepCopyInto(out *HabitatSpec) {
	*out = *in
	in.Service.DeepCopyInto(&out.Service)
	if in.Env != nil {
		in, out := &in.Env, &out.Env
		*out = make([]v1.EnvVar, len(*in))
		for i := range *in {
			(*in)[i].DeepCopyInto(&(*out)[i])
		}
	}
	if in.Persistence != nil {
		in, out := &in.Persistence, &out.Persistence
		if *in == nil {
			*out = nil
		} else {
			*out = new(Persistence)
			**out = **in
		}
	}
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HabitatSpec.
func (in *HabitatSpec) DeepCopy() *HabitatSpec {
	if in == nil {
		return nil
	}
	out := new(HabitatSpec)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HabitatStatus) DeepCopyInto(out *HabitatStatus) {
	*out = *in
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HabitatStatus.
func (in *HabitatStatus) DeepCopy() *HabitatStatus {
	if in == nil {
		return nil
	}
	out := new(HabitatStatus)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Persistence) DeepCopyInto(out *Persistence) {
	*out = *in
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Persistence.
func (in *Persistence) DeepCopy() *Persistence {
	if in == nil {
		return nil
	}
	out := new(Persistence)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Service) DeepCopyInto(out *Service) {
	*out = *in
	if in.Bind != nil {
		in, out := &in.Bind, &out.Bind
		*out = make([]Bind, len(*in))
		copy(*out, *in)
	}
	return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Service.
func (in *Service) DeepCopy() *Service {
	if in == nil {
		return nil
	}
	out := new(Service)
	in.DeepCopyInto(out)
	return out
}
<|endoftext|>


<commit_before>package houki

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sync"
	"testing"
)

func TestReCreateDirectory(t *testing.T) {
	content := []byte("temporary file's content")
	dir, err := ioutil.TempDir("", "example")
	if err != nil {
		log.Fatal(err)
	}

	defer os.RemoveAll(dir) // clean up

	tmpfn := filepath.Join(dir, "tmpfile")
	if err := ioutil.WriteFile(tmpfn, content, 0666); err != nil {
		log.Fatal(err)
	}

	var wg sync.WaitGroup
	wg.Add(1)
	reCreateDirectory(dir, &wg)
	_, err = os.Stat(tmpfn)
	expect := fmt.Sprintf("stat %s: no such file or directory", tmpfn)
	if err.Error() != expect {
		t.Errorf("Expect %s is not exists, but not %s", tmpfn, err)
	}

	if _, err = os.Stat(dir); err != nil {
		t.Errorf("Expect %s is exists, but not %s", dir, err)
	}
}
<commit_msg>fix test<commit_after>package houki

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sync"
	"testing"
)

func TestReCreateDirectory(t *testing.T) {
	var houki Houki
	content := []byte("temporary file's content")
	dir, err := ioutil.TempDir("", "example")
	if err != nil {
		log.Fatal(err)
	}

	defer os.RemoveAll(dir) // clean up

	tmpfn := filepath.Join(dir, "tmpfile")
	if err := ioutil.WriteFile(tmpfn, content, 0666); err != nil {
		log.Fatal(err)
	}

	var wg sync.WaitGroup
	wg.Add(1)
	houki.reCreateDirectory(dir, &wg)
	_, err = os.Stat(tmpfn)
	expect := fmt.Sprintf("stat %s: no such file or directory", tmpfn)
	if err.Error() != expect {
		t.Errorf("Expect %s is not exists, but not %s", tmpfn, err)
	}

	if _, err = os.Stat(dir); err != nil {
		t.Errorf("Expect %s is exists, but not %s", dir, err)
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/codeartifact"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceAwsCodeArtifactDomain() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsCodeArtifactDomainCreate,
		Read:   resourceAwsCodeArtifactDomainRead,
		Delete: resourceAwsCodeArtifactDomainDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"domain": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"encryption_key": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"owner": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"created_time": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"asset_size_bytes": {
				Type:     schema.TypeInt,
				Computed: true,
			},
			"repository_count": {
				Type:     schema.TypeInt,
				Computed: true,
			},
		},
	}
}

func resourceAwsCodeArtifactDomainCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).codeartifactconn
	log.Print("[DEBUG] Creating CodeArtifact Domain")

	params := &codeartifact.CreateDomainInput{
		Domain:        aws.String(d.Get("domain").(string)),
		EncryptionKey: aws.String(d.Get("encryption_key").(string)),
	}

	domain, err := conn.CreateDomain(params)
	if err != nil {
		return fmt.Errorf("error creating CodeArtifact Domain: %s", err)
	}

	d.SetId(aws.StringValue(domain.Domain.Name))

	return resourceAwsCodeArtifactDomainRead(d, meta)
}

func resourceAwsCodeArtifactDomainRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).codeartifactconn

	log.Printf("[DEBUG] Reading CodeArtifact Domain: %s", d.Id())

	sm, err := conn.DescribeDomain(&codeartifact.DescribeDomainInput{
		Domain: aws.String(d.Id()),
	})
	if err != nil {
		if isAWSErr(err, codeartifact.ErrCodeResourceNotFoundException, "") {
			log.Printf("[WARN] CodeArtifact Domain %q not found, removing from state", d.Id())
			d.SetId("")
			return nil
		}
		return err
	}

	d.Set("domain", sm.Domain.Name)
	d.Set("arn", sm.Domain.Arn)
	d.Set("encryption_key", sm.Domain.EncryptionKey)
	d.Set("owner", sm.Domain.Owner)
	d.Set("asset_size_bytes", sm.Domain.AssetSizeBytes)
	d.Set("repository_count", sm.Domain.RepositoryCount)
	d.Set("created_time", sm.Domain.CreatedTime.Format(time.RFC3339))

	return nil
}

func resourceAwsCodeArtifactDomainDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).codeartifactconn
	log.Printf("[DEBUG] Deleting CodeArtifact Domain: %s", d.Id())

	input := &codeartifact.DeleteDomainInput{
		Domain: aws.String(d.Id()),
	}

	_, err := conn.DeleteDomain(input)

	if err != nil {
		return fmt.Errorf("error deleting CodeArtifact Domain: %s", err)
	}

	return nil
}
<commit_msg>fix error handling to add more logs<commit_after>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/codeartifact"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceAwsCodeArtifactDomain() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsCodeArtifactDomainCreate,
		Read:   resourceAwsCodeArtifactDomainRead,
		Delete: resourceAwsCodeArtifactDomainDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"domain": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"encryption_key": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"owner": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"created_time": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"asset_size_bytes": {
				Type:     schema.TypeInt,
				Computed: true,
			},
			"repository_count": {
				Type:     schema.TypeInt,
				Computed: true,
			},
		},
	}
}

func resourceAwsCodeArtifactDomainCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).codeartifactconn
	log.Print("[DEBUG] Creating CodeArtifact Domain")

	params := &codeartifact.CreateDomainInput{
		Domain:        aws.String(d.Get("domain").(string)),
		EncryptionKey: aws.String(d.Get("encryption_key").(string)),
	}

	domain, err := conn.CreateDomain(params)
	if err != nil {
		return fmt.Errorf("error creating CodeArtifact Domain: %s", err)
	}

	d.SetId(aws.StringValue(domain.Domain.Name))

	return resourceAwsCodeArtifactDomainRead(d, meta)
}

func resourceAwsCodeArtifactDomainRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).codeartifactconn

	log.Printf("[DEBUG] Reading CodeArtifact Domain: %s", d.Id())

	sm, err := conn.DescribeDomain(&codeartifact.DescribeDomainInput{
		Domain: aws.String(d.Id()),
	})
	if err != nil {
		if isAWSErr(err, codeartifact.ErrCodeResourceNotFoundException, "") {
			log.Printf("[WARN] CodeArtifact Domain %q not found, removing from state", d.Id())
			d.SetId("")
			return nil
		}
		return fmt.Errorf("error reading CodeArtifact Domain (%s): %s", d.Id(), err)
	}

	d.Set("domain", sm.Domain.Name)
	d.Set("arn", sm.Domain.Arn)
	d.Set("encryption_key", sm.Domain.EncryptionKey)
	d.Set("owner", sm.Domain.Owner)
	d.Set("asset_size_bytes", sm.Domain.AssetSizeBytes)
	d.Set("repository_count", sm.Domain.RepositoryCount)
	d.Set("created_time", sm.Domain.CreatedTime.Format(time.RFC3339))

	return nil
}

func resourceAwsCodeArtifactDomainDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).codeartifactconn
	log.Printf("[DEBUG] Deleting CodeArtifact Domain: %s", d.Id())

	input := &codeartifact.DeleteDomainInput{
		Domain: aws.String(d.Id()),
	}

	_, err := conn.DeleteDomain(input)

	if isAWSErr(err, codeartifact.ErrCodeResourceNotFoundException, "") {
		return nil
	}

	if err != nil {
		return fmt.Errorf("error deleting CodeArtifact Domain (%s): %s", d.Id(), err)
	}

	return nil
}
<|endoftext|>


<commit_before>package mydumpster

import (
	"database/sql"
	"encoding/json"
	//"errors"
	"fmt"
	"os"
)

type ConfTrigger struct {
	TableDstName string `json:"dst_table_name"`
	FieldSrcName string `json:"src_field_name"`
	FieldDstName string `json:"dst_field_name"`
}

type ConfCensorship struct {
	Prefix  string `json:"prefix"`
	Suffix  string `json:"suffix"`
	Blank   bool   `json:"blank"`
	Null    bool   `json:"null"`
	Default string `json:"default"`
}

type ConfTable struct {
	Filters    []string                   `json:"filters"`
	Censorship map[string]*ConfCensorship `json:"censorship"`
	Triggers   []*ConfTrigger             `json:"triggers"`
	Exclude    bool                       `json:"exclude"`
	DumpAll    bool                       `json:"dump_all"`
}

type ConfDatabase struct {
	Host     string `json:"host"`
	Port     int    `json:"port"`
	User     string `json:"user"`
	Password string `json:"password"`
	Db       string `json:"db"`
}

type ConfDump struct {
	AllTables bool `json:"all_tables"`
	Parallel  int  `json:"parallel"`
}

type Configuration struct {
	Tables      map[string]*ConfTable `json:"tables"`
	Database    *ConfDatabase         `json:"database"`
	DumpOptions *ConfDump             `json:"dump"`
}

func LoadConfiguration(filePath string) *Configuration {

	file, err := os.Open(filePath)
	CheckKill(err)

	decoder := json.NewDecoder(file)
	configuration := Configuration{}

	err = decoder.Decode(&configuration)
	CheckKill(err)
	//configuration.PrintConfiguration()

	return &configuration
}

func (c *Configuration) ConnectionStr() string {

	return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8",
		c.Database.User, c.Database.Password,
		c.Database.Host, c.Database.Port,
		c.Database.Db)
}

func (c *Configuration) GetTables(db *sql.DB) map[string]Table {

	// Create the containers
	var tables = make(map[string]Table)

	// If 'all_tables' is activated then we need all the tables from the database
	if c.DumpOptions.AllTables {
		tableNames, _ := GetTableNames(db)
		for _, i := range tableNames {
			// Exclude tables
			if !c.Tables[i].Exclude {
				tables[i] = Table{
					Db:          db,
					TableName:   i,
					Filters:     make([]string, 0),
					Censorships: make(map[string]Censorship),
					Triggers:    make([]*Trigger, 0),
					TriggeredBy: nil,
					DumpAll:     false,
				}
			}
		}
	}

	// Init our map (We need al the table structs created so
	// we can refer from a table to another while we parse)
	for k, v := range c.Tables {
		// Exclude table
		if !v.Exclude {
			tables[k] = Table{
				Db:          db,
				TableName:   k,
				Filters:     make([]string, len(v.Filters)),
				Censorships: make(map[string]Censorship),
				Triggers:    make([]*Trigger, len(v.Triggers)),
				TriggeredBy: nil,
				DumpAll:     v.DumpAll,
			}
		} else {
			log.Warning("Excludig '%s' table and pointing triggers ", k)
		}
	}

	for k, v := range c.Tables {
		// Exclude table
		if !v.Exclude {

			t := tables[k]

			// Create the filters
			for k, f := range v.Filters {
				t.Filters[k] = f
			}

			// Create the censorships
			if v.Censorship != nil {
				for ck, cv := range v.Censorship {
					t.Censorships[ck] = Censorship{
						Key:          ck,
						Suffix:       cv.Suffix,
						Prefix:       cv.Prefix,
						Blank:        cv.Blank,
						Null:         cv.Null,
						DefaultValue: cv.Default,
					}
				}
			}

			// Create the triggers
			if v.Triggers != nil {
				for tk, tv := range v.Triggers {

					// Check if triggers an excluded table
					// Optimization: Also check if there is a dump all flag to exclude the trigger
					// Note: if the table doesn't exist then there isn't configuration of exclude nor dump_all)
					auxConfTabl, ok := c.Tables[tv.TableDstName]
					if !ok || ok && !auxConfTabl.Exclude && !auxConfTabl.DumpAll {

						aux, ok := tables[tv.TableDstName]

						// Table not declared (We create)
						if !ok {
							//CheckKill(errors.New("Not table in map"))
							tables[tv.TableDstName] = Table{
								Db:          db,
								TableName:   tv.TableDstName,
								Filters:     make([]string, 0),
								Censorships: make(map[string]Censorship),
								Triggers:    make([]*Trigger, 0),
								TriggeredBy: &t,
								DumpAll:     false,
							}
							aux = tables[tv.TableDstName]
						}

						t.Triggers[tk] = &Trigger{
							TableDst:      aux,
							TableSrcName:  k,
							TableSrcField: tv.FieldSrcName,
							TableDstField: tv.FieldDstName,
						}
					} else { // Remove the element
						// Difference between slice 1 and 2 applied to the key
						//auxTk := tk - (len(v.Triggers) - len(t.Triggers))
						//t.Triggers = append(t.Triggers[:auxTk], t.Triggers[auxTk+1:]...)
						log.Warning("Removing trigger for table '%s'", tv.TableDstName)
						t.Triggers[tk] = nil
					}
				}
			}
			fmt.Println(len(t.Triggers))
		}
	}
	return tables
}

func (c *Configuration) PrintConfiguration() {
	d := c.Database
	fmt.Println("Database")
	fmt.Println("-----------")
	fmt.Println(fmt.Sprintf("  -Host: %s", d.Host))
	fmt.Println(fmt.Sprintf("  -Port: %d", d.Port))
	fmt.Println(fmt.Sprintf("  -Passwords: %s", d.Password))
	fmt.Println(fmt.Sprintf("  -User: %s", d.User))
	fmt.Println(fmt.Sprintf("  -Db: %s", d.Db))

	fmt.Println("")

	do := c.DumpOptions
	fmt.Println("Dump options")
	fmt.Println("-------------")
	fmt.Println(fmt.Sprintf("  - All tables: %t", do.AllTables))
	fmt.Println(fmt.Sprintf("  - Parallel: %d", do.Parallel))

	fmt.Println("")

	for k, v := range c.Tables {
		fmt.Println("Table " + k)
		fmt.Println("-----------")
		fmt.Println(fmt.Sprintf("Exclude: %t", v.Exclude))
		fmt.Println(fmt.Sprintf("Dump all: %t", v.DumpAll))

		fmt.Println("Filters:")
		for _, f := range v.Filters {
			fmt.Println("  -" + f)
		}

		fmt.Println("Censore:")
		for k2, v2 := range v.Censorship {
			fmt.Println("  -" + k2)
			fmt.Println(fmt.Sprintf("    -Prefix: %s", v2.Prefix))
			fmt.Println(fmt.Sprintf("    -Suffix: %s", v2.Suffix))
			fmt.Println(fmt.Sprintf("    -Blank: %t", v2.Blank))
			fmt.Println(fmt.Sprintf("    -Null: %t", v2.Null))
			fmt.Println(fmt.Sprintf("    -Default: %s", v2.Default))

		}

		fmt.Println("Triggers:")
		for _, v3 := range v.Triggers {
			fmt.Println(fmt.Sprintf("  -Src field name: %s", v3.FieldSrcName))
			fmt.Println(fmt.Sprintf("  -Dst field name: %s", v3.FieldDstName))
			fmt.Println(fmt.Sprintf("  -Dst Table name: %s", v3.TableDstName))
			fmt.Println("")
		}

		fmt.Println("=============================")
	}
}
<commit_msg>small changes<commit_after>package mydumpster

import (
	"database/sql"
	"encoding/json"
	//"errors"
	"fmt"
	"os"
)

type ConfTrigger struct {
	TableDstName string `json:"dst_table_name"`
	FieldSrcName string `json:"src_field_name"`
	FieldDstName string `json:"dst_field_name"`
}

type ConfCensorship struct {
	Prefix  string `json:"prefix"`
	Suffix  string `json:"suffix"`
	Blank   bool   `json:"blank"`
	Null    bool   `json:"null"`
	Default string `json:"default"`
}

type ConfTable struct {
	Filters    []string                   `json:"filters"`
	Censorship map[string]*ConfCensorship `json:"censorship"`
	Triggers   []*ConfTrigger             `json:"triggers"`
	Exclude    bool                       `json:"exclude"`
	DumpAll    bool                       `json:"dump_all"`
}

type ConfDatabase struct {
	Host     string `json:"host"`
	Port     int    `json:"port"`
	User     string `json:"user"`
	Password string `json:"password"`
	Db       string `json:"db"`
}

type ConfDump struct {
	AllTables bool `json:"all_tables"`
	Parallel  int  `json:"parallel"`
}

type Configuration struct {
	Tables      map[string]*ConfTable `json:"tables"`
	Database    *ConfDatabase         `json:"database"`
	DumpOptions *ConfDump             `json:"dump"`
}

func LoadConfiguration(filePath string) *Configuration {

	file, err := os.Open(filePath)
	CheckKill(err)

	decoder := json.NewDecoder(file)
	configuration := Configuration{}

	err = decoder.Decode(&configuration)
	CheckKill(err)
	//configuration.PrintConfiguration()

	return &configuration
}

func (c *Configuration) ConnectionStr() string {

	return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8",
		c.Database.User, c.Database.Password,
		c.Database.Host, c.Database.Port,
		c.Database.Db)
}

func (c *Configuration) GetTables(db *sql.DB) map[string]Table {

	// Create the containers
	var tables = make(map[string]Table)

	// If 'all_tables' is activated then we need all the tables from the database
	if c.DumpOptions.AllTables {
		tableNames, _ := GetTableNames(db)
		for _, i := range tableNames {
			// Exclude tables
			if !c.Tables[i].Exclude {
				tables[i] = Table{
					Db:          db,
					TableName:   i,
					Filters:     make([]string, 0),
					Censorships: make(map[string]Censorship),
					Triggers:    make([]*Trigger, 0),
					TriggeredBy: nil,
					DumpAll:     false,
				}
			}
		}
	}

	// Init our map (We need al the table structs created so
	// we can refer from a table to another while we parse)
	for k, v := range c.Tables {
		// Exclude table
		if !v.Exclude {
			tables[k] = Table{
				Db:          db,
				TableName:   k,
				Filters:     make([]string, len(v.Filters)),
				Censorships: make(map[string]Censorship),
				Triggers:    make([]*Trigger, len(v.Triggers)),
				TriggeredBy: nil,
				DumpAll:     v.DumpAll,
			}
		} else {
			log.Warning("Excludig '%s' table and pointing triggers ", k)
		}
	}

	for k, v := range c.Tables {
		// Exclude table
		if !v.Exclude {

			t := tables[k]

			// Create the filters
			for k, f := range v.Filters {
				t.Filters[k] = f
			}

			// Create the censorships
			if v.Censorship != nil {
				for ck, cv := range v.Censorship {
					t.Censorships[ck] = Censorship{
						Key:          ck,
						Suffix:       cv.Suffix,
						Prefix:       cv.Prefix,
						Blank:        cv.Blank,
						Null:         cv.Null,
						DefaultValue: cv.Default,
					}
				}
			}

			// Create the triggers
			if v.Triggers != nil {
				for tk, tv := range v.Triggers {

					// Check if triggers an excluded table
					// Optimization: Also check if there is a dump all flag to exclude the trigger
					// Note: if the table doesn't exist then there isn't configuration of exclude nor dump_all)
					auxConfTabl, ok := c.Tables[tv.TableDstName]
					if !ok || ok && !auxConfTabl.Exclude && !auxConfTabl.DumpAll {

						aux, ok := tables[tv.TableDstName]

						// Table not declared (We create)
						if !ok {
							//CheckKill(errors.New("Not table in map"))
							tables[tv.TableDstName] = Table{
								Db:          db,
								TableName:   tv.TableDstName,
								Filters:     make([]string, 0),
								Censorships: make(map[string]Censorship),
								Triggers:    make([]*Trigger, 0),
								TriggeredBy: &t,
								DumpAll:     false,
							}
							aux = tables[tv.TableDstName]
						}

						t.Triggers[tk] = &Trigger{
							TableDst:      aux,
							TableSrcName:  k,
							TableSrcField: tv.FieldSrcName,
							TableDstField: tv.FieldDstName,
						}
					} else { // Remove the element
						// Difference between slice 1 and 2 applied to the key
						//auxTk := tk - (len(v.Triggers) - len(t.Triggers))
						//t.Triggers = append(t.Triggers[:auxTk], t.Triggers[auxTk+1:]...)
						log.Warning("Removing trigger for table '%s'", tv.TableDstName)
						t.Triggers[tk] = nil
					}
				}
			}
		}
	}
	return tables
}

func (c *Configuration) PrintConfiguration() {
	d := c.Database
	fmt.Println("Database")
	fmt.Println("-----------")
	fmt.Println(fmt.Sprintf("  -Host: %s", d.Host))
	fmt.Println(fmt.Sprintf("  -Port: %d", d.Port))
	fmt.Println(fmt.Sprintf("  -Passwords: %s", d.Password))
	fmt.Println(fmt.Sprintf("  -User: %s", d.User))
	fmt.Println(fmt.Sprintf("  -Db: %s", d.Db))

	fmt.Println("")

	do := c.DumpOptions
	fmt.Println("Dump options")
	fmt.Println("-------------")
	fmt.Println(fmt.Sprintf("  - All tables: %t", do.AllTables))
	fmt.Println(fmt.Sprintf("  - Parallel: %d", do.Parallel))

	fmt.Println("")

	for k, v := range c.Tables {
		fmt.Println("Table " + k)
		fmt.Println("-----------")
		fmt.Println(fmt.Sprintf("Exclude: %t", v.Exclude))
		fmt.Println(fmt.Sprintf("Dump all: %t", v.DumpAll))

		fmt.Println("Filters:")
		for _, f := range v.Filters {
			fmt.Println("  -" + f)
		}

		fmt.Println("Censore:")
		for k2, v2 := range v.Censorship {
			fmt.Println("  -" + k2)
			fmt.Println(fmt.Sprintf("    -Prefix: %s", v2.Prefix))
			fmt.Println(fmt.Sprintf("    -Suffix: %s", v2.Suffix))
			fmt.Println(fmt.Sprintf("    -Blank: %t", v2.Blank))
			fmt.Println(fmt.Sprintf("    -Null: %t", v2.Null))
			fmt.Println(fmt.Sprintf("    -Default: %s", v2.Default))

		}

		fmt.Println("Triggers:")
		for _, v3 := range v.Triggers {
			fmt.Println(fmt.Sprintf("  -Src field name: %s", v3.FieldSrcName))
			fmt.Println(fmt.Sprintf("  -Dst field name: %s", v3.FieldDstName))
			fmt.Println(fmt.Sprintf("  -Dst Table name: %s", v3.TableDstName))
			fmt.Println("")
		}

		fmt.Println("=============================")
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package doc

import (
	"bytes"
	"sort"
	"strings"

	"github.com/caixw/apidoc/doc/lexer"
	"github.com/caixw/apidoc/doc/schema"
)

// API 表示单个 API 文档
type API struct {
	responses
	Method      string     `yaml:"method" json:"method"`
	Path        string     `yaml:"path" json:"path"`
	Summary     string     `yaml:"summary" json:"summary"`
	Description Markdown   `yaml:"description,omitempty" json:"description,omitempty"`
	Tags        []string   `yaml:"tags,omitempty" json:"tags,omitempty"`
	Queries     []*Param   `yaml:"queries,omitempty" json:"queries,omitempty"` // 查询参数
	Params      []*Param   `yaml:"params,omitempty" json:"params,omitempty"`   // URL 参数
	Requests    []*Request `yaml:"requests,omitempty" json:"requests,omitempty"`
	Deprecated  string     `yaml:"deprecated,omitempty" json:"deprecated,omitempty"`
	Servers     []string   `yaml:"servers" json:"servers"`

	// 路径参数名称的集合
	// TODO 比较与 Params 中的数据。
	pathParams []string
}

// Param 简单参数的描述，比如查询参数等
type Param struct {
	Name     string         `yaml:"name" json:"name"`                             // 参数名称
	Type     *schema.Schema `yaml:"type" json:"type"`                             // 类型
	Summary  string         `yaml:"summary" json:"summary"`                       // 参数介绍
	Optional bool           `yaml:"optional,omitempty" json:"optional,omitempty"` // 是否为可选参数
}

func (doc *Doc) parseAPI(l *lexer.Lexer) error {
	api := &API{}

	for tag := l.Tag(); tag != nil; tag = l.Tag() {
		parse := api.parseAPI
		switch strings.ToLower(tag.Name) {
		case "@api":
			parse = api.parseAPI
		case "@apirequest":
			parse = api.parseRequest
		case "@apiresponse":
			parse = api.parseResponse
		default:
			return tag.ErrInvalidTag()
		}

		if err := parse(l, tag); err != nil {
			return err
		}
	}

	doc.locker.Lock()
	doc.Apis = append(doc.Apis, api)
	doc.locker.Unlock()

	return nil
}

type apiParser func(*API, *lexer.Lexer, *lexer.Tag) error

var apiParsers = map[string]apiParser{
	"@api":           (*API).parseapi,
	"@apiservers":    (*API).parseServers,
	"@apitags":       (*API).parseTags,
	"@apideprecated": (*API).parseDeprecated,
	"@apiquery":      (*API).parseQuery,
	"@apiparam":      (*API).parseParam,
}

// 分析 @api 以及子标签
func (api *API) parseAPI(l *lexer.Lexer, tag *lexer.Tag) error {
	l.Backup(tag) // 进来时，第一个肯定是 @api 标签，退回该标签，统一让 for 处理。

	for tag := l.Tag(); tag != nil; tag = l.Tag() {
		fn, found := apiParsers[strings.ToLower(tag.Name)]
		if !found {
			l.Backup(tag)
			return nil
		}

		if err := fn(api, l, tag); err != nil {
			return err
		}
	}

	return nil
}

// 解析 @api 标签，格式如下：
//  @api GET /path summary
func (api *API) parseapi(l *lexer.Lexer, tag *lexer.Tag) error {
	if api.Method != "" || api.Path != "" || api.Summary != "" {
		return tag.ErrDuplicateTag()
	}
	data := tag.Words(3)
	if len(data) != 3 {
		return tag.ErrInvalidFormat()
	}

	api.Method = strings.ToUpper(string(data[0])) // TODO 验证请求方法
	api.Path = string(data[1])
	api.Summary = string(data[2])

	return api.genPathParams(tag)
}

func (api *API) genPathParams(tag *lexer.Tag) error {
	names := make([]string, 0, len(api.Params))

	state := '}'
	index := 0
	for i, b := range api.Path {
		switch b {
		case '{':
			if state != '}' {
				return tag.ErrInvalidFormat()
			}

			state = '{'
			index = i
		case '}':
			if state != '{' {
				return tag.ErrInvalidFormat()
			}
			names = append(names, api.Path[index+1:i])
			state = '}'
		}
	} // end for

	// 缺少 } 结束符号
	if state == '{' {
		return tag.ErrInvalidFormat()
	}

	api.pathParams = names
	return nil
}

// 解析 @apiServers 标签，格式如下：
//  @apiServers s1,s2
func (api *API) parseServers(l *lexer.Lexer, tag *lexer.Tag) error {
	if len(api.Servers) > 0 {
		return tag.ErrDuplicateTag()
	}

	if len(tag.Data) == 0 {
		return tag.ErrInvalidFormat()
	}

	srvs := bytes.FieldsFunc(tag.Data, func(r rune) bool { return r == ',' })
	api.Servers = make([]string, 0, len(srvs))
	for _, srv := range srvs {
		api.Servers = append(api.Servers, string(bytes.TrimSpace(srv)))
	}

	sort.Strings(api.Servers)
	for i := 1; i < len(api.Servers); i++ {
		if api.Servers[i] == api.Servers[i-1] {
			return tag.ErrInvalidFormat() // 重复的名称
		}
	}

	return nil
}

// 解析 @apiTags 标签，格式如下：
//  @apiTags t1,t2
func (api *API) parseTags(l *lexer.Lexer, tag *lexer.Tag) error {
	if len(api.Tags) > 0 {
		return tag.ErrDuplicateTag()
	}

	if len(tag.Data) == 0 {
		return tag.ErrInvalidFormat()
	}

	tags := bytes.FieldsFunc(tag.Data, func(r rune) bool { return r == ',' })
	api.Tags = make([]string, 0, len(tags))
	for _, tag := range tags {
		api.Tags = append(api.Tags, string(bytes.TrimSpace(tag)))
	}

	sort.Strings(api.Tags)
	for i := 1; i < len(api.Tags); i++ {
		if api.Tags[i] == api.Tags[i-1] {
			return tag.ErrInvalidFormat() // 重复的名称
		}
	}

	return nil
}

// 解析  @apiDeprecated 标签，格式如下：
//  @apiDeprecated description
func (api *API) parseDeprecated(l *lexer.Lexer, tag *lexer.Tag) error {
	if api.Deprecated != "" {
		return tag.ErrDuplicateTag()
	}

	if len(tag.Data) == 0 {
		return tag.ErrInvalidFormat()
	}

	api.Deprecated = string(tag.Data)
	return nil
}

// 解析 @apiQuery 标签，格式如下：
//  @apiQuery name type.subtype optional.defaultValue markdown desc
func (api *API) parseQuery(l *lexer.Lexer, tag *lexer.Tag) error {
	if api.Queries == nil {
		api.Queries = make([]*Param, 0, 10)
	}

	p, err := newParam(tag)
	if err != nil {
		return err
	}

	if api.queryExists(p.Name) {
		return tag.ErrDuplicateTag()
	}

	api.Queries = append(api.Queries, p)
	return nil
}

func (api *API) queryExists(name string) bool {
	for _, q := range api.Queries {
		if q.Name == name {
			return true
		}
	}

	return false
}

// 解析 @apiParam 标签，格式如下：
//  @apiParam name type.subtype optional.defaultValue markdown desc
func (api *API) parseParam(l *lexer.Lexer, tag *lexer.Tag) error {
	if api.Params == nil {
		api.Params = make([]*Param, 0, 3)
	}

	p, err := newParam(tag)
	if err != nil {
		return err
	}

	if api.paramExists(p.Name) {
		return tag.ErrDuplicateTag()
	}

	api.Params = append(api.Params, p)

	return nil
}

func (api *API) paramExists(name string) bool {
	for _, p := range api.Params {
		if p.Name == name {
			return true
		}
	}

	return false
}

// 解析 @apiRequest 及其子标签，格式如下：
//  @apirequest object * 通用的请求主体
//  @apiheader name optional desc
//  @apiheader name optional desc
//  @apiparam count int optional desc
//  @apiparam list array.string optional desc
//  @apiparam list.id int optional desc
//  @apiparam list.name int reqiured desc
//  @apiparam list.groups array.string optional.xxxx desc markdown enum:
//   * xx: xxxxx
//   * xx: xxxxx
//  @apiexample application/json summary
//  {
//   count: 5,
//   list: [
//     {id:1, name: 'name1', 'groups': [1,2]},
//     {id:2, name: 'name2', 'groups': [1,2]}
//   ]
//  }
func (api *API) parseRequest(l *lexer.Lexer, tag *lexer.Tag) error {
	data := tag.Words(3)
	if len(data) < 2 {
		return tag.ErrInvalidFormat()
	}

	if api.Requests == nil {
		api.Requests = make([]*Request, 0, 3)
	}

	var desc []byte
	if len(data) == 3 {
		desc = data[2]
	}

	req := &Request{
		Mimetype: string(data[1]),
		Type:     &schema.Schema{},
	}
	api.Requests = append(api.Requests, req)

	if err := req.Type.Build(tag, nil, data[0], nil, desc); err != nil {
		return err
	}

LOOP:
	for tag := l.Tag(); tag != nil; tag = l.Tag() {
		fn := req.parseExample
		switch strings.ToLower(tag.Name) {
		case "@apiexample":
			fn = req.parseExample
		case "@apiheader":
			fn = req.parseHeader
		case "@apiparam":
			fn = req.parseParam
		default:
			l.Backup(tag)
			break LOOP
		}

		if err := fn(tag); err != nil {
			return err
		}
	}

	return nil
}

// 检测内容是否都是有效的
/*
func (api *API) check() error {

}*/

// 解析参数标签，格式如下：
// 用于路径参数和查义参数，request 和 response 中的不在此解析
//  @tag name type.subtype optional.defaultValue markdown desc
func newParam(tag *lexer.Tag) (*Param, error) {
	data := tag.Words(4)
	if len(data) != 4 {
		return nil, tag.ErrInvalidFormat()
	}

	s := &schema.Schema{}
	if err := s.Build(tag, nil, data[1], data[2], nil); err != nil {
		return nil, err
	}

	return &Param{
		Name:     string(data[0]),
		Summary:  string(data[3]),
		Type:     s,
		Optional: s.Default != nil,
	}, nil
}
<commit_msg>[doc] 合并相同代码<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package doc

import (
	"bytes"
	"sort"
	"strings"

	"github.com/caixw/apidoc/doc/lexer"
	"github.com/caixw/apidoc/doc/schema"
)

// API 表示单个 API 文档
type API struct {
	responses
	Method      string     `yaml:"method" json:"method"`
	Path        string     `yaml:"path" json:"path"`
	Summary     string     `yaml:"summary" json:"summary"`
	Description Markdown   `yaml:"description,omitempty" json:"description,omitempty"`
	Tags        []string   `yaml:"tags,omitempty" json:"tags,omitempty"`
	Queries     []*Param   `yaml:"queries,omitempty" json:"queries,omitempty"` // 查询参数
	Params      []*Param   `yaml:"params,omitempty" json:"params,omitempty"`   // URL 参数
	Requests    []*Request `yaml:"requests,omitempty" json:"requests,omitempty"`
	Deprecated  string     `yaml:"deprecated,omitempty" json:"deprecated,omitempty"`
	Servers     []string   `yaml:"servers" json:"servers"`

	// 路径参数名称的集合
	// TODO 比较与 Params 中的数据。
	pathParams []string
}

// Param 简单参数的描述，比如查询参数等
type Param struct {
	Name     string         `yaml:"name" json:"name"`                             // 参数名称
	Type     *schema.Schema `yaml:"type" json:"type"`                             // 类型
	Summary  string         `yaml:"summary" json:"summary"`                       // 参数介绍
	Optional bool           `yaml:"optional,omitempty" json:"optional,omitempty"` // 是否为可选参数
}

func (doc *Doc) parseAPI(l *lexer.Lexer) error {
	api := &API{}

	for tag := l.Tag(); tag != nil; tag = l.Tag() {
		parse := api.parseAPI
		switch strings.ToLower(tag.Name) {
		case "@api":
			parse = api.parseAPI
		case "@apirequest":
			parse = api.parseRequest
		case "@apiresponse":
			parse = api.parseResponse
		default:
			return tag.ErrInvalidTag()
		}

		if err := parse(l, tag); err != nil {
			return err
		}
	}

	doc.locker.Lock()
	doc.Apis = append(doc.Apis, api)
	doc.locker.Unlock()

	return nil
}

type apiParser func(*API, *lexer.Lexer, *lexer.Tag) error

var apiParsers = map[string]apiParser{
	"@api":           (*API).parseapi,
	"@apiservers":    (*API).parseServers,
	"@apitags":       (*API).parseTags,
	"@apideprecated": (*API).parseDeprecated,
	"@apiquery":      (*API).parseQuery,
	"@apiparam":      (*API).parseParam,
}

// 分析 @api 以及子标签
func (api *API) parseAPI(l *lexer.Lexer, tag *lexer.Tag) error {
	l.Backup(tag) // 进来时，第一个肯定是 @api 标签，退回该标签，统一让 for 处理。

	for tag := l.Tag(); tag != nil; tag = l.Tag() {
		fn, found := apiParsers[strings.ToLower(tag.Name)]
		if !found {
			l.Backup(tag)
			return nil
		}

		if err := fn(api, l, tag); err != nil {
			return err
		}
	}

	return nil
}

// 解析 @api 标签，格式如下：
//  @api GET /path summary
func (api *API) parseapi(l *lexer.Lexer, tag *lexer.Tag) error {
	if api.Method != "" || api.Path != "" || api.Summary != "" {
		return tag.ErrDuplicateTag()
	}
	data := tag.Words(3)
	if len(data) != 3 {
		return tag.ErrInvalidFormat()
	}

	api.Method = strings.ToUpper(string(data[0])) // TODO 验证请求方法
	api.Path = string(data[1])
	api.Summary = string(data[2])

	return api.genPathParams(tag)
}

func (api *API) genPathParams(tag *lexer.Tag) error {
	names := make([]string, 0, len(api.Params))

	state := '}'
	index := 0
	for i, b := range api.Path {
		switch b {
		case '{':
			if state != '}' {
				return tag.ErrInvalidFormat()
			}

			state = '{'
			index = i
		case '}':
			if state != '{' {
				return tag.ErrInvalidFormat()
			}
			names = append(names, api.Path[index+1:i])
			state = '}'
		}
	} // end for

	// 缺少 } 结束符号
	if state == '{' {
		return tag.ErrInvalidFormat()
	}

	api.pathParams = names
	return nil
}

// 解析 @apiServers 标签，格式如下：
//  @apiServers s1,s2
func (api *API) parseServers(l *lexer.Lexer, tag *lexer.Tag) (err error) {
	if len(api.Servers) > 0 {
		return tag.ErrDuplicateTag()
	}

	if len(tag.Data) == 0 {
		return tag.ErrInvalidFormat()
	}

	if api.Servers, err = splitToArray(tag); err != nil {
		return err
	}

	return nil
}

// 解析 @apiTags 标签，格式如下：
//  @apiTags t1,t2
func (api *API) parseTags(l *lexer.Lexer, tag *lexer.Tag) (err error) {
	if len(api.Tags) > 0 {
		return tag.ErrDuplicateTag()
	}

	if len(tag.Data) == 0 {
		return tag.ErrInvalidFormat()
	}

	if api.Tags, err = splitToArray(tag); err != nil {
		return err
	}

	return nil
}

func splitToArray(tag *lexer.Tag) ([]string, error) {
	items := bytes.FieldsFunc(tag.Data, func(r rune) bool { return r == ',' })
	ret := make([]string, 0, len(items))

	for _, item := range items {
		ret = append(ret, string(bytes.TrimSpace(item)))
	}

	sort.Strings(ret)
	for i := 1; i < len(ret); i++ {
		if ret[i] == ret[i-1] {
			return nil, tag.ErrInvalidFormat() // 重复的名称
		}
	}

	return ret, nil
}

// 解析  @apiDeprecated 标签，格式如下：
//  @apiDeprecated description
func (api *API) parseDeprecated(l *lexer.Lexer, tag *lexer.Tag) error {
	if api.Deprecated != "" {
		return tag.ErrDuplicateTag()
	}

	if len(tag.Data) == 0 {
		return tag.ErrInvalidFormat()
	}

	api.Deprecated = string(tag.Data)
	return nil
}

// 解析 @apiQuery 标签，格式如下：
//  @apiQuery name type.subtype optional.defaultValue markdown desc
func (api *API) parseQuery(l *lexer.Lexer, tag *lexer.Tag) error {
	if api.Queries == nil {
		api.Queries = make([]*Param, 0, 10)
	}

	p, err := newParam(tag)
	if err != nil {
		return err
	}

	if api.queryExists(p.Name) {
		return tag.ErrDuplicateTag()
	}

	api.Queries = append(api.Queries, p)
	return nil
}

func (api *API) queryExists(name string) bool {
	for _, q := range api.Queries {
		if q.Name == name {
			return true
		}
	}

	return false
}

// 解析 @apiParam 标签，格式如下：
//  @apiParam name type.subtype optional.defaultValue markdown desc
func (api *API) parseParam(l *lexer.Lexer, tag *lexer.Tag) error {
	if api.Params == nil {
		api.Params = make([]*Param, 0, 3)
	}

	p, err := newParam(tag)
	if err != nil {
		return err
	}

	if api.paramExists(p.Name) {
		return tag.ErrDuplicateTag()
	}

	api.Params = append(api.Params, p)

	return nil
}

func (api *API) paramExists(name string) bool {
	for _, p := range api.Params {
		if p.Name == name {
			return true
		}
	}

	return false
}

// 解析 @apiRequest 及其子标签，格式如下：
//  @apirequest object * 通用的请求主体
//  @apiheader name optional desc
//  @apiheader name optional desc
//  @apiparam count int optional desc
//  @apiparam list array.string optional desc
//  @apiparam list.id int optional desc
//  @apiparam list.name int reqiured desc
//  @apiparam list.groups array.string optional.xxxx desc markdown enum:
//   * xx: xxxxx
//   * xx: xxxxx
//  @apiexample application/json summary
//  {
//   count: 5,
//   list: [
//     {id:1, name: 'name1', 'groups': [1,2]},
//     {id:2, name: 'name2', 'groups': [1,2]}
//   ]
//  }
func (api *API) parseRequest(l *lexer.Lexer, tag *lexer.Tag) error {
	data := tag.Words(3)
	if len(data) < 2 {
		return tag.ErrInvalidFormat()
	}

	if api.Requests == nil {
		api.Requests = make([]*Request, 0, 3)
	}

	var desc []byte
	if len(data) == 3 {
		desc = data[2]
	}

	req := &Request{
		Mimetype: string(data[1]),
		Type:     &schema.Schema{},
	}
	api.Requests = append(api.Requests, req)

	if err := req.Type.Build(tag, nil, data[0], nil, desc); err != nil {
		return err
	}

LOOP:
	for tag := l.Tag(); tag != nil; tag = l.Tag() {
		fn := req.parseExample
		switch strings.ToLower(tag.Name) {
		case "@apiexample":
			fn = req.parseExample
		case "@apiheader":
			fn = req.parseHeader
		case "@apiparam":
			fn = req.parseParam
		default:
			l.Backup(tag)
			break LOOP
		}

		if err := fn(tag); err != nil {
			return err
		}
	}

	return nil
}

// 检测内容是否都是有效的
/*
func (api *API) check() error {

}*/

// 解析参数标签，格式如下：
// 用于路径参数和查义参数，request 和 response 中的不在此解析
//  @tag name type.subtype optional.defaultValue markdown desc
func newParam(tag *lexer.Tag) (*Param, error) {
	data := tag.Words(4)
	if len(data) != 4 {
		return nil, tag.ErrInvalidFormat()
	}

	s := &schema.Schema{}
	if err := s.Build(tag, nil, data[1], data[2], nil); err != nil {
		return nil, err
	}

	return &Param{
		Name:     string(data[0]),
		Summary:  string(data[3]),
		Type:     s,
		Optional: s.Default != nil,
	}, nil
}
<|endoftext|>