<commit_before>package service

import (
	"github.com/jalle19/upcloud-go-sdk/upcloud"
	"github.com/jalle19/upcloud-go-sdk/upcloud/client"
	"github.com/jalle19/upcloud-go-sdk/upcloud/request"
	"os"
	"testing"
	"time"
)

// The service object used by the tests
var svc *Service

/**
Configures the test environment
*/
func init() {
	user, password := getCredentials()

	c := client.New(user, password)
	c.SetTimeout(time.Second * 30)
	svc = New(c)
}

/**
TestCreateModifyDeleteServer performs the following actions:

- creates a server
- modifies the server
- stops the server
- deletes the server

*/
func TestCreateModifyDeleteServer(t *testing.T) {
	t.Parallel()

	// Create a server
	serverDetails := createServer()
	t.Logf("Server %s with UUID %s created", serverDetails.Title, serverDetails.UUID)

	// Modify the server
	t.Log("Modifying the server ...")

	serverDetails, err := svc.ModifyServer(&request.ModifyServerRequest{
		UUID:  serverDetails.UUID,
		Title: "Modified server",
	})

	handleError(err)
	t.Log("Waiting for the server to exit maintenance state ...")

	err = svc.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         serverDetails.UUID,
		DesiredState: upcloud.ServerStateStarted,
		Timeout:      time.Minute * 5,
	})

	handleError(err)
	t.Logf("Server is now modified, new title is %s", serverDetails.Title)

	// Stop the server
	t.Logf("Stopping server with UUID %s ...", serverDetails.UUID)
	stopServer(serverDetails.UUID)
	t.Log("Server is now stopped")

	// Delete the server
	t.Log("Deleting the server ...")
	deleteServer(serverDetails.UUID)
	t.Log("Server is now deleted")
}

/**
TestCreateModifyDeleteStorage performs the following actions:

- creates a new storage disk
- modifies the storage
- deletes the storage

*/
func TestCreateModifyDeleteStorage(t *testing.T) {
	t.Parallel()

	// Create some storage
	storageDetails := createStorage()
	t.Logf("Storage %s with UUID %s created", storageDetails.Title, storageDetails.UUID)

	// Modify the storage
	t.Log("Modifying the storage ...")

	storageDetails, err := svc.ModifyStorage(&request.ModifyStorageRequest{
		UUID:  storageDetails.UUID,
		Title: "New fancy title",
	})

	handleError(err)
	t.Logf("Storage with UUID %s modified successfully, new title is %s", storageDetails.UUID, storageDetails.Title)

	// Delete the storage
	t.Log("Deleting the storage ...")
	deleteStorage(storageDetails.UUID)
	t.Log("Storage is now deleted")
}

/**
TestAttachDetachStorage performs the following actions:

- creates a server
- stops the server
- creates a new storage disk
- attaches the storage
- detaches the storage
- deletes the storage
- deletes the server

*/
func TestAttachDetachStorage(t *testing.T) {
	t.Parallel()

	// Create a server
	serverDetails := createServer()
	t.Logf("Server %s with UUID %s created", serverDetails.Title, serverDetails.UUID)

	// Stop the server
	t.Logf("Stopping server with UUID %s ...", serverDetails.UUID)
	stopServer(serverDetails.UUID)
	t.Log("Server is now stopped")

	// Create some storage
	storageDetails := createStorage()
	t.Logf("Storage %s with UUID %s created", storageDetails.Title, storageDetails.UUID)

	// Attach the storage
	t.Logf("Attaching storage %s", storageDetails.UUID)

	serverDetails, err := svc.AttachStorage(&request.AttachStorageRequest{
		StorageUUID: storageDetails.UUID,
		ServerUUID:  serverDetails.UUID,
		Type:        upcloud.StorageTypeDisk,
		Address:     "scsi:0:0",
	})

	handleError(err)
	t.Logf("Storage attached to server with UUID %s", serverDetails.UUID)

	// Detach the storage
	t.Logf("Detaching storage %s", storageDetails.UUID)

	serverDetails, err = svc.DetachStorage(&request.DetachStorageRequest{
		ServerUUID: serverDetails.UUID,
		Address:    "scsi:0:0",
	})

	handleError(err)
	t.Logf("Storage %s detached", storageDetails.UUID)

	// Delete the storage
	t.Log("Deleting the storage ...")
	deleteStorage(storageDetails.UUID)
	t.Log("Storage is now deleted")

	// Delete the server
	t.Log("Deleting the server ...")
	deleteServer(serverDetails.UUID)
	t.Log("Server is now deleted")
}

/**
TestCloneStorage performs the following actions:

- creates a storage device
- clones the storage device
- deletes the clone and the storage device

*/
func TestCloneStorage(t *testing.T) {
	// Create storage
	storageDetails := createStorage()
	t.Logf("Storage %s with UUID %s created", storageDetails.Title, storageDetails.UUID)

	// Clone the storage
	t.Log("Cloning storage ...")

	clonedStorageDetails, err := svc.CloneStorage(&request.CloneStorageRequest{
		UUID:  storageDetails.UUID,
		Title: "Cloned storage",
		Zone:  "fi-hel1",
		Tier:  upcloud.StorageTierMaxIOPS,
	})

	handleError(err)
	waitForStorageOnline(clonedStorageDetails.UUID)
	t.Logf("Storage cloned as %s", clonedStorageDetails.UUID)

	// Delete both storage devices
	t.Log("Deleting the storage ...")
	deleteStorage(storageDetails.UUID)
	t.Log("Storage is now deleted")

	t.Log("Deleting the cloned storage ...")
	deleteStorage(clonedStorageDetails.UUID)
	t.Log("Cloned storage is now deleted")

	err = svc.DeleteStorage(&request.DeleteStorageRequest{
		UUID: clonedStorageDetails.UUID,
	})

	handleError(err)
	t.Log("Cloned storage is now deleted")
}

/**
TestLoadEjectCDROM performs the following actions:

- creates a server
- stops the server
- attaches a CD-ROM device
- starts the server
- loads a CD-ROM
- ejects the CD-ROM
- stops the server
- deletes the server

*/
func TestLoadEjectCDROM(t *testing.T) {
	t.Parallel()

	// Create the server
	serverDetails := createServer()
	t.Logf("Server %s with UUID %s created", serverDetails.Title, serverDetails.UUID)

	// Stop the server
	t.Logf("Stopping server with UUID %s ...", serverDetails.UUID)
	stopServer(serverDetails.UUID)
	t.Log("Server is now stopped")

	// Attach CD-ROM device
	t.Logf("Attaching CD-ROM device to server with UUID %s", serverDetails.UUID)
	serverDetails, err := svc.AttachStorage(&request.AttachStorageRequest{
		ServerUUID: serverDetails.UUID,
		Type:       upcloud.StorageTypeCDROM,
	})

	handleError(err)
	t.Log("CD-ROM is now attached")

	// Start the server
	t.Logf("Starting server with UUID %s", serverDetails.UUID)
	serverDetails, err = svc.StartServer(&request.StartServerRequest{
		UUID:    serverDetails.UUID,
		Timeout: time.Minute * 5,
	})

	handleError(err)

	err = svc.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         serverDetails.UUID,
		DesiredState: upcloud.ServerStateStarted,
		Timeout:      time.Minute * 5,
	})

	handleError(err)
	t.Log("Server is now started")

	// Load the CD-ROM
	t.Log("Loading CD-ROM into CD-ROM device")
	serverDetails, err = svc.LoadCDROM(&request.LoadCDROMRequest{
		ServerUUID:  serverDetails.UUID,
		StorageUUID: "01000000-0000-4000-8000-000030060101",
	})

	handleError(err)
	t.Log("CD-ROM is now loaded")

	// Eject the CD-ROM
	t.Log("Ejecting CD-ROM from CD-ROM device")
	serverDetails, err = svc.EjectCDROM(&request.EjectCDROMRequest{
		ServerUUID: serverDetails.UUID,
	})

	handleError(err)
	t.Log("CD-ROM is now ejected")

	// Stop the server
	t.Logf("Stopping server with UUID %s ...", serverDetails.UUID)
	stopServer(serverDetails.UUID)
	t.Log("Server is now stopped")

	// Delete the server
	t.Log("Deleting the server ...")
	deleteServer(serverDetails.UUID)
	t.Log("Server is now deleted")
}

/**
Creates a server and returns the details about it, panic if creation fails
*/
func createServer() *upcloud.ServerDetails {
	createServerRequest := request.CreateServerRequest{
		Zone:             "fi-hel1",
		Title:            "Integration test server #1",
		Hostname:         "debian.example.com",
		PasswordDelivery: request.PasswordDeliveryNone,
		StorageDevices: []request.CreateServerStorageDevice{
			{
				Action:  request.CreateStorageDeviceActionClone,
				Storage: "01000000-0000-4000-8000-000030060200",
				Title:   "disk1",
				Size:    30,
				Tier:    request.CreateStorageDeviceTierMaxIOPS,
			},
		},
		IPAddresses: []request.CreateServerIPAddress{
			{
				Access: upcloud.IPAddressAccessPrivate,
				Family: upcloud.IPAddressFamilyIPv4,
			},
			{
				Access: upcloud.IPAddressAccessPublic,
				Family: upcloud.IPAddressFamilyIPv4,
			},
			{
				Access: upcloud.IPAddressAccessPublic,
				Family: upcloud.IPAddressFamilyIPv6,
			},
		},
	}

	// Create the server and block until it has started
	serverDetails, err := svc.CreateServer(&createServerRequest)

	if err != nil {
		panic(err)
	}

	// Wait for the server to start
	err = svc.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         serverDetails.UUID,
		DesiredState: upcloud.ServerStateStarted,
		Timeout:      time.Minute * 5,
	})

	handleError(err)

	return serverDetails
}

/**
Stops the specified server
*/
func stopServer(uuid string) {
	serverDetails, err := svc.StopServer(&request.StopServerRequest{
		UUID:    uuid,
		Timeout: time.Minute * 5,
	})

	handleError(err)

	err = svc.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         serverDetails.UUID,
		DesiredState: upcloud.ServerStateStopped,
		Timeout:      time.Minute * 5,
	})

	handleError(err)
}

/**
Deletes the specified server
*/
func deleteServer(uuid string) {
	err := svc.DeleteServer(&request.DeleteServerRequest{
		UUID: uuid,
	})

	handleError(err)
}

/**
Creates a piece of storage and returns the details about it, panic if creation fails
*/
func createStorage() *upcloud.StorageDetails {
	createStorageRequest := request.CreateStorageRequest{
		Tier:  upcloud.StorageTierMaxIOPS,
		Title: "Test storage",
		Size:  50,
		Zone:  "fi-hel1",
	}

	storageDetails, err := svc.CreateStorage(&createStorageRequest)

	if err != nil {
		panic(err)
	}

	return storageDetails
}

/**
Deletes the specified storage
*/
func deleteStorage(uuid string) {
	err := svc.DeleteStorage(&request.DeleteStorageRequest{
		UUID: uuid,
	})

	handleError(err)
}

/**
Waits for the specified storage to come online
*/
func waitForStorageOnline(uuid string) {
	err := svc.WaitForStorageState(&request.WaitForStorageStateRequest{
		UUID:         uuid,
		DesiredState: upcloud.StorageStateOnline,
		Timeout:      time.Minute * 5,
	})

	handleError(err)
}

/**
Handles the error by panicing, thus stopping the test execution
*/
func handleError(err error) {
	if err != nil {
		panic(err)
	}
}

/**
Reads the API username and password from the environment, panics if they are not available
*/
func getCredentials() (string, string) {
	user := os.Getenv("UPCLOUD_GO_SDK_TEST_USER")
	password := os.Getenv("UPCLOUD_GO_SDK_TEST_PASSWORD")

	if user == "" || password == "" {
		panic("Unable to retrieve credentials from the environment, ensure UPCLOUD_GO_SDK_TEST_USER and UPCLOUD_GO_SDK_TEST_PASSWORD are exported")
	}

	return user, password
}
<commit_msg>Add missing parallelization to TestCloneStorage()<commit_after>package service

import (
	"github.com/jalle19/upcloud-go-sdk/upcloud"
	"github.com/jalle19/upcloud-go-sdk/upcloud/client"
	"github.com/jalle19/upcloud-go-sdk/upcloud/request"
	"os"
	"testing"
	"time"
)

// The service object used by the tests
var svc *Service

/**
Configures the test environment
*/
func init() {
	user, password := getCredentials()

	c := client.New(user, password)
	c.SetTimeout(time.Second * 30)
	svc = New(c)
}

/**
TestCreateModifyDeleteServer performs the following actions:

- creates a server
- modifies the server
- stops the server
- deletes the server

*/
func TestCreateModifyDeleteServer(t *testing.T) {
	t.Parallel()

	// Create a server
	serverDetails := createServer()
	t.Logf("Server %s with UUID %s created", serverDetails.Title, serverDetails.UUID)

	// Modify the server
	t.Log("Modifying the server ...")

	serverDetails, err := svc.ModifyServer(&request.ModifyServerRequest{
		UUID:  serverDetails.UUID,
		Title: "Modified server",
	})

	handleError(err)
	t.Log("Waiting for the server to exit maintenance state ...")

	err = svc.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         serverDetails.UUID,
		DesiredState: upcloud.ServerStateStarted,
		Timeout:      time.Minute * 5,
	})

	handleError(err)
	t.Logf("Server is now modified, new title is %s", serverDetails.Title)

	// Stop the server
	t.Logf("Stopping server with UUID %s ...", serverDetails.UUID)
	stopServer(serverDetails.UUID)
	t.Log("Server is now stopped")

	// Delete the server
	t.Log("Deleting the server ...")
	deleteServer(serverDetails.UUID)
	t.Log("Server is now deleted")
}

/**
TestCreateModifyDeleteStorage performs the following actions:

- creates a new storage disk
- modifies the storage
- deletes the storage

*/
func TestCreateModifyDeleteStorage(t *testing.T) {
	t.Parallel()

	// Create some storage
	storageDetails := createStorage()
	t.Logf("Storage %s with UUID %s created", storageDetails.Title, storageDetails.UUID)

	// Modify the storage
	t.Log("Modifying the storage ...")

	storageDetails, err := svc.ModifyStorage(&request.ModifyStorageRequest{
		UUID:  storageDetails.UUID,
		Title: "New fancy title",
	})

	handleError(err)
	t.Logf("Storage with UUID %s modified successfully, new title is %s", storageDetails.UUID, storageDetails.Title)

	// Delete the storage
	t.Log("Deleting the storage ...")
	deleteStorage(storageDetails.UUID)
	t.Log("Storage is now deleted")
}

/**
TestAttachDetachStorage performs the following actions:

- creates a server
- stops the server
- creates a new storage disk
- attaches the storage
- detaches the storage
- deletes the storage
- deletes the server

*/
func TestAttachDetachStorage(t *testing.T) {
	t.Parallel()

	// Create a server
	serverDetails := createServer()
	t.Logf("Server %s with UUID %s created", serverDetails.Title, serverDetails.UUID)

	// Stop the server
	t.Logf("Stopping server with UUID %s ...", serverDetails.UUID)
	stopServer(serverDetails.UUID)
	t.Log("Server is now stopped")

	// Create some storage
	storageDetails := createStorage()
	t.Logf("Storage %s with UUID %s created", storageDetails.Title, storageDetails.UUID)

	// Attach the storage
	t.Logf("Attaching storage %s", storageDetails.UUID)

	serverDetails, err := svc.AttachStorage(&request.AttachStorageRequest{
		StorageUUID: storageDetails.UUID,
		ServerUUID:  serverDetails.UUID,
		Type:        upcloud.StorageTypeDisk,
		Address:     "scsi:0:0",
	})

	handleError(err)
	t.Logf("Storage attached to server with UUID %s", serverDetails.UUID)

	// Detach the storage
	t.Logf("Detaching storage %s", storageDetails.UUID)

	serverDetails, err = svc.DetachStorage(&request.DetachStorageRequest{
		ServerUUID: serverDetails.UUID,
		Address:    "scsi:0:0",
	})

	handleError(err)
	t.Logf("Storage %s detached", storageDetails.UUID)

	// Delete the storage
	t.Log("Deleting the storage ...")
	deleteStorage(storageDetails.UUID)
	t.Log("Storage is now deleted")

	// Delete the server
	t.Log("Deleting the server ...")
	deleteServer(serverDetails.UUID)
	t.Log("Server is now deleted")
}

/**
TestCloneStorage performs the following actions:

- creates a storage device
- clones the storage device
- deletes the clone and the storage device

*/
func TestCloneStorage(t *testing.T) {
	t.Parallel()

	// Create storage
	storageDetails := createStorage()
	t.Logf("Storage %s with UUID %s created", storageDetails.Title, storageDetails.UUID)

	// Clone the storage
	t.Log("Cloning storage ...")

	clonedStorageDetails, err := svc.CloneStorage(&request.CloneStorageRequest{
		UUID:  storageDetails.UUID,
		Title: "Cloned storage",
		Zone:  "fi-hel1",
		Tier:  upcloud.StorageTierMaxIOPS,
	})

	handleError(err)
	waitForStorageOnline(clonedStorageDetails.UUID)
	t.Logf("Storage cloned as %s", clonedStorageDetails.UUID)

	// Delete both storage devices
	t.Log("Deleting the storage ...")
	deleteStorage(storageDetails.UUID)
	t.Log("Storage is now deleted")

	t.Log("Deleting the cloned storage ...")
	deleteStorage(clonedStorageDetails.UUID)
	t.Log("Cloned storage is now deleted")

	err = svc.DeleteStorage(&request.DeleteStorageRequest{
		UUID: clonedStorageDetails.UUID,
	})

	handleError(err)
	t.Log("Cloned storage is now deleted")
}

/**
TestLoadEjectCDROM performs the following actions:

- creates a server
- stops the server
- attaches a CD-ROM device
- starts the server
- loads a CD-ROM
- ejects the CD-ROM
- stops the server
- deletes the server

*/
func TestLoadEjectCDROM(t *testing.T) {
	t.Parallel()

	// Create the server
	serverDetails := createServer()
	t.Logf("Server %s with UUID %s created", serverDetails.Title, serverDetails.UUID)

	// Stop the server
	t.Logf("Stopping server with UUID %s ...", serverDetails.UUID)
	stopServer(serverDetails.UUID)
	t.Log("Server is now stopped")

	// Attach CD-ROM device
	t.Logf("Attaching CD-ROM device to server with UUID %s", serverDetails.UUID)
	serverDetails, err := svc.AttachStorage(&request.AttachStorageRequest{
		ServerUUID: serverDetails.UUID,
		Type:       upcloud.StorageTypeCDROM,
	})

	handleError(err)
	t.Log("CD-ROM is now attached")

	// Start the server
	t.Logf("Starting server with UUID %s", serverDetails.UUID)
	serverDetails, err = svc.StartServer(&request.StartServerRequest{
		UUID:    serverDetails.UUID,
		Timeout: time.Minute * 5,
	})

	handleError(err)

	err = svc.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         serverDetails.UUID,
		DesiredState: upcloud.ServerStateStarted,
		Timeout:      time.Minute * 5,
	})

	handleError(err)
	t.Log("Server is now started")

	// Load the CD-ROM
	t.Log("Loading CD-ROM into CD-ROM device")
	serverDetails, err = svc.LoadCDROM(&request.LoadCDROMRequest{
		ServerUUID:  serverDetails.UUID,
		StorageUUID: "01000000-0000-4000-8000-000030060101",
	})

	handleError(err)
	t.Log("CD-ROM is now loaded")

	// Eject the CD-ROM
	t.Log("Ejecting CD-ROM from CD-ROM device")
	serverDetails, err = svc.EjectCDROM(&request.EjectCDROMRequest{
		ServerUUID: serverDetails.UUID,
	})

	handleError(err)
	t.Log("CD-ROM is now ejected")

	// Stop the server
	t.Logf("Stopping server with UUID %s ...", serverDetails.UUID)
	stopServer(serverDetails.UUID)
	t.Log("Server is now stopped")

	// Delete the server
	t.Log("Deleting the server ...")
	deleteServer(serverDetails.UUID)
	t.Log("Server is now deleted")
}

/**
Creates a server and returns the details about it, panic if creation fails
*/
func createServer() *upcloud.ServerDetails {
	createServerRequest := request.CreateServerRequest{
		Zone:             "fi-hel1",
		Title:            "Integration test server #1",
		Hostname:         "debian.example.com",
		PasswordDelivery: request.PasswordDeliveryNone,
		StorageDevices: []request.CreateServerStorageDevice{
			{
				Action:  request.CreateStorageDeviceActionClone,
				Storage: "01000000-0000-4000-8000-000030060200",
				Title:   "disk1",
				Size:    30,
				Tier:    request.CreateStorageDeviceTierMaxIOPS,
			},
		},
		IPAddresses: []request.CreateServerIPAddress{
			{
				Access: upcloud.IPAddressAccessPrivate,
				Family: upcloud.IPAddressFamilyIPv4,
			},
			{
				Access: upcloud.IPAddressAccessPublic,
				Family: upcloud.IPAddressFamilyIPv4,
			},
			{
				Access: upcloud.IPAddressAccessPublic,
				Family: upcloud.IPAddressFamilyIPv6,
			},
		},
	}

	// Create the server and block until it has started
	serverDetails, err := svc.CreateServer(&createServerRequest)

	if err != nil {
		panic(err)
	}

	// Wait for the server to start
	err = svc.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         serverDetails.UUID,
		DesiredState: upcloud.ServerStateStarted,
		Timeout:      time.Minute * 5,
	})

	handleError(err)

	return serverDetails
}

/**
Stops the specified server
*/
func stopServer(uuid string) {
	serverDetails, err := svc.StopServer(&request.StopServerRequest{
		UUID:    uuid,
		Timeout: time.Minute * 5,
	})

	handleError(err)

	err = svc.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         serverDetails.UUID,
		DesiredState: upcloud.ServerStateStopped,
		Timeout:      time.Minute * 5,
	})

	handleError(err)
}

/**
Deletes the specified server
*/
func deleteServer(uuid string) {
	err := svc.DeleteServer(&request.DeleteServerRequest{
		UUID: uuid,
	})

	handleError(err)
}

/**
Creates a piece of storage and returns the details about it, panic if creation fails
*/
func createStorage() *upcloud.StorageDetails {
	createStorageRequest := request.CreateStorageRequest{
		Tier:  upcloud.StorageTierMaxIOPS,
		Title: "Test storage",
		Size:  50,
		Zone:  "fi-hel1",
	}

	storageDetails, err := svc.CreateStorage(&createStorageRequest)

	if err != nil {
		panic(err)
	}

	return storageDetails
}

/**
Deletes the specified storage
*/
func deleteStorage(uuid string) {
	err := svc.DeleteStorage(&request.DeleteStorageRequest{
		UUID: uuid,
	})

	handleError(err)
}

/**
Waits for the specified storage to come online
*/
func waitForStorageOnline(uuid string) {
	err := svc.WaitForStorageState(&request.WaitForStorageStateRequest{
		UUID:         uuid,
		DesiredState: upcloud.StorageStateOnline,
		Timeout:      time.Minute * 5,
	})

	handleError(err)
}

/**
Handles the error by panicing, thus stopping the test execution
*/
func handleError(err error) {
	if err != nil {
		panic(err)
	}
}

/**
Reads the API username and password from the environment, panics if they are not available
*/
func getCredentials() (string, string) {
	user := os.Getenv("UPCLOUD_GO_SDK_TEST_USER")
	password := os.Getenv("UPCLOUD_GO_SDK_TEST_PASSWORD")

	if user == "" || password == "" {
		panic("Unable to retrieve credentials from the environment, ensure UPCLOUD_GO_SDK_TEST_USER and UPCLOUD_GO_SDK_TEST_PASSWORD are exported")
	}

	return user, password
}
<|endoftext|>


<commit_before>package stringsutil

import (
	"fmt"
	"regexp"
	"sort"
	"strings"
	"unicode"
	"unicode/utf8"
)

const (
	StringToLower     = "StringToLower"
	SpaceToHyphen     = "SpaceToHyphen"
	SpaceToUnderscore = "SpaceToUnderscore"
)

var (
	rxControl = regexp.MustCompile(`[[:cntrl:]]`)
	rxSpaces  = regexp.MustCompile(`\s+`)
)

// PadLeft prepends a string to a base string until the string
// length is greater or equal to the desired length.
func PadLeft(str string, pad string, length int) string {
	for {
		str = pad + str
		if len(str) >= length {
			return str[0:length]
		}
	}
}

// PadRight appends a string to a base string until the string
// length is greater or equal to the desired length.
func PadRight(str string, pad string, length int) string {
	for {
		str += pad
		if len(str) > length {
			return str[0:length]
		}
	}
}

// ToLowerFirst lower cases the first letter in the string
func ToLowerFirst(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToLower(r)) + s[n:]
}

// ToUpperFirst upper cases the first letter in the string
func ToUpperFirst(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToUpper(r)) + s[n:]
}

// SliceTrimSpace removes leading and trailing spaces per
// string and also removes empty strings.
func SliceTrimSpace(slice []string) []string {
	trimmed := []string{}
	for _, part := range slice {
		part := strings.TrimSpace(part)
		if len(part) > 0 {
			trimmed = append(trimmed, part)
		}
	}
	return trimmed
}

func SliceCondenseRegexps(texts []string, regexps []*regexp.Regexp, replacement string) []string {
	parts := []string{}
	for _, part := range texts {
		for _, rx := range regexps {
			part = rx.ReplaceAllString(part, replacement)
		}
		part = strings.TrimSpace(part)
		if len(part) > 0 {
			parts = append(parts, part)
		}
	}
	return parts
}

func SliceCondensePunctuation(texts []string) []string {
	parts := []string{}
	for _, part := range texts {
		part = regexp.MustCompile(`[^a-zA-Z0-9]+`).ReplaceAllString(part, " ")
		part = regexp.MustCompile(`\s+`).ReplaceAllString(part, " ")
		part = strings.TrimSpace(part)
		if len(part) > 0 {
			parts = append(parts, part)
		}
	}
	return parts
}

func SliceCondenseAndQuoteSpace(items []string, quoteLeft, quoteRight string) []string {
	return SliceCondenseAndQuote(items, " ", " ", quoteLeft, quoteRight)
}

func SliceCondenseAndQuote(items []string, trimLeft, trimRight, quoteLeft, quoteRight string) []string {
	newItems := []string{}
	for _, item := range items {
		item = strings.TrimLeft(item, trimLeft)
		item = strings.TrimRight(item, trimRight)
		if len(item) > 0 {
			item = quoteLeft + item + quoteRight
			newItems = append(newItems, item)
		}
	}
	return newItems
}

// SplitTrimSpace splits a string and trims spaces on
// remaining elements.
func SplitTrimSpace(s, sep string) []string {
	split := strings.Split(s, sep)
	strs := []string{}
	for _, str := range split {
		strs = append(strs, strings.TrimSpace(str))
	}
	return strs
}

// SplitCondenseSpace splits a string and trims spaces on
// remaining elements, removing empty elements.
func SplitCondenseSpace(s, sep string) []string {
	split := strings.Split(s, sep)
	strs := []string{}
	for _, str := range split {
		str = strings.TrimSpace(str)
		if len(str) > 0 {
			strs = append(strs, str)
		}
	}
	return strs
}

// CondenseString trims whitespace at the ends of the string
// as well as in between.
func CondenseString(content string, join_lines bool) string {
	if join_lines {
		content = regexp.MustCompile(`\n`).ReplaceAllString(content, " ")
	}
	// Beginning
	content = regexp.MustCompile(`^\s+`).ReplaceAllString(content, "")
	// End
	content = regexp.MustCompile(`\s+$`).ReplaceAllString(content, "")
	// Middle
	content = regexp.MustCompile(`\n[\s\t\r]*\n`).ReplaceAllString(content, "\n")
	// Indentation
	content = regexp.MustCompile(`\n[\s\t\r]*`).ReplaceAllString(content, "\n")
	// Collapse
	content = regexp.MustCompile(`\s+`).ReplaceAllString(content, " ")
	return strings.TrimSpace(content)
}

func StripControl(s string) string {
	return rxControl.ReplaceAllString(s, "")
}

// TrimSentenceLength trims a string by a max length at word boundaries.
func TrimSentenceLength(sentenceInput string, maxLength int) string {
	if len(sentenceInput) <= maxLength {
		return sentenceInput
	}
	sentenceLen := string(sentenceInput[0:maxLength]) // first350 := string(s[0:350])
	rx_end := regexp.MustCompile(`[[:punct:]][^[[:punct:]]]*$`)
	sentencePunct := rx_end.ReplaceAllString(sentenceLen, "")
	if len(sentencePunct) >= 2 {
		return sentencePunct
	}
	return sentenceLen
}

func JoinTrimSpace(strs []string) string {
	return rxSpaces.ReplaceAllString(strings.Join(strs, " "), " ")
}

// JoinInterface joins an interface and returns a string. It takes
// a join separator, boolean to replace the join separator in the
// string parts and a separator alternate. `stripEmbeddedSep` strips
// separator string found within parts. `stripRepeatedSep` strips
// repeating separators. This flexibility is designed to support
// joining data for both CSVs and paths.
func JoinInterface(arr []interface{}, sep string, stripRepeatedSep bool, stripEmbeddedSep bool, altSep string) string {
	parts := []string{}
	rx := regexp.MustCompile(sep)
	for _, el := range arr {
		part := fmt.Sprintf("%v", el)
		if stripEmbeddedSep {
			part = rx.ReplaceAllString(part, altSep)
		}
		parts = append(parts, part)
	}
	joined := strings.Join(parts, sep)
	if stripRepeatedSep {
		joined = regexp.MustCompile(fmt.Sprintf("%s+", sep)).
			ReplaceAllString(joined, sep)
	}
	return joined
}

func JoinLiterary(slice []string, sep, joinWord string) string {
	switch len(slice) {
	case 0:
		return ""
	case 1:
		return slice[0]
	case 2:
		return slice[0] + " " + joinWord + " " + slice[1]
	default:
		last, rest := slice[len(slice)-1], slice[:len(slice)-1]
		rest = append(rest, joinWord+" "+last)
		return strings.Join(rest, sep+" ")
	}
}

func JoinLiteraryQuote(slice []string, leftQuote, rightQuote, sep, joinWord string) string {
	newSlice := SliceCondenseAndQuoteSpace(slice, leftQuote, rightQuote)
	switch len(newSlice) {
	case 0:
		return ""
	case 1:
		return newSlice[0]
	case 2:
		return newSlice[0] + " " + joinWord + " " + newSlice[1]
	default:
		last, rest := newSlice[len(newSlice)-1], newSlice[:len(newSlice)-1]
		rest = append(rest, joinWord+" "+last)
		return strings.Join(rest, sep+" ")
	}
}

func JoinStringsTrimSpaceToLowerSort(strs []string, sep string) string {
	wip := []string{}
	for _, s := range strs {
		s = strings.ToLower(strings.TrimSpace(s))
		if len(s) > 0 {
			wip = append(wip, s)
		}
	}
	sort.Strings(wip)
	return strings.Join(wip, sep)
}

func FormatString(s string, options []string) string {
	for _, opt := range options {
		switch strings.TrimSpace(opt) {
		case StringToLower:
			s = strings.ToLower(s)
		case SpaceToHyphen:
			s = regexp.MustCompile(`[\s-]+`).ReplaceAllString(s, "-")
		case SpaceToUnderscore:
			s = regexp.MustCompile(`[\s_]+`).ReplaceAllString(s, "_")
		}
	}
	return s
}

// CommonInitialisms is the listed by Go Lint.
const CommonInitialisms = "ACL,API,ASCII,CPU,CSS,DNS,EOF,GUID,HTML,HTTP,HTTPS,ID,IP,JSON,LHS,QPS,RAM,RHS,RPC,SLA,SMTP,SQL,SSH,TCP,TLS,TTL,UDP,UI,UID,UUID,URI,URL,UTF8,VM,XML,XMPP,XSRF,XSS"

// CommonInitialismsMap returns map[string]bool of upper case initialisms.
func CommonInitialismsMap() map[string]bool {
	ciMap := map[string]bool{}
	commonInitialisms := strings.Split(CommonInitialisms, ",")
	for _, ci := range commonInitialisms {
		ciMap[ci] = true
	}
	return ciMap
}

// StringToConstant is used to generate constant names for code generation.
// It uses the commonInitialisms in Go Lint.
func StringToConstant(s string) string {
	newParts := []string{}
	parts := strings.Split(s, "_")
	ciMap := CommonInitialismsMap()
	for _, p := range parts {
		pUp := strings.ToUpper(p)
		if _, ok := ciMap[pUp]; ok {
			newParts = append(newParts, pUp)
		} else {
			newParts = append(newParts, ToUpperFirst(strings.ToLower(p)))
		}
	}
	return strings.Join(newParts, "")
}
<commit_msg>update formatting<commit_after>package stringsutil

import (
	"fmt"
	"regexp"
	"sort"
	"strings"
	"unicode"
	"unicode/utf8"
)

const (
	StringToLower     = "StringToLower"
	SpaceToHyphen     = "SpaceToHyphen"
	SpaceToUnderscore = "SpaceToUnderscore"
)

var (
	rxControl = regexp.MustCompile(`[[:cntrl:]]`)
	rxSpaces  = regexp.MustCompile(`\s+`)
)

// PadLeft prepends a string to a base string until the string
// length is greater or equal to the desired length.
func PadLeft(str string, pad string, length int) string {
	for {
		str = pad + str
		if len(str) >= length {
			return str[0:length]
		}
	}
}

// PadRight appends a string to a base string until the string
// length is greater or equal to the desired length.
func PadRight(str string, pad string, length int) string {
	for {
		str += pad
		if len(str) > length {
			return str[0:length]
		}
	}
}

// ToLowerFirst lower cases the first letter in the string
func ToLowerFirst(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToLower(r)) + s[n:]
}

// ToUpperFirst upper cases the first letter in the string
func ToUpperFirst(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToUpper(r)) + s[n:]
}

// SliceTrimSpace removes leading and trailing spaces per
// string and also removes empty strings.
func SliceTrimSpace(slice []string) []string {
	trimmed := []string{}
	for _, part := range slice {
		part := strings.TrimSpace(part)
		if len(part) > 0 {
			trimmed = append(trimmed, part)
		}
	}
	return trimmed
}

func SliceCondenseRegexps(texts []string, regexps []*regexp.Regexp, replacement string) []string {
	parts := []string{}
	for _, part := range texts {
		for _, rx := range regexps {
			part = rx.ReplaceAllString(part, replacement)
		}
		part = strings.TrimSpace(part)
		if len(part) > 0 {
			parts = append(parts, part)
		}
	}
	return parts
}

func SliceCondensePunctuation(texts []string) []string {
	parts := []string{}
	for _, part := range texts {
		part = regexp.MustCompile(`[^a-zA-Z0-9]+`).ReplaceAllString(part, " ")
		part = regexp.MustCompile(`\s+`).ReplaceAllString(part, " ")
		part = strings.TrimSpace(part)
		if len(part) > 0 {
			parts = append(parts, part)
		}
	}
	return parts
}

func SliceCondenseAndQuoteSpace(items []string, quoteLeft, quoteRight string) []string {
	return SliceCondenseAndQuote(items, " ", " ", quoteLeft, quoteRight)
}

func SliceCondenseAndQuote(items []string, trimLeft, trimRight, quoteLeft, quoteRight string) []string {
	newItems := []string{}
	for _, item := range items {
		item = strings.TrimLeft(item, trimLeft)
		item = strings.TrimRight(item, trimRight)
		if len(item) > 0 {
			item = quoteLeft + item + quoteRight
			newItems = append(newItems, item)
		}
	}
	return newItems
}

// SplitTrimSpace splits a string and trims spaces on
// remaining elements.
func SplitTrimSpace(s, sep string) []string {
	split := strings.Split(s, sep)
	strs := []string{}
	for _, str := range split {
		strs = append(strs, strings.TrimSpace(str))
	}
	return strs
}

// SplitCondenseSpace splits a string and trims spaces on
// remaining elements, removing empty elements.
func SplitCondenseSpace(s, sep string) []string {
	split := strings.Split(s, sep)
	strs := []string{}
	for _, str := range split {
		str = strings.TrimSpace(str)
		if len(str) > 0 {
			strs = append(strs, str)
		}
	}
	return strs
}

// CondenseString trims whitespace at the ends of the string
// as well as in between.
func CondenseString(content string, join_lines bool) string {
	if join_lines {
		content = regexp.MustCompile(`\n`).ReplaceAllString(content, " ")
	}
	// Beginning
	content = regexp.MustCompile(`^\s+`).ReplaceAllString(content, "")
	// End
	content = regexp.MustCompile(`\s+$`).ReplaceAllString(content, "")
	// Middle
	content = regexp.MustCompile(`\n[\s\t\r]*\n`).ReplaceAllString(content, "\n")
	// Indentation
	content = regexp.MustCompile(`\n[\s\t\r]*`).ReplaceAllString(content, "\n")
	// Collapse
	content = regexp.MustCompile(`\s+`).ReplaceAllString(content, " ")
	return strings.TrimSpace(content)
}

func StripControl(s string) string { return rxControl.ReplaceAllString(s, "") }

// TrimSentenceLength trims a string by a max length at word boundaries.
func TrimSentenceLength(sentenceInput string, maxLength int) string {
	if len(sentenceInput) <= maxLength {
		return sentenceInput
	}
	sentenceLen := string(sentenceInput[0:maxLength]) // first350 := string(s[0:350])
	rx_end := regexp.MustCompile(`[[:punct:]][^[[:punct:]]]*$`)
	sentencePunct := rx_end.ReplaceAllString(sentenceLen, "")
	if len(sentencePunct) >= 2 {
		return sentencePunct
	}
	return sentenceLen
}

func JoinTrimSpace(strs []string) string {
	return rxSpaces.ReplaceAllString(strings.Join(strs, " "), " ")
}

// JoinInterface joins an interface and returns a string. It takes
// a join separator, boolean to replace the join separator in the
// string parts and a separator alternate. `stripEmbeddedSep` strips
// separator string found within parts. `stripRepeatedSep` strips
// repeating separators. This flexibility is designed to support
// joining data for both CSVs and paths.
func JoinInterface(arr []interface{}, sep string, stripRepeatedSep bool, stripEmbeddedSep bool, altSep string) string {
	parts := []string{}
	rx := regexp.MustCompile(sep)
	for _, el := range arr {
		part := fmt.Sprintf("%v", el)
		if stripEmbeddedSep {
			part = rx.ReplaceAllString(part, altSep)
		}
		parts = append(parts, part)
	}
	joined := strings.Join(parts, sep)
	if stripRepeatedSep {
		joined = regexp.MustCompile(fmt.Sprintf("%s+", sep)).
			ReplaceAllString(joined, sep)
	}
	return joined
}

func JoinLiterary(slice []string, sep, joinWord string) string {
	switch len(slice) {
	case 0:
		return ""
	case 1:
		return slice[0]
	case 2:
		return slice[0] + " " + joinWord + " " + slice[1]
	default:
		last, rest := slice[len(slice)-1], slice[:len(slice)-1]
		rest = append(rest, joinWord+" "+last)
		return strings.Join(rest, sep+" ")
	}
}

func JoinLiteraryQuote(slice []string, leftQuote, rightQuote, sep, joinWord string) string {
	newSlice := SliceCondenseAndQuoteSpace(slice, leftQuote, rightQuote)
	switch len(newSlice) {
	case 0:
		return ""
	case 1:
		return newSlice[0]
	case 2:
		return newSlice[0] + " " + joinWord + " " + newSlice[1]
	default:
		last, rest := newSlice[len(newSlice)-1], newSlice[:len(newSlice)-1]
		rest = append(rest, joinWord+" "+last)
		return strings.Join(rest, sep+" ")
	}
}

func JoinStringsTrimSpaceToLowerSort(strs []string, sep string) string {
	wip := []string{}
	for _, s := range strs {
		s = strings.ToLower(strings.TrimSpace(s))
		if len(s) > 0 {
			wip = append(wip, s)
		}
	}
	sort.Strings(wip)
	return strings.Join(wip, sep)
}

func FormatString(s string, options []string) string {
	for _, opt := range options {
		switch strings.TrimSpace(opt) {
		case StringToLower:
			s = strings.ToLower(s)
		case SpaceToHyphen:
			s = regexp.MustCompile(`[\s-]+`).ReplaceAllString(s, "-")
		case SpaceToUnderscore:
			s = regexp.MustCompile(`[\s_]+`).ReplaceAllString(s, "_")
		}
	}
	return s
}

// CommonInitialisms is the listed by Go Lint.
const CommonInitialisms = "ACL,API,ASCII,CPU,CSS,DNS,EOF,GUID,HTML,HTTP,HTTPS,ID,IP,JSON,LHS,QPS,RAM,RHS,RPC,SLA,SMTP,SQL,SSH,TCP,TLS,TTL,UDP,UI,UID,UUID,URI,URL,UTF8,VM,XML,XMPP,XSRF,XSS"

// CommonInitialismsMap returns map[string]bool of upper case initialisms.
func CommonInitialismsMap() map[string]bool {
	ciMap := map[string]bool{}
	commonInitialisms := strings.Split(CommonInitialisms, ",")
	for _, ci := range commonInitialisms {
		ciMap[ci] = true
	}
	return ciMap
}

// StringToConstant is used to generate constant names for code generation.
// It uses the commonInitialisms in Go Lint.
func StringToConstant(s string) string {
	newParts := []string{}
	parts := strings.Split(s, "_")
	ciMap := CommonInitialismsMap()
	for _, p := range parts {
		pUp := strings.ToUpper(p)
		if _, ok := ciMap[pUp]; ok {
			newParts = append(newParts, pUp)
		} else {
			newParts = append(newParts, ToUpperFirst(strings.ToLower(p)))
		}
	}
	return strings.Join(newParts, "")
}
<|endoftext|>


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

// +build root,integration

package devicemapper_test

import (
	"fmt"
	"os"
	"syscall"
	"testing"

	. "gopkg.in/check.v1"

	"github.com/Sirupsen/logrus"
	"github.com/control-center/serviced/volume/drivertest"
	devmapper "github.com/docker/docker/daemon/graphdriver/devmapper"
	// Register the devicemapper driver
	_ "github.com/control-center/serviced/volume/devicemapper"
)

var (
	_                   = Suite(&DeviceMapperSuite{})
	devmapArgs []string = make([]string, 0)
)

func init() {
	// Reduce the size the the base fs and loopback for the tests
	devmapper.DefaultDataLoopbackSize = 300 * 1024 * 1024
	devmapper.DefaultMetaDataLoopbackSize = 199 * 1024 * 1024
	devmapper.DefaultBaseFsSize = 300 * 1024 * 1024
	devmapper.DefaultUdevSyncOverride = true
	if err := initLoopbacks(); err != nil {
		panic(err)
	}
	// Set Docker's logger to debug level, so we can get interesting
	// information if -v
	logrus.SetLevel(logrus.DebugLevel)
}

// getBaseLoopStats inspects /dev/loop0 to collect uid,gid, and mode for the
// loop0 device on the system.  If it does not exist we assume 0,0,0660 for the
// stat data
func getBaseLoopStats() (*syscall.Stat_t, error) {
	loop0, err := os.Stat("/dev/loop0")
	if err != nil {
		if os.IsNotExist(err) {
			return &syscall.Stat_t{
				Uid:  0,
				Gid:  0,
				Mode: 0660,
			}, nil
		}
		return nil, err
	}
	return loop0.Sys().(*syscall.Stat_t), nil
}

// initLoopbacks ensures that the loopback devices are properly created within
// the system running the device mapper tests.
func initLoopbacks() error {
	statT, err := getBaseLoopStats()
	if err != nil {
		return err
	}
	for i := 0; i < 8; i++ {
		loopPath := fmt.Sprintf("/dev/loop%d", i)
		// only create new loopback files if they don't exist
		if _, err := os.Stat(loopPath); err != nil {
			if mkerr := syscall.Mknod(loopPath,
				uint32(statT.Mode|syscall.S_IFBLK), int((7<<8)|(i&0xff)|((i&0xfff00)<<12))); mkerr != nil {
				return mkerr
			}
			os.Chown(loopPath, int(statT.Uid), int(statT.Gid))
		}
	}
	return nil
}

func Test(t *testing.T) { TestingT(t) }

type DeviceMapperSuite struct{}

func (s *DeviceMapperSuite) TestDeviceMapperCreateEmpty(c *C) {
	drivertest.DriverTestCreateEmpty(c, "devicemapper", "", devmapArgs)
}

func (s *DeviceMapperSuite) TestDeviceMapperCreateBase(c *C) {
	drivertest.DriverTestCreateBase(c, "devicemapper", "", devmapArgs)
}

func (s *DeviceMapperSuite) TestDeviceMapperSnapshots(c *C) {
	drivertest.DriverTestSnapshots(c, "devicemapper", "", devmapArgs)
}

func (s *DeviceMapperSuite) TestDeviceMapperExportImport(c *C) {
	drivertest.DriverTestExportImport(c, "devicemapper", "", "", devmapArgs)
}
<commit_msg>CC-1401: New way to control devicemapper FS size<commit_after>// Copyright 2015 The Serviced Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build root,integration

package devicemapper_test

import (
	"fmt"
	"os"
	"syscall"
	"testing"

	. "gopkg.in/check.v1"

	"github.com/Sirupsen/logrus"
	"github.com/control-center/serviced/volume/drivertest"
	// Register the devicemapper driver
	_ "github.com/control-center/serviced/volume/devicemapper"
)

var (
	_                   = Suite(&DeviceMapperSuite{})
	devmapArgs []string = make([]string, 0)
)

func init() {
	// Reduce the size the the base fs and loopback for the tests
	devmapArgs = append(devmapArgs,
		fmt.Sprintf("dm.loopdatasize=%d", 300*1024*1024),
		fmt.Sprintf("dm.loopmetadatasize=%d", 199*1024*1024),
		fmt.Sprintf("dm.basesize=%d", 300*1024*1024),
		"dm.override_udev_sync_check=true")
	if err := initLoopbacks(); err != nil {
		panic(err)
	}
	// Set Docker's logger to debug level, so we can get interesting
	// information if -v
	logrus.SetLevel(logrus.DebugLevel)
}

// getBaseLoopStats inspects /dev/loop0 to collect uid,gid, and mode for the
// loop0 device on the system.  If it does not exist we assume 0,0,0660 for the
// stat data
func getBaseLoopStats() (*syscall.Stat_t, error) {
	loop0, err := os.Stat("/dev/loop0")
	if err != nil {
		if os.IsNotExist(err) {
			return &syscall.Stat_t{
				Uid:  0,
				Gid:  0,
				Mode: 0660,
			}, nil
		}
		return nil, err
	}
	return loop0.Sys().(*syscall.Stat_t), nil
}

// initLoopbacks ensures that the loopback devices are properly created within
// the system running the device mapper tests.
func initLoopbacks() error {
	statT, err := getBaseLoopStats()
	if err != nil {
		return err
	}
	for i := 0; i < 8; i++ {
		loopPath := fmt.Sprintf("/dev/loop%d", i)
		// only create new loopback files if they don't exist
		if _, err := os.Stat(loopPath); err != nil {
			if mkerr := syscall.Mknod(loopPath,
				uint32(statT.Mode|syscall.S_IFBLK), int((7<<8)|(i&0xff)|((i&0xfff00)<<12))); mkerr != nil {
				return mkerr
			}
			os.Chown(loopPath, int(statT.Uid), int(statT.Gid))
		}
	}
	return nil
}

func Test(t *testing.T) { TestingT(t) }

type DeviceMapperSuite struct{}

func (s *DeviceMapperSuite) TestDeviceMapperCreateEmpty(c *C) {
	drivertest.DriverTestCreateEmpty(c, "devicemapper", "", devmapArgs)
}

func (s *DeviceMapperSuite) TestDeviceMapperCreateBase(c *C) {
	drivertest.DriverTestCreateBase(c, "devicemapper", "", devmapArgs)
}

func (s *DeviceMapperSuite) TestDeviceMapperSnapshots(c *C) {
	drivertest.DriverTestSnapshots(c, "devicemapper", "", devmapArgs)
}

func (s *DeviceMapperSuite) TestDeviceMapperExportImport(c *C) {
	drivertest.DriverTestExportImport(c, "devicemapper", "", "", devmapArgs)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"strings"
	"text/template"
)

var bindingsFile = `package {{.packageName}}
/*
This is an autogenerated file by autobindings
*/

import(
	"github.com/mholt/binding"
)

func ({{.variableName}} {{.structName}}) FieldMap() binding.FieldMap {
	b := binding.FieldMap{ {{$vname := .variableName}}{{range $field, $mapping := .mappings}}
			&{{$vname}}.{{$field}}: "{{$mapping}}",{{end}}
			}

	{{$vname := .variableName}}
	{{range $field, $type := .embeds}}
	var i interface{} = {{$vname}}.{{$type}}
	if m, ok := i.(binding.FieldMap); ok {
			for k, v := range m.FieldMap() {
				b[k] = v
			}
	}
	{{end}}
	return b
}`

func main() {

	prnt := flag.Bool("print", false, "Output In Console")
	filename := flag.String("file", "", "Input file")

	flag.Parse()

	if *filename == "" {
		fmt.Println("Usage : bindings {file_name}\nExample: bindings file.go")
		return
	}

	generateFieldMap(*filename, *prnt)
}

func generateFieldMap(fileName string, printOnConsole bool) {
	fset := token.NewFileSet() // positions are relative to fset
	// Parse the file given in arguments
	f, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
	if err != nil {
		panic(err)
	}
	structMap := map[string]*ast.FieldList{}
	// range over the structs and fill struct map
	for _, d := range f.Scope.Objects {
		ts, ok := d.Decl.(*ast.TypeSpec)
		if !ok {
			continue
		}
		switch ts.Type.(type) {
		case *ast.StructType:
			x, _ := ts.Type.(*ast.StructType)
			structMap[ts.Name.String()] = x.Fields
		}
	}
	// looping through each struct and creating a bindings file for it
	packageName := f.Name
	for structName, fields := range structMap {
		variableName := strings.ToLower(string(structName[0]))
		mappings := map[string]string{}
		embeds := []ast.Expr{}
		for _, field := range fields.List {
			if len(field.Names) == 0 {
				embeds = append(embeds, field.Type)
				continue
			}
			name := field.Names[0].String()
			// if tag for field doesn't exists, create one
			if field.Tag == nil {
				mappings[name] = name
			} else if strings.Contains(field.Tag.Value, "json") {
				tags := strings.Replace(field.Tag.Value, "`", "", -1)
				for _, tag := range strings.Split(tags, " ") {
					if strings.Contains(tag, "json") {
						mapping := strings.Replace(tag, "json:\"", "", -1)
						mapping = strings.Replace(mapping, "\"", "", -1)
						if mapping == "-" {
							continue
						}
						mappings[name] = mapping
					}
				}
			} else {
				// I will handle other cases later
				mappings[name] = name
			}
		}
		content := new(bytes.Buffer)
		t := template.Must(template.New("bindings").Parse(bindingsFile))
		err = t.Execute(content, map[string]interface{}{
			"packageName":  packageName,
			"variableName": variableName,
			"structName":   structName,
			"mappings":     mappings,
			"embeds":       embeds})
		if err != nil {
			panic(err)
		}
		finalContent, err := format.Source(content.Bytes())
		if err != nil {
			panic(err)
		}
		if printOnConsole {
			fmt.Println(string(finalContent))
			return
		}
		// opening file for writing content
		writer, err := os.Create(fmt.Sprintf("%s_bindings.go", strings.ToLower(structName)))
		if err != nil {
			fmt.Printf("Error opening file %v", err)
			panic(err)
		}
		writer.WriteString(string(finalContent))
		writer.Close()
	}
}
<commit_msg>generation logic added for embedded structs<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"strings"
	"text/template"
)

var bindingsFile = `package {{.packageName}}
/*
This is an autogenerated file by autobindings
*/

import(
	"github.com/mholt/binding"
)

func ({{.variableName}} {{.structName}}) FieldMap() binding.FieldMap {
	binding_fmap := binding.FieldMap{ {{$vname := .variableName}}{{range $field, $mapping := .mappings}}
			&{{$vname}}.{{$field}}: "{{$mapping}}",{{end}}
			}

	{{ if .hasEmbeds }}
	type FieldMap interface {
		FieldMap() binding.FieldMap
	}
	var iface interface{}
	{{$vname := .variableName}}
	{{range $field, $type := .embeds}}
	iface = {{$vname}}.{{$type}}
	if m, ok := iface.(FieldMap); ok {
			for k, v := range m.FieldMap() {
				binding_fmap[k] = v
			}
	}
	{{end}}
	{{end}}
	return binding_fmap
}
`

func main() {

	prnt := flag.Bool("print", false, "Output In Console")
	filename := flag.String("file", "", "Input file")

	flag.Parse()

	if *filename == "" {
		fmt.Println("Usage : bindings {file_name}\nExample: bindings file.go")
		return
	}

	generateFieldMap(*filename, *prnt)
}

func generateFieldMap(fileName string, printOnConsole bool) {
	fset := token.NewFileSet() // positions are relative to fset
	// Parse the file given in arguments
	f, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
	if err != nil {
		panic(err)
	}
	structMap := map[string]*ast.FieldList{}
	// range over the structs and fill struct map
	for _, d := range f.Scope.Objects {
		ts, ok := d.Decl.(*ast.TypeSpec)
		if !ok {
			continue
		}
		switch ts.Type.(type) {
		case *ast.StructType:
			x, _ := ts.Type.(*ast.StructType)
			structMap[ts.Name.String()] = x.Fields
		}
	}
	// looping through each struct and creating a bindings file for it
	packageName := f.Name
	for structName, fields := range structMap {
		variableName := strings.ToLower(string(structName[0]))
		mappings := map[string]string{}
		embeds := []ast.Expr{}
		hasEmbeds := false
		for _, field := range fields.List {
			if len(field.Names) == 0 {
				hasEmbeds = true
				embeds = append(embeds, field.Type)
				continue
			}
			name := field.Names[0].String()
			// if tag for field doesn't exists, create one
			if field.Tag == nil {
				mappings[name] = name
			} else if strings.Contains(field.Tag.Value, "json") {
				tags := strings.Replace(field.Tag.Value, "`", "", -1)
				for _, tag := range strings.Split(tags, " ") {
					if strings.Contains(tag, "json") {
						mapping := strings.Replace(tag, "json:\"", "", -1)
						mapping = strings.Replace(mapping, "\"", "", -1)
						if mapping == "-" {
							continue
						}
						mappings[name] = mapping
					}
				}
			} else {
				// I will handle other cases later
				mappings[name] = name
			}
		}
		content := new(bytes.Buffer)
		t := template.Must(template.New("bindings").Parse(bindingsFile))
		err = t.Execute(content, map[string]interface{}{
			"packageName":  packageName,
			"variableName": variableName,
			"structName":   structName,
			"mappings":     mappings,
			"embeds":       embeds,
			"hasEmbeds":    hasEmbeds})
		if err != nil {
			panic(err)
		}
		finalContent, err := format.Source(content.Bytes())
		if err != nil {
			panic(err)
		}
		if printOnConsole {
			fmt.Println(string(finalContent))
			return
		}
		// opening file for writing content
		writer, err := os.Create(fmt.Sprintf("%s_bindings.go", strings.ToLower(structName)))
		if err != nil {
			fmt.Printf("Error opening file %v", err)
			panic(err)
		}
		writer.WriteString(string(finalContent))
		writer.Close()
	}
}
<|endoftext|>


<commit_before>package main

import (
    "errors"
    "log"

    "menteslibres.net/gosexy/redis"
)

const ClientsKey = "SocketClients"
const PageKey    = "PageClients"

type RedisStore struct {
    clientsKey  string
    pageKey     string

    server      string
    port        uint
    pool        redisPool
}

//connection pool implimentation
type redisPool struct {
    connections chan *redis.Client
    maxIdle     int
    connFn      func() (*redis.Client, error) // function to create new connection.
}

func newRedisStore(redis_host string, redis_port uint) (*RedisStore) {

    return &RedisStore{
        ClientsKey,
        PageKey,

        redis_host,
        redis_port,

        redisPool{
            connections: make(chan *redis.Client, 6),
            maxIdle:     6,

            connFn:      func () (*redis.Client, error) {
                client := redis.New()
                err := client.Connect(redis_host, redis_port)

                if err != nil {
                    log.Printf("Redis connect failed: %s\n", err.Error())
                    return nil, err
                }

                return client, nil
            },
        },
    }

}

func (this *redisPool) Get() (*redis.Client, bool) {

    var conn *redis.Client
    select {
        case conn = <-this.connections:
        default:
            conn, err := this.connFn()
            if err != nil {
                return nil, false
            }

            return conn, true
    }

    if err := this.testConn(conn); err != nil {
        return this.Get() // if connection is bad, get the next one in line until base case is hit, then create new client
    }

    return conn, true
}

func (this *redisPool) Close(conn *redis.Client) {
    select {
        case this.connections <- conn:
            return
        default:
            conn.Quit()
    }
}

func (this *redisPool) testConn(conn *redis.Client) error {
    if _, err := conn.Ping(); err != nil {
        conn.Quit()
        return err
    }

    return nil
}

func (this *RedisStore) GetConn() (*redis.Client, error) {

    client, ok := this.pool.Get()
    if !ok {
        return nil, errors.New("Error while getting redis connection")
    }

    return client, nil

}

func (this *RedisStore) CloseConn(conn *redis.Client) {
    this.pool.Close(conn)
}

func (this *RedisStore) Subscribe(c chan []string, channel string) (*redis.Client, error) {
    consumer := redis.New()
    err := consumer.ConnectNonBlock(this.server, this.port)
    if err != nil {
        return nil, err
    }

    go consumer.Subscribe(c, channel)
    <- c // ignore subscribe command

    return consumer, nil
}

func (this *RedisStore) Publish(channel string, message string) {
    publisher, err := this.GetConn()
    if(err != nil) {
        return
    }
    defer this.CloseConn(publisher)

    publisher.Publish(channel, message)

    publisher.Quit()
}

func (this *RedisStore) Save(sock *Socket) (error) {
    client, err := this.GetConn()
    if(err != nil) {
        return err
    }
    defer this.CloseConn(client)

    _, err = client.SAdd(this.clientsKey, sock.UID)
    if err != nil {
        return err
    }

    return nil
}

func (this *RedisStore) Remove(sock *Socket) (error) {
    client, err := this.GetConn()
    if(err != nil) {
        return err
    }
    defer this.CloseConn(client)

    _, err = client.SRem(this.clientsKey, sock.UID)
    if err != nil {
        return err
    }

    return nil
}

func (this *RedisStore) Clients() ([]string, error) {
    client, err := this.GetConn()
    if(err != nil) {
        return nil, err
    }
    defer this.CloseConn(client)

    socks, err1 := client.SMembers(this.clientsKey)
    if err1 != nil {
        return nil, err1
    }

    return socks, nil
}

func (this *RedisStore) Count() (int64, error) {
    client, err := this.GetConn()
    if(err != nil) {
        return 0, err
    }
    defer this.CloseConn(client)

    socks, err1 := client.SCard(this.clientsKey)
    if err1 != nil {
        return 0, err1
    }

    return socks, nil
}

func (this *RedisStore) SetPage(sock *Socket) error {
    client, err := this.GetConn()
    if(err != nil) {
        return err
    }
    defer this.CloseConn(client)

    _, err = client.HIncrBy(this.pageKey, sock.Page, 1)
    if err != nil {
        return err
    }

    return nil
}

func (this *RedisStore) UnsetPage(sock *Socket) error {
    client, err := this.GetConn()
    if(err != nil) {
        return err
    }
    defer this.CloseConn(client)

    var i int64
    i, err = client.HIncrBy(this.pageKey, sock.Page, -1)
    if err != nil {
        return err
    }

    if i < 0 {
        client.HSet(this.pageKey, sock.Page, "0")
    }

    return nil
}
<commit_msg>delete pages from redis store that have no users on them<commit_after>package main

import (
    "errors"
    "log"

    "menteslibres.net/gosexy/redis"
)

const ClientsKey = "SocketClients"
const PageKey    = "PageClients"

type RedisStore struct {
    clientsKey  string
    pageKey     string

    server      string
    port        uint
    pool        redisPool
}

//connection pool implimentation
type redisPool struct {
    connections chan *redis.Client
    maxIdle     int
    connFn      func() (*redis.Client, error) // function to create new connection.
}

func newRedisStore(redis_host string, redis_port uint) (*RedisStore) {

    return &RedisStore{
        ClientsKey,
        PageKey,

        redis_host,
        redis_port,

        redisPool{
            connections: make(chan *redis.Client, 6),
            maxIdle:     6,

            connFn:      func () (*redis.Client, error) {
                client := redis.New()
                err := client.Connect(redis_host, redis_port)

                if err != nil {
                    log.Printf("Redis connect failed: %s\n", err.Error())
                    return nil, err
                }

                return client, nil
            },
        },
    }

}

func (this *redisPool) Get() (*redis.Client, bool) {

    var conn *redis.Client
    select {
        case conn = <-this.connections:
        default:
            conn, err := this.connFn()
            if err != nil {
                return nil, false
            }

            return conn, true
    }

    if err := this.testConn(conn); err != nil {
        return this.Get() // if connection is bad, get the next one in line until base case is hit, then create new client
    }

    return conn, true
}

func (this *redisPool) Close(conn *redis.Client) {
    select {
        case this.connections <- conn:
            return
        default:
            conn.Quit()
    }
}

func (this *redisPool) testConn(conn *redis.Client) error {
    if _, err := conn.Ping(); err != nil {
        conn.Quit()
        return err
    }

    return nil
}

func (this *RedisStore) GetConn() (*redis.Client, error) {

    client, ok := this.pool.Get()
    if !ok {
        return nil, errors.New("Error while getting redis connection")
    }

    return client, nil

}

func (this *RedisStore) CloseConn(conn *redis.Client) {
    this.pool.Close(conn)
}

func (this *RedisStore) Subscribe(c chan []string, channel string) (*redis.Client, error) {
    consumer := redis.New()
    err := consumer.ConnectNonBlock(this.server, this.port)
    if err != nil {
        return nil, err
    }

    go consumer.Subscribe(c, channel)
    <- c // ignore subscribe command

    return consumer, nil
}

func (this *RedisStore) Publish(channel string, message string) {
    publisher, err := this.GetConn()
    if(err != nil) {
        return
    }
    defer this.CloseConn(publisher)

    publisher.Publish(channel, message)

    publisher.Quit()
}

func (this *RedisStore) Save(sock *Socket) (error) {
    client, err := this.GetConn()
    if(err != nil) {
        return err
    }
    defer this.CloseConn(client)

    _, err = client.SAdd(this.clientsKey, sock.UID)
    if err != nil {
        return err
    }

    return nil
}

func (this *RedisStore) Remove(sock *Socket) (error) {
    client, err := this.GetConn()
    if(err != nil) {
        return err
    }
    defer this.CloseConn(client)

    _, err = client.SRem(this.clientsKey, sock.UID)
    if err != nil {
        return err
    }

    return nil
}

func (this *RedisStore) Clients() ([]string, error) {
    client, err := this.GetConn()
    if(err != nil) {
        return nil, err
    }
    defer this.CloseConn(client)

    socks, err1 := client.SMembers(this.clientsKey)
    if err1 != nil {
        return nil, err1
    }

    return socks, nil
}

func (this *RedisStore) Count() (int64, error) {
    client, err := this.GetConn()
    if(err != nil) {
        return 0, err
    }
    defer this.CloseConn(client)

    socks, err1 := client.SCard(this.clientsKey)
    if err1 != nil {
        return 0, err1
    }

    return socks, nil
}

func (this *RedisStore) SetPage(sock *Socket) error {
    client, err := this.GetConn()
    if(err != nil) {
        return err
    }
    defer this.CloseConn(client)

    _, err = client.HIncrBy(this.pageKey, sock.Page, 1)
    if err != nil {
        return err
    }

    return nil
}

func (this *RedisStore) UnsetPage(sock *Socket) error {
    client, err := this.GetConn()
    if(err != nil) {
        return err
    }
    defer this.CloseConn(client)

    var i int64
    i, err = client.HIncrBy(this.pageKey, sock.Page, -1)
    if err != nil {
        return err
    }

    if i <= 0 {
        client.HDel(this.pageKey, sock.Page)
    }

    return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"github.com/blackjack/syslog"
	"math/rand"
	"net/mail"
	"net/smtp"
	"net/textproto"
	"os"
	"os/user"
	"reflect"
	"regexp"
	"strings"
	"time"
)

var config = &Configuration{
	Verbose:     false,
	ConfigFile:  "/etc/go-ssmtp.ini",
	Port:        25,
	Server:      "127.0.0.1",
	Postmaster:  "postmaster",
	ScanMessage: false,
}

type Configuration struct {
	Verbose                      bool
	ConfigFile                   string
	Hostname                     string
	Server                       string
	Port                         int
	Postmaster                   string
	ScanMessage                  bool
	Authentication_User          string
	Authentication_Password      string
	Authentication_Identity      string
	Authentication_Mechanism     string
	Authentication_ForceStartTLS bool
	Message_To                   []string
	Message_From                 string
	Message_FromName             string
	Message_FromCronDaemon       bool
}

func generateMessageId() string {
	const CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	bytes := make([]byte, 16)

	for i, r := 0, rand.New(rand.NewSource(time.Now().UnixNano())); i < len(bytes); i++ {
		bytes[i] = CHARS[r.Intn(len(CHARS))]
	}

	return string(bytes)
}

func (c *Configuration) ParseFile(file string) error {
	var matchSection = regexp.MustCompile(`^\[([^]]+)\]$`)
	var matchPair = regexp.MustCompile(`^([^#;=]+)=(.*)$`)

	f, err := os.Open(file)

	if err != nil {
		return err
	}

	defer f.Close()

	s := bufio.NewScanner(f)
	s.Split(bufio.ScanLines)
	var n, section = 1, ""

	for s.Scan() {
		l := s.Text()

		if 0 == len(l) || ';' == l[0] {
			continue
		} else if parts := matchSection.FindStringSubmatch(l); parts != nil {
			section = parts[1]
		} else if parts := matchPair.FindStringSubmatch(l); parts != nil {
			k, v := parts[1], parts[2]

			if section != "" {
				k = section + "_" + k
			}

			if !c.Get(k).IsValid() {
				fmt.Fprintf(os.Stderr, "Warning: unknown configuration variable %s, line %d\n", k, n)
			} else if "string" == config.Get(k).Type().String() {
				c.Get(k).SetString(v)
			} else if "bool" == config.Get(k).Type().String() {
				c.Get(k).SetBool("1" == v)
			}
		} else {
			return fmt.Errorf("Failed to parse config, line %d: %s", n, l)
		}

		n++
	}

	return nil
}

func (c *Configuration) Get(k string) reflect.Value {
	r := reflect.ValueOf(c)
	return reflect.Indirect(r).FieldByName(k)
}

func compose() (*mail.Message, error) {
	// Make sure we can re-use Stdin even after being consumed by mail.ReadMessage
	b := bytes.Buffer{}
	b.ReadFrom(os.Stdin)
	msg := b.String()

	m, err := mail.ReadMessage(bytes.NewBufferString(msg))
	if err != nil {
		if config.ScanMessage {
			return nil, fmt.Errorf("ScanMessage: cannot parse message: %s", err)
		}

		// Assume there are no headers in the message
		m = &mail.Message{
			Header: mail.Header(textproto.MIMEHeader{}),
			Body:   bufio.NewReader(bytes.NewBufferString(msg)),
		}
	}

	// Make sure all required fields are set
	if 0 == len(m.Header["From"]) {
		m.Header["From"] = []string{(&mail.Address{config.Message_FromName, config.Message_From}).String()}
	} else if from, err := mail.ParseAddress(m.Header["From"][0]); config.ScanMessage && err == nil {
		// Parse and put in config; to be used by c.Mail
		config.Message_From = from.Address
	}

	if 0 == len(m.Header["To"]) {
		m.Header["To"] = config.Message_To
	}

	if 0 == len(m.Header["Date"]) {
		m.Header["Date"] = []string{time.Now().Format("Mon, 2 Jan 2006 15:04:05 -0700")}
	}

	if 0 == len(m.Header["Message-Id"]) {
		m.Header["Message-Id"] = []string{"<GOSSMTP." + generateMessageId() + "@" + config.Hostname + ">"}
	}

	return m, nil
}

func connect() (*smtp.Client, error) {
	c, err := smtp.Dial(fmt.Sprintf("%s:%d", config.Server, config.Port))

	if err != nil {
		return nil, fmt.Errorf("while connecting to %s on port %d: %s", config.Server, config.Port, err)
	}

	if err := c.Hello(config.Hostname); err != nil {
		return nil, fmt.Errorf("while sending Hello `%s`: %s", config.Hostname, err)
	}

	if ok, _ := c.Extension("STARTTLS"); ok {
		if err = c.StartTLS(&tls.Config{ServerName: config.Server}); err != nil {
			return nil, fmt.Errorf("while enabling startTLS: %s", err)
		}
	} else if config.Authentication_ForceStartTLS {
		return nil, fmt.Errorf("server does not support StartTLS")
	}

	switch config.Authentication_Mechanism {
	case "CRAM-MD5":
		auth := smtp.CRAMMD5Auth(
			config.Authentication_User,
			config.Authentication_Password,
		)

		if ok, _ := c.Extension("AUTH"); ok {
			if err = c.Auth(auth); err != nil {
				return nil, fmt.Errorf("while authenticating: %s", err)
			} else if config.Verbose {
				return nil, fmt.Errorf("Info: using authentication: CRAM-MD5")
			}
		}

	case "PLAIN":
		auth := smtp.PlainAuth(
			config.Authentication_Identity,
			config.Authentication_User,
			config.Authentication_Password,
			config.Server,
		)

		if ok, _ := c.Extension("AUTH"); ok {
			if err = c.Auth(auth); err != nil {
				return nil, fmt.Errorf("while authenticating: %s", err)
			} else if config.Verbose {
				fmt.Println("Info: using authentication: PLAIN")
			}
		}

	default:
		if config.Verbose {
			fmt.Println("Info: not using authentication")
		}
	}

	return c, nil
}

func send(c *smtp.Client, m *mail.Message) error {
	if err := c.Mail(config.Message_From); err != nil {
		return fmt.Errorf("while setting From `%s`: %s", config.Message_From, err)
	}

	if config.ScanMessage {
		for _, i := range []string{"To", "Cc", "Bcc"} {
			if 0 == len(m.Header[i]) {
				continue
			}

			if l, err := m.Header.AddressList(i); err != nil {
				fmt.Fprintf(os.Stderr, "ScanMessage: Could not parse recipients in %s `%s`; %s", i, l, err)
			} else {
				for _, v := range l {
					config.Message_To = append(config.Message_To, v.Address)
				}
			}
		}

		if 0 == len(config.Message_To) {
			fmt.Fprintln(os.Stderr, "ScanMessage: No recipients found in message-body")
		}
	}

	for _, to := range config.Message_To {
		if err := c.Rcpt(to); err != nil {
			return fmt.Errorf("while setting Recipient `%s`: %s", to, err)
		}
	}

	w, err := c.Data()
	if err != nil {
		return fmt.Errorf("while setting Data: %s", err)
	}

	var s = ""
	for k, h := range m.Header {
		for _, v := range h {
			s += k + ": " + v + "\r\n"
		}
	}

	b := bytes.Buffer{}
	b.ReadFrom(m.Body)

	if _, err := w.Write([]byte(s + "\r\n" + b.String())); err != nil {
		return fmt.Errorf("while sending message: %s", err)
	}

	if err = w.Close(); err != nil {
		return fmt.Errorf("while closing message: %s", err)
	}

	if err = c.Quit(); err != nil {
		return fmt.Errorf("while closing connection: %s", err)
	}

	return nil
}

func init() {
	if h, err := os.Hostname(); err == nil {
		config.Hostname = h
	} else {
		config.Hostname = "localhost"
	}

	if u, err := user.Current(); err == nil {
		config.Message_From = u.Username + "@" + config.Hostname

		if u.Name != "" {
			config.Message_FromName = u.Name
		} else {
			config.Message_FromName = u.Username
		}
	}

	if -1 == strings.Index(config.Postmaster, "@") {
		config.Postmaster += "@" + config.Hostname
	}

	syslog.Openlog("go-ssmtp", syslog.LOG_PID, syslog.LOG_USER)

	var ignore bool
	flag.BoolVar(&ignore, "i", false, "Ignore")
	flag.BoolVar(&ignore, "odi", false, "Ignore")
	flag.BoolVar(&config.Message_FromCronDaemon, "FCronDaemon", false, "Hack to allow crond to work with flag pkg")
	flag.BoolVar(&config.Message_FromCronDaemon, "FAnacron", false, "Hack to allow crond to work with flag pkg")
	flag.BoolVar(&config.Verbose, "v", config.Verbose, "Enable verbose mode")
	flag.StringVar(&config.ConfigFile, "C", config.ConfigFile, "Use alternate configuration file")
	flag.StringVar(&config.Message_From, "f", config.Message_From, "Manually specify the sender-address of the email")
	flag.StringVar(&config.Message_FromName, "F", config.Message_FromName, "Manually specify the sender-name of the email")
	flag.BoolVar(&config.ScanMessage, "t", config.ScanMessage, "Scan message for recipients")
}

func main() {
	// Don't throw an error when encountering an unknown flag (for sendmail compat)
	flag.CommandLine.Init(os.Args[0], flag.ContinueOnError)

	flag.Parse()

	if config.Message_FromCronDaemon {
		config.Message_FromName = "CronDaemon"
	}

	if err := config.ParseFile(config.ConfigFile); err != nil {
		fmt.Fprintf(os.Stderr, "Error while parsing configuration: %s\n", err)
		os.Exit(2)
	}

	// Map all local users to Postmaster address
	config.Message_To = flag.Args()
	for i, to := range config.Message_To {
		if -1 == strings.Index(to, "@") {
			config.Message_To[i] = config.Postmaster
		}
	}

	if config.Verbose {
		fmt.Printf("%#v\n", *config)
	}

	if len(config.Message_To) == 0 && !config.ScanMessage {
		fmt.Fprintln(os.Stderr, "Error: no recipients supplied")
		os.Exit(1)
	}

	m, err := compose()
	if err != nil {
		syslog.Errf("ComposeError: %s", err)
		fmt.Fprintf(os.Stderr, "ComposeError: %s\n", err)
		os.Exit(2)
	}

	c, err := connect()
	if err != nil {
		syslog.Errf("ConnectError: %s", err)
		fmt.Fprintf(os.Stderr, "ConnectError: %s\n", err)
		os.Exit(3)
	}

	if err := send(c, m); err != nil {
		syslog.Errf("SendError: %s", err)
		fmt.Fprintf(os.Stderr, "SendError: %s\n", err)
		os.Exit(4)
	}

	var subject string = "(unknown)"
	if len(m.Header["Subject"]) > 0 {
		subject = m.Header["Subject"][0]
	}

	syslog.Syslogf(syslog.LOG_INFO, "[%s] Sent mail; subject \"%s\"; from %s; to %#v", m.Header["Message-Id"][0], subject, config.Message_From, config.Message_To)

	if config.Verbose {
		fmt.Println("Info: send successful")
	}
}
<commit_msg>replace smtp.Dial with a custom Dialer with DualStack enabled<commit_after>package main

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"github.com/blackjack/syslog"
	"math/rand"
	"net"
	"net/mail"
	"net/smtp"
	"net/textproto"
	"os"
	"os/user"
	"reflect"
	"regexp"
	"strings"
	"time"
)

var config = &Configuration{
	Verbose:     false,
	ConfigFile:  "/etc/go-ssmtp.ini",
	Port:        25,
	Server:      "127.0.0.1",
	Postmaster:  "postmaster",
	ScanMessage: false,
}

type Configuration struct {
	Verbose                      bool
	ConfigFile                   string
	Hostname                     string
	Server                       string
	Port                         int
	Postmaster                   string
	ScanMessage                  bool
	Authentication_User          string
	Authentication_Password      string
	Authentication_Identity      string
	Authentication_Mechanism     string
	Authentication_ForceStartTLS bool
	Message_To                   []string
	Message_From                 string
	Message_FromName             string
	Message_FromCronDaemon       bool
}

func generateMessageId() string {
	const CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	bytes := make([]byte, 16)

	for i, r := 0, rand.New(rand.NewSource(time.Now().UnixNano())); i < len(bytes); i++ {
		bytes[i] = CHARS[r.Intn(len(CHARS))]
	}

	return string(bytes)
}

func (c *Configuration) ParseFile(file string) error {
	var matchSection = regexp.MustCompile(`^\[([^]]+)\]$`)
	var matchPair = regexp.MustCompile(`^([^#;=]+)=(.*)$`)

	f, err := os.Open(file)

	if err != nil {
		return err
	}

	defer f.Close()

	s := bufio.NewScanner(f)
	s.Split(bufio.ScanLines)
	var n, section = 1, ""

	for s.Scan() {
		l := s.Text()

		if 0 == len(l) || ';' == l[0] {
			continue
		} else if parts := matchSection.FindStringSubmatch(l); parts != nil {
			section = parts[1]
		} else if parts := matchPair.FindStringSubmatch(l); parts != nil {
			k, v := parts[1], parts[2]

			if section != "" {
				k = section + "_" + k
			}

			if !c.Get(k).IsValid() {
				fmt.Fprintf(os.Stderr, "Warning: unknown configuration variable %s, line %d\n", k, n)
			} else if "string" == config.Get(k).Type().String() {
				c.Get(k).SetString(v)
			} else if "bool" == config.Get(k).Type().String() {
				c.Get(k).SetBool("1" == v)
			}
		} else {
			return fmt.Errorf("Failed to parse config, line %d: %s", n, l)
		}

		n++
	}

	return nil
}

func (c *Configuration) Get(k string) reflect.Value {
	r := reflect.ValueOf(c)
	return reflect.Indirect(r).FieldByName(k)
}

func compose() (*mail.Message, error) {
	// Make sure we can re-use Stdin even after being consumed by mail.ReadMessage
	b := bytes.Buffer{}
	b.ReadFrom(os.Stdin)
	msg := b.String()

	m, err := mail.ReadMessage(bytes.NewBufferString(msg))
	if err != nil {
		if config.ScanMessage {
			return nil, fmt.Errorf("ScanMessage: cannot parse message: %s", err)
		}

		// Assume there are no headers in the message
		m = &mail.Message{
			Header: mail.Header(textproto.MIMEHeader{}),
			Body:   bufio.NewReader(bytes.NewBufferString(msg)),
		}
	}

	// Make sure all required fields are set
	if 0 == len(m.Header["From"]) {
		m.Header["From"] = []string{(&mail.Address{config.Message_FromName, config.Message_From}).String()}
	} else if from, err := mail.ParseAddress(m.Header["From"][0]); config.ScanMessage && err == nil {
		// Parse and put in config; to be used by c.Mail
		config.Message_From = from.Address
	}

	if 0 == len(m.Header["To"]) {
		m.Header["To"] = config.Message_To
	}

	if 0 == len(m.Header["Date"]) {
		m.Header["Date"] = []string{time.Now().Format("Mon, 2 Jan 2006 15:04:05 -0700")}
	}

	if 0 == len(m.Header["Message-Id"]) {
		m.Header["Message-Id"] = []string{"<GOSSMTP." + generateMessageId() + "@" + config.Hostname + ">"}
	}

	return m, nil
}

func connect() (*smtp.Client, error) {
	// Copied from smtp.Dial; but enable DualStack
	d := net.Dialer{DualStack: true}
	conn, err := d.Dial("tcp", fmt.Sprintf("%s:%d", config.Server, config.Port))
	if err != nil {
		return nil, fmt.Errorf("while connecting to %s on port %d: %s", config.Server, config.Port, err)
	}

	c, err := smtp.NewClient(conn, config.Server)

	if err != nil {
		return nil, fmt.Errorf("while connecting to %s on port %d: %s", config.Server, config.Port, err)
	}

	if err := c.Hello(config.Hostname); err != nil {
		return nil, fmt.Errorf("while sending Hello `%s`: %s", config.Hostname, err)
	}

	if ok, _ := c.Extension("STARTTLS"); ok {
		if err = c.StartTLS(&tls.Config{ServerName: config.Server}); err != nil {
			return nil, fmt.Errorf("while enabling StartTLS: %s", err)
		}
	} else if config.Authentication_ForceStartTLS {
		return nil, fmt.Errorf("server does not support StartTLS")
	}

	switch config.Authentication_Mechanism {
	case "CRAM-MD5":
		auth := smtp.CRAMMD5Auth(
			config.Authentication_User,
			config.Authentication_Password,
		)

		if ok, _ := c.Extension("AUTH"); ok {
			if err = c.Auth(auth); err != nil {
				return nil, fmt.Errorf("while authenticating: %s", err)
			} else if config.Verbose {
				return nil, fmt.Errorf("Info: using authentication: CRAM-MD5")
			}
		}

	case "PLAIN":
		auth := smtp.PlainAuth(
			config.Authentication_Identity,
			config.Authentication_User,
			config.Authentication_Password,
			config.Server,
		)

		if ok, _ := c.Extension("AUTH"); ok {
			if err = c.Auth(auth); err != nil {
				return nil, fmt.Errorf("while authenticating: %s", err)
			} else if config.Verbose {
				fmt.Println("Info: using authentication: PLAIN")
			}
		}

	default:
		if config.Verbose {
			fmt.Println("Info: not using authentication")
		}
	}

	return c, nil
}

func send(c *smtp.Client, m *mail.Message) error {
	if err := c.Mail(config.Message_From); err != nil {
		return fmt.Errorf("while setting From `%s`: %s", config.Message_From, err)
	}

	if config.ScanMessage {
		for _, i := range []string{"To", "Cc", "Bcc"} {
			if 0 == len(m.Header[i]) {
				continue
			}

			if l, err := m.Header.AddressList(i); err != nil {
				fmt.Fprintf(os.Stderr, "ScanMessage: Could not parse recipients in %s `%s`; %s", i, l, err)
			} else {
				for _, v := range l {
					config.Message_To = append(config.Message_To, v.Address)
				}
			}
		}

		if 0 == len(config.Message_To) {
			fmt.Fprintln(os.Stderr, "ScanMessage: No recipients found in message-body")
		}
	}

	for _, to := range config.Message_To {
		if err := c.Rcpt(to); err != nil {
			return fmt.Errorf("while setting Recipient `%s`: %s", to, err)
		}
	}

	w, err := c.Data()
	if err != nil {
		return fmt.Errorf("while setting Data: %s", err)
	}

	var s = ""
	for k, h := range m.Header {
		for _, v := range h {
			s += k + ": " + v + "\r\n"
		}
	}

	b := bytes.Buffer{}
	b.ReadFrom(m.Body)

	if _, err := w.Write([]byte(s + "\r\n" + b.String())); err != nil {
		return fmt.Errorf("while sending message: %s", err)
	}

	if err = w.Close(); err != nil {
		return fmt.Errorf("while closing message: %s", err)
	}

	if err = c.Quit(); err != nil {
		return fmt.Errorf("while closing connection: %s", err)
	}

	return nil
}

func init() {
	if h, err := os.Hostname(); err == nil {
		config.Hostname = h
	} else {
		config.Hostname = "localhost"
	}

	if u, err := user.Current(); err == nil {
		config.Message_From = u.Username + "@" + config.Hostname

		if u.Name != "" {
			config.Message_FromName = u.Name
		} else {
			config.Message_FromName = u.Username
		}
	}

	if -1 == strings.Index(config.Postmaster, "@") {
		config.Postmaster += "@" + config.Hostname
	}

	syslog.Openlog("go-ssmtp", syslog.LOG_PID, syslog.LOG_USER)

	var ignore bool
	flag.BoolVar(&ignore, "i", false, "Ignore")
	flag.BoolVar(&ignore, "odi", false, "Ignore")
	flag.BoolVar(&config.Message_FromCronDaemon, "FCronDaemon", false, "Hack to allow crond to work with flag pkg")
	flag.BoolVar(&config.Message_FromCronDaemon, "FAnacron", false, "Hack to allow crond to work with flag pkg")
	flag.BoolVar(&config.Verbose, "v", config.Verbose, "Enable verbose mode")
	flag.StringVar(&config.ConfigFile, "C", config.ConfigFile, "Use alternate configuration file")
	flag.StringVar(&config.Message_From, "f", config.Message_From, "Manually specify the sender-address of the email")
	flag.StringVar(&config.Message_FromName, "F", config.Message_FromName, "Manually specify the sender-name of the email")
	flag.BoolVar(&config.ScanMessage, "t", config.ScanMessage, "Scan message for recipients")
}

func main() {
	// Don't throw an error when encountering an unknown flag (for sendmail compat)
	flag.CommandLine.Init(os.Args[0], flag.ContinueOnError)

	flag.Parse()

	if config.Message_FromCronDaemon {
		config.Message_FromName = "CronDaemon"
	}

	if err := config.ParseFile(config.ConfigFile); err != nil {
		fmt.Fprintf(os.Stderr, "Error while parsing configuration: %s\n", err)
		os.Exit(2)
	}

	// Map all local users to Postmaster address
	config.Message_To = flag.Args()
	for i, to := range config.Message_To {
		if -1 == strings.Index(to, "@") {
			config.Message_To[i] = config.Postmaster
		}
	}

	if config.Verbose {
		fmt.Printf("%#v\n", *config)
	}

	if len(config.Message_To) == 0 && !config.ScanMessage {
		fmt.Fprintln(os.Stderr, "Error: no recipients supplied")
		os.Exit(1)
	}

	m, err := compose()
	if err != nil {
		syslog.Errf("ComposeError: %s", err)
		fmt.Fprintf(os.Stderr, "ComposeError: %s\n", err)
		os.Exit(2)
	}

	c, err := connect()
	if err != nil {
		syslog.Errf("ConnectError: %s", err)
		fmt.Fprintf(os.Stderr, "ConnectError: %s\n", err)
		os.Exit(3)
	}

	if err := send(c, m); err != nil {
		syslog.Errf("SendError: %s", err)
		fmt.Fprintf(os.Stderr, "SendError: %s\n", err)
		os.Exit(4)
	}

	var subject string = "(unknown)"
	if len(m.Header["Subject"]) > 0 {
		subject = m.Header["Subject"][0]
	}

	syslog.Syslogf(syslog.LOG_INFO, "[%s] Sent mail; subject \"%s\"; from %s; to %#v", m.Header["Message-Id"][0], subject, config.Message_From, config.Message_To)

	if config.Verbose {
		fmt.Println("Info: send successful")
	}
}<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

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

const (
	roleLowerBody        = "legislatorLowerBody"
	roleUpperBody        = "legislatorUpperBody"
	roleHeadOfGovernment = "headOfGovernment"
	roleLevelCountry     = "country"
	roleLevelState       = "administrativeArea1"
	areaHouse            = "House"
	areaSenate           = "Senate"
	areaGovernor         = "Governor"
)

var baseURL = "https://www.googleapis.com/civicinfo/v2/representatives"
var phoneRegex = regexp.MustCompile(`\((\d{3})\)\s+(\d{3})[\s\-](\d{4})`)
var roleLevel = [2]string{"country", "administrativeArea1"}

// RepFinder provides a mechanism to find local reps given an address.
type RepFinder interface {
	GetReps(address string) (*LocalReps, *Address, error)
}

// APIError is an error returned by the Google civic API, which also
// implements the error interface.
type APIError struct {
	Code    int
	Message string
	Errors  []struct {
		Domain  string
		Reason  string
		Message string
	}
}

func (ae *APIError) Error() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "%d %s", ae.Code, ae.Message)
	for _, e := range ae.Errors {
		if e.Message != ae.Message { // don't duplicate messages
			fmt.Fprintf(&buf, ";[domain=%s, reason=%s: %s]", e.Domain, e.Reason, e.Message)
		}
	}
	return buf.String()
}

// apiResponse is the response from the civic API. It encapsulates valid
// responses that set the normalized input, offices and officials,
// as well as error responses.
type apiResponse struct {
	NormalizedInput *Address
	Offices         []struct {
		Name            string
		DivisionId      string
		Levels          []string
		Roles           []string
		OfficialIndices []int
	}
	Officials []struct {
		Name     string
		Address  []Address
		Party    string
		Phones   []string
		PhotoUrl string
		Channels []struct {
			Id   string
			Type string
		}
	}
	Error *APIError
}

// toLocalReps converts an API response to a set of local reps. In addition,
// it also returns the normalized address for which the response is valid.
func (r *apiResponse) toLocalReps() (*LocalReps, *Address, error) {
	if r.Error != nil {
		return nil, nil, r.Error
	}
	if len(r.Offices) == 0 {
		return nil, nil, fmt.Errorf("no offices found ")
	}
	ret := &LocalReps{}
	for _, o := range r.Offices {
		var area string
		AreaLoop:
			for _, level := range o.Levels {
				for _, role := range o.Roles {
					switch {
					case level == roleLevelCountry && role == roleLowerBody:
						area = areaHouse
						continue AreaLoop
					case level == roleLevelCountry && role == roleUpperBody:
						area = areaSenate
						continue AreaLoop
					// Civic API returns governor and deputy governor under same
					// role level and role, comparing the name is the best we can do
					// with this dataset
					case level == roleLevelState && role == roleHeadOfGovernment && o.Name == "Governor":
						area = areaGovernor
						continue AreaLoop
					}
				}
			}
		if area != "" {
			for _, i := range o.OfficialIndices {
				official := r.Officials[i]
				var phone string
				if len(official.Phones) > 0 {
					phone = official.Phones[0]
				} else {
					continue
				}
				c := &Contact{
					ID:       fmt.Sprintf("%s-%s", r.NormalizedInput.State, strings.Replace(official.Name, " ", "", -1)),
					Name:     official.Name,
					Phone:    reformattedPhone(phone),
					PhotoURL: official.PhotoUrl,
					Party:    official.Party,
					State:    r.NormalizedInput.State,
					Area:     area,
				}
				switch area {
				case areaHouse:
					ret.HouseRep = c
				case areaSenate:
					ret.Senators = append(ret.Senators, c)
				case areaGovernor:
					ret.Governor = c
				}
			}
		}
	}
	return ret, r.NormalizedInput, nil
}

// civicAPI provides a semantic interface to the Google civic API.
type civicAPI struct {
	key string
	c   *http.Client
}

// NewCivicAPI returns an instance of the civic API.
func NewCivicAPI(key string, client *http.Client) RepFinder {
	return &civicAPI{
		key: key,
		c:   client,
	}
}

// GetReps returns local representatives for the supplied address.
func (c *civicAPI) GetReps(address string) (*LocalReps, *Address, error) {
	var u, _ = url.Parse(baseURL)
	q := u.Query()
	for _, l := range roleLevel {
		q.Add("levels", l)
	}
	q.Set("key", c.key)
	q.Set("address", url.QueryEscape(address))
	u.RawQuery = q.Encode()
	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, nil, err
	}
	res, err := c.c.Do(req)
	if err != nil {
		return nil, nil, err
	}
	defer res.Body.Close()
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, nil, err
	}
	var ar apiResponse
	err = json.Unmarshal(b, &ar)
	if err != nil {
		return nil, nil, err
	}
	return ar.toLocalReps()
}

// repCache implements a cache layer on top of a delegate rep finder.
type repCache struct {
	delegate RepFinder
	cache    *cache.Cache
}

type cacheItem struct {
	reps LocalReps
	addr Address
}

func NewRepCache(delegate RepFinder, ttl time.Duration, gc time.Duration) RepFinder {
	return &repCache{
		delegate: delegate,
		cache:    cache.New(ttl, gc),
	}
}

// reformat phone numbers that come from the google civic API
func reformattedPhone(civicPhone string) string {
	result := phoneRegex.FindStringSubmatch(civicPhone)

	if len(result) >= 3 {
		return fmt.Sprintf("%s-%s-%s", result[1], result[2], result[3])
	}

	return civicPhone
}

// GetReps returns local representatives for the supplied address.
func (r *repCache) GetReps(address string) (*LocalReps, *Address, error) {
	data, ok := r.cache.Get(address)
	if ok {
		ci := data.(*cacheItem)
		reps := ci.reps
		addr := ci.addr
		return &reps, &addr, nil
	}
	reps, addr, err := r.delegate.GetReps(address)
	if err != nil {
		return nil, nil, err
	}
	ci := &cacheItem{reps: *reps, addr: *addr}
	r.cache.Set(address, ci, cache.DefaultExpiration)
	return reps, addr, nil
}
<commit_msg>gofmt civic.go<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

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

const (
	roleLowerBody        = "legislatorLowerBody"
	roleUpperBody        = "legislatorUpperBody"
	roleHeadOfGovernment = "headOfGovernment"
	roleLevelCountry     = "country"
	roleLevelState       = "administrativeArea1"
	areaHouse            = "House"
	areaSenate           = "Senate"
	areaGovernor         = "Governor"
)

var baseURL = "https://www.googleapis.com/civicinfo/v2/representatives"
var phoneRegex = regexp.MustCompile(`\((\d{3})\)\s+(\d{3})[\s\-](\d{4})`)
var roleLevel = [2]string{"country", "administrativeArea1"}

// RepFinder provides a mechanism to find local reps given an address.
type RepFinder interface {
	GetReps(address string) (*LocalReps, *Address, error)
}

// APIError is an error returned by the Google civic API, which also
// implements the error interface.
type APIError struct {
	Code    int
	Message string
	Errors  []struct {
		Domain  string
		Reason  string
		Message string
	}
}

func (ae *APIError) Error() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "%d %s", ae.Code, ae.Message)
	for _, e := range ae.Errors {
		if e.Message != ae.Message { // don't duplicate messages
			fmt.Fprintf(&buf, ";[domain=%s, reason=%s: %s]", e.Domain, e.Reason, e.Message)
		}
	}
	return buf.String()
}

// apiResponse is the response from the civic API. It encapsulates valid
// responses that set the normalized input, offices and officials,
// as well as error responses.
type apiResponse struct {
	NormalizedInput *Address
	Offices         []struct {
		Name            string
		DivisionId      string
		Levels          []string
		Roles           []string
		OfficialIndices []int
	}
	Officials []struct {
		Name     string
		Address  []Address
		Party    string
		Phones   []string
		PhotoUrl string
		Channels []struct {
			Id   string
			Type string
		}
	}
	Error *APIError
}

// toLocalReps converts an API response to a set of local reps. In addition,
// it also returns the normalized address for which the response is valid.
func (r *apiResponse) toLocalReps() (*LocalReps, *Address, error) {
	if r.Error != nil {
		return nil, nil, r.Error
	}
	if len(r.Offices) == 0 {
		return nil, nil, fmt.Errorf("no offices found ")
	}
	ret := &LocalReps{}
	for _, o := range r.Offices {
		var area string
	AreaLoop:
		for _, level := range o.Levels {
			for _, role := range o.Roles {
				switch {
				case level == roleLevelCountry && role == roleLowerBody:
					area = areaHouse
					continue AreaLoop
				case level == roleLevelCountry && role == roleUpperBody:
					area = areaSenate
					continue AreaLoop
				// Civic API returns governor and deputy governor under same
				// role level and role, comparing the name is the best we can do
				// with this dataset
				case level == roleLevelState && role == roleHeadOfGovernment && o.Name == "Governor":
					area = areaGovernor
					continue AreaLoop
				}
			}
		}
		if area != "" {
			for _, i := range o.OfficialIndices {
				official := r.Officials[i]
				var phone string
				if len(official.Phones) > 0 {
					phone = official.Phones[0]
				} else {
					continue
				}
				c := &Contact{
					ID:       fmt.Sprintf("%s-%s", r.NormalizedInput.State, strings.Replace(official.Name, " ", "", -1)),
					Name:     official.Name,
					Phone:    reformattedPhone(phone),
					PhotoURL: official.PhotoUrl,
					Party:    official.Party,
					State:    r.NormalizedInput.State,
					Area:     area,
				}
				switch area {
				case areaHouse:
					ret.HouseRep = c
				case areaSenate:
					ret.Senators = append(ret.Senators, c)
				case areaGovernor:
					ret.Governor = c
				}
			}
		}
	}
	return ret, r.NormalizedInput, nil
}

// civicAPI provides a semantic interface to the Google civic API.
type civicAPI struct {
	key string
	c   *http.Client
}

// NewCivicAPI returns an instance of the civic API.
func NewCivicAPI(key string, client *http.Client) RepFinder {
	return &civicAPI{
		key: key,
		c:   client,
	}
}

// GetReps returns local representatives for the supplied address.
func (c *civicAPI) GetReps(address string) (*LocalReps, *Address, error) {
	var u, _ = url.Parse(baseURL)
	q := u.Query()
	for _, l := range roleLevel {
		q.Add("levels", l)
	}
	q.Set("key", c.key)
	q.Set("address", url.QueryEscape(address))
	u.RawQuery = q.Encode()
	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, nil, err
	}
	res, err := c.c.Do(req)
	if err != nil {
		return nil, nil, err
	}
	defer res.Body.Close()
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, nil, err
	}
	var ar apiResponse
	err = json.Unmarshal(b, &ar)
	if err != nil {
		return nil, nil, err
	}
	return ar.toLocalReps()
}

// repCache implements a cache layer on top of a delegate rep finder.
type repCache struct {
	delegate RepFinder
	cache    *cache.Cache
}

type cacheItem struct {
	reps LocalReps
	addr Address
}

func NewRepCache(delegate RepFinder, ttl time.Duration, gc time.Duration) RepFinder {
	return &repCache{
		delegate: delegate,
		cache:    cache.New(ttl, gc),
	}
}

// reformat phone numbers that come from the google civic API
func reformattedPhone(civicPhone string) string {
	result := phoneRegex.FindStringSubmatch(civicPhone)

	if len(result) >= 3 {
		return fmt.Sprintf("%s-%s-%s", result[1], result[2], result[3])
	}

	return civicPhone
}

// GetReps returns local representatives for the supplied address.
func (r *repCache) GetReps(address string) (*LocalReps, *Address, error) {
	data, ok := r.cache.Get(address)
	if ok {
		ci := data.(*cacheItem)
		reps := ci.reps
		addr := ci.addr
		return &reps, &addr, nil
	}
	reps, addr, err := r.delegate.GetReps(address)
	if err != nil {
		return nil, nil, err
	}
	ci := &cacheItem{reps: *reps, addr: *addr}
	r.cache.Set(address, ci, cache.DefaultExpiration)
	return reps, addr, nil
}
<|endoftext|>