<commit_before>/*
Copyright 2021 The Kubernetes Authors.

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

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

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

package cmd

import (
	"bytes"
	"os"
	"strings"

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

	"k8s.io/release/pkg/cve"
	"sigs.k8s.io/release-utils/editor"
)

// releaseNotesCmd represents the subcommand for `krel release-notes`
var cveCmd = &cobra.Command{
	Use:   "cve",
	Short: "Add and edit CVE information",
	Long: `krel cve
Subcommand to work with CVE data maps used to publish vulnerability information.
This subcommand enables a Release Manager to write and import new data maps with
CVE vulnerability information.

The command enables creatin, editing and deleting existing CVE entries in the
release bucket. See each subcommand for more information.
`,
	SilenceUsage:  false,
	SilenceErrors: false,
}

var cveDeleteCmd = &cobra.Command{
	Use:           "delete",
	Short:         "Delete an existing cve map",
	Long:          `Deletes an existing CVE map from the release bucket`,
	SilenceUsage:  true,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return deleteCVE(cveOpts)
	},
	Args: argFunc,
}

var cveEditCmd = &cobra.Command{
	Use:   "edit",
	Short: "Edit a CVE map file",
	Long: `The edit command opens an editor pulls a CVE map which has already been published and
opens it for editing in the user's editor of choice (defined by the $EDITOR
or $KUBEEDITOR env vars). When saving and exiting the editor, krel will check
the new CVE entry and upload it to the release bucket.

To abort the editing process, do no change anything in the file or simply
delete all content from the file.
`,
	SilenceUsage:  true,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return editCVE(cveOpts)
	},
	Args: argFunc,
}

type cveOptions struct {
	CVE      string   // CVE identifier to work on
	mapFiles []string // List of mapfiles
}

var argFunc = func(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return errors.New("command takes only one argument: a CVE identifier")
	}
	cveOpts.CVE = strings.ToUpper(args[0])
	if err := cve.NewClient().CheckID(cveOpts.CVE); err != nil {
		return errors.Errorf("invalid CVE ID. Format must match %s", cve.CVEIDRegExp)
	}
	return nil
}

var cveOpts = &cveOptions{}

func init() {
	cveCmd.PersistentFlags().StringSliceVarP(
		&cveOpts.mapFiles,
		"file",
		"f",
		[]string{},
		"version tag for the notes",
	)

	cveCmd.AddCommand(cveEditCmd, cveDeleteCmd)
	rootCmd.AddCommand(cveCmd)
}

// writeNewCVE opens an editor to edit a new CVE entry interactively
func writeNewCVE(opts *cveOptions) (err error) {
	client := cve.NewClient()

	file, err := client.CreateEmptyMap(opts.CVE)
	if err != nil {
		return errors.Wrap(err, "creating new cve data map")
	}

	oldFile, err := os.ReadFile(file.Name())
	if err != nil {
		return errors.Wrap(err, "reading local copy of CVE entry")
	}

	kubeEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"})
	changes, tempFilePath, err := kubeEditor.LaunchTempFile(
		"cve-datamap-", ".yaml", bytes.NewReader(oldFile),
	)
	if err != nil {
		return errors.Wrap(err, "launching editor")
	}

	if string(changes) == string(oldFile) || string(changes) == "" {
		logrus.Info("CVE information not modified")
		return nil
	}

	logrus.Infof("Creating %s entry", opts.CVE)

	// If the file was changed, re-write it:
	return client.Write(opts.CVE, tempFilePath)
}

// writeCVEFiles handles non interactive file writes
func writeCVEFiles(opts *cveOptions) error {
	client := cve.NewClient()
	for _, mapFile := range opts.mapFiles {
		if err := client.Write(opts.CVE, mapFile); err != nil {
			return errors.Wrapf(err, "writing map file %s", mapFile)
		}
	}
	return nil
}

// deleteCVE removes an existing map file
func deleteCVE(opts *cveOptions) (err error) {
	client := cve.NewClient()
	return client.Delete(opts.CVE)
}

// editCVE main edit funcion
func editCVE(opts *cveOptions) (err error) {
	client := cve.NewClient()

	// If yaml files were specified, skip the interactive mode
	if len(opts.mapFiles) != 0 {
		return writeCVEFiles(opts)
	}

	// If we're editing interactively, check if it is a new CVE
	// or we should first pull the data from the bucket
	exists, err := client.EntryExists(opts.CVE)
	if err != nil {
		return errors.Wrap(err, "checking if cve entry exists")
	}

	if exists {
		return editExistingCVE(opts)
	}

	return writeNewCVE(opts)
}

// editExistingCVE loads an existing map from the bucket and opens is
// in the user's default editor
func editExistingCVE(opts *cveOptions) (err error) {
	client := cve.NewClient()
	file, err := client.CopyToTemp(opts.CVE)
	if err != nil {
		return errors.Wrap(err, "copying CVE entry for edting")
	}
	oldFile, err := os.ReadFile(file.Name())
	if err != nil {
		return errors.Wrap(err, "reading local copy of CVE entry")
	}

	kubeEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"})
	changes, tempFilePath, err := kubeEditor.LaunchTempFile(
		"cve-datamap-", ".yaml", bytes.NewReader(oldFile),
	)
	if err != nil {
		return errors.Wrap(err, "launching editor")
	}

	if string(changes) == string(oldFile) || string(changes) == "" {
		logrus.Info("CVE information not modified")
		return nil
	}

	logrus.Infof("Updating %s entry", opts.CVE)

	// If the file was changed, re-write it:
	return client.Write(opts.CVE, tempFilePath)
}
<commit_msg>Fix krel cve -f flag description<commit_after>/*
Copyright 2021 The Kubernetes Authors.

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

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

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

package cmd

import (
	"bytes"
	"os"
	"strings"

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

	"k8s.io/release/pkg/cve"
	"sigs.k8s.io/release-utils/editor"
)

// releaseNotesCmd represents the subcommand for `krel release-notes`
var cveCmd = &cobra.Command{
	Use:   "cve",
	Short: "Add and edit CVE information",
	Long: `krel cve
Subcommand to work with CVE data maps used to publish vulnerability information.
This subcommand enables a Release Manager to write and import new data maps with
CVE vulnerability information.

The command enables creatin, editing and deleting existing CVE entries in the
release bucket. See each subcommand for more information.
`,
	SilenceUsage:  false,
	SilenceErrors: false,
}

var cveDeleteCmd = &cobra.Command{
	Use:           "delete",
	Short:         "Delete an existing cve map",
	Long:          `Deletes an existing CVE map from the release bucket`,
	SilenceUsage:  true,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return deleteCVE(cveOpts)
	},
	Args: argFunc,
}

var cveEditCmd = &cobra.Command{
	Use:   "edit",
	Short: "Edit a CVE map file",
	Long: `The edit command opens an editor pulls a CVE map which has already been published and
opens it for editing in the user's editor of choice (defined by the $EDITOR
or $KUBEEDITOR env vars). When saving and exiting the editor, krel will check
the new CVE entry and upload it to the release bucket.

To abort the editing process, do no change anything in the file or simply
delete all content from the file.
`,
	SilenceUsage:  true,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return editCVE(cveOpts)
	},
	Args: argFunc,
}

type cveOptions struct {
	CVE      string   // CVE identifier to work on
	mapFiles []string // List of mapfiles
}

var argFunc = func(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return errors.New("command takes only one argument: a CVE identifier")
	}
	cveOpts.CVE = strings.ToUpper(args[0])
	if err := cve.NewClient().CheckID(cveOpts.CVE); err != nil {
		return errors.Errorf("invalid CVE ID. Format must match %s", cve.CVEIDRegExp)
	}
	return nil
}

var cveOpts = &cveOptions{}

func init() {
	cveCmd.PersistentFlags().StringSliceVarP(
		&cveOpts.mapFiles,
		"file",
		"f",
		[]string{},
		"update vulnerability data from a local map file",
	)

	cveCmd.AddCommand(cveEditCmd, cveDeleteCmd)
	rootCmd.AddCommand(cveCmd)
}

// writeNewCVE opens an editor to edit a new CVE entry interactively
func writeNewCVE(opts *cveOptions) (err error) {
	client := cve.NewClient()

	file, err := client.CreateEmptyMap(opts.CVE)
	if err != nil {
		return errors.Wrap(err, "creating new cve data map")
	}

	oldFile, err := os.ReadFile(file.Name())
	if err != nil {
		return errors.Wrap(err, "reading local copy of CVE entry")
	}

	kubeEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"})
	changes, tempFilePath, err := kubeEditor.LaunchTempFile(
		"cve-datamap-", ".yaml", bytes.NewReader(oldFile),
	)
	if err != nil {
		return errors.Wrap(err, "launching editor")
	}

	if string(changes) == string(oldFile) || string(changes) == "" {
		logrus.Info("CVE information not modified")
		return nil
	}

	logrus.Infof("Creating %s entry", opts.CVE)

	// If the file was changed, re-write it:
	return client.Write(opts.CVE, tempFilePath)
}

// writeCVEFiles handles non interactive file writes
func writeCVEFiles(opts *cveOptions) error {
	client := cve.NewClient()
	for _, mapFile := range opts.mapFiles {
		if err := client.Write(opts.CVE, mapFile); err != nil {
			return errors.Wrapf(err, "writing map file %s", mapFile)
		}
	}
	return nil
}

// deleteCVE removes an existing map file
func deleteCVE(opts *cveOptions) (err error) {
	client := cve.NewClient()
	return client.Delete(opts.CVE)
}

// editCVE main edit funcion
func editCVE(opts *cveOptions) (err error) {
	client := cve.NewClient()

	// If yaml files were specified, skip the interactive mode
	if len(opts.mapFiles) != 0 {
		return writeCVEFiles(opts)
	}

	// If we're editing interactively, check if it is a new CVE
	// or we should first pull the data from the bucket
	exists, err := client.EntryExists(opts.CVE)
	if err != nil {
		return errors.Wrap(err, "checking if cve entry exists")
	}

	if exists {
		return editExistingCVE(opts)
	}

	return writeNewCVE(opts)
}

// editExistingCVE loads an existing map from the bucket and opens is
// in the user's default editor
func editExistingCVE(opts *cveOptions) (err error) {
	client := cve.NewClient()
	file, err := client.CopyToTemp(opts.CVE)
	if err != nil {
		return errors.Wrap(err, "copying CVE entry for edting")
	}
	oldFile, err := os.ReadFile(file.Name())
	if err != nil {
		return errors.Wrap(err, "reading local copy of CVE entry")
	}

	kubeEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"})
	changes, tempFilePath, err := kubeEditor.LaunchTempFile(
		"cve-datamap-", ".yaml", bytes.NewReader(oldFile),
	)
	if err != nil {
		return errors.Wrap(err, "launching editor")
	}

	if string(changes) == string(oldFile) || string(changes) == "" {
		logrus.Info("CVE information not modified")
		return nil
	}

	logrus.Infof("Updating %s entry", opts.CVE)

	// If the file was changed, re-write it:
	return client.Write(opts.CVE, tempFilePath)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/zyedidia/clipboard"
)

// The Cursor struct stores the location of the cursor in the view
// The complicated part about the cursor is storing its location.
// The cursor must be displayed at an x, y location, but since the buffer
// uses a rope to store text, to insert text we must have an index. It
// is also simpler to use character indicies for other tasks such as
// selection.
type Cursor struct {
	buf *Buffer
	Loc

	// Last cursor x position
	LastVisualX int

	// The current selection as a range of character numbers (inclusive)
	CurSelection [2]Loc
	// The original selection as a range of character numbers
	// This is used for line and word selection where it is necessary
	// to know what the original selection was
	OrigSelection [2]Loc

	// Which cursor index is this (for multiple cursors)
	Num int
}

// Goto puts the cursor at the given cursor's location and gives the current cursor its selection too
func (c *Cursor) Goto(b Cursor) {
	c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
	c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
}

// CopySelection copies the user's selection to either "primary" or "clipboard"
func (c *Cursor) CopySelection(target string) {
	if c.HasSelection() {
		if target != "primary" || c.buf.Settings["useprimary"].(bool) {
			clipboard.WriteAll(c.GetSelection(), target)
		}
	}
}

// ResetSelection resets the user's selection
func (c *Cursor) ResetSelection() {
	c.CurSelection[0] = c.buf.Start()
	c.CurSelection[1] = c.buf.Start()
}

// SetSelectionStart sets the start of the selection
func (c *Cursor) SetSelectionStart(pos Loc) {
	c.CurSelection[0] = pos
}

// SetSelectionEnd sets the end of the selection
func (c *Cursor) SetSelectionEnd(pos Loc) {
	c.CurSelection[1] = pos
}

// HasSelection returns whether or not the user has selected anything
func (c *Cursor) HasSelection() bool {
	return c.CurSelection[0] != c.CurSelection[1]
}

// DeleteSelection deletes the currently selected text
func (c *Cursor) DeleteSelection() {
	if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
		c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
		c.Loc = c.CurSelection[1]
	} else if !c.HasSelection() {
		return
	} else {
		c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
		c.Loc = c.CurSelection[0]
	}
}

// GetSelection returns the cursor's selection
func (c *Cursor) GetSelection() string {
	if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {
		if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
			return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
		}
		return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
	}
	return ""
}

// SelectLine selects the current line
func (c *Cursor) SelectLine() {
	c.Start()
	c.SetSelectionStart(c.Loc)
	c.End()
	if c.buf.NumLines-1 > c.Y {
		c.SetSelectionEnd(c.Loc.Move(1, c.buf))
	} else {
		c.SetSelectionEnd(c.Loc)
	}

	c.OrigSelection = c.CurSelection
}

// AddLineToSelection adds the current line to the selection
func (c *Cursor) AddLineToSelection() {
	if c.Loc.LessThan(c.OrigSelection[0]) {
		c.Start()
		c.SetSelectionStart(c.Loc)
		c.SetSelectionEnd(c.OrigSelection[1])
	}
	if c.Loc.GreaterThan(c.OrigSelection[1]) {
		c.End()
		c.SetSelectionEnd(c.Loc.Move(1, c.buf))
		c.SetSelectionStart(c.OrigSelection[0])
	}

	if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) {
		c.CurSelection = c.OrigSelection
	}
}

// SelectWord selects the word the cursor is currently on
func (c *Cursor) SelectWord() {
	if len(c.buf.Line(c.Y)) == 0 {
		return
	}

	if !IsWordChar(string(c.RuneUnder(c.X))) {
		c.SetSelectionStart(c.Loc)
		c.SetSelectionEnd(c.Loc.Move(1, c.buf))
		c.OrigSelection = c.CurSelection
		return
	}

	forward, backward := c.X, c.X

	for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
		backward--
	}

	c.SetSelectionStart(Loc{backward, c.Y})
	c.OrigSelection[0] = c.CurSelection[0]

	for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
		forward++
	}

	c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
	c.OrigSelection[1] = c.CurSelection[1]
	c.Loc = c.CurSelection[1]
}

// AddWordToSelection adds the word the cursor is currently on to the selection
func (c *Cursor) AddWordToSelection() {
	if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
		c.CurSelection = c.OrigSelection
		return
	}

	if c.Loc.LessThan(c.OrigSelection[0]) {
		backward := c.X

		for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
			backward--
		}

		c.SetSelectionStart(Loc{backward, c.Y})
		c.SetSelectionEnd(c.OrigSelection[1])
	}

	if c.Loc.GreaterThan(c.OrigSelection[1]) {
		forward := c.X

		for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
			forward++
		}

		c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
		c.SetSelectionStart(c.OrigSelection[0])
	}

	c.Loc = c.CurSelection[1]
}

// SelectTo selects from the current cursor location to the given location
func (c *Cursor) SelectTo(loc Loc) {
	if loc.GreaterThan(c.OrigSelection[0]) {
		c.SetSelectionStart(c.OrigSelection[0])
		c.SetSelectionEnd(loc)
	} else {
		c.SetSelectionStart(loc)
		c.SetSelectionEnd(c.OrigSelection[0])
	}
}

// WordRight moves the cursor one word to the right
func (c *Cursor) WordRight() {
	for IsWhitespace(c.RuneUnder(c.X)) {
		if c.X == Count(c.buf.Line(c.Y)) {
			c.Right()
			return
		}
		c.Right()
	}
	c.Right()
	for IsWordChar(string(c.RuneUnder(c.X))) {
		if c.X == Count(c.buf.Line(c.Y)) {
			return
		}
		c.Right()
	}
}

// WordLeft moves the cursor one word to the left
func (c *Cursor) WordLeft() {
	c.Left()
	for IsWhitespace(c.RuneUnder(c.X)) {
		if c.X == 0 {
			return
		}
		c.Left()
	}
	c.Left()
	for IsWordChar(string(c.RuneUnder(c.X))) {
		if c.X == 0 {
			return
		}
		c.Left()
	}
	c.Right()
}

// RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune {
	line := []rune(c.buf.Line(c.Y))
	if len(line) == 0 {
		return '\n'
	}
	if x >= len(line) {
		return '\n'
	} else if x < 0 {
		x = 0
	}
	return line[x]
}

// UpN moves the cursor up N lines (if possible)
func (c *Cursor) UpN(amount int) {
	proposedY := c.Y - amount
	if proposedY < 0 {
		proposedY = 0
	} else if proposedY >= c.buf.NumLines {
		proposedY = c.buf.NumLines - 1
	}
	if proposedY == c.Y {
		return
	}

	c.Y = proposedY
	runes := []rune(c.buf.Line(c.Y))
	c.X = c.GetCharPosInLine(c.Y, c.LastVisualX)
	if c.X > len(runes) {
		c.X = len(runes)
	}
}

// DownN moves the cursor down N lines (if possible)
func (c *Cursor) DownN(amount int) {
	c.UpN(-amount)
}

// Up moves the cursor up one line (if possible)
func (c *Cursor) Up() {
	c.UpN(1)
}

// Down moves the cursor down one line (if possible)
func (c *Cursor) Down() {
	c.DownN(1)
}

// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
func (c *Cursor) Left() {
	if c.Loc == c.buf.Start() {
		return
	}
	if c.X > 0 {
		c.X--
	} else {
		c.Up()
		c.End()
	}
	c.LastVisualX = c.GetVisualX()
}

// Right moves the cursor right one cell (if possible) or to the next line if it is at the end
func (c *Cursor) Right() {
	if c.Loc == c.buf.End() {
		return
	}
	if c.X < Count(c.buf.Line(c.Y)) {
		c.X++
	} else {
		c.Down()
		c.Start()
	}
	c.LastVisualX = c.GetVisualX()
}

// End moves the cursor to the end of the line it is on
func (c *Cursor) End() {
	c.X = Count(c.buf.Line(c.Y))
	c.LastVisualX = c.GetVisualX()
}

// Start moves the cursor to the start of the line it is on
func (c *Cursor) Start() {
	c.X = 0
	c.LastVisualX = c.GetVisualX()
}

// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
	// Get the tab size
	tabSize := int(c.buf.Settings["tabsize"].(float64))
	visualLineLen := StringWidth(c.buf.Line(lineNum), tabSize)
	if visualPos > visualLineLen {
		visualPos = visualLineLen
	}
	width := WidthOfLargeRunes(c.buf.Line(lineNum), tabSize)
	if visualPos >= width {
		return visualPos - width
	}
	return visualPos / tabSize
}

// GetVisualX returns the x value of the cursor in visual spaces
func (c *Cursor) GetVisualX() int {
	runes := []rune(c.buf.Line(c.Y))
	tabSize := int(c.buf.Settings["tabsize"].(float64))
	if c.X > len(runes) {
		c.X = len(runes) - 1
	}

	if c.X < 0 {
		c.X = 0
	}

	return StringWidth(string(runes[:c.X]), tabSize)
}

// StoreVisualX stores the current visual x value in the cursor
func (c *Cursor) StoreVisualX() {
	c.LastVisualX = c.GetVisualX()
}

// Relocate makes sure that the cursor is inside the bounds of the buffer
// If it isn't, it moves it to be within the buffer's lines
func (c *Cursor) Relocate() {
	if c.Y < 0 {
		c.Y = 0
	} else if c.Y >= c.buf.NumLines {
		c.Y = c.buf.NumLines - 1
	}

	if c.X < 0 {
		c.X = 0
	} else if c.X > Count(c.buf.Line(c.Y)) {
		c.X = Count(c.buf.Line(c.Y))
	}
}
<commit_msg>Cursor move at the first char when Up on the first line (and at the last char when Down on last the line) (#773)<commit_after>package main

import (
	"github.com/zyedidia/clipboard"
)

// The Cursor struct stores the location of the cursor in the view
// The complicated part about the cursor is storing its location.
// The cursor must be displayed at an x, y location, but since the buffer
// uses a rope to store text, to insert text we must have an index. It
// is also simpler to use character indicies for other tasks such as
// selection.
type Cursor struct {
	buf *Buffer
	Loc

	// Last cursor x position
	LastVisualX int

	// The current selection as a range of character numbers (inclusive)
	CurSelection [2]Loc
	// The original selection as a range of character numbers
	// This is used for line and word selection where it is necessary
	// to know what the original selection was
	OrigSelection [2]Loc

	// Which cursor index is this (for multiple cursors)
	Num int
}

// Goto puts the cursor at the given cursor's location and gives the current cursor its selection too
func (c *Cursor) Goto(b Cursor) {
	c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
	c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
}

// CopySelection copies the user's selection to either "primary" or "clipboard"
func (c *Cursor) CopySelection(target string) {
	if c.HasSelection() {
		if target != "primary" || c.buf.Settings["useprimary"].(bool) {
			clipboard.WriteAll(c.GetSelection(), target)
		}
	}
}

// ResetSelection resets the user's selection
func (c *Cursor) ResetSelection() {
	c.CurSelection[0] = c.buf.Start()
	c.CurSelection[1] = c.buf.Start()
}

// SetSelectionStart sets the start of the selection
func (c *Cursor) SetSelectionStart(pos Loc) {
	c.CurSelection[0] = pos
}

// SetSelectionEnd sets the end of the selection
func (c *Cursor) SetSelectionEnd(pos Loc) {
	c.CurSelection[1] = pos
}

// HasSelection returns whether or not the user has selected anything
func (c *Cursor) HasSelection() bool {
	return c.CurSelection[0] != c.CurSelection[1]
}

// DeleteSelection deletes the currently selected text
func (c *Cursor) DeleteSelection() {
	if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
		c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
		c.Loc = c.CurSelection[1]
	} else if !c.HasSelection() {
		return
	} else {
		c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
		c.Loc = c.CurSelection[0]
	}
}

// GetSelection returns the cursor's selection
func (c *Cursor) GetSelection() string {
	if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {
		if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
			return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
		}
		return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
	}
	return ""
}

// SelectLine selects the current line
func (c *Cursor) SelectLine() {
	c.Start()
	c.SetSelectionStart(c.Loc)
	c.End()
	if c.buf.NumLines-1 > c.Y {
		c.SetSelectionEnd(c.Loc.Move(1, c.buf))
	} else {
		c.SetSelectionEnd(c.Loc)
	}

	c.OrigSelection = c.CurSelection
}

// AddLineToSelection adds the current line to the selection
func (c *Cursor) AddLineToSelection() {
	if c.Loc.LessThan(c.OrigSelection[0]) {
		c.Start()
		c.SetSelectionStart(c.Loc)
		c.SetSelectionEnd(c.OrigSelection[1])
	}
	if c.Loc.GreaterThan(c.OrigSelection[1]) {
		c.End()
		c.SetSelectionEnd(c.Loc.Move(1, c.buf))
		c.SetSelectionStart(c.OrigSelection[0])
	}

	if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) {
		c.CurSelection = c.OrigSelection
	}
}

// SelectWord selects the word the cursor is currently on
func (c *Cursor) SelectWord() {
	if len(c.buf.Line(c.Y)) == 0 {
		return
	}

	if !IsWordChar(string(c.RuneUnder(c.X))) {
		c.SetSelectionStart(c.Loc)
		c.SetSelectionEnd(c.Loc.Move(1, c.buf))
		c.OrigSelection = c.CurSelection
		return
	}

	forward, backward := c.X, c.X

	for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
		backward--
	}

	c.SetSelectionStart(Loc{backward, c.Y})
	c.OrigSelection[0] = c.CurSelection[0]

	for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
		forward++
	}

	c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
	c.OrigSelection[1] = c.CurSelection[1]
	c.Loc = c.CurSelection[1]
}

// AddWordToSelection adds the word the cursor is currently on to the selection
func (c *Cursor) AddWordToSelection() {
	if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
		c.CurSelection = c.OrigSelection
		return
	}

	if c.Loc.LessThan(c.OrigSelection[0]) {
		backward := c.X

		for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
			backward--
		}

		c.SetSelectionStart(Loc{backward, c.Y})
		c.SetSelectionEnd(c.OrigSelection[1])
	}

	if c.Loc.GreaterThan(c.OrigSelection[1]) {
		forward := c.X

		for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
			forward++
		}

		c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
		c.SetSelectionStart(c.OrigSelection[0])
	}

	c.Loc = c.CurSelection[1]
}

// SelectTo selects from the current cursor location to the given location
func (c *Cursor) SelectTo(loc Loc) {
	if loc.GreaterThan(c.OrigSelection[0]) {
		c.SetSelectionStart(c.OrigSelection[0])
		c.SetSelectionEnd(loc)
	} else {
		c.SetSelectionStart(loc)
		c.SetSelectionEnd(c.OrigSelection[0])
	}
}

// WordRight moves the cursor one word to the right
func (c *Cursor) WordRight() {
	for IsWhitespace(c.RuneUnder(c.X)) {
		if c.X == Count(c.buf.Line(c.Y)) {
			c.Right()
			return
		}
		c.Right()
	}
	c.Right()
	for IsWordChar(string(c.RuneUnder(c.X))) {
		if c.X == Count(c.buf.Line(c.Y)) {
			return
		}
		c.Right()
	}
}

// WordLeft moves the cursor one word to the left
func (c *Cursor) WordLeft() {
	c.Left()
	for IsWhitespace(c.RuneUnder(c.X)) {
		if c.X == 0 {
			return
		}
		c.Left()
	}
	c.Left()
	for IsWordChar(string(c.RuneUnder(c.X))) {
		if c.X == 0 {
			return
		}
		c.Left()
	}
	c.Right()
}

// RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune {
	line := []rune(c.buf.Line(c.Y))
	if len(line) == 0 {
		return '\n'
	}
	if x >= len(line) {
		return '\n'
	} else if x < 0 {
		x = 0
	}
	return line[x]
}

// UpN moves the cursor up N lines (if possible)
func (c *Cursor) UpN(amount int) {
	proposedY := c.Y - amount
	if proposedY < 0 {
		c.X = 0 // first line: X moved before the first character
		return
	} else if proposedY >= c.buf.NumLines {
		proposedY = c.buf.NumLines - 1
	}

	runes := []rune(c.buf.Line(c.Y))
	c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)

	if c.X > len(runes) || proposedY == c.Y {
		c.X = len(runes)
	}

	c.Y = proposedY
}

// DownN moves the cursor down N lines (if possible)
func (c *Cursor) DownN(amount int) {
	c.UpN(-amount)
}

// Up moves the cursor up one line (if possible)
func (c *Cursor) Up() {
	c.UpN(1)
}

// Down moves the cursor down one line (if possible)
func (c *Cursor) Down() {
	c.DownN(1)
}

// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
func (c *Cursor) Left() {
	if c.Loc == c.buf.Start() {
		return
	}
	if c.X > 0 {
		c.X--
	} else {
		c.Up()
		c.End()
	}
	c.LastVisualX = c.GetVisualX()
}

// Right moves the cursor right one cell (if possible) or to the next line if it is at the end
func (c *Cursor) Right() {
	if c.Loc == c.buf.End() {
		return
	}
	if c.X < Count(c.buf.Line(c.Y)) {
		c.X++
	} else {
		c.Down()
		c.Start()
	}
	c.LastVisualX = c.GetVisualX()
}

// End moves the cursor to the end of the line it is on
func (c *Cursor) End() {
	c.X = Count(c.buf.Line(c.Y))
	c.LastVisualX = c.GetVisualX()
}

// Start moves the cursor to the start of the line it is on
func (c *Cursor) Start() {
	c.X = 0
	c.LastVisualX = c.GetVisualX()
}

// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
	// Get the tab size
	tabSize := int(c.buf.Settings["tabsize"].(float64))
	visualLineLen := StringWidth(c.buf.Line(lineNum), tabSize)
	if visualPos > visualLineLen {
		visualPos = visualLineLen
	}
	width := WidthOfLargeRunes(c.buf.Line(lineNum), tabSize)
	if visualPos >= width {
		return visualPos - width
	}
	return visualPos / tabSize
}

// GetVisualX returns the x value of the cursor in visual spaces
func (c *Cursor) GetVisualX() int {
	runes := []rune(c.buf.Line(c.Y))
	tabSize := int(c.buf.Settings["tabsize"].(float64))
	if c.X > len(runes) {
		c.X = len(runes) - 1
	}

	if c.X < 0 {
		c.X = 0
	}

	return StringWidth(string(runes[:c.X]), tabSize)
}

// StoreVisualX stores the current visual x value in the cursor
func (c *Cursor) StoreVisualX() {
	c.LastVisualX = c.GetVisualX()
}

// Relocate makes sure that the cursor is inside the bounds of the buffer
// If it isn't, it moves it to be within the buffer's lines
func (c *Cursor) Relocate() {
	if c.Y < 0 {
		c.Y = 0
	} else if c.Y >= c.buf.NumLines {
		c.Y = c.buf.NumLines - 1
	}

	if c.X < 0 {
		c.X = 0
	} else if c.X > Count(c.buf.Line(c.Y)) {
		c.X = Count(c.buf.Line(c.Y))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"io"
	"os"

	. "github.com/polydawn/go-errcat"
	"gopkg.in/alecthomas/kingpin.v2"

	"go.polydawn.net/go-timeless-api/repeatr"
)

func main() {
	ctx := context.Background()
	bhv := Main(ctx, os.Args, os.Stdin, os.Stdout, os.Stderr)
	err := bhv.action()
	exitCode := repeatr.GetExitCode(err)
	os.Exit(int(exitCode))
}

// Holder type which makes it easier for us to inspect
//  the args parser result in test code before running logic.
type behavior struct {
	parsedArgs interface{}
	action     func() error
}

func Main(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) behavior {
	// CLI boilerplate.
	app := kingpin.New("repeatr", "Functional computation.")
	app.HelpFlag.Short('h')
	app.UsageWriter(stderr)
	app.ErrorWriter(stderr)

	// Args struct defs and flag declarations.
	bhvs := map[string]behavior{}
	argsRun := struct {
		FormulaPath string
	}{}
	cmdRun := app.Command("run", "Execute a formula.")
	cmdRun.Arg("formula", "Path to formula file.").
		Required().
		StringVar(&argsRun.FormulaPath)
	bhvs[cmdRun.FullCommand()] = behavior{&argsRun, func() error {
		return Run(ctx, "chroot", argsRun.FormulaPath, nil, stdout, stderr)
	}}

	// Parse!
	parsedCmdStr, err := app.Parse(args[1:])
	if err != nil {
		return behavior{
			parsedArgs: err,
			action: func() error {
				//fmt.Fprintln(stderr, err)  // ?
				return Errorf(repeatr.ErrUsage, "error parsing args: %s", err)
			},
		}
	}
	// Return behavior named by the command and subcommand strings.
	if bhv, ok := bhvs[parsedCmdStr]; ok {
		return bhv
	}
	panic("unreachable, cli parser must error on unknown commands")
}
<commit_msg>cli: yes, we probably should print any errors, eh?<commit_after>package main

import (
	"context"
	"fmt"
	"io"
	"os"

	. "github.com/polydawn/go-errcat"
	"gopkg.in/alecthomas/kingpin.v2"

	"go.polydawn.net/go-timeless-api/repeatr"
)

func main() {
	ctx := context.Background()
	bhv := Main(ctx, os.Args, os.Stdin, os.Stdout, os.Stderr)
	err := bhv.action()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
	}
	exitCode := repeatr.GetExitCode(err)
	os.Exit(exitCode)
}

// Holder type which makes it easier for us to inspect
//  the args parser result in test code before running logic.
type behavior struct {
	parsedArgs interface{}
	action     func() error
}

func Main(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) behavior {
	// CLI boilerplate.
	app := kingpin.New("repeatr", "Functional computation.")
	app.HelpFlag.Short('h')
	app.UsageWriter(stderr)
	app.ErrorWriter(stderr)

	// Args struct defs and flag declarations.
	bhvs := map[string]behavior{}
	argsRun := struct {
		FormulaPath string
	}{}
	cmdRun := app.Command("run", "Execute a formula.")
	cmdRun.Arg("formula", "Path to formula file.").
		Required().
		StringVar(&argsRun.FormulaPath)
	bhvs[cmdRun.FullCommand()] = behavior{&argsRun, func() error {
		return Run(ctx, "chroot", argsRun.FormulaPath, nil, stdout, stderr)
	}}

	// Parse!
	parsedCmdStr, err := app.Parse(args[1:])
	if err != nil {
		return behavior{
			parsedArgs: err,
			action: func() error {
				return Errorf(repeatr.ErrUsage, "error parsing args: %s", err)
			},
		}
	}
	// Return behavior named by the command and subcommand strings.
	if bhv, ok := bhvs[parsedCmdStr]; ok {
		return bhv
	}
	panic("unreachable, cli parser must error on unknown commands")
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/scipipe/scipipe"
)

func main() {
	flag.Parse()
	cmd := flag.Arg(0)
	switch cmd {
	case "new":
		writeNewWorkflowFile(flag.Arg(1))
	default:
		printUsage()
	}
}

func printUsage() {
	fmt.Println("Usage of scipipe:\nscipipe new <filename.go>")
}

func writeNewWorkflowFile(fileName string) {
	wfcode := `// Workflow written in SciPipe.
// For more information about SciPipe, see: http://scipipe.org
package main

import sp "github.com/scipipe/scipipe"

func main() {
	// Create a workflow, using 4 cpu cores
	wf := sp.NewWorkflow("my_workflow", 4)

	// Initialize processes
	foo := wf.NewProc("fooer", "echo foo > {o:foo}")
	foo.SetPathStatic("foo", "foo.txt")

	f2b := wf.NewProc("foo2bar", "sed 's/foo/bar/g' {i:foo} > {o:bar}")
	f2b.SetPathExtend("foo", "bar", ".bar.txt")

	// From workflow dependency network
	f2b.In("foo").From(foo.Out("foo"))

	// Run the workflow
	wf.Run()
}`
	if fileName == "" {
		fmt.Println("ERROR: No filename specified!")
		printUsage()
		os.Exit(1)
	}
	f, err := os.Create(fileName)
	if err != nil {
		scipipe.Fail("Could not create file:", fileName)
	}
	defer f.Close()
	_, err = f.Write([]byte(wfcode))
	if err != nil {
		scipipe.Fail("Could not write to file:", fileName)
	}
	fmt.Println("Successfully wrote new workflow file to:", fileName, "\n\nNow you can run it with:\ngo run ", fileName)
}
<commit_msg>Add audit2html subcommand in scipipe command<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"strings"

	"github.com/pkg/errors"
	"github.com/scipipe/scipipe"
)

func main() {
	scipipe.InitLogError()

	flag.Parse()
	cmd := flag.Arg(0)
	switch cmd {
	case "new":
		if len(flag.Args()) < 2 {
			fmt.Println("ERROR: No filename specified!")
			printNewUsage()
			os.Exit(1)
		}
		writeNewWorkflowFile(flag.Arg(1))
	case "audit2html":
		if len(flag.Args()) < 2 {
			fmt.Println("ERROR: No infile specified!")
			printAudit2HTMLUsage()
			os.Exit(1)
		}
		inFile := flag.Arg(1)
		if len(flag.Args()) < 3 {
			fmt.Println("ERROR: No outfile specified!")
			printAudit2HTMLUsage()
			os.Exit(1)
		}
		outFile := flag.Arg(2)
		err := convertAudit2Html(inFile, outFile)
		if err != nil {
			scipipe.CheckWithMsg(err, "Could not convert Audit file to HTML")
		}
	default:
		printHelp()
	}
}

func printNewUsage() {
	fmt.Print(`
Usage:
$ scipipe new <filename.go>
`)
}
func printAudit2HTMLUsage() {
	fmt.Print(`
Usage:
$ scipipe audit2html <infile.audit.json> [<outfile.html>]
`)
}

func printHelp() {
	fmt.Printf(`________________________________________________________________________

SciPipe (http://scipipe.org)
Version: %s
________________________________________________________________________

Usage:
$ scipipe <command> [command options]

Available commands:
$ scipipe new <filename.go>
$ scipipe audit2html <infile.audit.json> [<outfile.html>]
________________________________________________________________________
`, scipipe.Version)
}

func writeNewWorkflowFile(fileName string) {
	wfcode := `// Workflow written in SciPipe.
// For more information about SciPipe, see: http://scipipe.org
package main

import sp "github.com/scipipe/scipipe"

func main() {
	// Create a workflow, using 4 cpu cores
	wf := sp.NewWorkflow("my_workflow", 4)

	// Initialize processes
	foo := wf.NewProc("fooer", "echo foo > {o:foo}")
	foo.SetPathStatic("foo", "foo.txt")

	f2b := wf.NewProc("foo2bar", "sed 's/foo/bar/g' {i:foo} > {o:bar}")
	f2b.SetPathExtend("foo", "bar", ".bar.txt")

	// From workflow dependency network
	f2b.In("foo").From(foo.Out("foo"))

	// Run the workflow
	wf.Run()
}`
	f, err := os.Create(fileName)
	if err != nil {
		scipipe.Fail("Could not create file:", fileName)
	}
	defer f.Close()
	_, err = f.Write([]byte(wfcode))
	if err != nil {
		scipipe.Fail("Could not write to file:", fileName)
	}
	fmt.Println("Successfully wrote new workflow file to:", fileName, "\n\nNow you can run it with:\ngo run ", fileName)
}

func convertAudit2Html(inFilePath string, outFilePath string) error {
	ip := scipipe.NewFileIP(strings.Replace(inFilePath, ".audit.json", "", 1))
	ai := ip.AuditInfo()

	outHTML := fmt.Sprintf(`<html><head><title>Audit info for: %s</title></head><body><h2>Audit Info for: %s</h2>`, ip.Path(), ip.Path())
	outHTML += formatAuditInfoHTML(ai)
	outHTML += `</body></html>`

	if _, err := os.Stat(outFilePath); os.IsExist(err) {
		return errors.Wrap(err, "File already exists:"+outFilePath)
	}
	outFile, err := os.Create(outFilePath)
	if err != nil {
		return errors.Wrap(err, "Could not create file:"+outFilePath)
	}
	outFile.WriteString(outHTML)
	outFile.Close()
	return nil
}

func formatAuditInfoHTML(ai *scipipe.AuditInfo) (outHTML string) {
	outHTML = "
\n"
	outHTML += fmt.Sprintf("
ID:	%s
\n", ai.ID)
	outHTML += fmt.Sprintf("
Process name:	%s
\n", ai.ProcessName)
	outHTML += fmt.Sprintf("
Command:
%s

\n", ai.Command)

	params := []string{}
	for pname, p := range ai.Params {
		params = append(params, fmt.Sprintf("%s: %s", pname, p))
	}
	outHTML += fmt.Sprintf("
Parameters:	%s
\n", strings.Join(params, ", "))
	tags := []string{}
	for pname, p := range ai.Tags {
		tags = append(tags, fmt.Sprintf("%s: %s", pname, p))
	}
	outHTML += fmt.Sprintf("
Tags:
%v

\n", strings.Join(tags, ", "))

	outHTML += fmt.Sprintf("
Start time:	%v
\n", ai.StartTime)
	outHTML += fmt.Sprintf("
Finish time:	%v
\n", ai.FinishTime)
	outHTML += fmt.Sprintf("
Execution time:	%d ms
\n", ai.ExecTimeMS)
	upStreamHTML := ""
	for filePath, uai := range ai.Upstream {
		upStreamHTML += `<h4> Audit info for: ` + filePath + `</h4>`
		upStreamHTML += formatAuditInfoHTML(uai)
	}
	if outHTML != "" {
		outHTML += "
Upstreams:	" + upStreamHTML + "
\n"
	}
	outHTML += "
\n"
	return
}
<|endoftext|>


<commit_before>// +build windows

package cmd_test

import (
	"errors"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"testing"
	"time"

	"github.com/go-cmd/cmd"
	"github.com/go-test/deep"
)

func TestCmdOK(t *testing.T) {
	now := time.Now().Unix()

	p := cmd.NewCmd("echo", "foo")
	gotStatus := <-p.Start()
	expectStatus := cmd.Status{
		Cmd:      "echo",
		PID:      gotStatus.PID, // nondeterministic
		Complete: true,
		Exit:     0,
		Error:    nil,
		Runtime:  gotStatus.Runtime, // nondeterministic
		Stdout:   []string{"foo"},
		Stderr:   []string{},
	}
	if gotStatus.StartTs < now {
		t.Error("StartTs < now")
	}
	if gotStatus.StopTs < gotStatus.StartTs {
		t.Error("StopTs < StartTs")
	}
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}
	if gotStatus.PID < 0 {
		t.Errorf("got PID %d, expected non-zero", gotStatus.PID)
	}
	if gotStatus.Runtime < 0 {
		t.Errorf("got runtime %f, expected non-zero", gotStatus.Runtime)
	}
}

func TestCmdClone(t *testing.T) {
	opt := cmd.Options{
		Buffered: true,
	}
	c1 := cmd.NewCmdOptions(opt, "ls")
	c1.Dir = "/tmp/"
	c1.Env = []string{"YES=please"}
	c2 := c1.Clone()

	if c1.Name != c2.Name {
		t.Errorf("got Name %s, expecting %s", c2.Name, c1.Name)
	}
	if c1.Dir != c2.Dir {
		t.Errorf("got Dir %s, expecting %s", c2.Dir, c1.Dir)
	}
	if diffs := deep.Equal(c1.Env, c2.Env); diffs != nil {
		t.Error(diffs)
	}
}

func TestCmdNonzeroExit(t *testing.T) {
	p := cmd.NewCmd("false")
	gotStatus := <-p.Start()
	expectStatus := cmd.Status{
		Cmd:      "false",
		PID:      gotStatus.PID, // nondeterministic
		Complete: true,
		Exit:     1,
		Error:    nil,
		Runtime:  gotStatus.Runtime, // nondeterministic
		Stdout:   []string{},
		Stderr:   []string{},
	}
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}
	if gotStatus.PID < 0 {
		t.Errorf("got PID %d, expected non-zero", gotStatus.PID)
	}
	if gotStatus.Runtime < 0 {
		t.Errorf("got runtime %f, expected non-zero", gotStatus.Runtime)
	}
}

func TestCmdStop(t *testing.T) {
	p := cmd.NewCmd("sleep", "5")

	// Start process in bg and get chan to receive final Status when done
	statusChan := p.Start()

	// Give it a second
	time.Sleep(1 * time.Second)

	// Kill the process
	err := p.Stop()
	if err != nil {
		t.Error(err)
	}

	// The final status should be returned instantly
	timeout := time.After(1 * time.Second)
	var gotStatus cmd.Status
	select {
	case gotStatus = <-statusChan:
	case <-timeout:
		t.Fatal("timeout waiting for statusChan")
	}

	start := time.Unix(0, gotStatus.StartTs)
	stop := time.Unix(0, gotStatus.StopTs)
	d := stop.Sub(start).Seconds()
	if d < 0.90 || d > 2 {
		t.Errorf("stop - start time not between 0.9s and 2.0s: %s - %s = %f", stop, start, d)
	}
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0

	expectStatus := cmd.Status{
		Cmd:      "sleep",
		PID:      gotStatus.PID, // nondeterministic
		Complete: false,
		Exit:     1,
		Error:    nil,
		Runtime:  gotStatus.Runtime, // nondeterministic
		Stdout:   []string{},
		Stderr:   []string{},
	}
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}
	if gotStatus.PID < 0 {
		t.Errorf("got PID %d, expected non-zero", gotStatus.PID)
	}
	if gotStatus.Runtime < 0 {
		t.Errorf("got runtime %f, expected non-zero", gotStatus.Runtime)
	}

	// Stop should be idempotent
	err = p.Stop()
	if err != nil {
		t.Error(err)
	}

	// Start should be idempotent, too. It just returns the same statusChan again.
	c2 := p.Start()
	if diffs := deep.Equal(statusChan, c2); diffs != nil {
		t.Error(diffs)
	}
}

func TestCmdNotStarted(t *testing.T) {
	// Call everything _but_ Start.
	p := cmd.NewCmd("echo", "foo")

	gotStatus := p.Status()
	expectStatus := cmd.Status{
		Cmd:      "echo",
		PID:      0,
		Complete: false,
		Exit:     -1,
		Error:    nil,
		Runtime:  0,
		Stdout:   nil,
		Stderr:   nil,
	}
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}

	err := p.Stop()
	if err != nil {
		t.Error(err)
	}
}

func TestCmdOutput(t *testing.T) {
	t.Skip("FIXME")

	tmpfile, err := ioutil.TempFile("", "cmd.TestCmdOutput")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(tmpfile.Name())
	if err := tmpfile.Close(); err != nil {
		t.Fatal(err)
	}
	t.Logf("temp file: %s", tmpfile.Name())
	os.Remove(tmpfile.Name())

	p := cmd.NewCmd(path.Join(".", "test", "touch-file-count"), tmpfile.Name())

	p.Start()

	touchFile := func(file string) {
		if err := exec.Command("touch", file).Run(); err != nil {
			t.Fatal(err)
		}
		time.Sleep(600 * time.Millisecond)
	}
	var s cmd.Status
	var stdout []string

	touchFile(tmpfile.Name())
	s = p.Status()
	stdout = []string{"1"}
	if diffs := deep.Equal(s.Stdout, stdout); diffs != nil {
		t.Log(s.Stdout)
		t.Error(diffs)
	}

	touchFile(tmpfile.Name())
	s = p.Status()
	stdout = []string{"1", "2"}
	if diffs := deep.Equal(s.Stdout, stdout); diffs != nil {
		t.Log(s.Stdout)
		t.Error(diffs)
	}

	// No more output yet
	s = p.Status()
	stdout = []string{"1", "2"}
	if diffs := deep.Equal(s.Stdout, stdout); diffs != nil {
		t.Log(s.Stdout)
		t.Error(diffs)
	}

	// +2 lines
	touchFile(tmpfile.Name())
	touchFile(tmpfile.Name())
	s = p.Status()
	stdout = []string{"1", "2", "3", "4"}
	if diffs := deep.Equal(s.Stdout, stdout); diffs != nil {
		t.Log(s.Stdout)
		t.Error(diffs)
	}

	// Kill the process
	if err := p.Stop(); err != nil {
		t.Error(err)
	}
}

func TestCmdNotFound(t *testing.T) {
	t.Skip("FIXME")

	p := cmd.NewCmd("cmd-does-not-exist")
	gotStatus := <-p.Start()
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0
	expectStatus := cmd.Status{
		Cmd:      "cmd-does-not-exist",
		PID:      0,
		Complete: false,
		Exit:     -1,
		Error:    errors.New(`exec: "cmd-does-not-exist": executable file not found in $PATH`),
		Runtime:  0,
		Stdout:   nil,
		Stderr:   nil,
	}
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Logf("%+v", gotStatus)
		t.Error(diffs)
	}
}

func TestDone(t *testing.T) {
	t.Skip("FIXME")

	// Count to 3 sleeping 1s between counts
	p := cmd.NewCmd(path.Join(".", "test", "count-and-sleep"), "3", "1")
	statusChan := p.Start()

	// For 2s while cmd is running, Done() chan should block, which means
	// it's still running
	runningTimer := time.After(2 * time.Second)
TIMER:
	for {
		select {
		case <-runningTimer:
			break TIMER
		default:
		}
		select {
		case <-p.Done():
			t.Fatal("Done chan is closed before runningTime finished")
		default:
			// Done chan blocked, cmd is still running
		}
		time.Sleep(400 * time.Millisecond)
	}

	// Wait for cmd to complete
	var s1 cmd.Status
	select {
	case s1 = <-statusChan:
		t.Logf("got status: %+v", s1)
	case <-time.After(2 * time.Second):
		t.Fatal("timeout waiting for cmd to complete")
	}

	// After cmd completes, Done chan should be closed and not block
	select {
	case <-p.Done():
	default:
		t.Fatal("Done chan did not block after cmd completed")
	}

	// After command completes, we should be able to get exact same
	// Status that's returned on the Start() chan
	s2 := p.Status()
	if diff := deep.Equal(s1, s2); diff != nil {
		t.Error(diff)
	}
}

func TestCmdEnvOK(t *testing.T) {
	t.Skip("FIXME")

	now := time.Now().Unix()

	p := cmd.NewCmd("env")
	p.Env = []string{"FOO=foo"}
	gotStatus := <-p.Start()
	expectStatus := cmd.Status{
		Cmd:      "env",
		PID:      gotStatus.PID, // nondeterministic
		Complete: true,
		Exit:     0,
		Error:    nil,
		Runtime:  gotStatus.Runtime, // nondeterministic
		Stdout:   []string{"FOO=foo"},
		Stderr:   []string{},
	}
	if gotStatus.StartTs < now {
		t.Error("StartTs < now")
	}
	if gotStatus.StopTs < gotStatus.StartTs {
		t.Error("StopTs < StartTs")
	}
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}
	if gotStatus.PID < 0 {
		t.Errorf("got PID %d, expected non-zero", gotStatus.PID)
	}
	if gotStatus.Runtime < 0 {
		t.Errorf("got runtime %f, expected non-zero", gotStatus.Runtime)
	}
}

func TestCmdNoOutput(t *testing.T) {
	// Set both output options to false to discard all output
	p := cmd.NewCmdOptions(
		cmd.Options{
			Buffered:  false,
			Streaming: false,
		},
		"echo", "hell-world")
	s := <-p.Start()
	if s.Exit != 0 {
		t.Errorf("got exit %d, expected 0", s.Exit)
	}
	if len(s.Stdout) != 0 {
		t.Errorf("got stdout, expected no output: %v", s.Stdout)
	}
	if len(s.Stderr) != 0 {
		t.Errorf("got stderr, expected no output: %v", s.Stderr)
	}
}
<commit_msg>Fix Windows test<commit_after>// +build windows

package cmd_test

import (
	"errors"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"testing"
	"time"

	"github.com/go-cmd/cmd"
	"github.com/go-test/deep"
)

func TestCmdOK(t *testing.T) {
	now := time.Now().Unix()

	p := cmd.NewCmd("echo", "foo")
	gotStatus := <-p.Start()
	expectStatus := cmd.Status{
		Cmd:      "echo",
		PID:      gotStatus.PID, // nondeterministic
		Complete: true,
		Exit:     0,
		Error:    nil,
		Runtime:  gotStatus.Runtime, // nondeterministic
		Stdout:   []string{"foo"},
		Stderr:   []string{},
	}
	if gotStatus.StartTs < now {
		t.Error("StartTs < now")
	}
	if gotStatus.StopTs < gotStatus.StartTs {
		t.Error("StopTs < StartTs")
	}
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}
	if gotStatus.PID < 0 {
		t.Errorf("got PID %d, expected non-zero", gotStatus.PID)
	}
	if gotStatus.Runtime < 0 {
		t.Errorf("got runtime %f, expected non-zero", gotStatus.Runtime)
	}
}

func TestCmdClone(t *testing.T) {
	opt := cmd.Options{
		Buffered: true,
	}
	c1 := cmd.NewCmdOptions(opt, "ls")
	c1.Dir = "/tmp/"
	c1.Env = []string{"YES=please"}
	c2 := c1.Clone()

	if c1.Name != c2.Name {
		t.Errorf("got Name %s, expecting %s", c2.Name, c1.Name)
	}
	if c1.Dir != c2.Dir {
		t.Errorf("got Dir %s, expecting %s", c2.Dir, c1.Dir)
	}
	if diffs := deep.Equal(c1.Env, c2.Env); diffs != nil {
		t.Error(diffs)
	}
}

func TestCmdNonzeroExit(t *testing.T) {
	p := cmd.NewCmd("false")
	gotStatus := <-p.Start()
	expectStatus := cmd.Status{
		Cmd:      "false",
		PID:      gotStatus.PID, // nondeterministic
		Complete: true,
		Exit:     1,
		Error:    nil,
		Runtime:  gotStatus.Runtime, // nondeterministic
		Stdout:   []string{},
		Stderr:   []string{},
	}
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}
	if gotStatus.PID < 0 {
		t.Errorf("got PID %d, expected non-zero", gotStatus.PID)
	}
	if gotStatus.Runtime < 0 {
		t.Errorf("got runtime %f, expected non-zero", gotStatus.Runtime)
	}
}

func TestCmdStop(t *testing.T) {
	p := cmd.NewCmd("sleep", "5")

	// Start process in bg and get chan to receive final Status when done
	statusChan := p.Start()

	// Give it a second
	time.Sleep(1 * time.Second)

	// Kill the process
	err := p.Stop()
	if err != nil {
		t.Error(err)
	}

	// The final status should be returned instantly
	timeout := time.After(1 * time.Second)
	var gotStatus cmd.Status
	select {
	case gotStatus = <-statusChan:
	case <-timeout:
		t.Fatal("timeout waiting for statusChan")
	}

	start := time.Unix(0, gotStatus.StartTs)
	stop := time.Unix(0, gotStatus.StopTs)
	d := stop.Sub(start).Seconds()
	if d < 0.90 || d > 2 {
		t.Errorf("stop - start time not between 0.9s and 2.0s: %s - %s = %f", stop, start, d)
	}
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0

	expectStatus := cmd.Status{
		Cmd:      "sleep",
		PID:      gotStatus.PID, // nondeterministic
		Complete: false,
		Exit:     1,
		Error:    nil,
		Runtime:  gotStatus.Runtime, // nondeterministic
		Stdout:   []string{},
		Stderr:   []string{},
	}
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}
	if gotStatus.PID < 0 {
		t.Errorf("got PID %d, expected non-zero", gotStatus.PID)
	}
	if gotStatus.Runtime < 0 {
		t.Errorf("got runtime %f, expected non-zero", gotStatus.Runtime)
	}

	// Stop should be idempotent
	err = p.Stop()
	if err != nil {
		t.Error(err)
	}

	// Start should be idempotent, too. It just returns the same statusChan again.
	c2 := p.Start()
	if diffs := deep.Equal(statusChan, c2); diffs != nil {
		t.Error(diffs)
	}
}

func TestCmdNotStarted(t *testing.T) {
	// Call everything _but_ Start.
	p := cmd.NewCmd("echo", "foo")

	gotStatus := p.Status()
	expectStatus := cmd.Status{
		Cmd:      "echo",
		PID:      0,
		Complete: false,
		Exit:     -1,
		Error:    nil,
		Runtime:  0,
		Stdout:   nil,
		Stderr:   nil,
	}
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}

	err := p.Stop()
	if err != cmd.ErrNotStarted {
		t.Error(err)
	}
}

func TestCmdOutput(t *testing.T) {
	t.Skip("FIXME")

	tmpfile, err := ioutil.TempFile("", "cmd.TestCmdOutput")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(tmpfile.Name())
	if err := tmpfile.Close(); err != nil {
		t.Fatal(err)
	}
	t.Logf("temp file: %s", tmpfile.Name())
	os.Remove(tmpfile.Name())

	p := cmd.NewCmd(path.Join(".", "test", "touch-file-count"), tmpfile.Name())

	p.Start()

	touchFile := func(file string) {
		if err := exec.Command("touch", file).Run(); err != nil {
			t.Fatal(err)
		}
		time.Sleep(600 * time.Millisecond)
	}
	var s cmd.Status
	var stdout []string

	touchFile(tmpfile.Name())
	s = p.Status()
	stdout = []string{"1"}
	if diffs := deep.Equal(s.Stdout, stdout); diffs != nil {
		t.Log(s.Stdout)
		t.Error(diffs)
	}

	touchFile(tmpfile.Name())
	s = p.Status()
	stdout = []string{"1", "2"}
	if diffs := deep.Equal(s.Stdout, stdout); diffs != nil {
		t.Log(s.Stdout)
		t.Error(diffs)
	}

	// No more output yet
	s = p.Status()
	stdout = []string{"1", "2"}
	if diffs := deep.Equal(s.Stdout, stdout); diffs != nil {
		t.Log(s.Stdout)
		t.Error(diffs)
	}

	// +2 lines
	touchFile(tmpfile.Name())
	touchFile(tmpfile.Name())
	s = p.Status()
	stdout = []string{"1", "2", "3", "4"}
	if diffs := deep.Equal(s.Stdout, stdout); diffs != nil {
		t.Log(s.Stdout)
		t.Error(diffs)
	}

	// Kill the process
	if err := p.Stop(); err != nil {
		t.Error(err)
	}
}

func TestCmdNotFound(t *testing.T) {
	t.Skip("FIXME")

	p := cmd.NewCmd("cmd-does-not-exist")
	gotStatus := <-p.Start()
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0
	expectStatus := cmd.Status{
		Cmd:      "cmd-does-not-exist",
		PID:      0,
		Complete: false,
		Exit:     -1,
		Error:    errors.New(`exec: "cmd-does-not-exist": executable file not found in $PATH`),
		Runtime:  0,
		Stdout:   nil,
		Stderr:   nil,
	}
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Logf("%+v", gotStatus)
		t.Error(diffs)
	}
}

func TestDone(t *testing.T) {
	t.Skip("FIXME")

	// Count to 3 sleeping 1s between counts
	p := cmd.NewCmd(path.Join(".", "test", "count-and-sleep"), "3", "1")
	statusChan := p.Start()

	// For 2s while cmd is running, Done() chan should block, which means
	// it's still running
	runningTimer := time.After(2 * time.Second)
TIMER:
	for {
		select {
		case <-runningTimer:
			break TIMER
		default:
		}
		select {
		case <-p.Done():
			t.Fatal("Done chan is closed before runningTime finished")
		default:
			// Done chan blocked, cmd is still running
		}
		time.Sleep(400 * time.Millisecond)
	}

	// Wait for cmd to complete
	var s1 cmd.Status
	select {
	case s1 = <-statusChan:
		t.Logf("got status: %+v", s1)
	case <-time.After(2 * time.Second):
		t.Fatal("timeout waiting for cmd to complete")
	}

	// After cmd completes, Done chan should be closed and not block
	select {
	case <-p.Done():
	default:
		t.Fatal("Done chan did not block after cmd completed")
	}

	// After command completes, we should be able to get exact same
	// Status that's returned on the Start() chan
	s2 := p.Status()
	if diff := deep.Equal(s1, s2); diff != nil {
		t.Error(diff)
	}
}

func TestCmdEnvOK(t *testing.T) {
	t.Skip("FIXME")

	now := time.Now().Unix()

	p := cmd.NewCmd("env")
	p.Env = []string{"FOO=foo"}
	gotStatus := <-p.Start()
	expectStatus := cmd.Status{
		Cmd:      "env",
		PID:      gotStatus.PID, // nondeterministic
		Complete: true,
		Exit:     0,
		Error:    nil,
		Runtime:  gotStatus.Runtime, // nondeterministic
		Stdout:   []string{"FOO=foo"},
		Stderr:   []string{},
	}
	if gotStatus.StartTs < now {
		t.Error("StartTs < now")
	}
	if gotStatus.StopTs < gotStatus.StartTs {
		t.Error("StopTs < StartTs")
	}
	gotStatus.StartTs = 0
	gotStatus.StopTs = 0
	if diffs := deep.Equal(gotStatus, expectStatus); diffs != nil {
		t.Error(diffs)
	}
	if gotStatus.PID < 0 {
		t.Errorf("got PID %d, expected non-zero", gotStatus.PID)
	}
	if gotStatus.Runtime < 0 {
		t.Errorf("got runtime %f, expected non-zero", gotStatus.Runtime)
	}
}

func TestCmdNoOutput(t *testing.T) {
	// Set both output options to false to discard all output
	p := cmd.NewCmdOptions(
		cmd.Options{
			Buffered:  false,
			Streaming: false,
		},
		"echo", "hell-world")
	s := <-p.Start()
	if s.Exit != 0 {
		t.Errorf("got exit %d, expected 0", s.Exit)
	}
	if len(s.Stdout) != 0 {
		t.Errorf("got stdout, expected no output: %v", s.Stdout)
	}
	if len(s.Stderr) != 0 {
		t.Errorf("got stderr, expected no output: %v", s.Stderr)
	}
}
<|endoftext|>


<commit_before>package siteengines

import (
	"strconv"
	"strings"
	"time"

	"github.com/hoisie/web"
	. "github.com/xyproto/browserspeak"
	. "github.com/xyproto/genericsite"
	"github.com/xyproto/moskus"
	"github.com/xyproto/simpleredis"
)

// TODO: Rename this module to something more generic than TimeTable
// TODO: Use the personplan and moskus module
// TODO: Add the timeTable pages to the search engine somehow (and the other engines too, like the chat)

/* Structure (TODO: Look at personplan for how the structure ended up)
 *
 * Three layers:
 *  workdays
 *  peopleplans
 *  hourchanges
 *
 * The workdays are automatically generated, no input needed.
 * A PeoplePlan is which hours, which days, from when to when a person is going to work
 * An HourChange is a change for a specific hour, from a username (if any), to a username
 *
 * There should exists functions that:
 * Can tell which hours a person actually ended up owning, after changes
 * Can tell how a day will look, after changes
 *
 */

type TimeTableEngine struct {
	userState      *UserState
	timeTableState *TimeTableState
}

type TimeTableState struct {
	// TODO: Find out how you are going to store the plans in Redis
	plans *simpleredis.HashMap

	pool *simpleredis.ConnectionPool // A connection pool for Redis
}

func NewTimeTableEngine(userState *UserState) *TimeTableEngine {
	pool := userState.GetPool()
	timeTableState := new(TimeTableState)

	timeTableState.plans = simpleredis.NewHashMap(pool, "plans")

	timeTableState.pool = pool
	return &TimeTableEngine{userState, timeTableState}
}

func (tte *TimeTableEngine) ServePages(basecp BaseCP, menuEntries MenuEntries) {
	timeTableCP := basecp(tte.userState)

	timeTableCP.ContentTitle = "TimeTable"
	timeTableCP.ExtraCSSurls = append(timeTableCP.ExtraCSSurls, "/css/timetable.css")

	tvgf := DynamicMenuFactoryGenerator(menuEntries)
	tvg := tvgf(tte.userState)

	web.Get("/timetable", tte.GenerateTimeTableRedirect())                                  // Redirect to /timeTable/main
	web.Get("/timetable/(.*)", timeTableCP.WrapWebHandle(tte.GenerateShowTimeTable(), tvg)) // Displaying timeTable pages
	web.Get("/css/timetable.css", tte.GenerateCSS(timeTableCP.ColorScheme))                 // CSS that is specific for timeTable pages
}

func RenderWeekFrom(t time.Time, locale string) string {

	cal, err := moskus.NewCalendar(locale, true)
	if err != nil {
		panic("Could not create a calendar for locale " + locCode + "!")
	}

	retval := ""
	retval += "
"

	// Headers
	retval += "
"
	retval += "	"

	// Loop through 7 days from the given date
	current := t
	for i := 0; i < 7; i++ {

		// Cell
		retval += ""

		// Contents
		retval += Num2dd(current.Day()) + ". " + cal.MonthName(current.Month())

		// End of cell
		retval += "	"

		// Advance to the next day
		current = current.AddDate(0, 0, 1)
	}

	// End of headers
	retval += "
"

	// Each row is an hour
	for hour := 8; hour < 22; hour++ {
		retval += "
"

		// Each column is a day
		retval += "kl. " + Num2dd(hour) + ":00	"

		// Loop through 7 days from the given date
		current := t
		for i := 0; i < 7; i++ {

			// Cell
			retval += ""

			// Contents
			retval += "FREE"

			// End of cell
			retval += "	"

			// Advance to the next day
			current = current.AddDate(0, 0, 1)
		}

		retval += "
"
	}

	retval += "
"
	return retval
}

// Convert from a number to a double digit string
func Num2dd(num int) string {
	s := strconv.Itoa(num)
	if len(s) == 1 {
		return "0" + s
	}
	return s
}

func (we *TimeTableEngine) GenerateShowTimeTable() WebHandle {
	return func(ctx *web.Context, userdate string) string {
		date := CleanUserInput(userdate)
		ymd := strings.Split(date, "-")
		if len(ymd) != 3 {
			return "Invalid yyyy-mm-dd: " + date
		}
		year, err := strconv.Atoi(ymd[0])
		if (err != nil) || (len(ymd[0]) != 4) {
			return "Invalid year: " + ymd[0]
		}
		month, err := strconv.Atoi(ymd[1])
		if (err != nil) || (len(ymd[1]) > 2) {
			return "Invalid month: " + ymd[1]
		}
		day, err := strconv.Atoi(ymd[2])
		if (err != nil) || (len(ymd[2]) > 2) {
			return "Invalid day: " + ymd[2]
		}
		retval := ""
		retval += "
En uke fra " + strconv.Itoa(year) + "-" + Num2dd(month) + "-" + Num2dd(day) + "
"

		weekstart := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)

		retval += RenderWeekFrom(weekstart, "nb_NO")
		retval += BackButton()
		return retval
	}
}

func (we *TimeTableEngine) GenerateTimeTableRedirect() SimpleContextHandle {
	return func(ctx *web.Context) string {
		t := time.Now()
		// Redirect to the current date on the form yyyy-mm-dd
		ctx.SetHeader("Refresh", "0; url=/timetable/"+t.String()[:10], true)
		return ""
	}
}

func (tte *TimeTableEngine) GenerateCSS(cs *ColorScheme) SimpleContextHandle {
	return func(ctx *web.Context) string {
		ctx.ContentType("css")
		return `
.even {
	background-color: "a0a0a0;
}
.odd {
	background-color: #f0f0f0;
}
.yes {
	background-color: #90ff90;
	color: black;
}
.no {
	background-color: #ff9090;
	color: black;
}
table {
	border-collapse: collapse;
	padding: 1em;
	margin-top: 1.5em;
	margin-bottom: 1em;
}
table, th, tr, td {
	border: 1px solid black;
	padding: 1em;
}

.username:link { color: green; }
.username:visited { color: green; }
.username:hover { color: green; }
.username:active { color: green; }

.whitebg {
	background-color: white;
}

.darkgrey:link { color: #404040; }
.darkgrey:visited { color: #404040; }
.darkgrey:hover { color: #404040; }
.darkgrey:active { color: #404040; }

.somewhatcareful:link { color: #e09000; }
.somewhatcareful:visited { color: #e09000; }
.somewhatcareful:hover { color: #e09000; }
.somewhatcareful:active { color: #e09000; }

.careful:link { color: #e00000; }
.careful:visited { color: #e00000; }
.careful:hover { color: #e00000; }
.careful:active { color: #e00000; }

`
		//
	}
}
<commit_msg>Added dummy content<commit_after>package siteengines

import (
	"strconv"
	"strings"
	"time"

	"github.com/hoisie/web"
	. "github.com/xyproto/browserspeak"
	. "github.com/xyproto/genericsite"
	"github.com/xyproto/moskus"
	"github.com/xyproto/simpleredis"
	"github.com/xyproto/personplan"
)

// TODO: Rename this module to something more generic than TimeTable
// TODO: Use the personplan and moskus module
// TODO: Add the timeTable pages to the search engine somehow (and the other engines too, like the chat)

/* Structure (TODO: Look at personplan for how the structure ended up)
 *
 * Three layers:
 *  workdays
 *  peopleplans
 *  hourchanges
 *
 * The workdays are automatically generated, no input needed.
 * A PeoplePlan is which hours, which days, from when to when a person is going to work
 * An HourChange is a change for a specific hour, from a username (if any), to a username
 *
 * There should exists functions that:
 * Can tell which hours a person actually ended up owning, after changes
 * Can tell how a day will look, after changes
 *
 */

type TimeTableEngine struct {
	userState      *UserState
	timeTableState *TimeTableState
}

type TimeTableState struct {
	// TODO: Find out how you are going to store the plans in Redis
	plans *simpleredis.HashMap

	pool *simpleredis.ConnectionPool // A connection pool for Redis
}

func NewTimeTableEngine(userState *UserState) *TimeTableEngine {
	pool := userState.GetPool()
	timeTableState := new(TimeTableState)

	timeTableState.plans = simpleredis.NewHashMap(pool, "plans")

	timeTableState.pool = pool
	return &TimeTableEngine{userState, timeTableState}
}

func (tte *TimeTableEngine) ServePages(basecp BaseCP, menuEntries MenuEntries) {
	timeTableCP := basecp(tte.userState)

	timeTableCP.ContentTitle = "TimeTable"
	timeTableCP.ExtraCSSurls = append(timeTableCP.ExtraCSSurls, "/css/timetable.css")

	tvgf := DynamicMenuFactoryGenerator(menuEntries)
	tvg := tvgf(tte.userState)

	web.Get("/timetable", tte.GenerateTimeTableRedirect())                                  // Redirect to /timeTable/main
	web.Get("/timetable/(.*)", timeTableCP.WrapWebHandle(tte.GenerateShowTimeTable(), tvg)) // Displaying timeTable pages
	web.Get("/css/timetable.css", tte.GenerateCSS(timeTableCP.ColorScheme))                 // CSS that is specific for timeTable pages
}

func AllPlansDummyContent() *personplan.Plans {
	ppAlexander := personplan.NewPersonPlan("Alexander")
	ppAlexander.AddWorkday(time.Monday, 8, 15, "KNH")     // monday, from 8, up to 15
	ppAlexander.AddWorkday(time.Wednesday, 12, 17, "KOH") // wednesday, from 12, up to 17

	ppBob := personplan.NewPersonPlan("Bob")
	ppBob.AddWorkday(time.Monday, 9, 11, "KOH")   // monday, from 9, up to 11
	ppBob.AddWorkday(time.Thursday, 8, 10, "KNH") // wednesday, from 8, up to 10

	periodplan := personplan.NewSemesterPlan(2013, 1, 8)
	periodplan.AddPersonPlan(ppAlexander)
	periodplan.AddPersonPlan(ppBob)

	allPlans := personplan.NewPlans()
	allPlans.AddSemesterPlan(periodplan)

	return allPlans
}

func RenderWeekFrom(t time.Time, locale string) string {

	allPlans := AllPlansDummyContent()

	cal, err := moskus.NewCalendar(locale, true)
	if err != nil {
		panic("Could not create a calendar for locale " + locale + "!")
	}

	retval := ""
	retval += "
"

	// Headers
	retval += "
"
	retval += "	"

	// Loop through 7 days from the given date
	current := t
	for i := 0; i < 7; i++ {

		// Cell
		retval += ""

		// Contents
		retval += Num2dd(current.Day()) + ". " + cal.MonthName(current.Month())

		// End of cell
		retval += "	"

		// Advance to the next day
		current = current.AddDate(0, 0, 1)
	}

	// End of headers
	retval += "
"

	// Each row is an hour
	for hour := 8; hour < 22; hour++ {
		retval += "
"

		// Each column is a day
		retval += "kl. " + Num2dd(hour) + ":00	"

		// Loop through 7 days from the given date
		current := t
		for i := 0; i < 7; i++ {

			// Cell with contents
			red, desc, _ := cal.RedDay(current)
			if red {
				retval += "" + desc + "	"
			} else {
				retval += "" + allPlans.HTMLHourEvents(current) + "	"
			}

			// Advance to the next day
			current = current.AddDate(0, 0, 1)
		}

		retval += "
"
	}

	retval += "
"
	return retval
}

// Convert from a number to a double digit string
func Num2dd(num int) string {
	s := strconv.Itoa(num)
	if len(s) == 1 {
		return "0" + s
	}
	return s
}

func (we *TimeTableEngine) GenerateShowTimeTable() WebHandle {
	return func(ctx *web.Context, userdate string) string {
		date := CleanUserInput(userdate)
		ymd := strings.Split(date, "-")
		if len(ymd) != 3 {
			return "Invalid yyyy-mm-dd: " + date
		}
		year, err := strconv.Atoi(ymd[0])
		if (err != nil) || (len(ymd[0]) != 4) {
			return "Invalid year: " + ymd[0]
		}
		month, err := strconv.Atoi(ymd[1])
		if (err != nil) || (len(ymd[1]) > 2) {
			return "Invalid month: " + ymd[1]
		}
		day, err := strconv.Atoi(ymd[2])
		if (err != nil) || (len(ymd[2]) > 2) {
			return "Invalid day: " + ymd[2]
		}
		retval := ""
		retval += "
En uke fra " + strconv.Itoa(year) + "-" + Num2dd(month) + "-" + Num2dd(day) + "
"

		weekstart := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)

		retval += RenderWeekFrom(weekstart, "nb_NO")
		retval += BackButton()
		return retval
	}
}

func (we *TimeTableEngine) GenerateTimeTableRedirect() SimpleContextHandle {
	return func(ctx *web.Context) string {
		t := time.Now()
		// Redirect to the current date on the form yyyy-mm-dd
		ctx.SetHeader("Refresh", "0; url=/timetable/"+t.String()[:10], true)
		return ""
	}
}

func (tte *TimeTableEngine) GenerateCSS(cs *ColorScheme) SimpleContextHandle {
	return func(ctx *web.Context) string {
		ctx.ContentType("css")
		return `
.even {
	background-color: "a0a0a0;
}
.odd {
	background-color: #f0f0f0;
}
.yes {
	background-color: #90ff90;
	color: black;
}
.no {
	background-color: #ff9090;
	color: black;
}
table {
	border-collapse: collapse;
	padding: 1em;
	margin-top: 1.5em;
	margin-bottom: 1em;
}
table, th, tr, td {
	border: 1px solid black;
	padding: 1em;
}

.username:link { color: green; }
.username:visited { color: green; }
.username:hover { color: green; }
.username:active { color: green; }

.whitebg {
	background-color: white;
}

.darkgrey:link { color: #404040; }
.darkgrey:visited { color: #404040; }
.darkgrey:hover { color: #404040; }
.darkgrey:active { color: #404040; }

.somewhatcareful:link { color: #e09000; }
.somewhatcareful:visited { color: #e09000; }
.somewhatcareful:hover { color: #e09000; }
.somewhatcareful:active { color: #e09000; }

.careful:link { color: #e00000; }
.careful:visited { color: #e00000; }
.careful:hover { color: #e00000; }
.careful:active { color: #e00000; }

`
		//
	}
}
<|endoftext|>


<commit_before>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package components

import (
	"bytes"
	"encoding/binary"
	"fmt"

	"github.com/brocaar/lorawan"
)

type entryReadWriter struct {
	err  error
	data *bytes.Buffer
}

func NewEntryReadWriter(buf []byte) *entryReadWriter {
	return &entryReadWriter{
		err:  nil,
		data: new(bytes.Buffer),
	}
}

func (w *entryReadWriter) Write(data interface{}) {
	var raw []byte
	switch data.(type) {
	case []byte:
		raw = data.([]byte)
	case lorawan.AES128Key:
		data := data.(lorawan.AES128Key)
		raw = data[:]
	case lorawan.EUI64:
		data := data.(lorawan.EUI64)
		raw = data[:]
	case lorawan.DevAddr:
		data := data.(lorawan.DevAddr)
		raw = data[:]
	case string:
		raw = []byte(data.(string))
	default:
		panic(fmt.Errorf("Unreckognized data type: %v", data))
	}
	w.DirectWrite(uint16(len(raw)))
	w.DirectWrite(raw)
}

func (w *entryReadWriter) DirectWrite(data interface{}) {
	if w.err != nil {
		return
	}
	in := w.data.Next(w.data.Len())
	binary.Write(w.data, binary.BigEndian, in)
	w.err = binary.Write(w.data, binary.BigEndian, data)
}

func (w *entryReadWriter) Read(to func(data []byte)) {
	if w.err != nil {
		return
	}

	lenTo := new(uint16)
	if w.err = binary.Read(w.data, binary.BigEndian, lenTo); w.err != nil {
		return
	}
	to(w.data.Next(int(*lenTo)))
}

func (w entryReadWriter) Bytes() ([]byte, error) {
	if w.err != nil {
		return nil, w.err
	}
	return w.data.Bytes(), nil
}

func (w entryReadWriter) Err() error {
	return w.err
}
<commit_msg>[in memory storage] Fix typo in entryReadWriter<commit_after>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package components

import (
	"bytes"
	"encoding/binary"
	"fmt"

	"github.com/brocaar/lorawan"
)

type entryReadWriter struct {
	err  error
	data *bytes.Buffer
}

func NewEntryReadWriter(buf []byte) *entryReadWriter {
	return &entryReadWriter{
		err:  nil,
		data: bytes.NewBuffer(buf),
	}
}

func (w *entryReadWriter) Write(data interface{}) {
	var raw []byte
	switch data.(type) {
	case []byte:
		raw = data.([]byte)
	case lorawan.AES128Key:
		data := data.(lorawan.AES128Key)
		raw = data[:]
	case lorawan.EUI64:
		data := data.(lorawan.EUI64)
		raw = data[:]
	case lorawan.DevAddr:
		data := data.(lorawan.DevAddr)
		raw = data[:]
	case string:
		raw = []byte(data.(string))
	default:
		panic(fmt.Errorf("Unreckognized data type: %v", data))
	}
	w.DirectWrite(uint16(len(raw)))
	w.DirectWrite(raw)
}

func (w *entryReadWriter) DirectWrite(data interface{}) {
	if w.err != nil {
		return
	}
	in := w.data.Next(w.data.Len())
	w.data = new(bytes.Buffer)
	binary.Write(w.data, binary.BigEndian, in)
	w.err = binary.Write(w.data, binary.BigEndian, data)
}

func (w *entryReadWriter) Read(to func(data []byte)) {
	if w.err != nil {
		return
	}

	lenTo := new(uint16)
	if w.err = binary.Read(w.data, binary.BigEndian, lenTo); w.err != nil {
		return
	}
	to(w.data.Next(int(*lenTo)))
}

func (w entryReadWriter) Bytes() ([]byte, error) {
	if w.err != nil {
		return nil, w.err
	}
	return w.data.Bytes(), nil
}

func (w entryReadWriter) Err() error {
	return w.err
}
<|endoftext|>


<commit_before>//----------------------------------------
//
// Copyright © ying32. All Rights Reserved.
//
// Licensed under Apache License 2.0
//
//----------------------------------------

// 实现一些Go的Image转vcl/lcl的
package bitmap

import (
	"bytes"
	"errors"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"unsafe"

	"github.com/ying32/govcl/vcl/types"

	"github.com/ying32/govcl/vcl"
)

var (
	ErrPixelDataEmpty        = errors.New("the pixel data is empty")
	ErrUnsupportedDataFormat = errors.New("unsupported pixel data format")
	ErrBitmapInvalid         = errors.New("bitmap invalid")
	ErrObjIsInvalid          = errors.New("obj is invalid")
	ErrUnknownError          = errors.New("unknown error")
)

// CN: 将Go的Image转为VCL/LCL的 TPngImage
//     返回的Png对象用完记得Free掉
// EN: Convert the image of go to TPngImage of VCL/LCL
//     Remember to free the returned png object
func ToPngImage(img image.Image) (*vcl.TPngImage, error) {
	buff := bytes.NewBuffer([]byte{})
	if err := png.Encode(buff, img); err != nil {
		return nil, err
	}
	mem := vcl.NewMemoryStreamFromBytes(buff.Bytes())
	defer mem.Free()
	mem.SetPosition(0)
	obj := vcl.NewPngImage()
	obj.LoadFromStream(mem)
	return obj, nil
}

// CN: 32bit bmp
//     返回的Bmp对象用完记得Free掉
// EN: 32bit bmp
//     Remember to free the returned bmp object
func ToBitmap(img image.Image) (*vcl.TBitmap, error) {
	bmp := vcl.NewBitmap()
	if err := ToBitmap2(img, bmp); err != nil {
		defer bmp.Free()
		return nil, err
	}
	return bmp, nil
}

func ToBitmap2(img image.Image, bmp *vcl.TBitmap) error {
	if bmp == nil || !bmp.IsValid() {
		return ErrBitmapInvalid
	}
	switch img.(type) {
	case *image.RGBA:
		data, _ := img.(*image.RGBA)
		err := toBitmap(img.Bounds().Size().X, img.Bounds().Size().Y, data.Pix, bmp)
		if err != nil {
			return err
		}
	case *image.NRGBA:
		data, _ := img.(*image.NRGBA)
		err := toBitmap(img.Bounds().Size().X, img.Bounds().Size().Y, data.Pix, bmp)
		if err != nil {
			return err
		}
	default:
		return ErrUnsupportedDataFormat
	}
	return nil
}

// CN: 将Go的Image转为VCL/LCL的 TJPEGImage
//     返回的jpg对象用完记得Free掉
// EN: Convert the image of go to TJPEGImage of VCL/LCL
//     Remember to free the returned jpg object
func ToJPEGImage(img image.Image, quality int) (*vcl.TJPEGImage, error) {
	buff := bytes.NewBuffer([]byte{})
	if err := jpeg.Encode(buff, img, &jpeg.Options{quality}); err != nil {
		return nil, err
	}
	mem := vcl.NewMemoryStreamFromBytes(buff.Bytes())
	defer mem.Free()
	mem.SetPosition(0)
	obj := vcl.NewJPEGImage()
	obj.LoadFromStream(mem)
	return obj, nil
}

// CN: 将Go的Image转为VCL/LCL的 TGIFImage
//     返回的gif对象用完记得Free掉
// EN: Convert the image of go to TGIFImage of VCL/LCL
//     Remember to free the returned GIF object
func ToGIFImage(img image.Image) (*vcl.TGIFImage, error) {
	buff := bytes.NewBuffer([]byte{})
	if err := gif.Encode(buff, img, &gif.Options{NumColors: 256}); err != nil {
		return nil, err
	}
	mem := vcl.NewMemoryStreamFromBytes(buff.Bytes())
	defer mem.Free()
	mem.SetPosition(0)
	obj := vcl.NewGIFImage()
	obj.LoadFromStream(mem)
	return obj, nil
}

func toBitmap(width, height int, pix []uint8, bmp *vcl.TBitmap) error {
	if len(pix) == 0 {
		return ErrPixelDataEmpty
	}
	if bmp == nil || !bmp.IsValid() {
		return ErrBitmapInvalid
	}

	bmp.SetSize(int32(width), int32(height))
	// 总是32位，不然没办法透明。
	bmp.SetPixelFormat(types.Pf32bit)
	bmp.SetHandleType(types.BmDIB)

	bmp.BeginUpdate(false)
	defer bmp.EndUpdate(false)

	// 填充，左下角为起点
	for h := height - 1; h >= 0; h-- {
		ptr := bmp.ScanLine(int32(h))
		for w := 0; w < width; w++ {
			index := (h*width + w) * 4
			c := (*rgba)(unsafe.Pointer(ptr + uintptr(w*4)))
			c.R = pix[index+0]
			c.G = pix[index+1]
			c.B = pix[index+2]
			c.A = pix[index+3]
		}
	}
	return nil
}

// CN: 将vcl/lcl的Graphic对象转为Go的Image
// EN: Convert the graphic object of VCL/LCL to the image of go
func ToGoImage(obj *vcl.TGraphic) (image.Image, error) {
	if obj == nil {
		return nil, ErrObjIsInvalid
	}
	if !obj.IsValid() {
		return nil, ErrObjIsInvalid
	}
	buff := bytes.NewBuffer([]byte{})
	mem := vcl.NewMemoryStream()
	defer mem.Free()
	obj.SaveToStream(mem)
	mem.SetPosition(0)
	_, bs := mem.Read(int32(mem.Size()))
	buff.Write(bs)
	if obj.Is().Bitmap() {
		height := int(obj.Height())
		width := int(obj.Width())
		img := image.NewRGBA(image.Rect(0, 0, width, height))

		bmp := vcl.AsBitmap(obj)
		switch bmp.PixelFormat() {

		// 还有待测试。。。
		case types.Pf24bit:

			for h := height - 1; h >= 0; h-- {
				ptr := bmp.ScanLine(int32(h))
				for w := 0; w < width; w++ {
					index := (h*width + w) * 4
					c := (*rgb)(unsafe.Pointer(ptr + uintptr(w*3)))
					img.Pix[index+0] = c.R
					img.Pix[index+1] = c.G
					img.Pix[index+2] = c.B
					img.Pix[index+3] = 0
				}
			}

		case types.Pf32bit:
			for h := height - 1; h >= 0; h-- {
				ptr := bmp.ScanLine(int32(h))
				for w := 0; w < width; w++ {
					index := (h*width + w) * 4
					c := (*rgba)(unsafe.Pointer(ptr + uintptr(w*4)))
					img.Pix[index+0] = c.R
					img.Pix[index+1] = c.G
					img.Pix[index+2] = c.B
					img.Pix[index+3] = c.A
				}
			}
		default:
			//case types.Pf1bit:
			//case types.Pf4bit:
			//case types.Pf8bit:
			//case types.Pf15bit:
			//case types.Pf16bit:
			//case types.PfCustom:
			return nil, ErrUnsupportedDataFormat
		}
		return img, nil
	} else if obj.Is().PngImage() {
		img, err := png.Decode(buff)
		if err != nil {
			return nil, err
		}
		return img, nil
	} else if obj.Is().JPEGImage() {
		img, err := jpeg.Decode(buff)
		if err != nil {
			return nil, err
		}
		return img, nil
	} else if obj.Is().GIFImage() {
		img, err := gif.Decode(buff)
		if err != nil {
			return nil, err
		}
		return img, nil
	}
	return nil, ErrUnknownError
}
<commit_msg>Update<commit_after>//----------------------------------------
//
// Copyright © ying32. All Rights Reserved.
//
// Licensed under Apache License 2.0
//
//----------------------------------------

// 实现一些Go的Image转vcl/lcl的
package bitmap

import (
	"bytes"
	"errors"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"unsafe"

	"github.com/ying32/govcl/vcl/types"

	"github.com/ying32/govcl/vcl"
)

var (
	ErrPixelDataEmpty        = errors.New("the pixel data is empty")
	ErrUnsupportedDataFormat = errors.New("unsupported pixel data format")
	ErrBitmapInvalid         = errors.New("bitmap invalid")
	ErrObjIsInvalid          = errors.New("object is invalid")
	ErrUnknownError          = errors.New("unknown error")
)

// CN: 将Go的Image转为VCL/LCL的 TPngImage
//     返回的Png对象用完记得Free掉
// EN: Convert the image of go to TPngImage of VCL/LCL
//     Remember to free the returned png object
func ToPngImage(img image.Image) (*vcl.TPngImage, error) {
	buff := bytes.NewBuffer([]byte{})
	if err := png.Encode(buff, img); err != nil {
		return nil, err
	}
	mem := vcl.NewMemoryStreamFromBytes(buff.Bytes())
	defer mem.Free()
	mem.SetPosition(0)
	obj := vcl.NewPngImage()
	obj.LoadFromStream(mem)
	return obj, nil
}

// CN: 32bit bmp
//     返回的Bmp对象用完记得Free掉
// EN: 32bit bmp
//     Remember to free the returned bmp object
func ToBitmap(img image.Image) (*vcl.TBitmap, error) {
	bmp := vcl.NewBitmap()
	if err := ToBitmap2(img, bmp); err != nil {
		defer bmp.Free()
		return nil, err
	}
	return bmp, nil
}

func ToBitmap2(img image.Image, bmp *vcl.TBitmap) error {
	if bmp == nil || !bmp.IsValid() {
		return ErrBitmapInvalid
	}
	switch img.(type) {
	case *image.RGBA:
		data, _ := img.(*image.RGBA)
		err := toBitmap(img.Bounds().Size().X, img.Bounds().Size().Y, data.Pix, bmp)
		if err != nil {
			return err
		}
	case *image.NRGBA:
		data, _ := img.(*image.NRGBA)
		err := toBitmap(img.Bounds().Size().X, img.Bounds().Size().Y, data.Pix, bmp)
		if err != nil {
			return err
		}
	default:
		return ErrUnsupportedDataFormat
	}
	return nil
}

// CN: 将Go的Image转为VCL/LCL的 TJPEGImage
//     返回的jpg对象用完记得Free掉
// EN: Convert the image of go to TJPEGImage of VCL/LCL
//     Remember to free the returned jpg object
func ToJPEGImage(img image.Image, quality int) (*vcl.TJPEGImage, error) {
	buff := bytes.NewBuffer([]byte{})
	if err := jpeg.Encode(buff, img, &jpeg.Options{quality}); err != nil {
		return nil, err
	}
	mem := vcl.NewMemoryStreamFromBytes(buff.Bytes())
	defer mem.Free()
	mem.SetPosition(0)
	obj := vcl.NewJPEGImage()
	obj.LoadFromStream(mem)
	return obj, nil
}

// CN: 将Go的Image转为VCL/LCL的 TGIFImage
//     返回的gif对象用完记得Free掉
// EN: Convert the image of go to TGIFImage of VCL/LCL
//     Remember to free the returned GIF object
func ToGIFImage(img image.Image) (*vcl.TGIFImage, error) {
	buff := bytes.NewBuffer([]byte{})
	if err := gif.Encode(buff, img, &gif.Options{NumColors: 256}); err != nil {
		return nil, err
	}
	mem := vcl.NewMemoryStreamFromBytes(buff.Bytes())
	defer mem.Free()
	mem.SetPosition(0)
	obj := vcl.NewGIFImage()
	obj.LoadFromStream(mem)
	return obj, nil
}

func toBitmap(width, height int, pix []uint8, bmp *vcl.TBitmap) error {
	if len(pix) == 0 {
		return ErrPixelDataEmpty
	}
	if bmp == nil || !bmp.IsValid() {
		return ErrBitmapInvalid
	}

	bmp.SetSize(int32(width), int32(height))
	// 总是32位，不然没办法透明。
	bmp.SetPixelFormat(types.Pf32bit)
	bmp.SetHandleType(types.BmDIB)

	bmp.BeginUpdate(false)
	defer bmp.EndUpdate(false)

	// 填充，左下角为起点
	for h := height - 1; h >= 0; h-- {
		ptr := bmp.ScanLine(int32(h))
		for w := 0; w < width; w++ {
			index := (h*width + w) * 4
			c := (*rgba)(unsafe.Pointer(ptr + uintptr(w*4)))
			c.R = pix[index+0]
			c.G = pix[index+1]
			c.B = pix[index+2]
			c.A = pix[index+3]
		}
	}
	return nil
}

// CN: 将vcl/lcl的Graphic对象转为Go的Image
// EN: Convert the graphic object of VCL/LCL to the image of go
func ToGoImage(obj *vcl.TGraphic) (image.Image, error) {
	if obj == nil {
		return nil, ErrObjIsInvalid
	}
	if !obj.IsValid() {
		return nil, ErrObjIsInvalid
	}
	buff := bytes.NewBuffer([]byte{})
	mem := vcl.NewMemoryStream()
	defer mem.Free()
	obj.SaveToStream(mem)
	mem.SetPosition(0)
	_, bs := mem.Read(int32(mem.Size()))
	buff.Write(bs)
	if obj.Is().Bitmap() {
		height := int(obj.Height())
		width := int(obj.Width())
		img := image.NewRGBA(image.Rect(0, 0, width, height))

		bmp := vcl.AsBitmap(obj)
		switch bmp.PixelFormat() {

		// 还有待测试。。。
		case types.Pf24bit:

			for h := height - 1; h >= 0; h-- {
				ptr := bmp.ScanLine(int32(h))
				for w := 0; w < width; w++ {
					index := (h*width + w) * 4
					c := (*rgb)(unsafe.Pointer(ptr + uintptr(w*3)))
					img.Pix[index+0] = c.R
					img.Pix[index+1] = c.G
					img.Pix[index+2] = c.B
					img.Pix[index+3] = 0
				}
			}

		case types.Pf32bit:
			for h := height - 1; h >= 0; h-- {
				ptr := bmp.ScanLine(int32(h))
				for w := 0; w < width; w++ {
					index := (h*width + w) * 4
					c := (*rgba)(unsafe.Pointer(ptr + uintptr(w*4)))
					img.Pix[index+0] = c.R
					img.Pix[index+1] = c.G
					img.Pix[index+2] = c.B
					img.Pix[index+3] = c.A
				}
			}
		default:
			//case types.Pf1bit:
			//case types.Pf4bit:
			//case types.Pf8bit:
			//case types.Pf15bit:
			//case types.Pf16bit:
			//case types.PfCustom:
			return nil, ErrUnsupportedDataFormat
		}
		return img, nil
	} else if obj.Is().PngImage() {
		img, err := png.Decode(buff)
		if err != nil {
			return nil, err
		}
		return img, nil
	} else if obj.Is().JPEGImage() {
		img, err := jpeg.Decode(buff)
		if err != nil {
			return nil, err
		}
		return img, nil
	} else if obj.Is().GIFImage() {
		img, err := gif.Decode(buff)
		if err != nil {
			return nil, err
		}
		return img, nil
	}
	return nil, ErrUnknownError
}
<|endoftext|>


<commit_before>package end2end_carbon

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"testing"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/grafana/metrictank/stacktest/docker"
	"github.com/grafana/metrictank/stacktest/fakemetrics"
	"github.com/grafana/metrictank/stacktest/grafana"
	"github.com/grafana/metrictank/stacktest/graphite"
	"github.com/grafana/metrictank/stacktest/track"
)

// TODO: cleanup when ctrl-C go test (teardown all containers)

var tracker *track.Tracker
var fm *fakemetrics.FakeMetrics

const metricsPerSecond = 1000

func TestMain(m *testing.M) {
	fmt.Println("stopping docker-dev stack should it be running...")
	cmd := exec.Command("docker-compose", "down")
	cmd.Dir = docker.Path("docker/docker-dev")
	var err error
	tracker, err = track.NewTracker(cmd, false, false, "compose-down-stdout", "compose-down-stderr")
	if err != nil {
		log.Fatal(err)
	}
	err = cmd.Start()
	if err != nil {
		log.Fatal(err)
	}
	// note: even when we don't care about the output, it's best to consume it before calling cmd.Wait()
	// even though the cmd.Wait docs say it will wait for stdout/stderr copying to complete
	// however the docs for cmd.StdoutPipe say "it is incorrect to call Wait before all reads from the pipe have completed"
	tracker.Wait()
	if err := cmd.Wait(); err != nil {
		log.Printf("ERROR: could not cleanly shutdown running docker-compose down command: %s", err)
		os.Exit(2)
	}

	fmt.Println("launching docker-dev stack...")
	cmd = exec.Command(docker.Path("docker/launch.sh"), "docker-dev")

	tracker, err = track.NewTracker(cmd, false, false, "launch-stdout", "launch-stderr")
	if err != nil {
		log.Fatal(err)
	}

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

	retcode := m.Run()
	fm.Close()

	fmt.Println("stopping docker-compose stack...")
	cmd.Process.Signal(syscall.SIGINT)
	tracker.Wait()
	if err := cmd.Wait(); err != nil {
		// 130 means ctrl-C (interrupt) which is what we want
		if err.Error() == "exit status 130" {
			os.Exit(retcode)
		}
		log.Printf("ERROR: could not cleanly shutdown running docker-compose command: %s", err)
		retcode = 1
	}

	os.Exit(retcode)
}

func TestStartup(t *testing.T) {
	matchers := []track.Matcher{
		{Str: "metrictank.*metricIndex initialized.*starting data consumption$"},
		{Str: "metrictank.*carbon-in: listening on.*2003"},
		{Str: "grafana.*Initializing HTTP Server.*:3000"},
	}
	select {
	case <-tracker.Match(matchers):
		fmt.Println("stack now running.")
		fmt.Println("Go to http://localhost:3000 (and login as admin:admin) to see what's going on")
	case <-time.After(time.Second * 40):
		grafana.PostAnnotation("TestStartup:FAIL")
		t.Fatal("timed out while waiting for all metrictank instances to come up")
	}
}

func TestBaseIngestWorkload(t *testing.T) {
	grafana.PostAnnotation("TestBaseIngestWorkload:begin")

	fm = fakemetrics.NewCarbon(metricsPerSecond)

	suc6, resp := graphite.RetryGraphite8080("perSecond(metrictank.stats.docker-env.*.input.carbon.metrics_received.counter32)", "-8s", 18, func(resp graphite.Response) bool {
		exp := []string{
			"perSecond(metrictank.stats.docker-env.default.input.carbon.metrics_received.counter32)",
		}
		a := graphite.ValidateTargets(exp)(resp)
		b := graphite.ValidatorLenNulls(1, 8)(resp)
		c := graphite.ValidatorAvgWindowed(8, graphite.Ge(metricsPerSecond))(resp)
		log.Printf("condition target names %t - condition len & nulls %t - condition avg value %t", a, b, c)
		return a && b && c
	})
	if !suc6 {
		grafana.PostAnnotation("TestBaseIngestWorkload:FAIL")
		t.Fatalf("cluster did not reach a state where the MT instance processes at least %d points per second. last response was: %s", metricsPerSecond, spew.Sdump(resp))
	}
}
<commit_msg>no need to run `docker-compose down` since launch.sh already does it<commit_after>package end2end_carbon

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"testing"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/grafana/metrictank/stacktest/docker"
	"github.com/grafana/metrictank/stacktest/fakemetrics"
	"github.com/grafana/metrictank/stacktest/grafana"
	"github.com/grafana/metrictank/stacktest/graphite"
	"github.com/grafana/metrictank/stacktest/track"
)

// TODO: cleanup when ctrl-C go test (teardown all containers)

var tracker *track.Tracker
var fm *fakemetrics.FakeMetrics

const metricsPerSecond = 1000

func TestMain(m *testing.M) {
	fmt.Println("launching docker-dev stack...")
	cmd := exec.Command(docker.Path("docker/launch.sh"), "docker-dev")
	var err error

	tracker, err = track.NewTracker(cmd, false, false, "launch-stdout", "launch-stderr")
	if err != nil {
		log.Fatal(err)
	}

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

	retcode := m.Run()
	fm.Close()

	fmt.Println("stopping docker-compose stack...")
	cmd.Process.Signal(syscall.SIGINT)
	// note: even when we don't care about the output, it's best to consume it before calling cmd.Wait()
	// even though the cmd.Wait docs say it will wait for stdout/stderr copying to complete
	// however the docs for cmd.StdoutPipe say "it is incorrect to call Wait before all reads from the pipe have completed"
	tracker.Wait()
	if err := cmd.Wait(); err != nil {
		// 130 means ctrl-C (interrupt) which is what we want
		if err.Error() == "exit status 130" {
			os.Exit(retcode)
		}
		log.Printf("ERROR: could not cleanly shutdown running docker-compose command: %s", err)
		retcode = 1
	}

	os.Exit(retcode)
}

func TestStartup(t *testing.T) {
	matchers := []track.Matcher{
		{Str: "metrictank.*metricIndex initialized.*starting data consumption$"},
		{Str: "metrictank.*carbon-in: listening on.*2003"},
		{Str: "grafana.*Initializing HTTP Server.*:3000"},
	}
	select {
	case <-tracker.Match(matchers):
		fmt.Println("stack now running.")
		fmt.Println("Go to http://localhost:3000 (and login as admin:admin) to see what's going on")
	case <-time.After(time.Second * 40):
		grafana.PostAnnotation("TestStartup:FAIL")
		t.Fatal("timed out while waiting for all metrictank instances to come up")
	}
}

func TestBaseIngestWorkload(t *testing.T) {
	grafana.PostAnnotation("TestBaseIngestWorkload:begin")

	fm = fakemetrics.NewCarbon(metricsPerSecond)

	suc6, resp := graphite.RetryGraphite8080("perSecond(metrictank.stats.docker-env.*.input.carbon.metrics_received.counter32)", "-8s", 18, func(resp graphite.Response) bool {
		exp := []string{
			"perSecond(metrictank.stats.docker-env.default.input.carbon.metrics_received.counter32)",
		}
		a := graphite.ValidateTargets(exp)(resp)
		b := graphite.ValidatorLenNulls(1, 8)(resp)
		c := graphite.ValidatorAvgWindowed(8, graphite.Ge(metricsPerSecond))(resp)
		log.Printf("condition target names %t - condition len & nulls %t - condition avg value %t", a, b, c)
		return a && b && c
	})
	if !suc6 {
		grafana.PostAnnotation("TestBaseIngestWorkload:FAIL")
		t.Fatalf("cluster did not reach a state where the MT instance processes at least %d points per second. last response was: %s", metricsPerSecond, spew.Sdump(resp))
	}
}
<|endoftext|>


<commit_before><commit_msg>[game] wipe the undo movement stack if undo is called<commit_after><|endoftext|>


<commit_before><commit_msg>Make KeepFiles a slice of filenames<commit_after><|endoftext|>