<commit_before>package resource

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

	"github.com/hashicorp/terraform/config/module"
	"github.com/hashicorp/terraform/terraform"
)

const TestEnvVar = "TF_ACC"

// TestCheckFunc is the callback type used with acceptance tests to check
// the state of a resource. The state passed in is the latest state known,
// or in the case of being after a destroy, it is the last known state when
// it was created.
type TestCheckFunc func(*terraform.State) error

// TestCase is a single acceptance test case used to test the apply/destroy
// lifecycle of a resource in a specific configuration.
//
// When the destroy plan is executed, the config from the last TestStep
// is used to plan it.
type TestCase struct {
	// PreCheck, if non-nil, will be called before any test steps are
	// executed. It will only be executed in the case that the steps
	// would run, so it can be used for some validation before running
	// acceptance tests, such as verifying that keys are setup.
	PreCheck func()

	// Providers is the ResourceProvider that will be under test.
	//
	// Alternately, ProviderFactories can be specified for the providers
	// that are valid. This takes priority over Providers.
	//
	// The end effect of each is the same: specifying the providers that
	// are used within the tests.
	Providers         map[string]terraform.ResourceProvider
	ProviderFactories map[string]terraform.ResourceProviderFactory

	// CheckDestroy is called after the resource is finally destroyed
	// to allow the tester to test that the resource is truly gone.
	CheckDestroy TestCheckFunc

	// Steps are the apply sequences done within the context of the
	// same state. Each step can have its own check to verify correctness.
	Steps []TestStep
}

// TestStep is a single apply sequence of a test, done within the
// context of a state.
//
// Multiple TestSteps can be sequenced in a Test to allow testing
// potentially complex update logic. In general, simply create/destroy
// tests will only need one step.
type TestStep struct {
	// Config a string of the configuration to give to Terraform.
	Config string

	// Check is called after the Config is applied. Use this step to
	// make your own API calls to check the status of things, and to
	// inspect the format of the ResourceState itself.
	//
	// If an error is returned, the test will fail. In this case, a
	// destroy plan will still be attempted.
	//
	// If this is nil, no check is done on this step.
	Check TestCheckFunc

	// Destroy will create a destroy plan if set to true.
	Destroy bool
}

// Test performs an acceptance test on a resource.
//
// Tests are not run unless an environmental variable "TF_ACC" is
// set to some non-empty value. This is to avoid test cases surprising
// a user by creating real resources.
//
// Tests will fail unless the verbose flag (`go test -v`, or explicitly
// the "-test.v" flag) is set. Because some acceptance tests take quite
// long, we require the verbose flag so users are able to see progress
// output.
func Test(t TestT, c TestCase) {
	// We only run acceptance tests if an env var is set because they're
	// slow and generally require some outside configuration.
	if os.Getenv(TestEnvVar) == "" {
		t.Skip(fmt.Sprintf(
			"Acceptance tests skipped unless env '%s' set",
			TestEnvVar))
		return
	}

	// We require verbose mode so that the user knows what is going on.
	if !testTesting && !testing.Verbose() {
		t.Fatal("Acceptance tests must be run with the -v flag on tests")
		return
	}

	// Run the PreCheck if we have it
	if c.PreCheck != nil {
		c.PreCheck()
	}

	// Build our context options that we can
	ctxProviders := c.ProviderFactories
	if ctxProviders == nil {
		ctxProviders = make(map[string]terraform.ResourceProviderFactory)
		for k, p := range c.Providers {
			ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
		}
	}
	opts := terraform.ContextOpts{Providers: ctxProviders}

	// A single state variable to track the lifecycle, starting with no state
	var state *terraform.State

	// Go through each step and run it
	for i, step := range c.Steps {
		var err error
		log.Printf("[WARN] Test: Executing step %d", i)
		state, err = testStep(opts, state, step)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Step %d error: %s", i, err))
			break
		}
	}

	// If we have a state, then run the destroy
	if state != nil {
		destroyStep := TestStep{
			Config:  c.Steps[len(c.Steps)-1].Config,
			Check:   c.CheckDestroy,
			Destroy: true,
		}

		log.Printf("[WARN] Test: Executing destroy step")
		state, err := testStep(opts, state, destroyStep)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Error destroying resource! WARNING: Dangling resources\n"+
					"may exist. The full state and error is shown below.\n\n"+
					"Error: %s\n\nState: %s",
				err,
				state))
		}
	} else {
		log.Printf("[WARN] Skipping destroy test since there is no state.")
	}
}

func testStep(
	opts terraform.ContextOpts,
	state *terraform.State,
	step TestStep) (*terraform.State, error) {
	cfgPath, err := ioutil.TempDir("", "tf-test")
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary directory for config: %s", err)
	}
	defer os.RemoveAll(cfgPath)

	// Write the configuration
	cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf"))
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	_, err = io.Copy(cfgF, strings.NewReader(step.Config))
	cfgF.Close()
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	// Parse the configuration
	mod, err := module.NewTreeModule("", cfgPath)
	if err != nil {
		return state, fmt.Errorf(
			"Error loading configuration: %s", err)
	}

	// Load the modules
	modStorage := &module.FolderStorage{
		StorageDir: filepath.Join(cfgPath, ".tfmodules"),
	}
	err = mod.Load(modStorage, module.GetModeGet)
	if err != nil {
		return state, fmt.Errorf("Error downloading modules: %s", err)
	}

	// Build the context
	opts.Module = mod
	opts.State = state
	opts.Destroy = step.Destroy
	ctx := terraform.NewContext(&opts)
	if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
		estrs := make([]string, len(es))
		for i, e := range es {
			estrs[i] = e.Error()
		}
		return state, fmt.Errorf(
			"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
			ws, estrs)
	}

	// Refresh!
	state, err = ctx.Refresh()
	if err != nil {
		return state, fmt.Errorf(
			"Error refreshing: %s", err)
	}

	// Plan!
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf(
			"Error planning: %s", err)
	} else {
		log.Printf("[WARN] Test: Step plan: %s", p)
	}

	// Apply!
	state, err = ctx.Apply()
	if err != nil {
		return state, fmt.Errorf("Error applying: %s", err)
	}

	// Check! Excitement!
	if step.Check != nil {
		if err := step.Check(state); err != nil {
			return state, fmt.Errorf("Check failed: %s", err)
		}
	}

	// Now, verify that Plan is now empty and we don't have a perpetual diff issue
	// We do this with TWO plans. One without a refresh.
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf("Error on follow-up plan: %s", err)
	} else {
		if p.Diff != nil && !p.Diff.Empty() {
			return state, fmt.Errorf(
				"After applying this step, the plan was not empty:\n\n%s", p)
		}
	}

	// And another after a Refresh.
	state, err = ctx.Refresh()
	if err != nil {
		return state, fmt.Errorf(
			"Error on follow-up refresh: %s", err)
	}
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf("Error on second follow-up plan: %s", err)
	} else {
		if p.Diff != nil && !p.Diff.Empty() {
			return state, fmt.Errorf(
				"After applying this step and refreshing, the plan was not empty:\n\n%s", p)
		}
	}

	// Made it here? Good job test step!
	return state, nil
}

// ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into
// a single TestCheckFunc.
//
// As a user testing their provider, this lets you decompose your checks
// into smaller pieces more easily.
func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc {
	return func(s *terraform.State) error {
		for _, f := range fs {
			if err := f(s); err != nil {
				return err
			}
		}

		return nil
	}
}

func TestCheckResourceAttr(name, key, value string) TestCheckFunc {
	return func(s *terraform.State) error {
		ms := s.RootModule()
		rs, ok := ms.Resources[name]
		if !ok {
			return fmt.Errorf("Not found: %s", name)
		}

		is := rs.Primary
		if is == nil {
			return fmt.Errorf("No primary instance: %s", name)
		}

		if is.Attributes[key] != value {
			return fmt.Errorf(
				"%s: Attribute '%s' expected %#v, got %#v",
				name,
				key,
				value,
				is.Attributes[key])
		}

		return nil
	}
}

// TestCheckResourceAttrPtr is like TestCheckResourceAttr except the
// value is a pointer so that it can be updated while the test is running.
// It will only be dereferenced at the point this step is run.
func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc {
	return func(s *terraform.State) error {
		return TestCheckResourceAttr(name, key, *value)(s)
	}
}

// TestT is the interface used to handle the test lifecycle of a test.
//
// Users should just use a *testing.T object, which implements this.
type TestT interface {
	Error(args ...interface{})
	Fatal(args ...interface{})
	Skip(args ...interface{})
}

// This is set to true by unit tests to alter some behavior
var testTesting = false
<commit_msg>providers/template: add tests, address review comments<commit_after>package resource

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

	"github.com/hashicorp/terraform/config/module"
	"github.com/hashicorp/terraform/terraform"
)

const TestEnvVar = "TF_ACC"

// TestCheckFunc is the callback type used with acceptance tests to check
// the state of a resource. The state passed in is the latest state known,
// or in the case of being after a destroy, it is the last known state when
// it was created.
type TestCheckFunc func(*terraform.State) error

// TestCase is a single acceptance test case used to test the apply/destroy
// lifecycle of a resource in a specific configuration.
//
// When the destroy plan is executed, the config from the last TestStep
// is used to plan it.
type TestCase struct {
	// PreCheck, if non-nil, will be called before any test steps are
	// executed. It will only be executed in the case that the steps
	// would run, so it can be used for some validation before running
	// acceptance tests, such as verifying that keys are setup.
	PreCheck func()

	// Providers is the ResourceProvider that will be under test.
	//
	// Alternately, ProviderFactories can be specified for the providers
	// that are valid. This takes priority over Providers.
	//
	// The end effect of each is the same: specifying the providers that
	// are used within the tests.
	Providers         map[string]terraform.ResourceProvider
	ProviderFactories map[string]terraform.ResourceProviderFactory

	// CheckDestroy is called after the resource is finally destroyed
	// to allow the tester to test that the resource is truly gone.
	CheckDestroy TestCheckFunc

	// Steps are the apply sequences done within the context of the
	// same state. Each step can have its own check to verify correctness.
	Steps []TestStep
}

// TestStep is a single apply sequence of a test, done within the
// context of a state.
//
// Multiple TestSteps can be sequenced in a Test to allow testing
// potentially complex update logic. In general, simply create/destroy
// tests will only need one step.
type TestStep struct {
	// Config a string of the configuration to give to Terraform.
	Config string

	// Check is called after the Config is applied. Use this step to
	// make your own API calls to check the status of things, and to
	// inspect the format of the ResourceState itself.
	//
	// If an error is returned, the test will fail. In this case, a
	// destroy plan will still be attempted.
	//
	// If this is nil, no check is done on this step.
	Check TestCheckFunc

	// Destroy will create a destroy plan if set to true.
	Destroy bool

	// TransientResource indicates that resources created as part
	// of this test step are temporary and might be recreated anew
	// with every planning step. This should only be set for
	// pseudo-resources, like the null resource or templates.
	TransientResource bool
}

// Test performs an acceptance test on a resource.
//
// Tests are not run unless an environmental variable "TF_ACC" is
// set to some non-empty value. This is to avoid test cases surprising
// a user by creating real resources.
//
// Tests will fail unless the verbose flag (`go test -v`, or explicitly
// the "-test.v" flag) is set. Because some acceptance tests take quite
// long, we require the verbose flag so users are able to see progress
// output.
func Test(t TestT, c TestCase) {
	// We only run acceptance tests if an env var is set because they're
	// slow and generally require some outside configuration.
	if os.Getenv(TestEnvVar) == "" {
		t.Skip(fmt.Sprintf(
			"Acceptance tests skipped unless env '%s' set",
			TestEnvVar))
		return
	}

	// We require verbose mode so that the user knows what is going on.
	if !testTesting && !testing.Verbose() {
		t.Fatal("Acceptance tests must be run with the -v flag on tests")
		return
	}

	// Run the PreCheck if we have it
	if c.PreCheck != nil {
		c.PreCheck()
	}

	// Build our context options that we can
	ctxProviders := c.ProviderFactories
	if ctxProviders == nil {
		ctxProviders = make(map[string]terraform.ResourceProviderFactory)
		for k, p := range c.Providers {
			ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
		}
	}
	opts := terraform.ContextOpts{Providers: ctxProviders}

	// A single state variable to track the lifecycle, starting with no state
	var state *terraform.State

	// Go through each step and run it
	for i, step := range c.Steps {
		var err error
		log.Printf("[WARN] Test: Executing step %d", i)
		state, err = testStep(opts, state, step)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Step %d error: %s", i, err))
			break
		}
	}

	// If we have a state, then run the destroy
	if state != nil {
		destroyStep := TestStep{
			Config:  c.Steps[len(c.Steps)-1].Config,
			Check:   c.CheckDestroy,
			Destroy: true,
		}

		log.Printf("[WARN] Test: Executing destroy step")
		state, err := testStep(opts, state, destroyStep)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Error destroying resource! WARNING: Dangling resources\n"+
					"may exist. The full state and error is shown below.\n\n"+
					"Error: %s\n\nState: %s",
				err,
				state))
		}
	} else {
		log.Printf("[WARN] Skipping destroy test since there is no state.")
	}
}

func testStep(
	opts terraform.ContextOpts,
	state *terraform.State,
	step TestStep) (*terraform.State, error) {
	cfgPath, err := ioutil.TempDir("", "tf-test")
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary directory for config: %s", err)
	}
	defer os.RemoveAll(cfgPath)

	// Write the configuration
	cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf"))
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	_, err = io.Copy(cfgF, strings.NewReader(step.Config))
	cfgF.Close()
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	// Parse the configuration
	mod, err := module.NewTreeModule("", cfgPath)
	if err != nil {
		return state, fmt.Errorf(
			"Error loading configuration: %s", err)
	}

	// Load the modules
	modStorage := &module.FolderStorage{
		StorageDir: filepath.Join(cfgPath, ".tfmodules"),
	}
	err = mod.Load(modStorage, module.GetModeGet)
	if err != nil {
		return state, fmt.Errorf("Error downloading modules: %s", err)
	}

	// Build the context
	opts.Module = mod
	opts.State = state
	opts.Destroy = step.Destroy
	ctx := terraform.NewContext(&opts)
	if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
		estrs := make([]string, len(es))
		for i, e := range es {
			estrs[i] = e.Error()
		}
		return state, fmt.Errorf(
			"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
			ws, estrs)
	}

	// Refresh!
	state, err = ctx.Refresh()
	if err != nil {
		return state, fmt.Errorf(
			"Error refreshing: %s", err)
	}

	// Plan!
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf(
			"Error planning: %s", err)
	} else {
		log.Printf("[WARN] Test: Step plan: %s", p)
	}

	// Apply!
	state, err = ctx.Apply()
	if err != nil {
		return state, fmt.Errorf("Error applying: %s", err)
	}

	// Check! Excitement!
	if step.Check != nil {
		if err := step.Check(state); err != nil {
			return state, fmt.Errorf("Check failed: %s", err)
		}
	}

	// Now, verify that Plan is now empty and we don't have a perpetual diff issue
	// We do this with TWO plans. One without a refresh.
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf("Error on follow-up plan: %s", err)
	} else {
		if p.Diff != nil && !p.Diff.Empty() {
			return state, fmt.Errorf(
				"After applying this step, the plan was not empty:\n\n%s", p)
		}
	}

	// And another after a Refresh.
	state, err = ctx.Refresh()
	if err != nil {
		return state, fmt.Errorf(
			"Error on follow-up refresh: %s", err)
	}
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf("Error on second follow-up plan: %s", err)
	} else {
		if p.Diff != nil && !p.Diff.Empty() && !step.TransientResource {
			return state, fmt.Errorf(
				"After applying this step and refreshing, the plan was not empty:\n\n%s", p)
		}
	}

	// Made it here? Good job test step!
	return state, nil
}

// ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into
// a single TestCheckFunc.
//
// As a user testing their provider, this lets you decompose your checks
// into smaller pieces more easily.
func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc {
	return func(s *terraform.State) error {
		for _, f := range fs {
			if err := f(s); err != nil {
				return err
			}
		}

		return nil
	}
}

func TestCheckResourceAttr(name, key, value string) TestCheckFunc {
	return func(s *terraform.State) error {
		ms := s.RootModule()
		rs, ok := ms.Resources[name]
		if !ok {
			return fmt.Errorf("Not found: %s", name)
		}

		is := rs.Primary
		if is == nil {
			return fmt.Errorf("No primary instance: %s", name)
		}

		if is.Attributes[key] != value {
			return fmt.Errorf(
				"%s: Attribute '%s' expected %#v, got %#v",
				name,
				key,
				value,
				is.Attributes[key])
		}

		return nil
	}
}

// TestCheckResourceAttrPtr is like TestCheckResourceAttr except the
// value is a pointer so that it can be updated while the test is running.
// It will only be dereferenced at the point this step is run.
func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc {
	return func(s *terraform.State) error {
		return TestCheckResourceAttr(name, key, *value)(s)
	}
}

// TestT is the interface used to handle the test lifecycle of a test.
//
// Users should just use a *testing.T object, which implements this.
type TestT interface {
	Error(args ...interface{})
	Fatal(args ...interface{})
	Skip(args ...interface{})
}

// This is set to true by unit tests to alter some behavior
var testTesting = false
<|endoftext|>


<commit_before>package resource

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"testing"

	"github.com/hashicorp/terraform/config"
	"github.com/hashicorp/terraform/terraform"
)

const TestEnvVar = "TF_ACC"

// TestCheckFunc is the callback type used with acceptance tests to check
// the state of a resource. The state passed in is the latest state known,
// or in the case of being after a destroy, it is the last known state when
// it was created.
type TestCheckFunc func(*terraform.State) error

// TestCase is a single acceptance test case used to test the apply/destroy
// lifecycle of a resource in a specific configuration.
//
// When the destroy plan is executed, the config from the last TestStep
// is used to plan it.
type TestCase struct {
	// Provider is the ResourceProvider that will be under test.
	Providers map[string]terraform.ResourceProvider

	// CheckDestroy is called after the resource is finally destroyed
	// to allow the tester to test that the resource is truly gone.
	CheckDestroy TestCheckFunc

	// Steps are the apply sequences done within the context of the
	// same state. Each step can have its own check to verify correctness.
	Steps []TestStep
}

// TestStep is a single apply sequence of a test, done within the
// context of a state.
//
// Multiple TestSteps can be sequenced in a Test to allow testing
// potentially complex update logic. In general, simply create/destroy
// tests will only need one step.
type TestStep struct {
	// Config a string of the configuration to give to Terraform.
	Config string

	// Check is called after the Config is applied. Use this step to
	// make your own API calls to check the status of things, and to
	// inspect the format of the ResourceState itself.
	//
	// If an error is returned, the test will fail. In this case, a
	// destroy plan will still be attempted.
	//
	// If this is nil, no check is done on this step.
	Check TestCheckFunc

	// Destroy will create a destroy plan if set to true.
	Destroy bool
}

// Test performs an acceptance test on a resource.
//
// Tests are not run unless an environmental variable "TF_ACC" is
// set to some non-empty value. This is to avoid test cases surprising
// a user by creating real resources.
//
// Tests will fail unless the verbose flag (`go test -v`, or explicitly
// the "-test.v" flag) is set. Because some acceptance tests take quite
// long, we require the verbose flag so users are able to see progress
// output.
func Test(t TestT, c TestCase) {
	// We only run acceptance tests if an env var is set because they're
	// slow and generally require some outside configuration.
	if os.Getenv(TestEnvVar) == "" {
		t.Skip(fmt.Sprintf(
			"Acceptance tests skipped unless env '%s' set",
			TestEnvVar))
		return
	}

	// We require verbose mode so that the user knows what is going on.
	if !testTesting && !testing.Verbose() {
		t.Fatal("Acceptance tests must be run with the -v flag on tests")
		return
	}

	// Build our context options that we can
	ctxProviders := make(map[string]terraform.ResourceProviderFactory)
	for k, p := range c.Providers {
		ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
	}
	opts := terraform.ContextOpts{Providers: ctxProviders}

	// A single state variable to track the lifecycle, starting with no state
	var state *terraform.State

	// Go through each step and run it
	for i, step := range c.Steps {
		var err error
		state, err = testStep(opts, state, step)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Step %d error: %s", i, err))
			break
		}
	}

	// If we have a state, then run the destroy
	if state != nil {
		destroyStep := TestStep{
			Config:  c.Steps[len(c.Steps)-1].Config,
			Check:   c.CheckDestroy,
			Destroy: true,
		}

		state, err := testStep(opts, state, destroyStep)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Error destroying resource! WARNING: Dangling resources\n"+
					"may exist. The full state and error is shown below.\n\n"+
					"Error: %s\n\nState: %s",
				err,
				state))
		}
	}
}

func testStep(
	opts terraform.ContextOpts,
	state *terraform.State,
	step TestStep) (*terraform.State, error) {
	// Write the configuration
	cfgF, err := ioutil.TempFile("", "tf-test")
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}
	cfgPath := cfgF.Name() + ".tf"
	cfgF.Close()
	os.Remove(cfgF.Name())

	cfgF, err = os.Create(cfgPath)
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}
	defer os.Remove(cfgPath)

	_, err = io.Copy(cfgF, strings.NewReader(step.Config))
	cfgF.Close()
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	// Parse the configuration
	config, err := config.Load(cfgPath)
	if err != nil {
		return state, fmt.Errorf(
			"Error parsing configuration: %s", err)
	}

	// Build the context
	opts.Config = config
	ctx := terraform.NewContext(&opts)
	if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
		return state, fmt.Errorf(
			"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
			ws, es)
	}

	// Plan!
	if _, err := ctx.Plan(&terraform.PlanOpts{Destroy: step.Destroy}); err != nil {
		return state, fmt.Errorf(
			"Error planning: %s", err)
	}

	// Apply!
	state, err = ctx.Apply()
	if err != nil {
		return state, fmt.Errorf("Error applying: %s", err)
	}

	// Check! Excitement!
	if step.Check != nil {
		if err = step.Check(state); err != nil {
			err = fmt.Errorf("Check failed: %s", err)
		}
	}

	return state, err
}

// TestT is the interface used to handle the test lifecycle of a test.
//
// Users should just use a *testing.T object, which implements this.
type TestT interface {
	Error(args ...interface{})
	Fatal(args ...interface{})
	Skip(args ...interface{})
}

// This is set to true by unit tests to alter some behavior
var testTesting = false
<commit_msg>helper/resource: persist state, log<commit_after>package resource

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"testing"

	"github.com/hashicorp/terraform/config"
	"github.com/hashicorp/terraform/terraform"
)

const TestEnvVar = "TF_ACC"

// TestCheckFunc is the callback type used with acceptance tests to check
// the state of a resource. The state passed in is the latest state known,
// or in the case of being after a destroy, it is the last known state when
// it was created.
type TestCheckFunc func(*terraform.State) error

// TestCase is a single acceptance test case used to test the apply/destroy
// lifecycle of a resource in a specific configuration.
//
// When the destroy plan is executed, the config from the last TestStep
// is used to plan it.
type TestCase struct {
	// Provider is the ResourceProvider that will be under test.
	Providers map[string]terraform.ResourceProvider

	// CheckDestroy is called after the resource is finally destroyed
	// to allow the tester to test that the resource is truly gone.
	CheckDestroy TestCheckFunc

	// Steps are the apply sequences done within the context of the
	// same state. Each step can have its own check to verify correctness.
	Steps []TestStep
}

// TestStep is a single apply sequence of a test, done within the
// context of a state.
//
// Multiple TestSteps can be sequenced in a Test to allow testing
// potentially complex update logic. In general, simply create/destroy
// tests will only need one step.
type TestStep struct {
	// Config a string of the configuration to give to Terraform.
	Config string

	// Check is called after the Config is applied. Use this step to
	// make your own API calls to check the status of things, and to
	// inspect the format of the ResourceState itself.
	//
	// If an error is returned, the test will fail. In this case, a
	// destroy plan will still be attempted.
	//
	// If this is nil, no check is done on this step.
	Check TestCheckFunc

	// Destroy will create a destroy plan if set to true.
	Destroy bool
}

// Test performs an acceptance test on a resource.
//
// Tests are not run unless an environmental variable "TF_ACC" is
// set to some non-empty value. This is to avoid test cases surprising
// a user by creating real resources.
//
// Tests will fail unless the verbose flag (`go test -v`, or explicitly
// the "-test.v" flag) is set. Because some acceptance tests take quite
// long, we require the verbose flag so users are able to see progress
// output.
func Test(t TestT, c TestCase) {
	// We only run acceptance tests if an env var is set because they're
	// slow and generally require some outside configuration.
	if os.Getenv(TestEnvVar) == "" {
		t.Skip(fmt.Sprintf(
			"Acceptance tests skipped unless env '%s' set",
			TestEnvVar))
		return
	}

	// We require verbose mode so that the user knows what is going on.
	if !testTesting && !testing.Verbose() {
		t.Fatal("Acceptance tests must be run with the -v flag on tests")
		return
	}

	// Build our context options that we can
	ctxProviders := make(map[string]terraform.ResourceProviderFactory)
	for k, p := range c.Providers {
		ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
	}
	opts := terraform.ContextOpts{Providers: ctxProviders}

	// A single state variable to track the lifecycle, starting with no state
	var state *terraform.State

	// Go through each step and run it
	for i, step := range c.Steps {
		var err error
		log.Printf("[WARN] Test: Executing step %d", i)
		state, err = testStep(opts, state, step)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Step %d error: %s", i, err))
			break
		}
	}

	// If we have a state, then run the destroy
	if state != nil {
		destroyStep := TestStep{
			Config:  c.Steps[len(c.Steps)-1].Config,
			Check:   c.CheckDestroy,
			Destroy: true,
		}

		log.Printf("[WARN] Test: Executing destroy step")
		state, err := testStep(opts, state, destroyStep)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Error destroying resource! WARNING: Dangling resources\n"+
					"may exist. The full state and error is shown below.\n\n"+
					"Error: %s\n\nState: %s",
				err,
				state))
		}
	} else {
		log.Printf("[WARN] Skipping destroy test since there is no state.")
	}
}

func testStep(
	opts terraform.ContextOpts,
	state *terraform.State,
	step TestStep) (*terraform.State, error) {
	// Write the configuration
	cfgF, err := ioutil.TempFile("", "tf-test")
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}
	cfgPath := cfgF.Name() + ".tf"
	cfgF.Close()
	os.Remove(cfgF.Name())

	cfgF, err = os.Create(cfgPath)
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}
	defer os.Remove(cfgPath)

	_, err = io.Copy(cfgF, strings.NewReader(step.Config))
	cfgF.Close()
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	// Parse the configuration
	config, err := config.Load(cfgPath)
	if err != nil {
		return state, fmt.Errorf(
			"Error parsing configuration: %s", err)
	}

	// Build the context
	opts.Config = config
	opts.State = state
	ctx := terraform.NewContext(&opts)
	if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
		return state, fmt.Errorf(
			"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
			ws, es)
	}

	// Plan!
	if _, err := ctx.Plan(&terraform.PlanOpts{Destroy: step.Destroy}); err != nil {
		return state, fmt.Errorf(
			"Error planning: %s", err)
	}

	// Apply!
	state, err = ctx.Apply()
	if err != nil {
		return state, fmt.Errorf("Error applying: %s", err)
	}

	// Check! Excitement!
	if step.Check != nil {
		if err = step.Check(state); err != nil {
			err = fmt.Errorf("Check failed: %s", err)
		}
	}

	return state, err
}

// TestT is the interface used to handle the test lifecycle of a test.
//
// Users should just use a *testing.T object, which implements this.
type TestT interface {
	Error(args ...interface{})
	Fatal(args ...interface{})
	Skip(args ...interface{})
}

// This is set to true by unit tests to alter some behavior
var testTesting = false
<|endoftext|>


<commit_before>package kloud

import (
	"encoding/json"
	"errors"
	"fmt"
	"koding/kites/kloud/contexthelper/session"
	"koding/kites/kloud/klient"
	"koding/kites/kloud/userdata"
	"strconv"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/fatih/structs"
	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/terraform/terraform"
	"github.com/koding/kite/protocol"
	"github.com/mitchellh/mapstructure"
	"github.com/nu7hatch/gouuid"
)

type TerraformMachine struct {
	Provider    string            `json:"provider"`
	Label       string            `json:"label"`
	Region      string            `json:"region"`
	QueryString string            `json:"queryString,omitempty"`
	Attributes  map[string]string `json:"attributes"`
}

type Machines struct {
	Machines []TerraformMachine `json:"machines"`
}

type buildData struct {
	Template string
	Region   string
	KiteIds  map[string]string
}

func (m *Machines) AppendRegion(region string) {
	for i, machine := range m.Machines {
		machine.Region = region
		m.Machines[i] = machine
	}
}

func (m *Machines) AppendQueryString(queryStrings map[string]string) {
	for i, machine := range m.Machines {
		queryString := queryStrings[machine.Label]
		machine.QueryString = protocol.Kite{ID: queryString}.String()
		m.Machines[i] = machine
	}
}

// WithLabel returns the machine with the associated label
func (m *Machines) WithLabel(label string) (TerraformMachine, error) {
	for _, machine := range m.Machines {
		if machine.Label == label {
			return machine, nil
		}
	}

	return TerraformMachine{}, fmt.Errorf("couldn't find machine with label '%s", label)
}

func machinesFromState(state *terraform.State) (*Machines, error) {
	if state.Modules == nil {
		return nil, errors.New("state modules is empty")
	}

	out := &Machines{
		Machines: make([]TerraformMachine, 0),
	}

	for _, m := range state.Modules {
		for resource, r := range m.Resources {
			if r.Primary == nil {
				continue
			}

			provider, label, err := parseProviderAndLabel(resource)
			if err != nil {
				return nil, err
			}

			attrs := make(map[string]string, len(r.Primary.Attributes))
			for key, val := range r.Primary.Attributes {
				attrs[key] = val
			}

			out.Machines = append(out.Machines, TerraformMachine{
				Provider:   provider,
				Label:      label,
				Attributes: attrs,
			})
		}
	}

	return out, nil
}

func machinesFromPlan(plan *terraform.Plan) (*Machines, error) {
	if plan.Diff == nil {
		return nil, errors.New("plan diff is empty")
	}

	if plan.Diff.Modules == nil {
		return nil, errors.New("plan diff module is empty")
	}

	out := &Machines{
		Machines: make([]TerraformMachine, 0),
	}

	for _, d := range plan.Diff.Modules {
		if d.Resources == nil {
			continue
		}

		for providerResource, r := range d.Resources {
			if r.Attributes == nil {
				continue
			}

			attrs := make(map[string]string, len(r.Attributes))
			for name, a := range r.Attributes {
				attrs[name] = a.New
			}

			provider, label, err := parseProviderAndLabel(providerResource)
			if err != nil {
				return nil, err
			}

			out.Machines = append(out.Machines, TerraformMachine{
				Provider:   provider,
				Label:      label,
				Attributes: attrs,
			})
		}
	}

	return out, nil
}

func parseProviderAndLabel(resource string) (string, string, error) {
	// resource is in the form of "aws_instance.foo.bar"
	splitted := strings.Split(resource, "_")
	if len(splitted) < 2 {
		return "", "", fmt.Errorf("provider resource is unknown: %v", splitted)
	}

	// splitted[1]: instance.foo.bar
	resourceSplitted := strings.SplitN(splitted[1], ".", 2)

	provider := splitted[0]      // aws
	label := resourceSplitted[1] // foo.bar

	return provider, label, nil
}

func injectKodingData(ctx context.Context, content, username string, creds *terraformCredentials) (*buildData, error) {
	sess, ok := session.FromContext(ctx)
	if !ok {
		return nil, errors.New("session context is not passed")
	}

	var awsOutput *AwsBootstrapOutput
	for _, cred := range creds.Creds {
		if cred.Provider != "aws" {
			continue
		}

		if err := mapstructure.Decode(cred.Data, &awsOutput); err != nil {
			return nil, err
		}
	}

	if structs.HasZero(awsOutput) {
		return nil, fmt.Errorf("Bootstrap data is incomplete: %v", awsOutput)
	}

	var data struct {
		Resource struct {
			Aws_Instance map[string]map[string]interface{} `json:"aws_instance"`
		} `json:"resource"`
		Provider struct {
			Aws struct {
				Region    string `json:"region"`
				AccessKey string `json:"access_key"`
				SecretKey string `json:"secret_key"`
			} `json:"aws"`
		} `json:"provider"`
		Variable map[string]map[string]interface{} `json:"variable,omitempty"`
	}

	if err := json.Unmarshal([]byte(content), &data); err != nil {
		return nil, err
	}

	if len(data.Resource.Aws_Instance) == 0 {
		return nil, fmt.Errorf("instance is empty: %v", data.Resource.Aws_Instance)
	}

	kiteIds := make(map[string]string)

	for resourceName, instance := range data.Resource.Aws_Instance {
		instance["key_name"] = awsOutput.KeyPair

		// if nothing is provided or the ami is empty use default Ubuntu AMI's
		if a, ok := instance["ami"]; !ok {
			instance["ami"] = awsOutput.AMI
		} else {
			if ami, ok := a.(string); ok && ami == "" {
				instance["ami"] = awsOutput.AMI
			}
		}

		// only ovveride if the user doesn't provider it's own subnet_id
		if instance["subnet_id"] == nil {
			instance["subnet_id"] = awsOutput.Subnet
			instance["security_groups"] = []string{awsOutput.SG}
		}

		// means there will be several instances, we need to create a userdata
		// with count interpolation, because each machine must have an unique
		// kite id.
		var count int = 1
		if c, ok := instance["count"]; ok {
			// we receive it as float64
			if cFloat, ok := c.(float64); !ok {
				return nil, fmt.Errorf("count statement should be an integer, got: %+v", c)
			} else {
				count = int(cFloat)
			}
		}

		// this part will be the same for all machines
		userCfg := &userdata.CloudInitConfig{
			Username: username,
			Groups:   []string{"sudo"},
			Hostname: username, // no typo here. hostname = username
		}

		// prepend custom script if available
		if c, ok := instance["user_data"]; ok {
			if customCMD, ok := c.(string); ok {
				userCfg.CustomCMD = customCMD
			}
		}

		// will be replaced with the kitekeys we create below
		userCfg.KiteKey = "${lookup(var.kitekeys, count.index)}"

		userdata, err := sess.Userdata.Create(userCfg)
		if err != nil {
			return nil, err
		}

		instance["user_data"] = string(userdata)

		// create independent kiteKey for each machine and create a Terraform
		// lookup map, which is used in conjuctuon with the `count.index`
		countKeys := map[string]string{}
		for i := 0; i < count; i++ {
			// create a new kite id for every new aws resource
			kiteUUID, err := uuid.NewV4()
			if err != nil {
				return nil, err
			}

			kiteId := kiteUUID.String()

			kiteKey, err := sess.Userdata.Keycreator.Create(username, kiteId)
			if err != nil {
				return nil, err
			}

			// if the count is greater than 1, terraform will change the labels
			// and append a number(starting with index 0) to each label
			if count != 1 {
				kiteIds[resourceName+"."+strconv.Itoa(i)] = kiteId
			} else {
				kiteIds[resourceName] = kiteId
			}

			countKeys[strconv.Itoa(i)] = kiteKey
		}

		data.Variable["kitekeys"] = map[string]interface{}{
			"default": countKeys,
		}

		data.Resource.Aws_Instance[resourceName] = instance
	}

	out, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		return nil, err
	}

	b := &buildData{
		Template: string(out),
		KiteIds:  kiteIds,
		Region:   data.Provider.Aws.Region,
	}

	return b, nil
}

func varsFromCredentials(creds *terraformCredentials) map[string]string {
	vars := make(map[string]string, 0)
	for _, cred := range creds.Creds {
		for k, v := range cred.Data {
			vars[k] = v
		}
	}
	return vars
}

func checkKlients(ctx context.Context, kiteIds map[string]string) error {
	sess, ok := session.FromContext(ctx)
	if !ok {
		return errors.New("session context is not passed")
	}

	var wg sync.WaitGroup
	var mu sync.Mutex // protects multierror and outputs
	var multiErrors error

	check := func(label, kiteId string) error {
		queryString := protocol.Kite{ID: kiteId}.String()
		klientRef, err := klient.NewWithTimeout(sess.Kite, queryString, time.Minute*5)
		if err != nil {
			return err
		}
		defer klientRef.Close()

		return klientRef.Ping()
	}

	for l, k := range kiteIds {
		wg.Add(1)
		go func(label, kiteId string) {
			if err := check(label, kiteId); err != nil {
				mu.Lock()
				multiErrors = multierror.Append(multiErrors,
					fmt.Errorf("Couldn't check '%s:%s'", label, kiteId))
				mu.Unlock()
			}
			wg.Done()
		}(l, k)
	}

	wg.Wait()

	return multiErrors
}

// isVariable checkes whether the given string is a template variable, such as:
// "${var.region}"
func isVariable(v string) bool {
	return v[0] == '$'
}

// parseAccountID parses an AWS arn string to get the Account ID
func parseAccountID(arn string) (string, error) {
	// example arn string: "arn:aws:iam::213456789:user/username"
	// returns: 213456789.
	splitted := strings.Split(strings.TrimPrefix(arn, "arn:aws:iam::"), ":")
	if len(splitted) != 2 {
		return "", fmt.Errorf("Couldn't parse arn string: %s", arn)
	}

	return splitted[0], nil
}
<commit_msg>kloud: fix panicing<commit_after>package kloud

import (
	"encoding/json"
	"errors"
	"fmt"
	"koding/kites/kloud/contexthelper/session"
	"koding/kites/kloud/klient"
	"koding/kites/kloud/userdata"
	"strconv"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/fatih/structs"
	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/terraform/terraform"
	"github.com/koding/kite/protocol"
	"github.com/mitchellh/mapstructure"
	"github.com/nu7hatch/gouuid"
)

type TerraformMachine struct {
	Provider    string            `json:"provider"`
	Label       string            `json:"label"`
	Region      string            `json:"region"`
	QueryString string            `json:"queryString,omitempty"`
	Attributes  map[string]string `json:"attributes"`
}

type Machines struct {
	Machines []TerraformMachine `json:"machines"`
}

type buildData struct {
	Template string
	Region   string
	KiteIds  map[string]string
}

func (m *Machines) AppendRegion(region string) {
	for i, machine := range m.Machines {
		machine.Region = region
		m.Machines[i] = machine
	}
}

func (m *Machines) AppendQueryString(queryStrings map[string]string) {
	for i, machine := range m.Machines {
		queryString := queryStrings[machine.Label]
		machine.QueryString = protocol.Kite{ID: queryString}.String()
		m.Machines[i] = machine
	}
}

// WithLabel returns the machine with the associated label
func (m *Machines) WithLabel(label string) (TerraformMachine, error) {
	for _, machine := range m.Machines {
		if machine.Label == label {
			return machine, nil
		}
	}

	return TerraformMachine{}, fmt.Errorf("couldn't find machine with label '%s", label)
}

func machinesFromState(state *terraform.State) (*Machines, error) {
	if state.Modules == nil {
		return nil, errors.New("state modules is empty")
	}

	out := &Machines{
		Machines: make([]TerraformMachine, 0),
	}

	for _, m := range state.Modules {
		for resource, r := range m.Resources {
			if r.Primary == nil {
				continue
			}

			provider, label, err := parseProviderAndLabel(resource)
			if err != nil {
				return nil, err
			}

			attrs := make(map[string]string, len(r.Primary.Attributes))
			for key, val := range r.Primary.Attributes {
				attrs[key] = val
			}

			out.Machines = append(out.Machines, TerraformMachine{
				Provider:   provider,
				Label:      label,
				Attributes: attrs,
			})
		}
	}

	return out, nil
}

func machinesFromPlan(plan *terraform.Plan) (*Machines, error) {
	if plan.Diff == nil {
		return nil, errors.New("plan diff is empty")
	}

	if plan.Diff.Modules == nil {
		return nil, errors.New("plan diff module is empty")
	}

	out := &Machines{
		Machines: make([]TerraformMachine, 0),
	}

	for _, d := range plan.Diff.Modules {
		if d.Resources == nil {
			continue
		}

		for providerResource, r := range d.Resources {
			if r.Attributes == nil {
				continue
			}

			attrs := make(map[string]string, len(r.Attributes))
			for name, a := range r.Attributes {
				attrs[name] = a.New
			}

			provider, label, err := parseProviderAndLabel(providerResource)
			if err != nil {
				return nil, err
			}

			out.Machines = append(out.Machines, TerraformMachine{
				Provider:   provider,
				Label:      label,
				Attributes: attrs,
			})
		}
	}

	return out, nil
}

func parseProviderAndLabel(resource string) (string, string, error) {
	// resource is in the form of "aws_instance.foo.bar"
	splitted := strings.Split(resource, "_")
	if len(splitted) < 2 {
		return "", "", fmt.Errorf("provider resource is unknown: %v", splitted)
	}

	// splitted[1]: instance.foo.bar
	resourceSplitted := strings.SplitN(splitted[1], ".", 2)

	provider := splitted[0]      // aws
	label := resourceSplitted[1] // foo.bar

	return provider, label, nil
}

func injectKodingData(ctx context.Context, content, username string, creds *terraformCredentials) (*buildData, error) {
	sess, ok := session.FromContext(ctx)
	if !ok {
		return nil, errors.New("session context is not passed")
	}

	awsOutput := &AwsBootstrapOutput{}

	for _, cred := range creds.Creds {
		if cred.Provider != "aws" {
			continue
		}

		if err := mapstructure.Decode(cred.Data, &awsOutput); err != nil {
			return nil, err
		}
	}

	if structs.HasZero(awsOutput) {
		return nil, fmt.Errorf("Bootstrap data is incomplete: %v", awsOutput)
	}

	var data struct {
		Resource struct {
			Aws_Instance map[string]map[string]interface{} `json:"aws_instance"`
		} `json:"resource"`
		Provider struct {
			Aws struct {
				Region    string `json:"region"`
				AccessKey string `json:"access_key"`
				SecretKey string `json:"secret_key"`
			} `json:"aws"`
		} `json:"provider"`
		Variable map[string]map[string]interface{} `json:"variable,omitempty"`
	}

	if err := json.Unmarshal([]byte(content), &data); err != nil {
		return nil, err
	}

	if len(data.Resource.Aws_Instance) == 0 {
		return nil, fmt.Errorf("instance is empty: %v", data.Resource.Aws_Instance)
	}

	kiteIds := make(map[string]string)

	for resourceName, instance := range data.Resource.Aws_Instance {
		instance["key_name"] = awsOutput.KeyPair

		// if nothing is provided or the ami is empty use default Ubuntu AMI's
		if a, ok := instance["ami"]; !ok {
			instance["ami"] = awsOutput.AMI
		} else {
			if ami, ok := a.(string); ok && ami == "" {
				instance["ami"] = awsOutput.AMI
			}
		}

		// only ovveride if the user doesn't provider it's own subnet_id
		if instance["subnet_id"] == nil {
			instance["subnet_id"] = awsOutput.Subnet
			instance["security_groups"] = []string{awsOutput.SG}
		}

		// means there will be several instances, we need to create a userdata
		// with count interpolation, because each machine must have an unique
		// kite id.
		var count int = 1
		if c, ok := instance["count"]; ok {
			// we receive it as float64
			if cFloat, ok := c.(float64); !ok {
				return nil, fmt.Errorf("count statement should be an integer, got: %+v", c)
			} else {
				count = int(cFloat)
			}
		}

		// this part will be the same for all machines
		userCfg := &userdata.CloudInitConfig{
			Username: username,
			Groups:   []string{"sudo"},
			Hostname: username, // no typo here. hostname = username
		}

		// prepend custom script if available
		if c, ok := instance["user_data"]; ok {
			if customCMD, ok := c.(string); ok {
				userCfg.CustomCMD = customCMD
			}
		}

		// will be replaced with the kitekeys we create below
		userCfg.KiteKey = "${lookup(var.kitekeys, count.index)}"

		userdata, err := sess.Userdata.Create(userCfg)
		if err != nil {
			return nil, err
		}

		instance["user_data"] = string(userdata)

		// create independent kiteKey for each machine and create a Terraform
		// lookup map, which is used in conjuctuon with the `count.index`
		countKeys := map[string]string{}
		for i := 0; i < count; i++ {
			// create a new kite id for every new aws resource
			kiteUUID, err := uuid.NewV4()
			if err != nil {
				return nil, err
			}

			kiteId := kiteUUID.String()

			kiteKey, err := sess.Userdata.Keycreator.Create(username, kiteId)
			if err != nil {
				return nil, err
			}

			// if the count is greater than 1, terraform will change the labels
			// and append a number(starting with index 0) to each label
			if count != 1 {
				kiteIds[resourceName+"."+strconv.Itoa(i)] = kiteId
			} else {
				kiteIds[resourceName] = kiteId
			}

			countKeys[strconv.Itoa(i)] = kiteKey
		}

		data.Variable["kitekeys"] = map[string]interface{}{
			"default": countKeys,
		}

		data.Resource.Aws_Instance[resourceName] = instance
	}

	out, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		return nil, err
	}

	b := &buildData{
		Template: string(out),
		KiteIds:  kiteIds,
		Region:   data.Provider.Aws.Region,
	}

	return b, nil
}

func varsFromCredentials(creds *terraformCredentials) map[string]string {
	vars := make(map[string]string, 0)
	for _, cred := range creds.Creds {
		for k, v := range cred.Data {
			vars[k] = v
		}
	}
	return vars
}

func checkKlients(ctx context.Context, kiteIds map[string]string) error {
	sess, ok := session.FromContext(ctx)
	if !ok {
		return errors.New("session context is not passed")
	}

	var wg sync.WaitGroup
	var mu sync.Mutex // protects multierror and outputs
	var multiErrors error

	check := func(label, kiteId string) error {
		queryString := protocol.Kite{ID: kiteId}.String()
		klientRef, err := klient.NewWithTimeout(sess.Kite, queryString, time.Minute*5)
		if err != nil {
			return err
		}
		defer klientRef.Close()

		return klientRef.Ping()
	}

	for l, k := range kiteIds {
		wg.Add(1)
		go func(label, kiteId string) {
			if err := check(label, kiteId); err != nil {
				mu.Lock()
				multiErrors = multierror.Append(multiErrors,
					fmt.Errorf("Couldn't check '%s:%s'", label, kiteId))
				mu.Unlock()
			}
			wg.Done()
		}(l, k)
	}

	wg.Wait()

	return multiErrors
}

// isVariable checkes whether the given string is a template variable, such as:
// "${var.region}"
func isVariable(v string) bool {
	return v[0] == '$'
}

// parseAccountID parses an AWS arn string to get the Account ID
func parseAccountID(arn string) (string, error) {
	// example arn string: "arn:aws:iam::213456789:user/username"
	// returns: 213456789.
	splitted := strings.Split(strings.TrimPrefix(arn, "arn:aws:iam::"), ":")
	if len(splitted) != 2 {
		return "", fmt.Errorf("Couldn't parse arn string: %s", arn)
	}

	return splitted[0], nil
}
<|endoftext|>


<commit_before>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(
				handler,
			),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	// tested
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	// tested
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))
	// tested
	mux.Handle("DELETE", "/message/{id}/reply/{replyId}", handlerWrapper(reply.Delete, "reply-delete"))
	// tested
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// add new like
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	// delete like - unlike
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	// mux.Handle("GET", "/message/{id}/interaction", handlerWrapper(interaction.List, "interactions-list"))
	// get typed interactions
	// tested
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))

	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	// tested
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	// tested
	mux.Handle("DELETE", "/channel/{id}", handlerWrapper(channel.Delete, "channel-delete"))
	// tested
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))

	// add a new messages to the channel
	// tested
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))

	// list participants of the channel
	// tested
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))

	// add participant to the channel
	//tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))

	// remove participant from the channel
	// tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))

	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))

	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))

	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))

	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))

	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))

	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))

	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))

	// mux.Handle("POST", "/follow/{id}", handlerWrapper(post, "follow-id"))
	// mux.Handle("POST", "/unfollow/{id}", handlerWrapper(post, "follow-id"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<commit_msg>Social: add channel update and delete handler with post request<commit_after>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(
				handler,
			),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	// tested
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	// tested
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))
	// tested
	mux.Handle("DELETE", "/message/{id}/reply/{replyId}", handlerWrapper(reply.Delete, "reply-delete"))
	// tested
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// add new like
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	// delete like - unlike
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	// mux.Handle("GET", "/message/{id}/interaction", handlerWrapper(interaction.List, "interactions-list"))
	// get typed interactions
	// tested
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))

	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	// tested
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	//
	mux.Handle("POST", "/channel/{id}/update", handlerWrapper(channel.Update, "channel-update"))
	// tested
	mux.Handle("POST", "/channel/{id}/delete", handlerWrapper(channel.Delete, "channel-delete"))
	// tested
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))

	// add a new messages to the channel
	// tested
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))

	// list participants of the channel
	// tested
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))

	// add participant to the channel
	//tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))

	// remove participant from the channel
	// tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))

	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))

	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))

	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))

	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))

	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))

	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))

	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))

	// mux.Handle("POST", "/follow/{id}", handlerWrapper(post, "follow-id"))
	// mux.Handle("POST", "/unfollow/{id}", handlerWrapper(post, "follow-id"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<|endoftext|>


<commit_before>package main

import (
	"curses"
	"os"
)

const (
	ESC = 27
	NOLINE = "~"

)

type Terminal struct {
	fid     uint64
	ln, col uint64
	cache   map[uint64]string
	client  *Client
	x, y    int
	cwin    *curses.Window
	k int
	ex bool
	exbuff string
}

func NewTerminal(client *Client) *Terminal {
	t := new(Terminal)
	t.client = client
	t.cache = make(map[uint64]string)
	t.ln, t.col = 0, 0
	t.fid = 0
	return t
}

func (t *Terminal) init() {
	curses.Initscr()
	curses.Cbreak()
	curses.Noecho()
	curses.Nonl()
	curses.Stdwin.Keypad(true)

	t.cwin = curses.Stdwin
	t.x = *curses.Cols
	t.y = *curses.Rows
	t.ln = 0
	t.col = 0
	t.ex = false
}

func (t *Terminal) run() {
	for {
		t.display()
		t.k = t.cwin.Getch()
		switch t.k {
		case 'o':
			if o, e := t.client.open("Makefile"); e == nil {
				t.fid = o.fid
			} else {
				panic(e.String())
			}
		case ':':
			t.basicEx()
		case ESC:
			if _, e := t.client.close(t.fid); e == nil {
				t.fid = 0
				for lno, _ := range t.cache {
					t.cache[lno] = "", false
				}
			} else {
				panic(e.String())
			}
		}
	}
}

func (t *Terminal) basicEx() {
	t.ex = true
	t.exbuff = ""
	for {
		t.display()
		k := t.cwin.Getch()
		if k == ESC {
			break
		}
		t.exbuff += string(k)
	}
	t.ex = false
}

func (t *Terminal) modeline() string {
	return string(t.k)
}

func (t *Terminal) display() {
	t.clear()
	if t.fid == 0 {
		t.draw(0, 0, "No file...")
		goto lastline
	}
	for y := 0; y < t.y-1; y++ {
		if ln, e := t.fetch(uint64(y)); e == nil {
			t.draw(0, y, ln)
		} else if e.String() == "noline" {
			t.draw(0, y, NOLINE)
		}
	}
lastline:
	if t.ex {
		t.draw(0, t.y-1, ":" + t.exbuff)
	} else {
		t.draw(0, t.y-1, t.modeline())
	}
}

func (t *Terminal) clear() {
	t.cwin.Clear()
}

func (t *Terminal) draw(x, y int, ln string) {
	t.cwin.Mvwaddnstr(y, x, ln, t.x)
}

func (t *Terminal) fetch(lno uint64) (string, os.Error) {
	if ln, ok := t.cache[lno]; ok {
		return ln, nil
	} else {
		if lr, e := t.client.line(t.fid, lno); e == nil {
			t.cache[lno] = lr.text
			return t.cache[lno], nil
		}
	}
	return "", &DviError{"noline"}
}
<commit_msg>poc ex<commit_after>package main

import (
	"curses"
	"os"
	"strings"
)

const (
	ESC = 27
	NOLINE = "~"
)

type Terminal struct {
	fid     uint64
	ln, col int
	cache   map[uint64]string
	client  *Client
	x, y    int
	cwin    *curses.Window
	k int
	ex bool
	exbuff string
}

func NewTerminal(client *Client) *Terminal {
	t := new(Terminal)
	t.client = client
	t.cache = make(map[uint64]string)
	t.ln, t.col = 0, 0
	t.fid = 0
	return t
}

func (t *Terminal) init() {
	curses.Initscr()
	curses.Cbreak()
	curses.Noecho()
	curses.Nonl()
	curses.Stdwin.Keypad(true)

	t.cwin = curses.Stdwin
	t.x = *curses.Cols
	t.y = *curses.Rows
	t.ln = 0
	t.col = 0
	t.ex = false
}

func (t *Terminal) run() {
	for {
		t.display()
		t.k = t.cwin.Getch()
		switch t.k {
		case ':':
			t.basicEx()
		default:
		}
	}
}

func (t *Terminal) basicEx() {
	t.ex = true
	t.exbuff = ""
	for {
		t.display()
		k := t.cwin.Getch()
		if k == ESC {
			break
		} else if k == 0xa || k == 0xd {
			t.parseAndExecEx(t.exbuff)
			break
		}
		t.exbuff += string(k)
	}
	t.ex = false
}

func (t *Terminal) parseAndExecEx(exbuff string) {
	exploded := strings.Split(exbuff, " ", -1)
	if len(exploded[0]) == 0 {
		return
	} else if len(exploded) == 1 && exploded[0] == "q" {
		if _, e := t.client.close(t.fid); e == nil {
			t.fid = 0
			for lno, _ := range t.cache {
				t.cache[lno] = "", false
			}
		} else {
			panic(e.String())
		}
	} else if len(exploded) >= 2 && exploded[0] == "e" {
		if o, e := t.client.open(exploded[1]); e == nil {
			t.fid = o.fid
		} else {
			panic(e.String())
		}
	}
}

func (t *Terminal) modeline() string {
	return string(t.k)
}

func (t *Terminal) display() {
	t.clear()
	if t.fid == 0 {
		t.draw(0, 0, "No file...")
		goto lastline
	}
	for y := 0; y < t.y-1; y++ {
		if ln, e := t.fetch(uint64(y)); e == nil {
			t.draw(0, y, ln)
		} else if e.String() == "noline" {
			t.draw(0, y, NOLINE)
		}
	}
lastline:
	if t.ex {
		t.draw(0, t.y-1, ":" + t.exbuff)
	} else {
		t.draw(0, t.y-1, t.modeline())
		t.cwin.Move(t.ln, t.col)
	}
}

func (t *Terminal) clear() {
	t.cwin.Clear()
}

func (t *Terminal) draw(x, y int, ln string) {
	t.cwin.Mvwaddnstr(y, x, ln, t.x)
}

func (t *Terminal) fetch(lno uint64) (string, os.Error) {
	if ln, ok := t.cache[lno]; ok {
		return ln, nil
	} else {
		if lr, e := t.client.line(t.fid, lno); e == nil {
			t.cache[lno] = lr.text
			return t.cache[lno], nil
		}
	}
	return "", &DviError{"noline"}
}
<|endoftext|>


<commit_before>package easy

import (
	"regexp"
	"testing"
)

func TestRegex(t *testing.T) {
	type testcase struct {
		str       string
		doesMatch bool
	}

	testdata := []struct {
		re        *regexp.Regexp
		ref       *regexp.Regexp
		testcases []testcase
	}{
		{
			Regex(Digit()),
			regexp.MustCompile("[0-9]"),
			[]testcase{
				{"0", true},
				{"1", true},
				{"9", true},
				{"9.", true},
				{"-9", true},
				{"-999.2", true},
				{" asdasda s -999.2\n asdasdsad", true},
				{"", false},
				{"asdasd", false},
				{"digit", false},
			},
		},
		{
			Regex(Digits(), Then("."), Digits()),
			regexp.MustCompile("[0-9]+\\.[0-9]+"),
			[]testcase{
				{"1320.0", true},
				{"2.1", true},
				{"0.", false},
				{"0", false},
				{"   0.0", true},
				{"here is > 0.0", true},
				{"here is not", false},
			},
		},
		{
			Regex(Digits(), Period(), Digits()),
			regexp.MustCompile("[0-9]+\\.[0-9]+"),
			[]testcase{
				{"1320.0", true},
				{"2.1", true},
				{"0.", false},
				{"0", false},
				{"   0.0", true},
				{"here is > 0.0", true},
				{"here is not", false},
			},
		},
		{
			Regex(Word()),
			regexp.MustCompile("\\w+"),
			[]testcase{
				{"the quick brown fox jumps over the lazy dog", true},
				{"the", true},
				{"quick", true},
				{"brown", true},
				{"fox", true},
				{"jumps", true},
				{"over", true},
				{"lazy", true},
				{"dog", true},
				{"a", true},
				{"zzzz", true},
				{"", false},
				{"abc999", true},
				{"999", true},
				{"----", false},
				{"", false},
			},
		},
		{
			Regex(Then("$"), Digits(), Then("USD")),
			regexp.MustCompile("\\$\\d+USD"),
			[]testcase{
				{"$123USD", true},
				{"$0USD", true},
				{"$0.", false},
				{"$USD", false},
				{"abc", false},
				{"  $99999999999999USD    ", true},
				{"  $999 USD ", false},
			},
		},
		{
			Regex(Repeat(3, Then("a")), Then("b")),
			regexp.MustCompile("aaab"),
			[]testcase{
				{"aaab", true},
				{"aab", false},
				{" aaaab", true},
				{" aaaaabbbbb ", true},
				{"aabaab", false},
				{"abaabaaabbb", true},
			},
		},
		{
			Regex(Max(3, Then("a")), Then("b")),
			regexp.MustCompile("a{0,3}b"),
			[]testcase{
				{"aaab", true},
				{"aab", true},
				{" aaaab", true},
				{" aaaaabbbbb ", true},
				{"aabaab", true},
				{"abaabaaabbb", true},
				{"bbb", true},
			},
		},
		{
			Regex(Min(3, Then("a")), Then("b")),
			regexp.MustCompile("a{3,}b"),
			[]testcase{
				{"", false},
				{"b", false},
				{"aab", false},
				{"aaab", true},
				{"aaaab", true},
				{"aaaaab", true},
				{"aaaaabb", true},
			},
		},
		{
			Regex(
				Group("dividend", Digits()),
				Then("/"),
				Group("divisor", Digits()),
			),
			regexp.MustCompile("(\\d+)/(\\d+)"),
			[]testcase{
				{"", false},
				{"1", false},
				{"/", false},
				{"1/", false},
				{"1/2", true},
				{"99/9", true},
				{"91231239/1231231239", true},
				{"-91231239/1231231239", true},
				{"+91231239/1231231239", true},
				{" +91231239/1231231239", true},
				{" +/2", false},
				{"-/9", false},
			},
		},
	}

	for _, td := range testdata {
		t.Run(td.re.String(), func(t *testing.T) {
			for _, tc := range td.testcases {
				t.Run(tc.str, func(t *testing.T) {
					if td.ref.MatchString(tc.str) != tc.doesMatch {
						t.Fatal(tc.str, tc.doesMatch)
					}

					if td.re.MatchString(tc.str) != tc.doesMatch {
						t.Fatal(tc.str, tc.doesMatch)
					}
				})
			}
		})
	}
}

func TestRegexGroups(t *testing.T) {
	type testcase struct {
		str     string
		matches []string
	}

	testdata := []struct {
		re        *regexp.Regexp
		ref       *regexp.Regexp
		testcases []testcase
	}{
		{
			Regex(
				Group("dividend", Digits()),
				Then("/"),
				Group("divisor", Digits()),
			),
			regexp.MustCompile("(\\d+)\\.(\\d+)"),
			[]testcase{
				{"", nil},
				{"1", nil},
				{"/", nil},
				{"1/", nil},
				{"1/2", []string{"1/2", "1", "2"}},
				{"99/9", []string{"99/9", "99", "9"}},
				{"91231239/1231231239", []string{"91231239/1231231239", "91231239", "1231231239"}},
				{"-91231239/1231231239", []string{"91231239/1231231239", "91231239", "1231231239"}},
				{"+91231239/1231231239", []string{"91231239/1231231239", "91231239", "1231231239"}},
				{" +321/765", []string{"321/765", "321", "765"}},
				{" +/2", nil},
				{"-/9", nil},
			},
		},
		{
			Regex(
				Group("user", Word()),
				Then("@"),
				Group("domain", Word()),
			),
			regexp.MustCompile("(\\w+)@(\\w+)"),
			[]testcase{
				{"foo-bar.com", nil},
				{"foo@bar", []string{"foo@bar", "foo", "bar"}},
				{"foo@bar.com", []string{"foo@bar", "foo", "bar"}},
			},
		},
	}

	for _, td := range testdata {
		t.Run(td.re.String(), func(t *testing.T) {
			for _, tc := range td.testcases {
				t.Run(tc.str, func(t *testing.T) {
					submatches := td.re.FindSubmatch([]byte(tc.str))
					if len(submatches) != len(tc.matches) {
						t.Fatal(submatches, len(submatches))
					}

					for i := range submatches {
						if string(submatches[i]) != tc.matches[i] {
							t.Fatal(string(submatches[i]), tc.matches[i])
						}
					}
				})
			}
		})
	}
}
<commit_msg>Add testcase for or<commit_after>package easy

import (
	"regexp"
	"testing"
)

func TestRegex(t *testing.T) {
	type testcase struct {
		str       string
		doesMatch bool
	}

	testdata := []struct {
		re        *regexp.Regexp
		ref       *regexp.Regexp
		testcases []testcase
	}{
		{
			Regex(Digit()),
			regexp.MustCompile("[0-9]"),
			[]testcase{
				{"0", true},
				{"1", true},
				{"9", true},
				{"9.", true},
				{"-9", true},
				{"-999.2", true},
				{" asdasda s -999.2\n asdasdsad", true},
				{"", false},
				{"asdasd", false},
				{"digit", false},
			},
		},
		{
			Regex(Digits(), Then("."), Digits()),
			regexp.MustCompile("[0-9]+\\.[0-9]+"),
			[]testcase{
				{"1320.0", true},
				{"2.1", true},
				{"0.", false},
				{"0", false},
				{"   0.0", true},
				{"here is > 0.0", true},
				{"here is not", false},
			},
		},
		{
			Regex(Digits(), Period(), Digits()),
			regexp.MustCompile("[0-9]+\\.[0-9]+"),
			[]testcase{
				{"1320.0", true},
				{"2.1", true},
				{"0.", false},
				{"0", false},
				{"   0.0", true},
				{"here is > 0.0", true},
				{"here is not", false},
			},
		},
		{
			Regex(Word()),
			regexp.MustCompile("\\w+"),
			[]testcase{
				{"the quick brown fox jumps over the lazy dog", true},
				{"the", true},
				{"quick", true},
				{"brown", true},
				{"fox", true},
				{"jumps", true},
				{"over", true},
				{"lazy", true},
				{"dog", true},
				{"a", true},
				{"zzzz", true},
				{"", false},
				{"abc999", true},
				{"999", true},
				{"----", false},
				{"", false},
			},
		},
		{
			Regex(Then("$"), Digits(), Then("USD")),
			regexp.MustCompile("\\$\\d+USD"),
			[]testcase{
				{"$123USD", true},
				{"$0USD", true},
				{"$0.", false},
				{"$USD", false},
				{"abc", false},
				{"  $99999999999999USD    ", true},
				{"  $999 USD ", false},
			},
		},
		{
			Regex(Repeat(3, Then("a")), Then("b")),
			regexp.MustCompile("aaab"),
			[]testcase{
				{"aaab", true},
				{"aab", false},
				{" aaaab", true},
				{" aaaaabbbbb ", true},
				{"aabaab", false},
				{"abaabaaabbb", true},
			},
		},
		{
			Regex(Max(3, Then("a")), Then("b")),
			regexp.MustCompile("a{0,3}b"),
			[]testcase{
				{"aaab", true},
				{"aab", true},
				{" aaaab", true},
				{" aaaaabbbbb ", true},
				{"aabaab", true},
				{"abaabaaabbb", true},
				{"bbb", true},
			},
		},
		{
			Regex(Min(3, Then("a")), Then("b")),
			regexp.MustCompile("a{3,}b"),
			[]testcase{
				{"", false},
				{"b", false},
				{"aab", false},
				{"aaab", true},
				{"aaaab", true},
				{"aaaaab", true},
				{"aaaaabb", true},
			},
		},
		{
			Regex(Or(Digit(), Alpha())),
			regexp.MustCompile("[A-Za-z]|[0-9]"),
			[]testcase{
				{"", false},
				{"b", true},
				{"1", true},
				{"z", true},
				{"-", false},
				{" ", false},
				{"X", true},
				{"A", true},
			},
		},
		{
			Regex(Or(Then("aaa"), Then("bbb"))),
			regexp.MustCompile("(aaa)|(bbb)"),
			[]testcase{
				{"", false},
				{"aaa", true},
				{"bbb", true},
				{"z", false},
				{"AAA", false},
			},
		},
		{
			Regex(
				Group("dividend", Digits()),
				Then("/"),
				Group("divisor", Digits()),
			),
			regexp.MustCompile("(\\d+)/(\\d+)"),
			[]testcase{
				{"", false},
				{"1", false},
				{"/", false},
				{"1/", false},
				{"1/2", true},
				{"99/9", true},
				{"91231239/1231231239", true},
				{"-91231239/1231231239", true},
				{"+91231239/1231231239", true},
				{" +91231239/1231231239", true},
				{" +/2", false},
				{"-/9", false},
			},
		},
	}

	for _, td := range testdata {
		t.Run(td.re.String(), func(t *testing.T) {
			for _, tc := range td.testcases {
				t.Run(tc.str, func(t *testing.T) {
					if td.ref.MatchString(tc.str) != tc.doesMatch {
						t.Fatal(tc.str, tc.doesMatch)
					}

					if td.re.MatchString(tc.str) != tc.doesMatch {
						t.Fatal(tc.str, tc.doesMatch)
					}
				})
			}
		})
	}
}

func TestRegexGroups(t *testing.T) {
	type testcase struct {
		str     string
		matches []string
	}

	testdata := []struct {
		re        *regexp.Regexp
		ref       *regexp.Regexp
		testcases []testcase
	}{
		{
			Regex(
				Group("dividend", Digits()),
				Then("/"),
				Group("divisor", Digits()),
			),
			regexp.MustCompile("(\\d+)\\.(\\d+)"),
			[]testcase{
				{"", nil},
				{"1", nil},
				{"/", nil},
				{"1/", nil},
				{"1/2", []string{"1/2", "1", "2"}},
				{"99/9", []string{"99/9", "99", "9"}},
				{"91231239/1231231239", []string{"91231239/1231231239", "91231239", "1231231239"}},
				{"-91231239/1231231239", []string{"91231239/1231231239", "91231239", "1231231239"}},
				{"+91231239/1231231239", []string{"91231239/1231231239", "91231239", "1231231239"}},
				{" +321/765", []string{"321/765", "321", "765"}},
				{" +/2", nil},
				{"-/9", nil},
			},
		},
		{
			Regex(
				Group("user", Word()),
				Then("@"),
				Group("domain", Word()),
			),
			regexp.MustCompile("(\\w+)@(\\w+)"),
			[]testcase{
				{"foo-bar.com", nil},
				{"foo@bar", []string{"foo@bar", "foo", "bar"}},
				{"foo@bar.com", []string{"foo@bar", "foo", "bar"}},
			},
		},
	}

	for _, td := range testdata {
		t.Run(td.re.String(), func(t *testing.T) {
			for _, tc := range td.testcases {
				t.Run(tc.str, func(t *testing.T) {
					submatches := td.re.FindSubmatch([]byte(tc.str))
					if len(submatches) != len(tc.matches) {
						t.Fatal(submatches, len(submatches))
					}

					for i := range submatches {
						if string(submatches[i]) != tc.matches[i] {
							t.Fatal(string(submatches[i]), tc.matches[i])
						}
					}
				})
			}
		})
	}
}
<|endoftext|>


<commit_before>package messages

import (
	"fmt"
	"gnd.la/internal/astutil"
	"gnd.la/internal/pkgutil"
	"gnd.la/internal/templateutil"
	"gnd.la/log"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/template/parse"
)

func DefaultFunctions() []*Function {
	return []*Function{
		// Singular functions without context
		{Name: "gnd.la/i18n.T", Start: 1},
		{Name: "gnd.la/i18n.Errorf"},
		{Name: "gnd.la/i18n.Sprintf", Start: 1},
		{Name: "gnd.la/i18n.NewError"},
		{Name: "gnd.la/app.Context.T", Start: 1},
		{Name: "t", Template: true},
		// Singular functions with context
		{Name: "gnd.la/i18n.Tc", Context: true, Start: 1},
		{Name: "gnd.la/i18n.Sprintfc", Context: true, Start: 1},
		{Name: "gnd.la/i18n.Errorfc", Context: true},
		{Name: "gnd.la/i18n.NewErrorc", Context: true},
		{Name: "gnd.la/app.Context.Tc", Context: true, Start: 1},
		{Name: "tc", Template: true, Context: true},
		// Plural functions without context
		{Name: "gnd.la/i18n.Tn", Plural: true, Start: 1},
		{Name: "gnd.la/i18n.Sprintfn", Plural: true, Start: 1},
		{Name: "gnd.la/i18n.Errorfn", Plural: true},
		{Name: "gnd.la/i18n.NewErrorn", Plural: true},
		{Name: "gnd.la/app.Context.Tn", Plural: true, Start: 1},
		{Name: "tn", Template: true, Plural: true},
		// Plural functions with context
		{Name: "gnd.la/i18n.Tnc", Context: true, Plural: true, Start: 1},
		{Name: "gnd.la/i18n.Errorfnc", Context: true, Plural: true},
		{Name: "gnd.la/i18n.Sprintfnc", Context: true, Plural: true, Start: 1},
		{Name: "gnd.la/i18n.NewErrornc", Context: true, Plural: true},
		{Name: "gnd.la/app.Context.Tnc", Context: true, Plural: true, Start: 1},
		{Name: "tnc", Template: true, Context: true, Plural: true},
	}
}

func DefaultTypes() []string {
	return []string{
		"gnd.la/i18n.String",
	}
}

func DefaultTagFields() []string {
	return []string{
		"help",
		"label",
		"placeholder",
	}
}

type ExtractOptions struct {
	Functions []*Function
	Types     []string
	TagFields []string
}

func DefaultExtractOptions() *ExtractOptions {
	return &ExtractOptions{
		Functions: DefaultFunctions(),
		Types:     DefaultTypes(),
		TagFields: DefaultTagFields(),
	}
}

func Extract(dir string, opts *ExtractOptions) ([]*Message, error) {
	if opts == nil {
		opts = DefaultExtractOptions()
	}
	messages := make(messageMap)
	err := extract(messages, dir, opts)
	if err != nil {
		return nil, err
	}
	return messages.Messages(), nil
}

func extract(messages messageMap, dir string, opts *ExtractOptions) error {
	f, err := os.Open(dir)
	if err != nil {
		return err
	}
	defer f.Close()
	infos, err := f.Readdir(-1)
	if err != nil {
		return err
	}
	for _, v := range infos {
		name := v.Name()
		p := filepath.Join(dir, name)
		if v.IsDir() {
			if !pkgutil.IsPackage(p) {
				if err := extract(messages, p, opts); err != nil {
					return err
				}
			}
			continue
		}
		switch strings.ToLower(filepath.Ext(name)) {
		// TODO: text and strings files
		case ".html", ".txt":
			if err := extractTemplateMessages(messages, p, opts); err != nil {
				return err
			}
		case ".go":
			if err := extractGoMessages(messages, p, opts); err != nil {
				return err
			}
		case ".po", ".pot":
			// Do nothing
		}
	}
	return nil
}

func extractGoMessages(messages messageMap, path string, opts *ExtractOptions) error {
	log.Debugf("Extracting messages from Go file %s", path)
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
	if err != nil {
		return fmt.Errorf("error parsing go file %s: %s", path, err)
	}
	if opts != nil {
		for _, v := range opts.Functions {
			if v.Template {
				continue
			}
			if err := extractGoFunc(messages, fset, f, v); err != nil {
				return err
			}
		}
		for _, v := range opts.Types {
			if err := extractGoType(messages, fset, f, v); err != nil {
				return err
			}
		}
		for _, v := range opts.TagFields {
			if err := extractGoTagField(messages, fset, f, v); err != nil {
				return err
			}
		}
	}
	return nil
}

func extractGoFunc(messages messageMap, fset *token.FileSet, f *ast.File, fn *Function) error {
	calls, err := astutil.Calls(fset, f, fn.Name)
	if err != nil {
		return err
	}
	n := fn.Start
	if fn.Context {
		n++
	}
	var message *Message
	var position *token.Position
	for _, c := range calls {
		if fn.Plural {
			if len(c.Args) < n+3 {
				log.Debugf("Skipping plural function %s (%v) - not enough arguments", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			slit, spos := astutil.StringLiteral(fset, c.Args[n])
			if slit == "" || spos == nil {
				log.Debugf("Skipping first argument to plural function %s (%v) - not a literal", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			plit, ppos := astutil.StringLiteral(fset, c.Args[n+1])
			if plit == "" || ppos == nil {
				log.Debugf("Skipping second argument to plural function %s (%v) - not a literal", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			message = &Message{
				Singular: slit,
				Plural:   plit,
			}
			position = spos
		} else {
			if len(c.Args) < n+1 {
				log.Debugf("Skipping singular function %s (%v) - not enough arguments", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			lit, pos := astutil.StringLiteral(fset, c.Args[n])
			if lit == "" || pos == nil {
				log.Debugf("Skipping argument to singular function %s (%v) - not a literal", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			message = &Message{
				Singular: lit,
			}
			position = pos
		}
		if message != nil && position != nil {
			if fn.Context {
				ctx, cpos := astutil.StringLiteral(fset, c.Args[fn.Start])
				if ctx == "" || cpos == nil {
					log.Debugf("Skipping argument to context function %s (%v) - empty context", astutil.Ident(c.Fun), fset.Position(c.Pos()))
					continue
				}
				message.Context = ctx
			}
			if err := messages.Add(message, position, comments(fset, f, position)); err != nil {
				return err
			}
		}
	}
	return nil
}

func extractGoType(messages messageMap, fset *token.FileSet, f *ast.File, typ string) error {
	// for castings
	tf := &Function{Name: typ}
	if err := extractGoFunc(messages, fset, f, tf); err != nil {
		return err
	}
	strings, err := astutil.Strings(fset, f, typ)
	if err != nil {
		return err
	}
	for _, s := range strings {
		comment := comments(fset, f, s.Position)
		if err := messages.AddString(s, comment); err != nil {
			return err
		}
	}
	return nil
}

func extractGoTagField(messages messageMap, fset *token.FileSet, f *ast.File, tagField string) error {
	strings, err := astutil.TagFields(fset, f, tagField)
	if err != nil {
		return err
	}
	for _, s := range strings {
		comment := comments(fset, f, s.Position)
		if err := messages.AddString(s, comment); err != nil {
			return err
		}
	}
	return nil
}

func extractTemplateMessages(messages messageMap, path string, opts *ExtractOptions) error {
	log.Debugf("Extracting messages from template file %s", path)
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	funcs := make(map[string]*Function)
	if opts != nil {
		for _, v := range opts.Functions {
			if v.Template {
				funcs[v.Name] = v
			}
		}
	}
	text := string(b)
	treeSet, err := templateutil.Parse(path, text)
	if err != nil {
		return err
	}
	for _, v := range treeSet {
		if err := templateutil.ReplaceTranslatableBlocks(v, "t"); err != nil {
			return err
		}
		templateutil.WalkTree(v, func(n, p parse.Node) {
			var fname string
			switch n.Type() {
			case parse.NodeIdentifier:
				fname = n.(*parse.IdentifierNode).Ident
			case parse.NodeField:
				ident := n.(*parse.FieldNode).Ident
				if len(ident) > 1 {
					fname = ident[len(ident)-1]
				}
			case parse.NodeVariable:
				ident := n.(*parse.VariableNode).Ident
				if len(ident) > 1 {
					fname = ident[len(ident)-1]
				}
			}
			if fname != "" {
				f := funcs[fname]
				if f != nil {
					count := 1
					if f.Context {
						count++
					}
					if f.Plural {
						count++
					}
					cmd := p.(*parse.CommandNode)
					// First argument is the function name
					if c := len(cmd.Args) - 1; c != count {
						log.Debugf("Skipping function %s (%v) - want %d arguments, got %d", f.Name, n.Position(), count, c)
						return
					}
					var s []string
					for ii := 1; ii < len(cmd.Args); ii++ {
						if sn, ok := cmd.Args[ii].(*parse.StringNode); ok {
							s = append(s, sn.Text)
						} else {
							log.Debugf("Skipping function %s (%v) - non-string argument at position %d", f.Name, n.Position(), ii)
							return
						}
					}
					message := &Message{}
					switch len(s) {
					case 1:
						message.Singular = s[0]
					case 2:
						if f.Context {
							message.Context = s[0]
							message.Singular = s[1]
						} else {
							message.Singular = s[0]
							message.Plural = s[1]
						}
					case 3:
						message.Context = s[0]
						message.Singular = s[1]
						message.Plural = s[2]
					}
					// TODO: The line number doesn't match exactly because of the
					// prepended variables
					pos := templatePosition(path, text, n)
					if err = messages.Add(message, pos, ""); err != nil {
						return
					}
				}
			}
		})
	}
	return err
}

func templatePosition(name string, text string, n parse.Node) *token.Position {
	return &token.Position{
		Filename: name,
		Line:     strings.Count(text[:int(n.Position())], "\n") + 1,
	}
}
<commit_msg>Correctly extract castings to i18n.String with context or plural<commit_after>package messages

import (
	"fmt"
	"gnd.la/internal/astutil"
	"gnd.la/internal/pkgutil"
	"gnd.la/internal/templateutil"
	"gnd.la/log"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/template/parse"
)

func DefaultFunctions() []*Function {
	return []*Function{
		// Singular functions without context
		{Name: "gnd.la/i18n.T", Start: 1},
		{Name: "gnd.la/i18n.Errorf"},
		{Name: "gnd.la/i18n.Sprintf", Start: 1},
		{Name: "gnd.la/i18n.NewError"},
		{Name: "gnd.la/app.Context.T", Start: 1},
		{Name: "t", Template: true},
		// Singular functions with context
		{Name: "gnd.la/i18n.Tc", Context: true, Start: 1},
		{Name: "gnd.la/i18n.Sprintfc", Context: true, Start: 1},
		{Name: "gnd.la/i18n.Errorfc", Context: true},
		{Name: "gnd.la/i18n.NewErrorc", Context: true},
		{Name: "gnd.la/app.Context.Tc", Context: true, Start: 1},
		{Name: "tc", Template: true, Context: true},
		// Plural functions without context
		{Name: "gnd.la/i18n.Tn", Plural: true, Start: 1},
		{Name: "gnd.la/i18n.Sprintfn", Plural: true, Start: 1},
		{Name: "gnd.la/i18n.Errorfn", Plural: true},
		{Name: "gnd.la/i18n.NewErrorn", Plural: true},
		{Name: "gnd.la/app.Context.Tn", Plural: true, Start: 1},
		{Name: "tn", Template: true, Plural: true},
		// Plural functions with context
		{Name: "gnd.la/i18n.Tnc", Context: true, Plural: true, Start: 1},
		{Name: "gnd.la/i18n.Errorfnc", Context: true, Plural: true},
		{Name: "gnd.la/i18n.Sprintfnc", Context: true, Plural: true, Start: 1},
		{Name: "gnd.la/i18n.NewErrornc", Context: true, Plural: true},
		{Name: "gnd.la/app.Context.Tnc", Context: true, Plural: true, Start: 1},
		{Name: "tnc", Template: true, Context: true, Plural: true},
	}
}

func DefaultTypes() []string {
	return []string{
		"gnd.la/i18n.String",
	}
}

func DefaultTagFields() []string {
	return []string{
		"help",
		"label",
		"placeholder",
	}
}

type ExtractOptions struct {
	Functions []*Function
	Types     []string
	TagFields []string
}

func DefaultExtractOptions() *ExtractOptions {
	return &ExtractOptions{
		Functions: DefaultFunctions(),
		Types:     DefaultTypes(),
		TagFields: DefaultTagFields(),
	}
}

func Extract(dir string, opts *ExtractOptions) ([]*Message, error) {
	if opts == nil {
		opts = DefaultExtractOptions()
	}
	messages := make(messageMap)
	err := extract(messages, dir, opts)
	if err != nil {
		return nil, err
	}
	return messages.Messages(), nil
}

func extract(messages messageMap, dir string, opts *ExtractOptions) error {
	f, err := os.Open(dir)
	if err != nil {
		return err
	}
	defer f.Close()
	infos, err := f.Readdir(-1)
	if err != nil {
		return err
	}
	for _, v := range infos {
		name := v.Name()
		p := filepath.Join(dir, name)
		if v.IsDir() {
			if !pkgutil.IsPackage(p) {
				if err := extract(messages, p, opts); err != nil {
					return err
				}
			}
			continue
		}
		switch strings.ToLower(filepath.Ext(name)) {
		// TODO: text and strings files
		case ".html", ".txt":
			if err := extractTemplateMessages(messages, p, opts); err != nil {
				return err
			}
		case ".go":
			if err := extractGoMessages(messages, p, opts); err != nil {
				return err
			}
		case ".po", ".pot":
			// Do nothing
		}
	}
	return nil
}

func extractGoMessages(messages messageMap, path string, opts *ExtractOptions) error {
	log.Debugf("Extracting messages from Go file %s", path)
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
	if err != nil {
		return fmt.Errorf("error parsing go file %s: %s", path, err)
	}
	if opts != nil {
		for _, v := range opts.Functions {
			if v.Template {
				continue
			}
			if err := extractGoFunc(messages, fset, f, v); err != nil {
				return err
			}
		}
		for _, v := range opts.Types {
			if err := extractGoType(messages, fset, f, v); err != nil {
				return err
			}
		}
		for _, v := range opts.TagFields {
			if err := extractGoTagField(messages, fset, f, v); err != nil {
				return err
			}
		}
	}
	return nil
}

func extractGoFunc(messages messageMap, fset *token.FileSet, f *ast.File, fn *Function) error {
	calls, err := astutil.Calls(fset, f, fn.Name)
	if err != nil {
		return err
	}
	n := fn.Start
	if fn.Context {
		n++
	}
	var message *Message
	var position *token.Position
	for _, c := range calls {
		if fn.Plural {
			if len(c.Args) < n+3 {
				log.Debugf("Skipping plural function %s (%v) - not enough arguments", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			slit, spos := astutil.StringLiteral(fset, c.Args[n])
			if slit == "" || spos == nil {
				log.Debugf("Skipping first argument to plural function %s (%v) - not a literal", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			plit, ppos := astutil.StringLiteral(fset, c.Args[n+1])
			if plit == "" || ppos == nil {
				log.Debugf("Skipping second argument to plural function %s (%v) - not a literal", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			message = &Message{
				Singular: slit,
				Plural:   plit,
			}
			position = spos
		} else {
			if len(c.Args) < n+1 {
				log.Debugf("Skipping singular function %s (%v) - not enough arguments", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			lit, pos := astutil.StringLiteral(fset, c.Args[n])
			if lit == "" || pos == nil {
				log.Debugf("Skipping argument to singular function %s (%v) - not a literal", astutil.Ident(c.Fun), fset.Position(c.Pos()))
				continue
			}
			message = &Message{
				Singular: lit,
			}
			position = pos
		}
		if message != nil && position != nil {
			if fn.Context {
				ctx, cpos := astutil.StringLiteral(fset, c.Args[fn.Start])
				if ctx == "" || cpos == nil {
					log.Debugf("Skipping argument to context function %s (%v) - empty context", astutil.Ident(c.Fun), fset.Position(c.Pos()))
					continue
				}
				message.Context = ctx
			}
			if err := messages.Add(message, position, comments(fset, f, position)); err != nil {
				return err
			}
		}
	}
	return nil
}

func extractGoType(messages messageMap, fset *token.FileSet, f *ast.File, typ string) error {
	// for castings
	calls, err := astutil.Calls(fset, f, typ)
	if err != nil {
		return err
	}
	for _, c := range calls {
		if len(c.Args) > 0 {
			lit, pos := astutil.StringLiteral(fset, c.Args[0])
			if pos == nil {
				p := fset.Position(c.Pos())
				log.Debugf("Skipping cast to %s (%v) - not a literal", typ, p)
				continue
			}
			comment := comments(fset, f, pos)
			if err := messages.AddString(&astutil.String{Value: lit, Position: pos}, comment); err != nil {
				return err
			}
		}
	}
	strings, err := astutil.Strings(fset, f, typ)
	if err != nil {
		return err
	}
	for _, s := range strings {
		comment := comments(fset, f, s.Position)
		if err := messages.AddString(s, comment); err != nil {
			return err
		}
	}
	return nil
}

func extractGoTagField(messages messageMap, fset *token.FileSet, f *ast.File, tagField string) error {
	strings, err := astutil.TagFields(fset, f, tagField)
	if err != nil {
		return err
	}
	for _, s := range strings {
		comment := comments(fset, f, s.Position)
		if err := messages.AddString(s, comment); err != nil {
			return err
		}
	}
	return nil
}

func extractTemplateMessages(messages messageMap, path string, opts *ExtractOptions) error {
	log.Debugf("Extracting messages from template file %s", path)
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	funcs := make(map[string]*Function)
	if opts != nil {
		for _, v := range opts.Functions {
			if v.Template {
				funcs[v.Name] = v
			}
		}
	}
	text := string(b)
	treeSet, err := templateutil.Parse(path, text)
	if err != nil {
		return err
	}
	for _, v := range treeSet {
		if err := templateutil.ReplaceTranslatableBlocks(v, "t"); err != nil {
			return err
		}
		templateutil.WalkTree(v, func(n, p parse.Node) {
			var fname string
			switch n.Type() {
			case parse.NodeIdentifier:
				fname = n.(*parse.IdentifierNode).Ident
			case parse.NodeField:
				ident := n.(*parse.FieldNode).Ident
				if len(ident) > 1 {
					fname = ident[len(ident)-1]
				}
			case parse.NodeVariable:
				ident := n.(*parse.VariableNode).Ident
				if len(ident) > 1 {
					fname = ident[len(ident)-1]
				}
			}
			if fname != "" {
				f := funcs[fname]
				if f != nil {
					count := 1
					if f.Context {
						count++
					}
					if f.Plural {
						count++
					}
					cmd := p.(*parse.CommandNode)
					// First argument is the function name
					if c := len(cmd.Args) - 1; c != count {
						log.Debugf("Skipping function %s (%v) - want %d arguments, got %d", f.Name, n.Position(), count, c)
						return
					}
					var s []string
					for ii := 1; ii < len(cmd.Args); ii++ {
						if sn, ok := cmd.Args[ii].(*parse.StringNode); ok {
							s = append(s, sn.Text)
						} else {
							log.Debugf("Skipping function %s (%v) - non-string argument at position %d", f.Name, n.Position(), ii)
							return
						}
					}
					message := &Message{}
					switch len(s) {
					case 1:
						message.Singular = s[0]
					case 2:
						if f.Context {
							message.Context = s[0]
							message.Singular = s[1]
						} else {
							message.Singular = s[0]
							message.Plural = s[1]
						}
					case 3:
						message.Context = s[0]
						message.Singular = s[1]
						message.Plural = s[2]
					}
					// TODO: The line number doesn't match exactly because of the
					// prepended variables
					pos := templatePosition(path, text, n)
					if err = messages.Add(message, pos, ""); err != nil {
						return
					}
				}
			}
		})
	}
	return err
}

func templatePosition(name string, text string, n parse.Node) *token.Position {
	return &token.Position{
		Filename: name,
		Line:     strings.Count(text[:int(n.Position())], "\n") + 1,
	}
}
<|endoftext|>


<commit_before>// (C) 2014 Mathias Dalheimer <md@gonium.net>. See LICENSE file for
// license.
package defluxio

import (
	"bufio"
	"fmt"
	"os"
	"text/template"
	"time"
)

const (
	licenseTemplate = `# The https://netzsin.us grid frequency measurements are
# (c) Mathias Dalheimer, <md@gonium.net>.
#
# This database is made available under the Open Database License:
#   http://opendatacommons.org/licenses/odbl/1.0/.
# Any rights in individual contents of the database are licensed under
# the Database Contents License:
#   http://opendatacommons.org/licenses/dbcl/1.0/
# Please see http://opendatacommons.org/licenses/odbl/summary/ for a
# human-readable explanation of the license.
#
# Generated on {{.GenerationDate}}
timestamp	reading
`
)

type LicenseData struct {
	GenerationDate string
}

type TsvExporter struct {
	Path string
}

func NewTsvExporter(path string) (*TsvExporter, error) {
	retval := new(TsvExporter)
	retval.Path = path
	return retval, nil
}

func (tsve *TsvExporter) ExportDataset(timeReadings []MeterReading) error {
	file, err := os.Create(tsve.Path)
	if err != nil {
		return err
	}
	defer file.Close()

	w := bufio.NewWriter(file)
	gd := time.Now().String()
	ld := LicenseData{GenerationDate: gd}
	t :=
		template.Must(template.New("licenseTemplate").Parse(licenseTemplate))
	err = t.Execute(w, ld)
	if err != nil {
		return fmt.Errorf("Failed to create license header: %s", err)
	}
	for _, element := range timeReadings {
		exportLine := fmt.Sprintf("%d\t%f", element.Reading.Timestamp.Unix(),
			element.Reading.Value)
		fmt.Fprintln(w, exportLine)
	}
	return w.Flush()
}
<commit_msg>marked the timestamp frequency-header line as a comment<commit_after>// (C) 2014 Mathias Dalheimer <md@gonium.net>. See LICENSE file for
// license.
package defluxio

import (
	"bufio"
	"fmt"
	"os"
	"text/template"
	"time"
)

const (
	licenseTemplate = `# The https://netzsin.us grid frequency measurements are
# (c) Mathias Dalheimer, <md@gonium.net>.
#
# This database is made available under the Open Database License:
#   http://opendatacommons.org/licenses/odbl/1.0/.
# Any rights in individual contents of the database are licensed under
# the Database Contents License:
#   http://opendatacommons.org/licenses/dbcl/1.0/
# Please see http://opendatacommons.org/licenses/odbl/summary/ for a
# human-readable explanation of the license.
#
# Generated on {{.GenerationDate}}
#timestamp	reading
`
)

type LicenseData struct {
	GenerationDate string
}

type TsvExporter struct {
	Path string
}

func NewTsvExporter(path string) (*TsvExporter, error) {
	retval := new(TsvExporter)
	retval.Path = path
	return retval, nil
}

func (tsve *TsvExporter) ExportDataset(timeReadings []MeterReading) error {
	file, err := os.Create(tsve.Path)
	if err != nil {
		return err
	}
	defer file.Close()

	w := bufio.NewWriter(file)
	gd := time.Now().String()
	ld := LicenseData{GenerationDate: gd}
	t :=
		template.Must(template.New("licenseTemplate").Parse(licenseTemplate))
	err = t.Execute(w, ld)
	if err != nil {
		return fmt.Errorf("Failed to create license header: %s", err)
	}
	for _, element := range timeReadings {
		exportLine := fmt.Sprintf("%d\t%f", element.Reading.Timestamp.Unix(),
			element.Reading.Value)
		fmt.Fprintln(w, exportLine)
	}
	return w.Flush()
}
<|endoftext|>