<commit_before>package raws

import (
	"context"
	"errors"
	"fmt"
	"io"
	"path/filepath"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
	"github.com/aws/aws-sdk-go/service/elasticache"
	"github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface"
	"github.com/aws/aws-sdk-go/service/elb"
	"github.com/aws/aws-sdk-go/service/elb/elbiface"
	"github.com/aws/aws-sdk-go/service/elbv2"
	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
	"github.com/aws/aws-sdk-go/service/s3/s3manager/s3manageriface"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/aws/aws-sdk-go/service/sts/stsiface"
)

// AWSReader is the interface defining all methods that need to be implemented
//
// The next behavior commented in the below paragraph, applies to every method
// which clearly match what's explained, for the sake of not repeating the same,
// over and over.
// The most of the methods defined by this interface, return their results in a
// map. Those maps, have as keys, the AWS region which have been requested and
// the values are the items returned by AWS for such region.
// Because the methods may make calls to different regions, in case that there
// is an error on a region, the returned map won't have any entry for such
// region and such errors will be reported by the returned error, nonetheless
// the items, got from the successful requests to other regions, will be
// returned, with the meaning that the methods will return partial results, in
// case of errors.
// For avoiding by the callers the problem of if the returned map may be nil,
// the function will always return a map instance, which will be of length 0
// in case that there is not any successful request.
type AWSReader interface {
	// GetAccountID returns the current ID for the account used
	GetAccountID() string

	// GetRegions returns the currently used regions for the Connector
	GetRegions() []string

	// GetInstances returns all EC2 instances based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetInstances(ctx context.Context, input *ec2.DescribeInstancesInput) (map[string]ec2.DescribeInstancesOutput, error)

	// GetVpcs returns all EC2 VPCs based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetVpcs(ctx context.Context, input *ec2.DescribeVpcsInput) (map[string]ec2.DescribeVpcsOutput, error)

	// GetImages returns all EC2 AMI belonging to the Account ID based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetImages(ctx context.Context, input *ec2.DescribeImagesInput) (map[string]ec2.DescribeImagesOutput, error)

	// GetSecurityGroups returns all EC2 security groups based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetSecurityGroups(
		ctx context.Context, input *ec2.DescribeSecurityGroupsInput,
	) (map[string]ec2.DescribeSecurityGroupsOutput, error)

	// GetSubnets returns all EC2 subnets based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetSubnets(ctx context.Context, input *ec2.DescribeSubnetsInput) (map[string]ec2.DescribeSubnetsOutput, error)

	// GetVolumes returns all EC2 volumes based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetVolumes(ctx context.Context, input *ec2.DescribeVolumesInput) (map[string]ec2.DescribeVolumesOutput, error)

	// GetSnapshots returns all snapshots belonging to the Account ID based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetSnapshots(ctx context.Context, input *ec2.DescribeSnapshotsInput) (map[string]ec2.DescribeSnapshotsOutput, error)

	// GetElastiCacheCluster returns all Elasticache clusters based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetElastiCacheCluster(
		ctx context.Context, input *elasticache.DescribeCacheClustersInput,
	) (map[string]elasticache.DescribeCacheClustersOutput, error)

	// GetElastiCacheTags returns a list of tags of Elasticache resources based on its ARN.
	// Returned values are commented in the interface doc comment block.
	GetElastiCacheTags(
		ctx context.Context, input *elasticache.ListTagsForResourceInput,
	) (map[string]elasticache.TagListMessage, error)

	// GetLoadBalancers returns a list of ELB (v1) based on the input from the different regions.
	// Returned values are commented in the interface doc comment block.
	GetLoadBalancers(
		ctx context.Context, input *elb.DescribeLoadBalancersInput,
	) (map[string]elb.DescribeLoadBalancersOutput, error)

	// GetLoadBalancersTags returns a list of Tags based on the input from the different regions.
	// Returned values are commented in the interface doc comment block.
	GetLoadBalancersTags(ctx context.Context, input *elb.DescribeTagsInput) (map[string]elb.DescribeTagsOutput, error)

	// GetLoadBalancersV2 returns a list of ELB (v2) - also known as ALB - based on the input from the different regions.
	// Returned values are commented in the interface doc comment block.
	GetLoadBalancersV2(
		ctx context.Context, input *elbv2.DescribeLoadBalancersInput,
	) (map[string]elbv2.DescribeLoadBalancersOutput, error)

	// GetLoadBalancersV2Tags returns a list of Tags based on the input from the different regions.
	// Returned values are commented in the interface doc comment block.
	GetLoadBalancersV2Tags(
		ctx context.Context, input *elbv2.DescribeTagsInput,
	) (map[string]elbv2.DescribeTagsOutput, error)

	// GetDBInstances returns all DB instances based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetDBInstances(
		ctx context.Context, input *rds.DescribeDBInstancesInput,
	) (map[string]rds.DescribeDBInstancesOutput, error)

	// GetDBInstancesTags returns a list of tags from an ARN, extra filters for tags can also be provided.
	// Returned values are commented in the interface doc comment block.
	GetDBInstancesTags(
		ctx context.Context, input *rds.ListTagsForResourceInput,
	) (map[string]rds.ListTagsForResourceOutput, error)

	// ListBuckets returns all S3 buckets based on the input given.
	// Returned values are commented in the interface doc comment block.
	ListBuckets(ctx context.Context, input *s3.ListBucketsInput) (map[string]s3.ListBucketsOutput, error)

	// GetBucketTags returns tags associated with S3 buckets based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetBucketTags(ctx context.Context, input *s3.GetBucketTaggingInput) (map[string]s3.GetBucketTaggingOutput, error)

	// ListObjects returns a list of all S3 objects in a bucket based on the input given.
	// Returned values are commented in the interface doc comment block.
	ListObjects(ctx context.Context, input *s3.ListObjectsInput) (map[string]s3.ListObjectsOutput, error)

	// DownloadObject downloads an object in a bucket based on the input given
	DownloadObject(
		ctx context.Context, w io.WriterAt, input *s3.GetObjectInput, options ...func(*s3manager.Downloader),
	) (int64, error)

	// GetObjectsTags returns tags associated with S3 objects based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetObjectsTags(ctx context.Context, input *s3.GetObjectTaggingInput) (map[string]s3.GetObjectTaggingOutput, error)
}

// NewAWSReader returns an object which also contains the accountID and extend the different regions to use.
//
// The accountID is helpful to return only the AMI or snapshots that belong to the account.
//
// While the regions slice also supports regex so, "eu-*" can be passed, and will be extended to: eu-west-1, eu-west-2 &
// eu-central-1.
//
// When calls are done through the Connector instance, then all regions will be called for those services.
// Thus making requests to AWS much easier than through the different connectors/regions of its go SDK.
//
// The connections are not all established while instancing, but the various sessions are, this way connections are only
// made for services that are called, otherwise only the sessions remain.
func NewAWSReader(
	ctx context.Context, accessKey string, secretKey string, regions []string, config *aws.Config,
) (AWSReader, error) {
	var c = connector{}

	creds, ec2s, sts, err := configureAWS(accessKey, secretKey)
	if err != nil {
		return nil, err
	}
	c.creds = creds
	if err := c.setAccountID(ctx, sts); err != nil {
		return nil, err
	}
	if err := c.setRegions(ctx, ec2s, regions); err != nil {
		return nil, err
	}
	c.setServices(config)
	return &c, nil
}

// The connector provides easy access to AWS SDK calls.
//
// By using it, calls can be made directly through multiple regions, and will filter only data that belongs to you.
// For example, when fetching the list of AMI, or snapshots.
//
// In order to start making calls, only calling NewAWSReader is required.
type connector struct {
	regions   []string
	svcs      []*serviceConnector
	creds     *credentials.Credentials
	accountID *string
}

func (c *connector) GetAccountID() string {
	return *c.accountID
}

func (c *connector) GetRegions() []string {
	return c.regions
}

type serviceConnector struct {
	region       string
	session      *session.Session
	ec2          ec2iface.EC2API
	elb          elbiface.ELBAPI
	elbv2        elbv2iface.ELBV2API
	rds          rdsiface.RDSAPI
	s3           s3iface.S3API
	s3downloader s3manageriface.DownloaderAPI
	elasticache  elasticacheiface.ElastiCacheAPI
}

func configureAWS(accessKey string, secretKey string) (*credentials.Credentials, ec2iface.EC2API, stsiface.STSAPI, error) {
	/* The default region is only used to (1) get the list of region and
	 * (2) get the account ID associated with the credentials.
	 *
	 * It is not used as a default region for services, therefore if no
	 * region is specified when instantiating the connector, then it will
	 * not try to establish any connections with AWS services.
	 */
	const defaultRegion string = "eu-west-1"
	var token = ""

	creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
	_, err := creds.Get()
	if err != nil {
		return nil, nil, nil, err
	}
	sess := session.Must(
		session.NewSession(&aws.Config{
			Region:      aws.String(defaultRegion),
			DisableSSL:  aws.Bool(false),
			MaxRetries:  aws.Int(3),
			Credentials: creds,
		}),
	)
	return creds, ec2.New(sess), sts.New(sess), nil
}

func (c *connector) setRegions(ctx context.Context, ec2 ec2iface.EC2API, enabledRegions []string) error {
	if len(enabledRegions) == 0 {
		return errors.New("at least one region name is required")
	}
	regions, err := ec2.DescribeRegionsWithContext(ctx, nil)
	if err != nil {
		return err
	}
	for _, enabledRegion := range enabledRegions {
		for _, region := range regions.Regions {
			if match, _ := filepath.Match(enabledRegion, *region.RegionName); match {
				c.regions = append(c.regions, *region.RegionName)
			}
		}
	}
	if len(c.regions) == 0 {
		return fmt.Errorf("found 0 regions matching: %v", enabledRegions)
	}
	return nil
}

func (c *connector) setAccountID(ctx context.Context, sts stsiface.STSAPI) error {
	resp, err := sts.GetCallerIdentityWithContext(ctx, nil)
	if err != nil {
		return err
	}
	c.accountID = resp.Account
	return nil
}

func (c *connector) setServices(config *aws.Config) {
	if config != nil {
		config.Credentials = c.creds
	} else {
		config = &aws.Config{
			DisableSSL:  aws.Bool(false),
			MaxRetries:  aws.Int(3),
			Credentials: c.creds,
		}
	}
	for _, region := range c.regions {
		config.Region = aws.String(region)
		sess := session.Must(session.NewSession(config))
		svc := &serviceConnector{
			region:  region,
			session: sess,
		}
		c.svcs = append(c.svcs, svc)
	}
}
<commit_msg>connnector: Define GetRecordedResourcesCounts method<commit_after>package raws

import (
	"context"
	"errors"
	"fmt"
	"io"
	"path/filepath"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/configservice"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
	"github.com/aws/aws-sdk-go/service/elasticache"
	"github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface"
	"github.com/aws/aws-sdk-go/service/elb"
	"github.com/aws/aws-sdk-go/service/elb/elbiface"
	"github.com/aws/aws-sdk-go/service/elbv2"
	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
	"github.com/aws/aws-sdk-go/service/s3/s3manager/s3manageriface"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/aws/aws-sdk-go/service/sts/stsiface"
)

// AWSReader is the interface defining all methods that need to be implemented
//
// The next behavior commented in the below paragraph, applies to every method
// which clearly match what's explained, for the sake of not repeating the same,
// over and over.
// The most of the methods defined by this interface, return their results in a
// map. Those maps, have as keys, the AWS region which have been requested and
// the values are the items returned by AWS for such region.
// Because the methods may make calls to different regions, in case that there
// is an error on a region, the returned map won't have any entry for such
// region and such errors will be reported by the returned error, nonetheless
// the items, got from the successful requests to other regions, will be
// returned, with the meaning that the methods will return partial results, in
// case of errors.
// For avoiding by the callers the problem of if the returned map may be nil,
// the function will always return a map instance, which will be of length 0
// in case that there is not any successful request.
type AWSReader interface {
	// GetAccountID returns the current ID for the account used
	GetAccountID() string

	// GetRegions returns the currently used regions for the Connector
	GetRegions() []string

	// GetInstances returns all EC2 instances based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetInstances(ctx context.Context, input *ec2.DescribeInstancesInput) (map[string]ec2.DescribeInstancesOutput, error)

	// GetVpcs returns all EC2 VPCs based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetVpcs(ctx context.Context, input *ec2.DescribeVpcsInput) (map[string]ec2.DescribeVpcsOutput, error)

	// GetImages returns all EC2 AMI belonging to the Account ID based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetImages(ctx context.Context, input *ec2.DescribeImagesInput) (map[string]ec2.DescribeImagesOutput, error)

	// GetSecurityGroups returns all EC2 security groups based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetSecurityGroups(
		ctx context.Context, input *ec2.DescribeSecurityGroupsInput,
	) (map[string]ec2.DescribeSecurityGroupsOutput, error)

	// GetSubnets returns all EC2 subnets based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetSubnets(ctx context.Context, input *ec2.DescribeSubnetsInput) (map[string]ec2.DescribeSubnetsOutput, error)

	// GetVolumes returns all EC2 volumes based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetVolumes(ctx context.Context, input *ec2.DescribeVolumesInput) (map[string]ec2.DescribeVolumesOutput, error)

	// GetSnapshots returns all snapshots belonging to the Account ID based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetSnapshots(ctx context.Context, input *ec2.DescribeSnapshotsInput) (map[string]ec2.DescribeSnapshotsOutput, error)

	// GetElastiCacheCluster returns all Elasticache clusters based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetElastiCacheCluster(
		ctx context.Context, input *elasticache.DescribeCacheClustersInput,
	) (map[string]elasticache.DescribeCacheClustersOutput, error)

	// GetElastiCacheTags returns a list of tags of Elasticache resources based on its ARN.
	// Returned values are commented in the interface doc comment block.
	GetElastiCacheTags(
		ctx context.Context, input *elasticache.ListTagsForResourceInput,
	) (map[string]elasticache.TagListMessage, error)

	// GetLoadBalancers returns a list of ELB (v1) based on the input from the different regions.
	// Returned values are commented in the interface doc comment block.
	GetLoadBalancers(
		ctx context.Context, input *elb.DescribeLoadBalancersInput,
	) (map[string]elb.DescribeLoadBalancersOutput, error)

	// GetLoadBalancersTags returns a list of Tags based on the input from the different regions.
	// Returned values are commented in the interface doc comment block.
	GetLoadBalancersTags(ctx context.Context, input *elb.DescribeTagsInput) (map[string]elb.DescribeTagsOutput, error)

	// GetLoadBalancersV2 returns a list of ELB (v2) - also known as ALB - based on the input from the different regions.
	// Returned values are commented in the interface doc comment block.
	GetLoadBalancersV2(
		ctx context.Context, input *elbv2.DescribeLoadBalancersInput,
	) (map[string]elbv2.DescribeLoadBalancersOutput, error)

	// GetLoadBalancersV2Tags returns a list of Tags based on the input from the different regions.
	// Returned values are commented in the interface doc comment block.
	GetLoadBalancersV2Tags(
		ctx context.Context, input *elbv2.DescribeTagsInput,
	) (map[string]elbv2.DescribeTagsOutput, error)

	// GetDBInstances returns all DB instances based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetDBInstances(
		ctx context.Context, input *rds.DescribeDBInstancesInput,
	) (map[string]rds.DescribeDBInstancesOutput, error)

	// GetDBInstancesTags returns a list of tags from an ARN, extra filters for tags can also be provided.
	// Returned values are commented in the interface doc comment block.
	GetDBInstancesTags(
		ctx context.Context, input *rds.ListTagsForResourceInput,
	) (map[string]rds.ListTagsForResourceOutput, error)

	// ListBuckets returns all S3 buckets based on the input given.
	// Returned values are commented in the interface doc comment block.
	ListBuckets(ctx context.Context, input *s3.ListBucketsInput) (map[string]s3.ListBucketsOutput, error)

	// GetBucketTags returns tags associated with S3 buckets based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetBucketTags(ctx context.Context, input *s3.GetBucketTaggingInput) (map[string]s3.GetBucketTaggingOutput, error)

	// ListObjects returns a list of all S3 objects in a bucket based on the input given.
	// Returned values are commented in the interface doc comment block.
	ListObjects(ctx context.Context, input *s3.ListObjectsInput) (map[string]s3.ListObjectsOutput, error)

	// DownloadObject downloads an object in a bucket based on the input given
	DownloadObject(
		ctx context.Context, w io.WriterAt, input *s3.GetObjectInput, options ...func(*s3manager.Downloader),
	) (int64, error)

	// GetObjectsTags returns tags associated with S3 objects based on the input given.
	// Returned values are commented in the interface doc comment block.
	GetObjectsTags(ctx context.Context, input *s3.GetObjectTaggingInput) (map[string]s3.GetObjectTaggingOutput, error)

	// GetRecordedResourceCounts returns counts of the AWS resources which have
	// been recorded by AWS Config.
	// See https://docs.aws.amazon.com/config/latest/APIReference/API_GetDiscoveredResourceCounts.html
	// for more information about what to enable in your AWS account, the list of
	// supported resources, etc.
	GetRecordedResourceCounts(
		ctx context.Context, input *configservice.GetDiscoveredResourceCountsInput,
	) (map[string]configservice.GetDiscoveredResourceCountsOutput, error)
}

// NewAWSReader returns an object which also contains the accountID and extend the different regions to use.
//
// The accountID is helpful to return only the AMI or snapshots that belong to the account.
//
// While the regions slice also supports regex so, "eu-*" can be passed, and will be extended to: eu-west-1, eu-west-2 &
// eu-central-1.
//
// When calls are done through the Connector instance, then all regions will be called for those services.
// Thus making requests to AWS much easier than through the different connectors/regions of its go SDK.
//
// The connections are not all established while instancing, but the various sessions are, this way connections are only
// made for services that are called, otherwise only the sessions remain.
func NewAWSReader(
	ctx context.Context, accessKey string, secretKey string, regions []string, config *aws.Config,
) (AWSReader, error) {
	var c = connector{}

	creds, ec2s, sts, err := configureAWS(accessKey, secretKey)
	if err != nil {
		return nil, err
	}
	c.creds = creds
	if err := c.setAccountID(ctx, sts); err != nil {
		return nil, err
	}
	if err := c.setRegions(ctx, ec2s, regions); err != nil {
		return nil, err
	}
	c.setServices(config)
	return &c, nil
}

// The connector provides easy access to AWS SDK calls.
//
// By using it, calls can be made directly through multiple regions, and will filter only data that belongs to you.
// For example, when fetching the list of AMI, or snapshots.
//
// In order to start making calls, only calling NewAWSReader is required.
type connector struct {
	regions   []string
	svcs      []*serviceConnector
	creds     *credentials.Credentials
	accountID *string
}

func (c *connector) GetAccountID() string {
	return *c.accountID
}

func (c *connector) GetRegions() []string {
	return c.regions
}

type serviceConnector struct {
	region       string
	session      *session.Session
	ec2          ec2iface.EC2API
	elb          elbiface.ELBAPI
	elbv2        elbv2iface.ELBV2API
	rds          rdsiface.RDSAPI
	s3           s3iface.S3API
	s3downloader s3manageriface.DownloaderAPI
	elasticache  elasticacheiface.ElastiCacheAPI
}

func configureAWS(accessKey string, secretKey string) (*credentials.Credentials, ec2iface.EC2API, stsiface.STSAPI, error) {
	/* The default region is only used to (1) get the list of region and
	 * (2) get the account ID associated with the credentials.
	 *
	 * It is not used as a default region for services, therefore if no
	 * region is specified when instantiating the connector, then it will
	 * not try to establish any connections with AWS services.
	 */
	const defaultRegion string = "eu-west-1"
	var token = ""

	creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
	_, err := creds.Get()
	if err != nil {
		return nil, nil, nil, err
	}
	sess := session.Must(
		session.NewSession(&aws.Config{
			Region:      aws.String(defaultRegion),
			DisableSSL:  aws.Bool(false),
			MaxRetries:  aws.Int(3),
			Credentials: creds,
		}),
	)
	return creds, ec2.New(sess), sts.New(sess), nil
}

func (c *connector) setRegions(ctx context.Context, ec2 ec2iface.EC2API, enabledRegions []string) error {
	if len(enabledRegions) == 0 {
		return errors.New("at least one region name is required")
	}
	regions, err := ec2.DescribeRegionsWithContext(ctx, nil)
	if err != nil {
		return err
	}
	for _, enabledRegion := range enabledRegions {
		for _, region := range regions.Regions {
			if match, _ := filepath.Match(enabledRegion, *region.RegionName); match {
				c.regions = append(c.regions, *region.RegionName)
			}
		}
	}
	if len(c.regions) == 0 {
		return fmt.Errorf("found 0 regions matching: %v", enabledRegions)
	}
	return nil
}

func (c *connector) setAccountID(ctx context.Context, sts stsiface.STSAPI) error {
	resp, err := sts.GetCallerIdentityWithContext(ctx, nil)
	if err != nil {
		return err
	}
	c.accountID = resp.Account
	return nil
}

func (c *connector) setServices(config *aws.Config) {
	if config != nil {
		config.Credentials = c.creds
	} else {
		config = &aws.Config{
			DisableSSL:  aws.Bool(false),
			MaxRetries:  aws.Int(3),
			Credentials: c.creds,
		}
	}
	for _, region := range c.regions {
		config.Region = aws.String(region)
		sess := session.Must(session.NewSession(config))
		svc := &serviceConnector{
			region:  region,
			session: sess,
		}
		c.svcs = append(c.svcs, svc)
	}
}
<|endoftext|>


<commit_before>package main

import (
    "flag"
    "fmt"
    "io"
    "path/filepath"
    "os"
    "os/exec"

    "github.com/fatih/color"
    "github.com/Varjelus/archivist"
)

// Versions
const (
    VERSION = "0.1.0"
    NW_VERSION = "1.2.0"
    AR_VERSION = "0.9.0"
)

// Defaults
const (
    DEFAULT_SRC = "."
    DEFAULT_REL = "nw-release"
    DEFAULT_ICO = "icon.ico"
)
var DEFAULT_TMP = os.TempDir()
var DEFAULT_TOL = func() string {
    dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
    if err != nil {
        fatalError(err.Error())
    }
    return filepath.Join(dir, "buildTools")
}()

// Colors
var (
    fatalRed    = color.New(color.BgRed)
	red         = color.New(color.FgRed)
	yellow      = color.New(color.FgYellow)
	cyan        = color.New(color.FgCyan)
	green       = color.New(color.FgGreen)
)

// Platforms
var platforms = []string{"win64"}

// Flags
var (
    helpFlag        = flag.Bool("help", false, "Show possible arguments and their default values and explanations.")
    platformsFlag   = flag.Bool("targets", false, "Show available target platforms.")
    versionFlag     = flag.Bool("version", false, "Show the versions and required versions of external tools.")
    nwDir           = flag.String("nw", fmt.Sprintf("%s%cnw", DEFAULT_TOL, os.PathSeparator), "Set NW.js path.")
    deflate         = flag.Bool("cmp", false, "Should the ZIP be compressed.")
    srcDir          = flag.String("src", DEFAULT_SRC, "Source directory containing all your project files, including package.json.")
    outDir          = flag.String("out", DEFAULT_REL, "Output directory where you want the packaged application.")
    icon            = flag.String("icon", DEFAULT_ICO, "Desired application icon path.")
    target          = flag.String("target", "win64", "Target platform.")
    projectName     = flag.String("name", "app", "Application name.")
    tmp             = flag.String("tmp", DEFAULT_TMP, "Temporary files directory.")
    usePdf          = flag.Bool("pdf", false, "Does your app need PDF capabilities via pdf.dll? (Large DLL)")
)

var exePath string
var NW_OMIT = []string{"nwjc.exe", "nw.exe", "credits.html"}

func init() {
    flag.Parse()
    if *helpFlag {
		flag.PrintDefaults()
		os.Exit(0)
    }

    if *platformsFlag {
		fmt.Println("Available target platforms:")
        for i := range platforms {
            fmt.Printf("\t* %s", platforms[i])
        }
		os.Exit(0)
    }

    if *versionFlag {
		fmt.Printf("nodebob-go version %s\n", VERSION)
        fmt.Printf("nw.js version %s\n", NW_VERSION)
        fmt.Printf("Anolis Resourcer version %s\n", AR_VERSION)
		os.Exit(0)
    }

    exePath = filepath.Join(*outDir, fmt.Sprintf("%s.exe", *projectName))
}

func fatalError(err string) {
    fatalRed.Println(err)
    os.Exit(2)
}

func copyFile(src, dst string) error {
    // Check first file
    sfi, err := os.Stat(src)
    if err != nil {
        return err
    }
    if !sfi.Mode().IsRegular() {
        // cannot copy non-regular files (e.g., directories,
        // symlinks, devices, etc.)
        return fmt.Errorf("copyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
    }

    // Check second file
    dfi, err := os.Stat(dst)
    if err != nil {
        if !os.IsNotExist(err) {
            return err
        }
    } else {
        if !(dfi.Mode().IsRegular()) {
            return fmt.Errorf("copyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
        }
        if os.SameFile(sfi, dfi) {
            return nil
        }
    }

    // Open source file
    in, err := os.Open(src)
    if err != nil {
        return err
    }
    defer in.Close()

    // Create destination file
    out, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer func() {
        cerr := out.Close()
        if err == nil {
            err = cerr
        }
    }()

    // Copy
    if _, err = io.Copy(out, in); err != nil {
        return err
    }

    // Sync
    return out.Sync()
}

// First argument is the final destination path, the rest are files to combine
func copyPlus(dst string, srcs ...string) error {
    temp := filepath.Join(os.TempDir(), "bob.plus")
    together, err := os.OpenFile(temp, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
    if err != nil {
        return err
    }
    defer together.Close()

    for _, src := range srcs {
        f, err := os.Open(src)
        if err != nil {
            return err
        }

        if _, err = io.Copy(together, f); err != nil {
            return err
        }

        f.Close()
    }

    if err := together.Close(); err != nil {
        return err
    }

    return copyFile(temp, dst)
}

func copyWalk(path string, info os.FileInfo, err error) error {
    if err != nil { return err }

    // Keep only regular, nonempty files
    if !info.Mode().IsRegular() || info.Size() == 0 {
        return nil
    }

    // Omit pdf.dll unless otherwise specified
    if info.Name() == "pdf.dll" && !*usePdf {
        return nil
    }

    // Omit specified files
    for _, e := range NW_OMIT {
        if e == info.Name() {
            return nil
        }
    }

    dstPath := filepath.Join(*outDir, info.Name())

    // Check if the destination file exists
    dstInfo, err := os.Stat(dstPath)
    // If it does and it errors, return
    if err != nil && os.IsExist(err) {
        return err
    }
    // If it DOES exist and has the same size, return nil
    if os.IsExist(err) && os.SameFile(info, dstInfo) {
        return nil
    }

    return copyFile(path, dstPath)
}

func createZip() {
    abs, err := filepath.Abs(*srcDir)
    if err != nil {
        fatalError(err.Error())
    }
    if err := archivist.Store(abs, filepath.Join(*tmp, "bob.nw")); err != nil {
        fatalError(err.Error())
    }
}

func createExe() {
    if err := os.MkdirAll(*outDir, 0666); err != nil {
        fatalError(err.Error())
    }
    if err := copyFile(filepath.Join(*nwDir, "nw.exe"), exePath); err != nil {
        fatalError(err.Error())
    }
    if err := copyPlus(exePath, exePath, filepath.Join(*tmp, "bob.nw")); err != nil {
        fatalError("Can't copy app.nw+nw.exe: " + err.Error())
    }
}

func createIcon() error {
    if _, err := os.Stat(*icon); err != nil {
        if os.IsNotExist(err) {
            return fmt.Errorf("Can't find '%s'", *icon)
        }
        return fmt.Errorf("Can't read '%s': %s", *icon, err.Error())
    }

    resourcer, err := exec.LookPath(fmt.Sprintf("%s/ar/Resourcer.exe", DEFAULT_TOL))
    if err != nil {
        return fmt.Errorf("Can't find the Anolis Resourcer executable: %s", err.Error())
    }
    srcParam := fmt.Sprintf("-src:%s", exePath)
    icoParam := fmt.Sprintf("-file:%s", *icon)
    cmd := exec.Command(resourcer, "-op:upd", srcParam, "-type:14", "-name:IDR_MAINFRAME", icoParam)
    if err := cmd.Run(); err != nil {
        return fmt.Errorf("Can't embed the icon resource: %s", err.Error())
    }

    return nil
}

func main() {
    cyan.Println("nodebob-go\n---\n\n")
    fmt.Printf("NW.js files directory: %s\n", *nwDir)
    fmt.Printf("Source files directory: %s\n", *srcDir)
    fmt.Printf("Output directory: %s\n", *outDir)
    fmt.Printf("Icon path: %s\n", *icon)
    fmt.Printf("Output executable path: %s \n", exePath)
    fmt.Printf("Temporary files directory: %s\n\n", *tmp)

    // Make a ZIP
    fmt.Print("Packaging sources... ")
    createZip()
    green.Println("OK")

    // Create the exe
    fmt.Print("Creating the executable... ")
    createExe()
    green.Println("OK")

    // Embed the icon
    fmt.Print("Embedding icon... ")
    if err := createIcon(); err != nil {
        yellow.Println(err.Error())
    } else { green.Println("OK") }

    // Copy files
    fmt.Print("Copying NW.js files... ")
    if err := filepath.Walk(*nwDir, copyWalk); err != nil {
        fatalError("Error copying NW.js files: " + err.Error())
    }
    green.Println("OK")

    // Delete temporary files
    fmt.Print("Deleting temporary files... ")
    if err := os.Remove(filepath.Join(os.TempDir(), "bob.plus")); err != nil {
        fatalError(err.Error())
    }
    if err := os.Remove(filepath.Join(*tmp, "bob.nw")); err != nil {
        fatalError(err.Error())
    }
    green.Println("OK")

    cyan.Println("Done!")
}
<commit_msg>Fix release dir perm issue which prevented nwjs write to temp<commit_after>package main

import (
    "flag"
    "fmt"
    "io"
    "path/filepath"
    "os"
    "os/exec"
    "strings"

    "github.com/fatih/color"
    "github.com/Varjelus/archivist"
)

// Versions
const (
    VERSION = "0.1.0"
    NW_VERSION = "1.2.0"
    AR_VERSION = "0.9.0"
)

// Defaults
const (
    DEFAULT_SRC = "."
    DEFAULT_REL = "nw-release"
    DEFAULT_ICO = "icon.ico"
)
var DEFAULT_TMP = os.TempDir()
var DEFAULT_TOL = func() string {
    dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
    if err != nil {
        fatalError(err.Error())
    }
    return filepath.Join(dir, "buildTools")
}()

// Colors
var (
    fatalRed    = color.New(color.BgRed)
	red         = color.New(color.FgRed)
	yellow      = color.New(color.FgYellow)
	cyan        = color.New(color.FgCyan)
	green       = color.New(color.FgGreen)
)

// Platforms
var platforms = []string{"win64"}

// Flags
var (
    helpFlag        = flag.Bool("help", false, "Show possible arguments and their default values and explanations.")
    platformsFlag   = flag.Bool("targets", false, "Show available target platforms.")
    versionFlag     = flag.Bool("version", false, "Show the versions and required versions of external tools.")
    nwDir           = flag.String("nw", fmt.Sprintf("%s%cnw", DEFAULT_TOL, os.PathSeparator), "Set NW.js path.")
    deflate         = flag.Bool("cmp", false, "Should the ZIP be compressed.")
    srcDir          = flag.String("src", DEFAULT_SRC, "Source directory containing all your project files, including package.json.")
    outDir          = flag.String("out", DEFAULT_REL, "Output directory where you want the packaged application.")
    icon            = flag.String("icon", DEFAULT_ICO, "Desired application icon path.")
    target          = flag.String("target", "win64", "Target platform.")
    projectName     = flag.String("name", "app", "Application name.")
    tmp             = flag.String("tmp", DEFAULT_TMP, "Temporary files directory.")
    usePdf          = flag.Bool("pdf", false, "Does your app need PDF capabilities via pdf.dll? (Large DLL)")
)

var exePath string
var NW_OMIT = []string{"nwjc.exe", "nw.exe", "credits.html"}

func init() {
    flag.Parse()
    if *helpFlag {
		flag.PrintDefaults()
		os.Exit(0)
    }

    if *platformsFlag {
		fmt.Println("Available target platforms:")
        for i := range platforms {
            fmt.Printf("\t* %s", platforms[i])
        }
		os.Exit(0)
    }

    if *versionFlag {
		fmt.Printf("nodebob-go version %s\n", VERSION)
        fmt.Printf("nw.js version %s\n", NW_VERSION)
        fmt.Printf("Anolis Resourcer version %s\n", AR_VERSION)
		os.Exit(0)
    }

    exePath = filepath.Join(*outDir, fmt.Sprintf("%s.exe", *projectName))
}

func fatalError(err string) {
    fatalRed.Println(err)
    os.Exit(2)
}

func copyFile(src, dst string) error {
    // Check first file
    sfi, err := os.Stat(src)
    if err != nil {
        return err
    }
    if !sfi.Mode().IsRegular() {
        // cannot copy non-regular files (e.g., directories,
        // symlinks, devices, etc.)
        return fmt.Errorf("copyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
    }

    // Check second file
    dfi, err := os.Stat(dst)
    if err != nil {
        if !os.IsNotExist(err) {
            return err
        }
    } else {
        if !(dfi.Mode().IsRegular()) {
            return fmt.Errorf("copyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
        }
        if os.SameFile(sfi, dfi) {
            return nil
        }
    }

    // Open source file
    in, err := os.Open(src)
    if err != nil {
        return err
    }
    defer in.Close()

    // Create destination file
    out, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer func() {
        cerr := out.Close()
        if err == nil {
            err = cerr
        }
    }()

    // Copy
    if _, err = io.Copy(out, in); err != nil {
        return err
    }

    // Sync
    return out.Sync()
}

// First argument is the final destination path, the rest are files to combine
func copyPlus(dst string, srcs ...string) error {
    temp := filepath.Join(*tmp, "bob.plus")
    together, err := os.OpenFile(temp, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
    if err != nil {
        return err
    }
    defer together.Close()

    for _, src := range srcs {
        f, err := os.Open(src)
        if err != nil {
            return err
        }

        if _, err = io.Copy(together, f); err != nil {
            return err
        }

        f.Close()
    }

    if err := together.Close(); err != nil {
        return err
    }

    return copyFile(temp, dst)
}

func copyWalk(path string, info os.FileInfo, err error) error {
    if err != nil { return err }

    fileName := strings.TrimPrefix(path, *nwDir)
    if len(fileName) < 1 {
        return nil
    }
    dstPath := filepath.Join(*outDir, fileName)

    // Check if the destination file exists
    dstInfo, err := os.Stat(dstPath)
    // If it does exist and it errors, return
    if err != nil && os.IsExist(err) {
        return err
    }
    // If it does exist and it's the same file, return nil
    if os.IsExist(err) && os.SameFile(info, dstInfo) {
        return nil
    }

    if info.IsDir() {
        if err := os.Mkdir(dstPath, info.Mode()); err != nil {
            return err
        }
        return nil
    }

    // Keep only regular, nonempty files
    if !info.Mode().IsRegular() || info.Size() == 0 {
        return nil
    }

    // Omit pdf.dll unless otherwise specified
    if info.Name() == "pdf.dll" && !*usePdf {
        return nil
    }

    // Omit specified files
    for _, e := range NW_OMIT {
        if e == info.Name() {
            return nil
        }
    }

    return copyFile(path, dstPath)
}

func createZip() {
    if err := archivist.Store(*srcDir, filepath.Join(*tmp, "bob.nw")); err != nil {
        fatalError(err.Error())
    }
}

func createExe() {
    if err := os.RemoveAll(*outDir); err != nil && os.IsExist(err) {
        fatalError(err.Error())
    }

    if err := os.MkdirAll(*outDir, os.ModeDir); err != nil {
        fatalError(err.Error())
    }

    if err := copyFile(filepath.Join(*nwDir, "nw.exe"), exePath); err != nil {
        fatalError(err.Error())
    }

    if err := copyPlus(exePath, exePath, filepath.Join(*tmp, "bob.nw")); err != nil {
        fatalError("Can't copy app.nw+nw.exe: " + err.Error())
    }
}

func createIcon() error {
    if _, err := os.Stat(*icon); err != nil {
        if os.IsNotExist(err) {
            return fmt.Errorf("Can't find '%s'", *icon)
        }
        return fmt.Errorf("Can't read '%s': %s", *icon, err.Error())
    }

    resourcer, err := exec.LookPath(fmt.Sprintf("%s/ar/Resourcer.exe", DEFAULT_TOL))
    if err != nil {
        return fmt.Errorf("Can't find the Anolis Resourcer executable: %s", err.Error())
    }
    srcParam := fmt.Sprintf("-src:%s", exePath)
    icoParam := fmt.Sprintf("-file:%s", *icon)
    cmd := exec.Command(resourcer, "-op:upd", srcParam, "-type:14", "-name:IDR_MAINFRAME", icoParam)
    if err := cmd.Run(); err != nil {
        return fmt.Errorf("Can't embed the icon resource: %s", err.Error())
    }

    return nil
}

func main() {
    cyan.Println("nodebob-go\n---\n\n")
    fmt.Printf("NW.js files directory: %s\n", *nwDir)
    fmt.Printf("Source files directory: %s\n", *srcDir)
    fmt.Printf("Output directory: %s\n", *outDir)
    fmt.Printf("Icon path: %s\n", *icon)
    fmt.Printf("Output executable path: %s \n", exePath)
    fmt.Printf("Temporary files directory: %s\n\n", *tmp)

    // Make a ZIP
    fmt.Print("Packaging sources... ")
    createZip()
    green.Println("OK")

    // Create the exe
    fmt.Print("Creating the executable... ")
    createExe()
    green.Println("OK")

    // Embed the icon
    /*fmt.Print("Embedding icon... ")
    if err := createIcon(); err != nil {
        yellow.Println(err.Error())
    } else { green.Println("OK") }*/

    // Copy files
    fmt.Print("Copying NW.js files... ")
    if err := filepath.Walk(*nwDir, copyWalk); err != nil {
        fatalError("Error copying NW.js files: " + err.Error())
    }
    green.Println("OK")

    // Delete temporary files
    fmt.Print("Deleting temporary files... ")
    if err := os.Remove(filepath.Join(*tmp, "bob.plus")); err != nil {
        fatalError(err.Error())
    }
    if err := os.Remove(filepath.Join(*tmp, "bob.nw")); err != nil {
        fatalError(err.Error())
    }
    green.Println("OK")

    cyan.Println("Done!")
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	_ "fmt"
	"log"
	"os"
	"strings"
	"unicode/utf8"
)

const (
	delimiter = ','
	fn        = "unicode_things.csv"
)

func main() {
	// 	s := `FirstName,MiddleName,LastName,Email
	// John,K,Moore,jkdoe@nothing.com
	// Winston,Moby,Kazowski,Winston@nothing.net
	// Dilbert,Sylvester,AlecBaldwin,dsa@silly.com`
	var columnNum int
	var columnCounts = make(map[int]int)
	var lines []string
	f, err := os.Open(fn)
	if err != nil {
		log.Fatalln(err)
	}
	defer f.Close()
	out, err := os.Create("out" + fn)
	if err != nil {
		log.Fatalln(err)
	}
	defer out.Close()
	scanner := bufio.NewScanner(f)

	for scanner.Scan() {
		temp := 0
		columnNum = 0
		line := scanner.Text()
		for i, v := range line {
			temp += utf8.RuneLen(v)
			if v != delimiter && i < len(line)-1 {
				continue
			}
			if temp > columnCounts[columnNum] {
				columnCounts[columnNum] = temp
			}
			columnNum++
			temp = 0
		}
		lines = append(lines, line)
	}

	// w := bufio.NewWriter(out)
	w := bufio.NewWriter(os.Stdout)
	for _, line := range lines {
		words := strings.Split(line, string(delimiter))
		columnNum = 0
		for _, word := range words {
			for len(word) < columnCounts[columnNum] {
				word += " "
			}
			columnNum++
			if _, ok := columnCounts[columnNum]; ok {
				w.WriteString(word + string(delimiter))
				continue
			}
			w.WriteString(word)
		}
		w.WriteByte('\n')
	}
	w.Flush()
}
<commit_msg>Support padding for fields with multi-byte characters<commit_after>package main

import (
	"bufio"
	"log"
	"os"
	"strings"
	"unicode/utf8"
)

const (
	delimiter = ','
	fn        = "things.csv"
)

func main() {
	var columnNum int
	var columnCounts = make(map[int]int)
	var lines []string
	f, err := os.Open(fn)
	if err != nil {
		log.Fatalln(err)
	}
	defer f.Close()
	out, err := os.Create("out" + fn)
	if err != nil {
		log.Fatalln(err)
	}
	defer out.Close()
	scanner := bufio.NewScanner(f)

	for scanner.Scan() {
		temp := 0
		columnNum = 0
		line := scanner.Text()
		for i, v := range line {
			temp += utf8.RuneLen(v)
			if v != delimiter && i < len(line)-1 {
				continue
			}
			if temp > columnCounts[columnNum] {
				columnCounts[columnNum] = temp
			}
			columnNum++
			temp = 0
		}
		lines = append(lines, line)
	}

	w := bufio.NewWriter(out)
	// w := bufio.NewWriter(os.Stdout)
	for _, line := range lines {
		words := strings.Split(line, string(delimiter))
		columnNum = 0
		for _, word := range words {
			for len(word) < columnCounts[columnNum] {
				word += " "
			}
			rCount, wordLen := utf8.RuneCountInString(word), len(word)
			if rCount < wordLen {
				for i := 0; i < wordLen-rCount; i++ {
					word += " "
					// if i == 10 {
					// 	break
					// }
				}
			}
			columnNum++
			if _, ok := columnCounts[columnNum]; ok {
				w.WriteString(word + string(delimiter))
				continue
			}
			w.WriteString(word)
		}
		w.WriteByte('\n')
	}
	w.Flush()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"strings"
)

func main() {
	b, e := ioutil.ReadFile("./aprstestdata.txt")
	if e != nil {
		panic("Cannot read file")
	}

	lines := strings.Split(string(b), "\n")
	for _, v := range lines {
		p, e := ParseAPRSPacket(v)
		if e != nil {
			panic("Failed to recode packet" + string(e))
		}
		fmt.Println(p)
	}
}
<commit_msg>Fixed casting issue<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"strings"
)

func main() {
	b, e := ioutil.ReadFile("./aprstestdata.txt")
	if e != nil {
		panic("Cannot read file")
	}

	lines := strings.Split(string(b), "\n")
	for _, v := range lines {
		p, e := ParseAPRSPacket(v)
		if e != nil {
			panic("Failed to recode packet" + string(e.Error()))
		}
		fmt.Println(p)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/evolsnow/negroni"
	"github.com/evolsnow/samaritan/base"
	"github.com/evolsnow/samaritan/conn"
	mw "github.com/evolsnow/samaritan/middleware"
	//"log"
	"flag"
	"net"
	"strconv"
)

const LRUCacheSize = 100
const CacheDB = 0

var log = base.Logger

func main() {
	var debug bool
	var configFile string
	flag.BoolVar(&debug, "d", false, "debug mode")
	flag.StringVar(&configFile, "c", "config.json", "specify config file")
	flag.Parse()
	//set global log level
	base.SetLogLevel(debug)
	//parse config
	config, err := ParseConfig(configFile)
	if err != nil {
		log.Fatal("a vailid json config file must exist")
	}
	if config.RedisDB == CacheDB {
		log.Fatal("redis db can not be same as cache db: '0'")
	}
	//init redis database pool
	redisPort := strconv.Itoa(config.RedisPort)
	redisServer := net.JoinHostPort(config.RedisAddress, redisPort)
	if !conn.Ping(redisServer, config.RedisPassword) {
		log.Fatal("connect to redis server failed")
	}
	conn.Pool = conn.NewPool(redisServer, config.RedisPassword, config.RedisDB)
	conn.CachePool = conn.NewPool(redisServer, config.RedisPassword, CacheDB)

	//init LRU cache and simple redis cache
	base.LRUCache = base.NewLRUCache(LRUCacheSize)
	base.Cache = base.NewCache()

	//init server
	n := negroni.New(
		negroni.NewRecovery(),
		negroni.NewLogger(),
		negroni.HandlerFunc(mw.CTypeMiddleware),
	)
	r := newRouter()
	n.UseHandler(r)
	srvPort := strconv.Itoa(config.Port)
	n.Run(net.JoinHostPort(config.Server, srvPort))
}
<commit_msg>remove stdlog<commit_after>package main

import (
	"flag"
	"github.com/evolsnow/negroni"
	"github.com/evolsnow/samaritan/base"
	"github.com/evolsnow/samaritan/conn"
	mw "github.com/evolsnow/samaritan/middleware"
	"net"
	"strconv"
)

const LRUCacheSize = 100
const CacheDB = 0

var log = base.Logger

func main() {
	var debug bool
	var configFile string
	flag.BoolVar(&debug, "d", false, "debug mode")
	flag.StringVar(&configFile, "c", "config.json", "specify config file")
	flag.Parse()
	//set global log level
	base.SetLogLevel(debug)
	//parse config
	config, err := ParseConfig(configFile)
	if err != nil {
		log.Fatal("a vailid json config file must exist")
	}
	if config.RedisDB == CacheDB {
		log.Fatal("redis db can not be same as cache db: '0'")
	}
	//init redis database pool
	redisPort := strconv.Itoa(config.RedisPort)
	redisServer := net.JoinHostPort(config.RedisAddress, redisPort)
	if !conn.Ping(redisServer, config.RedisPassword) {
		log.Fatal("connect to redis server failed")
	}
	conn.Pool = conn.NewPool(redisServer, config.RedisPassword, config.RedisDB)
	conn.CachePool = conn.NewPool(redisServer, config.RedisPassword, CacheDB)

	//init LRU cache and simple redis cache
	base.LRUCache = base.NewLRUCache(LRUCacheSize)
	base.Cache = base.NewCache()

	//init server
	n := negroni.New(
		negroni.NewRecovery(),
		negroni.NewLogger(),
		negroni.HandlerFunc(mw.CTypeMiddleware),
	)
	r := newRouter()
	n.UseHandler(r)
	srvPort := strconv.Itoa(config.Port)
	n.Run(net.JoinHostPort(config.Server, srvPort))
}
<|endoftext|>


<commit_before>package digo

var (
	GITCOMMIT string
)

const (
	VERSION                  = "0.1.2"
	IMAGE_UBUNTU_13_04_64BIT = 350076
	REGION_SF1               = 3
	SIZE_512M                = 66
)
<commit_msg>version 0.1.3<commit_after>package digo

var (
	GITCOMMIT string
)

const (
	VERSION                  = "0.1.3"
	IMAGE_UBUNTU_13_04_64BIT = 350076
	REGION_SF1               = 3
	SIZE_512M                = 66
)
<|endoftext|>


<commit_before>package main

import (
	"os"
	"strings"
	"time"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	log "github.com/Sirupsen/logrus"
	"github.com/bmuller/arrow/lib"
	"github.com/carlescere/scheduler"
	"github.com/gin-gonic/contrib/ginrus"
	"github.com/gin-gonic/gin"
	"github.com/nlopes/slack"
)

const (
	dbName string = "working"
)

type Item struct {
	ID        bson.ObjectId `json:"id" bson:"_id"`
	UserID    string        `json:"user_id" bson:"user_id"`
	Name      string        `json:"user_name" bson:"user_name"`
	Text      string        `json:"text" bson:"text"`
	CreatedAt time.Time     `json:"created_at" bson:"created_at"`
}

func main() {

	// Read configuration from file and env
	port := os.Getenv("PORT")

	// Setup schedule jobs
	digestJob := postDigest
	scheduler.Every().Day().At("09:30").Run(digestJob)

	// Prepare router
	router := gin.New()
	router.Use(ginrus.Ginrus(log.StandardLogger(), time.RFC3339, true))
	router.POST("/on", addItem)

	// Start server
	router.Run(":" + port)
}

// Message will be passed to server with '-' prefix via various way
//	+ Direct message with the bots
//	+ Use slash command `/working <message>`
//	+ Use cli `working-on <message>`
//	+ Might use Chrome plugin
//	+ ...
// Token is secondary param to indicate the user
func addItem(ctx *gin.Context) {

	// Parse token and message
	var item Item
	if ctx.Bind(&item) != nil {
		log.Fatal("Cannot parse data")
	}

	item.ID = bson.NewObjectId()
	item.CreatedAt = time.Now()

	message := strings.TrimSpace(item.Text)
	if !strings.HasPrefix(message, "-") {
		panic("Wrong format: " + item.Text)
	}

	dbURL := os.Getenv("MONGOLAB_URI")
	if dbURL == "" {
		log.Fatal("No connection string provided")
		os.Exit(1)
	}

	session, err := mgo.Dial(dbURL)

	if err != nil {
		panic(err)
	}

	defer session.Close()

	// Add Item to database
	c := session.DB(dbName).C("items")
	err = c.Insert(item)
	if err != nil {
		log.Fatalln(err)
	}
}

// Post summary to Slack channel
func postDigest() {

	channel := "#general"
	botToken := os.Getenv("BOT_TOKEN")

	if botToken == "" {
		log.Fatal("No token provided")
		os.Exit(1)
	}

	s := slack.New(botToken)
	users, err := s.GetUsers()

	if err != nil {
		log.Fatal("Cannot get users")
		os.Exit(1)
	}

	dbURL := os.Getenv("MONGOLAB_URI")
	if dbURL == "" {
		log.Fatal("No connection string provided")
		os.Exit(1)
	}

	session, err := mgo.Dial(dbURL)

	if err != nil {
		panic(err)
	}

	defer session.Close()

	itemCollection := session.DB(dbName).C("items")

	// If count > 0, it means there is data to show
	count := 0
	title := "Yesterday I learnt"
	params := slack.PostMessageParameters{}
	fields := []slack.AttachmentField{}

	// Prepare attachment of done items
	for _, user := range users {

		if user.IsBot || user.Deleted {
			continue
		}

		// Query done items from Database
		var values string
		var items []Item
		err = itemCollection.Find(bson.M{"user_id": user.Id, "created_at": bson.M{"$gt": arrow.Yesterday()}}).All(&items)
		if err != nil {
			log.Fatal("Cannot query done items.")
			os.Exit(1)
		}

		if len(items) > 0 {
			count = count + 1
		}

		for _, item := range items {
			values = values + item.Text + "\n"
		}

		field := slack.AttachmentField{
			Title: user.Name,
			Value: values,
		}

		fields = append(fields, field)
	}

	params.Attachments = []slack.Attachment{
		slack.Attachment{
			Color:  "#7CD197",
			Fields: fields,
		},
	}

	if count > 0 {
		s.PostMessage(channel, title, params)
	}
}
<commit_msg>fix item<commit_after>package main

import (
	"fmt"
	"os"
	"strings"
	"time"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	log "github.com/Sirupsen/logrus"
	"github.com/bmuller/arrow/lib"
	"github.com/carlescere/scheduler"
	"github.com/gin-gonic/contrib/ginrus"
	"github.com/gin-gonic/gin"
	"github.com/nlopes/slack"
)

const (
	dbName string = "working"
)

type Item struct {
	ID        bson.ObjectId `json:"id" bson:"_id"`
	UserID    string        `json:"user_id" bson:"user_id"`
	Name      string        `json:"user_name" bson:"user_name"`
	Text      string        `json:"text" bson:"text"`
	CreatedAt time.Time     `json:"created_at" bson:"created_at"`
}

func main() {

	// Read configuration from file and env
	port := os.Getenv("PORT")

	// Setup schedule jobs
	digestJob := postDigest
	scheduler.Every().Day().At("09:30").Run(digestJob)

	// Prepare router
	router := gin.New()
	router.Use(ginrus.Ginrus(log.StandardLogger(), time.RFC3339, true))
	router.POST("/on", addItem)

	// Start server
	router.Run(":" + port)
}

// Message will be passed to server with '-' prefix via various way
//	+ Direct message with the bots
//	+ Use slash command `/working <message>`
//	+ Use cli `working-on <message>`
//	+ Might use Chrome plugin
//	+ ...
// Token is secondary param to indicate the user
func addItem(ctx *gin.Context) {

	// Parse token and message
	var item Item
	if ctx.Bind(&item) != nil {
		log.Fatal("Cannot parse data")
	}

	fmt.Printf("Message: %v\n", item)
	item.ID = bson.NewObjectId()
	item.CreatedAt = time.Now()

	message := strings.TrimSpace(item.Text)
	if !strings.HasPrefix(message, "-") {
		panic("Wrong format: " + item.Text)
	}

	dbURL := os.Getenv("MONGOLAB_URI")
	if dbURL == "" {
		log.Fatal("No connection string provided")
		os.Exit(1)
	}

	session, err := mgo.Dial(dbURL)

	if err != nil {
		panic(err)
	}

	defer session.Close()

	// Add Item to database
	c := session.DB(dbName).C("items")
	err = c.Insert(item)
	if err != nil {
		log.Fatalln(err)
	}
}

// Post summary to Slack channel
func postDigest() {

	channel := "#general"
	botToken := os.Getenv("BOT_TOKEN")

	if botToken == "" {
		log.Fatal("No token provided")
		os.Exit(1)
	}

	s := slack.New(botToken)
	users, err := s.GetUsers()

	if err != nil {
		log.Fatal("Cannot get users")
		os.Exit(1)
	}

	dbURL := os.Getenv("MONGOLAB_URI")
	if dbURL == "" {
		log.Fatal("No connection string provided")
		os.Exit(1)
	}

	session, err := mgo.Dial(dbURL)

	if err != nil {
		panic(err)
	}

	defer session.Close()

	itemCollection := session.DB(dbName).C("items")

	// If count > 0, it means there is data to show
	count := 0
	title := "Yesterday I learnt"
	params := slack.PostMessageParameters{}
	fields := []slack.AttachmentField{}

	// Prepare attachment of done items
	for _, user := range users {

		if user.IsBot || user.Deleted {
			continue
		}

		// Query done items from Database
		var values string
		var items []Item
		err = itemCollection.Find(bson.M{"user_id": user.Id, "created_at": bson.M{"$gt": arrow.Yesterday()}}).All(&items)
		if err != nil {
			log.Fatal("Cannot query done items.")
			os.Exit(1)
		}

		if len(items) > 0 {
			count = count + 1
		}

		for _, item := range items {
			values = values + item.Text + "\n"
		}

		field := slack.AttachmentField{
			Title: user.Name,
			Value: values,
		}

		fields = append(fields, field)
	}

	params.Attachments = []slack.Attachment{
		slack.Attachment{
			Color:  "#7CD197",
			Fields: fields,
		},
	}

	if count > 0 {
		s.PostMessage(channel, title, params)
	}
}
<|endoftext|>


<commit_before>// Package alfred provides an API and various utility methods for creating
// Alfred workflows.
package alfred

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path"
	"strings"

	"github.com/jason0x43/go-plist"
)

//
// Public API
//

const (
	Line       = "––––––––––––––––––––––––––––––––––––––––––––––"
	Invalid    = "no"
	Valid      = "yes"
	Separator  = " »"
	Terminator = ""
)

const (
	FILTER_MENU = 0x01
)

var MaxResults = 9

type Results struct {
	items []Item
}

type Command interface {
	Keyword() string
	IsEnabled() bool
}

type Filter interface {
	Command
	MenuItem() Item
	Items(prefix, query string) ([]Item, error)
}

type Action interface {
	Command
	Do(query string) (string, error)
}

type Item struct {
	Uid           string
	Arg           string
	Title         string
	Subtitle      string
	SubtitleAll   string
	SubtitleShift string
	SubtitleAlt   string
	SubtitleCmd   string
	SubtitleCtrl  string
	SubtitleFn    string
	Icon          string
	Valid         string
	Autocomplete  string
}

type XmlItem struct {
	XMLName      xml.Name      `xml:"item"`
	Uid          string        `xml:"uid,attr,omitempty"`
	Title        string        `xml:"title"`
	Subtitles    []XmlSubtitle `xml:"subtitle,omitempty"`
	Icon         string        `xml:"icon,omitempty"`
	Arg          string        `xml:"arg,attr,omitempty"`
	Valid        string        `xml:"valid,attr,omitempty"`
	Autocomplete string        `xml:"autocomplete,attr,omitempty"`
}

type XmlSubtitle struct {
	Mod   string `xml:"mod,attr,omitempty"`
	Value string `xml:",chardata"`
}

// func (w *Workflow) Run(filters []Filter, actions []Action) {
func (w *Workflow) Run(commands []Command) {
	var op string
	var kind string
	var query string
	var err error

	log.Printf("args: %#v\n", os.Args)

	if len(os.Args) > 1 {
		op = os.Args[1]
	}

	if len(os.Args) > 3 {
		query = os.Args[3]
		kind = os.Args[2]
	} else if len(os.Args) > 2 {
		parts := strings.SplitN(os.Args[2], " ", 2)
		kind = parts[0]

		if len(parts) > 1 {
			query = parts[1]
		}
	}

	log.Printf("op: '%s'", op)
	log.Printf("kind: '%s'", kind)
	log.Printf("query: '%s'", query)

	var active []Command
	for _, c := range commands {
		if c.IsEnabled() {
			active = append(active, c)
		}
	}

	switch op {
	case "tell":
		var err error
		var items []Item
		filters := findFilter(kind, active)

		if len(filters) > 0 {
			var cmdItems []Item
			for _, f := range filters {
				cmdItems, err = f.Items(kind+" ", query)
				if err == nil {
					for _, i := range cmdItems {
						items = append(items, i)
					}
				}
			}
		} else {
			for _, f := range active {
				filter, ok := f.(Filter)
				if ok && strings.HasPrefix(f.Keyword(), kind) {
					item := filter.MenuItem()
					items = append(items, item)
				}
			}
		}

		if err != nil {
			log.Printf("Error: %s", err)
			items = append(items, Item{Title: fmt.Sprintf("Error: %s", err)})
		}

		SendToAlfred(items)

	case "do":
		var output string
		action := findAction(kind, active)

		if action == nil {
			err = fmt.Errorf("Unknown command '%s'", kind)
		} else {
			output, err = action.Do(query)
		}

		if err != nil {
			output = fmt.Sprintf("Error: %s", err)
		}

		if output != "" {
			fmt.Println(output)
		}

	default:
		fmt.Printf("Invalid operation: '%s'\n", op)
	}
}

func SplitQuery(query string) []string {
	return strings.Split(query, Separator)
}

func SplitAndTrimQuery(query string) []string {
	parts := SplitQuery(query)
	for i, part := range parts {
		parts[i] = strings.TrimSpace(part)
	}
	return parts
}

func InsertItem(items []Item, item Item, index int) []Item {
	items = append(items, item)
	copy(items[index+1:], items[index:])
	items[index] = item
	return items
}

func SendToAlfred(items []Item) {
	fmt.Println(ToXML(items))
}

func ToXML(items []Item) string {
	newxml := "<?xml version=\"1.0\"?><items>"

	for _, item := range items {
		xmlItem := XmlItem{
			Uid:          item.Uid,
			Arg:          item.Arg,
			Title:        item.Title,
			Icon:         item.Icon,
			Valid:        item.Valid,
			Autocomplete: item.Autocomplete,
		}

		getSubtitle := func(subtitle string) string {
			if subtitle != "" {
				return subtitle
			}
			return item.SubtitleAll
		}

		addSubtitle := func(subtitle, mod string) {
			if st := getSubtitle(subtitle); st != "" {
				xmlItem.Subtitles = append(xmlItem.Subtitles, XmlSubtitle{mod, st})
			}
		}

		addSubtitle(item.Subtitle, "")
		addSubtitle(item.SubtitleAlt, "alt")
		addSubtitle(item.SubtitleCmd, "cmd")
		addSubtitle(item.SubtitleCtrl, "ctrl")
		addSubtitle(item.SubtitleFn, "fn")
		addSubtitle(item.SubtitleShift, "shift")

		data, err := xml.Marshal(xmlItem)
		if err != nil {
			log.Fatalf("ToXML Error: %v\n", err)
		}
		newxml += string(data)
	}

	newxml += "</items>"
	return newxml
}

func FuzzyMatches(val string, test string) bool {
	if test == "" {
		return true
	}

	lval := strings.ToLower(val)
	ltest := strings.ToLower(test)

	words := strings.Split(ltest, " ")
	lastIndex := 0
	for _, word := range words {
		wi := strings.Index(lval, word)
		if wi < lastIndex {
			return false
		}
		lastIndex = wi
	}

	return true
}

type Workflow struct {
	name     string
	bundleId string
	cacheDir string
	dataDir  string
}

func OpenWorkflow(workflowDir string, createDirs bool) (*Workflow, error) {
	pl, err := plist.UnmarshalFile("info.plist")
	if err != nil {
		log.Println("alfred: Error opening plist:", err)
	}

	plData := pl.Root.(plist.Dict)
	bundleId := plData["bundleid"].(string)
	name := plData["name"].(string)

	user, err := user.Current()
	if err != nil {
		return nil, err
	}

	cacheDir := path.Join(user.HomeDir, "Library", "Caches", "com.runningwithcrayons.Alfred-2", "Workflow Data", bundleId)
	dataDir := path.Join(user.HomeDir, "Library", "Application Support", "Alfred 2", "Workflow Data", bundleId)

	if createDirs {
		err := os.MkdirAll(cacheDir, 0755)
		if err != nil {
			return nil, err
		}

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

	w := Workflow{
		name:     name,
		bundleId: bundleId,
		cacheDir: cacheDir,
		dataDir:  dataDir,
	}

	return &w, nil
}

func GetWorkflow() (*Workflow, error) {
	return OpenWorkflow(".", true)
}

func (w *Workflow) CacheDir() string {
	return w.cacheDir
}

func (w *Workflow) DataDir() string {
	return w.dataDir
}

func (w *Workflow) BundleId() string {
	return w.bundleId
}

func (w *Workflow) GetConfirmation(prompt string, defaultYes bool) (bool, error) {
	script :=
		`on run argv
		  tell application "Alfred 2"
			  activate
			  set alfredPath to (path to application "Alfred 2")
			  set alfredIcon to path to resource "appicon.icns" in bundle (alfredPath as alias)

			  try
				display dialog "%s" with title "%s" buttons {"Yes", "No"} default button "%s" with icon alfredIcon
				set answer to (button returned of result)
			  on error number -128
				set answer to "No"
			  end
		  end tell
		end run`
	var def string
	if defaultYes {
		def = "Yes"
	} else {
		def = "No"
	}
	script = fmt.Sprintf(script, prompt, w.name, def)
	answer, err := RunScript(script)
	if err != nil {
		return false, err
	}

	if strings.TrimSpace(answer) == "Yes" {
		return true, nil
	} else {
		return false, nil
	}
}

func (w *Workflow) GetInput(prompt, defaultVal string, hideAnswer bool) (button, value string, err error) {
	script :=
		`on run argv
		  tell application "Alfred 2"
			  activate
			  set alfredPath to (path to application "Alfred 2")
			  set alfredIcon to path to resource "appicon.icns" in bundle (alfredPath as alias)

			  try
				display dialog "%s:" with title "%s" default answer "%s" buttons {%s} default button "Ok" with icon alfredIcon %s
				set answer to (button returned of result) & "|" & (text returned of result)
			  on error number -128
				set answer to "Cancel|"
			  end
		  end tell
		end run`

	var hidden string
	if hideAnswer {
		hidden = "with hidden answer"
	}

	script = fmt.Sprintf(script, prompt, w.name, defaultVal, `"Cancel", "Ok"`, hidden)
	answer, err := RunScript(script)
	if err != nil {
		return button, value, err
	}

	answer = strings.TrimRight(answer, "\n")
	parts := strings.SplitN(answer, "|", 2)

	button = parts[0]
	if len(parts) > 1 {
		value = parts[1]
	}

	return button, value, err
}

func (w *Workflow) ShowMessage(message string) error {
	script :=
		`on run argv
		  tell application "Alfred 2"
			  activate
			  set alfredPath to (path to application "Alfred 2")
			  set alfredIcon to path to resource "appicon.icns" in bundle (alfredPath as alias)
			  display dialog "%s" with title "%s" buttons {"Ok"} default button "Ok" with icon alfredIcon
		  end tell
		end run`
	script = fmt.Sprintf(script, message, w.name)
	_, err := RunScript(script)
	return err
}

func LoadJson(filename string, structure interface{}) error {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}

	dec := json.NewDecoder(bytes.NewReader(data))
	return dec.Decode(&structure)
}

func SaveJson(filename string, structure interface{}) error {
	data, _ := json.MarshalIndent(structure, "", "\t")
	log.Println("Saving JSON to", filename)
	return ioutil.WriteFile(filename, data, 0600)
}

func RunScript(script string) (string, error) {
	cmd := exec.Command("osascript", "-")
	cmd.Stdin = strings.NewReader(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", err
	}
	return string(output), nil
}

type ByTitle []Item

func (b ByTitle) Len() int {
	return len(b)
}

func (b ByTitle) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}

func (b ByTitle) Less(i, j int) bool {
	return b[i].Title < b[j].Title
}

//
// Internal
//

var cacheRoot string
var dataRoot string

func (item *Item) fillSubtitles() {
	if item.SubtitleAlt == "" {
		item.SubtitleAlt = item.Subtitle
	}
	if item.SubtitleCmd == "" {
		item.SubtitleCmd = item.Subtitle
	}
	if item.SubtitleCtrl == "" {
		item.SubtitleCtrl = item.Subtitle
	}
	if item.SubtitleFn == "" {
		item.SubtitleFn = item.Subtitle
	}
	if item.SubtitleShift == "" {
		item.SubtitleShift = item.Subtitle
	}
}

func findFilter(name string, commands []Command) (f []Filter) {
	for _, c := range commands {
		filter, ok := c.(Filter)
		if ok && name == c.Keyword() {
			f = append(f, filter)
		}
	}
	return f
}

func findAction(name string, commands []Command) Action {
	for _, c := range commands {
		action, ok := c.(Action)
		if ok && name == c.Keyword() {
			return action
		}
	}
	return nil
}
<commit_msg>Use unicode literals rather than multibyte runes<commit_after>// Package alfred provides an API and various utility methods for creating
// Alfred workflows.
package alfred

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path"
	"strings"

	"github.com/jason0x43/go-plist"
)

//
// Public API
//

const (
	Line       = "––––––––––––––––––––––––––––––––––––––––––––––"
	Invalid    = "no"
	Valid      = "yes"
	Separator  = " \u00BB" // »
	Terminator = "\u200C"  // zero width joiner
)

const (
	FILTER_MENU = 0x01
)

var MaxResults = 9

type Results struct {
	items []Item
}

type Command interface {
	Keyword() string
	IsEnabled() bool
}

type Filter interface {
	Command
	MenuItem() Item
	Items(prefix, query string) ([]Item, error)
}

type Action interface {
	Command
	Do(query string) (string, error)
}

type Item struct {
	Uid           string
	Arg           string
	Title         string
	Subtitle      string
	SubtitleAll   string
	SubtitleShift string
	SubtitleAlt   string
	SubtitleCmd   string
	SubtitleCtrl  string
	SubtitleFn    string
	Icon          string
	Valid         string
	Autocomplete  string
}

type XmlItem struct {
	XMLName      xml.Name      `xml:"item"`
	Uid          string        `xml:"uid,attr,omitempty"`
	Title        string        `xml:"title"`
	Subtitles    []XmlSubtitle `xml:"subtitle,omitempty"`
	Icon         string        `xml:"icon,omitempty"`
	Arg          string        `xml:"arg,attr,omitempty"`
	Valid        string        `xml:"valid,attr,omitempty"`
	Autocomplete string        `xml:"autocomplete,attr,omitempty"`
}

type XmlSubtitle struct {
	Mod   string `xml:"mod,attr,omitempty"`
	Value string `xml:",chardata"`
}

// func (w *Workflow) Run(filters []Filter, actions []Action) {
func (w *Workflow) Run(commands []Command) {
	var op string
	var kind string
	var query string
	var err error

	log.Printf("args: %#v\n", os.Args)

	if len(os.Args) > 1 {
		op = os.Args[1]
	}

	if len(os.Args) > 3 {
		query = os.Args[3]
		kind = os.Args[2]
	} else if len(os.Args) > 2 {
		parts := strings.SplitN(os.Args[2], " ", 2)
		kind = parts[0]

		if len(parts) > 1 {
			query = parts[1]
		}
	}

	log.Printf("op: '%s'", op)
	log.Printf("kind: '%s'", kind)
	log.Printf("query: '%s'", query)

	var active []Command
	for _, c := range commands {
		if c.IsEnabled() {
			active = append(active, c)
		}
	}

	switch op {
	case "tell":
		var err error
		var items []Item
		filters := findFilter(kind, active)

		if len(filters) > 0 {
			var cmdItems []Item
			for _, f := range filters {
				cmdItems, err = f.Items(kind+" ", query)
				if err == nil {
					for _, i := range cmdItems {
						items = append(items, i)
					}
				}
			}
		} else {
			for _, f := range active {
				filter, ok := f.(Filter)
				if ok && strings.HasPrefix(f.Keyword(), kind) {
					item := filter.MenuItem()
					items = append(items, item)
				}
			}
		}

		if err != nil {
			log.Printf("Error: %s", err)
			items = append(items, Item{Title: fmt.Sprintf("Error: %s", err)})
		}

		SendToAlfred(items)

	case "do":
		var output string
		action := findAction(kind, active)

		if action == nil {
			err = fmt.Errorf("Unknown command '%s'", kind)
		} else {
			output, err = action.Do(query)
		}

		if err != nil {
			output = fmt.Sprintf("Error: %s", err)
		}

		if output != "" {
			fmt.Println(output)
		}

	default:
		fmt.Printf("Invalid operation: '%s'\n", op)
	}
}

func SplitQuery(query string) []string {
	return strings.Split(query, Separator)
}

func SplitAndTrimQuery(query string) []string {
	parts := SplitQuery(query)
	for i, part := range parts {
		parts[i] = strings.TrimSpace(part)
	}
	return parts
}

func InsertItem(items []Item, item Item, index int) []Item {
	items = append(items, item)
	copy(items[index+1:], items[index:])
	items[index] = item
	return items
}

func SendToAlfred(items []Item) {
	fmt.Println(ToXML(items))
}

func ToXML(items []Item) string {
	newxml := "<?xml version=\"1.0\"?><items>"

	for _, item := range items {
		xmlItem := XmlItem{
			Uid:          item.Uid,
			Arg:          item.Arg,
			Title:        item.Title,
			Icon:         item.Icon,
			Valid:        item.Valid,
			Autocomplete: item.Autocomplete,
		}

		getSubtitle := func(subtitle string) string {
			if subtitle != "" {
				return subtitle
			}
			return item.SubtitleAll
		}

		addSubtitle := func(subtitle, mod string) {
			if st := getSubtitle(subtitle); st != "" {
				xmlItem.Subtitles = append(xmlItem.Subtitles, XmlSubtitle{mod, st})
			}
		}

		addSubtitle(item.Subtitle, "")
		addSubtitle(item.SubtitleAlt, "alt")
		addSubtitle(item.SubtitleCmd, "cmd")
		addSubtitle(item.SubtitleCtrl, "ctrl")
		addSubtitle(item.SubtitleFn, "fn")
		addSubtitle(item.SubtitleShift, "shift")

		data, err := xml.Marshal(xmlItem)
		if err != nil {
			log.Fatalf("ToXML Error: %v\n", err)
		}
		newxml += string(data)
	}

	newxml += "</items>"
	return newxml
}

func FuzzyMatches(val string, test string) bool {
	if test == "" {
		return true
	}

	lval := strings.ToLower(val)
	ltest := strings.ToLower(test)

	words := strings.Split(ltest, " ")
	lastIndex := 0
	for _, word := range words {
		wi := strings.Index(lval, word)
		if wi < lastIndex {
			return false
		}
		lastIndex = wi
	}

	return true
}

type Workflow struct {
	name     string
	bundleId string
	cacheDir string
	dataDir  string
}

func OpenWorkflow(workflowDir string, createDirs bool) (*Workflow, error) {
	pl, err := plist.UnmarshalFile("info.plist")
	if err != nil {
		log.Println("alfred: Error opening plist:", err)
	}

	plData := pl.Root.(plist.Dict)
	bundleId := plData["bundleid"].(string)
	name := plData["name"].(string)

	user, err := user.Current()
	if err != nil {
		return nil, err
	}

	cacheDir := path.Join(user.HomeDir, "Library", "Caches", "com.runningwithcrayons.Alfred-2", "Workflow Data", bundleId)
	dataDir := path.Join(user.HomeDir, "Library", "Application Support", "Alfred 2", "Workflow Data", bundleId)

	if createDirs {
		err := os.MkdirAll(cacheDir, 0755)
		if err != nil {
			return nil, err
		}

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

	w := Workflow{
		name:     name,
		bundleId: bundleId,
		cacheDir: cacheDir,
		dataDir:  dataDir,
	}

	return &w, nil
}

func GetWorkflow() (*Workflow, error) {
	return OpenWorkflow(".", true)
}

func (w *Workflow) CacheDir() string {
	return w.cacheDir
}

func (w *Workflow) DataDir() string {
	return w.dataDir
}

func (w *Workflow) BundleId() string {
	return w.bundleId
}

func (w *Workflow) GetConfirmation(prompt string, defaultYes bool) (bool, error) {
	script :=
		`on run argv
		  tell application "Alfred 2"
			  activate
			  set alfredPath to (path to application "Alfred 2")
			  set alfredIcon to path to resource "appicon.icns" in bundle (alfredPath as alias)

			  try
				display dialog "%s" with title "%s" buttons {"Yes", "No"} default button "%s" with icon alfredIcon
				set answer to (button returned of result)
			  on error number -128
				set answer to "No"
			  end
		  end tell
		end run`
	var def string
	if defaultYes {
		def = "Yes"
	} else {
		def = "No"
	}
	script = fmt.Sprintf(script, prompt, w.name, def)
	answer, err := RunScript(script)
	if err != nil {
		return false, err
	}

	if strings.TrimSpace(answer) == "Yes" {
		return true, nil
	} else {
		return false, nil
	}
}

func (w *Workflow) GetInput(prompt, defaultVal string, hideAnswer bool) (button, value string, err error) {
	script :=
		`on run argv
		  tell application "Alfred 2"
			  activate
			  set alfredPath to (path to application "Alfred 2")
			  set alfredIcon to path to resource "appicon.icns" in bundle (alfredPath as alias)

			  try
				display dialog "%s:" with title "%s" default answer "%s" buttons {%s} default button "Ok" with icon alfredIcon %s
				set answer to (button returned of result) & "|" & (text returned of result)
			  on error number -128
				set answer to "Cancel|"
			  end
		  end tell
		end run`

	var hidden string
	if hideAnswer {
		hidden = "with hidden answer"
	}

	script = fmt.Sprintf(script, prompt, w.name, defaultVal, `"Cancel", "Ok"`, hidden)
	answer, err := RunScript(script)
	if err != nil {
		return button, value, err
	}

	answer = strings.TrimRight(answer, "\n")
	parts := strings.SplitN(answer, "|", 2)

	button = parts[0]
	if len(parts) > 1 {
		value = parts[1]
	}

	return button, value, err
}

func (w *Workflow) ShowMessage(message string) error {
	script :=
		`on run argv
		  tell application "Alfred 2"
			  activate
			  set alfredPath to (path to application "Alfred 2")
			  set alfredIcon to path to resource "appicon.icns" in bundle (alfredPath as alias)
			  display dialog "%s" with title "%s" buttons {"Ok"} default button "Ok" with icon alfredIcon
		  end tell
		end run`
	script = fmt.Sprintf(script, message, w.name)
	_, err := RunScript(script)
	return err
}

func LoadJson(filename string, structure interface{}) error {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}

	dec := json.NewDecoder(bytes.NewReader(data))
	return dec.Decode(&structure)
}

func SaveJson(filename string, structure interface{}) error {
	data, _ := json.MarshalIndent(structure, "", "\t")
	log.Println("Saving JSON to", filename)
	return ioutil.WriteFile(filename, data, 0600)
}

func RunScript(script string) (string, error) {
	cmd := exec.Command("osascript", "-")
	cmd.Stdin = strings.NewReader(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", err
	}
	return string(output), nil
}

type ByTitle []Item

func (b ByTitle) Len() int {
	return len(b)
}

func (b ByTitle) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}

func (b ByTitle) Less(i, j int) bool {
	return b[i].Title < b[j].Title
}

//
// Internal
//

var cacheRoot string
var dataRoot string

func (item *Item) fillSubtitles() {
	if item.SubtitleAlt == "" {
		item.SubtitleAlt = item.Subtitle
	}
	if item.SubtitleCmd == "" {
		item.SubtitleCmd = item.Subtitle
	}
	if item.SubtitleCtrl == "" {
		item.SubtitleCtrl = item.Subtitle
	}
	if item.SubtitleFn == "" {
		item.SubtitleFn = item.Subtitle
	}
	if item.SubtitleShift == "" {
		item.SubtitleShift = item.Subtitle
	}
}

func findFilter(name string, commands []Command) (f []Filter) {
	for _, c := range commands {
		filter, ok := c.(Filter)
		if ok && name == c.Keyword() {
			f = append(f, filter)
		}
	}
	return f
}

func findAction(name string, commands []Command) Action {
	for _, c := range commands {
		action, ok := c.(Action)
		if ok && name == c.Keyword() {
			return action
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path"
	"strings"

	homedir "github.com/mitchellh/go-homedir"
)

var debug = flag.Bool("debug", false, "debug mode")

func DebugLogf(s string, v ...interface{}) {
	if *debug {
		log.Printf(s, v...)
	}
}

func Fatal(e error) {
	fmt.Printf("goprompt-err(%s)", e)
	os.Exit(1)
}

var collapsablePrefixes = map[string]string{
	"~/Code/go/src/": "",
}

func main() {
	flag.Parse()
	var prompt string

	cwd, err := os.Getwd()
	if err != nil {
		Fatal(err)
	}
	displayPath := cwd

	// home prefix
	home, err := homedir.Dir()
	if err != nil {
		Fatal(err)
	}
	if strings.HasPrefix(displayPath, home) {
		displayPath = "~" + strings.TrimPrefix(displayPath, home)
	}

	// other collapsable prefixes
	for k, v := range collapsablePrefixes {
		if strings.HasPrefix(displayPath, k) {
			displayPath = v + strings.TrimPrefix(displayPath, k)
		}
	}
	prompt += displayPath

	// git
	isGit := false
	ccwd := cwd
	for {
		if path.Base(ccwd) == ".git" {
			DebugLogf("In git dir: %s", ccwd)
			prompt += "\x1b[31;1m" // red
			prompt += "[GIT DIR!]"
			prompt += "\x1b[0m"
			break
		}
		gitPath := path.Join(ccwd, ".git")
		DebugLogf("Checking for %s", gitPath)
		s, err := os.Stat(gitPath)
		if err == nil && s.IsDir() {
			isGit = true
			DebugLogf("Found git dir: %s", gitPath)
			break
		}
		if ccwd == "/" {
			break
		}
		ccwd = path.Dir(ccwd)
	}
	if isGit {
		cmd := exec.Command("git", "status", "--porcelain", "--branch")
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			Fatal(err)
		}
		if err := cmd.Start(); err != nil {
			Fatal(err)
		}
		reader := bufio.NewReader(stdout)
		branch, err := reader.ReadString('\n')
		if err != nil {
			Fatal(err)
		}
		branch = strings.TrimPrefix(branch, "## ")
		branch = strings.SplitN(branch, "...", 2)[0]
		branch = strings.TrimSpace(branch)

		scanner := bufio.NewScanner(reader)
		untracked := false
		isDirty := false   // work tree != index
		isPending := false // index != HEAD
		for scanner.Scan() {
			line := string(scanner.Text())
			if strings.HasPrefix(line, "?? ") {
				untracked = true
				continue
			}
			if line[0] != ' ' {
				isPending = true
			}
			if line[1] != ' ' {
				isDirty = true
			}
		}
		prompt += ":"
		colorCode := ""
		switch {
		case isPending:
			colorCode = "\x1b[33;1m" // yellow
		case isDirty:
			colorCode = "\x1b[31;1m" // red
		default:
			colorCode = "\x1b[32;1m" // green
		}
		prompt += colorCode
		prompt += branch
		if untracked {
			prompt += "+?"
		}
	}

	// clear formatting for sure so we don't mess up the terminal
	prompt += "\x1b[0m"

	fmt.Printf("%s", prompt)
}
<commit_msg>untracked files mean dirty working tree<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path"
	"strings"

	homedir "github.com/mitchellh/go-homedir"
)

var debug = flag.Bool("debug", false, "debug mode")

func DebugLogf(s string, v ...interface{}) {
	if *debug {
		log.Printf(s, v...)
	}
}

func Fatal(e error) {
	fmt.Printf("goprompt-err(%s)", e)
	os.Exit(1)
}

var collapsablePrefixes = map[string]string{
	"~/Code/go/src/": "",
}

func main() {
	flag.Parse()
	var prompt string

	cwd, err := os.Getwd()
	if err != nil {
		Fatal(err)
	}
	displayPath := cwd

	// home prefix
	home, err := homedir.Dir()
	if err != nil {
		Fatal(err)
	}
	if strings.HasPrefix(displayPath, home) {
		displayPath = "~" + strings.TrimPrefix(displayPath, home)
	}

	// other collapsable prefixes
	for k, v := range collapsablePrefixes {
		if strings.HasPrefix(displayPath, k) {
			displayPath = v + strings.TrimPrefix(displayPath, k)
		}
	}
	prompt += displayPath

	// git
	isGit := false
	ccwd := cwd
	for {
		if path.Base(ccwd) == ".git" {
			DebugLogf("In git dir: %s", ccwd)
			prompt += "\x1b[31;1m" // red
			prompt += "[GIT DIR!]"
			prompt += "\x1b[0m"
			break
		}
		gitPath := path.Join(ccwd, ".git")
		DebugLogf("Checking for %s", gitPath)
		s, err := os.Stat(gitPath)
		if err == nil && s.IsDir() {
			isGit = true
			DebugLogf("Found git dir: %s", gitPath)
			break
		}
		if ccwd == "/" {
			break
		}
		ccwd = path.Dir(ccwd)
	}
	if isGit {
		cmd := exec.Command("git", "status", "--porcelain", "--branch")
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			Fatal(err)
		}
		if err := cmd.Start(); err != nil {
			Fatal(err)
		}
		reader := bufio.NewReader(stdout)
		branch, err := reader.ReadString('\n')
		if err != nil {
			Fatal(err)
		}
		branch = strings.TrimPrefix(branch, "## ")
		branch = strings.SplitN(branch, "...", 2)[0]
		branch = strings.TrimSpace(branch)

		scanner := bufio.NewScanner(reader)
		untracked := false
		isDirty := false   // work tree != index
		isPending := false // index != HEAD
		for scanner.Scan() {
			line := string(scanner.Text())
			if strings.HasPrefix(line, "?? ") {
				untracked = true
				continue
			}
			if line[0] != ' ' {
				isPending = true
			}
			if line[1] != ' ' {
				isDirty = true
			}
		}
		prompt += ":"
		colorCode := ""
		switch {
		case isPending:
			colorCode = "\x1b[33;1m" // yellow
		case isDirty:
			colorCode = "\x1b[31;1m" // red
		case untracked:
			colorCode = "\x1b[31;1m" // red
		default:
			colorCode = "\x1b[32;1m" // green
		}
		prompt += colorCode
		prompt += branch
		if untracked {
			prompt += "+?"
		}
	}

	// clear formatting for sure so we don't mess up the terminal
	prompt += "\x1b[0m"

	fmt.Printf("%s", prompt)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/skratchdot/open-golang/open"
)

func in(slice []string, str string) bool {
	for _, s := range slice {
		if s == str {
			return true
		}
	}
	return false
}

var acceptedImageExt = []string{".jpg", ".jpeg"}
var dirThumbs = fmt.Sprintf("%s%s", os.Getenv("HOME"), "/.cache/lk")
var dirPath = "."
var gitVersion string
var showVersionFlag = flag.Bool("version", false, "Show version")
var port = flag.Int("port", 0, "listen port")

func hostname() string {
	hostname, _ := os.Hostname()
	if strings.Split(hostname, ".")[0] == hostname {
		return hostname + ".local"
	}
	return hostname
}

func main() {

	flag.Parse()

	if *showVersionFlag {
		log.Println("lk", gitVersion, "https://github.com/kaihendry/lk")
		os.Exit(0)
	}

	directory := flag.Arg(0)
	dirPath, _ = filepath.Abs(directory)

	// Don't allow path under dirPath to be viewed
	http.Handle("/o/", http.StripPrefix(path.Join("/o", dirPath), http.FileServer(http.Dir(dirPath))))
	http.HandleFunc("/favicon.ico", http.NotFound)

	http.HandleFunc("/", lk)
	http.HandleFunc("/t/", thumb)

	// http://stackoverflow.com/a/33985208/4534
	ln, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Panic(err)
	}

	if a, ok := ln.Addr().(*net.TCPAddr); ok {
		hostname := hostname()
		host := fmt.Sprintf("http://%s:%d", hostname, a.Port)
		log.Println("Serving from", host)
		open.Start(host)
	}
	if err := http.Serve(ln, nil); err != nil {
		log.Panic(err)
	}

}

func thumb(w http.ResponseWriter, r *http.Request) {

	// Make sure you can't go under the dirPath
	if !strings.HasPrefix(r.URL.Path[2:], dirPath) {
		http.NotFound(w, r)
		return
	}

	thumbPath := filepath.Join(dirThumbs, r.URL.Path[2:])
	if _, err := os.Stat(thumbPath); err != nil {
		log.Println("THUMB:", thumbPath, "does not exist")
		srcPath := r.URL.Path[2:]
		if _, err := os.Stat(srcPath); err != nil {
			log.Println("ORIGINAL", srcPath, "does not exist")
			http.NotFound(w, r)
			return
		} else {
			log.Println("Must generate thumb for", srcPath)
			err := genthumb(srcPath, thumbPath)
			if err != nil {
				http.Error(w, err.Error(), 400)
				return
			}
			log.Println("Created thumb", thumbPath)
		}
	}
	log.Println("Serving thumb", thumbPath)
	http.ServeFile(w, r, thumbPath)
}

func lk(w http.ResponseWriter, r *http.Request) {

	// log.Println("dirPath", dirPath, "Web path", r.URL.Path)
	srcPath := filepath.Join(dirPath, r.URL.Path)
	// log.Println("Combined", srcPath)

	files, err := ioutil.ReadDir(srcPath)
	// log.Println(files)

	if err != nil {
		panic(err)
	}

	dirs := []string{}
	images := []string{}

	for _, f := range files {

		if strings.HasPrefix(filepath.Base(f.Name()), ".") {
			continue
		}

		// log.Println("Filename", f.Name())
		if f.IsDir() {
			// log.Println(f.Name(), "is a directory")
			// TODO check they have a JPG in them?
			dirs = append(dirs, filepath.Join(r.URL.Path, f.Name()))
		}
		// Only append jpg images
		if in(acceptedImageExt, strings.ToLower(path.Ext(f.Name()))) {
			log.Printf("Appending %s", f.Name())
			images = append(images, filepath.Join(srcPath, f.Name()))
		}
	}

	t, err := template.New("foo").Parse(`<!DOCTYPE html>

<meta charset="utf-8" />

{{ range .Dirs }}
{{ . }}

{{ end }}

{{ range .Images }}
{{ end }}

By lk {{ .Version }}

`)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	data := struct {
		Images  []string
		Dirs    []string
		Version string
	}{
		images,
		dirs,
		gitVersion,
	}

	t.Execute(w, data)

	log.Printf("%s %s %s %s\n", r.RemoteAddr, r.Method, r.URL, r.UserAgent())

}
<commit_msg>Comment<commit_after>package main

import (
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/skratchdot/open-golang/open"
)

func in(slice []string, str string) bool {
	for _, s := range slice {
		if s == str {
			return true
		}
	}
	return false
}

var acceptedImageExt = []string{".jpg", ".jpeg"}
var dirThumbs = fmt.Sprintf("%s%s", os.Getenv("HOME"), "/.cache/lk")
var dirPath = "."
var gitVersion string
var showVersionFlag = flag.Bool("version", false, "Show version")
var port = flag.Int("port", 0, "listen port")

func hostname() string {
	hostname, _ := os.Hostname()
	// If hostname does not have dots (i.e. not fully qualified), then return zeroconf address for LAN browsing
	if strings.Split(hostname, ".")[0] == hostname {
		return hostname + ".local"
	}
	return hostname
}

func main() {

	flag.Parse()

	if *showVersionFlag {
		log.Println("lk", gitVersion, "https://github.com/kaihendry/lk")
		os.Exit(0)
	}

	directory := flag.Arg(0)
	dirPath, _ = filepath.Abs(directory)

	// Don't allow path under dirPath to be viewed
	http.Handle("/o/", http.StripPrefix(path.Join("/o", dirPath), http.FileServer(http.Dir(dirPath))))
	http.HandleFunc("/favicon.ico", http.NotFound)

	http.HandleFunc("/", lk)
	http.HandleFunc("/t/", thumb)

	// http://stackoverflow.com/a/33985208/4534
	ln, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Panic(err)
	}

	if a, ok := ln.Addr().(*net.TCPAddr); ok {
		host := fmt.Sprintf("http://%s:%d", hostname(), a.Port)
		log.Println("Serving from", host)
		open.Start(host)
	}
	if err := http.Serve(ln, nil); err != nil {
		log.Panic(err)
	}

}

func thumb(w http.ResponseWriter, r *http.Request) {

	// Make sure you can't go under the dirPath
	if !strings.HasPrefix(r.URL.Path[2:], dirPath) {
		http.NotFound(w, r)
		return
	}

	thumbPath := filepath.Join(dirThumbs, r.URL.Path[2:])
	if _, err := os.Stat(thumbPath); err != nil {
		log.Println("THUMB:", thumbPath, "does not exist")
		srcPath := r.URL.Path[2:]
		if _, err := os.Stat(srcPath); err != nil {
			log.Println("ORIGINAL", srcPath, "does not exist")
			http.NotFound(w, r)
			return
		} else {
			log.Println("Must generate thumb for", srcPath)
			err := genthumb(srcPath, thumbPath)
			if err != nil {
				http.Error(w, err.Error(), 400)
				return
			}
			log.Println("Created thumb", thumbPath)
		}
	}
	log.Println("Serving thumb", thumbPath)
	http.ServeFile(w, r, thumbPath)
}

func lk(w http.ResponseWriter, r *http.Request) {

	// log.Println("dirPath", dirPath, "Web path", r.URL.Path)
	srcPath := filepath.Join(dirPath, r.URL.Path)
	// log.Println("Combined", srcPath)

	files, err := ioutil.ReadDir(srcPath)
	// log.Println(files)

	if err != nil {
		panic(err)
	}

	dirs := []string{}
	images := []string{}

	for _, f := range files {

		if strings.HasPrefix(filepath.Base(f.Name()), ".") {
			continue
		}

		// log.Println("Filename", f.Name())
		if f.IsDir() {
			// log.Println(f.Name(), "is a directory")
			// TODO check they have a JPG in them?
			dirs = append(dirs, filepath.Join(r.URL.Path, f.Name()))
		}
		// Only append jpg images
		if in(acceptedImageExt, strings.ToLower(path.Ext(f.Name()))) {
			log.Printf("Appending %s", f.Name())
			images = append(images, filepath.Join(srcPath, f.Name()))
		}
	}

	t, err := template.New("foo").Parse(`<!DOCTYPE html>

<meta charset="utf-8" />

{{ range .Dirs }}
{{ . }}

{{ end }}

{{ range .Images }}
{{ end }}

By lk {{ .Version }}

`)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	data := struct {
		Images  []string
		Dirs    []string
		Version string
	}{
		images,
		dirs,
		gitVersion,
	}

	t.Execute(w, data)

	log.Printf("%s %s %s %s\n", r.RemoteAddr, r.Method, r.URL, r.UserAgent())

}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"time"

	"github.com/sambaiz/sendjson/input"
	"github.com/urfave/cli"
)

func main() {
	var (
		interval, duration time.Duration
		url, method        string
		check              bool
	)

	app := cli.NewApp()

	app.Flags = []cli.Flag{
		cli.DurationFlag{
			Name:        "interval, i",
			Value:       1 * time.Second,
			Usage:       "send interval",
			Destination: &interval,
		},
		cli.DurationFlag{
			Name:        "duration, d",
			Value:       60 * time.Second,
			Usage:       "send duration",
			Destination: &duration,
		},
		cli.StringFlag{
			Name:        "url, u",
			Value:       "http://127.0.0.1",
			Usage:       "send to url",
			Destination: &url,
		},
		cli.StringFlag{
			Name:        "method, m",
			Value:       "POST",
			Usage:       "send method; POST, PUT etc.",
			Destination: &method,
		},
		cli.BoolFlag{
			Name:        "check",
			Usage:       "for checking output without sending",
			Destination: &check,
		},
	}

	app.Action = func(c *cli.Context) error {

		var input string
		if c.NArg() > 0 {
			input = c.Args()[0]
		} else {
			panic("json input required")
		}

		ticker := time.NewTicker(interval)

		go func() {
			for {
				select {
				case <-ticker.C:
					go func() {
						if b, err := generateJSON([]byte(input)); err != nil {
							panic(err)
						} else if check {
							fmt.Printf("not sended: %v\n", string(b))
						} else if err := send(method, url, b); err != nil {
							panic(err)
						} else {
							fmt.Printf("%v\n", string(b))
						}
					}()
				}
			}
		}()

		time.Sleep(duration)
		ticker.Stop()
		return nil
	}

	app.Run(os.Args)
}

func generateJSON(inbyte []byte) ([]byte, error) {

	var input map[string]input.Input
	output := map[string]interface{}{}

	if err := json.Unmarshal(inbyte, &input); err != nil {
		return nil, err
	}

	for k, v := range input {
		ev := v.Eval()
		if ev == nil {
			return nil, fmt.Errorf("eval %v then null!", k)
		}
		output[k] = ev
	}

	outbyte, err := json.Marshal(output)
	return outbyte, err
}

func send(method, url string, data []byte) error {
	req, err := http.NewRequest(method, url, bytes.NewReader(data))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode/100 != 2 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return err
		} else {
			fmt.Printf("[send error] status code: %d, response: %s\n", res.StatusCode, string(body))
		}
	}

	return nil
}
<commit_msg>add app info<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"time"

	"github.com/sambaiz/sendjson/input"
	"github.com/urfave/cli"
)

func main() {
	var (
		interval, duration time.Duration
		url, method        string
		check              bool
	)

	app := cli.NewApp()
	app.Name = "sendjson"
	app.Usage = "generate and send json continuously"
	app.Version = "0.5.0"
	app.Authors = []cli.Author{
		cli.Author{
			Name: "sambaiz",
		},
	}

	app.Flags = []cli.Flag{
		cli.DurationFlag{
			Name:        "interval, i",
			Value:       1 * time.Second,
			Usage:       "send interval",
			Destination: &interval,
		},
		cli.DurationFlag{
			Name:        "duration, d",
			Value:       60 * time.Second,
			Usage:       "send duration",
			Destination: &duration,
		},
		cli.StringFlag{
			Name:        "url, u",
			Value:       "http://127.0.0.1",
			Usage:       "send to url",
			Destination: &url,
		},
		cli.StringFlag{
			Name:        "method, m",
			Value:       "POST",
			Usage:       "send method; POST, PUT etc.",
			Destination: &method,
		},
		cli.BoolFlag{
			Name:        "check",
			Usage:       "for checking output without sending",
			Destination: &check,
		},
	}

	app.Action = func(c *cli.Context) error {

		var input string
		if c.NArg() > 0 {
			input = c.Args()[0]
		} else {
			panic("json input required")
		}

		ticker := time.NewTicker(interval)

		go func() {
			for {
				select {
				case <-ticker.C:
					go func() {
						if b, err := generateJSON([]byte(input)); err != nil {
							panic(err)
						} else if check {
							fmt.Printf("not sended: %v\n", string(b))
						} else if err := send(method, url, b); err != nil {
							panic(err)
						} else {
							fmt.Printf("%v\n", string(b))
						}
					}()
				}
			}
		}()

		time.Sleep(duration)
		ticker.Stop()
		return nil
	}

	app.Run(os.Args)
}

func generateJSON(inbyte []byte) ([]byte, error) {

	var input map[string]input.Input
	output := map[string]interface{}{}

	if err := json.Unmarshal(inbyte, &input); err != nil {
		return nil, err
	}

	for k, v := range input {
		ev := v.Eval()
		if ev == nil {
			return nil, fmt.Errorf("eval %v then null!", k)
		}
		output[k] = ev
	}

	outbyte, err := json.Marshal(output)
	return outbyte, err
}

func send(method, url string, data []byte) error {
	req, err := http.NewRequest(method, url, bytes.NewReader(data))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode/100 != 2 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return err
		} else {
			fmt.Printf("[send error] status code: %d, response: %s\n", res.StatusCode, string(body))
		}
	}

	return nil
}
<|endoftext|>