<commit_before>package examples

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/pachyderm/pachyderm/src/client"
	pfsclient "github.com/pachyderm/pachyderm/src/client/pfs"
	"github.com/pachyderm/pachyderm/src/client/pkg/require"
)

func getPachClient(t testing.TB) *client.APIClient {
	client, err := client.NewFromAddress("0.0.0.0:30650")
	require.NoError(t, err)
	return client
}

func TestExampleTensorFlow(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}

	c := getPachClient(t)
	t.Parallel()

	cwd, err := os.Getwd()
	require.NoError(t, err)
	exampleDir := filepath.Join(cwd, "../../../doc/examples/tensor_flow")
	cmd := exec.Command("make", "test")
	cmd.Dir = exampleDir
	_, err = cmd.CombinedOutput()
	require.NoError(t, err)

	commitInfos, err := c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"GoT_scripts"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusAll,
		false,
	)
	require.NoError(t, err)
	require.Equal(t, 1, len(commitInfos))
	inputCommitID := commitInfos[0].Commit.ID

	// Wait until the GoT_generate job has finished
	commitInfos, err = c.FlushCommit([]*pfsclient.Commit{client.NewCommit("GoT_scripts", inputCommitID)}, nil)
	require.NoError(t, err)
	require.Equal(t, 3, len(commitInfos))

	repos := []interface{}{"GoT_train", "GoT_generate", "GoT_scripts"}
	var generateCommitID string
	for _, commitInfo := range commitInfos {
		require.EqualOneOf(t, repos, commitInfo.Commit.Repo.Name)
		if commitInfo.Commit.Repo.Name == "GoT_generate" {
			generateCommitID = commitInfo.Commit.ID
		}
	}

	// Make sure the final output is non zero
	var buffer bytes.Buffer
	require.NoError(t, c.GetFile("GoT_generate", generateCommitID, "new_script.txt", 0, 0, "", false, nil, &buffer))
	if buffer.Len() < 100 {
		t.Fatalf("Output GoT script is too small (has len=%v)", buffer.Len())
	}
	require.NoError(t, c.DeleteRepo("GoT_generate", false))
	require.NoError(t, c.DeleteRepo("GoT_train", false))
	require.NoError(t, c.DeleteRepo("GoT_scripts", false))
}

func TestFruitStand(t *testing.T) {

	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}

	c := getPachClient(t)
	t.Parallel()

	require.NoError(t, c.CreateRepo("data"))
	repoInfos, err := c.ListRepo(nil)
	require.NoError(t, err)
	var repoNames []interface{}
	for _, repoInfo := range repoInfos {
		repoNames = append(repoNames, repoInfo.Repo.Name)
	}
	require.OneOfEquals(t, "data", repoNames)

	raw, err := exec.Command(
		"git",
		"log",
		"--format=\"%H\"",
	).CombinedOutput()
	require.NoError(t, err)
	currentCodeCommitID := string(raw)

	cmd := exec.Command(
		"pachctl",
		"put-file",
		"data",
		"master",
		"sales",
		"-c",
		"-f",
		fmt.Sprintf("https://raw.githubusercontent.com/pachyderm/pachyderm/%v/doc/examples/fruit_stand/set1.txt", currentCodeCommitID),
	)
	_, err = cmd.CombinedOutput()
	require.NoError(t, err)

	commitInfos, err := c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"data"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusNormal,
		false,
	)
	require.Equal(t, 1, len(commitInfos))
	commit := commitInfos[0].Commit

	var buffer bytes.Buffer
	require.NoError(t, c.GetFile("data", commit.ID, "sales", 0, 0, "", false, nil, &buffer))
	lines := strings.Split(buffer.String(), "\n")
	if len(lines) < 100 {
		t.Fatalf("Sales file has too few lines (%v)\n", len(lines))
	}

	cmd = exec.Command(
		"pachctl",
		"create-pipeline",
		"-f",
		fmt.Sprintf("https://raw.githubusercontent.com/pachyderm/pachyderm/%v/doc/examples/fruit_stand/pipeline.json", currentCodeCommitID),
	)
	raw, err = cmd.CombinedOutput()
	fmt.Printf("raw output: %v\n", string(raw))
	require.NoError(t, err)

	time.Sleep(5)
	repoInfos, err = c.ListRepo(nil)
	require.NoError(t, err)
	repoNames = []interface{}{}
	for _, repoInfo := range repoInfos {
		repoNames = append(repoNames, repoInfo.Repo.Name)
	}
	require.OneOfEquals(t, "sum", repoNames)
	require.OneOfEquals(t, "filter", repoNames)
	require.OneOfEquals(t, "data", repoNames)

	commitInfos, err = c.FlushCommit([]*pfsclient.Commit{client.NewCommit("data", commit.ID)}, nil)
	require.NoError(t, err)
	require.Equal(t, 3, len(commitInfos))

	commitInfos, err = c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"sum"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusNormal,
		false,
	)
	require.NoError(t, err)
	require.Equal(t, 1, len(commitInfos))

	buffer.Reset()
	require.NoError(t, c.GetFile("sum", commitInfos[0].Commit.ID, "apple", 0, 0, "", false, nil, &buffer))
	require.NotNil(t, buffer)
	firstCount, err := strconv.ParseInt(strings.TrimRight(buffer.String(), "\n"), 10, 0)
	require.NoError(t, err)
	if firstCount < 100 {
		t.Fatalf("Wrong sum for apple (%i) ... too low\n", firstCount)
	}

	// Add more data to input

	cmd = exec.Command(
		"pachctl",
		"put-file",
		"data",
		"master",
		"sales",
		"-c",
		"-f",
		"https://raw.githubusercontent.com/pachyderm/pachyderm/master/examples/fruit_stand/set2.txt",
	)
	_, err = cmd.Output()
	require.NoError(t, err)

	// Flush commit!
	commitInfos, err = c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"data"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusAll,
		false,
	)
	require.NoError(t, err)
	require.Equal(t, 2, len(commitInfos))

	commitInfos, err = c.FlushCommit([]*pfsclient.Commit{client.NewCommit("data", commitInfos[1].Commit.ID)}, nil)
	require.NoError(t, err)
	require.Equal(t, 3, len(commitInfos))

	commitInfos, err = c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"sum"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusNormal,
		false,
	)
	require.NoError(t, err)
	require.Equal(t, 2, len(commitInfos))

	buffer.Reset()
	require.NoError(t, c.GetFile("sum", commitInfos[1].Commit.ID, "apple", 0, 0, "", false, nil, &buffer))
	require.NotNil(t, buffer)
	secondCount, err := strconv.ParseInt(strings.TrimRight(buffer.String(), "\n"), 10, 0)
	require.NoError(t, err)
	if firstCount > secondCount {
		t.Fatalf("Second sum (%v) is smaller than first (%v)\n", secondCount, firstCount)
	}

	require.NoError(t, c.DeleteRepo("sum", false))
	require.NoError(t, c.DeleteRepo("filter", false))
	require.NoError(t, c.DeleteRepo("data", false))
}
<commit_msg>Make sure to get the right commit ID<commit_after>package examples

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/pachyderm/pachyderm/src/client"
	pfsclient "github.com/pachyderm/pachyderm/src/client/pfs"
	"github.com/pachyderm/pachyderm/src/client/pkg/require"
)

func getPachClient(t testing.TB) *client.APIClient {
	client, err := client.NewFromAddress("0.0.0.0:30650")
	require.NoError(t, err)
	return client
}

func TestExampleTensorFlow(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}

	c := getPachClient(t)
	t.Parallel()

	cwd, err := os.Getwd()
	require.NoError(t, err)
	exampleDir := filepath.Join(cwd, "../../../doc/examples/tensor_flow")
	cmd := exec.Command("make", "test")
	cmd.Dir = exampleDir
	_, err = cmd.CombinedOutput()
	require.NoError(t, err)

	commitInfos, err := c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"GoT_scripts"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusAll,
		false,
	)
	require.NoError(t, err)
	require.Equal(t, 1, len(commitInfos))
	inputCommitID := commitInfos[0].Commit.ID

	// Wait until the GoT_generate job has finished
	commitInfos, err = c.FlushCommit([]*pfsclient.Commit{client.NewCommit("GoT_scripts", inputCommitID)}, nil)
	require.NoError(t, err)
	require.Equal(t, 3, len(commitInfos))

	repos := []interface{}{"GoT_train", "GoT_generate", "GoT_scripts"}
	var generateCommitID string
	for _, commitInfo := range commitInfos {
		require.EqualOneOf(t, repos, commitInfo.Commit.Repo.Name)
		if commitInfo.Commit.Repo.Name == "GoT_generate" {
			generateCommitID = commitInfo.Commit.ID
		}
	}

	// Make sure the final output is non zero
	var buffer bytes.Buffer
	require.NoError(t, c.GetFile("GoT_generate", generateCommitID, "new_script.txt", 0, 0, "", false, nil, &buffer))
	if buffer.Len() < 100 {
		t.Fatalf("Output GoT script is too small (has len=%v)", buffer.Len())
	}
	require.NoError(t, c.DeleteRepo("GoT_generate", false))
	require.NoError(t, c.DeleteRepo("GoT_train", false))
	require.NoError(t, c.DeleteRepo("GoT_scripts", false))
}

func TestFruitStand(t *testing.T) {

	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}

	c := getPachClient(t)
	t.Parallel()

	require.NoError(t, c.CreateRepo("data"))
	repoInfos, err := c.ListRepo(nil)
	require.NoError(t, err)
	var repoNames []interface{}
	for _, repoInfo := range repoInfos {
		repoNames = append(repoNames, repoInfo.Repo.Name)
	}
	require.OneOfEquals(t, "data", repoNames)

	raw, err := exec.Command(
		"git",
		"log",
		"--format=%H",
	).CombinedOutput()
	require.NoError(t, err)
	lines := strings.Split(string(raw), "\n")
	require.NotEqual(t, 0, len(lines))
	currentCodeCommitID := lines[0]

	cmd := exec.Command(
		"pachctl",
		"put-file",
		"data",
		"master",
		"sales",
		"-c",
		"-f",
		fmt.Sprintf("https://raw.githubusercontent.com/pachyderm/pachyderm/%v/doc/examples/fruit_stand/set1.txt", currentCodeCommitID),
	)
	raw, err = cmd.CombinedOutput()
	fmt.Printf("raw: %v\n", string(raw))
	require.NoError(t, err)

	commitInfos, err := c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"data"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusNormal,
		false,
	)
	require.Equal(t, 1, len(commitInfos))
	commit := commitInfos[0].Commit

	var buffer bytes.Buffer
	require.NoError(t, c.GetFile("data", commit.ID, "sales", 0, 0, "", false, nil, &buffer))
	lines = strings.Split(buffer.String(), "\n")
	if len(lines) < 100 {
		t.Fatalf("Sales file has too few lines (%v)\n", len(lines))
	}

	pipelineURL := fmt.Sprintf("https://raw.githubusercontent.com/pachyderm/pachyderm/%v/doc/examples/fruit_stand/pipeline.json", currentCodeCommitID)
	fmt.Printf("using pipeline: [%v]\n", pipelineURL)
	cmd = exec.Command(
		"pachctl",
		"create-pipeline",
		"-f",
		pipelineURL,
	)
	raw, err = cmd.CombinedOutput()
	fmt.Printf("raw output: %v\n", string(raw))
	require.NoError(t, err)

	time.Sleep(5)
	repoInfos, err = c.ListRepo(nil)
	require.NoError(t, err)
	repoNames = []interface{}{}
	for _, repoInfo := range repoInfos {
		repoNames = append(repoNames, repoInfo.Repo.Name)
	}
	require.OneOfEquals(t, "sum", repoNames)
	require.OneOfEquals(t, "filter", repoNames)
	require.OneOfEquals(t, "data", repoNames)

	commitInfos, err = c.FlushCommit([]*pfsclient.Commit{client.NewCommit("data", commit.ID)}, nil)
	require.NoError(t, err)
	require.Equal(t, 3, len(commitInfos))

	commitInfos, err = c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"sum"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusNormal,
		false,
	)
	require.NoError(t, err)
	require.Equal(t, 1, len(commitInfos))

	buffer.Reset()
	require.NoError(t, c.GetFile("sum", commitInfos[0].Commit.ID, "apple", 0, 0, "", false, nil, &buffer))
	require.NotNil(t, buffer)
	firstCount, err := strconv.ParseInt(strings.TrimRight(buffer.String(), "\n"), 10, 0)
	require.NoError(t, err)
	if firstCount < 100 {
		t.Fatalf("Wrong sum for apple (%i) ... too low\n", firstCount)
	}

	// Add more data to input

	cmd = exec.Command(
		"pachctl",
		"put-file",
		"data",
		"master",
		"sales",
		"-c",
		"-f",
		"https://raw.githubusercontent.com/pachyderm/pachyderm/master/examples/fruit_stand/set2.txt",
	)
	_, err = cmd.Output()
	require.NoError(t, err)

	// Flush commit!
	commitInfos, err = c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"data"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusAll,
		false,
	)
	require.NoError(t, err)
	require.Equal(t, 2, len(commitInfos))

	commitInfos, err = c.FlushCommit([]*pfsclient.Commit{client.NewCommit("data", commitInfos[1].Commit.ID)}, nil)
	require.NoError(t, err)
	require.Equal(t, 3, len(commitInfos))

	commitInfos, err = c.ListCommit(
		[]*pfsclient.Commit{{
			Repo: &pfsclient.Repo{"sum"},
		}},
		nil,
		client.CommitTypeRead,
		client.CommitStatusNormal,
		false,
	)
	require.NoError(t, err)
	require.Equal(t, 2, len(commitInfos))

	buffer.Reset()
	require.NoError(t, c.GetFile("sum", commitInfos[1].Commit.ID, "apple", 0, 0, "", false, nil, &buffer))
	require.NotNil(t, buffer)
	secondCount, err := strconv.ParseInt(strings.TrimRight(buffer.String(), "\n"), 10, 0)
	require.NoError(t, err)
	if firstCount > secondCount {
		t.Fatalf("Second sum (%v) is smaller than first (%v)\n", secondCount, firstCount)
	}

	require.NoError(t, c.DeleteRepo("sum", false))
	require.NoError(t, c.DeleteRepo("filter", false))
	require.NoError(t, c.DeleteRepo("data", false))
}
<|endoftext|>


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

// +build example

package twenty48

import (
	"github.com/hajimehoshi/ebiten"
)

// Dir represents a direction.
type Dir int

const (
	DirUp Dir = iota
	DirRight
	DirDown
	DirLeft
)

type mouseState int

const (
	mouseStateNone mouseState = iota
	mouseStatePressing
	mouseStateSettled
)

type touchState int

const (
	touchStateNone touchState = iota
	touchStatePressing
	touchStateSettled
	touchStateInvalid
	n
)

// String returns a string representing the direction.
func (d Dir) String() string {
	switch d {
	case DirUp:
		return "Up"
	case DirRight:
		return "Right"
	case DirDown:
		return "Down"
	case DirLeft:
		return "Left"
	}
	panic("not reach")
}

// Vector returns a [-1, 1] value for each axis.
func (d Dir) Vector() (x, y int) {
	switch d {
	case DirUp:
		return 0, -1
	case DirRight:
		return 1, 0
	case DirDown:
		return 0, 1
	case DirLeft:
		return -1, 0
	}
	panic("not reach")
}

// Input represents the current key states.
type Input struct {
	keyState map[ebiten.Key]int

	mouseState    mouseState
	mouseInitPosX int
	mouseInitPosY int
	mouseDir      Dir

	touchState    touchState
	touchID       int
	touchInitPosX int
	touchInitPosY int
	touchLastPosX int
	touchLastPosY int
	touchDir      Dir
}

// NewInput generates a new Input object.
func NewInput() *Input {
	return &Input{
		keyState: map[ebiten.Key]int{},
	}
}

var (
	dirKeys = map[ebiten.Key]Dir{
		ebiten.KeyUp:    DirUp,
		ebiten.KeyRight: DirRight,
		ebiten.KeyDown:  DirDown,
		ebiten.KeyLeft:  DirLeft,
	}
)

func abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

func vecToDir(dx, dy int) (Dir, bool) {
	if abs(dx) < 4 && abs(dy) < 4 {
		return 0, false
	}
	if abs(dx) < abs(dy) {
		if dy < 0 {
			return DirUp, true
		}
		return DirDown, true
	} else {
		if dx < 0 {
			return DirLeft, true
		}
		return DirRight, true
	}
}

// Update updates the current input states.
func (i *Input) Update() {
	for k := range dirKeys {
		if ebiten.IsKeyPressed(k) {
			i.keyState[k]++
		} else {
			i.keyState[k] = 0
		}
	}
	switch i.mouseState {
	case mouseStateNone:
		if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
			x, y := ebiten.CursorPosition()
			i.mouseInitPosX = x
			i.mouseInitPosY = y
			i.mouseState = mouseStatePressing
		}
	case mouseStatePressing:
		if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
			x, y := ebiten.CursorPosition()
			dx := x - i.mouseInitPosX
			dy := y - i.mouseInitPosY
			d, ok := vecToDir(dx, dy)
			if !ok {
				i.mouseState = mouseStateNone
				break
			}
			i.mouseDir = d
			i.mouseState = mouseStateSettled
		}
	case mouseStateSettled:
		i.mouseState = mouseStateNone
	}
	switch i.touchState {
	case touchStateNone:
		ts := ebiten.Touches()
		if len(ts) == 1 {
			i.touchID = ts[0].ID()
			x, y := ts[0].Position()
			i.touchInitPosX = x
			i.touchInitPosY = y
			i.touchLastPosX = x
			i.touchLastPosX = y
			i.touchState = touchStatePressing
		}
	case touchStatePressing:
		ts := ebiten.Touches()
		if len(ts) >= 2 {
			break
		}
		if len(ts) == 1 {
			if ts[0].ID() != i.touchID {
				i.touchState = touchStateInvalid
			} else {
				x, y := ts[0].Position()
				i.touchLastPosX = x
				i.touchLastPosY = y
			}
			break
		}
		if len(ts) == 0 {
			dx := i.touchLastPosX - i.touchInitPosX
			dy := i.touchLastPosY - i.touchInitPosY
			d, ok := vecToDir(dx, dy)
			if !ok {
				i.touchState = touchStateNone
				break
			}
			i.touchDir = d
			i.touchState = touchStateSettled
		}
	case touchStateSettled:
		i.touchState = touchStateNone
	case touchStateInvalid:
		if len(ebiten.Touches()) == 0 {
			i.touchState = touchStateNone
		}
	}
}

// Dir returns a currently pressed direction.
// Dir returns false if no direction key is pressed.
func (i *Input) Dir() (Dir, bool) {
	for k, d := range dirKeys {
		if i.keyState[k] == 1 {
			return d, true
		}
	}
	if i.mouseState == mouseStateSettled {
		return i.mouseDir, true
	}
	if i.touchState == touchStateSettled {
		return i.touchDir, true
	}
	return 0, false
}
<commit_msg>examples/2048: Remove unused consts<commit_after>// Copyright 2016 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build example

package twenty48

import (
	"github.com/hajimehoshi/ebiten"
)

// Dir represents a direction.
type Dir int

const (
	DirUp Dir = iota
	DirRight
	DirDown
	DirLeft
)

type mouseState int

const (
	mouseStateNone mouseState = iota
	mouseStatePressing
	mouseStateSettled
)

type touchState int

const (
	touchStateNone touchState = iota
	touchStatePressing
	touchStateSettled
	touchStateInvalid
)

// String returns a string representing the direction.
func (d Dir) String() string {
	switch d {
	case DirUp:
		return "Up"
	case DirRight:
		return "Right"
	case DirDown:
		return "Down"
	case DirLeft:
		return "Left"
	}
	panic("not reach")
}

// Vector returns a [-1, 1] value for each axis.
func (d Dir) Vector() (x, y int) {
	switch d {
	case DirUp:
		return 0, -1
	case DirRight:
		return 1, 0
	case DirDown:
		return 0, 1
	case DirLeft:
		return -1, 0
	}
	panic("not reach")
}

// Input represents the current key states.
type Input struct {
	keyState map[ebiten.Key]int

	mouseState    mouseState
	mouseInitPosX int
	mouseInitPosY int
	mouseDir      Dir

	touchState    touchState
	touchID       int
	touchInitPosX int
	touchInitPosY int
	touchLastPosX int
	touchLastPosY int
	touchDir      Dir
}

// NewInput generates a new Input object.
func NewInput() *Input {
	return &Input{
		keyState: map[ebiten.Key]int{},
	}
}

var (
	dirKeys = map[ebiten.Key]Dir{
		ebiten.KeyUp:    DirUp,
		ebiten.KeyRight: DirRight,
		ebiten.KeyDown:  DirDown,
		ebiten.KeyLeft:  DirLeft,
	}
)

func abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

func vecToDir(dx, dy int) (Dir, bool) {
	if abs(dx) < 4 && abs(dy) < 4 {
		return 0, false
	}
	if abs(dx) < abs(dy) {
		if dy < 0 {
			return DirUp, true
		}
		return DirDown, true
	} else {
		if dx < 0 {
			return DirLeft, true
		}
		return DirRight, true
	}
}

// Update updates the current input states.
func (i *Input) Update() {
	for k := range dirKeys {
		if ebiten.IsKeyPressed(k) {
			i.keyState[k]++
		} else {
			i.keyState[k] = 0
		}
	}
	switch i.mouseState {
	case mouseStateNone:
		if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
			x, y := ebiten.CursorPosition()
			i.mouseInitPosX = x
			i.mouseInitPosY = y
			i.mouseState = mouseStatePressing
		}
	case mouseStatePressing:
		if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
			x, y := ebiten.CursorPosition()
			dx := x - i.mouseInitPosX
			dy := y - i.mouseInitPosY
			d, ok := vecToDir(dx, dy)
			if !ok {
				i.mouseState = mouseStateNone
				break
			}
			i.mouseDir = d
			i.mouseState = mouseStateSettled
		}
	case mouseStateSettled:
		i.mouseState = mouseStateNone
	}
	switch i.touchState {
	case touchStateNone:
		ts := ebiten.Touches()
		if len(ts) == 1 {
			i.touchID = ts[0].ID()
			x, y := ts[0].Position()
			i.touchInitPosX = x
			i.touchInitPosY = y
			i.touchLastPosX = x
			i.touchLastPosX = y
			i.touchState = touchStatePressing
		}
	case touchStatePressing:
		ts := ebiten.Touches()
		if len(ts) >= 2 {
			break
		}
		if len(ts) == 1 {
			if ts[0].ID() != i.touchID {
				i.touchState = touchStateInvalid
			} else {
				x, y := ts[0].Position()
				i.touchLastPosX = x
				i.touchLastPosY = y
			}
			break
		}
		if len(ts) == 0 {
			dx := i.touchLastPosX - i.touchInitPosX
			dy := i.touchLastPosY - i.touchInitPosY
			d, ok := vecToDir(dx, dy)
			if !ok {
				i.touchState = touchStateNone
				break
			}
			i.touchDir = d
			i.touchState = touchStateSettled
		}
	case touchStateSettled:
		i.touchState = touchStateNone
	case touchStateInvalid:
		if len(ebiten.Touches()) == 0 {
			i.touchState = touchStateNone
		}
	}
}

// Dir returns a currently pressed direction.
// Dir returns false if no direction key is pressed.
func (i *Input) Dir() (Dir, bool) {
	for k, d := range dirKeys {
		if i.keyState[k] == 1 {
			return d, true
		}
	}
	if i.mouseState == mouseStateSettled {
		return i.mouseDir, true
	}
	if i.touchState == touchStateSettled {
		return i.touchDir, true
	}
	return 0, false
}
<|endoftext|>


<commit_before>// Implements a wrapper around net/smtp
package libsmtp

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/zerklabs/auburn"
	"io/ioutil"
	"net/smtp"
	"path/filepath"
	"strings"
	"time"
)

type MailMessage struct {
	attachmentLengths    map[string]int
	attachments          map[string][]byte
	attachmentBoundaries map[string]string
	body                 *bytes.Buffer
	buf                  *bytes.Buffer
	from                 string
	port                 int
	server               string
	subject              string
	tls                  bool
	to                   []string
	contentType          string

	buildCalled bool
}

var (
	mailMessage *MailMessage
)

func New(server string, port int, from string, to []string, usetls bool) (*MailMessage, error) {
	if len(server) == 0 {
		return &MailMessage{}, errors.New("SMTP server required")
	}

	if len(from) == 0 {
		return &MailMessage{}, errors.New("SMTP sender required")
	}

	if len(to) == 0 {
		return &MailMessage{}, errors.New("Mail recipient(s) required")
	}

	if port <= 0 {
		port = 25
	}

	mailMessage = &MailMessage{port: port, server: server}

	mailMessage.attachments = make(map[string][]byte, 0)
	mailMessage.attachmentLengths = make(map[string]int, 0)
	mailMessage.attachmentBoundaries = make(map[string]string, 0)
	mailMessage.subject = fmt.Sprintf("libsmtp - %s", time.Now())
	mailMessage.to = to
	mailMessage.from = from
	mailMessage.buf = bytes.NewBuffer(nil)
	mailMessage.body = bytes.NewBuffer(nil)
	mailMessage.tls = usetls
	mailMessage.SetContentType("")

	return mailMessage, nil
}

// Given a path to a file, we will base64 encode and generate a unique boundary ID for it
func (m *MailMessage) AddAttachment(pathToFile string) error {
	if len(pathToFile) > 0 {

		attachmentName := filepath.Base(pathToFile)
		b, err := ioutil.ReadFile(pathToFile)

		if err != nil {
			return err
		}

		encodedLen := base64.StdEncoding.EncodedLen(len(b))
		encodedAttachment := make([]byte, encodedLen)
		base64.StdEncoding.Encode(encodedAttachment, b)

		m.attachments[attachmentName] = encodedAttachment
		m.attachmentLengths[attachmentName] = encodedLen
		m.attachmentBoundaries[attachmentName] = auburn.RandomBase36()
	} else {
		return errors.New("No attachment specified")
	}

	return nil
}

// Set the body to the given string
func (m *MailMessage) SetBody(data string) {
	if len(data) > 0 {
		m.body.WriteString(data)
	}
}

// An alternative to SetBody(string), this lets you set pre-existing
// bytes as the body
func (m *MailMessage) SetBodyBytes(data []byte) {
	if len(data) > 0 {
		m.body.Write(data)
	}
}

// Defaults to text/plain
func (m *MailMessage) SetContentType(data string) {
	if len(data) > 0 {
		m.contentType = data
	} else {
		m.contentType = "text/plain"
	}
}

func (m *MailMessage) Subject(subject string) {
	if len(subject) > 0 {
		m.subject = subject
	}
}

func (m *MailMessage) build() error {
	if m.body.Len() == 0 {
		return errors.New("Message body is empty")
	}

	m.buf.WriteString(fmt.Sprintf("Subject: %s\n", m.subject))
	m.buf.WriteString("MIME-version: 1.0;\n")

	if len(m.attachments) > 0 {
		for _, v := range m.attachmentBoundaries {
			m.buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", v))
			m.buf.WriteString(fmt.Sprintf("--%s\n", v))
		}
	}

	m.buf.WriteString(fmt.Sprintf("Content-Type: %s; charset=\"UTF-8\";\n\n", m.contentType))
	m.buf.Write(m.body.Bytes())

	if len(m.attachments) > 0 {
		for k, v := range m.attachmentBoundaries {
			m.buf.WriteString(fmt.Sprintf("\n\n--%s\n", v))
			m.buf.WriteString(fmt.Sprintf("Content-Type: application/octet-stream; name=\"%s\"\n", k))
			m.buf.WriteString(fmt.Sprintf("Content-Description: %s\n", k))
			m.buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"%s\"; size=%d\n", k, m.attachmentLengths[k]))
			m.buf.WriteString("Content-Transfer-Encoding: base64\n\n")

			m.buf.Write(m.attachments[k])
			m.buf.WriteString(fmt.Sprintf("\n--%s--", v))
		}
	}

	m.buildCalled = true

	return nil
}

// Returns the entire message as a byte array
func (m *MailMessage) Bytes() ([]byte, error) {
	if m.buildCalled {
		return m.buf.Bytes(), nil
	}

	err := m.build()

	return m.buf.Bytes(), err
}

// Attempts to send the mail message.
//
// By default, if TLS is desired and the handshake fails with the server,
// this will continue to send the mail over an unencrypted channel
func (m *MailMessage) Send() error {
	var smtpUri string

	if err := m.build(); err != nil {
		return err
	}

	if strings.Contains(m.server, ":") {
		smtpUri = m.server
	} else {
		smtpUri = fmt.Sprintf("%s:%d", m.server, m.port)
	}

	c, err := smtp.Dial(smtpUri)

	if err != nil {
		return err
	}

	if m.tls {
		// check if TLS is supported
		if ok, _ := c.Extension("STARTTLS"); ok {
			if err = c.StartTLS(&tls.Config{InsecureSkipVerify: true, ServerName: m.server}); err != nil {
				c.Reset()
				c.Quit()

				return err
			}
		}
	}

	// set the from addr
	if err = c.Mail(m.from); err != nil {
		c.Reset()
		c.Quit()

		return err
	}

	// add the recipients
	for _, v := range m.to {
		if err = c.Rcpt(v); err != nil {
			c.Reset()
			c.Quit()

			return err
		}
	}

	w, err := c.Data()

	if err != nil {
		c.Reset()
		c.Quit()

		return err
	}

	_, err = w.Write(m.buf.Bytes())

	if err != nil {
		c.Reset()
		c.Quit()

		return err
	}

	if err = w.Close(); err != nil {
		c.Reset()
		c.Quit()

		return err
	}

	c.Quit()

	return nil
}
<commit_msg>Set Content-Transfer-Encoding to be base64 by default for messages and fixed the order of mime message elements<commit_after>// Implements a wrapper around net/smtp
package libsmtp

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/zerklabs/auburn"
	"io/ioutil"
	"net/smtp"
	"path/filepath"
	"strings"
	"time"
)

type MailMessage struct {
	attachmentLengths    map[string]int
	attachments          map[string][]byte
	attachmentBoundaries map[string]string
	body                 *bytes.Buffer
	buf                  *bytes.Buffer
	from                 string
	port                 int
	server               string
	subject              string
	tls                  bool
	to                   []string
	contentType          string

	buildCalled bool
}

func New(server string, port int, from string, to []string, usetls bool) (*MailMessage, error) {
	if len(server) == 0 {
		return &MailMessage{}, errors.New("SMTP server required")
	}

	if len(from) == 0 {
		return &MailMessage{}, errors.New("SMTP sender required")
	}

	if len(to) == 0 {
		return &MailMessage{}, errors.New("Mail recipient(s) required")
	}

	if port <= 0 {
		port = 25
	}

	mailMessage := &MailMessage{
		attachmentBoundaries: make(map[string]string, 0),
		attachmentLengths:    make(map[string]int, 0),
		attachments:          make(map[string][]byte, 0),
		body:                 bytes.NewBuffer(nil),
		buf:                  bytes.NewBuffer(nil),
		contentType:          "text/plain",
		from:                 from,
		port:                 port,
		server:               server,
		subject:              fmt.Sprintf("libsmtp - %s", time.Now()),
		tls:                  usetls,
		to:                   to,
	}

	return mailMessage, nil
}

// (optional) Given a path to a file, we will base64 encode and
// generate a unique boundary ID for it
func (m *MailMessage) AddAttachment(pathToFile string) error {
	if len(pathToFile) > 0 {

		attachmentName := filepath.Base(pathToFile)
		b, err := ioutil.ReadFile(pathToFile)

		if err != nil {
			return err
		}

		encodedLen := base64.StdEncoding.EncodedLen(len(b))
		encodedAttachment := make([]byte, encodedLen)
		base64.StdEncoding.Encode(encodedAttachment, b)

		m.attachments[attachmentName] = encodedAttachment
		m.attachmentLengths[attachmentName] = encodedLen
		m.attachmentBoundaries[attachmentName] = auburn.RandomBase36()
	} else {
		return errors.New("No attachment specified")
	}

	return nil
}

// Set the body to the given string
func (m *MailMessage) SetBody(data string) {
	if len(data) > 0 {
		m.body.WriteString(data)
	}
}

// An alternative to SetBody(string), this lets you set pre-existing
// bytes as the body
func (m *MailMessage) SetBodyBytes(data []byte) {
	if len(data) > 0 {
		m.body.Write(data)
	}
}

// (optional) Defaults to text/plain
func (m *MailMessage) SetContentType(data string) {
	if len(data) > 0 {
		m.contentType = data
	} else {
		m.contentType = "text/plain"
	}
}

// (optional) Set the message subject
func (m *MailMessage) Subject(subject string) {
	if len(subject) > 0 {
		m.subject = subject
	}
}

// Creates the MIME mail message and writes it to the MailMessage buffer
func (m *MailMessage) build() error {
	if m.body.Len() == 0 {
		return errors.New("Message body is empty")
	}

	// base64 encode the body
	// write the body
	body := m.body.Bytes()
	encodedBodyLen := base64.StdEncoding.EncodedLen(len(body))
	encodedBody := make([]byte, encodedBodyLen)
	base64.StdEncoding.Encode(encodedBody, body)

	m.buf.WriteString(fmt.Sprintf("To: %s\n", strings.Join(m.to, ",")))
	m.buf.WriteString(fmt.Sprintf("Subject: %s\n", m.subject))
	if len(m.attachments) > 0 {
		for _, v := range m.attachmentBoundaries {
			m.buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", v))
			m.buf.WriteString(fmt.Sprintf("--%s\n", v))
		}
	}

	m.buf.WriteString("Content-Transfer-Encoding: base64\n")
	m.buf.WriteString("MIME-Version: 1.0;\n")
	m.buf.WriteString(fmt.Sprintf("Content-Type: %s; charset=\"utf-8\";\n\n", m.contentType))
	m.buf.Write(encodedBody)

	if len(m.attachments) > 0 {
		for k, v := range m.attachmentBoundaries {
			m.buf.WriteString(fmt.Sprintf("\n\n--%s\n", v))
			m.buf.WriteString(fmt.Sprintf("Content-Type: application/octet-stream; name=\"%s\"\n", k))
			m.buf.WriteString(fmt.Sprintf("Content-Description: %s\n", k))
			m.buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"%s\"; size=%d\n", k, m.attachmentLengths[k]))
			m.buf.WriteString("Content-Transfer-Encoding: base64\n\n")

			m.buf.Write(m.attachments[k])
			m.buf.WriteString(fmt.Sprintf("\n--%s--", v))
		}
	}

	m.buildCalled = true

	return nil
}

// Returns the entire message as a byte array
func (m *MailMessage) Bytes() ([]byte, error) {
	if m.buildCalled {
		return m.buf.Bytes(), nil
	}

	err := m.build()

	return m.buf.Bytes(), err
}

// Attempts to send the mail message.
//
// By default, if TLS is desired and the handshake fails with the server,
// this will continue to send the mail over an unencrypted channel
func (m *MailMessage) Send() error {
	var smtpUri string

	if err := m.build(); err != nil {
		return err
	}

	if strings.Contains(m.server, ":") {
		smtpUri = m.server
	} else {
		smtpUri = fmt.Sprintf("%s:%d", m.server, m.port)
	}

	c, err := smtp.Dial(smtpUri)

	if err != nil {
		return err
	}

	if m.tls {
		// check if TLS is supported
		if ok, _ := c.Extension("STARTTLS"); ok {
			if err = c.StartTLS(&tls.Config{InsecureSkipVerify: true, ServerName: m.server}); err != nil {
				c.Reset()
				c.Quit()

				return err
			}
		}
	}

	// set the from addr
	if err = c.Mail(m.from); err != nil {
		c.Reset()
		c.Quit()

		return err
	}

	// add the recipients
	for _, v := range m.to {
		if err = c.Rcpt(v); err != nil {
			c.Reset()
			c.Quit()

			return err
		}
	}

	w, err := c.Data()

	if err != nil {
		c.Reset()
		c.Quit()

		return err
	}

	_, err = w.Write(m.buf.Bytes())

	if err != nil {
		c.Reset()
		c.Quit()

		return err
	}

	if err = w.Close(); err != nil {
		c.Reset()
		c.Quit()

		return err
	}

	c.Quit()

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2022 The go-python Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package py

import (
	"errors"
	"strconv"
)

var (
	ErrUnsupportedObjType = errors.New("unsupported obj type")
)

// GetLen is a high-level convenience function that returns the length of the given Object.
func GetLen(obj Object) (Int, error) {
	getlen, ok := obj.(I__len__)
	if !ok {
		return 0, nil
	}

	lenObj, err := getlen.M__len__()
	if err != nil {
		return 0, err
	}

	return GetInt(lenObj)
}

// GetInt is a high-level convenience function that converts the given value to an int.
func GetInt(obj Object) (Int, error) {
	toIdx, ok := obj.(I__index__)
	if !ok {
		_, err := cantConvert(obj, "int")
		return 0, err
	}

	return toIdx.M__index__()
}

// LoadTuple attempts to convert each element of the given list and store into each destination value (based on its type).
func LoadTuple(args Tuple, vars []interface{}) error {

	if len(args) > len(vars) {
		return ExceptionNewf(RuntimeError, "%d args given, expected %d", len(args), len(vars))
	}

	if len(vars) > len(args) {
		vars = vars[:len(args)]
	}

	for i, rval := range vars {
		err := loadValue(args[i], rval)
		if err == ErrUnsupportedObjType {
			return ExceptionNewf(TypeError, "arg %d has unsupported object type: %s", i, args[i].Type().Name)
		}
	}

	return nil
}

// LoadAttr gets the named attribute and attempts to store it into the given destination value (based on its type).
func LoadAttr(obj Object, attrName string, dst interface{}) error {
	attr, err := GetAttrString(obj, attrName)
	if err != nil {
		return err
	}
	err = loadValue(attr, dst)
	if err == ErrUnsupportedObjType {
		return ExceptionNewf(TypeError, "attribute \"%s\" has unsupported object type: %s", attrName, attr.Type().Name)
	}
	return nil
}

// LoadIntsFromList extracts a list of ints contained given a py.List or py.Tuple
func LoadIntsFromList(list Object) ([]int64, error) {
	N, err := GetLen(list)
	if err != nil {
		return nil, err
	}

	getter, ok := list.(I__getitem__)
	if !ok {
		return nil, nil
	}

	if N <= 0 {
		return nil, nil
	}

	intList := make([]int64, N)
	for i := Int(0); i < N; i++ {
		item, err := getter.M__getitem__(i)
		if err != nil {
			return nil, err
		}

		var intVal Int
		intVal, err = GetInt(item)
		if err != nil {
			return nil, err
		}

		intList[i] = int64(intVal)
	}

	return intList, nil
}

func loadValue(src Object, data interface{}) error {
	var (
		v_str   string
		v_float float64
		v_int   int64
	)

	haveStr := false

	switch v := src.(type) {
	case Bool:
		if v {
			v_int = 1
			v_float = 1
			v_str = "True"
		} else {
			v_str = "False"
		}
		haveStr = true
	case Int:
		v_int = int64(v)
		v_float = float64(v)
	case Float:
		v_int = int64(v)
		v_float = float64(v)
	case String:
		v_str = string(v)
		haveStr = true
	case NoneType:
		// No-op
	default:
		return ErrUnsupportedObjType
	}

	switch dst := data.(type) {
	case *Int:
		*dst = Int(v_int)
	case *bool:
		*dst = v_int != 0
	case *int8:
		*dst = int8(v_int)
	case *uint8:
		*dst = uint8(v_int)
	case *int16:
		*dst = int16(v_int)
	case *uint16:
		*dst = uint16(v_int)
	case *int32:
		*dst = int32(v_int)
	case *uint32:
		*dst = uint32(v_int)
	case *int:
		*dst = int(v_int)
	case *uint:
		*dst = uint(v_int)
	case *int64:
		*dst = v_int
	case *uint64:
		*dst = uint64(v_int)
	case *float32:
		if haveStr {
			v_float, _ = strconv.ParseFloat(v_str, 32)
		}
		*dst = float32(v_float)
	case *float64:
		if haveStr {
			v_float, _ = strconv.ParseFloat(v_str, 64)
		}
		*dst = v_float
	case *Float:
		if haveStr {
			v_float, _ = strconv.ParseFloat(v_str, 64)
		}
		*dst = Float(v_float)
	case *string:
		*dst = v_str
	case *String:
		*dst = String(v_str)
	// case []uint64:
	// 	for i := range data {
	// 		dst[i] = order.Uint64(bs[8*i:])
	// 	}
	// case []float32:
	// 	for i := range data {
	// 		dst[i] = math.Float32frombits(order.Uint32(bs[4*i:]))
	// 	}
	// case []float64:
	// 	for i := range data {
	// 		dst[i] = math.Float64frombits(order.Uint64(bs[8*i:]))
	// 	}

	default:
		return ExceptionNewf(NotImplementedError, "%s", "unsupported Go data type")
	}
	return nil
}
<commit_msg>py: add convenience function Println<commit_after>// Copyright 2022 The go-python Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package py

import (
	"errors"
	"strconv"
	"strings"
)

var (
	ErrUnsupportedObjType = errors.New("unsupported obj type")
)

// GetLen is a high-level convenience function that returns the length of the given Object.
func GetLen(obj Object) (Int, error) {
	getlen, ok := obj.(I__len__)
	if !ok {
		return 0, nil
	}

	lenObj, err := getlen.M__len__()
	if err != nil {
		return 0, err
	}

	return GetInt(lenObj)
}

// GetInt is a high-level convenience function that converts the given value to an int.
func GetInt(obj Object) (Int, error) {
	toIdx, ok := obj.(I__index__)
	if !ok {
		_, err := cantConvert(obj, "int")
		return 0, err
	}

	return toIdx.M__index__()
}

// LoadTuple attempts to convert each element of the given list and store into each destination value (based on its type).
func LoadTuple(args Tuple, vars []interface{}) error {

	if len(args) > len(vars) {
		return ExceptionNewf(RuntimeError, "%d args given, expected %d", len(args), len(vars))
	}

	if len(vars) > len(args) {
		vars = vars[:len(args)]
	}

	for i, rval := range vars {
		err := loadValue(args[i], rval)
		if err == ErrUnsupportedObjType {
			return ExceptionNewf(TypeError, "arg %d has unsupported object type: %s", i, args[i].Type().Name)
		}
	}

	return nil
}

// LoadAttr gets the named attribute and attempts to store it into the given destination value (based on its type).
func LoadAttr(obj Object, attrName string, dst interface{}) error {
	attr, err := GetAttrString(obj, attrName)
	if err != nil {
		return err
	}
	err = loadValue(attr, dst)
	if err == ErrUnsupportedObjType {
		return ExceptionNewf(TypeError, "attribute \"%s\" has unsupported object type: %s", attrName, attr.Type().Name)
	}
	return nil
}

// LoadIntsFromList extracts a list of ints contained given a py.List or py.Tuple
func LoadIntsFromList(list Object) ([]int64, error) {
	N, err := GetLen(list)
	if err != nil {
		return nil, err
	}

	getter, ok := list.(I__getitem__)
	if !ok {
		return nil, nil
	}

	if N <= 0 {
		return nil, nil
	}

	intList := make([]int64, N)
	for i := Int(0); i < N; i++ {
		item, err := getter.M__getitem__(i)
		if err != nil {
			return nil, err
		}

		var intVal Int
		intVal, err = GetInt(item)
		if err != nil {
			return nil, err
		}

		intList[i] = int64(intVal)
	}

	return intList, nil
}

func loadValue(src Object, data interface{}) error {
	var (
		v_str   string
		v_float float64
		v_int   int64
	)

	haveStr := false

	switch v := src.(type) {
	case Bool:
		if v {
			v_int = 1
			v_float = 1
			v_str = "True"
		} else {
			v_str = "False"
		}
		haveStr = true
	case Int:
		v_int = int64(v)
		v_float = float64(v)
	case Float:
		v_int = int64(v)
		v_float = float64(v)
	case String:
		v_str = string(v)
		haveStr = true
	case NoneType:
		// No-op
	default:
		return ErrUnsupportedObjType
	}

	switch dst := data.(type) {
	case *Int:
		*dst = Int(v_int)
	case *bool:
		*dst = v_int != 0
	case *int8:
		*dst = int8(v_int)
	case *uint8:
		*dst = uint8(v_int)
	case *int16:
		*dst = int16(v_int)
	case *uint16:
		*dst = uint16(v_int)
	case *int32:
		*dst = int32(v_int)
	case *uint32:
		*dst = uint32(v_int)
	case *int:
		*dst = int(v_int)
	case *uint:
		*dst = uint(v_int)
	case *int64:
		*dst = v_int
	case *uint64:
		*dst = uint64(v_int)
	case *float32:
		if haveStr {
			v_float, _ = strconv.ParseFloat(v_str, 32)
		}
		*dst = float32(v_float)
	case *float64:
		if haveStr {
			v_float, _ = strconv.ParseFloat(v_str, 64)
		}
		*dst = v_float
	case *Float:
		if haveStr {
			v_float, _ = strconv.ParseFloat(v_str, 64)
		}
		*dst = Float(v_float)
	case *string:
		*dst = v_str
	case *String:
		*dst = String(v_str)
	// case []uint64:
	// 	for i := range data {
	// 		dst[i] = order.Uint64(bs[8*i:])
	// 	}
	// case []float32:
	// 	for i := range data {
	// 		dst[i] = math.Float32frombits(order.Uint32(bs[4*i:]))
	// 	}
	// case []float64:
	// 	for i := range data {
	// 		dst[i] = math.Float64frombits(order.Uint64(bs[8*i:]))
	// 	}

	default:
		return ExceptionNewf(NotImplementedError, "%s", "unsupported Go data type")
	}
	return nil
}

// Println prints the provided strings to gpython's stdout.
func Println(self Object, args ...string) bool {
	sysModule, err := self.(*Module).Context.GetModule("sys")
	if err != nil {
		return false
	}
	stdout := sysModule.Globals["stdout"]
	write, err := GetAttrString(stdout, "write")
	if err != nil {
		return false
	}
	call, ok := write.(I__call__)
	if !ok {
		return false
	}
	for _, v := range args {
		if !strings.Contains(v, "\n") {
			v += " "
		}
		_, err := call.M__call__(Tuple{String(v)}, nil)
		if err != nil {
			return false
		}

	}
	_, err = call.M__call__(Tuple{String("\n")}, nil) // newline
	return err == nil
}
<|endoftext|>


<commit_before>package imagejob

import (
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/trilopin/godinary/storage"
)

var fetchCases = []struct {
	url     string
	method  string
	status  int
	message string
}{
	{
		"/image/fetch/w_500,c_limit/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg",
		"GET",
		200,
		"Regular use",
	},
	{
		"/image/fetch/w_500,c_limit/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg",
		"POST",
		405,
		"Bad Method POST",
	},
	{
		"/image/fetch/w_500,c_limit/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg",
		"PUT",
		405,
		"Bad Method PUT",
	},
	{
		"/image/fetch/w_pp,c_limit/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg",
		"GET",
		400,
		"Wrong filter",
	},
	{
		"/image/fetch/w_500,c_limit/",
		"GET",
		500,
		"Non existent URI",
	},
}

func setupModule() {

	os.Setenv("GODINARY_FS_BASE", "/tmp/.godinary/")
	storage.StorageDriver = storage.NewFileDriver()
	MaxRequest = 2
	MaxRequestPerDomain = 1
	SpecificThrotling = make(map[string]chan struct{}, MaxRequestPerDomain)
	GlobalThrotling = make(chan struct{}, MaxRequest)
}

func TestFetch(t *testing.T) {
	setupModule()
	defer os.RemoveAll("/tmp/.godinary")

	for _, test := range fetchCases {
		req, _ := http.NewRequest(test.method, test.url, nil)
		req.Header.Set("Accept", "image/webp")
		rr := httptest.NewRecorder()
		handler := http.HandlerFunc(Fetch)
		handler.ServeHTTP(rr, req)

		assert.Equal(t, test.status, rr.Code, test.message)

		if test.status == 200 {
			assert.NotEqual(t, "", rr.Body.String())
		}
	}
}
<commit_msg>improved complete image test<commit_after>package imagejob

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	bimg "gopkg.in/h2non/bimg.v1"

	"github.com/stretchr/testify/assert"
	"github.com/trilopin/godinary/storage"
)

var fetchCases = []struct {
	url     string
	method  string
	status  int
	message string
}{
	{
		"/image/fetch/w_100,h_100,c_limit/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg",
		"GET",
		200,
		"Regular use",
	},
	{
		"/image/fetch/w_500,c_limit/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg",
		"POST",
		405,
		"Bad Method POST",
	},
	{
		"/image/fetch/w_500,c_limit/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg",
		"PUT",
		405,
		"Bad Method PUT",
	},
	{
		"/image/fetch/w_pp,c_limit/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg",
		"GET",
		400,
		"Wrong filter",
	},
	{
		"/image/fetch/w_500,c_limit/",
		"GET",
		500,
		"Non existent URI",
	},
}

func setupModule() {

	os.Setenv("GODINARY_FS_BASE", "/tmp/.godinary/")
	storage.StorageDriver = storage.NewFileDriver()
	MaxRequest = 2
	MaxRequestPerDomain = 1
	SpecificThrotling = make(map[string]chan struct{}, MaxRequestPerDomain)
	GlobalThrotling = make(chan struct{}, MaxRequest)
}

func TestFetch(t *testing.T) {
	setupModule()
	defer os.RemoveAll("/tmp/.godinary")

	for _, test := range fetchCases {
		req, _ := http.NewRequest(test.method, test.url, nil)
		req.Header.Set("Accept", "image/webp")
		rr := httptest.NewRecorder()
		handler := http.HandlerFunc(Fetch)
		handler.ServeHTTP(rr, req)

		assert.Equal(t, test.status, rr.Code, test.message)

		if test.status == 200 {
			buffer, err := ioutil.ReadAll(rr.Body)
			assert.Nil(t, err)
			img := bimg.NewImage(buffer)
			size, _ := img.Size()
			assert.Equal(t, 141, size.Height, "height")
			assert.Equal(t, 100, size.Width, "width")
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2019 The Go Cloud Development Kit 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
//
//     https://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 docstore provides a portable way of interacting with a document store.
// A document store is a service that stores data in semi-structured JSON-like
// documents. Like other NoSQL databases, document stores are schemaless.
// See https://en.wikipedia.org/wiki/Document-oriented_database for more information.
//
// Subpackages contain distinct implementations ("drivers") of docstore for public
// cloud services like Google Cloud Firestore and Amazon DynamoDB as well as any MongoDB
// or MongoDB-compatible service hosted by a cloud provider or on-premise. For example, you can
// use Azure Cosmos DB and Amazon DocumentDB via the MongoDB driver. There is also
// memdocstore, an in-process, in-memory implementation suitable for testing and
// development.
//
// In docstore, documents are grouped into collections, and each document has a key
// that is unique in its collection. You can add, retrieve, modify and delete
// documents by key, and you can query a collection to retrieve documents that match
// certain criteria.
//
// Your application should import one of the service-specific subpackages and use
// its exported functions to create a *Collection; do not use the NewCollection
// function in this package. For example:
//
//   coll, err := memdocstore.OpenCollection("SSN", nil)
//   if err != nil {
//       return fmt.Errorf("opening collection: %v", err)
//   }
//   defer coll.Close()
//   // coll is a *docstore.Collection
//
// Then, write your application code using the *Collection type. You can easily
// reconfigure your initialization code to choose a different driver.
// You can develop your application locally using memdocstore, or deploy it to
// multiple Cloud providers. You may find http://github.com/google/wire useful
// for managing your initialization code.
//
// Alternatively, you can construct a *Collection via a URL and the
// OpenCollection function. See https://gocloud.dev/concepts/urls for more
// information.
//
//
// Representing Documents
//
// A document is a set of named fields, each with a value. A field's value can be a scalar,
// a list, or a nested document.
//
// Docstore allows you to represent documents as either map[string]interface{} or
// struct pointers. When you represent a document as a map, the fields are map keys
// and the values are map values. Lists are represented with slices. For example,
// here is a document about a book described as a map:
//
//   doc := map[string]interface{}{
//       "Title": "The Master and Margarita",
//       "Author": map[string]interface{}{
//           "First": "Mikhail",
//           "Last": "Bulgakov",
//       },
//       "PublicationYears": []int{1967, 1973},
//   }
//
// Note that the value of "PublicationYears" is a list, and the value of "Author" is
// itself a document.
//
// Here is the same document represented with structs:
//
//   type Book struct {
//       Title            string
//       Author           Name
//       PublicationYears []int
//   }
//
//   type Name struct {
//       First, Last string
//   }
//
//   doc := &Book{
//       Title: "The Master and Margarita",
//       Author: Name{
//           First: "Mikhail",
//           Last:  "Bulgakov",
//       },
//       PublicationYears: []int{1967, 1973},
//   }
//
// You must use a pointer to a struct to represent a document, although structs
// nested inside a document, like the Name struct above, need not be pointers.
//
// Maps are best for applications where you don't know the structure of the
// documents. Using structs is preferred because it enforces some structure on your
// data.
//
// Docstore mimics the encoding/json package in its treatment of
// structs: by default, a struct's exported fields are the fields of the document.
// You can alter this default mapping by using a struct tag beginning with
// "docstore:". Docstore struct tags support renaming, omitting fields
// unconditionally, or omitting them only when they are empty, exactly like
// encoding/json. Docstore also honors a "json" struct tag if there is no "docstore"
// tag on the field. For example, this is the Book struct with different field names:
//
//   type Book struct {
//       Title            string `docstore:"title"`
//       Author           Name   `docstore:"author"`
//       PublicationYears []int  `docstore:"pub_years,omitempty"`
//       NumPublications  int    `docstore:"-"`
//   }
//
// This struct describes a document with field names "title", "author" and
// "pub_years". The pub_years field is omitted from the stored document if it has
// length zero. The NumPublications field is never stored because it can easily be
// computed from the PublicationYears field.
//
//
// Representing Data
//
// Values stored in document fields can be any of a wide range of types. All
// primitive types except for complex numbers are supported, as well as slices and
// maps (the map key type must be a string, an integer, or a type that implements
// encoding.TextMarshaler). In addition, any type that implements
// encoding.BinaryMarshaler or encoding.TextMarshaler is permitted. This set of types
// closely matches the encoding/json package (see https://golang.org/pkg/encoding/json).
//
// Times deserve special mention. Docstore can store and retrieve values of type
// time.Time, with two caveats. First, the timezone will not be preserved. Second,
// Docstore guarantees only that time.Time values are represented to millisecond
// precision. Many providers will do better, but if you need to be sure that times
// are stored with nanosecond precision, convert the time.Time to another type before
// storing and re-create when you retrieve it. For instance, if you store Unix
// time in nanoseconds using time's UnixNano method, you can get the original
// time back (in the local timezone) with the time.Unix function.
//
//
// Representing Keys
//
// The key of a docstore document is some function of its contents, usually a field.
// Keys never appear alone in the docstore API, only as part of a document. For
// instance, to retrieve a document by key, you pass the Collection.Get method
// a document as a struct pointer or map with the key field populated, and docstore
// populates the rest of that argument with the stored contents.
//
// When you open a collection using the OpenCollection method of the service-specific driver or
// a URL, you specify how to extract the key from a document.
// Usually, you provide the name of the key field, as in the example below:
//
//   coll, err := memdocstore.OpenCollection("SSN", nil)
//
// Here, the "SSN" field of the document is used as the key. Some drivers let you
// supply a function to extract the key from the document, which can be useful if the
// key is composed of more than one field.
//
//
// Actions
//
// Docstore supports six actions on documents as methods on the Collection type:
//   - Get retrieves a document.
//   - Create creates a new document.
//   - Replace replaces an existing document.
//   - Put puts a document into a collection, replacing it if it is already present.
//   - Update applies a set of modifications to a document.
//   - Delete deletes a document.
//
// Each action acts atomically on a single document. You can execute actions
// individually or you can group them into an action list, like so:
//
//   err := coll.Actions().Put(doc1).Replace(doc2).Get(doc3).Do(ctx)
//
// When you use an action list, docstore will try to optimize the execution of the
// actions. For example, multiple Get actions may be combined into a single "batch
// get" RPC. For the most part, actions in a list execute in an undefined order
// (perhaps concurrently) and independently, but read and write operations on the same
// document are executed in the user-specified order. See the documentation of
// ActionList for details.
//
//
// Revisions
//
// Docstore supports document revisions to distinguish different versions of a
// document and enable optimistic locking. By default, Docstore stores the revision
// in the field named "DocstoreRevision" (stored in the constant
// DefaultRevisionField). Providers give you the option of changing that field
// name.
//
// Docstore gives every document a revision at creation time and changes that
// revision whenever the document is modified. If modification methods are called
// on a document struct or map without a revision field, then no revision
// checks are performed, and changes are forced blindly. Otherwise, whenever
// you pass a document with a revision to Put, Replace, Update or Delete,
// docstore will compare the revision of the stored document to that of the
// given document and return an error with code FailedPrecondition on mismatch.
// (See https://gocloud.dev/gcerrors for information about error codes.)
// If you call Get to retrieve a document with a revision, then later perform a
// write action with that same document, it will fail if the document was changed
// since the Get. Create, Put, Replace and Update all populate the revision
// field of the argument document with the new revision value.
//
// Since different services use different types for revisions, revision fields
// of unspecified type must be handled. When defining a document struct,
// define the field to be of type interface{}. For example,
//
//   type User {
//       Name             string
//       DocstoreRevision interface{}
//   }
//
//
// Queries
//
// Docstore supports querying within a collection. Call the Query method on
// Collection to obtain a Query value, then build your query by calling Query methods
// like Where, Limit and so on. Finally, call the Get method on the query to execute it.
// The result is an iterator, whose use is described below.
//
//   iter := coll.Query().Where("size", ">", 10).Limit(5).Get(ctx)
//
// The Where method defines a filter condition, much like a WHERE clause in SQL.
// Conditions are of the form "field op value", where field is any document field
// path (including dot-separated paths), op is one of "=", ">", "<", ">=" or "<=",
// and value can be any value.
//
//   iter := coll.Query().Where("Author.Last", "=", "Bulgakov").Limit(3).Get(ctx)
//
// You can make multiple Where calls. In some cases, parts of a Where clause may be
// processed on the client rather than natively by the provider, which may have
// performance implications for large result sets. See the provider-specific package
// documentation for details.
//
// Use the DocumentIterator returned from Query.Get by repeatedly calling its Next
// method until it returns io.EOF. Always call Stop when you are finished with an
// iterator. It is wise to use a defer statement for this.
//
//   iter := coll.Query().Where("size", ">", 10).Limit(5).Get(ctx)
//   defer iter.Stop()
//   for {
//       m := map[string]interface{}{}
//       err := iter.Next(ctx, m)
//       if err == io.EOF {
//           break
//       }
//       if err != nil {
//           return err
//       }
//       fmt.Println(m)
//   }
//
//
// Errors
//
// The errors returned from this package can be inspected in several ways:
//
// The Code function from https://gocloud.dev/gcerrors will return an error code, also
// defined in that package, when invoked on an error.
//
// The Collection.ErrorAs method can retrieve the underlying driver error from
// the returned error. See the specific driver's package doc for the supported
// types.
//
//
// OpenCensus Integration
//
// TODO(jba): implement and document
//
package docstore // import "gocloud.dev/docstore"
<commit_msg>docstore: document decoder does case-sensitive matching (#2559)<commit_after>// Copyright 2019 The Go Cloud Development Kit 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
//
//     https://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 docstore provides a portable way of interacting with a document store.
// A document store is a service that stores data in semi-structured JSON-like
// documents. Like other NoSQL databases, document stores are schemaless.
// See https://en.wikipedia.org/wiki/Document-oriented_database for more information.
//
// Subpackages contain distinct implementations ("drivers") of docstore for public
// cloud services like Google Cloud Firestore and Amazon DynamoDB as well as any MongoDB
// or MongoDB-compatible service hosted by a cloud provider or on-premise. For example, you can
// use Azure Cosmos DB and Amazon DocumentDB via the MongoDB driver. There is also
// memdocstore, an in-process, in-memory implementation suitable for testing and
// development.
//
// In docstore, documents are grouped into collections, and each document has a key
// that is unique in its collection. You can add, retrieve, modify and delete
// documents by key, and you can query a collection to retrieve documents that match
// certain criteria.
//
// Your application should import one of the service-specific subpackages and use
// its exported functions to create a *Collection; do not use the NewCollection
// function in this package. For example:
//
//   coll, err := memdocstore.OpenCollection("SSN", nil)
//   if err != nil {
//       return fmt.Errorf("opening collection: %v", err)
//   }
//   defer coll.Close()
//   // coll is a *docstore.Collection
//
// Then, write your application code using the *Collection type. You can easily
// reconfigure your initialization code to choose a different driver.
// You can develop your application locally using memdocstore, or deploy it to
// multiple Cloud providers. You may find http://github.com/google/wire useful
// for managing your initialization code.
//
// Alternatively, you can construct a *Collection via a URL and the
// OpenCollection function. See https://gocloud.dev/concepts/urls for more
// information.
//
//
// Representing Documents
//
// A document is a set of named fields, each with a value. A field's value can be a scalar,
// a list, or a nested document.
//
// Docstore allows you to represent documents as either map[string]interface{} or
// struct pointers. When you represent a document as a map, the fields are map keys
// and the values are map values. Lists are represented with slices. For example,
// here is a document about a book described as a map:
//
//   doc := map[string]interface{}{
//       "Title": "The Master and Margarita",
//       "Author": map[string]interface{}{
//           "First": "Mikhail",
//           "Last": "Bulgakov",
//       },
//       "PublicationYears": []int{1967, 1973},
//   }
//
// Note that the value of "PublicationYears" is a list, and the value of "Author" is
// itself a document.
//
// Here is the same document represented with structs:
//
//   type Book struct {
//       Title            string
//       Author           Name
//       PublicationYears []int
//   }
//
//   type Name struct {
//       First, Last string
//   }
//
//   doc := &Book{
//       Title: "The Master and Margarita",
//       Author: Name{
//           First: "Mikhail",
//           Last:  "Bulgakov",
//       },
//       PublicationYears: []int{1967, 1973},
//   }
//
// You must use a pointer to a struct to represent a document, although structs
// nested inside a document, like the Name struct above, need not be pointers.
//
// Maps are best for applications where you don't know the structure of the
// documents. Using structs is preferred because it enforces some structure on your
// data.
//
// By default, Docstore treats a struct's exported fields as the fields of the
// document. You can alter this default mapping by using a struct tag beginning
// with "docstore:". Docstore struct tags support renaming, omitting fields
// unconditionally, or omitting them only when they are empty, exactly like
// encoding/json. For example, this is the Book struct with different field
// names:
//
//   type Book struct {
//       Title            string `docstore:"title"`
//       Author           Name   `docstore:"author"`
//       PublicationYears []int  `docstore:"pub_years,omitempty"`
//       NumPublications  int    `docstore:"-"`
//   }
//
// This struct describes a document with field names "title", "author" and
// "pub_years". The pub_years field is omitted from the stored document if it has
// length zero. The NumPublications field is never stored because it can easily be
// computed from the PublicationYears field.
//
// Given a document field "Foo" and a struct type document, Docstore's decoder
// will look through the destination struct's field to find (in order of
// preference):
//   - An exported field with a tag of "Foo";
//   - An exported field named "Foo".
//
// Note that unlike encoding/json, Docstore does case-sensitive matching during
// decoding to match the behavior of decoders in most docstore services.
//
//
// Representing Data
//
// Values stored in document fields can be any of a wide range of types. All
// primitive types except for complex numbers are supported, as well as slices and
// maps (the map key type must be a string, an integer, or a type that implements
// encoding.TextMarshaler). In addition, any type that implements
// encoding.BinaryMarshaler or encoding.TextMarshaler is permitted. This set of types
// closely matches the encoding/json package (see https://golang.org/pkg/encoding/json).
//
// Times deserve special mention. Docstore can store and retrieve values of type
// time.Time, with two caveats. First, the timezone will not be preserved. Second,
// Docstore guarantees only that time.Time values are represented to millisecond
// precision. Many providers will do better, but if you need to be sure that times
// are stored with nanosecond precision, convert the time.Time to another type before
// storing and re-create when you retrieve it. For instance, if you store Unix
// time in nanoseconds using time's UnixNano method, you can get the original
// time back (in the local timezone) with the time.Unix function.
//
//
// Representing Keys
//
// The key of a docstore document is some function of its contents, usually a field.
// Keys never appear alone in the docstore API, only as part of a document. For
// instance, to retrieve a document by key, you pass the Collection.Get method
// a document as a struct pointer or map with the key field populated, and docstore
// populates the rest of that argument with the stored contents.
//
// When you open a collection using the OpenCollection method of the service-specific driver or
// a URL, you specify how to extract the key from a document.
// Usually, you provide the name of the key field, as in the example below:
//
//   coll, err := memdocstore.OpenCollection("SSN", nil)
//
// Here, the "SSN" field of the document is used as the key. Some drivers let you
// supply a function to extract the key from the document, which can be useful if the
// key is composed of more than one field.
//
//
// Actions
//
// Docstore supports six actions on documents as methods on the Collection type:
//   - Get retrieves a document.
//   - Create creates a new document.
//   - Replace replaces an existing document.
//   - Put puts a document into a collection, replacing it if it is already present.
//   - Update applies a set of modifications to a document.
//   - Delete deletes a document.
//
// Each action acts atomically on a single document. You can execute actions
// individually or you can group them into an action list, like so:
//
//   err := coll.Actions().Put(doc1).Replace(doc2).Get(doc3).Do(ctx)
//
// When you use an action list, docstore will try to optimize the execution of the
// actions. For example, multiple Get actions may be combined into a single "batch
// get" RPC. For the most part, actions in a list execute in an undefined order
// (perhaps concurrently) and independently, but read and write operations on the same
// document are executed in the user-specified order. See the documentation of
// ActionList for details.
//
//
// Revisions
//
// Docstore supports document revisions to distinguish different versions of a
// document and enable optimistic locking. By default, Docstore stores the revision
// in the field named "DocstoreRevision" (stored in the constant
// DefaultRevisionField). Providers give you the option of changing that field
// name.
//
// Docstore gives every document a revision at creation time and changes that
// revision whenever the document is modified. If modification methods are called
// on a document struct or map without a revision field, then no revision
// checks are performed, and changes are forced blindly. Otherwise, whenever
// you pass a document with a revision to Put, Replace, Update or Delete,
// docstore will compare the revision of the stored document to that of the
// given document and return an error with code FailedPrecondition on mismatch.
// (See https://gocloud.dev/gcerrors for information about error codes.)
// If you call Get to retrieve a document with a revision, then later perform a
// write action with that same document, it will fail if the document was changed
// since the Get. Create, Put, Replace and Update all populate the revision
// field of the argument document with the new revision value.
//
// Since different services use different types for revisions, revision fields
// of unspecified type must be handled. When defining a document struct,
// define the field to be of type interface{}. For example,
//
//   type User {
//       Name             string
//       DocstoreRevision interface{}
//   }
//
//
// Queries
//
// Docstore supports querying within a collection. Call the Query method on
// Collection to obtain a Query value, then build your query by calling Query methods
// like Where, Limit and so on. Finally, call the Get method on the query to execute it.
// The result is an iterator, whose use is described below.
//
//   iter := coll.Query().Where("size", ">", 10).Limit(5).Get(ctx)
//
// The Where method defines a filter condition, much like a WHERE clause in SQL.
// Conditions are of the form "field op value", where field is any document field
// path (including dot-separated paths), op is one of "=", ">", "<", ">=" or "<=",
// and value can be any value.
//
//   iter := coll.Query().Where("Author.Last", "=", "Bulgakov").Limit(3).Get(ctx)
//
// You can make multiple Where calls. In some cases, parts of a Where clause may be
// processed on the client rather than natively by the provider, which may have
// performance implications for large result sets. See the provider-specific package
// documentation for details.
//
// Use the DocumentIterator returned from Query.Get by repeatedly calling its Next
// method until it returns io.EOF. Always call Stop when you are finished with an
// iterator. It is wise to use a defer statement for this.
//
//   iter := coll.Query().Where("size", ">", 10).Limit(5).Get(ctx)
//   defer iter.Stop()
//   for {
//       m := map[string]interface{}{}
//       err := iter.Next(ctx, m)
//       if err == io.EOF {
//           break
//       }
//       if err != nil {
//           return err
//       }
//       fmt.Println(m)
//   }
//
//
// Errors
//
// The errors returned from this package can be inspected in several ways:
//
// The Code function from https://gocloud.dev/gcerrors will return an error code, also
// defined in that package, when invoked on an error.
//
// The Collection.ErrorAs method can retrieve the underlying driver error from
// the returned error. See the specific driver's package doc for the supported
// types.
//
//
// OpenCensus Integration
//
// TODO(jba): implement and document
//
package docstore // import "gocloud.dev/docstore"
<|endoftext|>


<commit_before>package parser

import (
	"fmt"
	"html"
	"log"
	"net/url"
	"strings"
	"sync"

	textapi "github.com/AYLIEN/aylien_textapi_go"
)

const (
	// AylienAPIMaxConcurrentCount maximum concurrent api request count for aylien
	AylienAPIMaxConcurrentCount = 5
)

// Analyzer interface to provider analyze function for articles
type Analyzer interface {
	Analyze([]Article)
}

// AylienAnalyzer analyzer using aylien api
type AylienAnalyzer struct {
	AppID      string
	AppKey     string
	Client     *textapi.Client
	APIChannel chan struct{}
}

func (ay *AylienAnalyzer) init() error {
	auth := textapi.Auth{ay.AppID, ay.AppKey}
	client, err := textapi.NewClient(auth, true)
	if err != nil {
		log.Printf("could not create aylien api client, %v", err)
		return err
	}
	ay.Client = client
	ay.APIChannel = make(chan struct{}, AylienAPIMaxConcurrentCount)
	for i := 0; i < AylienAPIMaxConcurrentCount; i++ {
		ay.APIChannel <- struct{}{}
	}
	return nil
}

// Analyze use aylien api to extract each article and get summary of them
func (ay *AylienAnalyzer) Analyze(articles []Article) {
	if len(articles) == 0 {
		return
	}

	totalCount := len(articles)

	wg := sync.WaitGroup{}
	wg.Add(totalCount)

	for i := 0; i < totalCount; i++ {
		<-ay.APIChannel

		go func(index int) {
			//not analyze full article now
			//analyze(ay.Client, &articles[index])
			summarize(ay.Client, &articles[index])
			articles[index].Author = getHostname(articles[index].Link)

			ay.APIChannel <- struct{}{}
			wg.Done()
		}(i)
	}

	wg.Wait()
}

func analyze(client *textapi.Client, article *Article) {
	log.Printf("use aylien extract api to analyze article")

	extractParams := &textapi.ExtractParams{
		URL:       article.Link,
		BestImage: true,
	}

	result, err := client.Extract(extractParams)
	if err != nil {
		log.Printf("got error while calling aylien api: %v", err)
		return
	}

	article.Author = result.Author
	article.Image = result.Image
	article.PublishDate = result.PublishDate.Time
	article.Videos = result.Videos[:]
	article.Content = makeContent(article.Link, article.Image, result.Article)
}

func getHostname(link string) string {
	host := ""
	linkURL, err := url.Parse(link)
	if err != nil {
		log.Printf("failed to parse title url %s: %v", link, err)
	} else {
		host = linkURL.Hostname()
	}
	return host
}

func makeContent(link, image, contents string) string {

	imageLink := fmt.Sprintf("
", image)
	contents = strings.Replace(contents, "\r\n", "\n", -1)
	contents = strings.Replace(contents, "\n", "
", -1)
	contents = imageLink + contents

	return html.EscapeString(contents)
}

func summarize(client *textapi.Client, article *Article) {
	log.Printf("getting summary for article: %s", article.Title)
	summarizeParams := &textapi.SummarizeParams{
		URL:               article.Link,
		NumberOfSentences: 5,
		Title:             article.Title,
	}
	summary, err := client.Summarize(summarizeParams)
	if err != nil {
		log.Printf("failed to get summary, %v", err)
		return
	}
	if len(summary.Sentences) != 0 {
		article.Summary = article.Summary + "\n\n" + strings.Join(summary.Sentences, "\n")
	}
}

// NewAylienAnalyzer create an aylien analyzer
func NewAylienAnalyzer(appid, appkey string) Analyzer {
	analyzer := &AylienAnalyzer{AppID: appid, AppKey: appkey}
	err := analyzer.init()

	if err != nil {
		log.Printf("failed to create aylien api client")
		return nil
	}

	return analyzer
}
<commit_msg>remove summary<commit_after>package parser

import (
	"fmt"
	"html"
	"log"
	"net/url"
	"strings"
	"sync"

	textapi "github.com/AYLIEN/aylien_textapi_go"
)

const (
	// AylienAPIMaxConcurrentCount maximum concurrent api request count for aylien
	AylienAPIMaxConcurrentCount = 5
)

// Analyzer interface to provider analyze function for articles
type Analyzer interface {
	Analyze([]Article)
}

// AylienAnalyzer analyzer using aylien api
type AylienAnalyzer struct {
	AppID      string
	AppKey     string
	Client     *textapi.Client
	APIChannel chan struct{}
}

func (ay *AylienAnalyzer) init() error {
	auth := textapi.Auth{ay.AppID, ay.AppKey}
	client, err := textapi.NewClient(auth, true)
	if err != nil {
		log.Printf("could not create aylien api client, %v", err)
		return err
	}
	ay.Client = client
	ay.APIChannel = make(chan struct{}, AylienAPIMaxConcurrentCount)
	for i := 0; i < AylienAPIMaxConcurrentCount; i++ {
		ay.APIChannel <- struct{}{}
	}
	return nil
}

// Analyze use aylien api to extract each article and get summary of them
func (ay *AylienAnalyzer) Analyze(articles []Article) {
	if len(articles) == 0 {
		return
	}

	totalCount := len(articles)

	wg := sync.WaitGroup{}
	wg.Add(totalCount)

	for i := 0; i < totalCount; i++ {
		<-ay.APIChannel

		go func(index int) {
			//not analyze full article now
			//analyze(ay.Client, &articles[index])
			summarize(ay.Client, &articles[index])
			articles[index].Author = getHostname(articles[index].Link)

			ay.APIChannel <- struct{}{}
			wg.Done()
		}(i)
	}

	wg.Wait()
}

func analyze(client *textapi.Client, article *Article) {
	log.Printf("use aylien extract api to analyze article")

	extractParams := &textapi.ExtractParams{
		URL:       article.Link,
		BestImage: true,
	}

	result, err := client.Extract(extractParams)
	if err != nil {
		log.Printf("got error while calling aylien api: %v", err)
		return
	}

	article.Author = result.Author
	article.Image = result.Image
	article.PublishDate = result.PublishDate.Time
	article.Videos = result.Videos[:]
	article.Content = makeContent(article.Link, article.Image, result.Article)
}

func getHostname(link string) string {
	host := ""
	linkURL, err := url.Parse(link)
	if err != nil {
		log.Printf("failed to parse title url %s: %v", link, err)
	} else {
		host = linkURL.Hostname()
	}
	return host
}

func makeContent(link, image, contents string) string {

	imageLink := fmt.Sprintf("
", image)
	contents = strings.Replace(contents, "\r\n", "\n", -1)
	contents = strings.Replace(contents, "\n", "
", -1)
	contents = imageLink + contents

	return html.EscapeString(contents)
}

func summarize(client *textapi.Client, article *Article) {
	log.Printf("getting summary for article: %s", article.Title)
	summarizeParams := &textapi.SummarizeParams{
		URL:               article.Link,
		NumberOfSentences: 5,
		Title:             article.Title,
	}
	summary, err := client.Summarize(summarizeParams)
	if err != nil {
		log.Printf("failed to get summary, %v", err)
		return
	}
	if len(summary.Sentences) != 0 {
		article.Summary = strings.Join(summary.Sentences, "\n")
	}
}

// NewAylienAnalyzer create an aylien analyzer
func NewAylienAnalyzer(appid, appkey string) Analyzer {
	analyzer := &AylienAnalyzer{AppID: appid, AppKey: appkey}
	err := analyzer.init()

	if err != nil {
		log.Printf("failed to create aylien api client")
		return nil
	}

	return analyzer
}
<|endoftext|>


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

package net

import (
	"io"
	"os"
	"syscall"
	"time"
)

// Network file descriptor.
type netFD struct {
	// locking/lifetime of sysfd + serialize access to Read and Write methods
	fdmu fdMutex

	// immutable until Close
	net          string
	n            string
	dir          string
	ctl, data    *os.File
	laddr, raddr Addr
}

var (
	netdir string // default network
)

func sysInit() {
	netdir = "/net"
}

func newFD(net, name string, ctl, data *os.File, laddr, raddr Addr) (*netFD, error) {
	return &netFD{net: net, n: name, dir: netdir + "/" + net + "/" + name, ctl: ctl, data: data, laddr: laddr, raddr: raddr}, nil
}

func (fd *netFD) init() error {
	// stub for future fd.pd.Init(fd)
	return nil
}

func (fd *netFD) name() string {
	var ls, rs string
	if fd.laddr != nil {
		ls = fd.laddr.String()
	}
	if fd.raddr != nil {
		rs = fd.raddr.String()
	}
	return fd.net + ":" + ls + "->" + rs
}

func (fd *netFD) ok() bool { return fd != nil && fd.ctl != nil }

func (fd *netFD) destroy() {
	if !fd.ok() {
		return
	}
	err := fd.ctl.Close()
	if fd.data != nil {
		if err1 := fd.data.Close(); err1 != nil && err == nil {
			err = err1
		}
	}
	fd.ctl = nil
	fd.data = nil
}

func (fd *netFD) Read(b []byte) (n int, err error) {
	if !fd.ok() || fd.data == nil {
		return 0, syscall.EINVAL
	}
	if err := fd.readLock(); err != nil {
		return 0, err
	}
	defer fd.readUnlock()
	n, err = fd.data.Read(b)
	if fd.net == "udp" && err == io.EOF {
		n = 0
		err = nil
	}
	return
}

func (fd *netFD) Write(b []byte) (n int, err error) {
	if !fd.ok() || fd.data == nil {
		return 0, syscall.EINVAL
	}
	if err := fd.writeLock(); err != nil {
		return 0, err
	}
	defer fd.writeUnlock()
	return fd.data.Write(b)
}

func (fd *netFD) closeRead() error {
	if !fd.ok() {
		return syscall.EINVAL
	}
	return syscall.EPLAN9
}

func (fd *netFD) closeWrite() error {
	if !fd.ok() {
		return syscall.EINVAL
	}
	return syscall.EPLAN9
}

func (fd *netFD) Close() error {
	if !fd.fdmu.increfAndClose() {
		return errClosing
	}
	if !fd.ok() {
		return syscall.EINVAL
	}
	if fd.net == "tcp" {
		// The following line is required to unblock Reads.
		// For some reason, WriteString returns an error:
		// "write /net/tcp/39/listen: inappropriate use of fd"
		// But without it, Reads on dead conns hang forever.
		// See Issue 9554.
		fd.ctl.WriteString("hangup")
	}
	err := fd.ctl.Close()
	if fd.data != nil {
		if err1 := fd.data.Close(); err1 != nil && err == nil {
			err = err1
		}
	}
	fd.ctl = nil
	fd.data = nil
	return err
}

// This method is only called via Conn.
func (fd *netFD) dup() (*os.File, error) {
	if !fd.ok() || fd.data == nil {
		return nil, syscall.EINVAL
	}
	return fd.file(fd.data, fd.dir+"/data")
}

func (l *TCPListener) dup() (*os.File, error) {
	if !l.fd.ok() {
		return nil, syscall.EINVAL
	}
	return l.fd.file(l.fd.ctl, l.fd.dir+"/ctl")
}

func (fd *netFD) file(f *os.File, s string) (*os.File, error) {
	syscall.ForkLock.RLock()
	dfd, err := syscall.Dup(int(f.Fd()), -1)
	syscall.ForkLock.RUnlock()
	if err != nil {
		return nil, os.NewSyscallError("dup", err)
	}
	return os.NewFile(uintptr(dfd), s), nil
}

func (fd *netFD) setDeadline(t time.Time) error {
	return syscall.EPLAN9
}

func (fd *netFD) setReadDeadline(t time.Time) error {
	return syscall.EPLAN9
}

func (fd *netFD) setWriteDeadline(t time.Time) error {
	return syscall.EPLAN9
}

func setReadBuffer(fd *netFD, bytes int) error {
	return syscall.EPLAN9
}

func setWriteBuffer(fd *netFD, bytes int) error {
	return syscall.EPLAN9
}
<commit_msg>net: handle hangup in read on Plan 9<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

import (
	"io"
	"os"
	"syscall"
	"time"
)

// Network file descriptor.
type netFD struct {
	// locking/lifetime of sysfd + serialize access to Read and Write methods
	fdmu fdMutex

	// immutable until Close
	net          string
	n            string
	dir          string
	ctl, data    *os.File
	laddr, raddr Addr
}

var (
	netdir string // default network
)

func sysInit() {
	netdir = "/net"
}

func newFD(net, name string, ctl, data *os.File, laddr, raddr Addr) (*netFD, error) {
	return &netFD{net: net, n: name, dir: netdir + "/" + net + "/" + name, ctl: ctl, data: data, laddr: laddr, raddr: raddr}, nil
}

func (fd *netFD) init() error {
	// stub for future fd.pd.Init(fd)
	return nil
}

func (fd *netFD) name() string {
	var ls, rs string
	if fd.laddr != nil {
		ls = fd.laddr.String()
	}
	if fd.raddr != nil {
		rs = fd.raddr.String()
	}
	return fd.net + ":" + ls + "->" + rs
}

func (fd *netFD) ok() bool { return fd != nil && fd.ctl != nil }

func (fd *netFD) destroy() {
	if !fd.ok() {
		return
	}
	err := fd.ctl.Close()
	if fd.data != nil {
		if err1 := fd.data.Close(); err1 != nil && err == nil {
			err = err1
		}
	}
	fd.ctl = nil
	fd.data = nil
}

func (fd *netFD) Read(b []byte) (n int, err error) {
	if !fd.ok() || fd.data == nil {
		return 0, syscall.EINVAL
	}
	if err := fd.readLock(); err != nil {
		return 0, err
	}
	defer fd.readUnlock()
	n, err = fd.data.Read(b)
	if isHangup(err) {
		err = io.EOF
	}
	if fd.net == "udp" && err == io.EOF {
		n = 0
		err = nil
	}
	return
}

func (fd *netFD) Write(b []byte) (n int, err error) {
	if !fd.ok() || fd.data == nil {
		return 0, syscall.EINVAL
	}
	if err := fd.writeLock(); err != nil {
		return 0, err
	}
	defer fd.writeUnlock()
	return fd.data.Write(b)
}

func (fd *netFD) closeRead() error {
	if !fd.ok() {
		return syscall.EINVAL
	}
	return syscall.EPLAN9
}

func (fd *netFD) closeWrite() error {
	if !fd.ok() {
		return syscall.EINVAL
	}
	return syscall.EPLAN9
}

func (fd *netFD) Close() error {
	if !fd.fdmu.increfAndClose() {
		return errClosing
	}
	if !fd.ok() {
		return syscall.EINVAL
	}
	if fd.net == "tcp" {
		// The following line is required to unblock Reads.
		// For some reason, WriteString returns an error:
		// "write /net/tcp/39/listen: inappropriate use of fd"
		// But without it, Reads on dead conns hang forever.
		// See Issue 9554.
		fd.ctl.WriteString("hangup")
	}
	err := fd.ctl.Close()
	if fd.data != nil {
		if err1 := fd.data.Close(); err1 != nil && err == nil {
			err = err1
		}
	}
	fd.ctl = nil
	fd.data = nil
	return err
}

// This method is only called via Conn.
func (fd *netFD) dup() (*os.File, error) {
	if !fd.ok() || fd.data == nil {
		return nil, syscall.EINVAL
	}
	return fd.file(fd.data, fd.dir+"/data")
}

func (l *TCPListener) dup() (*os.File, error) {
	if !l.fd.ok() {
		return nil, syscall.EINVAL
	}
	return l.fd.file(l.fd.ctl, l.fd.dir+"/ctl")
}

func (fd *netFD) file(f *os.File, s string) (*os.File, error) {
	syscall.ForkLock.RLock()
	dfd, err := syscall.Dup(int(f.Fd()), -1)
	syscall.ForkLock.RUnlock()
	if err != nil {
		return nil, os.NewSyscallError("dup", err)
	}
	return os.NewFile(uintptr(dfd), s), nil
}

func (fd *netFD) setDeadline(t time.Time) error {
	return syscall.EPLAN9
}

func (fd *netFD) setReadDeadline(t time.Time) error {
	return syscall.EPLAN9
}

func (fd *netFD) setWriteDeadline(t time.Time) error {
	return syscall.EPLAN9
}

func setReadBuffer(fd *netFD, bytes int) error {
	return syscall.EPLAN9
}

func setWriteBuffer(fd *netFD, bytes int) error {
	return syscall.EPLAN9
}

func isHangup(err error) bool {
	return err != nil && stringsHasSuffix(err.Error(), "Hangup")
}
<|endoftext|>


<commit_before>package ams

import (
	"context"
	"net/http"
	"net/url"
	"path"
	"time"

	"github.com/pkg/errors"
)

const (
	locatorsEndpoint = "Locators"
)

const (
	LocatorNone = iota
	LocatorSAS
	LocatorOnDemandOrigin
)

type Locator struct {
	ID                     string `json:"Id"`
	ExpirationDateTime     string `json:"ExpirationDateTime"`
	Type                   int    `json:"Type"`
	Path                   string `json:"Path"`
	BaseURI                string `json:"BaseUri"`
	ContentAccessComponent string `json:"ContentAccessComponent"`
	AccessPolicyID         string `json:"AccessPolicyId"`
	AssetID                string `json:"AssetID"`
	StartTime              string `json:"StartTime"`
	Name                   string `json:"Name"`
}

func (l *Locator) ToUploadURL(name string) (*url.URL, error) {
	uploadURL, err := url.ParseRequestURI(l.Path)
	if err != nil {
		return nil, errors.Wrapf(err, "parse url failed: %s", l.Path)
	}
	uploadURL.Path = path.Join(uploadURL.Path, name)
	return uploadURL, nil
}

func (c *Client) CreateLocator(ctx context.Context, accessPolicyID, assetID string, startTime time.Time, locatorType int) (*Locator, error) {
	params := map[string]interface{}{
		"AccessPolicyId": accessPolicyID,
		"AssetId":        assetID,
		"StartTime":      formatTime(startTime),
		"Type":           locatorType,
	}
	req, err := c.newRequest(ctx, http.MethodPost, locatorsEndpoint, withJSON(params))
	if err != nil {
		return nil, errors.Wrap(err, "request build failed")
	}

	c.logger.Printf("[INFO] create locator ...")
	var out Locator
	if err := c.do(req, http.StatusCreated, &out); err != nil {
		return nil, errors.Wrap(err, "request failed")
	}
	c.logger.Printf("[INFO] completed, new locator[#%s]", out.ID)
	return &out, nil
}

func (c *Client) DeleteLocator(ctx context.Context, locatorID string) error {
	endpoint := toLocatorResource(locatorID)
	req, err := c.newRequest(ctx, http.MethodDelete, endpoint)
	if err != nil {
		return errors.Wrap(err, "request build failed")
	}
	c.logger.Printf("[INFO] delete locator #%s ...", locatorID)
	if err := c.do(req, http.StatusNoContent, nil); err != nil {
		return errors.Wrap(err, "request failed")
	}
	c.logger.Printf("[INFO] completed")
	return nil
}

func (c *Client) getLocators(ctx context.Context, endpoint string) ([]Locator, error) {
	req, err := c.newRequest(ctx, http.MethodGet, endpoint)
	if err != nil {
		return nil, errors.Wrap(err, "request build failed")
	}
	var out struct {
		Locators []Locator `json:"value"`
	}
	if err := c.do(req, http.StatusOK, &out); err != nil {
		return nil, errors.Wrap(err, "request failed")
	}
	return out.Locators, nil
}

func (c *Client) GetLocators(ctx context.Context) ([]Locator, error) {
	return c.getLocators(ctx, locatorsEndpoint)
}

func (c *Client) GetLocatorsWithAsset(ctx context.Context, assetID string) ([]Locator, error) {
	endpoint := path.Join(assetsEndpoint, toAssetResource(assetID), locatorsEndpoint)
	return c.getLocators(ctx, endpoint)
}

func toLocatorResource(locatorID string) string {
	return toResource(locatorsEndpoint, locatorID)
}
<commit_msg>fix: access invalid path<commit_after>package ams

import (
	"context"
	"net/http"
	"net/url"
	"path"
	"time"

	"github.com/pkg/errors"
)

const (
	locatorsEndpoint = "Locators"
)

const (
	LocatorNone = iota
	LocatorSAS
	LocatorOnDemandOrigin
)

type Locator struct {
	ID                     string `json:"Id"`
	ExpirationDateTime     string `json:"ExpirationDateTime"`
	Type                   int    `json:"Type"`
	Path                   string `json:"Path"`
	BaseURI                string `json:"BaseUri"`
	ContentAccessComponent string `json:"ContentAccessComponent"`
	AccessPolicyID         string `json:"AccessPolicyId"`
	AssetID                string `json:"AssetID"`
	StartTime              string `json:"StartTime"`
	Name                   string `json:"Name"`
}

func (l *Locator) ToUploadURL(name string) (*url.URL, error) {
	uploadURL, err := url.ParseRequestURI(l.Path)
	if err != nil {
		return nil, errors.Wrapf(err, "parse url failed: %s", l.Path)
	}
	uploadURL.Path = path.Join(uploadURL.Path, name)
	return uploadURL, nil
}

func (c *Client) CreateLocator(ctx context.Context, accessPolicyID, assetID string, startTime time.Time, locatorType int) (*Locator, error) {
	params := map[string]interface{}{
		"AccessPolicyId": accessPolicyID,
		"AssetId":        assetID,
		"StartTime":      formatTime(startTime),
		"Type":           locatorType,
	}
	req, err := c.newRequest(ctx, http.MethodPost, locatorsEndpoint, withJSON(params))
	if err != nil {
		return nil, errors.Wrap(err, "request build failed")
	}

	c.logger.Printf("[INFO] create locator ...")
	var out Locator
	if err := c.do(req, http.StatusCreated, &out); err != nil {
		return nil, errors.Wrap(err, "request failed")
	}
	c.logger.Printf("[INFO] completed, new locator[#%s]", out.ID)
	return &out, nil
}

func (c *Client) DeleteLocator(ctx context.Context, locatorID string) error {
	endpoint := toLocatorResource(locatorID)
	req, err := c.newRequest(ctx, http.MethodDelete, endpoint)
	if err != nil {
		return errors.Wrap(err, "request build failed")
	}
	c.logger.Printf("[INFO] delete locator #%s ...", locatorID)
	if err := c.do(req, http.StatusNoContent, nil); err != nil {
		return errors.Wrap(err, "request failed")
	}
	c.logger.Printf("[INFO] completed")
	return nil
}

func (c *Client) getLocators(ctx context.Context, endpoint string) ([]Locator, error) {
	req, err := c.newRequest(ctx, http.MethodGet, endpoint)
	if err != nil {
		return nil, errors.Wrap(err, "request build failed")
	}
	var out struct {
		Locators []Locator `json:"value"`
	}
	if err := c.do(req, http.StatusOK, &out); err != nil {
		return nil, errors.Wrap(err, "request failed")
	}
	return out.Locators, nil
}

func (c *Client) GetLocators(ctx context.Context) ([]Locator, error) {
	return c.getLocators(ctx, locatorsEndpoint)
}

func (c *Client) GetLocatorsWithAsset(ctx context.Context, assetID string) ([]Locator, error) {
	endpoint := path.Join(toAssetResource(assetID), locatorsEndpoint)
	return c.getLocators(ctx, endpoint)
}

func toLocatorResource(locatorID string) string {
	return toResource(locatorsEndpoint, locatorID)
}
<|endoftext|>