<commit_before>package pushaction_test

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"

	. "code.cloudfoundry.org/cli/actor/pushaction"
	"code.cloudfoundry.org/cli/actor/pushaction/manifest"
	"code.cloudfoundry.org/cli/actor/pushaction/pushactionfakes"
	"code.cloudfoundry.org/cli/actor/v2action"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Application Config", func() {
	var (
		actor       *Actor
		fakeV2Actor *pushactionfakes.FakeV2Actor
	)

	BeforeEach(func() {
		fakeV2Actor = new(pushactionfakes.FakeV2Actor)
		actor = NewActor(fakeV2Actor)
	})

	Describe("ApplicationConfig", func() {
		Describe("CreatingApplication", func() {
			Context("when the app did not exist", func() {
				It("returns true", func() {
					config := ApplicationConfig{}
					Expect(config.CreatingApplication()).To(BeTrue())
				})
			})

			Context("when the app exists", func() {
				It("returns false", func() {
					config := ApplicationConfig{CurrentApplication: v2action.Application{GUID: "some-app-guid"}}
					Expect(config.CreatingApplication()).To(BeFalse())
				})
			})
		})

		Describe("UpdatedApplication", func() {
			Context("when the app did not exist", func() {
				It("returns false", func() {
					config := ApplicationConfig{}
					Expect(config.UpdatingApplication()).To(BeFalse())
				})
			})

			Context("when the app exists", func() {
				It("returns true", func() {
					config := ApplicationConfig{CurrentApplication: v2action.Application{GUID: "some-app-guid"}}
					Expect(config.UpdatingApplication()).To(BeTrue())
				})
			})
		})
	})

	Describe("ConvertToApplicationConfigs", func() {
		var (
			appName      string
			orgGUID      string
			spaceGUID    string
			domain       v2action.Domain
			manifestApps []manifest.Application
			filesPath    string

			configs    []ApplicationConfig
			warnings   Warnings
			executeErr error

			firstConfig ApplicationConfig
		)

		BeforeEach(func() {
			appName = "some-app"
			orgGUID = "some-org-guid"
			spaceGUID = "some-space-guid"

			var err error
			filesPath, err = ioutil.TempDir("", "convert-to-application-configs")
			Expect(err).ToNot(HaveOccurred())

			manifestApps = []manifest.Application{{
				Name: appName,
				Path: filesPath,
			}}

			domain = v2action.Domain{
				Name: "private-domain.com",
				GUID: "some-private-domain-guid",
			}
			// Prevents NoDomainsFoundError
			fakeV2Actor.GetOrganizationDomainsReturns(
				[]v2action.Domain{domain},
				v2action.Warnings{"private-domain-warnings", "shared-domain-warnings"},
				nil,
			)
		})

		JustBeforeEach(func() {
			configs, warnings, executeErr = actor.ConvertToApplicationConfigs(orgGUID, spaceGUID, manifestApps)
			if len(configs) > 0 {
				firstConfig = configs[0]
			}
		})

		AfterEach(func() {
			Expect(os.RemoveAll(filesPath)).ToNot(HaveOccurred())
		})

		Context("when the path is a symlink", func() {
			var target string

			BeforeEach(func() {
				parentDir := filepath.Dir(filesPath)
				target = filepath.Join(parentDir, "i-r-symlink")
				Expect(os.Symlink(filesPath, target)).ToNot(HaveOccurred())
				manifestApps[0].Path = target
			})

			AfterEach(func() {
				Expect(os.RemoveAll(target)).ToNot(HaveOccurred())
			})

			It("evaluates the symlink into an absolute path", func() {
				Expect(executeErr).ToNot(HaveOccurred())

				Expect(firstConfig.Path).To(Equal(filesPath))
			})

			Context("given a path that does not exist", func() {
				BeforeEach(func() {
					manifestApps[0].Path = "/i/will/fight/you/if/this/exists"
				})

				It("returns errors and warnings", func() {
					Expect(os.IsNotExist(executeErr)).To(BeTrue())

					Expect(fakeV2Actor.GatherDirectoryResourcesCallCount()).To(Equal(0))
					Expect(fakeV2Actor.GatherArchiveResourcesCallCount()).To(Equal(0))
				})
			})
		})

		Context("when the application exists", func() {
			var app v2action.Application
			var route v2action.Route

			BeforeEach(func() {
				app = v2action.Application{
					Name:      appName,
					GUID:      "some-app-guid",
					SpaceGUID: spaceGUID,
				}

				route = v2action.Route{
					Domain: v2action.Domain{
						Name: "some-domain.com",
						GUID: "some-domain-guid",
					},
					Host:      app.Name,
					GUID:      "route-guid",
					SpaceGUID: spaceGUID,
				}

				fakeV2Actor.GetApplicationByNameAndSpaceReturns(app, v2action.Warnings{"some-app-warning-1", "some-app-warning-2"}, nil)
			})

			Context("when retrieving the application's routes is successful", func() {
				BeforeEach(func() {
					fakeV2Actor.GetApplicationRoutesReturns([]v2action.Route{route}, v2action.Warnings{"app-route-warnings"}, nil)
				})

				It("sets the current application to the existing application", func() {
					Expect(executeErr).ToNot(HaveOccurred())
					Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2", "app-route-warnings", "private-domain-warnings", "shared-domain-warnings"))
					Expect(firstConfig.CurrentApplication).To(Equal(app))
					Expect(firstConfig.TargetedSpaceGUID).To(Equal(spaceGUID))

					Expect(fakeV2Actor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1))
					appName, passedSpaceGUID := fakeV2Actor.GetApplicationByNameAndSpaceArgsForCall(0)
					Expect(appName).To(Equal(app.Name))
					Expect(passedSpaceGUID).To(Equal(spaceGUID))
				})

				It("sets the current routes", func() {
					Expect(executeErr).ToNot(HaveOccurred())
					Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2", "app-route-warnings", "private-domain-warnings", "shared-domain-warnings"))
					Expect(firstConfig.CurrentRoutes).To(ConsistOf(route))
				})
			})

			Context("when retrieving the application's routes errors", func() {
				var expectedErr error

				BeforeEach(func() {
					expectedErr = errors.New("dios mio")
					fakeV2Actor.GetApplicationRoutesReturns(nil, v2action.Warnings{"app-route-warnings"}, expectedErr)
				})

				It("sets the current and desired application to the current", func() {
					Expect(executeErr).To(MatchError(expectedErr))
					Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2", "app-route-warnings"))

					Expect(fakeV2Actor.GetApplicationRoutesCallCount()).To(Equal(1))
					Expect(fakeV2Actor.GetApplicationRoutesArgsForCall(0)).To(Equal(app.GUID))
				})
			})
		})

		Context("when the application does not exist", func() {
			BeforeEach(func() {
				fakeV2Actor.GetApplicationByNameAndSpaceReturns(v2action.Application{}, v2action.Warnings{"some-app-warning-1", "some-app-warning-2"}, v2action.ApplicationNotFoundError{})
			})

			It("creates a new application and sets it to the desired application", func() {
				Expect(executeErr).ToNot(HaveOccurred())
				Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2", "private-domain-warnings", "shared-domain-warnings"))
				Expect(firstConfig.CurrentApplication).To(Equal(v2action.Application{}))
				Expect(firstConfig.DesiredApplication).To(Equal(v2action.Application{
					Name:      "some-app",
					SpaceGUID: spaceGUID,
				}))
				Expect(firstConfig.TargetedSpaceGUID).To(Equal(spaceGUID))
			})
		})

		Context("when retrieving the application errors", func() {
			var expectedErr error

			BeforeEach(func() {
				expectedErr = errors.New("dios mio")
				fakeV2Actor.GetApplicationByNameAndSpaceReturns(v2action.Application{}, v2action.Warnings{"some-app-warning-1", "some-app-warning-2"}, expectedErr)
			})

			It("returns the error and warnings", func() {
				Expect(executeErr).To(MatchError(expectedErr))
				Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2"))
			})
		})

		Context("when retrieving the default route is successful", func() {
			BeforeEach(func() {
				// Assumes new route
				fakeV2Actor.FindRouteBoundToSpaceWithSettingsReturns(v2action.Route{}, v2action.Warnings{"get-route-warnings"}, v2action.RouteNotFoundError{})
			})

			It("adds the route to desired routes", func() {
				Expect(executeErr).ToNot(HaveOccurred())
				Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings", "get-route-warnings"))
				Expect(firstConfig.DesiredRoutes).To(ConsistOf(v2action.Route{
					Domain:    domain,
					Host:      appName,
					SpaceGUID: spaceGUID,
				}))
			})
		})

		Context("when retrieving the default route errors", func() {
			var expectedErr error

			BeforeEach(func() {
				expectedErr = errors.New("dios mio")
				fakeV2Actor.FindRouteBoundToSpaceWithSettingsReturns(v2action.Route{}, v2action.Warnings{"get-route-warnings"}, expectedErr)
			})

			It("returns the error and warnings", func() {
				Expect(executeErr).To(MatchError(expectedErr))
				Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings", "get-route-warnings"))
			})
		})

		Context("when scanning for files", func() {
			Context("given a directory", func() {
				Context("when scanning is successful", func() {
					var resources []v2action.Resource

					BeforeEach(func() {
						resources = []v2action.Resource{
							{Filename: "I am a file!"},
							{Filename: "I am not a file"},
						}
						fakeV2Actor.GatherDirectoryResourcesReturns(resources, nil)
					})

					It("sets the full resource list on the config", func() {
						Expect(executeErr).ToNot(HaveOccurred())
						Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings"))
						Expect(firstConfig.AllResources).To(Equal(resources))
						Expect(firstConfig.Path).To(Equal(filesPath))
						Expect(firstConfig.Archive).To(BeFalse())

						Expect(fakeV2Actor.GatherDirectoryResourcesCallCount()).To(Equal(1))
						Expect(fakeV2Actor.GatherDirectoryResourcesArgsForCall(0)).To(Equal(filesPath))
					})
				})

				Context("when scanning errors", func() {
					var expectedErr error

					BeforeEach(func() {
						expectedErr = errors.New("dios mio")
						fakeV2Actor.GatherDirectoryResourcesReturns(nil, expectedErr)
					})

					It("returns the error and warnings", func() {
						Expect(executeErr).To(MatchError(expectedErr))
						Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings"))
					})
				})
			})

			Context("given archive", func() {
				var archive string

				BeforeEach(func() {
					f, err := ioutil.TempFile("", "convert-to-application-configs-archive")
					Expect(err).ToNot(HaveOccurred())
					archive = f.Name()
					Expect(f.Close()).ToNot(HaveOccurred())

					manifestApps[0].Path = archive
				})

				AfterEach(func() {
					Expect(os.RemoveAll(archive)).ToNot(HaveOccurred())
				})

				Context("when scanning is successful", func() {
					var resources []v2action.Resource

					BeforeEach(func() {
						resources = []v2action.Resource{
							{Filename: "I am a file!"},
							{Filename: "I am not a file"},
						}
						fakeV2Actor.GatherArchiveResourcesReturns(resources, nil)
					})

					It("sets the full resource list on the config", func() {
						Expect(executeErr).ToNot(HaveOccurred())
						Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings"))
						Expect(firstConfig.AllResources).To(Equal(resources))
						Expect(firstConfig.Path).To(Equal(archive))
						Expect(firstConfig.Archive).To(BeTrue())

						Expect(fakeV2Actor.GatherArchiveResourcesCallCount()).To(Equal(1))
						Expect(fakeV2Actor.GatherArchiveResourcesArgsForCall(0)).To(Equal(archive))
					})
				})

				Context("when scanning errors", func() {
					var expectedErr error

					BeforeEach(func() {
						expectedErr = errors.New("dios mio")
						fakeV2Actor.GatherArchiveResourcesReturns(nil, expectedErr)
					})

					It("returns the error and warnings", func() {
						Expect(executeErr).To(MatchError(expectedErr))
						Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings"))
					})
				})
			})

		})

		Context("when a docker image is configured", func() {
			BeforeEach(func() {
				manifestApps[0].DockerImage = "some-docker-image-path"
			})

			It("sets the docker image on DesiredApplication and does not gather resources", func() {
				Expect(executeErr).ToNot(HaveOccurred())
				Expect(firstConfig.DesiredApplication.DockerImage).To(Equal("some-docker-image-path"))

				Expect(fakeV2Actor.GatherDirectoryResourcesCallCount()).To(Equal(0))
			})
		})
	})
})
<commit_msg>no shadows!<commit_after>package pushaction_test

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"

	. "code.cloudfoundry.org/cli/actor/pushaction"
	"code.cloudfoundry.org/cli/actor/pushaction/manifest"
	"code.cloudfoundry.org/cli/actor/pushaction/pushactionfakes"
	"code.cloudfoundry.org/cli/actor/v2action"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Application Config", func() {
	var (
		actor       *Actor
		fakeV2Actor *pushactionfakes.FakeV2Actor
	)

	BeforeEach(func() {
		fakeV2Actor = new(pushactionfakes.FakeV2Actor)
		actor = NewActor(fakeV2Actor)
	})

	Describe("ApplicationConfig", func() {
		Describe("CreatingApplication", func() {
			Context("when the app did not exist", func() {
				It("returns true", func() {
					config := ApplicationConfig{}
					Expect(config.CreatingApplication()).To(BeTrue())
				})
			})

			Context("when the app exists", func() {
				It("returns false", func() {
					config := ApplicationConfig{CurrentApplication: v2action.Application{GUID: "some-app-guid"}}
					Expect(config.CreatingApplication()).To(BeFalse())
				})
			})
		})

		Describe("UpdatedApplication", func() {
			Context("when the app did not exist", func() {
				It("returns false", func() {
					config := ApplicationConfig{}
					Expect(config.UpdatingApplication()).To(BeFalse())
				})
			})

			Context("when the app exists", func() {
				It("returns true", func() {
					config := ApplicationConfig{CurrentApplication: v2action.Application{GUID: "some-app-guid"}}
					Expect(config.UpdatingApplication()).To(BeTrue())
				})
			})
		})
	})

	Describe("ConvertToApplicationConfigs", func() {
		var (
			appName      string
			orgGUID      string
			spaceGUID    string
			domain       v2action.Domain
			manifestApps []manifest.Application
			filesPath    string

			configs    []ApplicationConfig
			warnings   Warnings
			executeErr error

			firstConfig ApplicationConfig
		)

		BeforeEach(func() {
			appName = "some-app"
			orgGUID = "some-org-guid"
			spaceGUID = "some-space-guid"

			var err error
			filesPath, err = ioutil.TempDir("", "convert-to-application-configs")
			Expect(err).ToNot(HaveOccurred())

			manifestApps = []manifest.Application{{
				Name: appName,
				Path: filesPath,
			}}

			domain = v2action.Domain{
				Name: "private-domain.com",
				GUID: "some-private-domain-guid",
			}
			// Prevents NoDomainsFoundError
			fakeV2Actor.GetOrganizationDomainsReturns(
				[]v2action.Domain{domain},
				v2action.Warnings{"private-domain-warnings", "shared-domain-warnings"},
				nil,
			)
		})

		JustBeforeEach(func() {
			configs, warnings, executeErr = actor.ConvertToApplicationConfigs(orgGUID, spaceGUID, manifestApps)
			if len(configs) > 0 {
				firstConfig = configs[0]
			}
		})

		AfterEach(func() {
			Expect(os.RemoveAll(filesPath)).ToNot(HaveOccurred())
		})

		Context("when the path is a symlink", func() {
			var target string

			BeforeEach(func() {
				parentDir := filepath.Dir(filesPath)
				target = filepath.Join(parentDir, "i-r-symlink")
				Expect(os.Symlink(filesPath, target)).ToNot(HaveOccurred())
				manifestApps[0].Path = target
			})

			AfterEach(func() {
				Expect(os.RemoveAll(target)).ToNot(HaveOccurred())
			})

			It("evaluates the symlink into an absolute path", func() {
				Expect(executeErr).ToNot(HaveOccurred())

				Expect(firstConfig.Path).To(Equal(filesPath))
			})

			Context("given a path that does not exist", func() {
				BeforeEach(func() {
					manifestApps[0].Path = "/i/will/fight/you/if/this/exists"
				})

				It("returns errors and warnings", func() {
					Expect(os.IsNotExist(executeErr)).To(BeTrue())

					Expect(fakeV2Actor.GatherDirectoryResourcesCallCount()).To(Equal(0))
					Expect(fakeV2Actor.GatherArchiveResourcesCallCount()).To(Equal(0))
				})
			})
		})

		Context("when the application exists", func() {
			var app v2action.Application
			var route v2action.Route

			BeforeEach(func() {
				app = v2action.Application{
					Name:      appName,
					GUID:      "some-app-guid",
					SpaceGUID: spaceGUID,
				}

				route = v2action.Route{
					Domain: v2action.Domain{
						Name: "some-domain.com",
						GUID: "some-domain-guid",
					},
					Host:      app.Name,
					GUID:      "route-guid",
					SpaceGUID: spaceGUID,
				}

				fakeV2Actor.GetApplicationByNameAndSpaceReturns(app, v2action.Warnings{"some-app-warning-1", "some-app-warning-2"}, nil)
			})

			Context("when retrieving the application's routes is successful", func() {
				BeforeEach(func() {
					fakeV2Actor.GetApplicationRoutesReturns([]v2action.Route{route}, v2action.Warnings{"app-route-warnings"}, nil)
				})

				It("sets the current application to the existing application", func() {
					Expect(executeErr).ToNot(HaveOccurred())
					Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2", "app-route-warnings", "private-domain-warnings", "shared-domain-warnings"))
					Expect(firstConfig.CurrentApplication).To(Equal(app))
					Expect(firstConfig.TargetedSpaceGUID).To(Equal(spaceGUID))

					Expect(fakeV2Actor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1))
					passedName, passedSpaceGUID := fakeV2Actor.GetApplicationByNameAndSpaceArgsForCall(0)
					Expect(passedName).To(Equal(app.Name))
					Expect(passedSpaceGUID).To(Equal(spaceGUID))
				})

				It("sets the current routes", func() {
					Expect(executeErr).ToNot(HaveOccurred())
					Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2", "app-route-warnings", "private-domain-warnings", "shared-domain-warnings"))
					Expect(firstConfig.CurrentRoutes).To(ConsistOf(route))
				})
			})

			Context("when retrieving the application's routes errors", func() {
				var expectedErr error

				BeforeEach(func() {
					expectedErr = errors.New("dios mio")
					fakeV2Actor.GetApplicationRoutesReturns(nil, v2action.Warnings{"app-route-warnings"}, expectedErr)
				})

				It("sets the current and desired application to the current", func() {
					Expect(executeErr).To(MatchError(expectedErr))
					Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2", "app-route-warnings"))

					Expect(fakeV2Actor.GetApplicationRoutesCallCount()).To(Equal(1))
					Expect(fakeV2Actor.GetApplicationRoutesArgsForCall(0)).To(Equal(app.GUID))
				})
			})
		})

		Context("when the application does not exist", func() {
			BeforeEach(func() {
				fakeV2Actor.GetApplicationByNameAndSpaceReturns(v2action.Application{}, v2action.Warnings{"some-app-warning-1", "some-app-warning-2"}, v2action.ApplicationNotFoundError{})
			})

			It("creates a new application and sets it to the desired application", func() {
				Expect(executeErr).ToNot(HaveOccurred())
				Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2", "private-domain-warnings", "shared-domain-warnings"))
				Expect(firstConfig.CurrentApplication).To(Equal(v2action.Application{}))
				Expect(firstConfig.DesiredApplication).To(Equal(v2action.Application{
					Name:      "some-app",
					SpaceGUID: spaceGUID,
				}))
				Expect(firstConfig.TargetedSpaceGUID).To(Equal(spaceGUID))
			})
		})

		Context("when retrieving the application errors", func() {
			var expectedErr error

			BeforeEach(func() {
				expectedErr = errors.New("dios mio")
				fakeV2Actor.GetApplicationByNameAndSpaceReturns(v2action.Application{}, v2action.Warnings{"some-app-warning-1", "some-app-warning-2"}, expectedErr)
			})

			It("returns the error and warnings", func() {
				Expect(executeErr).To(MatchError(expectedErr))
				Expect(warnings).To(ConsistOf("some-app-warning-1", "some-app-warning-2"))
			})
		})

		Context("when retrieving the default route is successful", func() {
			BeforeEach(func() {
				// Assumes new route
				fakeV2Actor.FindRouteBoundToSpaceWithSettingsReturns(v2action.Route{}, v2action.Warnings{"get-route-warnings"}, v2action.RouteNotFoundError{})
			})

			It("adds the route to desired routes", func() {
				Expect(executeErr).ToNot(HaveOccurred())
				Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings", "get-route-warnings"))
				Expect(firstConfig.DesiredRoutes).To(ConsistOf(v2action.Route{
					Domain:    domain,
					Host:      appName,
					SpaceGUID: spaceGUID,
				}))
			})
		})

		Context("when retrieving the default route errors", func() {
			var expectedErr error

			BeforeEach(func() {
				expectedErr = errors.New("dios mio")
				fakeV2Actor.FindRouteBoundToSpaceWithSettingsReturns(v2action.Route{}, v2action.Warnings{"get-route-warnings"}, expectedErr)
			})

			It("returns the error and warnings", func() {
				Expect(executeErr).To(MatchError(expectedErr))
				Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings", "get-route-warnings"))
			})
		})

		Context("when scanning for files", func() {
			Context("given a directory", func() {
				Context("when scanning is successful", func() {
					var resources []v2action.Resource

					BeforeEach(func() {
						resources = []v2action.Resource{
							{Filename: "I am a file!"},
							{Filename: "I am not a file"},
						}
						fakeV2Actor.GatherDirectoryResourcesReturns(resources, nil)
					})

					It("sets the full resource list on the config", func() {
						Expect(executeErr).ToNot(HaveOccurred())
						Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings"))
						Expect(firstConfig.AllResources).To(Equal(resources))
						Expect(firstConfig.Path).To(Equal(filesPath))
						Expect(firstConfig.Archive).To(BeFalse())

						Expect(fakeV2Actor.GatherDirectoryResourcesCallCount()).To(Equal(1))
						Expect(fakeV2Actor.GatherDirectoryResourcesArgsForCall(0)).To(Equal(filesPath))
					})
				})

				Context("when scanning errors", func() {
					var expectedErr error

					BeforeEach(func() {
						expectedErr = errors.New("dios mio")
						fakeV2Actor.GatherDirectoryResourcesReturns(nil, expectedErr)
					})

					It("returns the error and warnings", func() {
						Expect(executeErr).To(MatchError(expectedErr))
						Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings"))
					})
				})
			})

			Context("given archive", func() {
				var archive string

				BeforeEach(func() {
					f, err := ioutil.TempFile("", "convert-to-application-configs-archive")
					Expect(err).ToNot(HaveOccurred())
					archive = f.Name()
					Expect(f.Close()).ToNot(HaveOccurred())

					manifestApps[0].Path = archive
				})

				AfterEach(func() {
					Expect(os.RemoveAll(archive)).ToNot(HaveOccurred())
				})

				Context("when scanning is successful", func() {
					var resources []v2action.Resource

					BeforeEach(func() {
						resources = []v2action.Resource{
							{Filename: "I am a file!"},
							{Filename: "I am not a file"},
						}
						fakeV2Actor.GatherArchiveResourcesReturns(resources, nil)
					})

					It("sets the full resource list on the config", func() {
						Expect(executeErr).ToNot(HaveOccurred())
						Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings"))
						Expect(firstConfig.AllResources).To(Equal(resources))
						Expect(firstConfig.Path).To(Equal(archive))
						Expect(firstConfig.Archive).To(BeTrue())

						Expect(fakeV2Actor.GatherArchiveResourcesCallCount()).To(Equal(1))
						Expect(fakeV2Actor.GatherArchiveResourcesArgsForCall(0)).To(Equal(archive))
					})
				})

				Context("when scanning errors", func() {
					var expectedErr error

					BeforeEach(func() {
						expectedErr = errors.New("dios mio")
						fakeV2Actor.GatherArchiveResourcesReturns(nil, expectedErr)
					})

					It("returns the error and warnings", func() {
						Expect(executeErr).To(MatchError(expectedErr))
						Expect(warnings).To(ConsistOf("private-domain-warnings", "shared-domain-warnings"))
					})
				})
			})

		})

		Context("when a docker image is configured", func() {
			BeforeEach(func() {
				manifestApps[0].DockerImage = "some-docker-image-path"
			})

			It("sets the docker image on DesiredApplication and does not gather resources", func() {
				Expect(executeErr).ToNot(HaveOccurred())
				Expect(firstConfig.DesiredApplication.DockerImage).To(Equal("some-docker-image-path"))

				Expect(fakeV2Actor.GatherDirectoryResourcesCallCount()).To(Equal(0))
			})
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2011 Miek Gieben. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dns

// Inspired copied from:
// http://blog.cloudflare.com/recycling-memory-buffers-in-go
// ... carries no lisence

import (
	"container/list"
	"time"
)

func mkBuf(size int) []byte { return make([]byte, size) }

type queued struct {
	when time.Time
	buf  []byte
}

func pool(size, bufsize int) (get, give chan []byte) {
	get = make(chan []byte, bufsize)
	give = make(chan []byte, bufsize)

	go func() {
		q := new(list.List)
		for {
			e := q.Front()
			if e == nil {
				q.PushFront(queued{when: time.Now(), buf: mkBuf(size)})
				e = q.Front()
			}

			timeout := time.NewTimer(time.Minute)
			select {
			case b := <-give:
				timeout.Stop()
				q.PushFront(queued{when: time.Now(), buf: b})

			case get <- e.Value.(queued).buf:
				timeout.Stop()
				q.Remove(e)

			case <-timeout.C:
				e := q.Front()
				for e != nil {
					n := e.Next()
					if time.Since(e.Value.(queued).when) > time.Minute {
						q.Remove(e)
						e.Value = nil
					}
					e = n
				}
			}
		}

	}()
	return
}
<commit_msg>Correct doc<commit_after>// Copyright 2011 Miek Gieben. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dns

// Inspired copied from:
// http://blog.cloudflare.com/recycling-memory-buffers-in-go
// ... carries no license ...

import (
	"container/list"
	"time"
)

func mkBuf(size int) []byte { return make([]byte, size) }

type queued struct {
	when time.Time
	buf  []byte
}

func pool(size, bufsize int) (get, give chan []byte) {
	get = make(chan []byte, bufsize)
	give = make(chan []byte, bufsize)

	go func() {
		q := new(list.List)
		for {
			e := q.Front()
			if e == nil {
				q.PushFront(queued{when: time.Now(), buf: mkBuf(size)})
				e = q.Front()
			}

			timeout := time.NewTimer(time.Minute)
			select {
			case b := <-give:
				timeout.Stop()
				q.PushFront(queued{when: time.Now(), buf: b})

			case get <- e.Value.(queued).buf:
				timeout.Stop()
				q.Remove(e)

			case <-timeout.C:
				e := q.Front()
				for e != nil {
					n := e.Next()
					if time.Since(e.Value.(queued).when) > time.Minute {
						q.Remove(e)
						e.Value = nil
					}
					e = n
				}
			}
		}

	}()
	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"sync"
	"time"

	"github.com/pborman/uuid"
)

// default average speed interval
var SpeedInterval time.Duration = 10

// payload type(job type)
type PayloadType string

type fnType func(payload PayloadType)

// --------------------------------------- //

// Job
type Job struct {
	ID      string                    //job ID
	fn      func(payload PayloadType) //job function
	Payload PayloadType               //job payload
}

// Worker
type Worker struct {
	ID            int32    //job ID
	jobCacheQueue chan Job //job channl
	die           bool
	wait          bool //wait for done
	wg            *sync.WaitGroup
}

func (w *Worker) Run() {
	go func() {
		for {
			// shutdown
			if w.die {
				return
			}
			job := <-w.jobCacheQueue
			job.fn(job.Payload)
			// wait for done
			if w.wait {
				w.wg.Done()
			}
		}
	}()
}

type GoroutinePool struct {
	maxWorkers       int32 //max workers
	doneJobs         int32
	jobCacheQueue    chan Job
	jobCacheQueueLen int32
	workers          []*Worker
	wg               *sync.WaitGroup
	wait             bool
	feedback         bool
}

// new GoroutinePool
func NewGoroutinePool(maxWorkers int32, jobCacheQueueLen int32, feedback bool) *GoroutinePool {
	jobCacheQueue := make(chan Job, jobCacheQueueLen)
	workers := make([]*Worker, jobCacheQueueLen)

	if maxWorkers > jobCacheQueueLen {
		panic("maxWorkers must <= jobCacheQueueLen")
	}

	var wg sync.WaitGroup

	pool := &GoroutinePool{
		maxWorkers:       maxWorkers,
		jobCacheQueueLen: jobCacheQueueLen,
		jobCacheQueue:    jobCacheQueue,
		wait:             true,
		feedback:         feedback,
		wg:               &wg,
		workers:          workers,
		doneJobs:         0,
	}
	// start worker
	pool.Start()
	// start monitor
	pool.Monitor()

	return pool
}

func (pool *GoroutinePool) AddJob(fn func(payload PayloadType), payload PayloadType) {
	ID := uuid.NewUUID()
	job := Job{
		ID:      ID.String(),
		fn:      fn,
		Payload: payload,
	}
	if pool.wait {
		pool.wg.Add(1)
	}
	// bad
	pool.doneJobs++
	pool.jobCacheQueue <- job
}

func (pool *GoroutinePool) Monitor() {
	var lastDone int32
	// bad
	lastDone = pool.jobCacheQueueLen
	var speed int32
	interval := time.NewTicker(SpeedInterval * time.Second)

	// real-time speed
	var realtimeLastDone int32
	// bad
	realtimeLastDone = pool.jobCacheQueueLen
	var realtimeSpeed int32

	// interval monintor
	ticker := time.NewTicker(1 * time.Second)
	quit := make(chan struct{})

	// time cost
	tStart := time.Now()
	var costDuration time.Duration

	initWorkers := pool.maxWorkers

	var DC bool = false

	go func() {
		for {
			select {
			case <-ticker.C:
				tCurrent := time.Now()
				costDuration = tCurrent.Sub(tStart)

				// real-time speed
				tmpRealtimeSpeed := (pool.doneJobs - realtimeLastDone)

				realtimeSpeed = tmpRealtimeSpeed
				realtimeLastDone = pool.doneJobs

				fmt.Printf("\r Start at: %s, time cost: %s, average speed: %d, read-time speed: %d, current workers: %d, done jobs: %d     ", tStart.Format("15:04:05.000"), costDuration.String(), speed, realtimeSpeed, pool.maxWorkers, pool.doneJobs)

			// feedback mechanism!
			case <-interval.C:
				// average speed
				tmpSpeed := (pool.doneJobs - lastDone) / int32(SpeedInterval)

				if pool.feedback {
					var feedbackMaxWorkers int32

					feedbackMaxWorkers = pool.maxWorkers
					if tmpSpeed < speed {
						if DC == true {
							feedbackMaxWorkers = pool.maxWorkers * 6 / 7
							fmt.Printf("DoubleCheck Feedback \n")
							DC = false
						} else {
							DC = true
						}
					} else {
						feedbackMaxWorkers = pool.maxWorkers * 8 / 7
						DC = false
					}

					if feedbackMaxWorkers < initWorkers {
						feedbackMaxWorkers = initWorkers
					}
					if feedbackMaxWorkers > pool.jobCacheQueueLen {
						feedbackMaxWorkers = pool.jobCacheQueueLen
					}
					// feedback worker numbers

					fmt.Printf("\r Start at: %s, time cost: %s, average speed: %d, read-time speed: %d, current workers: %d, done jobs: %d     ", tStart.Format("15:04:05.000"), costDuration.String(), speed, realtimeSpeed, feedbackMaxWorkers, pool.doneJobs)
					pool.feedbackWorkers(feedbackMaxWorkers)
				}

				speed = tmpSpeed
				lastDone = pool.doneJobs

			case <-quit:
				ticker.Stop()
				return
			}
		}
	}()
}

func (pool *GoroutinePool) MapRun(fn func(payload PayloadType), payloads []PayloadType) {
	// maybe reflect
	// http://blog.burntsushi.net/type-parametric-functions-golang/
	for _, payload := range payloads {
		pool.AddJob(fn, payload)
	}
}

func (pool *GoroutinePool) MapRunChan(fn func(payload PayloadType), fnfetch func() PayloadType) {
	for {
		payload := fnfetch()
		pool.AddJob(fn, payload)
	}
}

func (pool *GoroutinePool) Start() {
	for i := int32(0); i < pool.maxWorkers; i++ {
		worker := &Worker{
			ID:            int32(i),
			jobCacheQueue: pool.jobCacheQueue,
			wait:          pool.wait,
			die:           false,
			wg:            pool.wg,
		}
		worker.Run()
		pool.workers[i] = worker
	}
}

func (pool *GoroutinePool) feedbackWorkers(feedbackMaxWorkers int32) {
	if feedbackMaxWorkers > pool.maxWorkers {
		for i := pool.maxWorkers; i < feedbackMaxWorkers; i++ {
			worker := &Worker{
				ID:            int32(i),
				jobCacheQueue: pool.jobCacheQueue,
				wait:          pool.wait,
				die:           false,
				wg:            pool.wg,
			}
			worker.Run()
			pool.workers[i] = worker
		}
	} else {
		for i := feedbackMaxWorkers; i < pool.maxWorkers; i++ {
			pool.workers[i].die = true
		}
	}

	pool.maxWorkers = feedbackMaxWorkers
}

func (pool *GoroutinePool) Wait() {
	pool.wg.Wait()
}

func CustomLogger(fileName string) *log.Logger {
	// f, err := os.OpenFile(fileName, os.O_APPEND | os.O_CREATE | os.O_RDWR, 0666)
	f, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		fmt.Printf("error opening file: %v", err)
	}
	logger = log.New(f, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
	return logger
}
<commit_msg>update feedback rules<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"sync"
	"time"

	"github.com/pborman/uuid"
)

// default average speed interval
var SpeedInterval time.Duration = 10

// payload type(job type)
type PayloadType string

type fnType func(payload PayloadType)

// --------------------------------------- //

// Job
type Job struct {
	ID      string                    //job ID
	fn      func(payload PayloadType) //job function
	Payload PayloadType               //job payload
}

// Worker
type Worker struct {
	ID            int32    //job ID
	jobCacheQueue chan Job //job channl
	die           bool
	wait          bool //wait for done
	wg            *sync.WaitGroup
}

func (w *Worker) Run() {
	go func() {
		for {
			// shutdown
			if w.die {
				return
			}
			job := <-w.jobCacheQueue
			job.fn(job.Payload)
			// wait for done
			if w.wait {
				w.wg.Done()
			}
		}
	}()
}

type GoroutinePool struct {
	maxWorkers       int32 //max workers
	doneJobs         int32
	jobCacheQueue    chan Job
	jobCacheQueueLen int32
	workers          []*Worker
	wg               *sync.WaitGroup
	wait             bool
	feedback         bool
}

// new GoroutinePool
func NewGoroutinePool(maxWorkers int32, jobCacheQueueLen int32, feedback bool) *GoroutinePool {
	jobCacheQueue := make(chan Job, jobCacheQueueLen)
	workers := make([]*Worker, jobCacheQueueLen)

	if maxWorkers > jobCacheQueueLen {
		panic("maxWorkers must <= jobCacheQueueLen")
	}

	var wg sync.WaitGroup

	pool := &GoroutinePool{
		maxWorkers:       maxWorkers,
		jobCacheQueueLen: jobCacheQueueLen,
		jobCacheQueue:    jobCacheQueue,
		wait:             true,
		feedback:         feedback,
		wg:               &wg,
		workers:          workers,
		doneJobs:         0,
	}
	// start worker
	pool.Start()
	// start monitor
	pool.Monitor()

	return pool
}

func (pool *GoroutinePool) AddJob(fn func(payload PayloadType), payload PayloadType) {
	ID := uuid.NewUUID()
	job := Job{
		ID:      ID.String(),
		fn:      fn,
		Payload: payload,
	}
	if pool.wait {
		pool.wg.Add(1)
	}
	// bad
	pool.doneJobs++
	pool.jobCacheQueue <- job
}

func (pool *GoroutinePool) Monitor() {
	var lastDone int32
	// bad
	lastDone = pool.jobCacheQueueLen
	var speed int32
	interval := time.NewTicker(SpeedInterval * time.Second)

	// real-time speed
	var realtimeLastDone int32
	// bad
	realtimeLastDone = pool.jobCacheQueueLen
	var realtimeSpeed int32

	// interval monintor
	ticker := time.NewTicker(1 * time.Second)
	quit := make(chan struct{})

	// time cost
	tStart := time.Now()
	var costDuration time.Duration

	var feedbackDoubleCheck bool = false

	initWorkers := pool.maxWorkers

	go func() {
		for {
			select {

			case <-ticker.C:
				tCurrent := time.Now()
				costDuration = tCurrent.Sub(tStart)

				// real-time speed
				tmpRealtimeSpeed := (pool.doneJobs - realtimeLastDone)

				realtimeSpeed = tmpRealtimeSpeed
				realtimeLastDone = pool.doneJobs

				fmt.Printf("\r Start at: %s, time cost: %s, average speed: %d, read-time speed: %d, current workers: %d, done jobs: %d     ", tStart.Format("15:04:05.000"), costDuration.String(), speed, realtimeSpeed, pool.maxWorkers, pool.doneJobs)

			// feedback mechanism!
			case <-interval.C:
				// average speed
				tmpSpeed := (pool.doneJobs - lastDone) / int32(SpeedInterval)

				if pool.feedback {
					var feedbackMaxWorkers int32

					feedbackMaxWorkers = pool.maxWorkers
					if tmpSpeed < speed {
						if feedbackDoubleCheck == true {
							//feedbackMaxWorkers = pool.maxWorkers - pool.maxWorkers*(speed-tmpSpeed)/speed
							feedbackMaxWorkers -= feedbackMaxWorkers / 10
							fmt.Printf("DoubleCheck Feedback \n")
							feedbackDoubleCheck = false
						} else {
							feedbackDoubleCheck = true
						}
					} else {
						if speed > 0 {
							//feedbackMaxWorkers = pool.maxWorkers + pool.maxWorkers*(tmpSpeed-speed)/speed
							feedbackMaxWorkers += feedbackMaxWorkers / 10

						}
						feedbackDoubleCheck = false
					}

					if feedbackMaxWorkers < initWorkers {
						feedbackMaxWorkers = initWorkers
					}
					if feedbackMaxWorkers > pool.jobCacheQueueLen {
						feedbackMaxWorkers = pool.jobCacheQueueLen
					}
					if feedbackMaxWorkers > 2*pool.maxWorkers {
						feedbackMaxWorkers = 2 * pool.maxWorkers
					}
					// feedback worker numbers

					fmt.Printf("\r Start at: %s, time cost: %s, average speed: %d, read-time speed: %d, current workers: %d, done jobs: %d     ", tStart.Format("15:04:05.000"), costDuration.String(), speed, realtimeSpeed, feedbackMaxWorkers, pool.doneJobs)
					pool.feedbackWorkers(feedbackMaxWorkers)
				}

				speed = tmpSpeed
				lastDone = pool.doneJobs

			case <-quit:
				ticker.Stop()
				return
			}
		}
	}()
}

func (pool *GoroutinePool) MapRun(fn func(payload PayloadType), payloads []PayloadType) {
	// maybe reflect
	// http://blog.burntsushi.net/type-parametric-functions-golang/
	for _, payload := range payloads {
		pool.AddJob(fn, payload)
	}
}

func (pool *GoroutinePool) MapRunChan(fn func(payload PayloadType), fnfetch func() PayloadType) {
	for {
		payload := fnfetch()
		pool.AddJob(fn, payload)
	}
}

func (pool *GoroutinePool) Start() {
	for i := int32(0); i < pool.maxWorkers; i++ {
		worker := &Worker{
			ID:            int32(i),
			jobCacheQueue: pool.jobCacheQueue,
			wait:          pool.wait,
			die:           false,
			wg:            pool.wg,
		}
		worker.Run()
		pool.workers[i] = worker
	}
}

func (pool *GoroutinePool) feedbackWorkers(feedbackMaxWorkers int32) {
	if feedbackMaxWorkers > pool.maxWorkers {
		for i := pool.maxWorkers; i < feedbackMaxWorkers; i++ {
			worker := &Worker{
				ID:            int32(i),
				jobCacheQueue: pool.jobCacheQueue,
				wait:          pool.wait,
				die:           false,
				wg:            pool.wg,
			}
			worker.Run()
			pool.workers[i] = worker
		}
	} else {
		for i := feedbackMaxWorkers; i < pool.maxWorkers; i++ {
			pool.workers[i].die = true
		}
	}

	pool.maxWorkers = feedbackMaxWorkers
}

func (pool *GoroutinePool) Wait() {
	pool.wg.Wait()
}

func CustomLogger(fileName string) *log.Logger {
	// f, err := os.OpenFile(fileName, os.O_APPEND | os.O_CREATE | os.O_RDWR, 0666)
	f, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		fmt.Printf("error opening file: %v", err)
	}
	logger = log.New(f, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
	return logger
}
<|endoftext|>


<commit_before>package apachelog

import (
	"bytes"
	"net/http"
	"sync"
	"time"
)

var logBufferPool sync.Pool
var logCtxPool sync.Pool
var responseWriterPool sync.Pool

func init() {
	logBufferPool.New = allocLogBuffer
	logCtxPool.New = allocLogCtx
	responseWriterPool.New = allocResponseWriter
}

func allocLogBuffer() interface{} {
	return &bytes.Buffer{}
}

func getLogBuffer() *bytes.Buffer {
	return logBufferPool.Get().(*bytes.Buffer)
}

func releaseLogBuffer(v *bytes.Buffer) {
	v.Reset()
	logBufferPool.Put(v)
}

func allocLogCtx() interface{} {
	return &LogCtx{}
}

func getLogCtx() *LogCtx {
	return logCtxPool.Get().(*LogCtx)
}

func releaseLogCtx(v *LogCtx) {
	v.Request = nil
	v.RequestTime = time.Time{}
	v.ResponseStatus = http.StatusOK
	v.ResponseHeader = nil
	v.ElapsedTime = 0
	logCtxPool.Put(v)
}

func allocResponseWriter() interface{} {
	return &absorbingResponseWriter{}
}

func getResponseWriter(w http.ResponseWriter, ctx *LogCtx) *absorbingResponseWriter {
	w2 := responseWriterPool.Get().(*absorbingResponseWriter)
	w2.w = w
	w2.ctx = ctx
	return w2
}

func releaseResponseWriter(v *absorbingResponseWriter) {
	v.w = nil
	v.ctx = nil
	responseWriterPool.Put(v)
}
<commit_msg>reset ResponseContentLength<commit_after>package apachelog

import (
	"bytes"
	"net/http"
	"sync"
	"time"
)

var logBufferPool sync.Pool
var logCtxPool sync.Pool
var responseWriterPool sync.Pool

func init() {
	logBufferPool.New = allocLogBuffer
	logCtxPool.New = allocLogCtx
	responseWriterPool.New = allocResponseWriter
}

func allocLogBuffer() interface{} {
	return &bytes.Buffer{}
}

func getLogBuffer() *bytes.Buffer {
	return logBufferPool.Get().(*bytes.Buffer)
}

func releaseLogBuffer(v *bytes.Buffer) {
	v.Reset()
	logBufferPool.Put(v)
}

func allocLogCtx() interface{} {
	return &LogCtx{}
}

func getLogCtx() *LogCtx {
	return logCtxPool.Get().(*LogCtx)
}

func releaseLogCtx(v *LogCtx) {
	v.Request = nil
	v.RequestTime = time.Time{}
	v.ResponseContentLength = 0
	v.ResponseHeader = nil
	v.ResponseStatus = http.StatusOK
	v.ElapsedTime = 0
	logCtxPool.Put(v)
}

func allocResponseWriter() interface{} {
	return &absorbingResponseWriter{}
}

func getResponseWriter(w http.ResponseWriter, ctx *LogCtx) *absorbingResponseWriter {
	w2 := responseWriterPool.Get().(*absorbingResponseWriter)
	w2.w = w
	w2.ctx = ctx
	return w2
}

func releaseResponseWriter(v *absorbingResponseWriter) {
	v.w = nil
	v.ctx = nil
	responseWriterPool.Put(v)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"sort"
)

var cmdPs = &Command{
	Run:   runPs,
	Usage: "ps [-a app]",
	Short: "list processes",
	Long:  `List app processes.`,
}

var cmdRestart = &Command{
	Run:   runRestart,
	Usage: "restart [-a app]",
	Short: "restart processes",
	Long:  `Restart app processes.`,
}

func init() {
	cmds := []*Command{cmdPs, cmdRestart}
	for _, c := range cmds {
		c.Flag.StringVar(&flagApp, "a", "", "app")
	}
}

type Procs []*struct {
	Name    string `json:"process"`
	State   string
	Command string
}

func (p Procs) Len() int           { return len(p) }
func (p Procs) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p Procs) Less(i, j int) bool { return p[i].Name < p[j].Name }

func runPs(cmd *Command, args []string) {
	var procs Procs
	APIReq("GET", "/apps/"+mustApp()+"/ps").Do(&procs)
	sort.Sort(procs)
	fmt.Printf("Process           State       Command\n")
	fmt.Printf("----------------  ----------  ------------------------\n")
	for _, proc := range procs {
		fmt.Printf("%-16s  %-10s  %s\n", proc.Name, proc.State, proc.Command)
	}
}

func runRestart(cmd *Command, args []string) {
	APIReq("POST", "/apps/"+mustApp()+"/ps/restart").Do(nil)
}
<commit_msg>Restart takes an optional type/name argument.<commit_after>package main

import (
	"fmt"
	"log"
	"net/url"
	"sort"
	"strings"
)

var cmdPs = &Command{
	Run:   runPs,
	Usage: "ps [-a app]",
	Short: "list processes",
	Long:  `List app processes.`,
}

var cmdRestart = &Command{
	Run:   runRestart,
	Usage: "restart [-a app] [type or name]",
	Short: "restart processes",
	Long: `
Restart all app processes, all processes of a specific type, or a single process.

Examples:

  $ hk restart
  $ hk restart web
  $ hk restart web.1
`,
}

func init() {
	cmds := []*Command{cmdPs, cmdRestart}
	for _, c := range cmds {
		c.Flag.StringVar(&flagApp, "a", "", "app")
	}
}

type Procs []*struct {
	Name    string `json:"process"`
	State   string
	Command string
}

func (p Procs) Len() int           { return len(p) }
func (p Procs) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p Procs) Less(i, j int) bool { return p[i].Name < p[j].Name }

func runPs(cmd *Command, args []string) {
	var procs Procs
	APIReq("GET", "/apps/"+mustApp()+"/ps").Do(&procs)
	sort.Sort(procs)
	fmt.Printf("Process           State       Command\n")
	fmt.Printf("----------------  ----------  ------------------------\n")
	for _, proc := range procs {
		fmt.Printf("%-16s  %-10s  %s\n", proc.Name, proc.State, proc.Command)
	}
}

func runRestart(cmd *Command, args []string) {
	if len(args) > 1 {
		log.Fatal("Invalid usage. See 'hk help restart'")
	}

	v := make(url.Values)

	if len(args) == 1 {
		if strings.Index(args[0], ".") > 0 {
			v.Add("ps", args[0])
		} else {
			v.Add("type", args[0])
		}
	}

	req := APIReq("POST", "/apps/"+mustApp()+"/ps/restart")
	req.SetBodyForm(v)
	req.Do(nil)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"gopkg.in/yaml.v2"

	"github.com/daviddengcn/go-colortext"
	"github.com/phrase/phraseapp-go/phraseapp"
)

type PushCommand struct {
	Credentials
}

func (cmd *PushCommand) Run() error {
	if cmd.Debug {
		// suppresses content output
		cmd.Debug = false
		Debug = true
	}
	client, err := ClientFromCmdCredentials(cmd.Credentials)
	if err != nil {
		return err
	}

	sources, err := SourcesFromConfig(cmd)
	if err != nil {
		return err
	}

	for _, source := range sources {
		err := source.Push(client)
		if err != nil {
			return err
		}
	}
	return nil
}

type Sources []*Source

type Source struct {
	File          string      `yaml:"file,omitempty"`
	ProjectID     string      `yaml:"project_id,omitempty"`
	AccessToken   string      `yaml:"access_token,omitempty"`
	FileFormat    string      `yaml:"file_format,omitempty"`
	Params        *phraseapp.UploadParams `yaml:"params"`

	RemoteLocales []*phraseapp.Locale
	Extension     string
}

var separator = string(os.PathSeparator)

func (source *Source) CheckPreconditions() error {
	if err := ValidPath(source.File, source.FileFormat, ""); err != nil {
		return err
	}

	duplicatedPlaceholders := []string{}
	for _, name := range []string{"<locale_name>", "<locale_code>", "<tag>"} {
		if strings.Count(source.File, name) > 1 {
			duplicatedPlaceholders = append(duplicatedPlaceholders, name)
		}
	}

	starCount := strings.Count(source.File, "*")
	recCount := strings.Count(source.File, "**")

	if recCount == 0 && starCount > 1 || starCount-(recCount*2) > 1 {
		duplicatedPlaceholders = append(duplicatedPlaceholders, "*")
	}

	if recCount > 1 {
		duplicatedPlaceholders = append(duplicatedPlaceholders, "**")
	}

	if len(duplicatedPlaceholders) > 0 {
		dups := strings.Join(duplicatedPlaceholders, ", ")
		return fmt.Errorf(fmt.Sprintf("%s can only occur once in a file pattern!", dups))
	}

	return nil
}

func (source *Source) Push(client *phraseapp.Client) error {
	if err := source.CheckPreconditions(); err != nil {
		return err
	}

	source.Extension = filepath.Ext(source.File)

	remoteLocales, err := RemoteLocales(client, source.ProjectID)
	if err != nil {
		return err
	}
	source.RemoteLocales = remoteLocales

	localeFiles, err := source.LocaleFiles()
	if err != nil {
		return err
	}

	for _, localeFile := range localeFiles {
		fmt.Println("Uploading", localeFile.RelPath())

		if !localeFile.ExistsRemote {
			localeDetails, err := source.createLocale(client, localeFile)
			if err == nil {
				localeFile.ID = localeDetails.ID
				localeFile.RFC = localeDetails.Code
				localeFile.Name = localeDetails.Name
			} else {
				fmt.Printf("failed to create locale: %s\n", err)
				continue
			}
		}

		err = source.uploadFile(client, localeFile)
		if err != nil {
			return err
		} else {
			sharedMessage("push", localeFile)
		}

		if Debug {
			fmt.Fprintln(os.Stderr, strings.Repeat("-", 10))
		}

	}

	return nil
}

func (source *Source) createLocale(client *phraseapp.Client, localeFile *LocaleFile) (*phraseapp.LocaleDetails, error) {
	if localeFile.RFC == "" {
		return nil, fmt.Errorf("no locale code specified")
	}

	localeParams := new(phraseapp.LocaleParams)

	if localeFile.Name != "" {
		localeParams.Name = &localeFile.Name
	} else if localeFile.RFC != "" {
		localeParams.Name = &localeFile.RFC
	}

	localeName := source.replacePlaceholderInParams(localeFile)
	if localeName != "" && localeName != localeFile.RFC {
		localeParams.Name = &localeName
	}

	if localeFile.RFC != "" {
		localeParams.Code = &localeFile.RFC
	}

	localeDetails, err := client.LocaleCreate(source.ProjectID, localeParams)
	if err != nil {
		return nil, err
	}
	return localeDetails, nil
}

func (source *Source) replacePlaceholderInParams(localeFile *LocaleFile) string {
	if localeFile.RFC != "" && strings.Contains(source.GetLocaleID(), "<locale_code>") {
		return strings.Replace(source.GetLocaleID(), "<locale_code>", localeFile.RFC, 1)
	}
	return ""
}

func (source *Source) uploadFile(client *phraseapp.Client, localeFile *LocaleFile) error {
	if Debug {
		fmt.Fprintln(os.Stdout, "Source file pattern:", source.File)
		fmt.Fprintln(os.Stdout, "Actual file location:", localeFile.Path)
	}

	params := new(phraseapp.UploadParams)
	*params = *source.Params

	params.File = &localeFile.Path

	if params.LocaleID == nil {
		switch {
		case localeFile.ID != "":
			params.LocaleID = &localeFile.ID
		case localeFile.RFC != "":
			params.LocaleID = &localeFile.RFC
		}
	}

	aUpload, err := client.UploadCreate(source.ProjectID, params)
	if err != nil {
		return err
	}

	printSummary(&aUpload.Summary)

	return nil
}

func (source *Source) SystemFiles() ([]string, error) {
	if strings.Contains(source.File, "**") {
		return source.recurse()
	}

	return source.glob()
}

func (source *Source) glob() ([]string, error) {
	withoutPlaceholder := placeholderRegexp.ReplaceAllString(source.File, "*")
	tokens := splitPathToTokens(withoutPlaceholder)

	fileHead := tokens[len(tokens)-1]
	if strings.HasPrefix(fileHead, ".") {
		tokens[len(tokens)-1] = "*" + fileHead
	}
	pattern := strings.Join(tokens, separator)

	files, err := filepath.Glob(pattern)
	if err != nil {
		return nil, err
	}

	if Debug {
		fmt.Fprintln(os.Stderr, "Found", len(files), "files matching the source pattern", pattern)
	}

	return files, nil
}

func (source *Source) recurse() ([]string, error) {
	files := []string{}
	err := filepath.Walk(source.root(), func(path string, f os.FileInfo, err error) error {
		if err != nil {
			errmsg := fmt.Sprintf("%s for pattern: %s", err, source.File)
			ReportError("Push Error", errmsg)
			return fmt.Errorf(errmsg)
		}
		if !f.Mode().IsDir() && strings.HasSuffix(f.Name(), source.Extension) {
			files = append(files, path)
		}
		return nil
	})

	return files, err
}

func (source *Source) root() string {
	parts := splitPathToTokens(source.File)
	rootParts := TakeWhile(parts, func(x string) bool {
		return x != "**"
	})
	root := strings.Join(rootParts, separator)
	if root == "" {
		root = "."
	}
	return root
}

// Return all locale files from disk that match the source pattern.
func (source *Source) LocaleFiles() (LocaleFiles, error) {
	filePaths, err := source.SystemFiles()
	if err != nil {
		return nil, err
	}

	tokens := splitPathToTokens(source.File)

	var localeFiles LocaleFiles
	for _, path := range filePaths {

		pathTokens := splitPathToTokens(path)
		if len(pathTokens) < len(tokens) {
			continue
		}
		localeFile := Reduce(tokens, pathTokens)

		absolutePath, err := filepath.Abs(path)
		if err != nil {
			return nil, err
		}
		localeFile.Path = absolutePath

		locale := source.getRemoteLocaleForLocaleFile(localeFile)
		if locale != nil {
			localeFile.ExistsRemote = true
			localeFile.RFC = locale.Code
			localeFile.Name = locale.Name
			localeFile.ID = locale.ID
		}

		if Debug {
			fmt.Println(fmt.Sprintf(
				"RFC:'%s', Name:'%s', Tag;'%s', Pattern:'%s'",
				localeFile.RFC, localeFile.Name, localeFile.Tag,
			))
		}

		localeFiles = append(localeFiles, localeFile)
	}

	if len(localeFiles) <= 0 {
		abs, err := filepath.Abs(source.File)
		if err != nil {
			return nil, err
		}
		errmsg := fmt.Sprintf("Could not find any files on your system that matches: '%s'", abs)
		ReportError("Push Error", errmsg)
		return nil, fmt.Errorf(errmsg)
	}
	return localeFiles, nil
}

func (source *Source) getRemoteLocaleForLocaleFile(localeFile *LocaleFile) *phraseapp.Locale {
	for _, remote := range source.RemoteLocales {
		if remote.Name == source.GetLocaleID() || remote.ID == source.GetLocaleID() {
			return remote
		}

		localeName := source.replacePlaceholderInParams(localeFile)
		if localeName != "" && strings.Contains(remote.Name, localeName) {
			return remote
		}

		if remote.Name == localeFile.Name {
			return remote
		}

		if remote.Name == localeFile.RFC {
			return remote
		}
	}
	return nil
}

func splitPathToTokens(s string) []string {
	tokens := []string{}
	for _, token := range strings.Split(s, separator) {
		if token == "." || token == "" {
			continue
		}
		tokens = append(tokens, token)
	}
	return tokens
}

func Reduce(tokens, pathTokens []string) *LocaleFile {
	tagged := map[string]string{}

	for idx, token := range tokens {
		pathToken := pathTokens[idx]
		if token == "*" {
			continue
		}
		if token == "**" {
			break
		}
		tagged = tag(tagged, token, pathToken)
	}

	if Contains(tokens, "**") {
		offset := 1
		for idx := len(tokens) - 1; idx >= 0; idx-- {
			token := tokens[idx]
			pathToken := pathTokens[len(pathTokens)-offset]
			offset += 1

			if token == "*" {
				continue
			}
			if token == "**" {
				break
			}

			tagged = tag(tagged, token, pathToken)
		}
	}

	return &LocaleFile{
		Name: tagged["locale_name"],
		RFC:  tagged["locale_code"],
		Tag:  tagged["tag"],
	}
}

func tag(tagged map[string]string, token, pathToken string) map[string]string {
	groups := placeholderRegexp.FindAllString(token, -1)
	if len(groups) <= 0 {
		return tagged
	}

	match := strings.Replace(token, ".", "[.]", -1)
	if strings.HasPrefix(match, "*") {
		match = strings.Replace(match, "*", ".*", -1)
	}

	for _, group := range groups {
		replacer := fmt.Sprintf("(?P%s.+)", group)
		match = strings.Replace(match, group, replacer, 1)
	}

	if match == "" {
		return tagged
	}

	tmpRegexp, err := regexp.Compile(match)
	if err != nil {
		return tagged
	}

	namedMatches := tmpRegexp.SubexpNames()
	subMatches := tmpRegexp.FindStringSubmatch(pathToken)
	for i, subMatch := range subMatches {
		if subMatch != "" {
			tagged[namedMatches[i]] = strings.Trim(subMatch, separator)
		}
	}

	return tagged
}

// Print out
func printSummary(summary *phraseapp.SummaryType) {
	newItems := []int64{
		summary.LocalesCreated,
		summary.TranslationsUpdated,
		summary.TranslationKeysCreated,
		summary.TranslationsCreated,
	}
	var changed bool
	for _, item := range newItems {
		if item > 0 {
			changed = true
		}
	}
	if changed || Debug {
		printMessage("Locales created: ", fmt.Sprintf("%d", summary.LocalesCreated))
		printMessage(" - Keys created: ", fmt.Sprintf("%d", summary.TranslationKeysCreated))
		printMessage(" - Translations created: ", fmt.Sprintf("%d", summary.TranslationsCreated))
		printMessage(" - Translations updated: ", fmt.Sprintf("%d", summary.TranslationsUpdated))
		fmt.Print("\n")
	}
}

func printMessage(msg, stat string) {
	fmt.Print(msg)
	ct.Foreground(ct.Green, true)
	fmt.Print(stat)
	ct.ResetColor()
}

// Configuration
type PushConfig struct {
	Phraseapp struct {
		AccessToken string `yaml:"access_token"`
		ProjectID   string `yaml:"project_id"`
		FileFormat  string `yaml:"file_format,omitempty"`
		Push        struct {
			Sources Sources
		}
	}
}

func SourcesFromConfig(cmd *PushCommand) (Sources, error) {
	content, err := ConfigContent()
	if err != nil {
		return nil, err
	}

	var config *PushConfig

	err = yaml.Unmarshal([]byte(content), &config)
	if err != nil {
		return nil, err
	}

	token := config.Phraseapp.AccessToken
	if cmd.Token != "" {
		token = cmd.Token
	}
	projectId := config.Phraseapp.ProjectID
	fileFormat := config.Phraseapp.FileFormat

	if &config.Phraseapp.Push == nil || config.Phraseapp.Push.Sources == nil {
		errmsg := "no sources for upload specified"
		ReportError("Push Error", errmsg)
		return nil, fmt.Errorf(errmsg)
	}

	sources := config.Phraseapp.Push.Sources

	validSources := []*Source{}
	for _, source := range sources {
		if source == nil {
			continue
		}
		if source.ProjectID == "" {
			source.ProjectID = projectId
		}
		if source.AccessToken == "" {
			source.AccessToken = token
		}
		if source.Params == nil {
			source.Params = new(phraseapp.UploadParams)
		}

		if source.Params.FileFormat == nil {
			switch {
			case source.FileFormat != "":
				source.Params.FileFormat = &source.FileFormat
			case fileFormat != "":
				source.Params.FileFormat = &fileFormat
			}
		}
		validSources = append(validSources, source)
	}

	if len(validSources) <= 0 {
		errmsg := "no sources could be identified! Refine the sources list in your config"
		ReportError("Push Error", errmsg)
		return nil, fmt.Errorf(errmsg)
	}

	return validSources, nil
}

func (source *Source) GetLocaleID() string {
	if source.Params != nil && source.Params.LocaleID != nil {
		return *source.Params.LocaleID
	}
	return ""
}
<commit_msg>moved error reporting at top level<commit_after>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"gopkg.in/yaml.v2"

	"github.com/daviddengcn/go-colortext"
	"github.com/phrase/phraseapp-go/phraseapp"
)

type PushCommand struct {
	Credentials
}

func (cmd *PushCommand) Run() error {
	if cmd.Debug {
		// suppresses content output
		cmd.Debug = false
		Debug = true
	}

	err := func() error {
		client, err := ClientFromCmdCredentials(cmd.Credentials)
		if err != nil {
			return err
		}

		sources, err := SourcesFromConfig(cmd)
		if err != nil {
			return err
		}

		for _, source := range sources {
			err := source.Push(client)
			if err != nil {
				return err
			}
		}
		return nil
	}()

	if err != nil {
		ReportError("Push Error", err.Error())
	}

	return err
}

type Sources []*Source

type Source struct {
	File          string      `yaml:"file,omitempty"`
	ProjectID     string      `yaml:"project_id,omitempty"`
	AccessToken   string      `yaml:"access_token,omitempty"`
	FileFormat    string      `yaml:"file_format,omitempty"`
	Params        *phraseapp.UploadParams `yaml:"params"`

	RemoteLocales []*phraseapp.Locale
	Extension     string
}

var separator = string(os.PathSeparator)

func (source *Source) CheckPreconditions() error {
	if err := ValidPath(source.File, source.FileFormat, ""); err != nil {
		return err
	}

	duplicatedPlaceholders := []string{}
	for _, name := range []string{"<locale_name>", "<locale_code>", "<tag>"} {
		if strings.Count(source.File, name) > 1 {
			duplicatedPlaceholders = append(duplicatedPlaceholders, name)
		}
	}

	starCount := strings.Count(source.File, "*")
	recCount := strings.Count(source.File, "**")

	if recCount == 0 && starCount > 1 || starCount-(recCount*2) > 1 {
		duplicatedPlaceholders = append(duplicatedPlaceholders, "*")
	}

	if recCount > 1 {
		duplicatedPlaceholders = append(duplicatedPlaceholders, "**")
	}

	if len(duplicatedPlaceholders) > 0 {
		dups := strings.Join(duplicatedPlaceholders, ", ")
		return fmt.Errorf(fmt.Sprintf("%s can only occur once in a file pattern!", dups))
	}

	return nil
}

func (source *Source) Push(client *phraseapp.Client) error {
	if err := source.CheckPreconditions(); err != nil {
		return err
	}

	source.Extension = filepath.Ext(source.File)

	remoteLocales, err := RemoteLocales(client, source.ProjectID)
	if err != nil {
		return err
	}
	source.RemoteLocales = remoteLocales

	localeFiles, err := source.LocaleFiles()
	if err != nil {
		return err
	}

	for _, localeFile := range localeFiles {
		fmt.Println("Uploading", localeFile.RelPath())

		if !localeFile.ExistsRemote {
			localeDetails, err := source.createLocale(client, localeFile)
			if err == nil {
				localeFile.ID = localeDetails.ID
				localeFile.RFC = localeDetails.Code
				localeFile.Name = localeDetails.Name
			} else {
				fmt.Printf("failed to create locale: %s\n", err)
				continue
			}
		}

		err = source.uploadFile(client, localeFile)
		if err != nil {
			return err
		} else {
			sharedMessage("push", localeFile)
		}

		if Debug {
			fmt.Fprintln(os.Stderr, strings.Repeat("-", 10))
		}

	}

	return nil
}

func (source *Source) createLocale(client *phraseapp.Client, localeFile *LocaleFile) (*phraseapp.LocaleDetails, error) {
	if localeFile.RFC == "" {
		return nil, fmt.Errorf("no locale code specified")
	}

	localeParams := new(phraseapp.LocaleParams)

	if localeFile.Name != "" {
		localeParams.Name = &localeFile.Name
	} else if localeFile.RFC != "" {
		localeParams.Name = &localeFile.RFC
	}

	localeName := source.replacePlaceholderInParams(localeFile)
	if localeName != "" && localeName != localeFile.RFC {
		localeParams.Name = &localeName
	}

	if localeFile.RFC != "" {
		localeParams.Code = &localeFile.RFC
	}

	localeDetails, err := client.LocaleCreate(source.ProjectID, localeParams)
	if err != nil {
		return nil, err
	}
	return localeDetails, nil
}

func (source *Source) replacePlaceholderInParams(localeFile *LocaleFile) string {
	if localeFile.RFC != "" && strings.Contains(source.GetLocaleID(), "<locale_code>") {
		return strings.Replace(source.GetLocaleID(), "<locale_code>", localeFile.RFC, 1)
	}
	return ""
}

func (source *Source) uploadFile(client *phraseapp.Client, localeFile *LocaleFile) error {
	if Debug {
		fmt.Fprintln(os.Stdout, "Source file pattern:", source.File)
		fmt.Fprintln(os.Stdout, "Actual file location:", localeFile.Path)
	}

	params := new(phraseapp.UploadParams)
	*params = *source.Params

	params.File = &localeFile.Path

	if params.LocaleID == nil {
		switch {
		case localeFile.ID != "":
			params.LocaleID = &localeFile.ID
		case localeFile.RFC != "":
			params.LocaleID = &localeFile.RFC
		}
	}

	aUpload, err := client.UploadCreate(source.ProjectID, params)
	if err != nil {
		return err
	}

	printSummary(&aUpload.Summary)

	return nil
}

func (source *Source) SystemFiles() ([]string, error) {
	if strings.Contains(source.File, "**") {
		return source.recurse()
	}

	return source.glob()
}

func (source *Source) glob() ([]string, error) {
	withoutPlaceholder := placeholderRegexp.ReplaceAllString(source.File, "*")
	tokens := splitPathToTokens(withoutPlaceholder)

	fileHead := tokens[len(tokens)-1]
	if strings.HasPrefix(fileHead, ".") {
		tokens[len(tokens)-1] = "*" + fileHead
	}
	pattern := strings.Join(tokens, separator)

	files, err := filepath.Glob(pattern)
	if err != nil {
		return nil, err
	}

	if Debug {
		fmt.Fprintln(os.Stderr, "Found", len(files), "files matching the source pattern", pattern)
	}

	return files, nil
}

func (source *Source) recurse() ([]string, error) {
	files := []string{}
	err := filepath.Walk(source.root(), func(path string, f os.FileInfo, err error) error {
		if err != nil {
			errmsg := fmt.Sprintf("%s for pattern: %s", err, source.File)
			ReportError("Push Error", errmsg)
			return fmt.Errorf(errmsg)
		}
		if !f.Mode().IsDir() && strings.HasSuffix(f.Name(), source.Extension) {
			files = append(files, path)
		}
		return nil
	})

	return files, err
}

func (source *Source) root() string {
	parts := splitPathToTokens(source.File)
	rootParts := TakeWhile(parts, func(x string) bool {
		return x != "**"
	})
	root := strings.Join(rootParts, separator)
	if root == "" {
		root = "."
	}
	return root
}

// Return all locale files from disk that match the source pattern.
func (source *Source) LocaleFiles() (LocaleFiles, error) {
	filePaths, err := source.SystemFiles()
	if err != nil {
		return nil, err
	}

	tokens := splitPathToTokens(source.File)

	var localeFiles LocaleFiles
	for _, path := range filePaths {

		pathTokens := splitPathToTokens(path)
		if len(pathTokens) < len(tokens) {
			continue
		}
		localeFile := Reduce(tokens, pathTokens)

		absolutePath, err := filepath.Abs(path)
		if err != nil {
			return nil, err
		}
		localeFile.Path = absolutePath

		locale := source.getRemoteLocaleForLocaleFile(localeFile)
		if locale != nil {
			localeFile.ExistsRemote = true
			localeFile.RFC = locale.Code
			localeFile.Name = locale.Name
			localeFile.ID = locale.ID
		}

		if Debug {
			fmt.Println(fmt.Sprintf(
				"RFC:'%s', Name:'%s', Tag;'%s', Pattern:'%s'",
				localeFile.RFC, localeFile.Name, localeFile.Tag,
			))
		}

		localeFiles = append(localeFiles, localeFile)
	}

	if len(localeFiles) <= 0 {
		abs, err := filepath.Abs(source.File)
		if err != nil {
			abs = source.File
		}
		return nil, fmt.Errorf("Could not find any files on your system that matches: '%s'", abs)
	}
	return localeFiles, nil
}

func (source *Source) getRemoteLocaleForLocaleFile(localeFile *LocaleFile) *phraseapp.Locale {
	for _, remote := range source.RemoteLocales {
		if remote.Name == source.GetLocaleID() || remote.ID == source.GetLocaleID() {
			return remote
		}

		localeName := source.replacePlaceholderInParams(localeFile)
		if localeName != "" && strings.Contains(remote.Name, localeName) {
			return remote
		}

		if remote.Name == localeFile.Name {
			return remote
		}

		if remote.Name == localeFile.RFC {
			return remote
		}
	}
	return nil
}

func splitPathToTokens(s string) []string {
	tokens := []string{}
	for _, token := range strings.Split(s, separator) {
		if token == "." || token == "" {
			continue
		}
		tokens = append(tokens, token)
	}
	return tokens
}

func Reduce(tokens, pathTokens []string) *LocaleFile {
	tagged := map[string]string{}

	for idx, token := range tokens {
		pathToken := pathTokens[idx]
		if token == "*" {
			continue
		}
		if token == "**" {
			break
		}
		tagged = tag(tagged, token, pathToken)
	}

	if Contains(tokens, "**") {
		offset := 1
		for idx := len(tokens) - 1; idx >= 0; idx-- {
			token := tokens[idx]
			pathToken := pathTokens[len(pathTokens)-offset]
			offset += 1

			if token == "*" {
				continue
			}
			if token == "**" {
				break
			}

			tagged = tag(tagged, token, pathToken)
		}
	}

	return &LocaleFile{
		Name: tagged["locale_name"],
		RFC:  tagged["locale_code"],
		Tag:  tagged["tag"],
	}
}

func tag(tagged map[string]string, token, pathToken string) map[string]string {
	groups := placeholderRegexp.FindAllString(token, -1)
	if len(groups) <= 0 {
		return tagged
	}

	match := strings.Replace(token, ".", "[.]", -1)
	if strings.HasPrefix(match, "*") {
		match = strings.Replace(match, "*", ".*", -1)
	}

	for _, group := range groups {
		replacer := fmt.Sprintf("(?P%s.+)", group)
		match = strings.Replace(match, group, replacer, 1)
	}

	if match == "" {
		return tagged
	}

	tmpRegexp, err := regexp.Compile(match)
	if err != nil {
		return tagged
	}

	namedMatches := tmpRegexp.SubexpNames()
	subMatches := tmpRegexp.FindStringSubmatch(pathToken)
	for i, subMatch := range subMatches {
		if subMatch != "" {
			tagged[namedMatches[i]] = strings.Trim(subMatch, separator)
		}
	}

	return tagged
}

// Print out
func printSummary(summary *phraseapp.SummaryType) {
	newItems := []int64{
		summary.LocalesCreated,
		summary.TranslationsUpdated,
		summary.TranslationKeysCreated,
		summary.TranslationsCreated,
	}
	var changed bool
	for _, item := range newItems {
		if item > 0 {
			changed = true
		}
	}
	if changed || Debug {
		printMessage("Locales created: ", fmt.Sprintf("%d", summary.LocalesCreated))
		printMessage(" - Keys created: ", fmt.Sprintf("%d", summary.TranslationKeysCreated))
		printMessage(" - Translations created: ", fmt.Sprintf("%d", summary.TranslationsCreated))
		printMessage(" - Translations updated: ", fmt.Sprintf("%d", summary.TranslationsUpdated))
		fmt.Print("\n")
	}
}

func printMessage(msg, stat string) {
	fmt.Print(msg)
	ct.Foreground(ct.Green, true)
	fmt.Print(stat)
	ct.ResetColor()
}

// Configuration
type PushConfig struct {
	Phraseapp struct {
		AccessToken string `yaml:"access_token"`
		ProjectID   string `yaml:"project_id"`
		FileFormat  string `yaml:"file_format,omitempty"`
		Push        struct {
			Sources Sources
		}
	}
}

func SourcesFromConfig(cmd *PushCommand) (Sources, error) {
	content, err := ConfigContent()
	if err != nil {
		return nil, err
	}

	var config *PushConfig

	err = yaml.Unmarshal([]byte(content), &config)
	if err != nil {
		return nil, err
	}

	token := config.Phraseapp.AccessToken
	if cmd.Token != "" {
		token = cmd.Token
	}
	projectId := config.Phraseapp.ProjectID
	fileFormat := config.Phraseapp.FileFormat

	if &config.Phraseapp.Push == nil || config.Phraseapp.Push.Sources == nil {
		return nil, fmt.Errorf("no sources for upload specified")
	}

	sources := config.Phraseapp.Push.Sources

	validSources := []*Source{}
	for _, source := range sources {
		if source == nil {
			continue
		}
		if source.ProjectID == "" {
			source.ProjectID = projectId
		}
		if source.AccessToken == "" {
			source.AccessToken = token
		}
		if source.Params == nil {
			source.Params = new(phraseapp.UploadParams)
		}

		if source.Params.FileFormat == nil {
			switch {
			case source.FileFormat != "":
				source.Params.FileFormat = &source.FileFormat
			case fileFormat != "":
				source.Params.FileFormat = &fileFormat
			}
		}
		validSources = append(validSources, source)
	}

	if len(validSources) <= 0 {
		return nil, fmt.Errorf("no sources could be identified! Refine the sources list in your config")
	}

	return validSources, nil
}

func (source *Source) GetLocaleID() string {
	if source.Params != nil && source.Params.LocaleID != nil {
		return *source.Params.LocaleID
	}
	return ""
}
<|endoftext|>


<commit_before>package wave

import (
	"strconv"
	"testing"
)

const (
	startPort = 3000 // Port to start on
	numPorts  = 10   // Number of ports to listen on
)

func FakeEndpoints() []string {
	var endpoints []string
	for i := 0; i < numPorts; i++ {
		port := ":" + strconv.Itoa(startPort+i)
		endpoints = append(endpoints, port)
	}
	return endpoints
}

func TestLaunch(t *testing.T) {
	hosts := FakeEndpoints()
	count := 0

	w := Once(10, hosts, func(host string) {
		count++
	})

	w.AfterEach(func() {
		if count != len(hosts) {
			t.Error("Expected 10, got", count)
		}
	})

	// Start implied by Finish (checks if start happened, if not, it starts).
	// Note: all callbacks must be called and done before Finish unblocks.
	w.Finish()
}

func TestContinuous(t *testing.T) {
	tick := make(chan struct{})
	waves := 10

	w := Continuous(10, FakeEndpoints(), func(host string) {
		tick <- struct{}{}
	})

	count := 0
	countWaves := 0

	w.AfterEach(func() {
		countWaves++
	})
	w.OnStop(func() {
		t.Log("Waves counted:", count)
	})

	go func() {
		for {
			<-tick
			if count++; count == (numPorts * waves) {
				t.Log("Ticks received:", count)
				w.Interrupt()
				break
			}
		}
	}()

	w.Finish() // Blocks until the wave stops for some reason; all callbacks will finish first.
}
<commit_msg>Fix Continuous/Interrupt test<commit_after>package wave

import (
	"strconv"
	"testing"
)

const (
	startPort = 3000 // Port to start on
	numPorts  = 10   // Number of ports to listen on
)

func FakeEndpoints() []string {
	var endpoints []string
	for i := 0; i < numPorts; i++ {
		port := ":" + strconv.Itoa(startPort+i)
		endpoints = append(endpoints, port)
	}
	return endpoints
}

func TestLaunch(t *testing.T) {
	hosts := FakeEndpoints()
	count := 0

	w := Once(10, hosts, func(host string) {
		count++
	})

	w.AfterEach(func() {
		if count != len(hosts) {
			t.Error("Expected 10, got", count)
		}
	})

	// Start implied by Finish (checks if start happened, if not, it starts).
	// Note: all callbacks must be called and done before Finish unblocks.
	w.Finish()
}

func TestContinuous(t *testing.T) {
	tick := make(chan struct{})
	waves := 10

	w := Continuous(10, FakeEndpoints(), func(host string) {
		tick <- struct{}{}
	})

	count := 0
	countWaves := 0

	w.AfterEach(func() {
		countWaves++
	})
	w.OnStop(func() {
		t.Log("Waves counted:", count)
	})

	go func() {
		for {
			<-tick
			if count++; count == (numPorts * waves) {
				t.Log("Ticks received:", count)
				w.Interrupt()
				break
			}
		}
	}()

	w.Start()
	w.Wait() // Interrupt will kick in later
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"math"
	"strings"

	"github.com/dustin/go-humanize"
	"github.com/itchio/butler/comm"
	"github.com/itchio/go-itchio"
	"github.com/itchio/wharf/counter"
	"github.com/itchio/wharf/pwr"
	"github.com/itchio/wharf/sync"
	"github.com/itchio/wharf/tlc"
	"github.com/itchio/wharf/uploader"
)

func push(buildPath string, spec string) {
	must(doPush(buildPath, spec))
}

func doPush(buildPath string, spec string) error {
	// start walking source container while waiting on auth flow
	sourceContainerChan := make(chan *tlc.Container)
	walkErrs := make(chan error)
	go doWalk(buildPath, sourceContainerChan, walkErrs)

	target, channel, err := parseSpec(spec)
	if err != nil {
		return err
	}

	client, err := authenticateViaOauth()
	if err != nil {
		return err
	}

	newBuildRes, err := client.CreateBuild(target, channel)
	if err != nil {
		return err
	}

	buildID := newBuildRes.Build.ID
	parentID := newBuildRes.Build.ParentBuild.ID

	var targetSignature []sync.BlockHash
	var targetContainer *tlc.Container

	if parentID == 0 {
		comm.Opf("For channel `%s`: pushing first build", channel)
		targetSignature = make([]sync.BlockHash, 0)
		targetContainer = &tlc.Container{}
	} else {
		comm.Opf("For channel `%s`: last build is %d, downloading its signature", channel, parentID)
		var buildFiles itchio.ListBuildFilesResponse
		buildFiles, err = client.ListBuildFiles(parentID)
		if err != nil {
			return err
		}

		var signatureFileID int64
		for _, f := range buildFiles.Files {
			if f.Type == itchio.BuildFileType_SIGNATURE {
				signatureFileID = f.ID
				break
			}
		}

		if signatureFileID == 0 {
			comm.Dief("Could not find signature for parent build %d, aborting", parentID)
		}

		var signatureReader io.Reader
		signatureReader, err = client.DownloadBuildFile(parentID, signatureFileID)
		if err != nil {
			return err
		}

		targetContainer, targetSignature, err = pwr.ReadSignature(signatureReader)
		if err != nil {
			return err
		}
	}

	newPatchRes, newSignatureRes, err := createBothFiles(client, buildID)

	uploadDone := make(chan bool)
	uploadErrs := make(chan error)

	patchWriter, err := uploader.NewMultipartUpload(newPatchRes.File.UploadURL,
		newPatchRes.File.UploadParams, fmt.Sprintf("%d-%d.pwr", parentID, buildID),
		uploadDone, uploadErrs, comm.NewStateConsumer())
	if err != nil {
		return err
	}

	signatureWriter, err := uploader.NewMultipartUpload(newSignatureRes.File.UploadURL,
		newSignatureRes.File.UploadParams, fmt.Sprintf("%d-%d.pwr", parentID, buildID),
		uploadDone, uploadErrs, comm.NewStateConsumer())
	if err != nil {
		return err
	}

	comm.Debugf("Launching patch & signature channels")

	patchCounter := counter.NewWriter(patchWriter)
	signatureCounter := counter.NewWriter(signatureWriter)

	// we started walking the source container in the beginning,
	// we actually need it now.
	// note that we could actually start diffing before all the file
	// creation & upload setup is done

	var sourceContainer *tlc.Container

	comm.Debugf("Waiting for source container")
	select {
	case err := <-walkErrs:
		return err
	case sourceContainer = <-sourceContainerChan:
		comm.Debugf("Got sourceContainer!")
		break
	}

	comm.Logf("")
	comm.Opf("Pushing %s (%s)", humanize.Bytes(uint64(sourceContainer.Size)), sourceContainer.Stats())

	comm.Debugf("Building diff context")
	var readBytes int64

	updateProgress := func() {
		uploadedBytes := int64(float64(patchWriter.UploadedBytes) * 0.97)

		// input bytes that aren't in output, for esxample:
		//  - bytes that have been compressed away
		//  - bytes that were in old build and were simply reused
		goneBytes := readBytes - patchWriter.TotalBytes

		conservativeTotalBytes := sourceContainer.Size - goneBytes

		leftBytes := conservativeTotalBytes - uploadedBytes
		if leftBytes > 10*1024 {
			comm.ProgressLabel(fmt.Sprintf("%s left", humanize.Bytes(uint64(leftBytes))))
		} else {
			comm.ProgressLabel(fmt.Sprintf("almost there"))
		}

		conservativeProgress := float64(uploadedBytes) / float64(conservativeTotalBytes)
		conservativeProgress = min(1.0, conservativeProgress)
		comm.Progress(conservativeProgress)
	}
	patchWriter.OnProgress = updateProgress

	stateConsumer := &pwr.StateConsumer{
		OnProgress: func(progress float64) {
			readBytes = int64(float64(sourceContainer.Size) * progress)
			updateProgress()
		},
	}

	dctx := &pwr.DiffContext{
		Compression: &pwr.CompressionSettings{
			Algorithm: pwr.CompressionAlgorithm_BROTLI,
			Quality:   1,
		},

		SourceContainer: sourceContainer,
		SourcePath:      buildPath,

		TargetContainer: targetContainer,
		TargetSignature: targetSignature,

		Consumer: stateConsumer,
	}

	comm.StartProgress()
	err = dctx.WritePatch(patchCounter, signatureCounter)
	if err != nil {
		return err
	}

	// close in a goroutine to avoid deadlocking
	doClose := func(c io.Closer, done chan bool, errs chan error) {
		err := c.Close()
		if err != nil {
			errs <- err
			return
		}

		done <- true
	}

	go doClose(patchWriter, uploadDone, uploadErrs)
	go doClose(signatureWriter, uploadDone, uploadErrs)

	for c := 0; c < 4; c++ {
		select {
		case err := <-uploadErrs:
			return err
		case <-uploadDone:
		}
	}
	comm.ProgressLabel("finalizing build")

	finalDone := make(chan bool)
	finalErrs := make(chan error)

	doFinalize := func(fileID int64, fileSize int64, done chan bool, errs chan error) {
		_, err = client.FinalizeBuildFile(buildID, fileID, fileSize)
		if err != nil {
			errs <- err
			return
		}

		done <- true
	}

	go doFinalize(newPatchRes.File.ID, patchCounter.Count(), finalDone, finalErrs)
	go doFinalize(newSignatureRes.File.ID, signatureCounter.Count(), finalDone, finalErrs)

	for i := 0; i < 2; i++ {
		select {
		case err := <-finalErrs:
			return err
		case <-finalDone:
		}
	}

	comm.EndProgress()

	{
		prettyPatchSize := humanize.Bytes(uint64(patchCounter.Count()))
		percReused := 100.0 * float64(dctx.ReusedBytes) / float64(dctx.FreshBytes+dctx.ReusedBytes)
		relToNew := 100.0 * float64(patchCounter.Count()) / float64(sourceContainer.Size)
		prettyFreshSize := humanize.Bytes(uint64(dctx.FreshBytes))
		savings := 100.0 - relToNew

		if dctx.ReusedBytes > 0 {
			comm.Statf("Re-used %.2f%% of old, added %s fresh data", percReused, prettyFreshSize)
		} else {
			comm.Statf("Added %s fresh data", percReused, prettyFreshSize)
		}

		if savings > 0 && !math.IsNaN(savings) {
			comm.Statf("%s patch (%.2f%% savings)", prettyPatchSize, 100.0-relToNew)
		} else {
			comm.Statf("%s patch (no savings)", prettyPatchSize)
		}
	}
	return nil
}

type fileSlot struct {
	Type     itchio.BuildFileType
	Response itchio.NewBuildFileResponse
}

func createBothFiles(client *itchio.Client, buildID int64) (patch itchio.NewBuildFileResponse, signature itchio.NewBuildFileResponse, err error) {
	createFile := func(buildType itchio.BuildFileType, done chan fileSlot, errs chan error) {
		var res itchio.NewBuildFileResponse
		res, err = client.CreateBuildFile(buildID, buildType)
		if err != nil {
			errs <- err
		}
		comm.Debugf("Created %s build file: %+v", buildType, res.File)
		done <- fileSlot{buildType, res}
	}

	done := make(chan fileSlot)
	errs := make(chan error)

	go createFile(itchio.BuildFileType_PATCH, done, errs)
	go createFile(itchio.BuildFileType_SIGNATURE, done, errs)

	for i := 0; i < 2; i++ {
		select {
		case err = <-errs:
			return
		case slot := <-done:
			switch slot.Type {
			case itchio.BuildFileType_PATCH:
				patch = slot.Response
			case itchio.BuildFileType_SIGNATURE:
				signature = slot.Response
			}
		}
	}

	return
}

func doWalk(path string, result chan *tlc.Container, errs chan error) {
	container, err := tlc.Walk(path, filterPaths)
	if err != nil {
		errs <- err
	}

	result <- container
}

func parseSpec(spec string) (string, string, error) {
	tokens := strings.Split(spec, ":")

	if len(tokens) == 1 {
		return "", "", fmt.Errorf("invalid spec: %s, missing channel (examples: %s:windows-32-beta, %s:linux-64)", spec, spec, spec)
	} else if len(tokens) != 2 {
		return "", "", fmt.Errorf("invalid spec: %s, expected something of the form user/page:channel", spec)
	}

	return tokens[0], tokens[1], nil
}

func min(a, b float64) float64 {
	if a < b {
		return a
	}
	return b
}
<commit_msg>:panda_face:<commit_after>package main

import (
	"fmt"
	"io"
	"math"
	"strings"

	"github.com/dustin/go-humanize"
	"github.com/itchio/butler/comm"
	"github.com/itchio/go-itchio"
	"github.com/itchio/wharf/counter"
	"github.com/itchio/wharf/pwr"
	"github.com/itchio/wharf/sync"
	"github.com/itchio/wharf/tlc"
	"github.com/itchio/wharf/uploader"
)

func push(buildPath string, spec string) {
	must(doPush(buildPath, spec))
}

func doPush(buildPath string, spec string) error {
	// start walking source container while waiting on auth flow
	sourceContainerChan := make(chan *tlc.Container)
	walkErrs := make(chan error)
	go doWalk(buildPath, sourceContainerChan, walkErrs)

	target, channel, err := parseSpec(spec)
	if err != nil {
		return err
	}

	client, err := authenticateViaOauth()
	if err != nil {
		return err
	}

	newBuildRes, err := client.CreateBuild(target, channel)
	if err != nil {
		return err
	}

	buildID := newBuildRes.Build.ID
	parentID := newBuildRes.Build.ParentBuild.ID

	var targetSignature []sync.BlockHash
	var targetContainer *tlc.Container

	if parentID == 0 {
		comm.Opf("For channel `%s`: pushing first build", channel)
		targetSignature = make([]sync.BlockHash, 0)
		targetContainer = &tlc.Container{}
	} else {
		comm.Opf("For channel `%s`: last build is %d, downloading its signature", channel, parentID)
		var buildFiles itchio.ListBuildFilesResponse
		buildFiles, err = client.ListBuildFiles(parentID)
		if err != nil {
			return err
		}

		var signatureFileID int64
		for _, f := range buildFiles.Files {
			if f.Type == itchio.BuildFileType_SIGNATURE {
				signatureFileID = f.ID
				break
			}
		}

		if signatureFileID == 0 {
			comm.Dief("Could not find signature for parent build %d, aborting", parentID)
		}

		var signatureReader io.Reader
		signatureReader, err = client.DownloadBuildFile(parentID, signatureFileID)
		if err != nil {
			return err
		}

		targetContainer, targetSignature, err = pwr.ReadSignature(signatureReader)
		if err != nil {
			return err
		}
	}

	newPatchRes, newSignatureRes, err := createBothFiles(client, buildID)

	uploadDone := make(chan bool)
	uploadErrs := make(chan error)

	patchWriter, err := uploader.NewMultipartUpload(newPatchRes.File.UploadURL,
		newPatchRes.File.UploadParams, fmt.Sprintf("%d-%d.pwr", parentID, buildID),
		uploadDone, uploadErrs, comm.NewStateConsumer())
	if err != nil {
		return err
	}

	signatureWriter, err := uploader.NewMultipartUpload(newSignatureRes.File.UploadURL,
		newSignatureRes.File.UploadParams, fmt.Sprintf("%d-%d.pwr", parentID, buildID),
		uploadDone, uploadErrs, comm.NewStateConsumer())
	if err != nil {
		return err
	}

	comm.Debugf("Launching patch & signature channels")

	patchCounter := counter.NewWriter(patchWriter)
	signatureCounter := counter.NewWriter(signatureWriter)

	// we started walking the source container in the beginning,
	// we actually need it now.
	// note that we could actually start diffing before all the file
	// creation & upload setup is done

	var sourceContainer *tlc.Container

	comm.Debugf("Waiting for source container")
	select {
	case err := <-walkErrs:
		return err
	case sourceContainer = <-sourceContainerChan:
		comm.Debugf("Got sourceContainer!")
		break
	}

	comm.Logf("")
	comm.Opf("Pushing %s (%s)", humanize.Bytes(uint64(sourceContainer.Size)), sourceContainer.Stats())

	comm.Debugf("Building diff context")
	var readBytes int64

	updateProgress := func() {
		uploadedBytes := int64(float64(patchWriter.UploadedBytes) * 0.97)

		// input bytes that aren't in output, for esxample:
		//  - bytes that have been compressed away
		//  - bytes that were in old build and were simply reused
		goneBytes := readBytes - patchWriter.TotalBytes

		conservativeTotalBytes := sourceContainer.Size - goneBytes

		leftBytes := conservativeTotalBytes - uploadedBytes
		if leftBytes > 10*1024 {
			comm.ProgressLabel(fmt.Sprintf("%s left", humanize.Bytes(uint64(leftBytes))))
		} else {
			comm.ProgressLabel(fmt.Sprintf("almost there"))
		}

		conservativeProgress := float64(uploadedBytes) / float64(conservativeTotalBytes)
		conservativeProgress = min(1.0, conservativeProgress)
		comm.Progress(conservativeProgress)
	}
	patchWriter.OnProgress = updateProgress

	stateConsumer := &pwr.StateConsumer{
		OnProgress: func(progress float64) {
			readBytes = int64(float64(sourceContainer.Size) * progress)
			updateProgress()
		},
	}

	dctx := &pwr.DiffContext{
		Compression: &pwr.CompressionSettings{
			Algorithm: pwr.CompressionAlgorithm_BROTLI,
			Quality:   1,
		},

		SourceContainer: sourceContainer,
		SourcePath:      buildPath,

		TargetContainer: targetContainer,
		TargetSignature: targetSignature,

		Consumer: stateConsumer,
	}

	comm.StartProgress()
	err = dctx.WritePatch(patchCounter, signatureCounter)
	if err != nil {
		return err
	}

	// close in a goroutine to avoid deadlocking
	doClose := func(c io.Closer, done chan bool, errs chan error) {
		err := c.Close()
		if err != nil {
			errs <- err
			return
		}

		done <- true
	}

	go doClose(patchWriter, uploadDone, uploadErrs)
	go doClose(signatureWriter, uploadDone, uploadErrs)

	for c := 0; c < 4; c++ {
		select {
		case err := <-uploadErrs:
			return err
		case <-uploadDone:
		}
	}
	comm.ProgressLabel("finalizing build")

	finalDone := make(chan bool)
	finalErrs := make(chan error)

	doFinalize := func(fileID int64, fileSize int64, done chan bool, errs chan error) {
		_, err = client.FinalizeBuildFile(buildID, fileID, fileSize)
		if err != nil {
			errs <- err
			return
		}

		done <- true
	}

	go doFinalize(newPatchRes.File.ID, patchCounter.Count(), finalDone, finalErrs)
	go doFinalize(newSignatureRes.File.ID, signatureCounter.Count(), finalDone, finalErrs)

	for i := 0; i < 2; i++ {
		select {
		case err := <-finalErrs:
			return err
		case <-finalDone:
		}
	}

	comm.EndProgress()

	{
		prettyPatchSize := humanize.Bytes(uint64(patchCounter.Count()))
		percReused := 100.0 * float64(dctx.ReusedBytes) / float64(dctx.FreshBytes+dctx.ReusedBytes)
		relToNew := 100.0 * float64(patchCounter.Count()) / float64(sourceContainer.Size)
		prettyFreshSize := humanize.Bytes(uint64(dctx.FreshBytes))
		savings := 100.0 - relToNew

		if dctx.ReusedBytes > 0 {
			comm.Statf("Re-used %.2f%% of old, added %s fresh data", percReused, prettyFreshSize)
		} else {
			comm.Statf("Added %s fresh data", prettyFreshSize)
		}

		if savings > 0 && !math.IsNaN(savings) {
			comm.Statf("%s patch (%.2f%% savings)", prettyPatchSize, 100.0-relToNew)
		} else {
			comm.Statf("%s patch (no savings)", prettyPatchSize)
		}
	}
	return nil
}

type fileSlot struct {
	Type     itchio.BuildFileType
	Response itchio.NewBuildFileResponse
}

func createBothFiles(client *itchio.Client, buildID int64) (patch itchio.NewBuildFileResponse, signature itchio.NewBuildFileResponse, err error) {
	createFile := func(buildType itchio.BuildFileType, done chan fileSlot, errs chan error) {
		var res itchio.NewBuildFileResponse
		res, err = client.CreateBuildFile(buildID, buildType)
		if err != nil {
			errs <- err
		}
		comm.Debugf("Created %s build file: %+v", buildType, res.File)
		done <- fileSlot{buildType, res}
	}

	done := make(chan fileSlot)
	errs := make(chan error)

	go createFile(itchio.BuildFileType_PATCH, done, errs)
	go createFile(itchio.BuildFileType_SIGNATURE, done, errs)

	for i := 0; i < 2; i++ {
		select {
		case err = <-errs:
			return
		case slot := <-done:
			switch slot.Type {
			case itchio.BuildFileType_PATCH:
				patch = slot.Response
			case itchio.BuildFileType_SIGNATURE:
				signature = slot.Response
			}
		}
	}

	return
}

func doWalk(path string, result chan *tlc.Container, errs chan error) {
	container, err := tlc.Walk(path, filterPaths)
	if err != nil {
		errs <- err
	}

	result <- container
}

func parseSpec(spec string) (string, string, error) {
	tokens := strings.Split(spec, ":")

	if len(tokens) == 1 {
		return "", "", fmt.Errorf("invalid spec: %s, missing channel (examples: %s:windows-32-beta, %s:linux-64)", spec, spec, spec)
	} else if len(tokens) != 2 {
		return "", "", fmt.Errorf("invalid spec: %s, expected something of the form user/page:channel", spec)
	}

	return tokens[0], tokens[1], nil
}

func min(a, b float64) float64 {
	if a < b {
		return a
	}
	return b
}
<|endoftext|>


<commit_before>package wdte_test

import (
	"strings"
	"testing"

	"github.com/DeedleFake/wdte"
	"github.com/DeedleFake/wdte/std"
)

func TestModule(t *testing.T) {
	const test = `
'stream' => s;

memo fib n => switch n {
	== 0 => 0;
	== 1 => 1;
	default => + (fib (- n 1)) (fib (- n 2));
};

memo fact n => switch n {
	<= 1 => 1;
	default => - n 1 -> fact -> * n;
};

main => (
	s.range 15
  -> s.map fib
	-> s.collect
	-> print;

	s.new [5; 2; fib 7]
	-> s.map (+ 2)
	-> s.collect
	-> print;

	fact 5 -> print;
);
`

	m, err := std.Module().Parse(strings.NewReader(test), std.Import)
	if err != nil {
		t.Fatal(err)
	}

	m.Funcs["print"] = wdte.GoFunc(func(frame wdte.Frame, args ...wdte.Func) wdte.Func {
		frame = frame.WithID("print")

		if len(args) < 1 {
			return m.Funcs["print"]
		}

		a := args[0].Call(frame)
		if _, ok := a.(error); !ok {
			t.Logf("%v", a)
		}
		return a
	})

	//t.Log("Imports:")
	//for i := range m.Imports {
	//	t.Logf("\t%q", i)
	//}

	//t.Log("Funcs:")
	//for f := range m.Funcs {
	//	t.Logf("\t%q", f)
	//}

	if err, ok := m.Funcs["main"].Call(wdte.F()).(error); ok {
		t.Fatal(err)
	}
}
<commit_msg>wdte: Use io module in test.<commit_after>package wdte_test

import (
	"strings"
	"testing"

	"github.com/DeedleFake/wdte"
	"github.com/DeedleFake/wdte/std"
	"github.com/DeedleFake/wdte/std/io"
)

type twriter struct {
	t *testing.T
}

func (w twriter) Write(data []byte) (int, error) {
	w.t.Logf("%s", data)
	return len(data), nil
}

func TestModule(t *testing.T) {
	const test = `
'stream' => s;
'io' => io;

memo fib n => switch n {
	== 0 => 0;
	== 1 => 1;
	default => + (fib (- n 1)) (fib (- n 2));
};

memo fact n => switch n {
	<= 1 => 1;
	default => - n 1 -> fact -> * n;
};

main w => (
	s.range 15
  -> s.map fib
	-> s.collect
	-> print;

	s.new [5; 2; fib 7]
	-> s.map (+ 2)
	-> s.collect
	-> print;

	fact 5 -> print;

	w
	-> io.writeln 'This is a test.'
	-> io.writeln 'Or is it?';
);
`

	m, err := std.Module().Parse(strings.NewReader(test), std.Import)
	if err != nil {
		t.Fatal(err)
	}

	m.Funcs["print"] = wdte.GoFunc(func(frame wdte.Frame, args ...wdte.Func) wdte.Func {
		frame = frame.WithID("print")

		if len(args) < 1 {
			return m.Funcs["print"]
		}

		a := args[0].Call(frame)
		if _, ok := a.(error); !ok {
			t.Logf("%v", a)
		}
		return a
	})

	//t.Log("Imports:")
	//for i := range m.Imports {
	//	t.Logf("\t%q", i)
	//}

	//t.Log("Funcs:")
	//for f := range m.Funcs {
	//	t.Logf("\t%q", f)
	//}

	w := twriter{t: t}
	if err, ok := m.Funcs["main"].Call(wdte.F(), io.Writer{w}).(error); ok {
		t.Fatal(err)
	}
}
<|endoftext|>