<commit_before>/*
Copyright 2019 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"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/blang/semver"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"gopkg.in/russross/blackfriday.v2"

	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/notes"
	"k8s.io/release/pkg/notes/options"
	"k8s.io/release/pkg/util"
)

// changelogCmd represents the subcommand for `krel changelog`
var changelogCmd = &cobra.Command{
	Use:   "changelog",
	Short: "changelog maintains the lifecycle of CHANGELOG-x.y.{md,html} files",
	Long: `krel changelog

The 'changelog' subcommand of 'krel' does the following things by utilizing
the golang based 'release-notes' tool:

1. Generate the release notes for either a patch or a new minor release. Minor
   releases can be alpha, beta or rc’s, too.
   a) Create a new CHANGELOG-x.y.md file if not existing.
   b) Correctly prepend the generated notes to the existing CHANGELOG-x.y.md
      file if already existing. This also includes the modification of the
	  table of contents.

2. Convert the markdown release notes into a HTML equivalent on purpose of
   sending it by mail to the announce list. The HTML file will be dropped into
   the current working directly as 'CHANGELOG-x.y.html'. Sending the
   announcement is done by another subcommand of 'krel', not "changelog'.

3. Commit the modified CHANGELOG-x.y.md into the master branch as well as the
   corresponding release-branch of kubernetes/kubernetes. The release branch
   will be pruned from all other CHANGELOG-*.md files which do not belong to
   this release branch.
`,
	SilenceUsage:  true,
	SilenceErrors: true,
	PreRunE:       initLogging,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runChangelog()
	},
}

type changelogOptions struct {
	tag      string
	bucket   string
	tars     string
	token    string
	htmlFile string
}

var changelogOpts = &changelogOptions{}

const (
	tocStart = "<!-- BEGIN MUNGE: GENERATED_TOC -->"
	tocEnd   = "<!-- END MUNGE: GENERATED_TOC -->"
)

func init() {
	cobra.OnInitialize(initConfig)

	const (
		tagFlag   = "tag"
		tarsFlag  = "tars"
		tokenFlag = "token"
	)
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.bucket, "bucket", "kubernetes-release", "Specify gs bucket to point to in generated notes")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.tag, tagFlag, "", "The version tag of the release, for example v1.17.0-rc.1")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.tars, tarsFlag, "", "Directory of tars to SHA512 sum for display")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.htmlFile, "html-file", "", "The target html file to be written. If empty, then it will be CHANGELOG-x.y.html in the current path.")

	if err := changelogCmd.MarkPersistentFlagRequired(tagFlag); err != nil {
		logrus.Fatal(err)
	}
	if err := changelogCmd.MarkPersistentFlagRequired(tarsFlag); err != nil {
		logrus.Fatal(err)
	}

	rootCmd.AddCommand(changelogCmd)
}

func runChangelog() (err error) {
	token, ok := os.LookupEnv("GITHUB_TOKEN")
	if !ok {
		return errors.New("environment variable `GITHUB_TOKEN` is not set but needed for release notes generation")
	}
	changelogOpts.token = token

	tag, err := semver.Make(util.TrimTagPrefix(changelogOpts.tag))
	if err != nil {
		return err
	}
	branch := fmt.Sprintf("release-%d.%d", tag.Major, tag.Minor)
	logrus.Infof("Using release branch %s", branch)

	logrus.Infof("Using local repository path %s", rootOpts.repoPath)
	repo, err := git.CloneOrOpenDefaultGitHubRepoSSH(rootOpts.repoPath, git.DefaultGithubOrg)
	if err != nil {
		return err
	}

	var markdown string
	if tag.Patch == 0 {
		if len(tag.Pre) == 0 {
			// New final minor versions should have remote release notes
			markdown, err = lookupRemoteReleaseNotes(branch)
		} else {
			// New minor alphas, betas and rc get generated notes
			start, e := repo.PreviousTag(changelogOpts.tag, branch)
			if e != nil {
				return e
			}
			logrus.Infof("Found previous tag %s", start)
			markdown, err = generateReleaseNotes(branch, start, changelogOpts.tag)
		}
	} else {
		// A patch version, let’s just use the previous patch
		start := util.AddTagPrefix(semver.Version{
			Major: tag.Major, Minor: tag.Minor, Patch: tag.Patch - 1,
		}.String())

		markdown, err = generateReleaseNotes(branch, start, changelogOpts.tag)
	}
	if err != nil {
		return err
	}

	logrus.Info("Generating TOC")
	toc, err := notes.GenerateTOC(markdown)
	if err != nil {
		return err
	}

	// Restore the currently checked out branch
	currentBranch, err := repo.CurrentBranch()
	if err != nil {
		return err
	}
	defer func() {
		if err := repo.CheckoutBranch(currentBranch); err != nil {
			logrus.Errorf("unable to restore branch %s: %v", currentBranch, err)
		}
	}()

	if err := repo.CheckoutBranch(git.Master); err != nil {
		return errors.Wrap(err, "checking out master branch")
	}

	if err := writeMarkdown(repo, toc, markdown, tag); err != nil {
		return err
	}

	if err := writeHTML(tag, markdown); err != nil {
		return err
	}

	return commitChanges(repo, branch, tag)
}

func generateReleaseNotes(branch, startRev, endRev string) (string, error) {
	logrus.Info("Generating release notes")

	notesOptions := options.New()
	notesOptions.Branch = branch
	notesOptions.StartRev = startRev
	notesOptions.EndRev = endRev
	notesOptions.GithubOrg = git.DefaultGithubOrg
	notesOptions.GithubRepo = git.DefaultGithubRepo
	notesOptions.GithubToken = changelogOpts.token
	notesOptions.RepoPath = rootOpts.repoPath
	notesOptions.ReleaseBucket = changelogOpts.bucket
	notesOptions.ReleaseTars = changelogOpts.tars
	notesOptions.Debug = logrus.StandardLogger().Level >= logrus.DebugLevel

	if err := notesOptions.ValidateAndFinish(); err != nil {
		return "", err
	}

	gatherer := notes.NewGatherer(context.Background(), notesOptions)
	releaseNotes, history, err := gatherer.ListReleaseNotes()
	if err != nil {
		return "", errors.Wrapf(err, "listing release notes")
	}

	// Create the markdown
	doc, err := notes.CreateDocument(releaseNotes, history)
	if err != nil {
		return "", errors.Wrapf(err, "creating release note document")
	}

	markdown, err := notes.RenderMarkdown(
		doc, changelogOpts.bucket, changelogOpts.tars,
		notesOptions.StartRev, notesOptions.EndRev,
	)
	if err != nil {
		return "", errors.Wrapf(
			err, "rendering release notes to markdown",
		)
	}

	return markdown, nil
}

func writeMarkdown(repo *git.Repo, toc, markdown string, tag semver.Version) error {
	changelogPath := markdownChangelogFilename(repo, tag)
	writeFile := func(t, m string) error {
		return ioutil.WriteFile(
			changelogPath, []byte(strings.Join(
				[]string{addTocMarkers(t), strings.TrimSpace(m)}, "\n",
			)), 0o644,
		)
	}

	// No changelog exists, simply write the content to a new one
	if _, err := os.Stat(changelogPath); os.IsNotExist(err) {
		logrus.Infof("Changelog %q does not exist, creating it", changelogPath)
		return writeFile(toc, markdown)
	}

	// Changelog seems to exist, prepend the notes and re-generate the TOC
	logrus.Infof("Adding new content to changelog file %s ", changelogPath)
	content, err := ioutil.ReadFile(changelogPath)
	if err != nil {
		return err
	}

	tocEndIndex := bytes.Index(content, []byte(tocEnd))
	if tocEndIndex < 0 {
		return errors.Errorf(
			"unable to find table of contents end marker `%s` in %q",
			tocEnd, changelogPath,
		)
	}

	mergedMarkdown := fmt.Sprintf(
		"%s\n%s",
		strings.TrimSpace(markdown),
		string(content[(len(tocEnd)+tocEndIndex):]),
	)
	mergedTOC, err := notes.GenerateTOC(mergedMarkdown)
	if err != nil {
		return err
	}
	return writeFile(mergedTOC, mergedMarkdown)
}

func htmlChangelogFilename(tag semver.Version) string {
	if changelogOpts.htmlFile != "" {
		return changelogOpts.htmlFile
	}
	return changelogFilename(tag, "html")
}

func markdownChangelogFilename(repo *git.Repo, tag semver.Version) string {
	return filepath.Join(repo.Dir(), changelogFilename(tag, "md"))
}

func changelogFilename(tag semver.Version, ext string) string {
	return fmt.Sprintf("CHANGELOG-%d.%d.%s", tag.Major, tag.Minor, ext)
}

func addTocMarkers(toc string) string {
	return fmt.Sprintf("%s\n\n%s\n%s\n", tocStart, toc, tocEnd)
}

const htmlTemplate = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{{ .Title }}</title>
    <style type="text/css">
      table,
      th,
      tr,
      td {
        border: 1px solid gray;
        border-collapse: collapse;
        padding: 5px;
      }
    </style>
  </head>
  <body>
    {{ .Content }}
  </body>
</html>`

func writeHTML(tag semver.Version, markdown string) error {
	content := blackfriday.Run([]byte(markdown))

	t, err := template.New("html").Parse(htmlTemplate)
	if err != nil {
		return err
	}

	output := bytes.Buffer{}
	if err := t.Execute(&output, struct {
		Title, Content string
	}{util.AddTagPrefix(tag.String()), string(content)}); err != nil {
		return err
	}

	absOutputPath, err := filepath.Abs(htmlChangelogFilename(tag))
	if err != nil {
		return err
	}
	logrus.Infof("Writing single HTML to %s", absOutputPath)
	return ioutil.WriteFile(absOutputPath, output.Bytes(), 0o644)
}

func lookupRemoteReleaseNotes(branch string) (string, error) {
	logrus.Info("Assuming new minor release")

	remote := fmt.Sprintf(
		"https://raw.githubusercontent.com/kubernetes/sig-release/master/"+
			"releases/%s/release-notes-draft.md", branch,
	)
	resp, err := http.Get(remote)
	if err != nil {
		return "", errors.Wrapf(err,
			"fetching release notes from remote: %s", remote,
		)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", errors.Errorf(
			"remote release notes not found at: %s", remote,
		)
	}
	logrus.Info("Found release notes")

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(content), nil
}

func commitChanges(repo *git.Repo, branch string, tag semver.Version) error {
	// Master branch modifications
	filename := filepath.Base(markdownChangelogFilename(repo, tag))
	logrus.Infof("Adding %s to repository", filename)
	if err := repo.Add(filename); err != nil {
		return errors.Wrapf(err, "trying to add file %s to repository", filename)
	}

	logrus.Info("Committing changes to master branch in repository")
	if err := repo.Commit(fmt.Sprintf(
		"Add %s for %s", filename, util.AddTagPrefix(tag.String()),
	)); err != nil {
		return errors.Wrap(err, "committing changes into repository")
	}

	// Release branch modifications
	if err := repo.CheckoutBranch(branch); err != nil {
		return errors.Wrapf(err, "checking out release branch %s", branch)
	}

	// Remove all other changelog files
	if err := repo.Rm(true, "CHANGELOG-*.md"); err != nil {
		return errors.Wrap(err, "unable to remove CHANGELOG-*.md files")
	}

	logrus.Info("Checking out changelog from master branch")
	if err := repo.Checkout(git.Master, filename); err != nil {
		return errors.Wrap(err, "checking out master branch changelog")
	}

	logrus.Info("Committing changes to release branch in repository")
	if err := repo.Commit(fmt.Sprintf(
		"Update %s for %s", filename, util.AddTagPrefix(tag.String()),
	)); err != nil {
		return errors.Wrap(err, "committing changes into repository")
	}

	return nil
}
<commit_msg>Add record/replay flags to `krel changelog`<commit_after>/*
Copyright 2019 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"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/blang/semver"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"gopkg.in/russross/blackfriday.v2"

	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/notes"
	"k8s.io/release/pkg/notes/options"
	"k8s.io/release/pkg/util"
)

// changelogCmd represents the subcommand for `krel changelog`
var changelogCmd = &cobra.Command{
	Use:   "changelog",
	Short: "changelog maintains the lifecycle of CHANGELOG-x.y.{md,html} files",
	Long: `krel changelog

The 'changelog' subcommand of 'krel' does the following things by utilizing
the golang based 'release-notes' tool:

1. Generate the release notes for either a patch or a new minor release. Minor
   releases can be alpha, beta or rc’s, too.
   a) Create a new CHANGELOG-x.y.md file if not existing.
   b) Correctly prepend the generated notes to the existing CHANGELOG-x.y.md
      file if already existing. This also includes the modification of the
	  table of contents.

2. Convert the markdown release notes into a HTML equivalent on purpose of
   sending it by mail to the announce list. The HTML file will be dropped into
   the current working directly as 'CHANGELOG-x.y.html'. Sending the
   announcement is done by another subcommand of 'krel', not "changelog'.

3. Commit the modified CHANGELOG-x.y.md into the master branch as well as the
   corresponding release-branch of kubernetes/kubernetes. The release branch
   will be pruned from all other CHANGELOG-*.md files which do not belong to
   this release branch.
`,
	SilenceUsage:  true,
	SilenceErrors: true,
	PreRunE:       initLogging,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runChangelog()
	},
}

type changelogOptions struct {
	tag       string
	bucket    string
	tars      string
	token     string
	htmlFile  string
	recordDir string
	replayDir string
}

var changelogOpts = &changelogOptions{}

const (
	tocStart = "<!-- BEGIN MUNGE: GENERATED_TOC -->"
	tocEnd   = "<!-- END MUNGE: GENERATED_TOC -->"
)

func init() {
	cobra.OnInitialize(initConfig)

	const (
		tagFlag   = "tag"
		tarsFlag  = "tars"
		tokenFlag = "token"
	)
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.bucket, "bucket", "kubernetes-release", "Specify gs bucket to point to in generated notes")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.tag, tagFlag, "", "The version tag of the release, for example v1.17.0-rc.1")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.tars, tarsFlag, ".", "Directory of tars to SHA512 sum for display")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.htmlFile, "html-file", "", "The target html file to be written. If empty, then it will be CHANGELOG-x.y.html in the current path.")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.recordDir, "record", "", "Record the API into a directory")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.replayDir, "replay", "", "Replay a previously recorded API from a directory")

	if err := changelogCmd.MarkPersistentFlagRequired(tagFlag); err != nil {
		logrus.Fatal(err)
	}

	rootCmd.AddCommand(changelogCmd)
}

func runChangelog() (err error) {
	token, ok := os.LookupEnv("GITHUB_TOKEN")
	if !ok && changelogOpts.replayDir == "" {
		return errors.New("neither environment variable `GITHUB_TOKEN` nor `--replay` is set")
	}
	changelogOpts.token = token

	tag, err := semver.Make(util.TrimTagPrefix(changelogOpts.tag))
	if err != nil {
		return err
	}
	branch := fmt.Sprintf("release-%d.%d", tag.Major, tag.Minor)
	logrus.Infof("Using release branch %s", branch)

	logrus.Infof("Using local repository path %s", rootOpts.repoPath)
	repo, err := git.CloneOrOpenDefaultGitHubRepoSSH(rootOpts.repoPath, git.DefaultGithubOrg)
	if err != nil {
		return err
	}

	var markdown string
	if tag.Patch == 0 {
		if len(tag.Pre) == 0 {
			// New final minor versions should have remote release notes
			markdown, err = lookupRemoteReleaseNotes(branch)
		} else {
			// New minor alphas, betas and rc get generated notes
			start, e := repo.PreviousTag(changelogOpts.tag, branch)
			if e != nil {
				return e
			}
			logrus.Infof("Found previous tag %s", start)
			markdown, err = generateReleaseNotes(branch, start, changelogOpts.tag)
		}
	} else {
		// A patch version, let’s just use the previous patch
		start := util.AddTagPrefix(semver.Version{
			Major: tag.Major, Minor: tag.Minor, Patch: tag.Patch - 1,
		}.String())

		markdown, err = generateReleaseNotes(branch, start, changelogOpts.tag)
	}
	if err != nil {
		return err
	}

	logrus.Info("Generating TOC")
	toc, err := notes.GenerateTOC(markdown)
	if err != nil {
		return err
	}

	// Restore the currently checked out branch
	currentBranch, err := repo.CurrentBranch()
	if err != nil {
		return err
	}
	defer func() {
		if err := repo.CheckoutBranch(currentBranch); err != nil {
			logrus.Errorf("unable to restore branch %s: %v", currentBranch, err)
		}
	}()

	if err := repo.CheckoutBranch(git.Master); err != nil {
		return errors.Wrap(err, "checking out master branch")
	}

	if err := writeMarkdown(repo, toc, markdown, tag); err != nil {
		return err
	}

	if err := writeHTML(tag, markdown); err != nil {
		return err
	}

	return commitChanges(repo, branch, tag)
}

func generateReleaseNotes(branch, startRev, endRev string) (string, error) {
	logrus.Info("Generating release notes")

	notesOptions := options.New()
	notesOptions.Branch = branch
	notesOptions.StartRev = startRev
	notesOptions.EndRev = endRev
	notesOptions.GithubOrg = git.DefaultGithubOrg
	notesOptions.GithubRepo = git.DefaultGithubRepo
	notesOptions.GithubToken = changelogOpts.token
	notesOptions.RepoPath = rootOpts.repoPath
	notesOptions.ReleaseBucket = changelogOpts.bucket
	notesOptions.ReleaseTars = changelogOpts.tars
	notesOptions.Debug = logrus.StandardLogger().Level >= logrus.DebugLevel
	notesOptions.RecordDir = changelogOpts.recordDir
	notesOptions.ReplayDir = changelogOpts.replayDir

	if err := notesOptions.ValidateAndFinish(); err != nil {
		return "", err
	}

	gatherer := notes.NewGatherer(context.Background(), notesOptions)
	releaseNotes, history, err := gatherer.ListReleaseNotes()
	if err != nil {
		return "", errors.Wrapf(err, "listing release notes")
	}

	// Create the markdown
	doc, err := notes.CreateDocument(releaseNotes, history)
	if err != nil {
		return "", errors.Wrapf(err, "creating release note document")
	}

	markdown, err := notes.RenderMarkdown(
		doc, changelogOpts.bucket, changelogOpts.tars,
		notesOptions.StartRev, notesOptions.EndRev,
	)
	if err != nil {
		return "", errors.Wrapf(
			err, "rendering release notes to markdown",
		)
	}

	return markdown, nil
}

func writeMarkdown(repo *git.Repo, toc, markdown string, tag semver.Version) error {
	changelogPath := markdownChangelogFilename(repo, tag)
	writeFile := func(t, m string) error {
		return ioutil.WriteFile(
			changelogPath, []byte(strings.Join(
				[]string{addTocMarkers(t), strings.TrimSpace(m)}, "\n",
			)), 0o644,
		)
	}

	// No changelog exists, simply write the content to a new one
	if _, err := os.Stat(changelogPath); os.IsNotExist(err) {
		logrus.Infof("Changelog %q does not exist, creating it", changelogPath)
		return writeFile(toc, markdown)
	}

	// Changelog seems to exist, prepend the notes and re-generate the TOC
	logrus.Infof("Adding new content to changelog file %s ", changelogPath)
	content, err := ioutil.ReadFile(changelogPath)
	if err != nil {
		return err
	}

	tocEndIndex := bytes.Index(content, []byte(tocEnd))
	if tocEndIndex < 0 {
		return errors.Errorf(
			"unable to find table of contents end marker `%s` in %q",
			tocEnd, changelogPath,
		)
	}

	mergedMarkdown := fmt.Sprintf(
		"%s\n%s",
		strings.TrimSpace(markdown),
		string(content[(len(tocEnd)+tocEndIndex):]),
	)
	mergedTOC, err := notes.GenerateTOC(mergedMarkdown)
	if err != nil {
		return err
	}
	return writeFile(mergedTOC, mergedMarkdown)
}

func htmlChangelogFilename(tag semver.Version) string {
	if changelogOpts.htmlFile != "" {
		return changelogOpts.htmlFile
	}
	return changelogFilename(tag, "html")
}

func markdownChangelogFilename(repo *git.Repo, tag semver.Version) string {
	return filepath.Join(repo.Dir(), changelogFilename(tag, "md"))
}

func changelogFilename(tag semver.Version, ext string) string {
	return fmt.Sprintf("CHANGELOG-%d.%d.%s", tag.Major, tag.Minor, ext)
}

func addTocMarkers(toc string) string {
	return fmt.Sprintf("%s\n\n%s\n%s\n", tocStart, toc, tocEnd)
}

const htmlTemplate = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{{ .Title }}</title>
    <style type="text/css">
      table,
      th,
      tr,
      td {
        border: 1px solid gray;
        border-collapse: collapse;
        padding: 5px;
      }
    </style>
  </head>
  <body>
    {{ .Content }}
  </body>
</html>`

func writeHTML(tag semver.Version, markdown string) error {
	content := blackfriday.Run([]byte(markdown))

	t, err := template.New("html").Parse(htmlTemplate)
	if err != nil {
		return err
	}

	output := bytes.Buffer{}
	if err := t.Execute(&output, struct {
		Title, Content string
	}{util.AddTagPrefix(tag.String()), string(content)}); err != nil {
		return err
	}

	absOutputPath, err := filepath.Abs(htmlChangelogFilename(tag))
	if err != nil {
		return err
	}
	logrus.Infof("Writing single HTML to %s", absOutputPath)
	return ioutil.WriteFile(absOutputPath, output.Bytes(), 0o644)
}

func lookupRemoteReleaseNotes(branch string) (string, error) {
	logrus.Info("Assuming new minor release")

	remote := fmt.Sprintf(
		"https://raw.githubusercontent.com/kubernetes/sig-release/master/"+
			"releases/%s/release-notes-draft.md", branch,
	)
	resp, err := http.Get(remote)
	if err != nil {
		return "", errors.Wrapf(err,
			"fetching release notes from remote: %s", remote,
		)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", errors.Errorf(
			"remote release notes not found at: %s", remote,
		)
	}
	logrus.Info("Found release notes")

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(content), nil
}

func commitChanges(repo *git.Repo, branch string, tag semver.Version) error {
	// Master branch modifications
	filename := filepath.Base(markdownChangelogFilename(repo, tag))
	logrus.Infof("Adding %s to repository", filename)
	if err := repo.Add(filename); err != nil {
		return errors.Wrapf(err, "trying to add file %s to repository", filename)
	}

	logrus.Info("Committing changes to master branch in repository")
	if err := repo.Commit(fmt.Sprintf(
		"Add %s for %s", filename, util.AddTagPrefix(tag.String()),
	)); err != nil {
		return errors.Wrap(err, "committing changes into repository")
	}

	// Release branch modifications
	if err := repo.CheckoutBranch(branch); err != nil {
		return errors.Wrapf(err, "checking out release branch %s", branch)
	}

	// Remove all other changelog files
	if err := repo.Rm(true, "CHANGELOG-*.md"); err != nil {
		return errors.Wrap(err, "unable to remove CHANGELOG-*.md files")
	}

	logrus.Info("Checking out changelog from master branch")
	if err := repo.Checkout(git.Master, filename); err != nil {
		return errors.Wrap(err, "checking out master branch changelog")
	}

	logrus.Info("Committing changes to release branch in repository")
	if err := repo.Commit(fmt.Sprintf(
		"Update %s for %s", filename, util.AddTagPrefix(tag.String()),
	)); err != nil {
		return errors.Wrap(err, "committing changes into repository")
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 Google LLC
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 api

import (
	"archive/zip"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	generator "shifter/generators"
	lib "shifter/lib"
	os "shifter/openshift"
	ops "shifter/ops"

	"shifter/processor"

	"github.com/gin-gonic/gin"
)

func (server *Server) Convert(ctx *gin.Context) {
	var openshift os.Openshift

	// Create API Unique RUN ID
	//uuid := uuid.New().String()
	suid := ops.CreateSUID("")
	convert := Convert{}
	// using BindJson method to serialize body with struct
	if err := ctx.BindJSON(&convert); err != nil {
		ctx.AbortWithError(http.StatusBadRequest, err)
		return
	}

	var openshift os.Openshift
	openshift.Endpoint = convert.Shifter.ClusterConfig.BaseUrl
	openshift.AuthToken = convert.Shifter.ClusterConfig.BearerToken

	// Process Each Item
	// Confirm Project/Namespace Exists
	deploymentConfig := openshift.GetDeploymentConfig(item.Namespace.ObjectMeta.Name, item.DeploymentConfig.ObjectMeta.Name)

		// Confirm Project/Namespace Exists
		deploymentConfig := openshift.GetDeploymentConfig(item.Namespace.ObjectMeta.Name, item.DeploymentConfig.ObjectMeta.Name)

		u, err := json.Marshal(deploymentConfig)
		if err != nil {
			panic(err)
		}

		// Handle the Conversion of the Manifests and File Writing
		var generator generator.Generator
		var objs []lib.K8sobject
		obj := processor.Processor(u, "DeploymentConfig", nil)
		objs = append(objs, obj)
		convertedObjects := generator.Yaml(item.DeploymentConfig.ObjectMeta.Name, objs)
		for _, conObj := range convertedObjects {
			fileObj := &ops.FileObject{
				//StorageType: "GCS",
				//SourcePath:  ("gs://shifter-lz-002-sample-files/" + uuid + "/" + item.Namespace.ObjectMeta.Name + "/" + item.DeploymentConfig.ObjectMeta.Name),
				StorageType:   server.config.serverStorage.storageType,
				SourcePath:    (server.config.serverStorage.sourcePath + "/" + suid.DirectoryName + "/" + item.Namespace.ObjectMeta.Name + "/" + item.DeploymentConfig.ObjectMeta.Name),
				Ext:           "yaml",
				Content:       conObj.Payload,
				ContentLength: conObj.Payload.Len(),
			}
			fileObj.WriteFile()
		}
		fileObj.WriteFile()
	}

	// Zip / Package Converted Objects
	err := server.PackageConversionObjects(suid)
	if err != nil {
		ctx.AbortWithError(http.StatusBadRequest, err)
	}

	// Construct API Endpoint Response
	r := ResponseConvert{
		SUID:    suid,
		Message: "Converted..." + string(len(convert.Items)) + " Objects",
	}
	ctx.JSON(http.StatusOK, r)
}

func (server *Server) PackageConversionObjects(suid ops.SUID) error {

	file, err := os.Create(server.config.serverStorage.outputPath + "/" + suid.DownloadId + ".zip")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	w := zip.NewWriter(file)
	defer w.Close()

	walker := func(path string, info os.FileInfo, err error) error {
		//fmt.Printf("Crawling: %#v\n", path)
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		// Ensure that `path` is not absolute; it should not start with "/".
		// This snippet happens to work because I don't use
		// absolute paths, but ensure your real-world code
		// transforms path into a zip-root relative path.
		f, err := w.Create(path)
		if err != nil {
			return err
		}

		_, err = io.Copy(f, file)
		if err != nil {
			return err
		}
		return nil
	}
	err = filepath.Walk(server.config.serverStorage.sourcePath+"/"+suid.DirectoryName+"/", walker)
	if err != nil {
		return errors.New("Unable to resolve or find Download ID")
	}
	return nil
}
<commit_msg>I removed the loop because of gitmerges -_-<commit_after>/*
Copyright 2019 Google LLC
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 api

import (
	"archive/zip"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"path/filepath"
	generator "shifter/generators"
	lib "shifter/lib"
	os "shifter/openshift"
	ops "shifter/ops"

	"shifter/processor"

	"github.com/gin-gonic/gin"
)

func (server *Server) Convert(ctx *gin.Context) {
	var openshift os.Openshift

	// Create API Unique RUN ID
	//uuid := uuid.New().String()
	suid := ops.CreateSUID("")
	convert := Convert{}
	// using BindJson method to serialize body with struct
	if err := ctx.BindJSON(&convert); err != nil {
		ctx.AbortWithError(http.StatusBadRequest, err)
		return
	}

	var openshift os.Openshift
	openshift.Endpoint = convert.Shifter.ClusterConfig.BaseUrl
	openshift.AuthToken = convert.Shifter.ClusterConfig.BearerToken

	// Process Each Item
	for _, item := range convert.Items {
		// Confirm Project/Namespace Exists
		deploymentConfig := openshift.GetDeploymentConfig(item.Namespace.ObjectMeta.Name, item.DeploymentConfig.ObjectMeta.Name)

		u, err := json.Marshal(deploymentConfig)
		if err != nil {
			panic(err)
		}

		// Handle the Conversion of the Manifests and File Writing
		var generator generator.Generator
		var objs []lib.K8sobject
		obj := processor.Processor(u, "DeploymentConfig", nil)
		objs = append(objs, obj)
		convertedObjects := generator.Yaml(item.DeploymentConfig.ObjectMeta.Name, objs)
		for _, conObj := range convertedObjects {
			fileObj := &ops.FileObject{
				//StorageType: "GCS",
				//SourcePath:  ("gs://shifter-lz-002-sample-files/" + uuid + "/" + item.Namespace.ObjectMeta.Name + "/" + item.DeploymentConfig.ObjectMeta.Name),
				StorageType:   server.config.serverStorage.storageType,
				SourcePath:    (server.config.serverStorage.sourcePath + "/" + suid.DirectoryName + "/" + item.Namespace.ObjectMeta.Name + "/" + item.DeploymentConfig.ObjectMeta.Name),
				Ext:           "yaml",
				Content:       conObj.Payload,
				ContentLength: conObj.Payload.Len(),
			}
			fileObj.WriteFile()
		}
	}

	// Zip / Package Converted Objects
	err := server.PackageConversionObjects(suid)
	if err != nil {
		ctx.AbortWithError(http.StatusBadRequest, err)
	}

	// Construct API Endpoint Response
	r := ResponseConvert{
		SUID:    suid,
		Message: "Converted..." + string(len(convert.Items)) + " Objects",
	}
	ctx.JSON(http.StatusOK, r)
}

func (server *Server) PackageConversionObjects(suid ops.SUID) error {

	file, err := os.Create(server.config.serverStorage.outputPath + "/" + suid.DownloadId + ".zip")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	w := zip.NewWriter(file)
	defer w.Close()

	walker := func(path string, info os.FileInfo, err error) error {
		//fmt.Printf("Crawling: %#v\n", path)
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		// Ensure that `path` is not absolute; it should not start with "/".
		// This snippet happens to work because I don't use
		// absolute paths, but ensure your real-world code
		// transforms path into a zip-root relative path.
		f, err := w.Create(path)
		if err != nil {
			return err
		}

		_, err = io.Copy(f, file)
		if err != nil {
			return err
		}
		return nil
	}
	err = filepath.Walk(server.config.serverStorage.sourcePath+"/"+suid.DirectoryName+"/", walker)
	if err != nil {
		return errors.New("Unable to resolve or find Download ID")
	}
	return nil
}
<|endoftext|>


<commit_before>package buffer

import "github.com/youtube/vitess/go/stats"

// This file contains all status variables which can be used to monitor the
// buffer.

var (
	// requestsInFlightMax has the maximum value of buffered requests in flight
	// of the last failover.
	requestsInFlightMax = stats.NewMultiCounters("BufferRequestsInFlightMax", []string{"Keyspace", "Shard"})
	// failoverDurationMs tracks for how long vtgate buffered requests during the
	// last failover.
	failoverDurationMs = stats.NewMultiCounters("BufferFailoverDurationMs", []string{"Keyspace", "Shard"})
)
<commit_msg>vtgate/buffer: Renaming stats variable label from "Shard" to "ShardName".<commit_after>package buffer

import "github.com/youtube/vitess/go/stats"

// This file contains all status variables which can be used to monitor the
// buffer.

var (
	// requestsInFlightMax has the maximum value of buffered requests in flight
	// of the last failover.
	requestsInFlightMax = stats.NewMultiCounters("BufferRequestsInFlightMax", []string{"Keyspace", "ShardName"})
	// failoverDurationMs tracks for how long vtgate buffered requests during the
	// last failover.
	failoverDurationMs = stats.NewMultiCounters("BufferFailoverDurationMs", []string{"Keyspace", "ShardName"})
)
<|endoftext|>


<commit_before>package steam

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

const steamEndpoint string = "http://api.steampowered.com"

type Param struct {
	key   string
	value string
}

type SteamRequest struct {
	Interface string
	Endpoint  string
	Version   int
	Params    []Param
}

// API Key
var steamAPIKey string = ""

func SetAPIKey(key string) {
	steamAPIKey = key
}

func GetAPIKey() string {
	return steamAPIKey
}

// Get Steam API Object
func (r *SteamRequest) Get(result interface{}) {
	requestUri := fmt.Sprintf("%s/%s/%s/v%04d/?format=json&%s", steamEndpoint, r.Interface, r.Endpoint, r.Version, buildQueryString(r.Params))

	res, _ := http.Get(requestUri)

	var body []byte
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)

	json.Unmarshal(body, result)
}

func buildQueryString(params []Param) string {
	var queries []string
	for k, v := range params {
		queries = append(queries, fmt.Sprintf("%s=%s", k, v))
	}
	return strings.Join(queries, "&")
}
<commit_msg>Fix query string builder<commit_after>package steam

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

const steamEndpoint string = "http://api.steampowered.com"

type Param struct {
	key   string
	value string
}

type SteamRequest struct {
	Interface string
	Endpoint  string
	Version   int
	Params    []Param
}

// API Key
var steamAPIKey string = ""

func SetAPIKey(key string) {
	steamAPIKey = key
}

func GetAPIKey() string {
	return steamAPIKey
}

// Get Steam API Object
func (r *SteamRequest) Get(result interface{}) {
	requestUri := fmt.Sprintf("%s/%s/%s/v%04d/?format=json&%s", steamEndpoint, r.Interface, r.Endpoint, r.Version, buildQueryString(r.Params))

	res, _ := http.Get(requestUri)

	var body []byte
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)

	json.Unmarshal(body, result)
}

func buildQueryString(params []Param) string {
	var queries []string
	for _, v := range params {
		queries = append(queries, fmt.Sprintf("%s=%s", v.key, v.value))
	}
	return strings.Join(queries, "&")
}
<|endoftext|>


<commit_before>package kuberneteslocator

import (
	"fmt"
	"strconv"

	"github.com/matt-deboer/mpp/pkg/locator"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

type kubeLocator struct {
	labelSelector string
	portName      string
	portNumber    int32
	serviceName   string
	clientset     *kubernetes.Clientset
}

// NewKubernetesLocator generates a new marathon prometheus locator
func NewKubernetesLocator(kubeconfig, labelSelector, port, serviceName string) (locator.Locator, error) {

	var config *rest.Config
	if len(kubeconfig) > 0 {
		cff, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
		if err != nil {
			return nil, err
		}
		config = cff
	} else {
		icc, err := rest.InClusterConfig()
		if err != nil {
			return nil, err
		}
		config = icc
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	portNumber, _ := strconv.ParseInt(port, 10, 32)

	return &kubeLocator{
		clientset:     clientset,
		labelSelector: labelSelector,
		portName:      port,
		portNumber:    int32(portNumber),
		serviceName:   serviceName,
	}, nil
}

// Endpoints provides a list of candidate prometheus endpoints
func (k *kubeLocator) Endpoints() ([]*locator.PrometheusEndpoint, error) {

	endpoints := []string{}
	if len(k.serviceName) > 0 {
		endp, err := k.clientset.Core().Endpoints("").Get(k.serviceName)
		if err != nil {
			return nil, err
		}
		var port int32
		for _, p := range endp.Subsets[0].Ports {
			if p.Protocol == v1.ProtocolTCP {
				if len(k.portName) > 0 {
					if k.portName == p.Name || p.Port == k.portNumber {
						port = p.Port
						break
					}
				} else {
					port = p.Port
					break
				}
			}
		}
		for _, a := range endp.Subsets[0].Addresses {
			endpoints = append(endpoints, fmt.Sprintf("http://%s:%d", a.IP, port))
		}
	} else {
		pods, err := k.clientset.Core().Pods("").List(v1.ListOptions{
			LabelSelector: k.labelSelector,
		})
		if err != nil {
			return nil, err
		}
		for _, pod := range pods.Items {
			var port int32
			for _, c := range pod.Spec.Containers {
				for _, p := range c.Ports {
					if p.Protocol == v1.ProtocolTCP {
						if len(k.portName) > 0 {
							if k.portName == p.Name || p.ContainerPort == k.portNumber {
								// 'port' flag was specified; match by name or port value
								port = p.ContainerPort
							}
						} else {
							// 'port' flag not specified; take the first (TCP) port we found
							port = p.ContainerPort
						}
					}
					if port > 0 {
						break
					}
				}
				if port > 0 {
					break
				}
			}
			endpoints = append(endpoints, fmt.Sprintf("http://%s:%d", pod.Status.PodIP, port))
		}
	}
	return locator.ToPrometheusClients(endpoints)
}
<commit_msg>update comments<commit_after>package kuberneteslocator

import (
	"fmt"
	"strconv"

	"github.com/matt-deboer/mpp/pkg/locator"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

type kubeLocator struct {
	labelSelector string
	portName      string
	portNumber    int32
	serviceName   string
	clientset     *kubernetes.Clientset
}

// NewKubernetesLocator generates a new marathon prometheus locator
func NewKubernetesLocator(kubeconfig, labelSelector, port, serviceName string) (locator.Locator, error) {

	var config *rest.Config
	if len(kubeconfig) > 0 {
		cff, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
		if err != nil {
			return nil, err
		}
		config = cff
	} else {
		icc, err := rest.InClusterConfig()
		if err != nil {
			return nil, err
		}
		config = icc
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	portNumber, _ := strconv.ParseInt(port, 10, 32)

	return &kubeLocator{
		clientset:     clientset,
		labelSelector: labelSelector,
		portName:      port,
		portNumber:    int32(portNumber),
		serviceName:   serviceName,
	}, nil
}

// Endpoints provides a list of candidate prometheus endpoints
func (k *kubeLocator) Endpoints() ([]*locator.PrometheusEndpoint, error) {

	endpoints := []string{}
	if len(k.serviceName) > 0 {
		endp, err := k.clientset.Core().Endpoints("").Get(k.serviceName)
		if err != nil {
			return nil, err
		}
		var port int32
		for _, p := range endp.Subsets[0].Ports {
			if p.Protocol == v1.ProtocolTCP {
				if len(k.portName) > 0 {
					if k.portName == p.Name || p.Port == k.portNumber {
						// 'port' flag was specified; match by name or port value
						port = p.Port
						break
					}
				} else {
					// 'port' flag not specified; take the first (TCP) port we found
					port = p.Port
					break
				}
			}
		}
		for _, a := range endp.Subsets[0].Addresses {
			endpoints = append(endpoints, fmt.Sprintf("http://%s:%d", a.IP, port))
		}
	} else {
		pods, err := k.clientset.Core().Pods("").List(v1.ListOptions{
			LabelSelector: k.labelSelector,
		})
		if err != nil {
			return nil, err
		}
		for _, pod := range pods.Items {
			var port int32
			for _, c := range pod.Spec.Containers {
				for _, p := range c.Ports {
					if p.Protocol == v1.ProtocolTCP {
						if len(k.portName) > 0 {
							if k.portName == p.Name || p.ContainerPort == k.portNumber {
								// 'port' flag was specified; match by name or port value
								port = p.ContainerPort
							}
						} else {
							// 'port' flag not specified; take the first (TCP) port we found
							port = p.ContainerPort
						}
					}
					if port > 0 {
						break
					}
				}
				if port > 0 {
					break
				}
			}
			endpoints = append(endpoints, fmt.Sprintf("http://%s:%d", pod.Status.PodIP, port))
		}
	}
	return locator.ToPrometheusClients(endpoints)
}
<|endoftext|>


<commit_before>package venom

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/spf13/pflag"
)

func parseTag(tag string) (string, string, string) {
	parts := strings.SplitN(tag, ",", 3)

	// flag: bar, b, Some barness -> flag: bar,b,Some barness
	for i, p := range parts {
		parts[i] = strings.TrimSpace(p)
	}

	switch len(parts) {
	case 1:
		// flag: b
		if len(parts[0]) == 1 {
			return "", parts[0], ""
		}
		// flag: bar
		return parts[0], "", ""
	case 2:
		// flag: b,Some barness
		if len(parts[0]) == 1 {
			return "", parts[0], parts[1]
		}
		// flag: bar,b
		if len(parts[1]) == 1 {
			return parts[0], parts[1], ""
		}
		// flag: bar,Some barness
		return parts[0], "", parts[1]
	case 3:
		// flag: bar,b,Some barness
		return parts[0], parts[1], parts[2]
	default:
		return "", "", ""
	}
}

func DefineFlags(config interface{}) *pflag.FlagSet {
	flags, err := NewFlags(config)
	if err != nil {
		panic(err)
	}
	return flags
}

func NewFlags(config interface{}) (*pflag.FlagSet, error) {
	var flags pflag.FlagSet

	//
	// Remove one level of indirection.
	//
	v := reflect.ValueOf(config)
	if v.Kind() == reflect.Ptr {
		v = reflect.Indirect(v)
	}

	//
	// Make sure we end up with a struct.
	//
	if v.Kind() != reflect.Struct {
		return nil, errors.New("Struct or pointer to struct expected")
	}

	//
	// For every struct field create a flag.
	//
	for i := 0; i < v.Type().NumField(); i++ {
		field := v.Type().Field(i)

		tag := field.Tag.Get("pflag")
		if tag == "" {
			continue
		}

		name, shorthand, usage := parseTag(tag)

		val := v.Field(i)
		typ := val.Type()
		switch typ.Kind() {
		case reflect.Bool:
			flags.BoolP(name, shorthand, false, usage)
		case reflect.Int:
			flags.IntP(name, shorthand, 0, usage)
		case reflect.Int8:
			flags.Int8P(name, shorthand, 0, usage)
		case reflect.Int16:
			flags.Int32P(name, shorthand, 0, usage) // Not a typo, pflags doesn't have Int16
		case reflect.Int32:
			flags.Int32P(name, shorthand, 0, usage)
		case reflect.Int64:
			flags.Int64P(name, shorthand, 0, usage)
		case reflect.Uint:
			flags.UintP(name, shorthand, 0, usage)
		case reflect.Uint8:
			flags.Uint8P(name, shorthand, 0, usage)
		case reflect.Uint16:
			flags.Uint16P(name, shorthand, 0, usage)
		case reflect.Uint32:
			flags.Uint32P(name, shorthand, 0, usage)
		case reflect.Uint64:
			flags.Uint64P(name, shorthand, 0, usage)
		case reflect.Float32:
			flags.Float32P(name, shorthand, 0, usage)
		case reflect.Float64:
			flags.Float64P(name, shorthand, 0, usage)
		case reflect.String:
			flags.StringP(name, shorthand, "", usage)
		default:
			return nil, fmt.Errorf("Unsupported type for field with flag tag %q: %s", name, typ)
		}
	}

	return &flags, nil
}
<commit_msg>Support multiple tags<commit_after>package venom

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/spf13/pflag"
)

func parseTag(tag string) (string, string, string) {
	parts := strings.SplitN(tag, ",", 3)

	// flag: bar, b, Some barness -> flag: bar,b,Some barness
	for i, p := range parts {
		parts[i] = strings.TrimSpace(p)
	}

	switch len(parts) {
	case 1:
		// flag: b
		if len(parts[0]) == 1 {
			return "", parts[0], ""
		}
		// flag: bar
		return parts[0], "", ""
	case 2:
		// flag: b,Some barness
		if len(parts[0]) == 1 {
			return "", parts[0], parts[1]
		}
		// flag: bar,b
		if len(parts[1]) == 1 {
			return parts[0], parts[1], ""
		}
		// flag: bar,Some barness
		return parts[0], "", parts[1]
	case 3:
		// flag: bar,b,Some barness
		return parts[0], parts[1], parts[2]
	default:
		return "", "", ""
	}
}

func DefineFlags(config interface{}) *pflag.FlagSet {
	flags, err := NewFlags(config)
	if err != nil {
		panic(err)
	}
	return flags
}

func NewFlags(config interface{}) (*pflag.FlagSet, error) {
	a := flagsFactory{
		tags: []string{"flag", "pflag"},
	}
	return a.createFlags(config)
}

type flagsFactory struct {
	tags []string
}

func (a flagsFactory) lookupTag(field reflect.StructField) (string, bool) {
	for _, name := range a.tags {
		v, ok := field.Tag.Lookup(name)
		if ok {
			return v, true
		}
	}
	return "", false
}

func (a flagsFactory) createFlags(config interface{}) (*pflag.FlagSet, error) {
	var flags pflag.FlagSet

	//
	// Remove one level of indirection.
	//
	v := reflect.ValueOf(config)
	if v.Kind() == reflect.Ptr {
		v = reflect.Indirect(v)
	}

	//
	// Make sure we end up with a struct.
	//
	if v.Kind() != reflect.Struct {
		return nil, errors.New("Struct or pointer to struct expected")
	}

	//
	// For every struct field create a flag.
	//
	for i := 0; i < v.Type().NumField(); i++ {
		field := v.Type().Field(i)

		tag, ok := a.lookupTag(field)
		if !ok {
			continue
		}

		name, shorthand, usage := parseTag(tag)

		val := v.Field(i)
		typ := val.Type()
		switch typ.Kind() {
		case reflect.Bool:
			flags.BoolP(name, shorthand, false, usage)
		case reflect.Int:
			flags.IntP(name, shorthand, 0, usage)
		case reflect.Int8:
			flags.Int8P(name, shorthand, 0, usage)
		case reflect.Int16:
			flags.Int32P(name, shorthand, 0, usage) // Not a typo, pflags doesn't have Int16
		case reflect.Int32:
			flags.Int32P(name, shorthand, 0, usage)
		case reflect.Int64:
			flags.Int64P(name, shorthand, 0, usage)
		case reflect.Uint:
			flags.UintP(name, shorthand, 0, usage)
		case reflect.Uint8:
			flags.Uint8P(name, shorthand, 0, usage)
		case reflect.Uint16:
			flags.Uint16P(name, shorthand, 0, usage)
		case reflect.Uint32:
			flags.Uint32P(name, shorthand, 0, usage)
		case reflect.Uint64:
			flags.Uint64P(name, shorthand, 0, usage)
		case reflect.Float32:
			flags.Float32P(name, shorthand, 0, usage)
		case reflect.Float64:
			flags.Float64P(name, shorthand, 0, usage)
		case reflect.String:
			flags.StringP(name, shorthand, "", usage)
		default:
			return nil, fmt.Errorf("Unsupported type for field with flag tag %q: %s", name, typ)
		}
	}

	return &flags, nil
}
<|endoftext|>


<commit_before>package eval_test

import (
	"bytes"
	"reflect"
	"sync"
	"testing"

	. "github.com/elves/elvish/pkg/eval"

	. "github.com/elves/elvish/pkg/eval/evaltest"
	"github.com/elves/elvish/pkg/parse"
	"github.com/elves/elvish/pkg/prog"
	"github.com/elves/elvish/pkg/testutil"
)

func TestNumBgJobs(t *testing.T) {
	Test(t,
		That("put $num-bg-jobs").Puts("0"),
		// TODO(xiaq): Test cases where $num-bg-jobs > 0. This cannot be done
		// with { put $num-bg-jobs }& because the output channel may have
		// already been closed when the closure is run.
	)
}

func TestEvalTimeDeprecate(t *testing.T) {
	restore := prog.SetShowDeprecations(true)
	defer restore()
	_, cleanup := testutil.InTestDir()
	defer cleanup()

	TestWithSetup(t, func(ev *Evaler) {
		ev.Global = NsBuilder{}.AddGoFn("", "dep", func(fm *Frame) {
			fm.Deprecate("deprecated", nil)
		}).Ns()
	},
		That("dep").PrintsStderrWith("deprecated"),
		// Deprecation message is only shown once.
		That("dep 2> tmp.txt; dep").DoesNothing(),
	)
}

func TestCompileTimeDeprecation(t *testing.T) {
	restore := prog.SetShowDeprecations(true)
	defer restore()

	ev := NewEvaler()
	r, w := testutil.MustPipe()
	_, err := ev.ParseAndCompile(parse.Source{Code: "ord a"}, w)
	if err != nil {
		t.Errorf("got err %v, want nil", err)
	}
	w.Close()
	warnings := testutil.MustReadAllAndClose(r)
	wantWarning := []byte(`the "ord" command is deprecated`)
	if !bytes.Contains(warnings, wantWarning) {
		t.Errorf("got warnings %q, want warnings to contain %q", warnings, wantWarning)
	}
}

func TestMiscEval(t *testing.T) {
	Test(t,
		// Pseudo-namespace E:
		That("E:FOO=lorem; put $E:FOO").Puts("lorem"),
		That("del E:FOO; put $E:FOO").Puts(""),
	)
}

func TestMultipleEval(t *testing.T) {
	texts := []string{"x=hello", "put $x"}
	r := EvalAndCollect(t, NewEvaler(), texts)
	wantOuts := []interface{}{"hello"}
	if r.Exception != nil {
		t.Errorf("eval %s => %v, want nil", texts, r.Exception)
	}
	if !reflect.DeepEqual(r.ValueOut, wantOuts) {
		t.Errorf("eval %s outputs %v, want %v", texts, r.ValueOut, wantOuts)
	}
}

func TestConcurrentEval(t *testing.T) {
	// Run this test with "go test -race".
	ev := NewEvaler()
	src := parse.Source{Name: "[test]", Code: ""}

	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		ev.Eval(src, EvalCfg{})
		wg.Done()
	}()
	go func() {
		ev.Eval(src, EvalCfg{})
		wg.Done()
	}()
	wg.Wait()
}
<commit_msg>pkg/eval: Test compile-time deprecations using (*Evaler).Eval.<commit_after>package eval_test

import (
	"reflect"
	"strings"
	"sync"
	"testing"

	. "github.com/elves/elvish/pkg/eval"

	. "github.com/elves/elvish/pkg/eval/evaltest"
	"github.com/elves/elvish/pkg/parse"
	"github.com/elves/elvish/pkg/prog"
	"github.com/elves/elvish/pkg/testutil"
)

func TestNumBgJobs(t *testing.T) {
	Test(t,
		That("put $num-bg-jobs").Puts("0"),
		// TODO(xiaq): Test cases where $num-bg-jobs > 0. This cannot be done
		// with { put $num-bg-jobs }& because the output channel may have
		// already been closed when the closure is run.
	)
}

func TestEvalTimeDeprecate(t *testing.T) {
	restore := prog.SetShowDeprecations(true)
	defer restore()
	_, cleanup := testutil.InTestDir()
	defer cleanup()

	TestWithSetup(t, func(ev *Evaler) {
		ev.Global = NsBuilder{}.AddGoFn("", "dep", func(fm *Frame) {
			fm.Deprecate("deprecated", nil)
		}).Ns()
	},
		That("dep").PrintsStderrWith("deprecated"),
		// Deprecation message is only shown once.
		That("dep 2> tmp.txt; dep").DoesNothing(),
	)
}

func TestCompileTimeDeprecation(t *testing.T) {
	restore := prog.SetShowDeprecations(true)
	defer restore()

	ev := NewEvaler()
	errPort, collect, err := CaptureStringPort()
	if err != nil {
		panic(err)
	}

	err = ev.Eval(
		parse.Source{Code: "ord a"},
		EvalCfg{Ports: []*Port{nil, nil, errPort}, NoExecute: true})
	warnings := collect()
	if err != nil {
		t.Errorf("got err %v, want nil", err)
	}

	warning := warnings[0]
	wantWarning := `the "ord" command is deprecated`
	if !strings.Contains(warning, wantWarning) {
		t.Errorf("got warning %q, want warning containing %q", warning, wantWarning)
	}
}

func TestMiscEval(t *testing.T) {
	Test(t,
		// Pseudo-namespace E:
		That("E:FOO=lorem; put $E:FOO").Puts("lorem"),
		That("del E:FOO; put $E:FOO").Puts(""),
	)
}

func TestMultipleEval(t *testing.T) {
	texts := []string{"x=hello", "put $x"}
	r := EvalAndCollect(t, NewEvaler(), texts)
	wantOuts := []interface{}{"hello"}
	if r.Exception != nil {
		t.Errorf("eval %s => %v, want nil", texts, r.Exception)
	}
	if !reflect.DeepEqual(r.ValueOut, wantOuts) {
		t.Errorf("eval %s outputs %v, want %v", texts, r.ValueOut, wantOuts)
	}
}

func TestConcurrentEval(t *testing.T) {
	// Run this test with "go test -race".
	ev := NewEvaler()
	src := parse.Source{Name: "[test]", Code: ""}

	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		ev.Eval(src, EvalCfg{})
		wg.Done()
	}()
	go func() {
		ev.Eval(src, EvalCfg{})
		wg.Done()
	}()
	wg.Wait()
}
<|endoftext|>


<commit_before>package html

const style = `
#topbar {
	padding: 5px 10px;
	background: #E0EBF5;
}

#topbar a {
	color: #375EAB;
	text-decoration: none;
}

h1, h2, h3, h4 {
	margin: 0;
	padding: 0;
	color: #375EAB;
	font-weight: bold;
}

table {
	border: 1px solid #ccc;
	margin: 20px 5px;
	border-collapse: collapse;
	white-space: nowrap;
	text-overflow: ellipsis;
	overflow: hidden;
}

table caption {
	font-weight: bold;
}

table td, table th {
	vertical-align: top;
	padding: 2px 8px;
	text-overflow: ellipsis;
	overflow: hidden;
}

.namespace {
	font-weight: bold;
	font-size: large;
	color: #375EAB;
}

.position_table {
	border: 0px;
	margin: 0px;
	width: 100%;
	border-collapse: collapse;
}

.position_table td, .position_table tr {
	vertical-align: center;
	padding: 0px;
}

.position_table .search {
	text-align: right;
}

.list_table td, .list_table th {
	border-left: 1px solid #ccc;
}

.list_table th {
	background: #F4F4F4;
}

.list_table tr:nth-child(2n+1) {
	background: #F4F4F4;
}

.list_table tr:hover {
	background: #ffff99;
}

.list_table .namespace {
	width: 100pt;
	max-width: 100pt;
}

.list_table .title {
	width: 350pt;
	max-width: 350pt;
}

.list_table .commit_list {
	width: 500pt;
	max-width: 500pt;
}

.list_table .tag {
	font-family: monospace;
	font-size: 8pt;
	width: 40pt;
	max-width: 40pt;
}

.list_table .opts {
	width: 40pt;
	max-width: 40pt;
}

.list_table .status {
	width: 250pt;
	max-width: 250pt;
}

.list_table .patched {
	width: 60pt;
	max-width: 60pt;
	text-align: center;
}

.list_table .kernel {
	width: 80pt;
	max-width: 80pt;
}

.list_table .maintainers {
	width: 150pt;
	max-width: 150pt;
}

.list_table .result {
	width: 60pt;
	max-width: 60pt;
}

.list_table .stat {
	width: 50pt;
	max-width: 50pt;
	font-family: monospace;
	text-align: right;
}

.list_table .date {
	width: 60pt;
	max-width: 60pt;
	font-family: monospace;
	text-align: right;
}

.list_table .stat_name {
	width: 150pt;
	max-width: 150pt;
	font-family: monospace;
}

.list_table .stat_value {
	width: 120pt;
	max-width: 120pt;
	font-family: monospace;
}

.bad {
	color: #f00;
	font-weight: bold;
}

.inactive {
	color: #888;
}

.plain {
	text-decoration: none;
}

textarea {
	width:100%;
	font-family: monospace;
}

.mono {
	font-family: monospace;
}
`
const js = `
// Copyright 2018 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

function sortTable(item, colName, conv, desc = false) {
	table = item.parentNode.parentNode.parentNode;
	rows = table.rows;
	col = findColumnByName(rows[0].getElementsByTagName("th"), colName);
	values = [];
	for (i = 1; i < rows.length; i++)
		values.push([conv(rows[i].getElementsByTagName("td")[col].textContent), rows[i]]);
	if (desc)
		desc = !isSorted(values.slice().reverse())
	else
		desc = isSorted(values);
	values.sort(function(a, b) {
		if (a[0] == b[0]) return 0;
		if (desc && a[0] > b[0] || !desc && a[0] < b[0]) return -1;
		return 1;
	});
	for (i = 0; i < values.length; i++)
		table.appendChild(values[i][1]);
	return false;
}

function findColumnByName(headers, colName) {
	for (i = 0; i < headers.length; i++) {
		if (headers[i].textContent == colName)
			return i;
	}
	return 0;
}

function isSorted(values) {
	for (i = 0; i < values.length - 1; i++) {
		if (values[i][0] > values[i + 1][0])
			return false;
	}
	return true;
}

function textSort(v) { return v.toLowerCase(); }
function numSort(v) { return -parseInt(v); }
function floatSort(v) { return -parseFloat(v); }
function yesSort(v) { return v == "yes" ? 0 : 1; }
function reproSort(v) { return v == "C" ? 0 : v == "syz" ? 1 : 2; }
function patchedSort(v) { return v == "" ? -1 : parseInt(v); }

function timeSort(v) {
	if (v == "now")
		return 0;
	m = v.indexOf('m');
	h = v.indexOf('h');
	d = v.indexOf('d');
	if (m > 0 && h < 0)
		return parseInt(v);
	if (h > 0 && m > 0)
		return parseInt(v) * 60 + parseInt(v.substring(h + 1));
	if (d > 0 && h > 0)
		return parseInt(v) * 60 * 24 + parseInt(v.substring(d + 1)) * 60;
	if (d > 0)
		return parseInt(v) * 60 * 24;
	return 1000000000;
}
`
<commit_msg>pkg/html: regenerate<commit_after>package html

const style = `
#topbar {
	padding: 5px 10px;
	background: #E0EBF5;
}

#topbar a {
	color: #375EAB;
	text-decoration: none;
}

h1, h2, h3, h4 {
	margin: 0;
	padding: 0;
	color: #375EAB;
	font-weight: bold;
}

table {
	border: 1px solid #ccc;
	margin: 20px 5px;
	border-collapse: collapse;
	white-space: nowrap;
	text-overflow: ellipsis;
	overflow: hidden;
}

table caption {
	font-weight: bold;
}

table td, table th {
	vertical-align: top;
	padding: 2px 8px;
	text-overflow: ellipsis;
	overflow: hidden;
}

.namespace {
	font-weight: bold;
	font-size: large;
	color: #375EAB;
}

.position_table {
	border: 0px;
	margin: 0px;
	width: 100%;
	border-collapse: collapse;
}

.position_table td, .position_table tr {
	vertical-align: center;
	padding: 0px;
}

.position_table .search {
	text-align: right;
}

.list_table td, .list_table th {
	border-left: 1px solid #ccc;
}

.list_table th {
	background: #F4F4F4;
}

.list_table tr:nth-child(2n) {
	background: #F4F4F4;
}

.list_table tr:hover {
	background: #ffff99;
}

.list_table .namespace {
	width: 100pt;
	max-width: 100pt;
}

.list_table .title {
	width: 350pt;
	max-width: 350pt;
}

.list_table .commit_list {
	width: 500pt;
	max-width: 500pt;
}

.list_table .tag {
	font-family: monospace;
	font-size: 8pt;
	width: 40pt;
	max-width: 40pt;
}

.list_table .opts {
	width: 40pt;
	max-width: 40pt;
}

.list_table .status {
	width: 250pt;
	max-width: 250pt;
}

.list_table .patched {
	width: 60pt;
	max-width: 60pt;
	text-align: center;
}

.list_table .kernel {
	width: 80pt;
	max-width: 80pt;
}

.list_table .maintainers {
	width: 150pt;
	max-width: 150pt;
}

.list_table .result {
	width: 60pt;
	max-width: 60pt;
}

.list_table .stat {
	width: 50pt;
	max-width: 50pt;
	font-family: monospace;
	text-align: right;
}

.list_table .date {
	width: 60pt;
	max-width: 60pt;
	font-family: monospace;
	text-align: right;
}

.list_table .stat_name {
	width: 150pt;
	max-width: 150pt;
	font-family: monospace;
}

.list_table .stat_value {
	width: 120pt;
	max-width: 120pt;
	font-family: monospace;
}

.bad {
	color: #f00;
	font-weight: bold;
}

.inactive {
	color: #888;
}

.plain {
	text-decoration: none;
}

textarea {
	width:100%;
	font-family: monospace;
}

.mono {
	font-family: monospace;
}
`
const js = `
// Copyright 2018 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

function sortTable(item, colName, conv, desc = false) {
	table = item.parentNode.parentNode.parentNode.parentNode;
	rows = table.rows;
	col = findColumnByName(rows[0].getElementsByTagName("th"), colName);
	values = [];
	for (i = 1; i < rows.length; i++)
		values.push([conv(rows[i].getElementsByTagName("td")[col].textContent), rows[i]]);
	if (desc)
		desc = !isSorted(values.slice().reverse())
	else
		desc = isSorted(values);
	values.sort(function(a, b) {
		if (a[0] == b[0]) return 0;
		if (desc && a[0] > b[0] || !desc && a[0] < b[0]) return -1;
		return 1;
	});
	for (i = 0; i < values.length; i++)
		table.tBodies[0].appendChild(values[i][1]);
	return false;
}

function findColumnByName(headers, colName) {
	for (i = 0; i < headers.length; i++) {
		if (headers[i].textContent == colName)
			return i;
	}
	return 0;
}

function isSorted(values) {
	for (i = 0; i < values.length - 1; i++) {
		if (values[i][0] > values[i + 1][0])
			return false;
	}
	return true;
}

function textSort(v) { return v.toLowerCase(); }
function numSort(v) { return -parseInt(v); }
function floatSort(v) { return -parseFloat(v); }
function yesSort(v) { return v == "yes" ? 0 : 1; }
function reproSort(v) { return v == "C" ? 0 : v == "syz" ? 1 : 2; }
function patchedSort(v) { return v == "" ? -1 : parseInt(v); }

function timeSort(v) {
	if (v == "now")
		return 0;
	m = v.indexOf('m');
	h = v.indexOf('h');
	d = v.indexOf('d');
	if (m > 0 && h < 0)
		return parseInt(v);
	if (h > 0 && m > 0)
		return parseInt(v) * 60 + parseInt(v.substring(h + 1));
	if (d > 0 && h > 0)
		return parseInt(v) * 60 * 24 + parseInt(v.substring(d + 1)) * 60;
	if (d > 0)
		return parseInt(v) * 60 * 24;
	return 1000000000;
}
`
<|endoftext|>


<commit_before>// Package main provides ...
package main

import (
	"flag"
	"fmt"
	"log"
	"math"
	"math/rand"
	"os"
	"runtime"
	"time"

	"github.com/mstruebing/tldr"
	"github.com/mstruebing/tldr/cache"
)

// Help message constants
const (
	listAllUsage  = "list all available commands for the current platform"
	platformUsage = "select platform; supported are: linux, osx, sunos, common"
	pathUsage     = "render a local page for testing purposes"
	updateUsage   = "update local database"
	versionUsage  = "print version and exit"
	randomUsage   = "prints a random page"
	historyUsage  = "show the latest search history"
)

const (
	remoteURL = "https://tldr.sh/assets/tldr.zip"
	ttl       = time.Hour * 24 * 7
)

const currentPlattform = runtime.GOOS

func printVersion() {
	fmt.Println("tldr v 1.3.0")
	fmt.Println("Copyright (C) 2017 Max Strübing")
	fmt.Println("Source available at https://github.com/mstruebing/tldr")
}

func listAllPages() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating repository: %s", err)
	}

	pages, err := repository.Pages()
	if err != nil {
		log.Fatalf("ERROR: getting pages: %s", err)
	}

	for _, page := range pages {
		fmt.Println(page)
	}
}

func printPageInPath(path string) {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		log.Fatal("ERROR: page doesn't exist")
	}

	page, err := os.Open(path)
	if err != nil {
		log.Fatal("ERROR: opening the page")
	}
	defer page.Close()

	err = tldr.Write(page, os.Stdout)
	if err != nil {
		log.Fatalf("ERROR: rendering the page: %s", err)
	}
}

func printPage(page string) {
	if page == "" {
		flag.PrintDefaults()
		os.Exit(0)
	}

	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	platform := tldr.CurrentPlatform(currentPlattform)
	markdown, err := repository.Markdown(platform, page)
	if err != nil {
		var platforms []string
		platforms, err = tldr.AvailablePlatforms(repository, currentPlattform)
		if err != nil {
			log.Fatalf("ERROR: getting available platforms: %s", err)
		}

		for _, platform = range platforms {
			markdown, err = repository.Markdown(platform, page)
			if err == nil {
				break
			}
		}
		if err != nil {
			log.Fatalf("ERROR: no page found for '%s' in any available platform", page)
		}
	}
	defer markdown.Close()

	err = tldr.Write(markdown, os.Stdout)
	if err != nil {
		log.Fatalf("ERROR: writing markdown: %s", err)
	}

	err = repository.RecordHistory(page)
	if err != nil {
		log.Fatalf("ERROR: saving history: %s", err)
	}
}

func printPageForPlatform(page string, platform string) {
	if page == "" {
		log.Fatal("ERROR: no page provided")
	}

	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	markdown, err := repository.Markdown(platform, page)
	if err != nil {
		log.Fatalf("ERROR: getting markdown for '%s/%s': %s", platform, page, err)
	}
	defer markdown.Close()

	err = tldr.Write(markdown, os.Stdout)
	if err != nil {
		log.Fatalf("ERROR: writing markdown: %s", err)
	}
}

func printRandomPage() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	pages, err := repository.Pages()
	if err != nil {
		log.Fatalf("ERROR: getting pages: %s", err)
	}
	s := rand.NewSource(time.Now().Unix())
	r := rand.New(s) // initialize local pseudorandom generator
	printPage(pages[r.Intn(len(pages))])
}

func updatePages() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}
	err = repository.Reload()
	if err != nil {
		log.Fatalf("ERROR: updating cache: %s", err)
	}
}

func printHistory() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	history, err := repository.LoadHistory()
	if err != nil {
		log.Fatalf("ERROR: error loading history: %s", err)
	}

	hisLen := len(*history)
	if hisLen == 0 {
		fmt.Println("No history is available yet")
	} else { //default print last 10.
		size := int(math.Min(10, float64(hisLen)))
		for i := 1; i <= size; i++ {
			record := (*history)[hisLen-i]
			fmt.Printf("%s\n", record)
		}
	}
}

func main() {
	version := flag.Bool("version", false, versionUsage)
	flag.BoolVar(version, "v", false, versionUsage)

	update := flag.Bool("update", false, updateUsage)
	flag.BoolVar(update, "u", false, updateUsage)

	path := flag.String("path", "", pathUsage)
	// f like file
	flag.StringVar(path, "f", "", pathUsage)

	listAll := flag.Bool("list-all", false, listAllUsage)
	flag.BoolVar(listAll, "a", false, listAllUsage)

	platform := flag.String("platform", "", platformUsage)
	flag.StringVar(platform, "p", "", platformUsage)

	random := flag.Bool("random", false, randomUsage)
	flag.BoolVar(random, "r", false, randomUsage)

	history := flag.Bool("history", false, historyUsage)
	flag.BoolVar(history, "t", false, historyUsage)

	flag.Parse()

	if *version {
		printVersion()
	} else if *update {
		updatePages()
	} else if *path != "" {
		printPageInPath(*path)
	} else if *listAll {
		listAllPages()
	} else if *platform != "" {
		page := flag.Arg(0)
		printPageForPlatform(page, *platform)
	} else if *random {
		printRandomPage()
	} else if *history {
		printHistory()
	} else {
		page := flag.Arg(0)
		printPage(page)
	}
}
<commit_msg>chore: update version<commit_after>// Package main provides ...
package main

import (
	"flag"
	"fmt"
	"log"
	"math"
	"math/rand"
	"os"
	"runtime"
	"time"

	"github.com/mstruebing/tldr"
	"github.com/mstruebing/tldr/cache"
)

// Help message constants
const (
	listAllUsage  = "list all available commands for the current platform"
	platformUsage = "select platform; supported are: linux, osx, sunos, common"
	pathUsage     = "render a local page for testing purposes"
	updateUsage   = "update local database"
	versionUsage  = "print version and exit"
	randomUsage   = "prints a random page"
	historyUsage  = "show the latest search history"
)

const (
	remoteURL = "https://tldr.sh/assets/tldr.zip"
	ttl       = time.Hour * 24 * 7
)

const currentPlattform = runtime.GOOS

func printVersion() {
	fmt.Println("tldr v 1.3.1")
	fmt.Println("Copyright (C) 2017 Max Strübing")
	fmt.Println("Source available at https://github.com/mstruebing/tldr")
}

func listAllPages() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating repository: %s", err)
	}

	pages, err := repository.Pages()
	if err != nil {
		log.Fatalf("ERROR: getting pages: %s", err)
	}

	for _, page := range pages {
		fmt.Println(page)
	}
}

func printPageInPath(path string) {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		log.Fatal("ERROR: page doesn't exist")
	}

	page, err := os.Open(path)
	if err != nil {
		log.Fatal("ERROR: opening the page")
	}
	defer page.Close()

	err = tldr.Write(page, os.Stdout)
	if err != nil {
		log.Fatalf("ERROR: rendering the page: %s", err)
	}
}

func printPage(page string) {
	if page == "" {
		flag.PrintDefaults()
		os.Exit(0)
	}

	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	platform := tldr.CurrentPlatform(currentPlattform)
	markdown, err := repository.Markdown(platform, page)
	if err != nil {
		var platforms []string
		platforms, err = tldr.AvailablePlatforms(repository, currentPlattform)
		if err != nil {
			log.Fatalf("ERROR: getting available platforms: %s", err)
		}

		for _, platform = range platforms {
			markdown, err = repository.Markdown(platform, page)
			if err == nil {
				break
			}
		}
		if err != nil {
			log.Fatalf("ERROR: no page found for '%s' in any available platform", page)
		}
	}
	defer markdown.Close()

	err = tldr.Write(markdown, os.Stdout)
	if err != nil {
		log.Fatalf("ERROR: writing markdown: %s", err)
	}

	err = repository.RecordHistory(page)
	if err != nil {
		log.Fatalf("ERROR: saving history: %s", err)
	}
}

func printPageForPlatform(page string, platform string) {
	if page == "" {
		log.Fatal("ERROR: no page provided")
	}

	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	markdown, err := repository.Markdown(platform, page)
	if err != nil {
		log.Fatalf("ERROR: getting markdown for '%s/%s': %s", platform, page, err)
	}
	defer markdown.Close()

	err = tldr.Write(markdown, os.Stdout)
	if err != nil {
		log.Fatalf("ERROR: writing markdown: %s", err)
	}
}

func printRandomPage() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	pages, err := repository.Pages()
	if err != nil {
		log.Fatalf("ERROR: getting pages: %s", err)
	}
	s := rand.NewSource(time.Now().Unix())
	r := rand.New(s) // initialize local pseudorandom generator
	printPage(pages[r.Intn(len(pages))])
}

func updatePages() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}
	err = repository.Reload()
	if err != nil {
		log.Fatalf("ERROR: updating cache: %s", err)
	}
}

func printHistory() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	history, err := repository.LoadHistory()
	if err != nil {
		log.Fatalf("ERROR: error loading history: %s", err)
	}

	hisLen := len(*history)
	if hisLen == 0 {
		fmt.Println("No history is available yet")
	} else { //default print last 10.
		size := int(math.Min(10, float64(hisLen)))
		for i := 1; i <= size; i++ {
			record := (*history)[hisLen-i]
			fmt.Printf("%s\n", record)
		}
	}
}

func main() {
	version := flag.Bool("version", false, versionUsage)
	flag.BoolVar(version, "v", false, versionUsage)

	update := flag.Bool("update", false, updateUsage)
	flag.BoolVar(update, "u", false, updateUsage)

	path := flag.String("path", "", pathUsage)
	// f like file
	flag.StringVar(path, "f", "", pathUsage)

	listAll := flag.Bool("list-all", false, listAllUsage)
	flag.BoolVar(listAll, "a", false, listAllUsage)

	platform := flag.String("platform", "", platformUsage)
	flag.StringVar(platform, "p", "", platformUsage)

	random := flag.Bool("random", false, randomUsage)
	flag.BoolVar(random, "r", false, randomUsage)

	history := flag.Bool("history", false, historyUsage)
	flag.BoolVar(history, "t", false, historyUsage)

	flag.Parse()

	if *version {
		printVersion()
	} else if *update {
		updatePages()
	} else if *path != "" {
		printPageInPath(*path)
	} else if *listAll {
		listAllPages()
	} else if *platform != "" {
		page := flag.Arg(0)
		printPageForPlatform(page, *platform)
	} else if *random {
		printRandomPage()
	} else if *history {
		printHistory()
	} else {
		page := flag.Arg(0)
		printPage(page)
	}
}
<|endoftext|>


<commit_before>package coinbase

// Contains code for making requests
// You don't want to call these, probably. Look in methods.go for functions to call

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	COINBASE_API_ENDPOINT = "https://coinbase.com/api/v1/"
)

// The client holds the necessary keys and our HTTP client for making requests
type Client struct {
	APIKey     string
	httpClient *http.Client
}

// Call an API method with auth, return the raw, unprocessed body
func (c *Client) Call(http_method string, api_method string, params map[string]interface{}) ([]byte, error) {
	// Build HTTP client
	if c.httpClient == nil {
		c.httpClient = &http.Client{}
	}

	apiURL := COINBASE_API_ENDPOINT + api_method

	var req *http.Request
	var err error
	if http_method == "POST" {
		params["api_key"] = c.APIKey
		postBody, err := json.Marshal(params)
		if err != nil {
			return nil, err
		}

		req, err = http.NewRequest("POST", apiURL, bytes.NewReader(postBody))
		if err != nil {
			return nil, err
		}
	} else if http_method == "GET" {
		apiURL = apiURL + "/?api_key=" + c.APIKey
		req, err = http.NewRequest("GET", apiURL, nil)
		if err != nil {
			return nil, err
		}
	} else {
		return nil, fmt.Errorf("Unknown HTTP method: %s", http_method)
	}

	// Make the request
	req.Header.Set("Content-type", "application/json")
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}

	// Make sure we close the body stream no matter what
	defer resp.Body.Close()

	// Read body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	// TODO: Check status code

	// Return
	return body, nil
}
<commit_msg>Allow for unauthed api requests<commit_after>package coinbase

// Contains code for making requests
// You don't want to call these, probably. Look in methods.go for functions to call

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	COINBASE_API_ENDPOINT = "https://coinbase.com/api/v1/"
)

// The client holds the necessary keys and our HTTP client for making requests
type Client struct {
	APIKey     string
	httpClient *http.Client
}

// Call an API method with auth, return the raw, unprocessed body
func (c *Client) Call(http_method string, api_method string, params map[string]interface{}) ([]byte, error) {
	// Build HTTP client
	if c.httpClient == nil {
		c.httpClient = &http.Client{}
	}

	apiURL := COINBASE_API_ENDPOINT + api_method

	var req *http.Request
	var err error
	if http_method == "POST" {
		if c.APIKey != "" {
			params["api_key"] = c.APIKey
		}

		postBody, err := json.Marshal(params)
		if err != nil {
			return nil, err
		}

		req, err = http.NewRequest("POST", apiURL, bytes.NewReader(postBody))
		if err != nil {
			return nil, err
		}
	} else if http_method == "GET" {
		if c.APIKey != "" {
			apiURL = apiURL + "/?api_key=" + c.APIKey
		}

		req, err = http.NewRequest("GET", apiURL, nil)
		if err != nil {
			return nil, err
		}
	} else {
		return nil, fmt.Errorf("Unknown HTTP method: %s", http_method)
	}

	// Make the request
	req.Header.Set("Content-type", "application/json")
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}

	// Make sure we close the body stream no matter what
	defer resp.Body.Close()

	// Read body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	// TODO: Check status code

	// Return
	return body, nil
}
<|endoftext|>


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

package clone

import (
	"fmt"
	"github.com/andreaskoch/dotman/ui"
	"github.com/andreaskoch/dotman/util/command"
	"strings"
)

const (
	ActionName        = "clone"
	ActionDescription = "Clone a dotfile repository."
)

type Clone struct {
	baseDirectory string
}

func New(baseDirectory string) *Clone {
	return &Clone{
		baseDirectory: baseDirectory,
	}
}

func (clone *Clone) Name() string {
	return ActionName
}

func (clone *Clone) Description() string {
	return ActionDescription
}

func (clone *Clone) Execute(arguments []string) {
	clone.execute(false, arguments)
}

func (clone *Clone) DryRun(arguments []string) {
	clone.execute(true, arguments)
}

func (clone *Clone) execute(executeADryRunOnly bool, arguments []string) {

	if len(arguments) == 0 {
		ui.Message("Please specifiy a repository path (e.g. git@bitbucket.org:andreaskoch/dotfiles-public.git).")
		return
	}

	// extract the repository url from the arguments
	repositoryUrl := ""
	if len(arguments) > 0 {
		repositoryUrl = strings.TrimSpace(arguments[0])
	}

	ui.Message("Cloning dotfile repository %q into %q.", repositoryUrl, clone.baseDirectory)
	if !executeADryRunOnly {
		command.Execute(clone.baseDirectory, fmt.Sprintf("git clone --recursive %s", repositoryUrl))
	}
}
<commit_msg>Clone command: Print out errors procuded by git.<commit_after>// Copyright 2013 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package clone

import (
	"fmt"
	"github.com/andreaskoch/dotman/ui"
	"github.com/andreaskoch/dotman/util/command"
	"strings"
)

const (
	ActionName        = "clone"
	ActionDescription = "Clone a dotfile repository."
)

type Clone struct {
	baseDirectory string
}

func New(baseDirectory string) *Clone {
	return &Clone{
		baseDirectory: baseDirectory,
	}
}

func (clone *Clone) Name() string {
	return ActionName
}

func (clone *Clone) Description() string {
	return ActionDescription
}

func (clone *Clone) Execute(arguments []string) {
	clone.execute(false, arguments)
}

func (clone *Clone) DryRun(arguments []string) {
	clone.execute(true, arguments)
}

func (clone *Clone) execute(executeADryRunOnly bool, arguments []string) {

	if len(arguments) == 0 {
		ui.Message("Please specifiy a repository path (e.g. git@bitbucket.org:andreaskoch/dotfiles-public.git).")
		return
	}

	// extract the repository url from the arguments
	repositoryUrl := ""
	if len(arguments) > 0 {
		repositoryUrl = strings.TrimSpace(arguments[0])
	}

	ui.Message("Cloning dotfile repository %q into %q.", repositoryUrl, clone.baseDirectory)
	if !executeADryRunOnly {
		if err := command.Execute(clone.baseDirectory, fmt.Sprintf("git clone --recursive %s", repositoryUrl)); err != nil {
			ui.Fatal("%s", err)
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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 tag

import (
	"testing"
	"text/template"
)

func TestEnvTemplateTagger_GenerateFullyQualifiedImageName(t *testing.T) {
	type fields struct {
		Template string
	}
	type args struct {
		opts *TagOptions
		env  []string
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    string
		wantErr bool
	}{
		{
			name: "empty env",
			fields: fields{
				Template: "{{.IMAGE_NAME}}:{{.DIGEST}}",
			},
			args: args{
				opts: &TagOptions{
					ImageName: "foo",
					Digest:    "bar",
				},
			},
			want: "foo:bar",
		},
		{
			name: "env",
			fields: fields{
				Template: "{{.FOO}}-{{.BAZ}}:latest",
			},
			args: args{
				env: []string{"FOO=BAR", "BAZ=BAT"},
				opts: &TagOptions{
					ImageName: "foo",
					Digest:    "bar",
				},
			},
			want: "BAR-BAT:latest",
		},
		{
			name: "opts precedence",
			fields: fields{
				Template: "{{.IMAGE_NAME}}-{{.FROM_ENV}}:latest",
			},
			args: args{
				env: []string{"FROM_ENV=FOO", "IMAGE_NAME=BAT"},
				opts: &TagOptions{
					ImageName: "image_name",
					Digest:    "bar",
				},
			},
			want: "image_name-FOO:latest",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt := tt
			t.Parallel()
			c := &EnvTemplateTagger{
				Template: template.Must(template.New("").Parse(tt.fields.Template)),
			}
			environ = func() []string {
				return tt.args.env
			}
			got, err := c.GenerateFullyQualifiedImageName("", tt.args.opts)
			if (err != nil) != tt.wantErr {
				t.Errorf("EnvTemplateTagger.GenerateFullyQualifiedImageName() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("EnvTemplateTagger.GenerateFullyQualifiedImageName() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestNewEnvTemplateTagger(t *testing.T) {
	type args struct {
		t string
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "valid template",
			args: args{
				t: "{{.FOO}}",
			},
			wantErr: false,
		},
		{
			name: "invalid template",
			args: args{
				t: "{{.FOO",
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := NewEnvTemplateTagger(tt.args.t)
			if (err != nil) != tt.wantErr {
				t.Errorf("NewEnvTemplateTagger() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
		})
	}
}
<commit_msg>Use Test Utils<commit_after>/*
Copyright 2018 Google LLC

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 tag

import (
	"testing"
	"text/template"

	"github.com/GoogleCloudPlatform/skaffold/testutil"
)

func TestEnvTemplateTagger_GenerateFullyQualifiedImageName(t *testing.T) {
	type fields struct {
		Template string
	}
	type args struct {
		opts *TagOptions
		env  []string
	}
	tests := []struct {
		name      string
		fields    fields
		args      args
		want      string
		shouldErr bool
	}{
		{
			name: "empty env",
			fields: fields{
				Template: "{{.IMAGE_NAME}}:{{.DIGEST}}",
			},
			args: args{
				opts: &TagOptions{
					ImageName: "foo",
					Digest:    "bar",
				},
			},
			want: "foo:bar",
		},
		{
			name: "env",
			fields: fields{
				Template: "{{.FOO}}-{{.BAZ}}:latest",
			},
			args: args{
				env: []string{"FOO=BAR", "BAZ=BAT"},
				opts: &TagOptions{
					ImageName: "foo",
					Digest:    "bar",
				},
			},
			want: "BAR-BAT:latest",
		},
		{
			name: "opts precedence",
			fields: fields{
				Template: "{{.IMAGE_NAME}}-{{.FROM_ENV}}:latest",
			},
			args: args{
				env: []string{"FROM_ENV=FOO", "IMAGE_NAME=BAT"},
				opts: &TagOptions{
					ImageName: "image_name",
					Digest:    "bar",
				},
			},
			want: "image_name-FOO:latest",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt := tt
			t.Parallel()
			c := &EnvTemplateTagger{
				Template: template.Must(template.New("").Parse(tt.fields.Template)),
			}
			environ = func() []string {
				return tt.args.env
			}

			got, err := c.GenerateFullyQualifiedImageName("", tt.args.opts)
			testutil.CheckErrorAndDeepEqual(t, tt.shouldErr, err, tt.want, got)
		})
	}
}

func TestNewEnvTemplateTagger(t *testing.T) {
	type args struct {
		t string
	}
	tests := []struct {
		name      string
		args      args
		shouldErr bool
	}{
		{
			name: "valid template",
			args: args{
				t: "{{.FOO}}",
			},
		},
		{
			name: "invalid template",
			args: args{
				t: "{{.FOO",
			},
			shouldErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := NewEnvTemplateTagger(tt.args.t)
			testutil.CheckError(t, tt.shouldErr, err)
		})
	}
}
<|endoftext|>