<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package main

import (
	"bufio"
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/spf13/pflag"

	v1 "k8s.io/api/core/v1"

	"kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/components"
	"kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/rbac"
	"kubevirt.io/kubevirt/tools/marketplace/helper"
	"kubevirt.io/kubevirt/tools/util"
)

type templateData struct {
	Namespace              string
	CDINamespace           string
	CSVNamespace           string
	DockerTag              string
	DockerPrefix           string
	ImagePrefix            string
	ImagePullPolicy        string
	Verbosity              string
	CsvVersion             string
	QuayRepository         string
	ReplacesCsvVersion     string
	OperatorDeploymentSpec string
	OperatorCsv            string
	OperatorRules          string
	KubeVirtLogo           string
	PackageName            string
	CreatedAt              string
	VirtOperatorSha        string
	VirtApiSha             string
	VirtControllerSha      string
	VirtHandlerSha         string
	VirtLauncherSha        string
	GsSha                  string
	PriorityClassSpec      string
	FeatureGates           []string
	GeneratedManifests     map[string]string
}

func main() {
	namespace := flag.String("namespace", "", "")
	csvNamespace := flag.String("csv-namespace", "placeholder", "")
	cdiNamespace := flag.String("cdi-namespace", "", "")
	dockerPrefix := flag.String("container-prefix", "", "")
	imagePrefix := flag.String("image-prefix", "", "")
	dockerTag := flag.String("container-tag", "", "")
	csvVersion := flag.String("csv-version", "", "")
	imagePullPolicy := flag.String("image-pull-policy", "IfNotPresent", "")
	verbosity := flag.String("verbosity", "2", "")
	genDir := flag.String("generated-manifests-dir", "", "")
	inputFile := flag.String("input-file", "", "")
	processFiles := flag.Bool("process-files", false, "")
	processVars := flag.Bool("process-vars", false, "")
	kubeVirtLogoPath := flag.String("kubevirt-logo-path", "", "")
	packageName := flag.String("package-name", "", "")
	bundleOutDir := flag.String("bundle-out-dir", "", "")
	quayRepository := flag.String("quay-repository", "", "")
	virtOperatorSha := flag.String("virt-operator-sha", "", "")
	virtApiSha := flag.String("virt-api-sha", "", "")
	virtControllerSha := flag.String("virt-controller-sha", "", "")
	virtHandlerSha := flag.String("virt-handler-sha", "", "")
	virtLauncherSha := flag.String("virt-launcher-sha", "", "")
	gsSha := flag.String("gs-sha", "", "")
	featureGates := flag.String("feature-gates", "", "")

	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
	pflag.CommandLine.ParseErrorsWhitelist.UnknownFlags = true
	pflag.Parse()

	if path := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); path != "" {
		if err := os.Chdir(path); err != nil {
			panic(err)
		}
	}

	if !(*processFiles || *processVars) {
		panic("at least one of process-files or process-vars must be true")
	}

	data := templateData{
		GeneratedManifests: make(map[string]string),
	}

	if *processVars {
		data.Namespace = *namespace
		data.CSVNamespace = *csvNamespace
		data.CDINamespace = *cdiNamespace
		data.DockerTag = *dockerTag
		data.DockerPrefix = *dockerPrefix
		data.ImagePrefix = *imagePrefix
		data.ImagePullPolicy = *imagePullPolicy
		data.Verbosity = fmt.Sprintf("\"%s\"", *verbosity)
		data.CsvVersion = *csvVersion
		data.QuayRepository = *quayRepository
		data.VirtOperatorSha = *virtOperatorSha
		data.VirtApiSha = *virtApiSha
		data.VirtControllerSha = *virtControllerSha
		data.VirtHandlerSha = *virtHandlerSha
		data.VirtLauncherSha = *virtLauncherSha
		data.GsSha = *gsSha
		data.OperatorRules = getOperatorRules()
		data.KubeVirtLogo = getKubeVirtLogo(*kubeVirtLogoPath)
		data.PackageName = *packageName
		data.CreatedAt = getTimestamp()
		data.ReplacesCsvVersion = ""
		data.OperatorDeploymentSpec = getOperatorDeploymentSpec(data, 2)
		data.PriorityClassSpec = getPriorityClassSpec(2)
		if *featureGates != "" {
			data.FeatureGates = strings.Split(*featureGates, ",")
		}

		// operator deployment differs a bit in normal manifest and CSV
		if strings.Contains(*inputFile, ".clusterserviceversion.yaml") {
			// prevent loading latest bundle from Quay for every file, only do it for the CSV manifest
			if *bundleOutDir != "" && data.QuayRepository != "" {
				bundleHelper, err := helper.NewBundleHelper(*quayRepository, *packageName)
				if err != nil {
					panic(err)
				}
				latestVersion := bundleHelper.GetLatestPublishedCSVVersion()
				if latestVersion != "" {
					// prevent generating the same version again
					if strings.HasSuffix(latestVersion, *csvVersion) {
						panic(fmt.Errorf("CSV version %s is already published!", *csvVersion))
					}
					data.ReplacesCsvVersion = latestVersion
					// also copy old manifests to out dir
					bundleHelper.AddOldManifests(*bundleOutDir, *csvVersion)
				}
			}
		}

	} else {
		// keep templates
		data.Namespace = "{{.Namespace}}"
		data.CDINamespace = "{{.CDINamespace}}"
		data.DockerTag = "{{.DockerTag}}"
		data.DockerPrefix = "{{.DockerPrefix}}"
		data.ImagePrefix = "{{.ImagePrefix}}"
		data.ImagePullPolicy = "{{.ImagePullPolicy}}"
		data.Verbosity = "{{.Verbosity}}"
		data.CsvVersion = "{{.CsvVersion}}"
		data.QuayRepository = "{{.QuayRepository}}"
		data.VirtOperatorSha = "{{.VirtOperatorSha}}"
		data.VirtApiSha = "{{.VirtApiSha}}"
		data.VirtControllerSha = "{{.VirtControllerSha}}"
		data.VirtHandlerSha = "{{.VirtHandlerSha}}"
		data.VirtLauncherSha = "{{.VirtLauncherSha}}"
		data.ReplacesCsvVersion = "{{.ReplacesCsvVersion}}"
		data.OperatorDeploymentSpec = "{{.OperatorDeploymentSpec}}"
		data.OperatorCsv = "{{.OperatorCsv}}"
		data.OperatorRules = "{{.OperatorRules}}"
		data.KubeVirtLogo = "{{.KubeVirtLogo}}"
		data.PackageName = "{{.PackageName}}"
		data.CreatedAt = "{{.CreatedAt}}"
	}

	if *processFiles {
		manifests, err := os.ReadDir(*genDir)
		if err != nil {
			panic(err)
		}

		for _, manifest := range manifests {
			if manifest.IsDir() {
				continue
			}
			b, err := os.ReadFile(filepath.Join(*genDir, manifest.Name()))
			if err != nil {
				panic(err)
			}
			data.GeneratedManifests[manifest.Name()] = string(b)
		}
	}

	tmpl := template.Must(template.ParseFiles(*inputFile))
	err := tmpl.Execute(os.Stdout, data)
	if err != nil {
		panic(err)
	}
}

func getOperatorRules() string {
	rules := rbac.NewOperatorClusterRole().Rules
	writer := strings.Builder{}
	for _, rule := range rules {
		err := util.MarshallObject(rule, &writer)
		if err != nil {
			panic(err)
		}
	}
	return fixResourceString(writer.String(), 14)
}

func getPriorityClassSpec(indentation int) string {
	priorityClassSpec := components.NewKubeVirtPriorityClassCR()
	writer := strings.Builder{}
	err := util.MarshallObject(priorityClassSpec, &writer)
	if err != nil {
		panic(err)
	}
	return fixResourceString(writer.String(), indentation)
}

func getOperatorDeploymentSpec(data templateData, indentation int) string {
	version := data.DockerTag
	if data.VirtOperatorSha != "" {
		version = data.VirtOperatorSha
	}

	deployment, err := components.NewOperatorDeployment(
		data.Namespace,
		data.DockerPrefix,
		data.ImagePrefix,
		version,
		v1.PullPolicy(data.ImagePullPolicy),
		data.Verbosity,
		data.DockerTag,
		data.VirtApiSha,
		data.VirtControllerSha,
		data.VirtHandlerSha,
		data.VirtLauncherSha,
		data.GsSha)
	if err != nil {
		panic(err)
	}

	writer := strings.Builder{}
	err = util.MarshallObject(deployment.Spec, &writer)
	if err != nil {
		panic(err)
	}
	spec := writer.String()

	return fixResourceString(spec, indentation)
}

func fixResourceString(in string, indention int) string {
	out := strings.Builder{}
	scanner := bufio.NewScanner(strings.NewReader(in))
	for scanner.Scan() {
		line := scanner.Text()
		// remove separator lines
		if !strings.HasPrefix(line, "---") {
			// indent so that it fits into the manifest
			// spaces is is indention - 2, because we want to have 2 spaces less for being able to start an array
			spaces := strings.Repeat(" ", indention-2)
			if strings.HasPrefix(line, "apiGroups") {
				// spaces + array start
				out.WriteString(spaces + "- " + line + "\n")
			} else {
				// 2 more spaces
				out.WriteString(spaces + "  " + line + "\n")
			}
		}
	}
	return out.String()
}

func getKubeVirtLogo(path string) string {
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}

	// Read entire file into byte slice.
	reader := bufio.NewReader(file)
	content, err := io.ReadAll(reader)
	if err != nil {
		panic(err)
	}

	// Encode as base64.
	encoded := base64.StdEncoding.EncodeToString(content)
	return encoded
}

func getTimestamp() string {
	return time.Now().UTC().Format("2006-01-02T15:04:05Z")
}
<commit_msg>Fix(temporary): Enable correct feature gate for non-root<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package main

import (
	"bufio"
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/spf13/pflag"

	v1 "k8s.io/api/core/v1"

	"kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/components"
	"kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/rbac"
	"kubevirt.io/kubevirt/tools/marketplace/helper"
	"kubevirt.io/kubevirt/tools/util"
)

type templateData struct {
	Namespace              string
	CDINamespace           string
	CSVNamespace           string
	DockerTag              string
	DockerPrefix           string
	ImagePrefix            string
	ImagePullPolicy        string
	Verbosity              string
	CsvVersion             string
	QuayRepository         string
	ReplacesCsvVersion     string
	OperatorDeploymentSpec string
	OperatorCsv            string
	OperatorRules          string
	KubeVirtLogo           string
	PackageName            string
	CreatedAt              string
	VirtOperatorSha        string
	VirtApiSha             string
	VirtControllerSha      string
	VirtHandlerSha         string
	VirtLauncherSha        string
	GsSha                  string
	PriorityClassSpec      string
	FeatureGates           []string
	GeneratedManifests     map[string]string
}

func main() {
	namespace := flag.String("namespace", "", "")
	csvNamespace := flag.String("csv-namespace", "placeholder", "")
	cdiNamespace := flag.String("cdi-namespace", "", "")
	dockerPrefix := flag.String("container-prefix", "", "")
	imagePrefix := flag.String("image-prefix", "", "")
	dockerTag := flag.String("container-tag", "", "")
	csvVersion := flag.String("csv-version", "", "")
	imagePullPolicy := flag.String("image-pull-policy", "IfNotPresent", "")
	verbosity := flag.String("verbosity", "2", "")
	genDir := flag.String("generated-manifests-dir", "", "")
	inputFile := flag.String("input-file", "", "")
	processFiles := flag.Bool("process-files", false, "")
	processVars := flag.Bool("process-vars", false, "")
	kubeVirtLogoPath := flag.String("kubevirt-logo-path", "", "")
	packageName := flag.String("package-name", "", "")
	bundleOutDir := flag.String("bundle-out-dir", "", "")
	quayRepository := flag.String("quay-repository", "", "")
	virtOperatorSha := flag.String("virt-operator-sha", "", "")
	virtApiSha := flag.String("virt-api-sha", "", "")
	virtControllerSha := flag.String("virt-controller-sha", "", "")
	virtHandlerSha := flag.String("virt-handler-sha", "", "")
	virtLauncherSha := flag.String("virt-launcher-sha", "", "")
	gsSha := flag.String("gs-sha", "", "")
	featureGates := flag.String("feature-gates", "", "")

	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
	pflag.CommandLine.ParseErrorsWhitelist.UnknownFlags = true
	pflag.Parse()

	if path := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); path != "" {
		if err := os.Chdir(path); err != nil {
			panic(err)
		}
	}

	if !(*processFiles || *processVars) {
		panic("at least one of process-files or process-vars must be true")
	}

	data := templateData{
		GeneratedManifests: make(map[string]string),
	}

	if featureGates != nil && strings.Contains(*featureGates, "NonRoot") && !strings.Contains(*featureGates, "NonRootExperimental") {
		*featureGates = *featureGates + ",NonRootExperimental"
	}

	if *processVars {
		data.Namespace = *namespace
		data.CSVNamespace = *csvNamespace
		data.CDINamespace = *cdiNamespace
		data.DockerTag = *dockerTag
		data.DockerPrefix = *dockerPrefix
		data.ImagePrefix = *imagePrefix
		data.ImagePullPolicy = *imagePullPolicy
		data.Verbosity = fmt.Sprintf("\"%s\"", *verbosity)
		data.CsvVersion = *csvVersion
		data.QuayRepository = *quayRepository
		data.VirtOperatorSha = *virtOperatorSha
		data.VirtApiSha = *virtApiSha
		data.VirtControllerSha = *virtControllerSha
		data.VirtHandlerSha = *virtHandlerSha
		data.VirtLauncherSha = *virtLauncherSha
		data.GsSha = *gsSha
		data.OperatorRules = getOperatorRules()
		data.KubeVirtLogo = getKubeVirtLogo(*kubeVirtLogoPath)
		data.PackageName = *packageName
		data.CreatedAt = getTimestamp()
		data.ReplacesCsvVersion = ""
		data.OperatorDeploymentSpec = getOperatorDeploymentSpec(data, 2)
		data.PriorityClassSpec = getPriorityClassSpec(2)
		if *featureGates != "" {
			data.FeatureGates = strings.Split(*featureGates, ",")
		}

		// operator deployment differs a bit in normal manifest and CSV
		if strings.Contains(*inputFile, ".clusterserviceversion.yaml") {
			// prevent loading latest bundle from Quay for every file, only do it for the CSV manifest
			if *bundleOutDir != "" && data.QuayRepository != "" {
				bundleHelper, err := helper.NewBundleHelper(*quayRepository, *packageName)
				if err != nil {
					panic(err)
				}
				latestVersion := bundleHelper.GetLatestPublishedCSVVersion()
				if latestVersion != "" {
					// prevent generating the same version again
					if strings.HasSuffix(latestVersion, *csvVersion) {
						panic(fmt.Errorf("CSV version %s is already published!", *csvVersion))
					}
					data.ReplacesCsvVersion = latestVersion
					// also copy old manifests to out dir
					bundleHelper.AddOldManifests(*bundleOutDir, *csvVersion)
				}
			}
		}

	} else {
		// keep templates
		data.Namespace = "{{.Namespace}}"
		data.CDINamespace = "{{.CDINamespace}}"
		data.DockerTag = "{{.DockerTag}}"
		data.DockerPrefix = "{{.DockerPrefix}}"
		data.ImagePrefix = "{{.ImagePrefix}}"
		data.ImagePullPolicy = "{{.ImagePullPolicy}}"
		data.Verbosity = "{{.Verbosity}}"
		data.CsvVersion = "{{.CsvVersion}}"
		data.QuayRepository = "{{.QuayRepository}}"
		data.VirtOperatorSha = "{{.VirtOperatorSha}}"
		data.VirtApiSha = "{{.VirtApiSha}}"
		data.VirtControllerSha = "{{.VirtControllerSha}}"
		data.VirtHandlerSha = "{{.VirtHandlerSha}}"
		data.VirtLauncherSha = "{{.VirtLauncherSha}}"
		data.ReplacesCsvVersion = "{{.ReplacesCsvVersion}}"
		data.OperatorDeploymentSpec = "{{.OperatorDeploymentSpec}}"
		data.OperatorCsv = "{{.OperatorCsv}}"
		data.OperatorRules = "{{.OperatorRules}}"
		data.KubeVirtLogo = "{{.KubeVirtLogo}}"
		data.PackageName = "{{.PackageName}}"
		data.CreatedAt = "{{.CreatedAt}}"
	}

	if *processFiles {
		manifests, err := os.ReadDir(*genDir)
		if err != nil {
			panic(err)
		}

		for _, manifest := range manifests {
			if manifest.IsDir() {
				continue
			}
			b, err := os.ReadFile(filepath.Join(*genDir, manifest.Name()))
			if err != nil {
				panic(err)
			}
			data.GeneratedManifests[manifest.Name()] = string(b)
		}
	}

	tmpl := template.Must(template.ParseFiles(*inputFile))
	err := tmpl.Execute(os.Stdout, data)
	if err != nil {
		panic(err)
	}
}

func getOperatorRules() string {
	rules := rbac.NewOperatorClusterRole().Rules
	writer := strings.Builder{}
	for _, rule := range rules {
		err := util.MarshallObject(rule, &writer)
		if err != nil {
			panic(err)
		}
	}
	return fixResourceString(writer.String(), 14)
}

func getPriorityClassSpec(indentation int) string {
	priorityClassSpec := components.NewKubeVirtPriorityClassCR()
	writer := strings.Builder{}
	err := util.MarshallObject(priorityClassSpec, &writer)
	if err != nil {
		panic(err)
	}
	return fixResourceString(writer.String(), indentation)
}

func getOperatorDeploymentSpec(data templateData, indentation int) string {
	version := data.DockerTag
	if data.VirtOperatorSha != "" {
		version = data.VirtOperatorSha
	}

	deployment, err := components.NewOperatorDeployment(
		data.Namespace,
		data.DockerPrefix,
		data.ImagePrefix,
		version,
		v1.PullPolicy(data.ImagePullPolicy),
		data.Verbosity,
		data.DockerTag,
		data.VirtApiSha,
		data.VirtControllerSha,
		data.VirtHandlerSha,
		data.VirtLauncherSha,
		data.GsSha)
	if err != nil {
		panic(err)
	}

	writer := strings.Builder{}
	err = util.MarshallObject(deployment.Spec, &writer)
	if err != nil {
		panic(err)
	}
	spec := writer.String()

	return fixResourceString(spec, indentation)
}

func fixResourceString(in string, indention int) string {
	out := strings.Builder{}
	scanner := bufio.NewScanner(strings.NewReader(in))
	for scanner.Scan() {
		line := scanner.Text()
		// remove separator lines
		if !strings.HasPrefix(line, "---") {
			// indent so that it fits into the manifest
			// spaces is is indention - 2, because we want to have 2 spaces less for being able to start an array
			spaces := strings.Repeat(" ", indention-2)
			if strings.HasPrefix(line, "apiGroups") {
				// spaces + array start
				out.WriteString(spaces + "- " + line + "\n")
			} else {
				// 2 more spaces
				out.WriteString(spaces + "  " + line + "\n")
			}
		}
	}
	return out.String()
}

func getKubeVirtLogo(path string) string {
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}

	// Read entire file into byte slice.
	reader := bufio.NewReader(file)
	content, err := io.ReadAll(reader)
	if err != nil {
		panic(err)
	}

	// Encode as base64.
	encoded := base64.StdEncoding.EncodeToString(content)
	return encoded
}

func getTimestamp() string {
	return time.Now().UTC().Format("2006-01-02T15:04:05Z")
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.

// Package lint provides the foundation for tools like gosimple.
package lint // import "honnef.co/go/tools/lint"

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/constant"
	"go/printer"
	"go/token"
	"go/types"
	"io/ioutil"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/loader"
	"honnef.co/go/tools/ssa"
)

type Ignore struct {
	Pattern string
	Checks  []string
}

type Program struct {
	Prog     *loader.Program
	Packages []*Pkg
}

type Func func(*File)

// Problem represents a problem in some source code.
type Problem struct {
	Position token.Position // position in source file
	Text     string         // the prose that describes the problem

	// If the problem has a suggested fix (the minority case),
	// ReplacementLine is a full replacement for the relevant line of the source file.
	ReplacementLine string
}

func (p *Problem) String() string {
	return p.Text
}

type ByPosition []Problem

func (p ByPosition) Len() int      { return len(p) }
func (p ByPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

func (p ByPosition) Less(i, j int) bool {
	pi, pj := p[i].Position, p[j].Position

	if pi.Filename != pj.Filename {
		return pi.Filename < pj.Filename
	}
	if pi.Line != pj.Line {
		return pi.Line < pj.Line
	}
	if pi.Column != pj.Column {
		return pi.Column < pj.Column
	}

	return p[i].Text < p[j].Text
}

type Checker interface {
	Init(*Program)
	Funcs() map[string]Func
}

// A Linter lints Go source code.
type Linter struct {
	Checker Checker
	Ignores []Ignore
}

func buildPackage(pkg *types.Package, files []*ast.File, info *types.Info, fset *token.FileSet, mode ssa.BuilderMode) *ssa.Package {
	prog := ssa.NewProgram(fset, mode)

	// Create SSA packages for all imports.
	// Order is not significant.
	created := make(map[*types.Package]bool)
	var createAll func(pkgs []*types.Package)
	createAll = func(pkgs []*types.Package) {
		for _, p := range pkgs {
			if !created[p] {
				created[p] = true
				prog.CreatePackage(p, nil, nil, true)
				createAll(p.Imports())
			}
		}
	}
	createAll(pkg.Imports())

	// Create and build the primary package.
	ssapkg := prog.CreatePackage(pkg, files, info, false)
	ssapkg.Build()
	return ssapkg
}

func (l *Linter) ignore(f *File, check string) bool {
	for _, ig := range l.Ignores {
		pkg := f.Pkg.TypesPkg.Path()
		if strings.HasSuffix(pkg, "_test") {
			pkg = pkg[:len(pkg)-len("_test")]
		}
		name := filepath.Join(pkg, filepath.Base(f.Filename))
		if m, _ := filepath.Match(ig.Pattern, name); !m {
			continue
		}
		for _, c := range ig.Checks {
			if m, _ := filepath.Match(c, check); m {
				return true
			}
		}
	}
	return false
}

func (l *Linter) Lint(lprog *loader.Program) map[string][]Problem {
	var pkgs []*Pkg
	for _, pkginfo := range lprog.InitialPackages() {
		ssapkg := buildPackage(pkginfo.Pkg, pkginfo.Files, &pkginfo.Info, lprog.Fset, ssa.GlobalDebug)
		pkg := &Pkg{
			TypesPkg:  pkginfo.Pkg,
			TypesInfo: pkginfo.Info,
			SSAPkg:    ssapkg,
			PkgInfo:   pkginfo,
		}
		pkgs = append(pkgs, pkg)
	}
	prog := &Program{
		Prog:     lprog,
		Packages: pkgs,
	}
	l.Checker.Init(prog)

	funcs := l.Checker.Funcs()
	var keys []string
	for k := range funcs {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	out := map[string][]Problem{}
	type result struct {
		path     string
		problems []Problem
	}
	work := make(chan *Pkg, runtime.NumCPU())
	res := make(chan result, runtime.NumCPU())
	worker := func(wg *sync.WaitGroup, work chan *Pkg, res chan result) {
		for pkg := range work {
			for _, file := range pkg.PkgInfo.Files {
				path := lprog.Fset.Position(file.Pos()).Filename
				for _, k := range keys {
					f := &File{
						Pkg:      pkg,
						File:     file,
						Filename: path,
						Fset:     lprog.Fset,
						Program:  lprog,
						check:    k,
					}

					fn := funcs[k]
					if fn == nil {
						continue
					}
					if l.ignore(f, k) {
						continue
					}
					fn(f)
				}
			}
			sort.Sort(ByPosition(pkg.problems))
			res <- result{pkg.PkgInfo.Pkg.Path(), pkg.problems}
		}
		wg.Done()
	}
	wg := &sync.WaitGroup{}
	for i := 0; i < runtime.NumCPU(); i++ {
		wg.Add(1)
		go worker(wg, work, res)
	}
	go func() {
		wg.Wait()
		close(res)
	}()
	go func() {
		for _, pkg := range pkgs {
			work <- pkg
		}
		close(work)
	}()
	for r := range res {
		out[r.path] = r.problems
	}
	return out
}

func (f *File) Source() []byte {
	if f.src != nil {
		return f.src
	}
	path := f.Fset.Position(f.File.Pos()).Filename
	if path != "" {
		f.src, _ = ioutil.ReadFile(path)
	}
	return f.src
}

// pkg represents a package being linted.
type Pkg struct {
	TypesPkg  *types.Package
	TypesInfo types.Info
	SSAPkg    *ssa.Package
	PkgInfo   *loader.PackageInfo

	problems []Problem
}

// file represents a file being linted.
type File struct {
	Pkg      *Pkg
	File     *ast.File
	Filename string
	Fset     *token.FileSet
	Program  *loader.Program
	src      []byte
	check    string
}

func (f *File) IsTest() bool { return strings.HasSuffix(f.Filename, "_test.go") }

type Positioner interface {
	Pos() token.Pos
}

func (f *File) Errorf(n Positioner, format string, args ...interface{}) *Problem {
	pos := f.Fset.Position(n.Pos())
	if !pos.IsValid() {
		pos = f.Fset.Position(f.File.Pos())
	}
	return f.Pkg.errorfAt(pos, f.check, format, args...)
}

func (p *Pkg) errorfAt(pos token.Position, check string, format string, args ...interface{}) *Problem {
	problem := Problem{
		Position: pos,
	}

	problem.Text = fmt.Sprintf(format, args...) + fmt.Sprintf(" (%s)", check)
	p.problems = append(p.problems, problem)
	return &p.problems[len(p.problems)-1]
}

func (p *Pkg) IsNamedType(typ types.Type, importPath, name string) bool {
	n, ok := typ.(*types.Named)
	if !ok {
		return false
	}
	tn := n.Obj()
	return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name
}

func (f *File) IsMain() bool {
	return f.File.Name.Name == "main"
}

// exportedType reports whether typ is an exported type.
// It is imprecise, and will err on the side of returning true,
// such as for composite types.
func ExportedType(typ types.Type) bool {
	switch T := typ.(type) {
	case *types.Named:
		// Builtin types have no package.
		return T.Obj().Pkg() == nil || T.Obj().Exported()
	case *types.Map:
		return ExportedType(T.Key()) && ExportedType(T.Elem())
	case interface {
		Elem() types.Type
	}: // array, slice, pointer, chan
		return ExportedType(T.Elem())
	}
	// Be conservative about other types, such as struct, interface, etc.
	return true
}

func ReceiverType(fn *ast.FuncDecl) string {
	switch e := fn.Recv.List[0].Type.(type) {
	case *ast.Ident:
		return e.Name
	case *ast.StarExpr:
		return e.X.(*ast.Ident).Name
	}
	panic(fmt.Sprintf("unknown method receiver AST node type %T", fn.Recv.List[0].Type))
}

func (f *File) Walk(fn func(ast.Node) bool) {
	ast.Inspect(f.File, fn)
}

func (f *File) Render(x interface{}) string {
	var buf bytes.Buffer
	if err := printer.Fprint(&buf, f.Fset, x); err != nil {
		panic(err)
	}
	return buf.String()
}

func (f *File) RenderArgs(args []ast.Expr) string {
	var ss []string
	for _, arg := range args {
		ss = append(ss, f.Render(arg))
	}
	return strings.Join(ss, ", ")
}

func IsIdent(expr ast.Expr, ident string) bool {
	id, ok := expr.(*ast.Ident)
	return ok && id.Name == ident
}

// isBlank returns whether id is the blank identifier "_".
// If id == nil, the answer is false.
func IsBlank(id ast.Expr) bool {
	ident, ok := id.(*ast.Ident)
	return ok && ident.Name == "_"
}

func IsPkgDot(expr ast.Expr, pkg, name string) bool {
	sel, ok := expr.(*ast.SelectorExpr)
	return ok && IsIdent(sel.X, pkg) && IsIdent(sel.Sel, name)
}

func IsZero(expr ast.Expr) bool {
	lit, ok := expr.(*ast.BasicLit)
	return ok && lit.Kind == token.INT && lit.Value == "0"
}

func IsOne(expr ast.Expr) bool {
	lit, ok := expr.(*ast.BasicLit)
	return ok && lit.Kind == token.INT && lit.Value == "1"
}

func (f *File) IsNil(expr ast.Expr) bool {
	return f.Pkg.TypesInfo.Types[expr].IsNil()
}

var basicTypeKinds = map[types.BasicKind]string{
	types.UntypedBool:    "bool",
	types.UntypedInt:     "int",
	types.UntypedRune:    "rune",
	types.UntypedFloat:   "float64",
	types.UntypedComplex: "complex128",
	types.UntypedString:  "string",
}

// isUntypedConst reports whether expr is an untyped constant,
// and indicates what its default type is.
// scope may be nil.
func (f *File) IsUntypedConst(expr ast.Expr) (defType string, ok bool) {
	// Re-evaluate expr outside of its context to see if it's untyped.
	// (An expr evaluated within, for example, an assignment context will get the type of the LHS.)
	exprStr := f.Render(expr)
	tv, err := types.Eval(f.Fset, f.Pkg.TypesPkg, expr.Pos(), exprStr)
	if err != nil {
		return "", false
	}
	if b, ok := tv.Type.(*types.Basic); ok {
		if dt, ok := basicTypeKinds[b.Kind()]; ok {
			return dt, true
		}
	}

	return "", false
}

func (f *File) BoolConst(expr ast.Expr) bool {
	val := f.Pkg.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
	return constant.BoolVal(val)
}

func (f *File) IsBoolConst(expr ast.Expr) bool {
	// We explicitly don't support typed bools because more often than
	// not, custom bool types are used as binary enums and the
	// explicit comparison is desired.

	ident, ok := expr.(*ast.Ident)
	if !ok {
		return false
	}
	obj := f.Pkg.TypesInfo.ObjectOf(ident)
	c, ok := obj.(*types.Const)
	if !ok {
		return false
	}
	basic, ok := c.Type().(*types.Basic)
	if !ok {
		return false
	}
	if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
		return false
	}
	return true
}

func (f *File) ExprToInt(expr ast.Expr) (int64, bool) {
	tv := f.Pkg.TypesInfo.Types[expr]
	if tv.Value == nil {
		return 0, false
	}
	if tv.Value.Kind() != constant.Int {
		return 0, false
	}
	return constant.Int64Val(tv.Value)
}

func (f *File) EnclosingSSAFunction(node Positioner) *ssa.Function {
	path, _ := astutil.PathEnclosingInterval(f.File, node.Pos(), node.Pos())
	return ssa.EnclosingFunction(f.Pkg.SSAPkg, path)
}

func (f *File) IsGenerated() bool {
	comments := f.File.Comments
	if len(comments) > 0 {
		comment := comments[0].Text()
		return strings.Contains(comment, "Code generated by") ||
			strings.Contains(comment, "DO NOT EDIT")
	}
	return false
}
<commit_msg>lint: delete dead code and unneeded helpers<commit_after>// Copyright (c) 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.

// Package lint provides the foundation for tools like gosimple.
package lint // import "honnef.co/go/tools/lint"

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/constant"
	"go/printer"
	"go/token"
	"go/types"
	"io/ioutil"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/loader"
	"honnef.co/go/tools/ssa"
)

type Ignore struct {
	Pattern string
	Checks  []string
}

type Program struct {
	Prog     *loader.Program
	Packages []*Pkg
}

type Func func(*File)

// Problem represents a problem in some source code.
type Problem struct {
	Position token.Position // position in source file
	Text     string         // the prose that describes the problem

	// If the problem has a suggested fix (the minority case),
	// ReplacementLine is a full replacement for the relevant line of the source file.
	ReplacementLine string
}

func (p *Problem) String() string {
	return p.Text
}

type ByPosition []Problem

func (p ByPosition) Len() int      { return len(p) }
func (p ByPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

func (p ByPosition) Less(i, j int) bool {
	pi, pj := p[i].Position, p[j].Position

	if pi.Filename != pj.Filename {
		return pi.Filename < pj.Filename
	}
	if pi.Line != pj.Line {
		return pi.Line < pj.Line
	}
	if pi.Column != pj.Column {
		return pi.Column < pj.Column
	}

	return p[i].Text < p[j].Text
}

type Checker interface {
	Init(*Program)
	Funcs() map[string]Func
}

// A Linter lints Go source code.
type Linter struct {
	Checker Checker
	Ignores []Ignore
}

func buildPackage(pkg *types.Package, files []*ast.File, info *types.Info, fset *token.FileSet, mode ssa.BuilderMode) *ssa.Package {
	prog := ssa.NewProgram(fset, mode)

	// Create SSA packages for all imports.
	// Order is not significant.
	created := make(map[*types.Package]bool)
	var createAll func(pkgs []*types.Package)
	createAll = func(pkgs []*types.Package) {
		for _, p := range pkgs {
			if !created[p] {
				created[p] = true
				prog.CreatePackage(p, nil, nil, true)
				createAll(p.Imports())
			}
		}
	}
	createAll(pkg.Imports())

	// Create and build the primary package.
	ssapkg := prog.CreatePackage(pkg, files, info, false)
	ssapkg.Build()
	return ssapkg
}

func (l *Linter) ignore(f *File, check string) bool {
	for _, ig := range l.Ignores {
		pkg := f.Pkg.TypesPkg.Path()
		if strings.HasSuffix(pkg, "_test") {
			pkg = pkg[:len(pkg)-len("_test")]
		}
		name := filepath.Join(pkg, filepath.Base(f.Filename))
		if m, _ := filepath.Match(ig.Pattern, name); !m {
			continue
		}
		for _, c := range ig.Checks {
			if m, _ := filepath.Match(c, check); m {
				return true
			}
		}
	}
	return false
}

func (l *Linter) Lint(lprog *loader.Program) map[string][]Problem {
	var pkgs []*Pkg
	for _, pkginfo := range lprog.InitialPackages() {
		ssapkg := buildPackage(pkginfo.Pkg, pkginfo.Files, &pkginfo.Info, lprog.Fset, ssa.GlobalDebug)
		pkg := &Pkg{
			TypesPkg:  pkginfo.Pkg,
			TypesInfo: pkginfo.Info,
			SSAPkg:    ssapkg,
			PkgInfo:   pkginfo,
		}
		pkgs = append(pkgs, pkg)
	}
	prog := &Program{
		Prog:     lprog,
		Packages: pkgs,
	}
	l.Checker.Init(prog)

	funcs := l.Checker.Funcs()
	var keys []string
	for k := range funcs {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	out := map[string][]Problem{}
	type result struct {
		path     string
		problems []Problem
	}
	work := make(chan *Pkg, runtime.NumCPU())
	res := make(chan result, runtime.NumCPU())
	worker := func(wg *sync.WaitGroup, work chan *Pkg, res chan result) {
		for pkg := range work {
			for _, file := range pkg.PkgInfo.Files {
				path := lprog.Fset.Position(file.Pos()).Filename
				for _, k := range keys {
					f := &File{
						Pkg:      pkg,
						File:     file,
						Filename: path,
						Fset:     lprog.Fset,
						Program:  lprog,
						check:    k,
					}

					fn := funcs[k]
					if fn == nil {
						continue
					}
					if l.ignore(f, k) {
						continue
					}
					fn(f)
				}
			}
			sort.Sort(ByPosition(pkg.problems))
			res <- result{pkg.PkgInfo.Pkg.Path(), pkg.problems}
		}
		wg.Done()
	}
	wg := &sync.WaitGroup{}
	for i := 0; i < runtime.NumCPU(); i++ {
		wg.Add(1)
		go worker(wg, work, res)
	}
	go func() {
		wg.Wait()
		close(res)
	}()
	go func() {
		for _, pkg := range pkgs {
			work <- pkg
		}
		close(work)
	}()
	for r := range res {
		out[r.path] = r.problems
	}
	return out
}

func (f *File) Source() []byte {
	if f.src != nil {
		return f.src
	}
	path := f.Fset.Position(f.File.Pos()).Filename
	if path != "" {
		f.src, _ = ioutil.ReadFile(path)
	}
	return f.src
}

// pkg represents a package being linted.
type Pkg struct {
	TypesPkg  *types.Package
	TypesInfo types.Info
	SSAPkg    *ssa.Package
	PkgInfo   *loader.PackageInfo

	problems []Problem
}

// file represents a file being linted.
type File struct {
	Pkg      *Pkg
	File     *ast.File
	Filename string
	Fset     *token.FileSet
	Program  *loader.Program
	src      []byte
	check    string
}

func (f *File) IsTest() bool { return strings.HasSuffix(f.Filename, "_test.go") }

type Positioner interface {
	Pos() token.Pos
}

func (f *File) Errorf(n Positioner, format string, args ...interface{}) *Problem {
	pos := f.Fset.Position(n.Pos())
	if !pos.IsValid() {
		pos = f.Fset.Position(f.File.Pos())
	}
	return f.Pkg.errorfAt(pos, f.check, format, args...)
}

func (p *Pkg) errorfAt(pos token.Position, check string, format string, args ...interface{}) *Problem {
	problem := Problem{
		Position: pos,
	}

	problem.Text = fmt.Sprintf(format, args...) + fmt.Sprintf(" (%s)", check)
	p.problems = append(p.problems, problem)
	return &p.problems[len(p.problems)-1]
}

func (f *File) IsMain() bool {
	return f.File.Name.Name == "main"
}

// exportedType reports whether typ is an exported type.
// It is imprecise, and will err on the side of returning true,
// such as for composite types.
func ExportedType(typ types.Type) bool {
	switch T := typ.(type) {
	case *types.Named:
		// Builtin types have no package.
		return T.Obj().Pkg() == nil || T.Obj().Exported()
	case *types.Map:
		return ExportedType(T.Key()) && ExportedType(T.Elem())
	case interface {
		Elem() types.Type
	}: // array, slice, pointer, chan
		return ExportedType(T.Elem())
	}
	// Be conservative about other types, such as struct, interface, etc.
	return true
}

func (f *File) Walk(fn func(ast.Node) bool) {
	ast.Inspect(f.File, fn)
}

func (f *File) Render(x interface{}) string {
	var buf bytes.Buffer
	if err := printer.Fprint(&buf, f.Fset, x); err != nil {
		panic(err)
	}
	return buf.String()
}

func (f *File) RenderArgs(args []ast.Expr) string {
	var ss []string
	for _, arg := range args {
		ss = append(ss, f.Render(arg))
	}
	return strings.Join(ss, ", ")
}

func IsIdent(expr ast.Expr, ident string) bool {
	id, ok := expr.(*ast.Ident)
	return ok && id.Name == ident
}

// isBlank returns whether id is the blank identifier "_".
// If id == nil, the answer is false.
func IsBlank(id ast.Expr) bool {
	ident, ok := id.(*ast.Ident)
	return ok && ident.Name == "_"
}

func IsPkgDot(expr ast.Expr, pkg, name string) bool {
	sel, ok := expr.(*ast.SelectorExpr)
	return ok && IsIdent(sel.X, pkg) && IsIdent(sel.Sel, name)
}

func IsZero(expr ast.Expr) bool {
	lit, ok := expr.(*ast.BasicLit)
	return ok && lit.Kind == token.INT && lit.Value == "0"
}

func (f *File) IsNil(expr ast.Expr) bool {
	return f.Pkg.TypesInfo.Types[expr].IsNil()
}

func (f *File) BoolConst(expr ast.Expr) bool {
	val := f.Pkg.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
	return constant.BoolVal(val)
}

func (f *File) IsBoolConst(expr ast.Expr) bool {
	// We explicitly don't support typed bools because more often than
	// not, custom bool types are used as binary enums and the
	// explicit comparison is desired.

	ident, ok := expr.(*ast.Ident)
	if !ok {
		return false
	}
	obj := f.Pkg.TypesInfo.ObjectOf(ident)
	c, ok := obj.(*types.Const)
	if !ok {
		return false
	}
	basic, ok := c.Type().(*types.Basic)
	if !ok {
		return false
	}
	if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
		return false
	}
	return true
}

func (f *File) ExprToInt(expr ast.Expr) (int64, bool) {
	tv := f.Pkg.TypesInfo.Types[expr]
	if tv.Value == nil {
		return 0, false
	}
	if tv.Value.Kind() != constant.Int {
		return 0, false
	}
	return constant.Int64Val(tv.Value)
}

func (f *File) EnclosingSSAFunction(node Positioner) *ssa.Function {
	path, _ := astutil.PathEnclosingInterval(f.File, node.Pos(), node.Pos())
	return ssa.EnclosingFunction(f.Pkg.SSAPkg, path)
}

func (f *File) IsGenerated() bool {
	comments := f.File.Comments
	if len(comments) > 0 {
		comment := comments[0].Text()
		return strings.Contains(comment, "Code generated by") ||
			strings.Contains(comment, "DO NOT EDIT")
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	. "github.com/gorilla/feeds"
	"github.com/gorilla/mux"
	"github.com/sourcegraph/sitemap"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const (
	itemsPerPage  = 10
	statusEnabled = 1
	levelRoot     = iota
	levelList
	levelVote
)

type Listboard struct {
	config *Config
	m      *Model
	tp     *TransPool
}

type ValidationErrors []string

func NewListboard() *Listboard {
	return &Listboard{}
}

func (l *Listboard) Run(args []string) {
	var err error

	l.config = NewConfig()
	if err = l.config.Load(args); err != nil {
		panic(err)
	}

	l.m = NewModel(l.config)
	if err = l.m.Init(l.config); err != nil {
		panic(err)
	}

	l.tp = NewTransPool(l.config.Translations)

	r := mux.NewRouter()
	r.HandleFunc("/", http.HandlerFunc(l.indexHandler)).Methods("GET")
	r.HandleFunc("/feed.xml", http.HandlerFunc(l.feedHandler)).Methods("GET")
	r.HandleFunc("/all.xml", http.HandlerFunc(l.feedAlllHandler)).Methods("GET")
	r.HandleFunc("/sitemap.xml", http.HandlerFunc(l.sitemapHandler)).Methods("GET")

	r.HandleFunc("/add.html", http.HandlerFunc(l.addFormHandler)).Methods("GET", "POST")
	r.HandleFunc("/list/{listId}/{slug}", http.HandlerFunc(l.listHandler)).Methods("GET", "POST")
	r.HandleFunc("/vote/{itemId}/{slug}", http.HandlerFunc(l.voteHandler)).Methods("GET", "POST")

	// Static assets
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("./public_html")))

	http.Handle("/", r)

	log.Printf("Starting server at %s", l.config.Server)
	if err := http.ListenAndServe(l.config.Server, nil); err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func (l *Listboard) indexHandler(w http.ResponseWriter, r *http.Request) {
	pageStr := r.URL.Query().Get("hostname")
	page := 0
	var err error
	if len(pageStr) != 0 {
		page, err = strconv.Atoi(pageStr)
		if err != nil {
			log.Printf("%s is not a valid page number", pageStr)
			page = 0
		}
	}
	sc := l.config.getSiteConfig(l.getToken(r))
	s := NewSession(sc, l.tp.Get(sc.Language))
	s.AddPath("", s.Lang("Home"))
	s.Set("Lists", l.m.mustGetChildNodes(sc.DomainId, 0, itemsPerPage, page, "updated"))
	s.render(w, r, "templates/layout.html", "templates/index.html")
}

func (l *Listboard) addFormHandler(w http.ResponseWriter, r *http.Request) {
	sc := l.config.getSiteConfig(l.getToken(r))

	var errors ValidationErrors
	var node Node
	tr := l.tp.Get(sc.Language)
	if r.Method == "POST" {
		if !inHoneypot(r.FormValue("name")) {
			node, errors = l.validateForm(r, sc.DomainId, 0, levelRoot, tr)
			if len(errors) == 0 {
				// save and redirect
				id, err := l.m.addNode(&node)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					panic(err)
				}
				url := "/list/" + strconv.Itoa(id) + "/" + hfSlug(node.Title)
				http.Redirect(w, r, url, http.StatusFound)
			}
		}
	}
	s := NewSession(sc, tr)
	s.Set("Errors", errors)
	s.Set("Form", node)
	s.AddPath("/", s.Lang("Home"))
	s.AddPath("", s.Lang("New list"))
	s.Set("Subtitle", s.Lang("New list"))
	s.render(w, r, "templates/layout.html", "templates/add.html", "templates/form.html")
}

func (l *Listboard) listHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	listId, err := strconv.Atoi(vars["listId"])
	if err != nil {
		log.Printf("%d is not a valid list number", listId)
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}
	sc := l.config.getSiteConfig(l.getToken(r))
	tr := l.tp.Get(sc.Language)

	var errors ValidationErrors
	var node Node

	if r.Method == "POST" {
		if !inHoneypot(r.FormValue("name")) {
			node, errors = l.validateForm(r, sc.DomainId, listId, levelList, tr)
			if len(errors) == 0 {
				// save and redirect
				id, err := l.m.addNode(&node)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					panic(err)
				}
				url := "/list/" + strconv.Itoa(listId) + "/" + hfSlug(node.Title) + "#I" + strconv.Itoa(id)
				http.Redirect(w, r, url, http.StatusFound)
			}
		}
	}
	s := NewSession(sc, tr)

	s.Set("Errors", errors)
	s.Set("Form", node)
	list := l.m.mustGetNode(sc.DomainId, listId)
	s.Set("List", list)
	s.Set("Items", l.m.mustGetChildNodes(sc.DomainId, listId, itemsPerPage, 0, "vote DESC, created"))
	s.Set("FormTitle", s.Lang("New suggestion"))
	s.Set("Subtitle", list.Title)
	s.Set("Description", list.Title)
	s.AddPath("/", s.Lang("Home"))
	s.AddPath("", list.Title)
	s.render(w, r, "templates/layout.html", "templates/list.html", "templates/form.html")
}

func (l *Listboard) voteHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	itemId, err := strconv.Atoi(vars["itemId"])
	if err != nil {
		log.Printf("%d is not a valid item number", itemId)
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}
	sc := l.config.getSiteConfig(l.getToken(r))
	tr := l.tp.Get(sc.Language)
	var errors ValidationErrors
	var node Node
	item := l.m.mustGetNode(sc.DomainId, itemId)

	if r.Method == "POST" {
		if !inHoneypot(r.FormValue("name")) {
			node, errors = l.validateForm(r, sc.DomainId, itemId, levelVote, tr)
			if len(errors) == 0 {
				id, err := l.m.addNode(&node)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					panic(err)
				}
				if err := l.m.Vote(sc.DomainId, node.Vote, id, itemId, item.ParentId); err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					panic(err)
				}
				http.Redirect(w, r, r.URL.String()+"#I"+strconv.Itoa(id), http.StatusFound)
			}
		}
	}
	s := NewSession(sc, tr)
	s.Set("Subtitle", item.Title)
	s.Set("Description", item.Title)
	s.Set("ShowVote", true)
	s.Set("Errors", errors)
	list := l.m.mustGetNode(sc.DomainId, item.ParentId)
	s.Set("List", list)
	s.Set("Item", item)
	if len(node.Title) == 0 {
		node.Title = s.Lang("Re") + ": " + item.Title
	}
	s.Set("Form", node)
	s.Set("Items", l.m.mustGetChildNodes(sc.DomainId, itemId, itemsPerPage, 0, "created"))
	s.Set("FormTitle", s.Lang("New vote"))
	s.AddPath("/", s.Lang("Home"))
	s.AddPath("/list/"+strconv.Itoa(list.Id)+"/"+hfSlug(list.Title), list.Title)
	s.AddPath("", item.Title)
	s.render(w, r, "templates/layout.html", "templates/vote.html", "templates/form.html")
}

func (l *Listboard) feed(w http.ResponseWriter, sc *SiteConfig, baseURL string, nodes *NodeList) {
	feed := &Feed{
		Title:       sc.Title,
		Link:        &Link{Href: baseURL},
		Description: sc.Description,
		Author:      &Author{sc.AuthorName, sc.AuthorEmail},
		Created:     time.Now(),
	}
	for _, node := range *nodes {
		feed.Items = append(feed.Items, &Item{
			Title:       node.Title,
			Link:        &Link{Href: baseURL + "/list/" + strconv.Itoa(node.Id) + "/" + hfSlug(node.Title)},
			Description: string(node.Rendered),
			Created:     node.Created,
		})
	}
	w.Header().Set("Content-Type", "application/rss+xml")
	err := feed.WriteRss(w)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (l *Listboard) feedHandler(w http.ResponseWriter, r *http.Request) {
	sc := l.config.getSiteConfig(l.getToken(r))
	nodes := l.m.mustGetChildNodes(sc.DomainId, 0, 20, 0, "created")
	baseUrl := "http://" + r.Host
	l.feed(w, sc, baseUrl, nodes)
}

func (l *Listboard) feedAlllHandler(w http.ResponseWriter, r *http.Request) {
	sc := l.config.getSiteConfig(l.getToken(r))
	nodes := l.m.mustGetAllNodes(sc.DomainId, 20, 0, "created")
	baseUrl := "http://" + r.Host
	l.feed(w, sc, baseUrl, nodes)
}

func (l *Listboard) sitemapHandler(w http.ResponseWriter, r *http.Request) {
	sc := l.config.getSiteConfig(l.getToken(r))
	nodes := l.m.mustGetChildNodes(sc.DomainId, 0, 1000, 0, "created")
	var urlSet sitemap.URLSet
	for _, node := range *nodes {
		urlSet.URLs = append(urlSet.URLs, sitemap.URL{
			Loc:        "http://" + r.Host + "/list/" + strconv.Itoa(node.Id) + "/" + hfSlug(node.Title),
			LastMod:    &node.Created,
			ChangeFreq: sitemap.Daily,
			Priority:   0.7,
		})
	}
	xml, err := sitemap.Marshal(&urlSet)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/xml")
	w.Write(xml)
}

func (l *Listboard) validateForm(r *http.Request, domainId, parentId, level int, ln *Language) (Node, ValidationErrors) {
	node := Node{
		ParentId: parentId,
		DomainId: domainId,
		Title:    strings.TrimSpace(r.FormValue("title")),
		Vote:     getVote(r.FormValue("vote")),
		Tripcode: getTripcode(r.FormValue("password")),
		Body:     r.FormValue("body"),
		Status:   statusEnabled,
		Level:    level,
	}
	errors := ValidationErrors{}
	if len(node.Title) < 3 {
		errors = append(errors, ln.Lang("Title must be at least 3 characters long"))
	}
	if len(node.Body) < 10 {
		errors = append(errors, ln.Lang("Please, write something"))
	} else {
		node.Rendered = renderText(node.Body)
		// Check again after the rendering
		if len(node.Rendered) < 10 {
			errors = append(errors, ln.Lang("Please, write something"))
		}
	}
	return node, errors
}

func (l *Listboard) getToken(r *http.Request) string {
	return r.Header.Get(l.config.Token)
}
<commit_msg>Changed front page ordering<commit_after>package main

import (
	. "github.com/gorilla/feeds"
	"github.com/gorilla/mux"
	"github.com/sourcegraph/sitemap"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const (
	itemsPerPage  = 10
	statusEnabled = 1
	levelRoot     = iota
	levelList
	levelVote
)

type Listboard struct {
	config *Config
	m      *Model
	tp     *TransPool
}

type ValidationErrors []string

func NewListboard() *Listboard {
	return &Listboard{}
}

func (l *Listboard) Run(args []string) {
	var err error

	l.config = NewConfig()
	if err = l.config.Load(args); err != nil {
		panic(err)
	}

	l.m = NewModel(l.config)
	if err = l.m.Init(l.config); err != nil {
		panic(err)
	}

	l.tp = NewTransPool(l.config.Translations)

	r := mux.NewRouter()
	r.HandleFunc("/", http.HandlerFunc(l.indexHandler)).Methods("GET")
	r.HandleFunc("/feed.xml", http.HandlerFunc(l.feedHandler)).Methods("GET")
	r.HandleFunc("/all.xml", http.HandlerFunc(l.feedAlllHandler)).Methods("GET")
	r.HandleFunc("/sitemap.xml", http.HandlerFunc(l.sitemapHandler)).Methods("GET")

	r.HandleFunc("/add.html", http.HandlerFunc(l.addFormHandler)).Methods("GET", "POST")
	r.HandleFunc("/list/{listId}/{slug}", http.HandlerFunc(l.listHandler)).Methods("GET", "POST")
	r.HandleFunc("/vote/{itemId}/{slug}", http.HandlerFunc(l.voteHandler)).Methods("GET", "POST")

	// Static assets
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("./public_html")))

	http.Handle("/", r)

	log.Printf("Starting server at %s", l.config.Server)
	if err := http.ListenAndServe(l.config.Server, nil); err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func (l *Listboard) indexHandler(w http.ResponseWriter, r *http.Request) {
	pageStr := r.URL.Query().Get("hostname")
	page := 0
	var err error
	if len(pageStr) != 0 {
		page, err = strconv.Atoi(pageStr)
		if err != nil {
			log.Printf("%s is not a valid page number", pageStr)
			page = 0
		}
	}
	sc := l.config.getSiteConfig(l.getToken(r))
	s := NewSession(sc, l.tp.Get(sc.Language))
	s.AddPath("", s.Lang("Home"))
	s.Set("Lists", l.m.mustGetChildNodes(sc.DomainId, 0, itemsPerPage, page, "updated DESC"))
	s.render(w, r, "templates/layout.html", "templates/index.html")
}

func (l *Listboard) addFormHandler(w http.ResponseWriter, r *http.Request) {
	sc := l.config.getSiteConfig(l.getToken(r))

	var errors ValidationErrors
	var node Node
	tr := l.tp.Get(sc.Language)
	if r.Method == "POST" {
		if !inHoneypot(r.FormValue("name")) {
			node, errors = l.validateForm(r, sc.DomainId, 0, levelRoot, tr)
			if len(errors) == 0 {
				// save and redirect
				id, err := l.m.addNode(&node)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					panic(err)
				}
				url := "/list/" + strconv.Itoa(id) + "/" + hfSlug(node.Title)
				http.Redirect(w, r, url, http.StatusFound)
			}
		}
	}
	s := NewSession(sc, tr)
	s.Set("Errors", errors)
	s.Set("Form", node)
	s.AddPath("/", s.Lang("Home"))
	s.AddPath("", s.Lang("New list"))
	s.Set("Subtitle", s.Lang("New list"))
	s.render(w, r, "templates/layout.html", "templates/add.html", "templates/form.html")
}

func (l *Listboard) listHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	listId, err := strconv.Atoi(vars["listId"])
	if err != nil {
		log.Printf("%d is not a valid list number", listId)
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}
	sc := l.config.getSiteConfig(l.getToken(r))
	tr := l.tp.Get(sc.Language)

	var errors ValidationErrors
	var node Node

	if r.Method == "POST" {
		if !inHoneypot(r.FormValue("name")) {
			node, errors = l.validateForm(r, sc.DomainId, listId, levelList, tr)
			if len(errors) == 0 {
				// save and redirect
				id, err := l.m.addNode(&node)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					panic(err)
				}
				url := "/list/" + strconv.Itoa(listId) + "/" + hfSlug(node.Title) + "#I" + strconv.Itoa(id)
				http.Redirect(w, r, url, http.StatusFound)
			}
		}
	}
	s := NewSession(sc, tr)

	s.Set("Errors", errors)
	s.Set("Form", node)
	list := l.m.mustGetNode(sc.DomainId, listId)
	s.Set("List", list)
	s.Set("Items", l.m.mustGetChildNodes(sc.DomainId, listId, itemsPerPage, 0, "vote DESC, created"))
	s.Set("FormTitle", s.Lang("New suggestion"))
	s.Set("Subtitle", list.Title)
	s.Set("Description", list.Title)
	s.AddPath("/", s.Lang("Home"))
	s.AddPath("", list.Title)
	s.render(w, r, "templates/layout.html", "templates/list.html", "templates/form.html")
}

func (l *Listboard) voteHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	itemId, err := strconv.Atoi(vars["itemId"])
	if err != nil {
		log.Printf("%d is not a valid item number", itemId)
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}
	sc := l.config.getSiteConfig(l.getToken(r))
	tr := l.tp.Get(sc.Language)
	var errors ValidationErrors
	var node Node
	item := l.m.mustGetNode(sc.DomainId, itemId)

	if r.Method == "POST" {
		if !inHoneypot(r.FormValue("name")) {
			node, errors = l.validateForm(r, sc.DomainId, itemId, levelVote, tr)
			if len(errors) == 0 {
				id, err := l.m.addNode(&node)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					panic(err)
				}
				if err := l.m.Vote(sc.DomainId, node.Vote, id, itemId, item.ParentId); err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					panic(err)
				}
				http.Redirect(w, r, r.URL.String()+"#I"+strconv.Itoa(id), http.StatusFound)
			}
		}
	}
	s := NewSession(sc, tr)
	s.Set("Subtitle", item.Title)
	s.Set("Description", item.Title)
	s.Set("ShowVote", true)
	s.Set("Errors", errors)
	list := l.m.mustGetNode(sc.DomainId, item.ParentId)
	s.Set("List", list)
	s.Set("Item", item)
	if len(node.Title) == 0 {
		node.Title = s.Lang("Re") + ": " + item.Title
	}
	s.Set("Form", node)
	s.Set("Items", l.m.mustGetChildNodes(sc.DomainId, itemId, itemsPerPage, 0, "created"))
	s.Set("FormTitle", s.Lang("New vote"))
	s.AddPath("/", s.Lang("Home"))
	s.AddPath("/list/"+strconv.Itoa(list.Id)+"/"+hfSlug(list.Title), list.Title)
	s.AddPath("", item.Title)
	s.render(w, r, "templates/layout.html", "templates/vote.html", "templates/form.html")
}

func (l *Listboard) feed(w http.ResponseWriter, sc *SiteConfig, baseURL string, nodes *NodeList) {
	feed := &Feed{
		Title:       sc.Title,
		Link:        &Link{Href: baseURL},
		Description: sc.Description,
		Author:      &Author{sc.AuthorName, sc.AuthorEmail},
		Created:     time.Now(),
	}
	for _, node := range *nodes {
		feed.Items = append(feed.Items, &Item{
			Title:       node.Title,
			Link:        &Link{Href: baseURL + "/list/" + strconv.Itoa(node.Id) + "/" + hfSlug(node.Title)},
			Description: string(node.Rendered),
			Created:     node.Created,
		})
	}
	w.Header().Set("Content-Type", "application/rss+xml")
	err := feed.WriteRss(w)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (l *Listboard) feedHandler(w http.ResponseWriter, r *http.Request) {
	sc := l.config.getSiteConfig(l.getToken(r))
	nodes := l.m.mustGetChildNodes(sc.DomainId, 0, 20, 0, "created")
	baseUrl := "http://" + r.Host
	l.feed(w, sc, baseUrl, nodes)
}

func (l *Listboard) feedAlllHandler(w http.ResponseWriter, r *http.Request) {
	sc := l.config.getSiteConfig(l.getToken(r))
	nodes := l.m.mustGetAllNodes(sc.DomainId, 20, 0, "created")
	baseUrl := "http://" + r.Host
	l.feed(w, sc, baseUrl, nodes)
}

func (l *Listboard) sitemapHandler(w http.ResponseWriter, r *http.Request) {
	sc := l.config.getSiteConfig(l.getToken(r))
	nodes := l.m.mustGetChildNodes(sc.DomainId, 0, 1000, 0, "created")
	var urlSet sitemap.URLSet
	for _, node := range *nodes {
		urlSet.URLs = append(urlSet.URLs, sitemap.URL{
			Loc:        "http://" + r.Host + "/list/" + strconv.Itoa(node.Id) + "/" + hfSlug(node.Title),
			LastMod:    &node.Created,
			ChangeFreq: sitemap.Daily,
			Priority:   0.7,
		})
	}
	xml, err := sitemap.Marshal(&urlSet)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/xml")
	w.Write(xml)
}

func (l *Listboard) validateForm(r *http.Request, domainId, parentId, level int, ln *Language) (Node, ValidationErrors) {
	node := Node{
		ParentId: parentId,
		DomainId: domainId,
		Title:    strings.TrimSpace(r.FormValue("title")),
		Vote:     getVote(r.FormValue("vote")),
		Tripcode: getTripcode(r.FormValue("password")),
		Body:     r.FormValue("body"),
		Status:   statusEnabled,
		Level:    level,
	}
	errors := ValidationErrors{}
	if len(node.Title) < 3 {
		errors = append(errors, ln.Lang("Title must be at least 3 characters long"))
	}
	if len(node.Body) < 10 {
		errors = append(errors, ln.Lang("Please, write something"))
	} else {
		node.Rendered = renderText(node.Body)
		// Check again after the rendering
		if len(node.Rendered) < 10 {
			errors = append(errors, ln.Lang("Please, write something"))
		}
	}
	return node, errors
}

func (l *Listboard) getToken(r *http.Request) string {
	return r.Header.Get(l.config.Token)
}
<|endoftext|>


<commit_before>// Copyright © 2015 Erik Brady <brady@dvln.org>
//
// 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.

// The dvln/api/json.go module is for routines that might be useful
// for manipulating json beyond (or wrapping) the Go standard lib,
// also useful for "combining" JSON fields/data/errors from various
// packages that can then be dumped as a whole from anywhere.

package api

// for imports the goal is to use very little outside the std lib,
// note that str and cast have no dependencies outside the std lib
// (exception: cast testing file which uses 'testify')
import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"os"

	"github.com/dvln/cast"
	"github.com/dvln/str"
)

var jsonIndentLevel = 2
var jsonPrefix = ""
var jsonRaw = false

// JSONIndentLevel can be used to get the current indentation level for each
// "step" in PrettyJSON() output (defaults to 2 currently)
func JSONIndentLevel() int {
	return jsonIndentLevel
}

// SetJSONIndentLevel can be used to change the indentation level for each
// "step" in pretty JSOn output being formatted via PrettyJSON()
func SetJSONIndentLevel(level int) {
	jsonIndentLevel = level
}

// JSONPrefix can be used to get the current prefix used for any JSON string
// being formatted via the PrettyJSON() routine
func JSONPrefix() string {
	return jsonPrefix
}

// SetJSONPrefix can be used to change the string prefix for any JSON string
// being formatted via the PrettyJSON() routine.
func SetJSONPrefix(pfx string) {
	jsonPrefix = pfx
}

// JSONRaw can be used to determine if we're in raw JSON output mode (true)
// or not, true means the PrettyJSON() routine will do nothing
func JSONRaw() bool {
	return jsonRaw
}

// SetJSONRaw can be used to change the indentation level for each
// "step" in pretty JSOn output being formatted via PrettyJSON()
// being formatted via the PrettyJSON() routine.
func SetJSONRaw(b bool) {
	jsonRaw = b
}

// PrettyJSON pretty prints JSON data, provide the data and that can be followed
// by two optional arguments, a prefix string and an indent level (both of which
// are strings).  If neither is provided then no prefix used and indent of two
// spaces is the default (see cfgfile:jsonprefix, cfgfile:jsonindent and the
// related DVLN_JSONPREFIX, DVLN_JSONINDENT to adjust indentation and prefix
// as well as cfgfile:jsonraw and DVLN_JSONRAW for skipping pretty printing)
func PrettyJSON(b []byte, fmt ...string) (string, error) {
	if jsonRaw {
		// if there's an override to say pretty JSON is not desired, honor it,
		// Feature: this could be changed to specifically remove carriage
		//          returns and shorten output around {} and :'s and such (?)
		return cast.ToString(b), nil
	}
	prefix := jsonPrefix
	indent := str.Pad("", " ", jsonIndentLevel)
	if len(fmt) == 1 {
		prefix = fmt[0]
	} else if len(fmt) == 2 {
		prefix = fmt[0]
		indent = fmt[1]
	}
	var out bytes.Buffer
	err := json.Indent(&out, b, prefix, indent)
	return cast.ToString(out.Bytes()) + "\n", err
}

// EscapeJSONString escapes control chars in a string so JSON likes em
func EscapeJSONString(ctrl []byte) (esc []byte) {
	u := []byte(`\u0000`)
	for i, ch := range ctrl {
		if ch <= 31 || ch == 34 {
			if esc == nil {
				esc = append(make([]byte, 0, len(ctrl)+len(u)), ctrl[:i]...)
			}
			esc = append(esc, u...)
			hex.Encode(esc[len(esc)-2:], ctrl[i:i+1])
			continue
		}
		if esc != nil {
			esc = append(esc, ch)
		}
	}
	if esc == nil {
		return ctrl
	}
	return esc
}

// encodeMsginRawJSON takes the flavor of the Msg ("error", "warning" or "note")
// and the message and returns a JSON encoded string with no preceeding or
// following comments
func encodeMsgInRawJSON(flavor string, msg Msg) string {
	if msg.Message == "" {
		return ""
	}
	cleanMsg := EscapeJSONString([]byte(msg.Message))
	rawJSON := fmt.Sprintf("\"%s\": { \"message\": \"%s\", \"code\": %d, \"level\": \"%s\"}", flavor, cleanMsg, msg.Code, msg.Level)
	return rawJSON
}

// FatalJSONMsg is for cases where Marshal is failing so we need
// some JSON we can dump on the output... if we get to this level then
// what we're generating is a valid JSON error basically (shouldn't happen)
func FatalJSONMsg(apiVer string, errMsg Msg) string {
	noteMsgJSON := encodeMsgInRawJSON("note", storedNote)
	warnMsgJSON := encodeMsgInRawJSON("warning", storedNonFatalWarning)
	errMsgJSON := encodeMsgInRawJSON("error", errMsg)
	// we really need an error, try global setting else fallback to unknown
	if errMsgJSON == "" {
		errMsgJSON = encodeMsgInRawJSON("error", storedFatalError)
		if errMsgJSON == "" {
			errMsg = NewMsg("Unknown Fatal Error (Coding Error?)", 0, "UNKNOWN")
			errMsgJSON = encodeMsgInRawJSON("error", errMsg)
		}
	}
	msgsJSON := ""
	if noteMsgJSON != "" {
		msgsJSON = fmt.Sprintf("%s, ", noteMsgJSON)
	}
	if warnMsgJSON != "" {
		msgsJSON = fmt.Sprintf("%s%s, ", msgsJSON, warnMsgJSON)
	}
	msgsJSON = fmt.Sprintf("%s%s", msgsJSON, errMsgJSON)
	cmdError := -1
	rawJSON := fmt.Sprintf("{ \"apiVersion\":\"%s\", \"id\": %d, %s }", apiVer, cmdError, msgsJSON)
	output, err := PrettyJSON([]byte(rawJSON))
	if err != nil {
		output = rawJSON
	}
	return output
}

// GetJSONOutput takes the various things needed from a DVLN api call and
// combines pertinent details into a JSON "results" string (pretty or not
// depending upon settings) and returns that representation to the caller.
// It will return a boolean indicating if a fatal occurred (if so the err
// will be encoded in the JSON being returned already, print the string and
// exit non-zero basically if you get false back in the boolean)
func GetJSONOutput(apiVer string, context string, kind string, verbosity string, fields []string, items []interface{}) (string, bool) {
	var j []byte
	var err error
	var output, rawJSON string
	var errMsg, warnMsg, noteMsg Msg
	fatalErr := false

	if apiVer == "" {
		// In case the API version couldn't be passed, last ditch try
		apiVer = os.Getenv("PKG_API_APIVER")
		if apiVer == "" {
			apiVer = "?.?"
			errMsg.Message = "No valid JSON API version is available"
			errMsg.Code = 1001
			errMsg.Level = "FATAL"
			fatalErr = true
		}
	}
	apiRoot := newAPIData(apiVer, context)
	if errMsg.Message == "" && storedFatalError.Message != "" {
		errMsg = storedFatalError
		cleanErrMsg := EscapeJSONString([]byte(errMsg.Message))
		errMsg.Message = string(cleanErrMsg)
		fatalErr = true
	}
	if storedNonFatalWarning.Message != "" {
		warnMsg = storedNonFatalWarning
		cleanWarnMsg := EscapeJSONString([]byte(warnMsg.Message))
		warnMsg.Message = string(cleanWarnMsg)
	}
	if storedNote.Message != "" {
		noteMsg = storedNote
		cleanNoteMsg := EscapeJSONString([]byte(noteMsg.Message))
		noteMsg.Message = string(cleanNoteMsg)
	}
	if errMsg.Message == "" {
		// if no errors so far then add in our items and 'data' details
		apiRoot.SetAPIItems(kind, verbosity, fields, items)
		if warnMsg.Message != "" {
			//need to escape warning message, no ?
			apiRoot.Warning = warnMsg
		}
		if noteMsg.Message != "" {
			//need to escape warning message, no ?
			apiRoot.Note = noteMsg
		}
	} else {
		// otherwise indicate issue and encode that into JSON
		apiRoot.ID = -1
		apiRoot.Error = errMsg
	}
	j, err = json.Marshal(apiRoot)
	if err != nil {
		if errMsg.Message == "" {
			errMsg.Message = "Unable to marshal basic JSON API string"
			errMsg.Code = 1002
			errMsg.Level = "FATAL"
			fatalErr = true
		}
		// hack: hard code some JSON and return an error... shouldn't happen
		rawJSON = FatalJSONMsg(apiVer, errMsg)
		return rawJSON, fatalErr
	}
	// put in indentation and formatting, can turn that off as well
	// if desired via the "jsonraw" globs (viper) setting
	output, err = PrettyJSON(j)
	if err != nil {
		warnMsg.Message = fmt.Sprintf("Unable to beautify JSON output: %s", err)
		warnMsg.Code = 1003
		warnMsg.Level = "ISSUE"
		apiRoot.Warning = warnMsg
		j, err = json.Marshal(apiRoot)
		// if 1st marshal ok but pretty failed, add warning to JSON and if basic
		// re-Marshal fails for any reason "bump" to a FATAL error, unlikely:
		if err != nil {
			// not a warning any more, scale it up to fatal error
			warnMsg.Level = "FATAL"
			fatalErr = true
			rawJSON = FatalJSONMsg(apiVer, warnMsg)
			return rawJSON, fatalErr
		}
		// retry pretty probably won't work again, if not just use raw json
		output, err = PrettyJSON(j)
		if err != nil {
			output = cast.ToString(j)
		}
	}
	// Return the output (typically), fatalErr is false if we get to here
	return output, fatalErr
}
<commit_msg>Minor typo correction<commit_after>// Copyright © 2015 Erik Brady <brady@dvln.org>
//
// 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.

// The dvln/api/json.go module is for routines that might be useful
// for manipulating json beyond (or wrapping) the Go standard lib,
// also useful for "combining" JSON fields/data/errors from various
// packages that can then be dumped as a whole from anywhere.

package api

// for imports the goal is to use very little outside the std lib,
// note that str and cast have no dependencies outside the std lib
// (exception: cast testing file which uses 'testify')
import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"os"

	"github.com/dvln/cast"
	"github.com/dvln/str"
)

var jsonIndentLevel = 2
var jsonPrefix = ""
var jsonRaw = false

// JSONIndentLevel can be used to get the current indentation level for each
// "step" in PrettyJSON() output (defaults to 2 currently)
func JSONIndentLevel() int {
	return jsonIndentLevel
}

// SetJSONIndentLevel can be used to change the indentation level for each
// "step" in pretty JSOn output being formatted via PrettyJSON()
func SetJSONIndentLevel(level int) {
	jsonIndentLevel = level
}

// JSONPrefix can be used to get the current prefix used for any JSON string
// being formatted via the PrettyJSON() routine
func JSONPrefix() string {
	return jsonPrefix
}

// SetJSONPrefix can be used to change the string prefix for any JSON string
// being formatted via the PrettyJSON() routine.
func SetJSONPrefix(pfx string) {
	jsonPrefix = pfx
}

// JSONRaw can be used to determine if we're in raw JSON output mode (true)
// or not, true means the PrettyJSON() routine will do nothing
func JSONRaw() bool {
	return jsonRaw
}

// SetJSONRaw can be used to change the indentation level for each
// "step" in pretty JSON output being formatted via PrettyJSON()
// being formatted via the PrettyJSON() routine.
func SetJSONRaw(b bool) {
	jsonRaw = b
}

// PrettyJSON pretty prints JSON data, provide the data and that can be followed
// by two optional arguments, a prefix string and an indent level (both of which
// are strings).  If neither is provided then no prefix used and indent of two
// spaces is the default (see cfgfile:jsonprefix, cfgfile:jsonindent and the
// related DVLN_JSONPREFIX, DVLN_JSONINDENT to adjust indentation and prefix
// as well as cfgfile:jsonraw and DVLN_JSONRAW for skipping pretty printing)
func PrettyJSON(b []byte, fmt ...string) (string, error) {
	if jsonRaw {
		// if there's an override to say pretty JSON is not desired, honor it,
		// Feature: this could be changed to specifically remove carriage
		//          returns and shorten output around {} and :'s and such (?)
		return cast.ToString(b), nil
	}
	prefix := jsonPrefix
	indent := str.Pad("", " ", jsonIndentLevel)
	if len(fmt) == 1 {
		prefix = fmt[0]
	} else if len(fmt) == 2 {
		prefix = fmt[0]
		indent = fmt[1]
	}
	var out bytes.Buffer
	err := json.Indent(&out, b, prefix, indent)
	return cast.ToString(out.Bytes()) + "\n", err
}

// EscapeJSONString escapes control chars in a string so JSON likes em
func EscapeJSONString(ctrl []byte) (esc []byte) {
	u := []byte(`\u0000`)
	for i, ch := range ctrl {
		if ch <= 31 || ch == 34 {
			if esc == nil {
				esc = append(make([]byte, 0, len(ctrl)+len(u)), ctrl[:i]...)
			}
			esc = append(esc, u...)
			hex.Encode(esc[len(esc)-2:], ctrl[i:i+1])
			continue
		}
		if esc != nil {
			esc = append(esc, ch)
		}
	}
	if esc == nil {
		return ctrl
	}
	return esc
}

// encodeMsginRawJSON takes the flavor of the Msg ("error", "warning" or "note")
// and the message and returns a JSON encoded string with no preceeding or
// following comments
func encodeMsgInRawJSON(flavor string, msg Msg) string {
	if msg.Message == "" {
		return ""
	}
	cleanMsg := EscapeJSONString([]byte(msg.Message))
	rawJSON := fmt.Sprintf("\"%s\": { \"message\": \"%s\", \"code\": %d, \"level\": \"%s\"}", flavor, cleanMsg, msg.Code, msg.Level)
	return rawJSON
}

// FatalJSONMsg is for cases where Marshal is failing so we need
// some JSON we can dump on the output... if we get to this level then
// what we're generating is a valid JSON error basically (shouldn't happen)
func FatalJSONMsg(apiVer string, errMsg Msg) string {
	noteMsgJSON := encodeMsgInRawJSON("note", storedNote)
	warnMsgJSON := encodeMsgInRawJSON("warning", storedNonFatalWarning)
	errMsgJSON := encodeMsgInRawJSON("error", errMsg)
	// we really need an error, try global setting else fallback to unknown
	if errMsgJSON == "" {
		errMsgJSON = encodeMsgInRawJSON("error", storedFatalError)
		if errMsgJSON == "" {
			errMsg = NewMsg("Unknown Fatal Error (Coding Error?)", 0, "UNKNOWN")
			errMsgJSON = encodeMsgInRawJSON("error", errMsg)
		}
	}
	msgsJSON := ""
	if noteMsgJSON != "" {
		msgsJSON = fmt.Sprintf("%s, ", noteMsgJSON)
	}
	if warnMsgJSON != "" {
		msgsJSON = fmt.Sprintf("%s%s, ", msgsJSON, warnMsgJSON)
	}
	msgsJSON = fmt.Sprintf("%s%s", msgsJSON, errMsgJSON)
	cmdError := -1
	rawJSON := fmt.Sprintf("{ \"apiVersion\":\"%s\", \"id\": %d, %s }", apiVer, cmdError, msgsJSON)
	output, err := PrettyJSON([]byte(rawJSON))
	if err != nil {
		output = rawJSON
	}
	return output
}

// GetJSONOutput takes the various things needed from a DVLN api call and
// combines pertinent details into a JSON "results" string (pretty or not
// depending upon settings) and returns that representation to the caller.
// It will return a boolean indicating if a fatal occurred (if so the err
// will be encoded in the JSON being returned already, print the string and
// exit non-zero basically if you get false back in the boolean)
func GetJSONOutput(apiVer string, context string, kind string, verbosity string, fields []string, items []interface{}) (string, bool) {
	var j []byte
	var err error
	var output, rawJSON string
	var errMsg, warnMsg, noteMsg Msg
	fatalErr := false

	if apiVer == "" {
		// In case the API version couldn't be passed, last ditch try
		apiVer = os.Getenv("PKG_API_APIVER")
		if apiVer == "" {
			apiVer = "?.?"
			errMsg.Message = "No valid JSON API version is available"
			errMsg.Code = 1001
			errMsg.Level = "FATAL"
			fatalErr = true
		}
	}
	apiRoot := newAPIData(apiVer, context)
	if errMsg.Message == "" && storedFatalError.Message != "" {
		errMsg = storedFatalError
		cleanErrMsg := EscapeJSONString([]byte(errMsg.Message))
		errMsg.Message = string(cleanErrMsg)
		fatalErr = true
	}
	if storedNonFatalWarning.Message != "" {
		warnMsg = storedNonFatalWarning
		cleanWarnMsg := EscapeJSONString([]byte(warnMsg.Message))
		warnMsg.Message = string(cleanWarnMsg)
	}
	if storedNote.Message != "" {
		noteMsg = storedNote
		cleanNoteMsg := EscapeJSONString([]byte(noteMsg.Message))
		noteMsg.Message = string(cleanNoteMsg)
	}
	if errMsg.Message == "" {
		// if no errors so far then add in our items and 'data' details
		apiRoot.SetAPIItems(kind, verbosity, fields, items)
		if warnMsg.Message != "" {
			//need to escape warning message, no ?
			apiRoot.Warning = warnMsg
		}
		if noteMsg.Message != "" {
			//need to escape warning message, no ?
			apiRoot.Note = noteMsg
		}
	} else {
		// otherwise indicate issue and encode that into JSON
		apiRoot.ID = -1
		apiRoot.Error = errMsg
	}
	j, err = json.Marshal(apiRoot)
	if err != nil {
		if errMsg.Message == "" {
			errMsg.Message = "Unable to marshal basic JSON API string"
			errMsg.Code = 1002
			errMsg.Level = "FATAL"
			fatalErr = true
		}
		// hack: hard code some JSON and return an error... shouldn't happen
		rawJSON = FatalJSONMsg(apiVer, errMsg)
		return rawJSON, fatalErr
	}
	// put in indentation and formatting, can turn that off as well
	// if desired via the "jsonraw" globs (viper) setting
	output, err = PrettyJSON(j)
	if err != nil {
		warnMsg.Message = fmt.Sprintf("Unable to beautify JSON output: %s", err)
		warnMsg.Code = 1003
		warnMsg.Level = "ISSUE"
		apiRoot.Warning = warnMsg
		j, err = json.Marshal(apiRoot)
		// if 1st marshal ok but pretty failed, add warning to JSON and if basic
		// re-Marshal fails for any reason "bump" to a FATAL error, unlikely:
		if err != nil {
			// not a warning any more, scale it up to fatal error
			warnMsg.Level = "FATAL"
			fatalErr = true
			rawJSON = FatalJSONMsg(apiVer, warnMsg)
			return rawJSON, fatalErr
		}
		// retry pretty probably won't work again, if not just use raw json
		output, err = PrettyJSON(j)
		if err != nil {
			output = cast.ToString(j)
		}
	}
	// Return the output (typically), fatalErr is false if we get to here
	return output, fatalErr
}
<|endoftext|>


<commit_before>package mongoes

import (
	"strings"
)

// GetDeepObject return object within JSON object
func GetDeepObject(obj map[string]interface{}, path string) map[string]interface{} {
	ret := obj
	splitted := strings.Split(path, ".")
	for _, v := range splitted {
		// fmt.Println(v)
		ret = ret[v].(map[string]interface{})
	}
	return ret
}
<commit_msg>add readjsonfromfile func<commit_after>package mongoes

import (
	"encoding/json"
	"io/ioutil"
	"strings"
)

func ReadJSONFromFile(path string, v *map[string]interface{}) error {
	fileBytes, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	err = json.Unmarshal(fileBytes, v)
	if err != nil {
		return err
	}
	return nil
}

// GetDeepObject return object within JSON object
func GetDeepObject(obj map[string]interface{}, path string) map[string]interface{} {
	ret := obj
	splitted := strings.Split(path, ".")
	for _, v := range splitted {
		// fmt.Println(v)
		ret = ret[v].(map[string]interface{})
	}
	return ret
}
<|endoftext|>


<commit_before>package kana

import (
	"io/ioutil"
	"strings"
)

type Kana struct {
	kanaToRomajiTrie     *Trie
	romajiToHiraganaTrie *Trie
	romajiToKatakanaTrie *Trie
}

func newKana() *Kana {
	/*
	   Build a trie for efficient retrieval of entries
	*/
	kana := &Kana{newTrie(), newTrie(), newTrie()}
	kana.initialize()
	return kana
}

func (k *Kana) initialize() {
	/*
		Build the Hiragana + Katakana trie.

		Because there is no overlap between the hiragana and katakana sets,
		they both use the same trie without conflict. Nice bonus!
	*/
	filenames := []string{"hiragana.in", "katakana.in"}
	for _, filename := range filenames {
		content, err := ioutil.ReadFile(filename)
		if err != nil {
			//Do something
		}
		rows := strings.Split(string(content), "\n")
		colNames := strings.Split(string(rows[0]), "\t")[1:]
		for _, row := range rows[1:] {
			cols := strings.Split(string(row), "\t")
			rowName := cols[0]
			for i, kana := range cols[1:] {
				value := rowName + colNames[i]
				kanas := strings.Split(kana, "/")
				for _, singleKana := range kanas {
					if singleKana != "" {
						// add to tries
						k.kanaToRomajiTrie.insert(singleKana, value)
						if filename == "hiragana.in" {
							k.romajiToHiraganaTrie.insert(value, singleKana)
						} else if filename == "katakana.in" {
							k.romajiToKatakanaTrie.insert(value, singleKana)
						}
					}
				}
			}
		}
	}
}

func (k Kana) kana_to_romaji(kana string) (romaji string) {
	romaji = k.kanaToRomajiTrie.convert(kana)

	// do some post-processing for the tsu and stripe characters
	// maybe a bit of a hacky solution - how can we improve?
	// (they act more like punctuation)
	tsus := []string{"っ", "ッ"}
	for _, tsu := range tsus {
		for i := strings.Index(romaji, tsu); i > -1; i = strings.Index(romaji, tsu) {
			rune_romaji := []rune(romaji)
			if len(rune_romaji) > i+2 {
				// TODO: should check if following letter is consonant
				followingLetter := string(rune_romaji[i+1 : i+2])
				romaji = strings.Replace(romaji, tsu, followingLetter, 1)
			} else {
				romaji = strings.Replace(romaji, tsu, "", 1)
			}
		}
	}

	line := "ー"
	for i := strings.Index(romaji, line); i > -1; i = strings.Index(romaji, line) {
		rune_romaji := []rune(romaji)
		if i > 0 {
			// TODO: should check if following letter is consonant
			previousLetter := string(rune_romaji[i-1 : i])
			romaji = strings.Replace(romaji, line, previousLetter, 1)
		} else {
			romaji = strings.Replace(romaji, line, "", 1)
		}
	}

	return romaji
}

func (k Kana) romaji_to_hiragana(romaji string) (hiragana string) {
	hiragana = k.romajiToHiraganaTrie.convert(romaji)
	return hiragana
}

func (k Kana) romaji_to_katakana(romaji string) (katakana string) {
	katakana = k.romajiToKatakanaTrie.convert(romaji)
	return katakana
}
<commit_msg>change to capital NewKana function<commit_after>package kana

import (
	"io/ioutil"
	"strings"
)

type Kana struct {
	kanaToRomajiTrie     *Trie
	romajiToHiraganaTrie *Trie
	romajiToKatakanaTrie *Trie
}

func NewKana() *Kana {
	/*
	   Build a trie for efficient retrieval of entries
	*/
	kana := &Kana{newTrie(), newTrie(), newTrie()}
	kana.initialize()
	return kana
}

func (k *Kana) initialize() {
	/*
		Build the Hiragana + Katakana trie.

		Because there is no overlap between the hiragana and katakana sets,
		they both use the same trie without conflict. Nice bonus!
	*/
	filenames := []string{"hiragana.in", "katakana.in"}
	for _, filename := range filenames {
		content, err := ioutil.ReadFile(filename)
		if err != nil {
			//Do something
		}
		rows := strings.Split(string(content), "\n")
		colNames := strings.Split(string(rows[0]), "\t")[1:]
		for _, row := range rows[1:] {
			cols := strings.Split(string(row), "\t")
			rowName := cols[0]
			for i, kana := range cols[1:] {
				value := rowName + colNames[i]
				kanas := strings.Split(kana, "/")
				for _, singleKana := range kanas {
					if singleKana != "" {
						// add to tries
						k.kanaToRomajiTrie.insert(singleKana, value)
						if filename == "hiragana.in" {
							k.romajiToHiraganaTrie.insert(value, singleKana)
						} else if filename == "katakana.in" {
							k.romajiToKatakanaTrie.insert(value, singleKana)
						}
					}
				}
			}
		}
	}
}

func (k Kana) kana_to_romaji(kana string) (romaji string) {
	romaji = k.kanaToRomajiTrie.convert(kana)

	// do some post-processing for the tsu and stripe characters
	// maybe a bit of a hacky solution - how can we improve?
	// (they act more like punctuation)
	tsus := []string{"っ", "ッ"}
	for _, tsu := range tsus {
		for i := strings.Index(romaji, tsu); i > -1; i = strings.Index(romaji, tsu) {
			rune_romaji := []rune(romaji)
			if len(rune_romaji) > i+2 {
				// TODO: should check if following letter is consonant
				followingLetter := string(rune_romaji[i+1 : i+2])
				romaji = strings.Replace(romaji, tsu, followingLetter, 1)
			} else {
				romaji = strings.Replace(romaji, tsu, "", 1)
			}
		}
	}

	line := "ー"
	for i := strings.Index(romaji, line); i > -1; i = strings.Index(romaji, line) {
		rune_romaji := []rune(romaji)
		if i > 0 {
			// TODO: should check if following letter is consonant
			previousLetter := string(rune_romaji[i-1 : i])
			romaji = strings.Replace(romaji, line, previousLetter, 1)
		} else {
			romaji = strings.Replace(romaji, line, "", 1)
		}
	}

	return romaji
}

func (k Kana) romaji_to_hiragana(romaji string) (hiragana string) {
	hiragana = k.romajiToHiraganaTrie.convert(romaji)
	return hiragana
}

func (k Kana) romaji_to_katakana(romaji string) (katakana string) {
	katakana = k.romajiToKatakanaTrie.convert(romaji)
	return katakana
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"syscall"
	"unsafe"
)

/*** defines ***/

const KILO_VERSION = "0.0.1"
const (
	ARROW_LEFT  = 1000 + iota
	ARROW_RIGHT = 1000 + iota
	ARROW_UP    = 1000 + iota
	ARROW_DOWN  = 1000 + iota
	PAGE_UP     = 1000 + iota
	PAGE_DOWN   = 1000 + iota
)

/*** data ***/

type Termios struct {
	Iflag  uint32
	Oflag  uint32
	Cflag  uint32
	Lflag  uint32
	Cc     [20]byte
	Ispeed uint32
	Ospeed uint32
}

type editorConfig struct {
	cx          int
	cy          int
	screenRows  int
	screenCols  int
	origTermios *Termios
}

type WinSize struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}

var E editorConfig

/*** terminal ***/

func die(err error) {
	disableRawMode()
	io.WriteString(os.Stdout, "\x1b[2J")
	io.WriteString(os.Stdout, "\x1b[H")
	log.Fatal(err)
}

func TcSetAttr(fd uintptr, termios *Termios) error {
	// TCSETS+1 == TCSETSW, because TCSAFLUSH doesn't exist
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TCSETS+1), uintptr(unsafe.Pointer(termios))); err != 0 {
		return err
	}
	return nil
}

func TcGetAttr(fd uintptr) *Termios {
	var termios = &Termios{}
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
		log.Fatalf("Problem getting terminal attributes: %s\n", err)
	}
	return termios
}

func enableRawMode() {
	E.origTermios = TcGetAttr(os.Stdin.Fd())
	var raw Termios
	raw = *E.origTermios
	raw.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	raw.Oflag &^= syscall.OPOST
	raw.Cflag |= syscall.CS8
	raw.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
	raw.Cc[syscall.VMIN+1] = 0
	raw.Cc[syscall.VTIME+1] = 1
	if e := TcSetAttr(os.Stdin.Fd(), &raw); e != nil {
		log.Fatalf("Problem enabling raw mode: %s\n", e)
	}
}

func disableRawMode() {
	if e := TcSetAttr(os.Stdin.Fd(), E.origTermios); e != nil {
		log.Fatalf("Problem disabling raw mode: %s\n", e)
	}
}

func editorReadKey() int {
	var buffer [1]byte
	var cc int
	var err error
	for cc, err = os.Stdin.Read(buffer[:]); cc != 1; cc, err = os.Stdin.Read(buffer[:]) {
	}
	if err != nil {
		die(err)
	}
	if buffer[0] == '\x1b' {
		var seq [2]byte
		if cc, _ = os.Stdin.Read(seq[:]); cc != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				if cc, err = os.Stdin.Read(buffer[:]); cc != 1 {
					return '\x1b'
				}
				if buffer[0] == '~' {
					switch seq[1] {
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					}
				}
				// XXX - what happens here?
			} else {
				switch seq[1] {
				case 'A':
					return ARROW_UP
				case 'B':
					return ARROW_DOWN
				case 'C':
					return ARROW_RIGHT
				case 'D':
					return ARROW_LEFT
				}
			}
		}

		return '\x1b'
	}
	return int(buffer[0])
}

func getCursorPosition(rows *int, cols *int) int {
	io.WriteString(os.Stdout, "\x1b[6n")
	var buffer [1]byte
	var buf []byte
	var cc int
	for cc, _ = os.Stdin.Read(buffer[:]); cc == 1; cc, _ = os.Stdin.Read(buffer[:]) {
		if buffer[0] == 'R' {
			break
		}
		buf = append(buf, buffer[0])
	}
	if string(buf[0:2]) != "\x1b[" {
		log.Printf("Failed to read rows;cols from tty\n")
		return -1
	}
	if n, e := fmt.Sscanf(string(buf[2:]), "%d;%d", rows, cols); n != 2 || e != nil {
		if e != nil {
			log.Printf("getCursorPosition: fmt.Sscanf() failed: %s\n", e)
		}
		if n != 2 {
			log.Printf("getCursorPosition: got %d items, wanted 2\n", n)
		}
		return -1
	}
	return 0
}

func getWindowSize(rows *int, cols *int) int {
	var w WinSize
	_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
		os.Stdout.Fd(),
		syscall.TIOCGWINSZ,
		uintptr(unsafe.Pointer(&w)),
	)
	if err != 0 { // type syscall.Errno
		io.WriteString(os.Stdout, "\x1b[999C\x1b[999B")
		return getCursorPosition(rows, cols)
	} else {
		*rows = int(w.Row)
		*cols = int(w.Col)
		return 0
	}
	return -1
}

/*** input ***/

func editorMoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if E.cx != 0 {
			E.cx--
		}
	case ARROW_RIGHT:
		if E.cx != E.screenCols-1 {
			E.cx++
		}
	case ARROW_UP:
		if E.cy != 0 {
			E.cy--
		}
	case ARROW_DOWN:
		if E.cy != E.screenRows-1 {
			E.cy++
		}
	}
}

func editorProcessKeypress() {
	c := editorReadKey()
	switch c {
	case ('q' & 0x1f):
		io.WriteString(os.Stdout, "\x1b[2J")
		io.WriteString(os.Stdout, "\x1b[H")
		disableRawMode()
		os.Exit(0)
	case PAGE_UP, PAGE_DOWN:
		dir := ARROW_DOWN
		if c == PAGE_UP {
			dir = ARROW_UP
		}
		for times := E.screenRows; times > 0; times-- {
			editorMoveCursor(dir)
		}
	case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
		editorMoveCursor(c)
	}
}

/*** append buffer ***/

type abuf struct {
	buf []byte
}

func (p abuf) String() string {
	return string(p.buf)
}

func (p *abuf) abAppend(s string) {
	p.buf = append(p.buf, []byte(s)...)
}

/*** output ***/

func editorRefreshScreen() {
	var ab abuf
	ab.abAppend("\x1b[25l")
	ab.abAppend("\x1b[H")
	editorDrawRows(&ab)
	ab.abAppend(fmt.Sprintf("\x1b[%d;%dH", E.cy+1, E.cx+1))
	ab.abAppend("\x1b[25h")
	io.WriteString(os.Stdout, ab.String())
}

func editorDrawRows(ab *abuf) {
	for y := 0; y < E.screenRows-1; y++ {
		if y == E.screenRows/3 {
			w := fmt.Sprintf("Kilo editor -- version %s", KILO_VERSION)
			if len(w) > E.screenCols {
				w = w[0:E.screenCols]
			}
			pad := "~ "
			for padding := (E.screenCols - len(w)) / 2; padding > 0; padding-- {
				ab.abAppend(pad)
				pad = " "
			}
			ab.abAppend(w)
		} else {
			ab.abAppend("~")
		}
		ab.abAppend("\x1b[K")
		if y < E.screenRows-1 {
			ab.abAppend("\r\n")
		}
	}
}

/*** init ***/

func initEditor() {
	if getWindowSize(&E.screenRows, &E.screenCols) == -1 {
		die(fmt.Errorf("couldn't get screen size"))
	}
}

func main() {
	enableRawMode()
	defer disableRawMode()
	initEditor()

	for {
		editorRefreshScreen()
		editorProcessKeypress()
	}
}
<commit_msg>Step 52 - Home and End keys input and processing<commit_after>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"syscall"
	"unsafe"
)

/*** defines ***/

const KILO_VERSION = "0.0.1"
const (
	ARROW_LEFT  = 1000 + iota
	ARROW_RIGHT = 1000 + iota
	ARROW_UP    = 1000 + iota
	ARROW_DOWN  = 1000 + iota
	HOME_KEY    = 1000 + iota
	END_KEY     = 1000 + iota
	PAGE_UP     = 1000 + iota
	PAGE_DOWN   = 1000 + iota
)

/*** data ***/

type Termios struct {
	Iflag  uint32
	Oflag  uint32
	Cflag  uint32
	Lflag  uint32
	Cc     [20]byte
	Ispeed uint32
	Ospeed uint32
}

type editorConfig struct {
	cx          int
	cy          int
	screenRows  int
	screenCols  int
	origTermios *Termios
}

type WinSize struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}

var E editorConfig

/*** terminal ***/

func die(err error) {
	disableRawMode()
	io.WriteString(os.Stdout, "\x1b[2J")
	io.WriteString(os.Stdout, "\x1b[H")
	log.Fatal(err)
}

func TcSetAttr(fd uintptr, termios *Termios) error {
	// TCSETS+1 == TCSETSW, because TCSAFLUSH doesn't exist
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TCSETS+1), uintptr(unsafe.Pointer(termios))); err != 0 {
		return err
	}
	return nil
}

func TcGetAttr(fd uintptr) *Termios {
	var termios = &Termios{}
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
		log.Fatalf("Problem getting terminal attributes: %s\n", err)
	}
	return termios
}

func enableRawMode() {
	E.origTermios = TcGetAttr(os.Stdin.Fd())
	var raw Termios
	raw = *E.origTermios
	raw.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	raw.Oflag &^= syscall.OPOST
	raw.Cflag |= syscall.CS8
	raw.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
	raw.Cc[syscall.VMIN+1] = 0
	raw.Cc[syscall.VTIME+1] = 1
	if e := TcSetAttr(os.Stdin.Fd(), &raw); e != nil {
		log.Fatalf("Problem enabling raw mode: %s\n", e)
	}
}

func disableRawMode() {
	if e := TcSetAttr(os.Stdin.Fd(), E.origTermios); e != nil {
		log.Fatalf("Problem disabling raw mode: %s\n", e)
	}
}

func editorReadKey() int {
	var buffer [1]byte
	var cc int
	var err error
	for cc, err = os.Stdin.Read(buffer[:]); cc != 1; cc, err = os.Stdin.Read(buffer[:]) {
	}
	if err != nil {
		die(err)
	}
	if buffer[0] == '\x1b' {
		var seq [2]byte
		if cc, _ = os.Stdin.Read(seq[:]); cc != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				if cc, err = os.Stdin.Read(buffer[:]); cc != 1 {
					return '\x1b'
				}
				if buffer[0] == '~' {
					switch seq[1] {
					case '1':
						return HOME_KEY
					case '4':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					case '7':
						return HOME_KEY
					case '8':
						return END_KEY
					}
				}
				// XXX - what happens here?
			} else {
				switch seq[1] {
				case 'A':
					return ARROW_UP
				case 'B':
					return ARROW_DOWN
				case 'C':
					return ARROW_RIGHT
				case 'D':
					return ARROW_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == '0' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}
	return int(buffer[0])
}

func getCursorPosition(rows *int, cols *int) int {
	io.WriteString(os.Stdout, "\x1b[6n")
	var buffer [1]byte
	var buf []byte
	var cc int
	for cc, _ = os.Stdin.Read(buffer[:]); cc == 1; cc, _ = os.Stdin.Read(buffer[:]) {
		if buffer[0] == 'R' {
			break
		}
		buf = append(buf, buffer[0])
	}
	if string(buf[0:2]) != "\x1b[" {
		log.Printf("Failed to read rows;cols from tty\n")
		return -1
	}
	if n, e := fmt.Sscanf(string(buf[2:]), "%d;%d", rows, cols); n != 2 || e != nil {
		if e != nil {
			log.Printf("getCursorPosition: fmt.Sscanf() failed: %s\n", e)
		}
		if n != 2 {
			log.Printf("getCursorPosition: got %d items, wanted 2\n", n)
		}
		return -1
	}
	return 0
}

func getWindowSize(rows *int, cols *int) int {
	var w WinSize
	_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
		os.Stdout.Fd(),
		syscall.TIOCGWINSZ,
		uintptr(unsafe.Pointer(&w)),
	)
	if err != 0 { // type syscall.Errno
		io.WriteString(os.Stdout, "\x1b[999C\x1b[999B")
		return getCursorPosition(rows, cols)
	} else {
		*rows = int(w.Row)
		*cols = int(w.Col)
		return 0
	}
	return -1
}

/*** input ***/

func editorMoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if E.cx != 0 {
			E.cx--
		}
	case ARROW_RIGHT:
		if E.cx != E.screenCols-1 {
			E.cx++
		}
	case ARROW_UP:
		if E.cy != 0 {
			E.cy--
		}
	case ARROW_DOWN:
		if E.cy != E.screenRows-1 {
			E.cy++
		}
	}
}

func editorProcessKeypress() {
	c := editorReadKey()
	switch c {
	case ('q' & 0x1f):
		io.WriteString(os.Stdout, "\x1b[2J")
		io.WriteString(os.Stdout, "\x1b[H")
		disableRawMode()
		os.Exit(0)
	case PAGE_UP, PAGE_DOWN:
		dir := ARROW_DOWN
		if c == PAGE_UP {
			dir = ARROW_UP
		}
		for times := E.screenRows; times > 0; times-- {
			editorMoveCursor(dir)
		}
	case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
		editorMoveCursor(c)
	}
}

/*** append buffer ***/

type abuf struct {
	buf []byte
}

func (p abuf) String() string {
	return string(p.buf)
}

func (p *abuf) abAppend(s string) {
	p.buf = append(p.buf, []byte(s)...)
}

/*** output ***/

func editorRefreshScreen() {
	var ab abuf
	ab.abAppend("\x1b[25l")
	ab.abAppend("\x1b[H")
	editorDrawRows(&ab)
	ab.abAppend(fmt.Sprintf("\x1b[%d;%dH", E.cy+1, E.cx+1))
	ab.abAppend("\x1b[25h")
	io.WriteString(os.Stdout, ab.String())
}

func editorDrawRows(ab *abuf) {
	for y := 0; y < E.screenRows-1; y++ {
		if y == E.screenRows/3 {
			w := fmt.Sprintf("Kilo editor -- version %s", KILO_VERSION)
			if len(w) > E.screenCols {
				w = w[0:E.screenCols]
			}
			pad := "~ "
			for padding := (E.screenCols - len(w)) / 2; padding > 0; padding-- {
				ab.abAppend(pad)
				pad = " "
			}
			ab.abAppend(w)
		} else {
			ab.abAppend("~")
		}
		ab.abAppend("\x1b[K")
		if y < E.screenRows-1 {
			ab.abAppend("\r\n")
		}
	}
}

/*** init ***/

func initEditor() {
	if getWindowSize(&E.screenRows, &E.screenCols) == -1 {
		die(fmt.Errorf("couldn't get screen size"))
	}
}

func main() {
	enableRawMode()
	defer disableRawMode()
	initEditor()

	for {
		editorRefreshScreen()
		editorProcessKeypress()
	}
}
<|endoftext|>