<commit_before>package engine

import (
	"context"
	"io"
	"time"

	enginetypes "github.com/projecteru2/core/engine/types"
	coresource "github.com/projecteru2/core/source"
)

// TimeoutAPI wraps engine implementation with context.WithTimeout
type TimeoutAPI struct {
	api     API
	timeout time.Duration
}

// WithGlobalTimeout .
func WithGlobalTimeout(api API, timeout time.Duration) API {
	return &TimeoutAPI{
		api:     api,
		timeout: timeout,
	}
}

// Info .
func (a *TimeoutAPI) Info(ctx context.Context) (*enginetypes.Info, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.Info(ctx)
}

// ExecCreate .
func (a *TimeoutAPI) ExecCreate(ctx context.Context, target string, config *enginetypes.ExecConfig) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ExecCreate(ctx, target, config)
}

// ExecAttach .
func (a *TimeoutAPI) ExecAttach(ctx context.Context, execID string, tty bool) (io.ReadCloser, io.WriteCloser, error) {
	return a.api.ExecAttach(ctx, execID, tty)
}

// Execute .
func (a *TimeoutAPI) Execute(ctx context.Context, target string, config *enginetypes.ExecConfig) (string, io.ReadCloser, io.WriteCloser, error) {
	return a.api.Execute(ctx, target, config)
}

// ExecResize .
func (a *TimeoutAPI) ExecResize(ctx context.Context, execID string, height, width uint) (err error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ExecResize(ctx, execID, height, width)
}

// ExecExitCode .
func (a *TimeoutAPI) ExecExitCode(ctx context.Context, execID string) (int, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ExecExitCode(ctx, execID)
}

// NetworkConnect .
func (a *TimeoutAPI) NetworkConnect(ctx context.Context, network, target, ipv4, ipv6 string) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.NetworkConnect(ctx, network, target, ipv4, ipv6)
}

// NetworkDisconnect .
func (a *TimeoutAPI) NetworkDisconnect(ctx context.Context, network, target string, force bool) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.NetworkDisconnect(ctx, network, target, force)
}

// NetworkList .
func (a *TimeoutAPI) NetworkList(ctx context.Context, drivers []string) ([]*enginetypes.Network, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.NetworkList(ctx, drivers)
}

// ImageList .
func (a *TimeoutAPI) ImageList(ctx context.Context, image string) ([]*enginetypes.Image, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageList(ctx, image)
}

// ImageRemove .
func (a *TimeoutAPI) ImageRemove(ctx context.Context, image string, force, prune bool) ([]string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageRemove(ctx, image, force, prune)
}

// ImagesPrune .
func (a *TimeoutAPI) ImagesPrune(ctx context.Context) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImagesPrune(ctx)
}

// ImagePull .
func (a *TimeoutAPI) ImagePull(ctx context.Context, ref string, all bool) (io.ReadCloser, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImagePull(ctx, ref, all)
}

// ImagePush .
func (a *TimeoutAPI) ImagePush(ctx context.Context, ref string) (io.ReadCloser, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImagePush(ctx, ref)
}

// ImageBuild .
func (a *TimeoutAPI) ImageBuild(ctx context.Context, input io.Reader, refs []string) (io.ReadCloser, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageBuild(ctx, input, refs)
}

// ImageBuildCachePrune .
func (a *TimeoutAPI) ImageBuildCachePrune(ctx context.Context, all bool) (uint64, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageBuildCachePrune(ctx, all)
}

// ImageLocalDigests .
func (a *TimeoutAPI) ImageLocalDigests(ctx context.Context, image string) ([]string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageLocalDigests(ctx, image)
}

// ImageRemoteDigest .
func (a *TimeoutAPI) ImageRemoteDigest(ctx context.Context, image string) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageRemoteDigest(ctx, image)
}

// ImageBuildFromExist .
func (a *TimeoutAPI) ImageBuildFromExist(ctx context.Context, ID, name string) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageBuildFromExist(ctx, ID, name)
}

// BuildRefs .
func (a *TimeoutAPI) BuildRefs(ctx context.Context, name string, tags []string) []string {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.BuildRefs(ctx, name, tags)
}

// BuildContent .
func (a *TimeoutAPI) BuildContent(ctx context.Context, scm coresource.Source, opts *enginetypes.BuildContentOptions) (string, io.Reader, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.BuildContent(ctx, scm, opts)
}

// VirtualizationCreate .
func (a *TimeoutAPI) VirtualizationCreate(ctx context.Context, opts *enginetypes.VirtualizationCreateOptions) (*enginetypes.VirtualizationCreated, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationCreate(ctx, opts)
}

// VirtualizationCopyTo .
func (a *TimeoutAPI) VirtualizationCopyTo(ctx context.Context, ID, target string, content io.Reader, AllowOverwriteDirWithFile, CopyUIDGID bool) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationCopyTo(ctx, ID, target, content, AllowOverwriteDirWithFile, CopyUIDGID)
}

// VirtualizationStart .
func (a *TimeoutAPI) VirtualizationStart(ctx context.Context, ID string) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationStart(ctx, ID)
}

// VirtualizationStop .
func (a *TimeoutAPI) VirtualizationStop(ctx context.Context, ID string) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationStop(ctx, ID)
}

// VirtualizationRemove .
func (a *TimeoutAPI) VirtualizationRemove(ctx context.Context, ID string, volumes, force bool) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationRemove(ctx, ID, volumes, force)
}

// VirtualizationInspect .
func (a *TimeoutAPI) VirtualizationInspect(ctx context.Context, ID string) (*enginetypes.VirtualizationInfo, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationInspect(ctx, ID)
}

// VirtualizationLogs .
func (a *TimeoutAPI) VirtualizationLogs(ctx context.Context, opts *enginetypes.VirtualizationLogStreamOptions) (io.ReadCloser, error) {
	return a.api.VirtualizationLogs(ctx, opts)
}

// VirtualizationAttach .
func (a *TimeoutAPI) VirtualizationAttach(ctx context.Context, ID string, stream, stdin bool) (io.ReadCloser, io.WriteCloser, error) {
	return a.api.VirtualizationAttach(ctx, ID, stream, stdin)
}

// VirtualizationResize .
func (a *TimeoutAPI) VirtualizationResize(ctx context.Context, ID string, height, width uint) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationResize(ctx, ID, height, width)
}

// VirtualizationWait .
func (a *TimeoutAPI) VirtualizationWait(ctx context.Context, ID, state string) (*enginetypes.VirtualizationWaitResult, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationWait(ctx, ID, state)
}

// VirtualizationUpdateResource .
func (a *TimeoutAPI) VirtualizationUpdateResource(ctx context.Context, ID string, opts *enginetypes.VirtualizationResource) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationUpdateResource(ctx, ID, opts)
}

// VirtualizationCopyFrom .
func (a *TimeoutAPI) VirtualizationCopyFrom(ctx context.Context, ID, path string) (io.ReadCloser, string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationCopyFrom(ctx, ID, path)
}

// ResourceValidate .
func (a *TimeoutAPI) ResourceValidate(ctx context.Context, cpu float64, cpumap map[string]int64, memory, storage int64) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ResourceValidate(ctx, cpu, cpumap, memory, storage)
}
<commit_msg>make TimeoutAPI compatible with new Image methods<commit_after>package engine

import (
	"context"
	"io"
	"time"

	enginetypes "github.com/projecteru2/core/engine/types"
	coresource "github.com/projecteru2/core/source"
)

// TimeoutAPI wraps engine implementation with context.WithTimeout
type TimeoutAPI struct {
	api     API
	timeout time.Duration
}

// WithGlobalTimeout .
func WithGlobalTimeout(api API, timeout time.Duration) API {
	return &TimeoutAPI{
		api:     api,
		timeout: timeout,
	}
}

// Info .
func (a *TimeoutAPI) Info(ctx context.Context) (*enginetypes.Info, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.Info(ctx)
}

// ExecCreate .
func (a *TimeoutAPI) ExecCreate(ctx context.Context, target string, config *enginetypes.ExecConfig) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ExecCreate(ctx, target, config)
}

// ExecAttach .
func (a *TimeoutAPI) ExecAttach(ctx context.Context, execID string, tty bool) (io.ReadCloser, io.WriteCloser, error) {
	return a.api.ExecAttach(ctx, execID, tty)
}

// Execute .
func (a *TimeoutAPI) Execute(ctx context.Context, target string, config *enginetypes.ExecConfig) (string, io.ReadCloser, io.WriteCloser, error) {
	return a.api.Execute(ctx, target, config)
}

// ExecResize .
func (a *TimeoutAPI) ExecResize(ctx context.Context, execID string, height, width uint) (err error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ExecResize(ctx, execID, height, width)
}

// ExecExitCode .
func (a *TimeoutAPI) ExecExitCode(ctx context.Context, execID string) (int, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ExecExitCode(ctx, execID)
}

// NetworkConnect .
func (a *TimeoutAPI) NetworkConnect(ctx context.Context, network, target, ipv4, ipv6 string) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.NetworkConnect(ctx, network, target, ipv4, ipv6)
}

// NetworkDisconnect .
func (a *TimeoutAPI) NetworkDisconnect(ctx context.Context, network, target string, force bool) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.NetworkDisconnect(ctx, network, target, force)
}

// NetworkList .
func (a *TimeoutAPI) NetworkList(ctx context.Context, drivers []string) ([]*enginetypes.Network, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.NetworkList(ctx, drivers)
}

// ImageList .
func (a *TimeoutAPI) ImageList(ctx context.Context, image string) ([]*enginetypes.Image, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageList(ctx, image)
}

// ImageRemove .
func (a *TimeoutAPI) ImageRemove(ctx context.Context, image string, force, prune bool) ([]string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageRemove(ctx, image, force, prune)
}

// ImagesPrune .
func (a *TimeoutAPI) ImagesPrune(ctx context.Context) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImagesPrune(ctx)
}

// ImagePull .
func (a *TimeoutAPI) ImagePull(ctx context.Context, ref string, all bool) (chan *enginetypes.ImageMessage, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImagePull(ctx, ref, all)
}

// ImagePush .
func (a *TimeoutAPI) ImagePush(ctx context.Context, ref string) (chan *enginetypes.ImageMessage, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImagePush(ctx, ref)
}

// ImageBuild .
func (a *TimeoutAPI) ImageBuild(ctx context.Context, input io.Reader, refs []string) (io.ReadCloser, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageBuild(ctx, input, refs)
}

// ImageBuildCachePrune .
func (a *TimeoutAPI) ImageBuildCachePrune(ctx context.Context, all bool) (uint64, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageBuildCachePrune(ctx, all)
}

// ImageLocalDigests .
func (a *TimeoutAPI) ImageLocalDigests(ctx context.Context, image string) ([]string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageLocalDigests(ctx, image)
}

// ImageRemoteDigest .
func (a *TimeoutAPI) ImageRemoteDigest(ctx context.Context, image string) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageRemoteDigest(ctx, image)
}

// ImageBuildFromExist .
func (a *TimeoutAPI) ImageBuildFromExist(ctx context.Context, ID, name string) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ImageBuildFromExist(ctx, ID, name)
}

// BuildRefs .
func (a *TimeoutAPI) BuildRefs(ctx context.Context, name string, tags []string) []string {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.BuildRefs(ctx, name, tags)
}

// BuildContent .
func (a *TimeoutAPI) BuildContent(ctx context.Context, scm coresource.Source, opts *enginetypes.BuildContentOptions) (string, io.Reader, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.BuildContent(ctx, scm, opts)
}

// VirtualizationCreate .
func (a *TimeoutAPI) VirtualizationCreate(ctx context.Context, opts *enginetypes.VirtualizationCreateOptions) (*enginetypes.VirtualizationCreated, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationCreate(ctx, opts)
}

// VirtualizationCopyTo .
func (a *TimeoutAPI) VirtualizationCopyTo(ctx context.Context, ID, target string, content io.Reader, AllowOverwriteDirWithFile, CopyUIDGID bool) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationCopyTo(ctx, ID, target, content, AllowOverwriteDirWithFile, CopyUIDGID)
}

// VirtualizationStart .
func (a *TimeoutAPI) VirtualizationStart(ctx context.Context, ID string) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationStart(ctx, ID)
}

// VirtualizationStop .
func (a *TimeoutAPI) VirtualizationStop(ctx context.Context, ID string) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationStop(ctx, ID)
}

// VirtualizationRemove .
func (a *TimeoutAPI) VirtualizationRemove(ctx context.Context, ID string, volumes, force bool) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationRemove(ctx, ID, volumes, force)
}

// VirtualizationInspect .
func (a *TimeoutAPI) VirtualizationInspect(ctx context.Context, ID string) (*enginetypes.VirtualizationInfo, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationInspect(ctx, ID)
}

// VirtualizationLogs .
func (a *TimeoutAPI) VirtualizationLogs(ctx context.Context, opts *enginetypes.VirtualizationLogStreamOptions) (io.ReadCloser, error) {
	return a.api.VirtualizationLogs(ctx, opts)
}

// VirtualizationAttach .
func (a *TimeoutAPI) VirtualizationAttach(ctx context.Context, ID string, stream, stdin bool) (io.ReadCloser, io.WriteCloser, error) {
	return a.api.VirtualizationAttach(ctx, ID, stream, stdin)
}

// VirtualizationResize .
func (a *TimeoutAPI) VirtualizationResize(ctx context.Context, ID string, height, width uint) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationResize(ctx, ID, height, width)
}

// VirtualizationWait .
func (a *TimeoutAPI) VirtualizationWait(ctx context.Context, ID, state string) (*enginetypes.VirtualizationWaitResult, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationWait(ctx, ID, state)
}

// VirtualizationUpdateResource .
func (a *TimeoutAPI) VirtualizationUpdateResource(ctx context.Context, ID string, opts *enginetypes.VirtualizationResource) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationUpdateResource(ctx, ID, opts)
}

// VirtualizationCopyFrom .
func (a *TimeoutAPI) VirtualizationCopyFrom(ctx context.Context, ID, path string) (io.ReadCloser, string, error) {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.VirtualizationCopyFrom(ctx, ID, path)
}

// ResourceValidate .
func (a *TimeoutAPI) ResourceValidate(ctx context.Context, cpu float64, cpumap map[string]int64, memory, storage int64) error {
	ctx, cancel := context.WithTimeout(ctx, a.timeout)
	defer cancel()
	return a.api.ResourceValidate(ctx, cpu, cpumap, memory, storage)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"

	"github.com/animenotifier/arn"
	"github.com/animenotifier/mal"
	"github.com/fatih/color"
)

const imageWidthThreshold = 225

var (
	malDB           = arn.Node.Namespace("mal").RegisterTypes((*mal.Anime)(nil))
	characterFinder = arn.NewCharacterFinder("myanimelist/character")
)

func main() {
	color.Yellow("Syncing with MAL DB")

	defer color.Green("Finished.")
	defer arn.Node.Close()

	// Invoke via parameters
	if InvokeShellArgs() {
		return
	}

	// Sync anime
	if objectType == "all" || objectType == "anime" {
		allAnime := arn.FilterAnime(func(anime *arn.Anime) bool {
			return anime.GetMapping("myanimelist/anime") != ""
		})

		arn.SortAnimeByQuality(allAnime)
		color.Yellow("%d anime found", len(allAnime))

		for _, anime := range allAnime {
			syncAnime(anime, anime.GetMapping("myanimelist/anime"))
		}
	}

	// Sync characters
	if objectType == "all" || objectType == "character" {
		allCharacters := arn.FilterCharacters(func(character *arn.Character) bool {
			return character.GetMapping("myanimelist/character") != ""
		})

		arn.SortCharactersByLikes(allCharacters)
		color.Yellow("%d characters found", len(allCharacters))

		for _, character := range allCharacters {
			syncCharacter(character, character.GetMapping("myanimelist/character"))
		}
	}
}

func syncAnime(anime *arn.Anime, malID string) {
	obj, err := malDB.Get("Anime", malID)

	if err != nil {
		return
	}

	malAnime := obj.(*mal.Anime)

	// Log title
	fmt.Printf("%s %s\n", color.CyanString(anime.Title.Canonical), malID)

	type syncFunction func(*arn.Anime, *mal.Anime)

	syncFunctions := []syncFunction{
		syncTitles,
		syncDates,
		syncEpisodes,
		syncOthers,
		syncImage,
		syncCharacters,
	}

	for _, syncFunction := range syncFunctions {
		syncFunction(anime, malAnime)
	}

	// Save in database
	anime.Save()
}

func syncCharacter(character *arn.Character, malID string) {
	obj, err := malDB.Get("Character", malID)

	if err != nil {
		return
	}

	malCharacter := obj.(*mal.Character)

	if character.Name.Japanese == "" && malCharacter.JapaneseName != "" {
		character.Name.Japanese = malCharacter.JapaneseName
	}

	if character.Name.Canonical == "" && malCharacter.Name != "" {
		character.Name.Canonical = malCharacter.Name
	}

	allowUpdating := character.CreatedBy == "" && character.EditedBy == ""
	description, attributes := parseCharacterDescription(malCharacter.Description)

	if (allowUpdating || character.Description == "") && description != "" {
		character.Description = description
	}

	if allowUpdating {
		character.Attributes = attributes
		character.Spoilers = []arn.Spoiler{}

		for _, spoilerText := range malCharacter.Spoilers {
			if !strings.Contains(strings.TrimSpace(spoilerText), "\n\n") {
				character.Spoilers = append(character.Spoilers, arn.Spoiler{
					Text: spoilerText,
				})

				continue
			}

			paragraphs := strings.Split(spoilerText, "\n\n")

			for _, paragraph := range paragraphs {
				character.Spoilers = append(character.Spoilers, arn.Spoiler{
					Text: paragraph,
				})
			}
		}
	}

	// Save in database
	character.Save()
}
<commit_msg>Minor fix<commit_after>package main

import (
	"fmt"
	"strings"

	"github.com/animenotifier/arn"
	"github.com/animenotifier/mal"
	"github.com/fatih/color"
)

const imageWidthThreshold = 225

var (
	malDB           = arn.Node.Namespace("mal").RegisterTypes((*mal.Anime)(nil))
	characterFinder = arn.NewCharacterFinder("myanimelist/character")
)

func main() {
	color.Yellow("Syncing with MAL DB")

	defer color.Green("Finished.")
	defer arn.Node.Close()

	// Invoke via parameters
	if InvokeShellArgs() {
		return
	}

	// Sync anime
	if objectType == "all" || objectType == "anime" {
		allAnime := arn.FilterAnime(func(anime *arn.Anime) bool {
			return anime.GetMapping("myanimelist/anime") != ""
		})

		arn.SortAnimeByQuality(allAnime)
		color.Yellow("%d anime found", len(allAnime))

		for _, anime := range allAnime {
			syncAnime(anime, anime.GetMapping("myanimelist/anime"))
		}
	}

	// Sync characters
	if objectType == "all" || objectType == "character" {
		allCharacters := arn.FilterCharacters(func(character *arn.Character) bool {
			return character.GetMapping("myanimelist/character") != ""
		})

		arn.SortCharactersByLikes(allCharacters)
		color.Yellow("%d characters found", len(allCharacters))

		for _, character := range allCharacters {
			syncCharacter(character, character.GetMapping("myanimelist/character"))
		}
	}
}

func syncAnime(anime *arn.Anime, malID string) {
	obj, err := malDB.Get("Anime", malID)

	if err != nil {
		return
	}

	malAnime := obj.(*mal.Anime)

	// Log title
	fmt.Printf("%s %s\n", color.CyanString(anime.Title.Canonical), malID)

	type syncFunction func(*arn.Anime, *mal.Anime)

	syncFunctions := []syncFunction{
		syncTitles,
		syncDates,
		syncEpisodes,
		syncOthers,
		syncImage,
		syncCharacters,
	}

	for _, syncFunction := range syncFunctions {
		syncFunction(anime, malAnime)
	}

	// Save in database
	anime.Save()
}

func syncCharacter(character *arn.Character, malID string) {
	obj, err := malDB.Get("Character", malID)

	if err != nil {
		return
	}

	malCharacter := obj.(*mal.Character)

	if character.Name.Japanese == "" && malCharacter.JapaneseName != "" {
		character.Name.Japanese = malCharacter.JapaneseName
	}

	if character.Name.Canonical == "" && malCharacter.Name != "" {
		character.Name.Canonical = malCharacter.Name
	}

	allowUpdating := character.CreatedBy == "" && character.EditedBy == ""
	description, attributes := parseCharacterDescription(malCharacter.Description)

	if strings.Contains(character.Description, "No biography written.") {
		character.Description = ""
	}

	if (allowUpdating || character.Description == "") && description != "" {
		character.Description = description
	}

	if allowUpdating {
		character.Attributes = attributes
		character.Spoilers = []arn.Spoiler{}

		for _, spoilerText := range malCharacter.Spoilers {
			if !strings.Contains(strings.TrimSpace(spoilerText), "\n\n") {
				character.Spoilers = append(character.Spoilers, arn.Spoiler{
					Text: spoilerText,
				})

				continue
			}

			paragraphs := strings.Split(spoilerText, "\n\n")

			for _, paragraph := range paragraphs {
				character.Spoilers = append(character.Spoilers, arn.Spoiler{
					Text: paragraph,
				})
			}
		}
	}

	// Save in database
	character.Save()
}
<|endoftext|>


<commit_before>package entry

import (
	"bufio"
	"github.com/ghthor/gospec"
	. "github.com/ghthor/gospec"
	"github.com/ghthor/journal/git"
	"github.com/ghthor/journal/idea"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"
)

func init() {
	var err error
	if _, err = os.Stat("_test/"); os.IsNotExist(err) {
		err = os.Mkdir("_test/", 0755)
	}

	if err != nil {
		log.Fatal(err)
	}
}

func DescribeAnEntry(c gospec.Context) {
	td, err := ioutil.TempDir("_test", "entry_")
	c.Assume(err, IsNil)

	ne := New(td)

	c.Specify("an entry", func() {
		c.Specify("can be opened", func() {
			c.Specify("at a specific time", func() {
				t := time.Date(2006, time.January, 1, 1, 0, 0, 0, time.UTC)
				oe, err := ne.Open(func() time.Time {
					return t
				}, nil)
				c.Assume(err, IsNil)
				c.Expect(oe.OpenedAt(), Equals, t)
			})

			c.Specify("with a list of ideas", func() {
				ideas := []idea.Idea{{
					Name:   "Active Idea",
					Status: idea.IS_Active,
					Body:   "Some text\n",
				}, {
					Name:   "Another Idea",
					Status: idea.IS_Active,
					Body:   "Some other text\n",
				}}

				oe, err := ne.Open(time.Now, ideas)
				c.Assume(err, IsNil)
				actualIdeas, err := oe.Ideas()
				c.Assume(err, IsNil)
				c.Expect(len(actualIdeas), Equals, len(ideas))
				for i, idea := range actualIdeas {
					c.Expect(idea, Equals, ideas[i])
				}
			})
		})
		openedAt := time.Date(2006, time.January, 1, 1, 0, 0, 0, time.UTC)
		closedAt := time.Date(2006, time.January, 1, 1, 10, 0, 0, time.UTC)

		ideas := []idea.Idea{{
			Name:   "Active Idea",
			Status: idea.IS_Active,
			Body:   "Some text\n",
		}, {
			Name:   "Another Idea",
			Status: idea.IS_Active,
			Body:   "Some other text\n",
		}}

		oe, err := ne.Open(func() time.Time {
			return openedAt
		}, ideas)
		c.Assume(err, IsNil)
		c.Assume(oe.OpenedAt(), Equals, openedAt)

		filename := filepath.Join(td, openedAt.Format(filenameLayout))

		c.Specify("that is open", func() {
			c.Specify("is a file", func() {
				_, err := os.Stat(filename)
				c.Expect(os.IsNotExist(err), IsFalse)

				actualBytes, err := ioutil.ReadFile(filename)
				c.Expect(err, IsNil)
				c.Expect(string(actualBytes), Equals,
					`Sun Jan  1 01:00:00 UTC 2006

#~ Title(will be used as commit message)
TODO Make this some random quote or something stupid

## [active] Active Idea
Some text

## [active] Another Idea
Some other text
`)
			})

			f, err := os.OpenFile(filename, os.O_RDONLY, 0600)
			c.Assume(err, IsNil)
			defer f.Close()

			c.Specify("will have the time opened as the first line of the entry", func() {
				scanner := bufio.NewScanner(f)
				c.Assume(scanner.Scan(), IsTrue)
				c.Expect(scanner.Text(), Equals, openedAt.Format(time.UnixDate))
			})

			c.Specify("will have a list of ideas appended to the entry", func() {
				actualIdeas, err := oe.Ideas()
				c.Assume(err, IsNil)
				c.Expect(len(actualIdeas), Equals, len(ideas))
				for i, actualIdea := range actualIdeas {
					c.Expect(actualIdea, Equals, ideas[i])
				}

				fa, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND, 0600)
				c.Assume(err, IsNil)
				defer func() { c.Assume(fa.Close(), IsNil) }()

				_, err = fa.WriteString(
					`## [inactive] A Third Idea
Sir Idea, The Third
`)
				ideas = append(ideas, idea.Idea{
					Name:   "A Third Idea",
					Status: idea.IS_Inactive,
					Body:   "Sir Idea, The Third\n",
				})
				c.Assume(err, IsNil)

				actualIdeas, err = oe.Ideas()
				c.Assume(err, IsNil)
				c.Expect(len(actualIdeas), Equals, len(ideas))
				for i, actualIdea := range actualIdeas {
					c.Expect(actualIdea, Equals, ideas[i])
				}
			})

			c.Specify("can be editted by a text editor", func() {
				sed, err := exec.LookPath("sed")
				c.Assume(err, IsNil)

				editCmd := exec.Command(sed, "-i", "s_active_inactive_", filename)
				_, err = oe.Edit(editCmd)
				c.Expect(err, IsNil)

				// Re-Open the file
				c.Assume(f.Close(), IsNil)
				f, err = os.OpenFile(filename, os.O_RDONLY, 0600)
				c.Assume(err, IsNil)

				// Check the Edit's went through
				scanner := idea.NewIdeaScanner(f)
				for i := 0; i < len(ideas); i++ {
					c.Assume(scanner.Scan(), IsTrue)
					c.Expect(scanner.Idea().Status, Equals, idea.IS_Inactive)
				}
			})

			c.Specify("can be closed", func() {
				_, err = oe.Close(closedAt)
				c.Expect(err, IsNil)
			})
		})

		c.Specify("that is closed", func() {
			ce, err := oe.Close(closedAt)
			c.Assume(err, IsNil)

			c.Specify("will have all ideas removed from the entry", func() {
				actualBytes, err := ioutil.ReadFile(filename)
				c.Assume(err, IsNil)

				c.Expect(string(actualBytes), Equals,
					`Sun Jan  1 01:00:00 UTC 2006

#~ Title(will be used as commit message)
TODO Make this some random quote or something stupid

Sun Jan  1 01:10:00 UTC 2006
`)
			})

			c.Specify("will have the time closed as the last line of the entry", func() {
				actualBytes, err := ioutil.ReadFile(filename)
				c.Assume(err, IsNil)

				c.Expect(string(actualBytes), Equals,
					`Sun Jan  1 01:00:00 UTC 2006

#~ Title(will be used as commit message)
TODO Make this some random quote or something stupid

Sun Jan  1 01:10:00 UTC 2006
`)
			})

			c.Specify("can be commited to the git repository", func() {
				commitable, isCommitable := ce.(git.Commitable)
				c.Expect(isCommitable, IsTrue)
				files, err := commitable.FilesToAdd()
				c.Assume(err, IsNil)
				c.Expect(len(files), Equals, 1)
				c.Expect(files[0], Equals, filename)

				commitMsg, err := commitable.CommitMsg()
				c.Assume(err, IsNil)
				c.Expect(commitMsg, Equals, "ENTRY - Title(will be used as commit message)")
			})
		})
	})
}
<commit_msg>Added a space for readability<commit_after>package entry

import (
	"bufio"
	"github.com/ghthor/gospec"
	. "github.com/ghthor/gospec"
	"github.com/ghthor/journal/git"
	"github.com/ghthor/journal/idea"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"
)

func init() {
	var err error
	if _, err = os.Stat("_test/"); os.IsNotExist(err) {
		err = os.Mkdir("_test/", 0755)
	}

	if err != nil {
		log.Fatal(err)
	}
}

func DescribeAnEntry(c gospec.Context) {
	td, err := ioutil.TempDir("_test", "entry_")
	c.Assume(err, IsNil)

	ne := New(td)

	c.Specify("an entry", func() {
		c.Specify("can be opened", func() {
			c.Specify("at a specific time", func() {
				t := time.Date(2006, time.January, 1, 1, 0, 0, 0, time.UTC)
				oe, err := ne.Open(func() time.Time {
					return t
				}, nil)
				c.Assume(err, IsNil)
				c.Expect(oe.OpenedAt(), Equals, t)
			})

			c.Specify("with a list of ideas", func() {
				ideas := []idea.Idea{{
					Name:   "Active Idea",
					Status: idea.IS_Active,
					Body:   "Some text\n",
				}, {
					Name:   "Another Idea",
					Status: idea.IS_Active,
					Body:   "Some other text\n",
				}}

				oe, err := ne.Open(time.Now, ideas)
				c.Assume(err, IsNil)
				actualIdeas, err := oe.Ideas()
				c.Assume(err, IsNil)
				c.Expect(len(actualIdeas), Equals, len(ideas))
				for i, idea := range actualIdeas {
					c.Expect(idea, Equals, ideas[i])
				}
			})
		})
		openedAt := time.Date(2006, time.January, 1, 1, 0, 0, 0, time.UTC)
		closedAt := time.Date(2006, time.January, 1, 1, 10, 0, 0, time.UTC)

		ideas := []idea.Idea{{
			Name:   "Active Idea",
			Status: idea.IS_Active,
			Body:   "Some text\n",
		}, {
			Name:   "Another Idea",
			Status: idea.IS_Active,
			Body:   "Some other text\n",
		}}

		oe, err := ne.Open(func() time.Time {
			return openedAt
		}, ideas)
		c.Assume(err, IsNil)
		c.Assume(oe.OpenedAt(), Equals, openedAt)

		filename := filepath.Join(td, openedAt.Format(filenameLayout))

		c.Specify("that is open", func() {
			c.Specify("is a file", func() {
				_, err := os.Stat(filename)
				c.Expect(os.IsNotExist(err), IsFalse)

				actualBytes, err := ioutil.ReadFile(filename)
				c.Expect(err, IsNil)
				c.Expect(string(actualBytes), Equals,
					`Sun Jan  1 01:00:00 UTC 2006

#~ Title(will be used as commit message)
TODO Make this some random quote or something stupid

## [active] Active Idea
Some text

## [active] Another Idea
Some other text
`)
			})

			f, err := os.OpenFile(filename, os.O_RDONLY, 0600)
			c.Assume(err, IsNil)
			defer f.Close()

			c.Specify("will have the time opened as the first line of the entry", func() {
				scanner := bufio.NewScanner(f)
				c.Assume(scanner.Scan(), IsTrue)
				c.Expect(scanner.Text(), Equals, openedAt.Format(time.UnixDate))
			})

			c.Specify("will have a list of ideas appended to the entry", func() {
				actualIdeas, err := oe.Ideas()
				c.Assume(err, IsNil)
				c.Expect(len(actualIdeas), Equals, len(ideas))
				for i, actualIdea := range actualIdeas {
					c.Expect(actualIdea, Equals, ideas[i])
				}

				fa, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND, 0600)
				c.Assume(err, IsNil)
				defer func() { c.Assume(fa.Close(), IsNil) }()

				_, err = fa.WriteString(
					`## [inactive] A Third Idea
Sir Idea, The Third
`)
				ideas = append(ideas, idea.Idea{
					Name:   "A Third Idea",
					Status: idea.IS_Inactive,
					Body:   "Sir Idea, The Third\n",
				})
				c.Assume(err, IsNil)

				actualIdeas, err = oe.Ideas()
				c.Assume(err, IsNil)
				c.Expect(len(actualIdeas), Equals, len(ideas))
				for i, actualIdea := range actualIdeas {
					c.Expect(actualIdea, Equals, ideas[i])
				}
			})

			c.Specify("can be editted by a text editor", func() {
				sed, err := exec.LookPath("sed")
				c.Assume(err, IsNil)

				editCmd := exec.Command(sed, "-i", "s_active_inactive_", filename)
				_, err = oe.Edit(editCmd)
				c.Expect(err, IsNil)

				// Re-Open the file
				c.Assume(f.Close(), IsNil)
				f, err = os.OpenFile(filename, os.O_RDONLY, 0600)
				c.Assume(err, IsNil)

				// Check the Edit's went through
				scanner := idea.NewIdeaScanner(f)
				for i := 0; i < len(ideas); i++ {
					c.Assume(scanner.Scan(), IsTrue)
					c.Expect(scanner.Idea().Status, Equals, idea.IS_Inactive)
				}
			})

			c.Specify("can be closed", func() {
				_, err = oe.Close(closedAt)
				c.Expect(err, IsNil)
			})
		})

		c.Specify("that is closed", func() {
			ce, err := oe.Close(closedAt)
			c.Assume(err, IsNil)

			c.Specify("will have all ideas removed from the entry", func() {
				actualBytes, err := ioutil.ReadFile(filename)
				c.Assume(err, IsNil)

				c.Expect(string(actualBytes), Equals,
					`Sun Jan  1 01:00:00 UTC 2006

#~ Title(will be used as commit message)
TODO Make this some random quote or something stupid

Sun Jan  1 01:10:00 UTC 2006
`)
			})

			c.Specify("will have the time closed as the last line of the entry", func() {
				actualBytes, err := ioutil.ReadFile(filename)
				c.Assume(err, IsNil)

				c.Expect(string(actualBytes), Equals,
					`Sun Jan  1 01:00:00 UTC 2006

#~ Title(will be used as commit message)
TODO Make this some random quote or something stupid

Sun Jan  1 01:10:00 UTC 2006
`)
			})

			c.Specify("can be commited to the git repository", func() {
				commitable, isCommitable := ce.(git.Commitable)
				c.Expect(isCommitable, IsTrue)

				files, err := commitable.FilesToAdd()
				c.Assume(err, IsNil)
				c.Expect(len(files), Equals, 1)
				c.Expect(files[0], Equals, filename)

				commitMsg, err := commitable.CommitMsg()
				c.Assume(err, IsNil)
				c.Expect(commitMsg, Equals, "ENTRY - Title(will be used as commit message)")
			})
		})
	})
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"path/filepath"
	"sort"
	"strings"
	"testing"
)

func TestLoad_badType(t *testing.T) {
	_, err := Load(filepath.Join(fixtureDir, "bad_type.tf.nope"))
	if err == nil {
		t.Fatal("should have error")
	}
}

func TestLoadBasic(t *testing.T) {
	c, err := Load(filepath.Join(fixtureDir, "basic.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(basicVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(basicProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(basicResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = outputsStr(c.Outputs)
	if actual != strings.TrimSpace(basicOutputsStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadBasic_import(t *testing.T) {
	c, err := Load(filepath.Join(fixtureDir, "import.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(importVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(importProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(importResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoad_variables(t *testing.T) {
	c, err := Load(filepath.Join(fixtureDir, "variables.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(variablesVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	if !c.Variables["foo"].Required() {
		t.Fatal("foo should be required")
	}
	if c.Variables["bar"].Required() {
		t.Fatal("bar should not be required")
	}
	if c.Variables["baz"].Required() {
		t.Fatal("baz should not be required")
	}
}

func outputsStr(os map[string]*Output) string {
	ns := make([]string, 0, len(os))
	for n, _ := range os {
		ns = append(ns, n)
	}
	sort.Strings(ns)

	result := ""
	for _, n := range ns {
		o := os[n]

		result += fmt.Sprintf("%s\n", n)

		if len(o.RawConfig.Variables) > 0 {
			result += fmt.Sprintf("  vars\n")
			for _, rawV := range o.RawConfig.Variables {
				kind := "unknown"
				str := rawV.FullKey()

				switch rawV.(type) {
				case *ResourceVariable:
					kind = "resource"
				case *UserVariable:
					kind = "user"
				}

				result += fmt.Sprintf("    %s: %s\n", kind, str)
			}
		}
	}

	return strings.TrimSpace(result)
}

// This helper turns a provider configs field into a deterministic
// string value for comparison in tests.
func providerConfigsStr(pcs map[string]*ProviderConfig) string {
	result := ""

	ns := make([]string, 0, len(pcs))
	for n, _ := range pcs {
		ns = append(ns, n)
	}
	sort.Strings(ns)

	for _, n := range ns {
		pc := pcs[n]

		result += fmt.Sprintf("%s\n", n)

		keys := make([]string, 0, len(pc.RawConfig.Raw))
		for k, _ := range pc.RawConfig.Raw {
			keys = append(keys, k)
		}
		sort.Strings(keys)

		for _, k := range keys {
			result += fmt.Sprintf("  %s\n", k)
		}

		if len(pc.RawConfig.Variables) > 0 {
			result += fmt.Sprintf("  vars\n")
			for _, rawV := range pc.RawConfig.Variables {
				kind := "unknown"
				str := rawV.FullKey()

				switch rawV.(type) {
				case *ResourceVariable:
					kind = "resource"
				case *UserVariable:
					kind = "user"
				}

				result += fmt.Sprintf("    %s: %s\n", kind, str)
			}
		}
	}

	return strings.TrimSpace(result)
}

// This helper turns a resources field into a deterministic
// string value for comparison in tests.
func resourcesStr(rs []*Resource) string {
	result := ""
	for _, r := range rs {
		result += fmt.Sprintf(
			"%s[%s] (x%d)\n",
			r.Type,
			r.Name,
			r.Count)

		ks := make([]string, 0, len(r.RawConfig.Raw))
		for k, _ := range r.RawConfig.Raw {
			ks = append(ks, k)
		}
		sort.Strings(ks)

		for _, k := range ks {
			result += fmt.Sprintf("  %s\n", k)
		}

		if len(r.RawConfig.Variables) > 0 {
			result += fmt.Sprintf("  vars\n")

			ks := make([]string, 0, len(r.RawConfig.Variables))
			for k, _ := range r.RawConfig.Variables {
				ks = append(ks, k)
			}
			sort.Strings(ks)

			for _, k := range ks {
				rawV := r.RawConfig.Variables[k]
				kind := "unknown"
				str := rawV.FullKey()

				switch rawV.(type) {
				case *ResourceVariable:
					kind = "resource"
				case *UserVariable:
					kind = "user"
				}

				result += fmt.Sprintf("    %s: %s\n", kind, str)
			}
		}
	}

	return strings.TrimSpace(result)
}

// This helper turns a variables field into a deterministic
// string value for comparison in tests.
func variablesStr(vs map[string]*Variable) string {
	result := ""
	ks := make([]string, 0, len(vs))
	for k, _ := range vs {
		ks = append(ks, k)
	}
	sort.Strings(ks)

	for _, k := range ks {
		v := vs[k]

		if v.Default == "" {
			v.Default = "<>"
		}
		if v.Description == "" {
			v.Description = "<>"
		}

		result += fmt.Sprintf(
			"%s\n  %s\n  %s\n",
			k,
			v.Default,
			v.Description)
	}

	return strings.TrimSpace(result)
}

const basicOutputsStr = `
web_ip
  vars
    resource: aws_instance.web.private_ip
`

const basicProvidersStr = `
aws
  access_key
  secret_key
do
  api_key
  vars
    user: var.foo
`

const basicResourcesStr = `
aws_security_group[firewall] (x5)
aws_instance[web] (x1)
  ami
  network_interface
  security_groups
  vars
    resource: aws_security_group.firewall.foo
    user: var.foo
aws_instance[db] (x1)
  security_groups
  vars
    resource: aws_security_group.firewall.*.id
`

const basicVariablesStr = `
foo
  bar
  bar
`

const importProvidersStr = `
aws
  foo
`

const importResourcesStr = `
aws_security_group[db] (x1)
aws_security_group[web] (x1)
`

const importVariablesStr = `
bar
  <>
  <>
foo
  bar
  bar
`

const variablesVariablesStr = `
bar
  <>
  <>
baz
  foo
  <>
foo
  <>
  <>
`
<commit_msg>config: deterministic test ordering<commit_after>package config

import (
	"fmt"
	"path/filepath"
	"sort"
	"strings"
	"testing"
)

func TestLoad_badType(t *testing.T) {
	_, err := Load(filepath.Join(fixtureDir, "bad_type.tf.nope"))
	if err == nil {
		t.Fatal("should have error")
	}
}

func TestLoadBasic(t *testing.T) {
	c, err := Load(filepath.Join(fixtureDir, "basic.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(basicVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(basicProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(basicResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = outputsStr(c.Outputs)
	if actual != strings.TrimSpace(basicOutputsStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadBasic_import(t *testing.T) {
	c, err := Load(filepath.Join(fixtureDir, "import.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(importVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(importProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(importResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoad_variables(t *testing.T) {
	c, err := Load(filepath.Join(fixtureDir, "variables.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(variablesVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	if !c.Variables["foo"].Required() {
		t.Fatal("foo should be required")
	}
	if c.Variables["bar"].Required() {
		t.Fatal("bar should not be required")
	}
	if c.Variables["baz"].Required() {
		t.Fatal("baz should not be required")
	}
}

func outputsStr(os map[string]*Output) string {
	ns := make([]string, 0, len(os))
	for n, _ := range os {
		ns = append(ns, n)
	}
	sort.Strings(ns)

	result := ""
	for _, n := range ns {
		o := os[n]

		result += fmt.Sprintf("%s\n", n)

		if len(o.RawConfig.Variables) > 0 {
			result += fmt.Sprintf("  vars\n")
			for _, rawV := range o.RawConfig.Variables {
				kind := "unknown"
				str := rawV.FullKey()

				switch rawV.(type) {
				case *ResourceVariable:
					kind = "resource"
				case *UserVariable:
					kind = "user"
				}

				result += fmt.Sprintf("    %s: %s\n", kind, str)
			}
		}
	}

	return strings.TrimSpace(result)
}

// This helper turns a provider configs field into a deterministic
// string value for comparison in tests.
func providerConfigsStr(pcs map[string]*ProviderConfig) string {
	result := ""

	ns := make([]string, 0, len(pcs))
	for n, _ := range pcs {
		ns = append(ns, n)
	}
	sort.Strings(ns)

	for _, n := range ns {
		pc := pcs[n]

		result += fmt.Sprintf("%s\n", n)

		keys := make([]string, 0, len(pc.RawConfig.Raw))
		for k, _ := range pc.RawConfig.Raw {
			keys = append(keys, k)
		}
		sort.Strings(keys)

		for _, k := range keys {
			result += fmt.Sprintf("  %s\n", k)
		}

		if len(pc.RawConfig.Variables) > 0 {
			result += fmt.Sprintf("  vars\n")
			for _, rawV := range pc.RawConfig.Variables {
				kind := "unknown"
				str := rawV.FullKey()

				switch rawV.(type) {
				case *ResourceVariable:
					kind = "resource"
				case *UserVariable:
					kind = "user"
				}

				result += fmt.Sprintf("    %s: %s\n", kind, str)
			}
		}
	}

	return strings.TrimSpace(result)
}

// This helper turns a resources field into a deterministic
// string value for comparison in tests.
func resourcesStr(rs []*Resource) string {
	result := ""
	order := make([]int, 0, len(rs))
	ks := make([]string, 0, len(rs))
	mapping := make(map[string]int)
	for i, r := range rs {
		k := fmt.Sprintf("%s[%s]", r.Type, r.Name)
		ks = append(ks, k)
		mapping[k] = i
	}
	sort.Strings(ks)
	for _, k := range ks {
		order = append(order, mapping[k])
	}

	for _, i := range order {
		r := rs[i]
		result += fmt.Sprintf(
			"%s[%s] (x%d)\n",
			r.Type,
			r.Name,
			r.Count)

		ks := make([]string, 0, len(r.RawConfig.Raw))
		for k, _ := range r.RawConfig.Raw {
			ks = append(ks, k)
		}
		sort.Strings(ks)

		for _, k := range ks {
			result += fmt.Sprintf("  %s\n", k)
		}

		if len(r.RawConfig.Variables) > 0 {
			result += fmt.Sprintf("  vars\n")

			ks := make([]string, 0, len(r.RawConfig.Variables))
			for k, _ := range r.RawConfig.Variables {
				ks = append(ks, k)
			}
			sort.Strings(ks)

			for _, k := range ks {
				rawV := r.RawConfig.Variables[k]
				kind := "unknown"
				str := rawV.FullKey()

				switch rawV.(type) {
				case *ResourceVariable:
					kind = "resource"
				case *UserVariable:
					kind = "user"
				}

				result += fmt.Sprintf("    %s: %s\n", kind, str)
			}
		}
	}

	return strings.TrimSpace(result)
}

// This helper turns a variables field into a deterministic
// string value for comparison in tests.
func variablesStr(vs map[string]*Variable) string {
	result := ""
	ks := make([]string, 0, len(vs))
	for k, _ := range vs {
		ks = append(ks, k)
	}
	sort.Strings(ks)

	for _, k := range ks {
		v := vs[k]

		if v.Default == "" {
			v.Default = "<>"
		}
		if v.Description == "" {
			v.Description = "<>"
		}

		result += fmt.Sprintf(
			"%s\n  %s\n  %s\n",
			k,
			v.Default,
			v.Description)
	}

	return strings.TrimSpace(result)
}

const basicOutputsStr = `
web_ip
  vars
    resource: aws_instance.web.private_ip
`

const basicProvidersStr = `
aws
  access_key
  secret_key
do
  api_key
  vars
    user: var.foo
`

const basicResourcesStr = `
aws_instance[db] (x1)
  security_groups
  vars
    resource: aws_security_group.firewall.*.id
aws_instance[web] (x1)
  ami
  network_interface
  security_groups
  vars
    resource: aws_security_group.firewall.foo
    user: var.foo
aws_security_group[firewall] (x5)
`

const basicVariablesStr = `
foo
  bar
  bar
`

const importProvidersStr = `
aws
  foo
`

const importResourcesStr = `
aws_security_group[db] (x1)
aws_security_group[web] (x1)
`

const importVariablesStr = `
bar
  <>
  <>
foo
  bar
  bar
`

const variablesVariablesStr = `
bar
  <>
  <>
baz
  foo
  <>
foo
  <>
  <>
`
<|endoftext|>


<commit_before>package pq

import (
	"bytes"
	"fmt"
	"testing"
	"time"
)

func TestScanTimestamp(t *testing.T) {
	var nt NullTime
	tn := time.Now()
	nt.Scan(tn)
	if !nt.Valid {
		t.Errorf("Expected Valid=false")
	}
	if nt.Time != tn {
		t.Errorf("Time value mismatch")
	}
}

func TestScanNilTimestamp(t *testing.T) {
	var nt NullTime
	nt.Scan(nil)
	if nt.Valid {
		t.Errorf("Expected Valid=false")
	}
}

var timeTests = []struct {
	str      string
	expected time.Time
}{
	{"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.UTC)},
	{"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.UTC)},
	{"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.UTC)},
	{"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.UTC)},
	{"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.UTC)},
	{"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.UTC)},
	{"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.UTC)},
	{"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.UTC)},
	{"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.UTC)},
	{"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.UTC)},
	{"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.UTC)},
	{"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.UTC)},
	{"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.UTC)},
	{"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.UTC)},
	{"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000,
		time.FixedZone("", -7*60*60))},
	{"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
		time.FixedZone("", -7*60*60))},
	{"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0,
		time.FixedZone("", -7*60*60+42*60))},
	{"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
		time.FixedZone("", -7*60*60+30*60+9))},
	{"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
		time.FixedZone("", 7*60*60))},
	{"10000-02-03 04:05:06 BC", time.Date(-10000, time.February, 3, 4, 5, 6, 0, time.UTC)},
	{"0010-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.UTC)},
	{"0010-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.UTC)},
	{"0010-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
		time.FixedZone("", -7*60*60))},
}

func tryParse(str string) (t time.Time, err error) {
	defer func() {
		if p := recover(); p != nil {
			err = fmt.Errorf("%v", p)
			return
		}
	}()
	t = parseTs(str)
	return
}

func TestParseTs(t *testing.T) {
	for i, tt := range timeTests {
		val, err := tryParse(tt.str)
		if !val.Equal(tt.expected) {
			t.Errorf("%d: expected to parse '%v' into '%v'; got '%v'",
				i, tt.str, tt.expected, val)
		}
		if err != nil {
			t.Errorf("%d: got error: %v", i, err)
		}
	}
}

func TestTimestampWithTimeZone(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	tx, err := db.Begin()
	if err != nil {
		t.Fatal(err)
	}
	defer tx.Rollback()

	_, err = tx.Exec("create temp table test (t timestamp with time zone)")
	if err != nil {
		t.Fatal(err)
	}

	// try several different locations, all included in Go's zoneinfo.zip
	for _, locName := range []string{
		"UTC",
		"America/Chicago",
		"America/New_York",
		"Australia/Darwin",
		"Australia/Perth",
	} {
		loc, err := time.LoadLocation(locName)
		if err != nil {
			t.Logf("Could not load time zone %s - skipping", locName)
			continue
		}

		// Postgres timestamps have a resolution of 1 microsecond, so don't
		// use the full range of the Nanosecond argument
		refTime := time.Date(2012, 11, 6, 10, 23, 42, 123456000, loc)
		_, err = tx.Exec("insert into test(t) values($1)", refTime)
		if err != nil {
			t.Fatal(err)
		}

		for _, pgTimeZone := range []string{"US/Eastern", "Australia/Darwin"} {
			// Switch Postgres's timezone to test different output timestamp formats
			_, err = tx.Exec(fmt.Sprintf("set time zone '%s'", pgTimeZone))
			if err != nil {
				t.Fatal(err)
			}

			var gotTime time.Time
			row := tx.QueryRow("select t from test")
			err = row.Scan(&gotTime)
			if err != nil {
				t.Fatal(err)
			}

			if !refTime.Equal(gotTime) {
				t.Errorf("timestamps not equal: %s != %s", refTime, gotTime)
			}
		}

		_, err = tx.Exec("delete from test")
		if err != nil {
			t.Fatal(err)
		}
	}
}

func TestTimestampWithOutTimezone(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	test := func(ts, pgts string) {
		r, err := db.Query("SELECT $1::timestamp", pgts)
		if err != nil {
			t.Fatalf("Could not run query: %v", err)
		}

		n := r.Next()

		if n != true {
			t.Fatal("Expected at least one row")
		}

		var result time.Time
		err = r.Scan(&result)
		if err != nil {
			t.Fatalf("Did not expect error scanning row: %v", err)
		}

		expected, err := time.Parse(time.RFC3339, ts)
		if err != nil {
			t.Fatalf("Could not parse test time literal: %v", err)
		}

		if !result.Equal(expected) {
			t.Fatalf("Expected time to match %v: got mismatch %v",
				expected, result)
		}

		n = r.Next()
		if n != false {
			t.Fatal("Expected only one row")
		}
	}

	test("2000-01-01T00:00:00Z", "2000-01-01T00:00:00")

	// Test higher precision time
	test("2013-01-04T20:14:58.80033Z", "2013-01-04 20:14:58.80033")
}

func TestStringWithNul(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	hello0world := string("hello\x00world")
	_, err := db.Query("SELECT $1::text", &hello0world)
	if err == nil {
		t.Fatal("Postgres accepts a string with nul in it; " +
			"injection attacks may be plausible")
	}
}

func TestByteToText(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	b := []byte("hello world")
	row := db.QueryRow("SELECT $1::text", b)

	var result []byte
	err := row.Scan(&result)
	if err != nil {
		t.Fatal(err)
	}

	if string(result) != string(b) {
		t.Fatalf("expected %v but got %v", b, result)
	}
}

func TestByteaOutputFormats(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	testByteaOutputFormat := func(f string) {
		expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08")
		sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')"

		var data []byte
		_, err := db.Exec("SET bytea_output TO " + f)
		if err != nil {
			t.Fatal(err)
		}
		// use Query; QueryRow would hide the actual error
		rows, err := db.Query(sqlQuery)
		if err != nil {
			t.Fatal(err)
		}
		if !rows.Next() {
			if rows.Err() != nil {
				t.Fatal(rows.Err())
			}
			t.Fatal("shouldn't happen")
		}
		err = rows.Scan(&data)
		if err != nil {
			t.Fatal(err)
		}
		err = rows.Close()
		if err != nil {
			t.Fatal(err)
		}
		if !bytes.Equal(data, expectedData) {
			t.Errorf("unexpected bytea value %v for format %s; expected %v", data, f, expectedData)
		}
	}

	testByteaOutputFormat("hex")
	testByteaOutputFormat("escape")
}
<commit_msg>Use a transaction in TestByteaOutputFormats<commit_after>package pq

import (
	"bytes"
	"fmt"
	"testing"
	"time"
)

func TestScanTimestamp(t *testing.T) {
	var nt NullTime
	tn := time.Now()
	nt.Scan(tn)
	if !nt.Valid {
		t.Errorf("Expected Valid=false")
	}
	if nt.Time != tn {
		t.Errorf("Time value mismatch")
	}
}

func TestScanNilTimestamp(t *testing.T) {
	var nt NullTime
	nt.Scan(nil)
	if nt.Valid {
		t.Errorf("Expected Valid=false")
	}
}

var timeTests = []struct {
	str      string
	expected time.Time
}{
	{"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.UTC)},
	{"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.UTC)},
	{"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.UTC)},
	{"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.UTC)},
	{"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.UTC)},
	{"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.UTC)},
	{"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.UTC)},
	{"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.UTC)},
	{"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.UTC)},
	{"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.UTC)},
	{"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.UTC)},
	{"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.UTC)},
	{"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.UTC)},
	{"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.UTC)},
	{"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000,
		time.FixedZone("", -7*60*60))},
	{"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
		time.FixedZone("", -7*60*60))},
	{"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0,
		time.FixedZone("", -7*60*60+42*60))},
	{"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
		time.FixedZone("", -7*60*60+30*60+9))},
	{"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
		time.FixedZone("", 7*60*60))},
	{"10000-02-03 04:05:06 BC", time.Date(-10000, time.February, 3, 4, 5, 6, 0, time.UTC)},
	{"0010-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.UTC)},
	{"0010-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.UTC)},
	{"0010-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
		time.FixedZone("", -7*60*60))},
}

func tryParse(str string) (t time.Time, err error) {
	defer func() {
		if p := recover(); p != nil {
			err = fmt.Errorf("%v", p)
			return
		}
	}()
	t = parseTs(str)
	return
}

func TestParseTs(t *testing.T) {
	for i, tt := range timeTests {
		val, err := tryParse(tt.str)
		if !val.Equal(tt.expected) {
			t.Errorf("%d: expected to parse '%v' into '%v'; got '%v'",
				i, tt.str, tt.expected, val)
		}
		if err != nil {
			t.Errorf("%d: got error: %v", i, err)
		}
	}
}

func TestTimestampWithTimeZone(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	tx, err := db.Begin()
	if err != nil {
		t.Fatal(err)
	}
	defer tx.Rollback()

	_, err = tx.Exec("create temp table test (t timestamp with time zone)")
	if err != nil {
		t.Fatal(err)
	}

	// try several different locations, all included in Go's zoneinfo.zip
	for _, locName := range []string{
		"UTC",
		"America/Chicago",
		"America/New_York",
		"Australia/Darwin",
		"Australia/Perth",
	} {
		loc, err := time.LoadLocation(locName)
		if err != nil {
			t.Logf("Could not load time zone %s - skipping", locName)
			continue
		}

		// Postgres timestamps have a resolution of 1 microsecond, so don't
		// use the full range of the Nanosecond argument
		refTime := time.Date(2012, 11, 6, 10, 23, 42, 123456000, loc)
		_, err = tx.Exec("insert into test(t) values($1)", refTime)
		if err != nil {
			t.Fatal(err)
		}

		for _, pgTimeZone := range []string{"US/Eastern", "Australia/Darwin"} {
			// Switch Postgres's timezone to test different output timestamp formats
			_, err = tx.Exec(fmt.Sprintf("set time zone '%s'", pgTimeZone))
			if err != nil {
				t.Fatal(err)
			}

			var gotTime time.Time
			row := tx.QueryRow("select t from test")
			err = row.Scan(&gotTime)
			if err != nil {
				t.Fatal(err)
			}

			if !refTime.Equal(gotTime) {
				t.Errorf("timestamps not equal: %s != %s", refTime, gotTime)
			}
		}

		_, err = tx.Exec("delete from test")
		if err != nil {
			t.Fatal(err)
		}
	}
}

func TestTimestampWithOutTimezone(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	test := func(ts, pgts string) {
		r, err := db.Query("SELECT $1::timestamp", pgts)
		if err != nil {
			t.Fatalf("Could not run query: %v", err)
		}

		n := r.Next()

		if n != true {
			t.Fatal("Expected at least one row")
		}

		var result time.Time
		err = r.Scan(&result)
		if err != nil {
			t.Fatalf("Did not expect error scanning row: %v", err)
		}

		expected, err := time.Parse(time.RFC3339, ts)
		if err != nil {
			t.Fatalf("Could not parse test time literal: %v", err)
		}

		if !result.Equal(expected) {
			t.Fatalf("Expected time to match %v: got mismatch %v",
				expected, result)
		}

		n = r.Next()
		if n != false {
			t.Fatal("Expected only one row")
		}
	}

	test("2000-01-01T00:00:00Z", "2000-01-01T00:00:00")

	// Test higher precision time
	test("2013-01-04T20:14:58.80033Z", "2013-01-04 20:14:58.80033")
}

func TestStringWithNul(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	hello0world := string("hello\x00world")
	_, err := db.Query("SELECT $1::text", &hello0world)
	if err == nil {
		t.Fatal("Postgres accepts a string with nul in it; " +
			"injection attacks may be plausible")
	}
}

func TestByteToText(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	b := []byte("hello world")
	row := db.QueryRow("SELECT $1::text", b)

	var result []byte
	err := row.Scan(&result)
	if err != nil {
		t.Fatal(err)
	}

	if string(result) != string(b) {
		t.Fatalf("expected %v but got %v", b, result)
	}
}

func TestByteaOutputFormats(t *testing.T) {
	db := openTestConn(t)
	defer db.Close()

	testByteaOutputFormat := func(f string) {
		expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08")
		sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')"

		var data []byte

		// use a txn to avoid relying on getting the same connection
		txn, err := db.Begin()
		if err != nil {
			t.Fatal(err)
		}
		defer txn.Rollback()

		_, err = txn.Exec("SET LOCAL bytea_output TO " + f)
		if err != nil {
			t.Fatal(err)
		}
		// use Query; QueryRow would hide the actual error
		rows, err := txn.Query(sqlQuery)
		if err != nil {
			t.Fatal(err)
		}
		if !rows.Next() {
			if rows.Err() != nil {
				t.Fatal(rows.Err())
			}
			t.Fatal("shouldn't happen")
		}
		err = rows.Scan(&data)
		if err != nil {
			t.Fatal(err)
		}
		err = rows.Close()
		if err != nil {
			t.Fatal(err)
		}
		if !bytes.Equal(data, expectedData) {
			t.Errorf("unexpected bytea value %v for format %s; expected %v", data, f, expectedData)
		}
	}

	testByteaOutputFormat("hex")
	testByteaOutputFormat("escape")
}
<|endoftext|>


<commit_before>// continuserv proactively re-generates the spec on filesystem changes, and serves it over HTTP.
// It will always serve the most recent version of the spec, and may block an HTTP request until regeneration is finished.
// It does not currently pre-empt stale generations, but will block until they are complete.
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	fsnotify "gopkg.in/fsnotify.v1"
)

var (
	port = flag.Int("port", 8000, "Port on which to serve HTTP")

	toServe atomic.Value   // Always contains valid []byte to serve. May be stale unless wg is zero.
	wg      sync.WaitGroup // Indicates how many updates are pending.
	mu      sync.Mutex     // Prevent multiple updates in parallel.
)

func main() {
	flag.Parse()

	w, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatalf("Error making watcher: %v", err)
	}

	dir, err := os.Getwd()
	if err != nil {
		log.Fatalf("Error getting wd: %v", err)
	}
	for ; !exists(path.Join(dir, ".git")); dir = path.Dir(dir) {
		if dir == "/" {
			log.Fatalf("Could not find git root")
		}
	}

	filepath.Walk(dir, makeWalker(w))

	wg.Add(1)
	populateOnce(dir)

	ch := make(chan struct{}, 100) // Buffered to ensure we can multiple-increment wg for pending writes
	go doPopulate(ch, dir)

	go watchFS(ch, w)
	fmt.Printf("Listening on port %d\n", *port)
	http.HandleFunc("/", serve)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))

}

func watchFS(ch chan struct{}, w *fsnotify.Watcher) {
	for {
		select {
		case e := <-w.Events:
			if filter(e) {
				fmt.Printf("Noticed change to %s, re-generating spec\n", e.Name)
				ch <- struct{}{}
			}
		}
	}
}

func makeWalker(w *fsnotify.Watcher) filepath.WalkFunc {
	return func(path string, _ os.FileInfo, err error) error {
		if err != nil {
			log.Fatalf("Error walking: %v", err)
		}
		if err := w.Add(path); err != nil {
			log.Fatalf("Failed to add watch: %v", err)
		}
		return nil
	}
}

// Return true if event should trigger re-population
func filter(e fsnotify.Event) bool {
	// vim is *really* noisy about how it writes files
	if e.Op != fsnotify.Write {
		return false
	}
	// Avoid some temp files that vim writes
	if strings.HasSuffix(e.Name, "~") || strings.HasSuffix(e.Name, ".swp") || strings.HasPrefix(e.Name, ".") {
		return false
	}

	// Ignore the .git directory - It's very noisy
	if strings.Contains(e.Name, "/.git/") {
		return false
	}

	// Avoid infinite cycles being caused by writing actual output
	if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") {
		return false
	}
	return true
}

func serve(w http.ResponseWriter, req *http.Request) {
	wg.Wait()
	b := toServe.Load().([]byte)
	w.Header().Set("Content-Type", "text/html")
	w.Write(b)
}

func populateOnce(dir string) {
	defer wg.Done()
	mu.Lock()
	defer mu.Unlock()
	cmd := exec.Command("python", "gendoc.py")
	cmd.Dir = path.Join(dir, "scripts")
	var b bytes.Buffer
	cmd.Stderr = &b
	err := cmd.Run()
	if err != nil {
		toServe.Store([]byte(fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String()).Error()))
		return
	}
	specBytes, err := ioutil.ReadFile(path.Join(dir, "scripts", "gen", "specification.html"))
	if err != nil {
		toServe.Store([]byte(fmt.Errorf("error reading spec: %v", err).Error()))
		return
	}
	toServe.Store(specBytes)
}

func doPopulate(ch chan struct{}, dir string) {
	var pending int
	for {
		select {
		case <-ch:
			if pending == 0 {
				wg.Add(1)
			}
			pending++
		case <-time.After(10 * time.Millisecond):
			if pending > 0 {
				pending = 0
				populateOnce(dir)
			}
		}
	}
}

func exists(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}
<commit_msg>Preserve text/plain for errors<commit_after>// continuserv proactively re-generates the spec on filesystem changes, and serves it over HTTP.
// It will always serve the most recent version of the spec, and may block an HTTP request until regeneration is finished.
// It does not currently pre-empt stale generations, but will block until they are complete.
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	fsnotify "gopkg.in/fsnotify.v1"
)

var (
	port = flag.Int("port", 8000, "Port on which to serve HTTP")

	toServe atomic.Value   // Always contains valid bytesOrErr to serve. May be stale unless wg is zero.
	wg      sync.WaitGroup // Indicates how many updates are pending.
	mu      sync.Mutex     // Prevent multiple updates in parallel.
)

func main() {
	flag.Parse()

	w, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatalf("Error making watcher: %v", err)
	}

	dir, err := os.Getwd()
	if err != nil {
		log.Fatalf("Error getting wd: %v", err)
	}
	for ; !exists(path.Join(dir, ".git")); dir = path.Dir(dir) {
		if dir == "/" {
			log.Fatalf("Could not find git root")
		}
	}

	filepath.Walk(dir, makeWalker(w))

	wg.Add(1)
	populateOnce(dir)

	ch := make(chan struct{}, 100) // Buffered to ensure we can multiple-increment wg for pending writes
	go doPopulate(ch, dir)

	go watchFS(ch, w)
	fmt.Printf("Listening on port %d\n", *port)
	http.HandleFunc("/", serve)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))

}

func watchFS(ch chan struct{}, w *fsnotify.Watcher) {
	for {
		select {
		case e := <-w.Events:
			if filter(e) {
				fmt.Printf("Noticed change to %s, re-generating spec\n", e.Name)
				ch <- struct{}{}
			}
		}
	}
}

func makeWalker(w *fsnotify.Watcher) filepath.WalkFunc {
	return func(path string, _ os.FileInfo, err error) error {
		if err != nil {
			log.Fatalf("Error walking: %v", err)
		}
		if err := w.Add(path); err != nil {
			log.Fatalf("Failed to add watch: %v", err)
		}
		return nil
	}
}

// Return true if event should trigger re-population
func filter(e fsnotify.Event) bool {
	// vim is *really* noisy about how it writes files
	if e.Op != fsnotify.Write {
		return false
	}
	// Avoid some temp files that vim writes
	if strings.HasSuffix(e.Name, "~") || strings.HasSuffix(e.Name, ".swp") || strings.HasPrefix(e.Name, ".") {
		return false
	}

	// Ignore the .git directory - It's very noisy
	if strings.Contains(e.Name, "/.git/") {
		return false
	}

	// Avoid infinite cycles being caused by writing actual output
	if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") {
		return false
	}
	return true
}

func serve(w http.ResponseWriter, req *http.Request) {
	wg.Wait()
	b := toServe.Load().(bytesOrErr)
	if b.err != nil {
		w.Header().Set("Content-Type", "text/plain")
		w.Write([]byte(b.err.Error()))
	} else {
		w.Header().Set("Content-Type", "text/html")
		w.Write([]byte(b.bytes))
	}
}

func populateOnce(dir string) {
	defer wg.Done()
	mu.Lock()
	defer mu.Unlock()
	cmd := exec.Command("python", "gendoc.py")
	cmd.Dir = path.Join(dir, "scripts")
	var b bytes.Buffer
	cmd.Stderr = &b
	err := cmd.Run()
	if err != nil {
		toServe.Store(bytesOrErr{nil, fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String())})
		return
	}
	specBytes, err := ioutil.ReadFile(path.Join(dir, "scripts", "gen", "specification.html"))
	if err != nil {
		toServe.Store(bytesOrErr{nil, fmt.Errorf("error reading spec: %v", err)})
		return
	}
	toServe.Store(bytesOrErr{specBytes, nil})
}

func doPopulate(ch chan struct{}, dir string) {
	var pending int
	for {
		select {
		case <-ch:
			if pending == 0 {
				wg.Add(1)
			}
			pending++
		case <-time.After(10 * time.Millisecond):
			if pending > 0 {
				pending = 0
				populateOnce(dir)
			}
		}
	}
}

func exists(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}

type bytesOrErr struct {
	bytes []byte
	err   error
}
<|endoftext|>


<commit_before>// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package main

import (
	"fmt"
	"os"

	//"github.com/uber/zanzibar/codegen"
	"go.uber.org/thriftrw/compile"
	"html/template"
	"bytes"
	"io/ioutil"
	"github.com/uber/zanzibar/runtime"
	"runtime"
	"path/filepath"
	"strings"
	"github.com/uber/zanzibar/codegen"
	"github.com/pkg/errors"
	"sort"
)

const (
	templateFileName = "i64.tmpl"
	outFileName = "/types_i64.go"
)

type naivePackageNameResolver struct {
}

func (r *naivePackageNameResolver) TypePackageName(
	thriftFile string,
) (string, error) {
	if thriftFile[0] == '.' {
		return "", errors.Errorf("Naive does not support relative imports")
	}

	return "", nil
}

// Meta is the struct container for i64 related meta data and package name
type Meta struct {
	PackageName string
	Types []I64Struct
}

// I64Structs is the struct container for array if I64Struct
type I64Structs []I64Struct

// I64Struct is the struct container for i64 related meta data
type I64Struct struct {
	IsLong bool
	IsTimestamp bool
	TypedefType string
}

func (l I64Structs) Len() int           { return len(l) }
func (l I64Structs) Less(i, j int) bool {
	return l[i].IsTimestamp
}
func (l I64Structs) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }

func getDirName() string {
	_, file, _, _ := runtime.Caller(0)
	return zanzibar.GetDirnameFromRuntimeCaller(file)
}

func main() {
	thriftFile := os.Args[1]
	idlPathPrefix := os.Args[2]
	annotationJSType := os.Args[3]

	module, err := compile.Compile(thriftFile)
	if err != nil {
		panic(fmt.Sprintf("Failed to parse thrift file: %s", thriftFile))
	}
	meta := &Meta{}

	s := strings.TrimSuffix(thriftFile, ".thrift")
	s = strings.Replace(s, idlPathPrefix, "/build/gen-code/", 1)
	meta.PackageName = filepath.Base(s)

	for _, typeDef := range module.Types {
		t, ok := typeDef.(*compile.TypedefSpec)
		if !ok {
			continue
		}
		i64Struct := I64Struct{}
		if t.Target != nil {
			typThriftAnnotation := t.Target.ThriftAnnotations()
			if typThriftAnnotation != nil {
				p := naivePackageNameResolver{}
				refType, err := codegen.GoReferenceType(&p, t)
				if err != nil {
					fmt.Fprintln(os.Stderr, fmt.Errorf("error parsing reference type: %s", err.Error()))
					os.Exit(1)
					return
				}

				i64Struct.TypedefType = refType[1:]
				if typThriftAnnotation[annotationJSType] == "Long" {
					i64Struct.IsLong = true
				}
				if typThriftAnnotation[annotationJSType] == "Date" {
					i64Struct.IsTimestamp = true
				}
			}
		}
		if i64Struct.IsTimestamp || i64Struct.IsLong {
			meta.Types = append(meta.Types, i64Struct)
		}
	}
	if len(meta.Types) == 0 {
		return
	}
	// Note type defs are not read by unique order so they need to be sorted before writing
	sort.Sort(I64Structs(meta.Types))
	tmpl, err := template.ParseFiles(filepath.Join(getDirName(), templateFileName))
	if err != nil {
		fmt.Fprintln(os.Stderr, fmt.Errorf(
			"error parsing template %s: %s", templateFileName, err.Error()))
		os.Exit(1)
		return
	}
	tplBuffer := bytes.NewBuffer(nil)
	err = tmpl.Execute(tplBuffer, meta)
	if err != nil {
		fmt.Fprintln(os.Stderr, fmt.Errorf(
			"error executing template %s: %s", templateFileName, err.Error()))
		os.Exit(1)
		return
	}
	outName := s + outFileName
	err = ioutil.WriteFile(outName, tplBuffer.Bytes(), 0644)
	if err != nil {
		fmt.Fprintln(os.Stderr, fmt.Errorf(
			"error writing file to %s, err: %s", templateFileName, err.Error()))
		os.Exit(1)
		return
	}

	err = codegen.FormatGoFile(outName)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
<commit_msg>sorting is non-deterministic for js.type annotations<commit_after>// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package main

import (
	"fmt"
	"os"

	//"github.com/uber/zanzibar/codegen"
	"bytes"
	"github.com/pkg/errors"
	"github.com/uber/zanzibar/codegen"
	"github.com/uber/zanzibar/runtime"
	"go.uber.org/thriftrw/compile"
	"html/template"
	"io/ioutil"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
)

const (
	templateFileName = "i64.tmpl"
	outFileName      = "/types_i64.go"
)

type naivePackageNameResolver struct {
}

func (r *naivePackageNameResolver) TypePackageName(
	thriftFile string,
) (string, error) {
	if thriftFile[0] == '.' {
		return "", errors.Errorf("Naive does not support relative imports")
	}

	return "", nil
}

// Meta is the struct container for i64 related meta data and package name
type Meta struct {
	PackageName string
	Types       []I64Struct
}

// I64Structs is the struct container for array if I64Struct
type I64Structs []I64Struct

// I64Struct is the struct container for i64 related meta data
type I64Struct struct {
	IsLong      bool
	IsTimestamp bool
	TypedefType string
}

func (l I64Structs) Len() int { return len(l) }
func (l I64Structs) Less(i, j int) bool {
	return l[i].TypedefType < l[j].TypedefType
}
func (l I64Structs) Swap(i, j int) { l[i], l[j] = l[j], l[i] }

func getDirName() string {
	_, file, _, _ := runtime.Caller(0)
	return zanzibar.GetDirnameFromRuntimeCaller(file)
}

func main() {
	thriftFile := os.Args[1]
	idlPathPrefix := os.Args[2]
	annotationJSType := os.Args[3]

	module, err := compile.Compile(thriftFile)
	if err != nil {
		panic(fmt.Sprintf("Failed to parse thrift file: %s", thriftFile))
	}
	meta := &Meta{}

	s := strings.TrimSuffix(thriftFile, ".thrift")
	s = strings.Replace(s, idlPathPrefix, "/build/gen-code/", 1)
	meta.PackageName = filepath.Base(s)

	for _, typeDef := range module.Types {
		t, ok := typeDef.(*compile.TypedefSpec)
		if !ok {
			continue
		}
		i64Struct := I64Struct{}
		if t.Target != nil {
			typThriftAnnotation := t.Target.ThriftAnnotations()
			if typThriftAnnotation != nil {
				p := naivePackageNameResolver{}
				refType, err := codegen.GoReferenceType(&p, t)
				if err != nil {
					fmt.Fprintln(os.Stderr, fmt.Errorf("error parsing reference type: %s", err.Error()))
					os.Exit(1)
					return
				}

				i64Struct.TypedefType = refType[1:]
				if typThriftAnnotation[annotationJSType] == "Long" {
					i64Struct.IsLong = true
				}
				if typThriftAnnotation[annotationJSType] == "Date" {
					i64Struct.IsTimestamp = true
				}
			}
		}
		if i64Struct.IsTimestamp || i64Struct.IsLong {
			meta.Types = append(meta.Types, i64Struct)
		}
	}
	if len(meta.Types) == 0 {
		return
	}
	// Note type defs are not read by unique order so they need to be sorted before writing
	sort.Sort(I64Structs(meta.Types))
	tmpl, err := template.ParseFiles(filepath.Join(getDirName(), templateFileName))
	if err != nil {
		fmt.Fprintln(os.Stderr, fmt.Errorf(
			"error parsing template %s: %s", templateFileName, err.Error()))
		os.Exit(1)
		return
	}
	tplBuffer := bytes.NewBuffer(nil)
	err = tmpl.Execute(tplBuffer, meta)
	if err != nil {
		fmt.Fprintln(os.Stderr, fmt.Errorf(
			"error executing template %s: %s", templateFileName, err.Error()))
		os.Exit(1)
		return
	}
	outName := s + outFileName
	err = ioutil.WriteFile(outName, tplBuffer.Bytes(), 0644)
	if err != nil {
		fmt.Fprintln(os.Stderr, fmt.Errorf(
			"error writing file to %s, err: %s", templateFileName, err.Error()))
		os.Exit(1)
		return
	}

	err = codegen.FormatGoFile(outName)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package main

import (
	"context"
	"fmt"
	"math/rand"
	"os"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"github.com/docker/compose-cli/cli/cmd"
	"github.com/docker/compose-cli/cli/cmd/compose"
	contextcmd "github.com/docker/compose-cli/cli/cmd/context"
	"github.com/docker/compose-cli/cli/cmd/login"
	"github.com/docker/compose-cli/cli/cmd/logout"
	"github.com/docker/compose-cli/cli/cmd/run"
	"github.com/docker/compose-cli/cli/cmd/volume"
	"github.com/docker/compose-cli/cli/mobycli"
	cliopts "github.com/docker/compose-cli/cli/options"
	"github.com/docker/compose-cli/config"
	apicontext "github.com/docker/compose-cli/context"
	"github.com/docker/compose-cli/context/store"
	"github.com/docker/compose-cli/errdefs"
	"github.com/docker/compose-cli/metrics"

	// Backend registrations
	_ "github.com/docker/compose-cli/aci"
	_ "github.com/docker/compose-cli/ecs"
	_ "github.com/docker/compose-cli/ecs/local"
	_ "github.com/docker/compose-cli/example"
	_ "github.com/docker/compose-cli/local"
)

var (
	contextAgnosticCommands = map[string]struct{}{
		"compose": {},
		"context": {},
		"login":   {},
		"logout":  {},
		"serve":   {},
		"version": {},
	}
	unknownCommandRegexp = regexp.MustCompile(`unknown command "([^"]*)"`)
)

func init() {
	// initial hack to get the path of the project's bin dir
	// into the env of this cli for development
	path, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		fatal(errors.Wrap(err, "unable to get absolute bin path"))
	}

	if err := os.Setenv("PATH", appendPaths(os.Getenv("PATH"), path)); err != nil {
		panic(err)
	}
	// Seed random
	rand.Seed(time.Now().UnixNano())
}

func appendPaths(envPath string, path string) string {
	if envPath == "" {
		return path
	}
	return strings.Join([]string{envPath, path}, string(os.PathListSeparator))
}

func isContextAgnosticCommand(cmd *cobra.Command) bool {
	if cmd == nil {
		return false
	}
	if _, ok := contextAgnosticCommands[cmd.Name()]; ok {
		return true
	}
	return isContextAgnosticCommand(cmd.Parent())
}

func main() {
	var opts cliopts.GlobalOpts
	root := &cobra.Command{
		Use:           "docker",
		SilenceErrors: true,
		SilenceUsage:  true,
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if !isContextAgnosticCommand(cmd) {
				mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root())
			}
			return nil
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			return cmd.Help()
		},
	}

	root.AddCommand(
		contextcmd.Command(),
		cmd.PsCommand(),
		cmd.ServeCommand(),
		cmd.ExecCommand(),
		cmd.LogsCommand(),
		cmd.RmCommand(),
		cmd.StartCommand(),
		cmd.InspectCommand(),
		login.Command(),
		logout.Command(),
		cmd.VersionCommand(),
		cmd.StopCommand(),
		cmd.KillCommand(),
		cmd.SecretCommand(),
		cmd.PruneCommand(),

		// Place holders
		cmd.EcsCommand(),
	)

	helpFunc := root.HelpFunc()
	root.SetHelpFunc(func(cmd *cobra.Command, args []string) {
		if !isContextAgnosticCommand(cmd) {
			mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root())
		}
		helpFunc(cmd, args)
	})

	root.PersistentFlags().BoolVarP(&opts.Debug, "debug", "D", false, "Enable debug output in the logs")
	root.PersistentFlags().StringVarP(&opts.Host, "host", "H", "", "Daemon socket(s) to connect to")
	opts.AddConfigFlags(root.PersistentFlags())
	opts.AddContextFlags(root.PersistentFlags())
	root.Flags().BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit")

	walk(root, func(c *cobra.Command) {
		c.Flags().BoolP("help", "h", false, "Help for "+c.Name())
	})

	// populate the opts with the global flags
	_ = root.PersistentFlags().Parse(os.Args[1:])
	if opts.Debug {
		logrus.SetLevel(logrus.DebugLevel)
	}

	ctx, cancel := newSigContext()
	defer cancel()

	// --host and --version should immediately be forwarded to the original cli
	if opts.Host != "" || opts.Version {
		mobycli.Exec(root)
	}

	if opts.Config == "" {
		fatal(errors.New("config path cannot be empty"))
	}
	configDir := opts.Config
	ctx = config.WithDir(ctx, configDir)

	currentContext := determineCurrentContext(opts.Context, configDir)

	s, err := store.New(configDir)
	if err != nil {
		mobycli.Exec(root)
	}

	ctype := store.DefaultContextType
	cc, _ := s.Get(currentContext)
	if cc != nil {
		ctype = cc.Type()
	}

	root.AddCommand(
		run.Command(ctype),
		compose.Command(ctype),
		volume.Command(ctype),
	)

	ctx = apicontext.WithCurrentContext(ctx, currentContext)
	ctx = store.WithContextStore(ctx, s)

	if err = root.ExecuteContext(ctx); err != nil {
		// if user canceled request, simply exit without any error message
		if errdefs.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
			metrics.Track(ctype, os.Args[1:], metrics.CanceledStatus)
			os.Exit(130)
		}
		if ctype == store.AwsContextType {
			exit(currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running:
$ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype)
		}

		// Context should always be handled by new CLI
		requiredCmd, _, _ := root.Find(os.Args[1:])
		if requiredCmd != nil && isContextAgnosticCommand(requiredCmd) {
			exit(currentContext, err, ctype)
		}
		mobycli.ExecIfDefaultCtxType(ctx, root)

		checkIfUnknownCommandExistInDefaultContext(err, currentContext, ctype)

		exit(currentContext, err, ctype)
	}
	metrics.Track(ctype, os.Args[1:], metrics.SuccessStatus)
}

func exit(ctx string, err error, ctype string) {
	metrics.Track(ctype, os.Args[1:], metrics.FailureStatus)

	if errors.Is(err, errdefs.ErrLoginRequired) {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(errdefs.ExitCodeLoginRequired)
	}
	if errors.Is(err, errdefs.ErrNotImplemented) {
		name := metrics.GetCommand(os.Args[1:])
		fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s)\n", name, ctx)

		os.Exit(1)
	}

	fatal(err)
}

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	os.Exit(1)
}

func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string, contextType string) {
	submatch := unknownCommandRegexp.FindSubmatch([]byte(err.Error()))
	if len(submatch) == 2 {
		dockerCommand := string(submatch[1])

		if mobycli.IsDefaultContextCommand(dockerCommand) {
			fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s), you can use the \"default\" context to run this command\n", dockerCommand, currentContext)
			metrics.Track(contextType, os.Args[1:], metrics.FailureStatus)
			os.Exit(1)
		}
	}
}

func newSigContext() (context.Context, func()) {
	ctx, cancel := context.WithCancel(context.Background())
	s := make(chan os.Signal, 1)
	signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		<-s
		cancel()
	}()
	return ctx, cancel
}

func determineCurrentContext(flag string, configDir string) string {
	res := flag
	if res == "" {
		config, err := config.LoadFile(configDir)
		if err != nil {
			fmt.Fprintln(os.Stderr, errors.Wrap(err, "WARNING"))
			return "default"
		}
		res = config.CurrentContext
	}
	if res == "" {
		res = "default"
	}
	return res
}

func walk(c *cobra.Command, f func(*cobra.Command)) {
	f(c)
	for _, c := range c.Commands() {
		walk(c, f)
	}
}
<commit_msg>Parse --version and forward to docker/cli<commit_after>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package main

import (
	"context"
	"fmt"
	"math/rand"
	"os"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"github.com/docker/compose-cli/cli/cmd"
	"github.com/docker/compose-cli/cli/cmd/compose"
	contextcmd "github.com/docker/compose-cli/cli/cmd/context"
	"github.com/docker/compose-cli/cli/cmd/login"
	"github.com/docker/compose-cli/cli/cmd/logout"
	"github.com/docker/compose-cli/cli/cmd/run"
	"github.com/docker/compose-cli/cli/cmd/volume"
	"github.com/docker/compose-cli/cli/mobycli"
	cliopts "github.com/docker/compose-cli/cli/options"
	"github.com/docker/compose-cli/config"
	apicontext "github.com/docker/compose-cli/context"
	"github.com/docker/compose-cli/context/store"
	"github.com/docker/compose-cli/errdefs"
	"github.com/docker/compose-cli/metrics"

	// Backend registrations
	_ "github.com/docker/compose-cli/aci"
	_ "github.com/docker/compose-cli/ecs"
	_ "github.com/docker/compose-cli/ecs/local"
	_ "github.com/docker/compose-cli/example"
	_ "github.com/docker/compose-cli/local"
)

var (
	contextAgnosticCommands = map[string]struct{}{
		"compose": {},
		"context": {},
		"login":   {},
		"logout":  {},
		"serve":   {},
		"version": {},
	}
	unknownCommandRegexp = regexp.MustCompile(`unknown command "([^"]*)"`)
)

func init() {
	// initial hack to get the path of the project's bin dir
	// into the env of this cli for development
	path, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		fatal(errors.Wrap(err, "unable to get absolute bin path"))
	}

	if err := os.Setenv("PATH", appendPaths(os.Getenv("PATH"), path)); err != nil {
		panic(err)
	}
	// Seed random
	rand.Seed(time.Now().UnixNano())
}

func appendPaths(envPath string, path string) string {
	if envPath == "" {
		return path
	}
	return strings.Join([]string{envPath, path}, string(os.PathListSeparator))
}

func isContextAgnosticCommand(cmd *cobra.Command) bool {
	if cmd == nil {
		return false
	}
	if _, ok := contextAgnosticCommands[cmd.Name()]; ok {
		return true
	}
	return isContextAgnosticCommand(cmd.Parent())
}

func main() {
	var opts cliopts.GlobalOpts
	root := &cobra.Command{
		Use:           "docker",
		SilenceErrors: true,
		SilenceUsage:  true,
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if !isContextAgnosticCommand(cmd) {
				mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root())
			}
			return nil
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			return cmd.Help()
		},
	}

	root.AddCommand(
		contextcmd.Command(),
		cmd.PsCommand(),
		cmd.ServeCommand(),
		cmd.ExecCommand(),
		cmd.LogsCommand(),
		cmd.RmCommand(),
		cmd.StartCommand(),
		cmd.InspectCommand(),
		login.Command(),
		logout.Command(),
		cmd.VersionCommand(),
		cmd.StopCommand(),
		cmd.KillCommand(),
		cmd.SecretCommand(),
		cmd.PruneCommand(),

		// Place holders
		cmd.EcsCommand(),
	)

	helpFunc := root.HelpFunc()
	root.SetHelpFunc(func(cmd *cobra.Command, args []string) {
		if !isContextAgnosticCommand(cmd) {
			mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root())
		}
		helpFunc(cmd, args)
	})

	root.PersistentFlags().BoolVarP(&opts.Debug, "debug", "D", false, "Enable debug output in the logs")
	root.PersistentFlags().StringVarP(&opts.Host, "host", "H", "", "Daemon socket(s) to connect to")
	opts.AddContextFlags(root.PersistentFlags())
	opts.AddConfigFlags(root.PersistentFlags())
	root.Flags().BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit")

	walk(root, func(c *cobra.Command) {
		c.Flags().BoolP("help", "h", false, "Help for "+c.Name())
	})

	// populate the opts with the global flags
	_ = root.PersistentFlags().Parse(os.Args[1:])
	_ = root.Flags().Parse(os.Args[1:])
	if opts.Debug {
		logrus.SetLevel(logrus.DebugLevel)
	}

	ctx, cancel := newSigContext()
	defer cancel()

	// --host and --version should immediately be forwarded to the original cli
	if opts.Host != "" || opts.Version {
		mobycli.Exec(root)
	}

	if opts.Config == "" {
		fatal(errors.New("config path cannot be empty"))
	}
	configDir := opts.Config
	ctx = config.WithDir(ctx, configDir)

	currentContext := determineCurrentContext(opts.Context, configDir)

	s, err := store.New(configDir)
	if err != nil {
		mobycli.Exec(root)
	}

	ctype := store.DefaultContextType
	cc, _ := s.Get(currentContext)
	if cc != nil {
		ctype = cc.Type()
	}

	root.AddCommand(
		run.Command(ctype),
		compose.Command(ctype),
		volume.Command(ctype),
	)

	ctx = apicontext.WithCurrentContext(ctx, currentContext)
	ctx = store.WithContextStore(ctx, s)

	if err = root.ExecuteContext(ctx); err != nil {
		// if user canceled request, simply exit without any error message
		if errdefs.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
			metrics.Track(ctype, os.Args[1:], metrics.CanceledStatus)
			os.Exit(130)
		}
		if ctype == store.AwsContextType {
			exit(currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running:
$ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype)
		}

		// Context should always be handled by new CLI
		requiredCmd, _, _ := root.Find(os.Args[1:])
		if requiredCmd != nil && isContextAgnosticCommand(requiredCmd) {
			exit(currentContext, err, ctype)
		}
		mobycli.ExecIfDefaultCtxType(ctx, root)

		checkIfUnknownCommandExistInDefaultContext(err, currentContext, ctype)

		exit(currentContext, err, ctype)
	}
	metrics.Track(ctype, os.Args[1:], metrics.SuccessStatus)
}

func exit(ctx string, err error, ctype string) {
	metrics.Track(ctype, os.Args[1:], metrics.FailureStatus)

	if errors.Is(err, errdefs.ErrLoginRequired) {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(errdefs.ExitCodeLoginRequired)
	}
	if errors.Is(err, errdefs.ErrNotImplemented) {
		name := metrics.GetCommand(os.Args[1:])
		fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s)\n", name, ctx)

		os.Exit(1)
	}

	fatal(err)
}

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	os.Exit(1)
}

func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string, contextType string) {
	submatch := unknownCommandRegexp.FindSubmatch([]byte(err.Error()))
	if len(submatch) == 2 {
		dockerCommand := string(submatch[1])

		if mobycli.IsDefaultContextCommand(dockerCommand) {
			fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s), you can use the \"default\" context to run this command\n", dockerCommand, currentContext)
			metrics.Track(contextType, os.Args[1:], metrics.FailureStatus)
			os.Exit(1)
		}
	}
}

func newSigContext() (context.Context, func()) {
	ctx, cancel := context.WithCancel(context.Background())
	s := make(chan os.Signal, 1)
	signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		<-s
		cancel()
	}()
	return ctx, cancel
}

func determineCurrentContext(flag string, configDir string) string {
	res := flag
	if res == "" {
		config, err := config.LoadFile(configDir)
		if err != nil {
			fmt.Fprintln(os.Stderr, errors.Wrap(err, "WARNING"))
			return "default"
		}
		res = config.CurrentContext
	}
	if res == "" {
		res = "default"
	}
	return res
}

func walk(c *cobra.Command, f func(*cobra.Command)) {
	f(c)
	for _, c := range c.Commands() {
		walk(c, f)
	}
}
<|endoftext|>