<commit_before>package scan

import (
	"bytes"
	"fmt"
	"net/http"
	"strings"
	"text/template"
	"time"

	"github.com/Sirupsen/logrus"
	restful "github.com/emicklei/go-restful"
	"github.com/facebookgo/stackerr"

	"github.com/bearded-web/bearded/models/report"
	"github.com/bearded-web/bearded/models/scan"
	"github.com/bearded-web/bearded/pkg/filters"
	"github.com/bearded-web/bearded/pkg/fltr"
	"github.com/bearded-web/bearded/pkg/manager"
	"github.com/bearded-web/bearded/pkg/pagination"
	"github.com/bearded-web/bearded/services"
)

const (
	ParamId        = "scan-id"
	SessionParamId = "session-id"
)

type ScanService struct {
	*services.BaseService
}

func New(base *services.BaseService) *ScanService {
	return &ScanService{
		BaseService: base,
	}
}

func addDefaults(r *restful.RouteBuilder) {
	r.Notes("Authorization required")
	r.Do(services.ReturnsE(
		http.StatusUnauthorized,
		http.StatusInternalServerError,
	))
}

// Fix for IntelijIdea inpsections. Cause it can't investigate anonymous method results =(
func (s *ScanService) Manager() *manager.Manager {
	return s.BaseService.Manager()
}

func (s *ScanService) Register(container *restful.Container) {
	ws := &restful.WebService{}
	ws.Path("/api/v1/scans")
	ws.Doc("Manage Scans")
	ws.Consumes(restful.MIME_JSON)
	ws.Produces(restful.MIME_JSON)
	ws.Filter(filters.AuthTokenFilter(s.BaseManager()))
	ws.Filter(filters.AuthRequiredFilter(s.BaseManager()))

	r := ws.GET("").To(s.list)
	r.Doc("list")
	r.Operation("list")
	s.SetParams(r, fltr.GetParams(ws, manager.ScanFltr{}))
	addDefaults(r)
	r.Writes(scan.ScanList{})
	r.Do(services.Returns(http.StatusOK))
	ws.Route(r)

	r = ws.POST("").To(s.create)
	r.Doc("create")
	r.Operation("create")
	addDefaults(r)
	r.Writes(scan.Scan{})
	r.Reads(scan.Scan{})
	r.Do(services.Returns(http.StatusCreated))
	r.Do(services.ReturnsE(
		http.StatusBadRequest,
		http.StatusConflict,
	))
	ws.Route(r)

	r = ws.GET(fmt.Sprintf("{%s}", ParamId)).To(s.TakeScan(s.get))
	r.Doc("get")
	r.Operation("get")
	addDefaults(r)
	r.Param(ws.PathParameter(ParamId, ""))
	r.Writes(scan.Scan{})
	r.Do(services.Returns(
		http.StatusOK,
		http.StatusNotFound))
	r.Do(services.ReturnsE(http.StatusBadRequest))
	ws.Route(r)

	//	r = ws.PUT(fmt.Sprintf("{%s}", ParamId)).To(s.TakeScan(s.update))
	//	r.Doc("update")
	//	r.Operation("update")
	//	r.Param(ws.PathParameter(ParamId, ""))
	//	r.Writes(scan.Scan{})
	//	r.Reads(scan.Scan{})
	//	r.Do(services.Returns(
	//		http.StatusOK,
	//		http.StatusNotFound))
	//	r.Do(services.ReturnsE(http.StatusBadRequest))
	//	ws.Route(r)

	r = ws.DELETE(fmt.Sprintf("{%s}", ParamId)).To(s.TakeScan(s.delete))
	r.Doc("delete")
	r.Operation("delete")
	addDefaults(r)
	r.Param(ws.PathParameter(ParamId, ""))
	r.Do(services.Returns(
		http.StatusNoContent,
		http.StatusNotFound))
	r.Do(services.ReturnsE(http.StatusBadRequest))
	ws.Route(r)

	// sessions

	r = ws.GET(fmt.Sprintf("{%s}/reports", ParamId)).To(s.TakeScan(s.reports))
	r.Doc("reports")
	r.Operation("reports")
	r.Param(ws.PathParameter(ParamId, ""))
	addDefaults(r)
	r.Writes(report.ReportList{})
	r.Do(services.Returns(http.StatusOK))
	ws.Route(r)

	s.RegisterSessions(ws)

	container.Add(ws)
}

// ====== service operations

func (s *ScanService) create(req *restful.Request, resp *restful.Response) {
	// TODO (m0sth8): Check permissions
	raw := &scan.Scan{}

	if err := req.ReadEntity(raw); err != nil {
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusBadRequest, services.WrongEntityErr)
		return
	}
	u := filters.GetUser(req)

	mgr := s.Manager()
	defer mgr.Close()

	// TODO (m0sth8): check project and target permissions for this user

	// validations
	project, err := mgr.Projects.GetById(raw.Project)
	if err != nil {
		resp.WriteServiceError(http.StatusBadRequest,
			services.NewBadReq("project not found"))
		return
	}

	target, err := mgr.Targets.GetById(raw.Target)
	if err != nil {
		resp.WriteServiceError(http.StatusBadRequest,
			services.NewBadReq("target not found"))
		return
	}
	if target.Project != project.Id {
		resp.WriteServiceError(http.StatusBadRequest,
			services.NewBadReq("this target is not from this project"))
		return
	}

	plan, err := mgr.Plans.GetById(raw.Plan)
	if err != nil {
		resp.WriteServiceError(http.StatusBadRequest,
			services.NewBadReq("plan not found"))
		return
	}
	if plan.TargetType != target.Type {
		resp.WriteServiceError(http.StatusBadRequest,
			services.NewBadReq("target.type and plan.targetType is not compatible"))
		return
	}

	sc := &scan.Scan{
		Status:  scan.StatusCreated,
		Owner:   u.Id,
		Plan:    raw.Plan,
		Project: project.Id,
		Target:  target.Id,
		Conf: scan.ScanConf{
			Target: target.Addr(),
		},
		Sessions: []*scan.Session{},
	}
	now := time.Now().UTC()
	// Add session from plans workflow steps
	for _, step := range plan.Workflow {
		// TODO (m0sth8): Take latest plugin or search by version, extract this logic
		plNameVersion := strings.Split(step.Plugin, ":")
		plugin, err := mgr.Plugins.GetByNameVersion(plNameVersion[0], plNameVersion[1])
		if err != nil {
			if mgr.IsNotFound(err) {
				resp.WriteServiceError(http.StatusBadRequest,
					services.NewBadReq(fmt.Sprintf("plugin %s is not found", step.Plugin)))
				return
			}
			logrus.Error(stackerr.Wrap(err))
			resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
			return
		}
		// TODO (m0sth8): extract template execution
		if step.Conf != nil {
			if command := step.Conf.CommandArgs; command != "" {
				t, err := template.New("").Parse(command)
				if err != nil {
					logrus.Error(stackerr.Wrap(err))
					resp.WriteServiceError(http.StatusInternalServerError, services.NewAppErr("Wrong command args template"))
					return
				}
				buf := bytes.NewBuffer(nil)
				err = t.Execute(buf, sc.Conf)
				if err != nil {
					logrus.Error(stackerr.Wrap(err))
					resp.WriteServiceError(http.StatusInternalServerError, services.NewAppErr("Wrong command args template"))
					return
				}
				step.Conf.CommandArgs = buf.String()
			}
		}

		sess := scan.Session{
			Id:     mgr.NewId(),
			Step:   step,
			Plugin: plugin.Id,
			Status: scan.StatusCreated,
			Dates: scan.Dates{
				Created: &now,
				Updated: &now,
			},
		}
		sc.Sessions = append(sc.Sessions, &sess)
	}

	obj, err := mgr.Scans.Create(sc)
	if err != nil {
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		return
	}
	// put scan to queue
	s.Scheduler().AddScan(obj)
	if _, err := mgr.Feed.AddScan(obj); err != nil {
		logrus.Error(stackerr.Wrap(err))
	}

	resp.WriteHeader(http.StatusCreated)
	resp.WriteEntity(obj)
}

func (s *ScanService) list(req *restful.Request, resp *restful.Response) {
	query, err := fltr.FromRequest(req, manager.ScanFltr{})
	if err != nil {
		resp.WriteServiceError(http.StatusBadRequest, services.NewBadReq(err.Error()))
		return
	}

	mgr := s.Manager()
	defer mgr.Close()

	results, count, err := mgr.Scans.FilterByQuery(query)
	if err != nil {
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		return
	}

	result := &scan.ScanList{
		Meta:    pagination.Meta{count, "", ""},
		Results: results,
	}
	resp.WriteEntity(result)
}

func (s *ScanService) get(_ *restful.Request, resp *restful.Response, pl *scan.Scan) {
	resp.WriteEntity(pl)
}

// disabled
func (s *ScanService) update(req *restful.Request, resp *restful.Response, pl *scan.Scan) {
	// TODO (m0sth8): Check permissions
	// TODO (m0sth8): Forbid changes in scan after finished status

	raw := &scan.Scan{}

	if err := req.ReadEntity(raw); err != nil {
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusBadRequest, services.WrongEntityErr)
		return
	}
	mgr := s.Manager()
	defer mgr.Close()

	raw.Id = pl.Id

	if err := mgr.Scans.Update(raw); err != nil {
		if mgr.IsNotFound(err) {
			resp.WriteErrorString(http.StatusNotFound, "Not found")
			return
		}
		if mgr.IsDup(err) {
			resp.WriteServiceError(
				http.StatusConflict,
				services.NewError(services.CodeDuplicate, "scan with this name and version is existed"))
			return
		}
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		return
	}

	resp.WriteHeader(http.StatusOK)
	resp.WriteEntity(raw)
}

func (s *ScanService) delete(_ *restful.Request, resp *restful.Response, obj *scan.Scan) {
	// TODO (m0sth8): Forbid to remove scan after queued status

	mgr := s.Manager()
	defer mgr.Close()

	mgr.Scans.Remove(obj)
	resp.WriteHeader(http.StatusNoContent)
}

func (s *ScanService) reports(_ *restful.Request, resp *restful.Response, sc *scan.Scan) {

	mgr := s.Manager()
	defer mgr.Close()

	results := []*report.Report{}

	for _, sess := range sc.Sessions {
		rep, err := mgr.Reports.GetBySession(sess.Id)
		if err != nil {
			if mgr.IsNotFound(err) {
				continue
			}
			logrus.Error(stackerr.Wrap(err))
			resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		}
		results = append(results, rep)
	}

	reportList := report.ReportList{
		Meta:    pagination.Meta{len(results), "", ""},
		Results: results,
	}

	resp.WriteEntity(reportList)
}

// Helpers

type ScanFunction func(*restful.Request, *restful.Response, *scan.Scan)

// Decorate restful.RouteFunction. Look for scan by ParamId
// and add scan object in the end. If scan is not found then return Not Found.
func (s *ScanService) TakeScan(fn ScanFunction) restful.RouteFunction {
	return func(req *restful.Request, resp *restful.Response) {
		id := req.PathParameter(ParamId)
		if !s.IsId(id) {
			resp.WriteServiceError(http.StatusBadRequest, services.IdHexErr)
			return
		}

		mgr := s.Manager()
		defer mgr.Close()

		obj, err := mgr.Scans.GetById(mgr.ToId(id))
		mgr.Close()
		if err != nil {
			if mgr.IsNotFound(err) {
				resp.WriteErrorString(http.StatusNotFound, "Not found")
				return
			}
			logrus.Error(stackerr.Wrap(err))
			resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
			return
		}
		fn(req, resp, obj)
	}
}
<commit_msg>fix scan creation errors<commit_after>package scan

import (
	"bytes"
	"fmt"
	"net/http"
	"strings"
	"text/template"
	"time"

	"github.com/Sirupsen/logrus"
	restful "github.com/emicklei/go-restful"
	"github.com/facebookgo/stackerr"

	"github.com/bearded-web/bearded/models/report"
	"github.com/bearded-web/bearded/models/scan"
	"github.com/bearded-web/bearded/pkg/filters"
	"github.com/bearded-web/bearded/pkg/fltr"
	"github.com/bearded-web/bearded/pkg/manager"
	"github.com/bearded-web/bearded/pkg/pagination"
	"github.com/bearded-web/bearded/services"
)

const (
	ParamId        = "scan-id"
	SessionParamId = "session-id"
)

type ScanService struct {
	*services.BaseService
}

func New(base *services.BaseService) *ScanService {
	return &ScanService{
		BaseService: base,
	}
}

func addDefaults(r *restful.RouteBuilder) {
	r.Notes("Authorization required")
	r.Do(services.ReturnsE(
		http.StatusUnauthorized,
		http.StatusInternalServerError,
	))
}

// Fix for IntelijIdea inpsections. Cause it can't investigate anonymous method results =(
func (s *ScanService) Manager() *manager.Manager {
	return s.BaseService.Manager()
}

func (s *ScanService) Register(container *restful.Container) {
	ws := &restful.WebService{}
	ws.Path("/api/v1/scans")
	ws.Doc("Manage Scans")
	ws.Consumes(restful.MIME_JSON)
	ws.Produces(restful.MIME_JSON)
	ws.Filter(filters.AuthTokenFilter(s.BaseManager()))
	ws.Filter(filters.AuthRequiredFilter(s.BaseManager()))

	r := ws.GET("").To(s.list)
	r.Doc("list")
	r.Operation("list")
	s.SetParams(r, fltr.GetParams(ws, manager.ScanFltr{}))
	addDefaults(r)
	r.Writes(scan.ScanList{})
	r.Do(services.Returns(http.StatusOK))
	ws.Route(r)

	r = ws.POST("").To(s.create)
	r.Doc("create")
	r.Operation("create")
	addDefaults(r)
	r.Writes(scan.Scan{})
	r.Reads(scan.Scan{})
	r.Do(services.Returns(http.StatusCreated))
	r.Do(services.ReturnsE(
		http.StatusBadRequest,
		http.StatusConflict,
	))
	ws.Route(r)

	r = ws.GET(fmt.Sprintf("{%s}", ParamId)).To(s.TakeScan(s.get))
	r.Doc("get")
	r.Operation("get")
	addDefaults(r)
	r.Param(ws.PathParameter(ParamId, ""))
	r.Writes(scan.Scan{})
	r.Do(services.Returns(
		http.StatusOK,
		http.StatusNotFound))
	r.Do(services.ReturnsE(http.StatusBadRequest))
	ws.Route(r)

	//	r = ws.PUT(fmt.Sprintf("{%s}", ParamId)).To(s.TakeScan(s.update))
	//	r.Doc("update")
	//	r.Operation("update")
	//	r.Param(ws.PathParameter(ParamId, ""))
	//	r.Writes(scan.Scan{})
	//	r.Reads(scan.Scan{})
	//	r.Do(services.Returns(
	//		http.StatusOK,
	//		http.StatusNotFound))
	//	r.Do(services.ReturnsE(http.StatusBadRequest))
	//	ws.Route(r)

	r = ws.DELETE(fmt.Sprintf("{%s}", ParamId)).To(s.TakeScan(s.delete))
	r.Doc("delete")
	r.Operation("delete")
	addDefaults(r)
	r.Param(ws.PathParameter(ParamId, ""))
	r.Do(services.Returns(
		http.StatusNoContent,
		http.StatusNotFound))
	r.Do(services.ReturnsE(http.StatusBadRequest))
	ws.Route(r)

	// sessions

	r = ws.GET(fmt.Sprintf("{%s}/reports", ParamId)).To(s.TakeScan(s.reports))
	r.Doc("reports")
	r.Operation("reports")
	r.Param(ws.PathParameter(ParamId, ""))
	addDefaults(r)
	r.Writes(report.ReportList{})
	r.Do(services.Returns(http.StatusOK))
	ws.Route(r)

	s.RegisterSessions(ws)

	container.Add(ws)
}

// ====== service operations

func (s *ScanService) create(req *restful.Request, resp *restful.Response) {
	// TODO (m0sth8): Check permissions
	raw := &scan.Scan{}

	if err := req.ReadEntity(raw); err != nil {
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusBadRequest, services.WrongEntityErr)
		return
	}
	u := filters.GetUser(req)

	mgr := s.Manager()
	defer mgr.Close()

	// TODO (m0sth8): check project and target permissions for this user

	// validations
	project, err := mgr.Projects.GetById(raw.Project)
	if err != nil {
		resp.WriteServiceError(http.StatusBadRequest,
			services.NewBadReq("project not found"))
		return
	}

	target, err := mgr.Targets.GetById(raw.Target)
	if err != nil {
		if mgr.IsNotFound(err) {
			resp.WriteServiceError(http.StatusBadRequest,
				services.NewBadReq("target not found"))
			return
		}
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		return
	}
	if target.Project != project.Id {
		resp.WriteServiceError(http.StatusBadRequest,
			services.NewBadReq("this target is not from this project"))
		return
	}

	plan, err := mgr.Plans.GetById(raw.Plan)
	if err != nil {
		if mgr.IsNotFound(err) {
			resp.WriteServiceError(http.StatusBadRequest,
				services.NewBadReq("plan not found"))
			return
		}
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		return
	}
	if plan.TargetType != target.Type {
		resp.WriteServiceError(http.StatusBadRequest,
			services.NewBadReq("target.type and plan.targetType is not compatible"))
		return
	}

	sc := &scan.Scan{
		Status:  scan.StatusCreated,
		Owner:   u.Id,
		Plan:    raw.Plan,
		Project: project.Id,
		Target:  target.Id,
		Conf: scan.ScanConf{
			Target: target.Addr(),
		},
		Sessions: []*scan.Session{},
	}
	now := time.Now().UTC()
	// Add session from plans workflow steps
	for _, step := range plan.Workflow {
		// TODO (m0sth8): Take latest plugin or search by version, extract this logic
		plNameVersion := strings.Split(step.Plugin, ":")
		plugin, err := mgr.Plugins.GetByNameVersion(plNameVersion[0], plNameVersion[1])
		if err != nil {
			if mgr.IsNotFound(err) {
				resp.WriteServiceError(http.StatusBadRequest,
					services.NewBadReq(fmt.Sprintf("plugin %s is not found", step.Plugin)))
				return
			}
			logrus.Error(stackerr.Wrap(err))
			resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
			return
		}
		// TODO (m0sth8): extract template execution
		if step.Conf != nil {
			if command := step.Conf.CommandArgs; command != "" {
				t, err := template.New("").Parse(command)
				if err != nil {
					logrus.Error(stackerr.Wrap(err))
					resp.WriteServiceError(http.StatusInternalServerError, services.NewAppErr("Wrong command args template"))
					return
				}
				buf := bytes.NewBuffer(nil)
				err = t.Execute(buf, sc.Conf)
				if err != nil {
					logrus.Error(stackerr.Wrap(err))
					resp.WriteServiceError(http.StatusInternalServerError, services.NewAppErr("Wrong command args template"))
					return
				}
				step.Conf.CommandArgs = buf.String()
			}
		}

		sess := scan.Session{
			Id:     mgr.NewId(),
			Step:   step,
			Plugin: plugin.Id,
			Status: scan.StatusCreated,
			Dates: scan.Dates{
				Created: &now,
				Updated: &now,
			},
		}
		sc.Sessions = append(sc.Sessions, &sess)
	}

	obj, err := mgr.Scans.Create(sc)
	if err != nil {
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		return
	}
	// put scan to queue
	s.Scheduler().AddScan(obj)
	if _, err := mgr.Feed.AddScan(obj); err != nil {
		logrus.Error(stackerr.Wrap(err))
	}

	resp.WriteHeader(http.StatusCreated)
	resp.WriteEntity(obj)
}

func (s *ScanService) list(req *restful.Request, resp *restful.Response) {
	query, err := fltr.FromRequest(req, manager.ScanFltr{})
	if err != nil {
		resp.WriteServiceError(http.StatusBadRequest, services.NewBadReq(err.Error()))
		return
	}

	mgr := s.Manager()
	defer mgr.Close()

	results, count, err := mgr.Scans.FilterByQuery(query)
	if err != nil {
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		return
	}

	result := &scan.ScanList{
		Meta:    pagination.Meta{count, "", ""},
		Results: results,
	}
	resp.WriteEntity(result)
}

func (s *ScanService) get(_ *restful.Request, resp *restful.Response, pl *scan.Scan) {
	resp.WriteEntity(pl)
}

// disabled
func (s *ScanService) update(req *restful.Request, resp *restful.Response, pl *scan.Scan) {
	// TODO (m0sth8): Check permissions
	// TODO (m0sth8): Forbid changes in scan after finished status

	raw := &scan.Scan{}

	if err := req.ReadEntity(raw); err != nil {
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusBadRequest, services.WrongEntityErr)
		return
	}
	mgr := s.Manager()
	defer mgr.Close()

	raw.Id = pl.Id

	if err := mgr.Scans.Update(raw); err != nil {
		if mgr.IsNotFound(err) {
			resp.WriteErrorString(http.StatusNotFound, "Not found")
			return
		}
		if mgr.IsDup(err) {
			resp.WriteServiceError(
				http.StatusConflict,
				services.NewError(services.CodeDuplicate, "scan with this name and version is existed"))
			return
		}
		logrus.Error(stackerr.Wrap(err))
		resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		return
	}

	resp.WriteHeader(http.StatusOK)
	resp.WriteEntity(raw)
}

func (s *ScanService) delete(_ *restful.Request, resp *restful.Response, obj *scan.Scan) {
	// TODO (m0sth8): Forbid to remove scan after queued status

	mgr := s.Manager()
	defer mgr.Close()

	mgr.Scans.Remove(obj)
	resp.WriteHeader(http.StatusNoContent)
}

func (s *ScanService) reports(_ *restful.Request, resp *restful.Response, sc *scan.Scan) {

	mgr := s.Manager()
	defer mgr.Close()

	results := []*report.Report{}

	for _, sess := range sc.Sessions {
		rep, err := mgr.Reports.GetBySession(sess.Id)
		if err != nil {
			if mgr.IsNotFound(err) {
				continue
			}
			logrus.Error(stackerr.Wrap(err))
			resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
		}
		results = append(results, rep)
	}

	reportList := report.ReportList{
		Meta:    pagination.Meta{len(results), "", ""},
		Results: results,
	}

	resp.WriteEntity(reportList)
}

// Helpers

type ScanFunction func(*restful.Request, *restful.Response, *scan.Scan)

// Decorate restful.RouteFunction. Look for scan by ParamId
// and add scan object in the end. If scan is not found then return Not Found.
func (s *ScanService) TakeScan(fn ScanFunction) restful.RouteFunction {
	return func(req *restful.Request, resp *restful.Response) {
		id := req.PathParameter(ParamId)
		if !s.IsId(id) {
			resp.WriteServiceError(http.StatusBadRequest, services.IdHexErr)
			return
		}

		mgr := s.Manager()
		defer mgr.Close()

		obj, err := mgr.Scans.GetById(mgr.ToId(id))
		mgr.Close()
		if err != nil {
			if mgr.IsNotFound(err) {
				resp.WriteErrorString(http.StatusNotFound, "Not found")
				return
			}
			logrus.Error(stackerr.Wrap(err))
			resp.WriteServiceError(http.StatusInternalServerError, services.DbErr)
			return
		}
		fn(req, resp, obj)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package protobuf

import (
	"bytes"
	"errors"
	"fmt"
	"go/format"
	"io/ioutil"
	"os"
	"reflect"
	"strings"

	"k8s.io/kubernetes/third_party/golang/go/ast"
	"k8s.io/kubernetes/third_party/golang/go/parser"
	"k8s.io/kubernetes/third_party/golang/go/printer"
	"k8s.io/kubernetes/third_party/golang/go/token"
	customreflect "k8s.io/kubernetes/third_party/golang/reflect"
)

func rewriteFile(name string, header []byte, rewriteFn func(*token.FileSet, *ast.File) error) error {
	fset := token.NewFileSet()
	src, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}
	file, err := parser.ParseFile(fset, name, src, parser.DeclarationErrors|parser.ParseComments)
	if err != nil {
		return err
	}

	if err := rewriteFn(fset, file); err != nil {
		return err
	}

	b := &bytes.Buffer{}
	b.Write(header)
	if err := printer.Fprint(b, fset, file); err != nil {
		return err
	}

	body, err := format.Source(b.Bytes())
	if err != nil {
		return err
	}

	f, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()
	if _, err := f.Write(body); err != nil {
		return err
	}
	return f.Close()
}

// ExtractFunc extracts information from the provided TypeSpec and returns true if the type should be
// removed from the destination file.
type ExtractFunc func(*ast.TypeSpec) bool

func RewriteGeneratedGogoProtobufFile(name string, extractFn ExtractFunc, header []byte) error {
	return rewriteFile(name, header, func(fset *token.FileSet, file *ast.File) error {
		cmap := ast.NewCommentMap(fset, file, file.Comments)

		// remove types that are already declared
		decls := []ast.Decl{}
		for _, d := range file.Decls {
			if !dropExistingTypeDeclarations(d, extractFn) {
				decls = append(decls, d)
			}
		}
		file.Decls = decls

		// remove unmapped comments
		file.Comments = cmap.Filter(file).Comments()
		return nil
	})
}

func dropExistingTypeDeclarations(decl ast.Decl, extractFn ExtractFunc) bool {
	switch t := decl.(type) {
	case *ast.GenDecl:
		if t.Tok != token.TYPE {
			return false
		}
		specs := []ast.Spec{}
		for _, s := range t.Specs {
			switch spec := s.(type) {
			case *ast.TypeSpec:
				if extractFn(spec) {
					continue
				}
				specs = append(specs, spec)
			}
		}
		if len(specs) == 0 {
			return true
		}
		t.Specs = specs
	}
	return false
}

func RewriteTypesWithProtobufStructTags(name string, structTags map[string]map[string]string) error {
	return rewriteFile(name, []byte{}, func(fset *token.FileSet, file *ast.File) error {
		allErrs := []error{}

		// set any new struct tags
		for _, d := range file.Decls {
			if errs := updateStructTags(d, structTags, []string{"protobuf"}); len(errs) > 0 {
				allErrs = append(allErrs, errs...)
			}
		}

		if len(allErrs) > 0 {
			var s string
			for _, err := range allErrs {
				s += err.Error() + "\n"
			}
			return errors.New(s)
		}
		return nil
	})
}

func updateStructTags(decl ast.Decl, structTags map[string]map[string]string, toCopy []string) []error {
	var errs []error
	t, ok := decl.(*ast.GenDecl)
	if !ok {
		return nil
	}
	if t.Tok != token.TYPE {
		return nil
	}

	for _, s := range t.Specs {
		spec, ok := s.(*ast.TypeSpec)
		if !ok {
			continue
		}
		typeName := spec.Name.Name
		fieldTags, ok := structTags[typeName]
		if !ok {
			continue
		}
		st, ok := spec.Type.(*ast.StructType)
		if !ok {
			continue
		}

		for i := range st.Fields.List {
			f := st.Fields.List[i]
			var name string
			if len(f.Names) == 0 {
				switch t := f.Type.(type) {
				case *ast.Ident:
					name = t.Name
				case *ast.SelectorExpr:
					name = t.Sel.Name
				default:
					errs = append(errs, fmt.Errorf("unable to get name for tag from struct %q, field %#v", spec.Name.Name, t))
					continue
				}
			} else {
				name = f.Names[0].Name
			}
			value, ok := fieldTags[name]
			if !ok {
				continue
			}
			var tags customreflect.StructTags
			if f.Tag != nil {
				oldTags, err := customreflect.ParseStructTags(strings.Trim(f.Tag.Value, "`"))
				if err != nil {
					errs = append(errs, fmt.Errorf("unable to read struct tag from struct %q, field %q: %v", spec.Name.Name, name, err))
					continue
				}
				tags = oldTags
			}
			for _, name := range toCopy {
				// don't overwrite existing tags
				if tags.Has(name) {
					continue
				}
				// append new tags
				if v := reflect.StructTag(value).Get(name); len(v) > 0 {
					tags = append(tags, customreflect.StructTag{Name: name, Value: v})
				}
			}
			if len(tags) == 0 {
				continue
			}
			if f.Tag == nil {
				f.Tag = &ast.BasicLit{}
			}
			f.Tag.Value = tags.String()
		}
	}
	return errs
}
<commit_msg>Protobuf generation should strip empty-imports<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package protobuf

import (
	"bytes"
	"errors"
	"fmt"
	"go/format"
	"io/ioutil"
	"os"
	"reflect"
	"strings"

	"k8s.io/kubernetes/third_party/golang/go/ast"
	"k8s.io/kubernetes/third_party/golang/go/parser"
	"k8s.io/kubernetes/third_party/golang/go/printer"
	"k8s.io/kubernetes/third_party/golang/go/token"
	customreflect "k8s.io/kubernetes/third_party/golang/reflect"
)

func rewriteFile(name string, header []byte, rewriteFn func(*token.FileSet, *ast.File) error) error {
	fset := token.NewFileSet()
	src, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}
	file, err := parser.ParseFile(fset, name, src, parser.DeclarationErrors|parser.ParseComments)
	if err != nil {
		return err
	}

	if err := rewriteFn(fset, file); err != nil {
		return err
	}

	b := &bytes.Buffer{}
	b.Write(header)
	if err := printer.Fprint(b, fset, file); err != nil {
		return err
	}

	body, err := format.Source(b.Bytes())
	if err != nil {
		return err
	}

	f, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()
	if _, err := f.Write(body); err != nil {
		return err
	}
	return f.Close()
}

// ExtractFunc extracts information from the provided TypeSpec and returns true if the type should be
// removed from the destination file.
type ExtractFunc func(*ast.TypeSpec) bool

func RewriteGeneratedGogoProtobufFile(name string, extractFn ExtractFunc, header []byte) error {
	return rewriteFile(name, header, func(fset *token.FileSet, file *ast.File) error {
		cmap := ast.NewCommentMap(fset, file, file.Comments)

		// remove types that are already declared
		decls := []ast.Decl{}
		for _, d := range file.Decls {
			if dropExistingTypeDeclarations(d, extractFn) {
				continue
			}
			if dropEmptyImportDeclarations(d) {
				continue
			}
			decls = append(decls, d)
		}
		file.Decls = decls

		// remove unmapped comments
		file.Comments = cmap.Filter(file).Comments()
		return nil
	})
}

// dropExistingTypeDeclarations removes any type declaration for which extractFn returns true. The function
// returns true if the entire declaration should be dropped.
func dropExistingTypeDeclarations(decl ast.Decl, extractFn ExtractFunc) bool {
	switch t := decl.(type) {
	case *ast.GenDecl:
		if t.Tok != token.TYPE {
			return false
		}
		specs := []ast.Spec{}
		for _, s := range t.Specs {
			switch spec := s.(type) {
			case *ast.TypeSpec:
				if extractFn(spec) {
					continue
				}
				specs = append(specs, spec)
			}
		}
		if len(specs) == 0 {
			return true
		}
		t.Specs = specs
	}
	return false
}

// dropEmptyImportDeclarations strips any generated but no-op imports from the generated code
// to prevent generation from being able to define side-effects.  The function returns true
// if the entire declaration should be dropped.
func dropEmptyImportDeclarations(decl ast.Decl) bool {
	switch t := decl.(type) {
	case *ast.GenDecl:
		if t.Tok != token.IMPORT {
			return false
		}
		specs := []ast.Spec{}
		for _, s := range t.Specs {
			switch spec := s.(type) {
			case *ast.ImportSpec:
				if spec.Name != nil && spec.Name.Name == "_" {
					continue
				}
				specs = append(specs, spec)
			}
		}
		if len(specs) == 0 {
			return true
		}
		t.Specs = specs
	}
	return false
}

func RewriteTypesWithProtobufStructTags(name string, structTags map[string]map[string]string) error {
	return rewriteFile(name, []byte{}, func(fset *token.FileSet, file *ast.File) error {
		allErrs := []error{}

		// set any new struct tags
		for _, d := range file.Decls {
			if errs := updateStructTags(d, structTags, []string{"protobuf"}); len(errs) > 0 {
				allErrs = append(allErrs, errs...)
			}
		}

		if len(allErrs) > 0 {
			var s string
			for _, err := range allErrs {
				s += err.Error() + "\n"
			}
			return errors.New(s)
		}
		return nil
	})
}

func updateStructTags(decl ast.Decl, structTags map[string]map[string]string, toCopy []string) []error {
	var errs []error
	t, ok := decl.(*ast.GenDecl)
	if !ok {
		return nil
	}
	if t.Tok != token.TYPE {
		return nil
	}

	for _, s := range t.Specs {
		spec, ok := s.(*ast.TypeSpec)
		if !ok {
			continue
		}
		typeName := spec.Name.Name
		fieldTags, ok := structTags[typeName]
		if !ok {
			continue
		}
		st, ok := spec.Type.(*ast.StructType)
		if !ok {
			continue
		}

		for i := range st.Fields.List {
			f := st.Fields.List[i]
			var name string
			if len(f.Names) == 0 {
				switch t := f.Type.(type) {
				case *ast.Ident:
					name = t.Name
				case *ast.SelectorExpr:
					name = t.Sel.Name
				default:
					errs = append(errs, fmt.Errorf("unable to get name for tag from struct %q, field %#v", spec.Name.Name, t))
					continue
				}
			} else {
				name = f.Names[0].Name
			}
			value, ok := fieldTags[name]
			if !ok {
				continue
			}
			var tags customreflect.StructTags
			if f.Tag != nil {
				oldTags, err := customreflect.ParseStructTags(strings.Trim(f.Tag.Value, "`"))
				if err != nil {
					errs = append(errs, fmt.Errorf("unable to read struct tag from struct %q, field %q: %v", spec.Name.Name, name, err))
					continue
				}
				tags = oldTags
			}
			for _, name := range toCopy {
				// don't overwrite existing tags
				if tags.Has(name) {
					continue
				}
				// append new tags
				if v := reflect.StructTag(value).Get(name); len(v) > 0 {
					tags = append(tags, customreflect.StructTag{Name: name, Value: v})
				}
			}
			if len(tags) == 0 {
				continue
			}
			if f.Tag == nil {
				f.Tag = &ast.BasicLit{}
			}
			f.Tag.Value = tags.String()
		}
	}
	return errs
}
<|endoftext|>


<commit_before>package providers

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/digitalocean/godo"
	"golang.org/x/oauth2"
)

const (
	cloudInit = `#cloud-config

  coreos:
    units:
      - name: nimona.service
        command: start
        enable: true
        content: |
          [Unit]
          Description=nimona
          After=docker.service
          Requires=docker.service
          After=docker.redis.service

          [Service]
          TimeoutStartSec=0
          Restart=always
          ExecStartPre=-/usr/bin/docker stop nimona
          ExecStartPre=-/usr/bin/docker rm nimona
          ExecStartPre=/usr/bin/docker pull nimona/nimona:latest
          ExecStart=/usr/bin/docker run --name nimona --rm -p 21013:21013 nimona/nimona daemon --port=21013 --api-port=8080 %s

          [Install]
          WantedBy=multi-user.target`
)

// DigitalOceanProvider prodides a DO operations
type DigitalOceanProvider struct {
	client *godo.Client
}

type tokenSource struct {
	AccessToken string
}

func (t *tokenSource) Token() (*oauth2.Token, error) {
	token := &oauth2.Token{
		AccessToken: t.AccessToken,
	}
	return token, nil
}

var (
	// ErrNewinstanceTimeout is returned when NewInstances times out
	// while waiting for the instance to start
	ErrNewinstanceTimeout = errors.New(
		"timeout while waiting for new instance, please check manually")
	// ErrInvalidName is returned when a domain cannot be split in two parts
	ErrInvalidName = errors.New("invalid name for domain")
)

// NewDigitalocean creates a new DigitalOcean Provider
func NewDigitalocean(token string) (Provider, error) {
	if token == "" {
		return nil, ErrNoToken
	}

	tokenSource := &tokenSource{
		AccessToken: token,
	}

	oauthClient := oauth2.NewClient(context.Background(), tokenSource)
	client := godo.NewClient(oauthClient)

	return &DigitalOceanProvider{
		client: client,
	}, nil
}

// NewInstance creates a new DO Droplet
func (dp *DigitalOceanProvider) NewInstance(name, sshFingerprint,
	size, region string) (string, error) {
	if size == "" {
		size = "s-1vcpu-1gb"
	}

	if region == "" {
		region = "lon1"
	}

	userData := fmt.Sprintf(cloudInit, "")
	if name != "" {
		userData = fmt.Sprintf(cloudInit, "--announce-hostname="+name)
	}

	ctx := context.Background()
	createRequest := &godo.DropletCreateRequest{
		Name:   name,
		Region: region,
		Size:   size,
		Image: godo.DropletCreateImage{
			Slug: "coreos-stable",
		},
		SSHKeys: []godo.DropletCreateSSHKey{godo.DropletCreateSSHKey{
			Fingerprint: sshFingerprint,
		}},
		UserData: userData,
	}

	// Create server
	drop, _, err := dp.client.Droplets.Create(
		ctx, createRequest)
	if err != nil {
		return "", err
	}

	wn := 0

	// Wait for the API to return an IP
	for {
		d, _, err := dp.client.Droplets.Get(ctx, drop.ID)
		if err != nil {
			return "", err
		}

		ip, err := d.PublicIPv4()
		if err != nil {
			return "", err
		}
		if ip != "" {
			return ip, nil
		}

		if wn == 60 {
			break
		}

		wn++
		time.Sleep(2 * time.Second)
	}

	return "", ErrNewinstanceTimeout
}

func (dp *DigitalOceanProvider) UpdateDomain(ctx context.Context,
	name, ip string) error {

	ds := strings.SplitN(name, ".", 2)
	if len(ds) != 2 {
		return ErrInvalidName
	}

	userSubdomain := ds[0]
	userDomain := ds[1]

	list, _, err := dp.client.Domains.List(ctx, &godo.ListOptions{})
	if err != nil {
		return err
	}

	domainFound := false
	fullPathFound := false
	record := godo.DomainRecord{}

	for _, domain := range list {
		if domain.Name == userDomain {
			domainFound = true
			break
		}
	}

	if !domainFound {
		_, _, err := dp.client.Domains.Create(ctx,
			&godo.DomainCreateRequest{
				Name: userDomain,
			})
		if err != nil {
			return err
		}
	}

	if domainFound {
		recs, _, err := dp.client.Domains.Records(ctx, userDomain,
			&godo.ListOptions{})
		if err != nil {
			return err
		}

		for _, rec := range recs {
			if rec.Name == userSubdomain {
				fullPathFound = true
				record = rec
			}
		}
	}

	if !fullPathFound {
		_, _, err := dp.client.Domains.CreateRecord(ctx, userDomain,
			&godo.DomainRecordEditRequest{
				Name: userSubdomain,
				Data: ip,
				Type: "A",
			})
		if err != nil {
			return err
		}
	}

	if fullPathFound {
		_, _, err := dp.client.Domains.EditRecord(ctx, userDomain, record.ID,
			&godo.DomainRecordEditRequest{
				Name: userSubdomain,
				Data: ip,
				Type: "A",
			})
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>Fix empty domain name for daemon install<commit_after>package providers

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/digitalocean/godo"
	"golang.org/x/oauth2"
)

const (
	cloudInit = `#cloud-config

  coreos:
    units:
      - name: nimona.service
        command: start
        enable: true
        content: |
          [Unit]
          Description=nimona
          After=docker.service
          Requires=docker.service
          After=docker.redis.service

          [Service]
          TimeoutStartSec=0
          Restart=always
          ExecStartPre=-/usr/bin/docker stop nimona
          ExecStartPre=-/usr/bin/docker rm nimona
          ExecStartPre=/usr/bin/docker pull nimona/nimona:latest
          ExecStart=/usr/bin/docker run --name nimona --rm -p 21013:21013 nimona/nimona daemon --port=21013 --api-port=8080 %s

          [Install]
          WantedBy=multi-user.target`
)

// DigitalOceanProvider prodides a DO operations
type DigitalOceanProvider struct {
	client *godo.Client
}

type tokenSource struct {
	AccessToken string
}

func (t *tokenSource) Token() (*oauth2.Token, error) {
	token := &oauth2.Token{
		AccessToken: t.AccessToken,
	}
	return token, nil
}

var (
	// ErrNewinstanceTimeout is returned when NewInstances times out
	// while waiting for the instance to start
	ErrNewinstanceTimeout = errors.New(
		"timeout while waiting for new instance, please check manually")
	// ErrInvalidName is returned when a domain cannot be split in two parts
	ErrInvalidName = errors.New("invalid name for domain")
)

// NewDigitalocean creates a new DigitalOcean Provider
func NewDigitalocean(token string) (Provider, error) {
	if token == "" {
		return nil, ErrNoToken
	}

	tokenSource := &tokenSource{
		AccessToken: token,
	}

	oauthClient := oauth2.NewClient(context.Background(), tokenSource)
	client := godo.NewClient(oauthClient)

	return &DigitalOceanProvider{
		client: client,
	}, nil
}

// NewInstance creates a new DO Droplet
func (dp *DigitalOceanProvider) NewInstance(name, sshFingerprint,
	size, region string) (string, error) {
	if size == "" {
		size = "s-1vcpu-1gb"
	}

	if region == "" {
		region = "lon1"
	}

	userData := fmt.Sprintf(cloudInit, "")
	if name != "" {
		userData = fmt.Sprintf(cloudInit, "--announce-hostname="+name)
	} else {
		name = fmt.Sprintf("nimona-%d", time.Now().Unix())
	}
	ctx := context.Background()
	createRequest := &godo.DropletCreateRequest{
		Name:   name,
		Region: region,
		Size:   size,
		Image: godo.DropletCreateImage{
			Slug: "coreos-stable",
		},
		SSHKeys: []godo.DropletCreateSSHKey{godo.DropletCreateSSHKey{
			Fingerprint: sshFingerprint,
		}},
		UserData: userData,
	}

	// Create server
	drop, _, err := dp.client.Droplets.Create(
		ctx, createRequest)
	if err != nil {
		return "", err
	}

	wn := 0

	// Wait for the API to return an IP
	for {
		d, _, err := dp.client.Droplets.Get(ctx, drop.ID)
		if err != nil {
			return "", err
		}

		ip, err := d.PublicIPv4()
		if err != nil {
			return "", err
		}
		if ip != "" {
			return ip, nil
		}

		if wn == 60 {
			break
		}

		wn++
		time.Sleep(2 * time.Second)
	}

	return "", ErrNewinstanceTimeout
}

func (dp *DigitalOceanProvider) UpdateDomain(ctx context.Context,
	name, ip string) error {

	ds := strings.SplitN(name, ".", 2)
	if len(ds) != 2 {
		return ErrInvalidName
	}

	userSubdomain := ds[0]
	userDomain := ds[1]

	list, _, err := dp.client.Domains.List(ctx, &godo.ListOptions{})
	if err != nil {
		return err
	}

	domainFound := false
	fullPathFound := false
	record := godo.DomainRecord{}

	for _, domain := range list {
		if domain.Name == userDomain {
			domainFound = true
			break
		}
	}

	if !domainFound {
		_, _, err := dp.client.Domains.Create(ctx,
			&godo.DomainCreateRequest{
				Name: userDomain,
			})
		if err != nil {
			return err
		}
	}

	if domainFound {
		recs, _, err := dp.client.Domains.Records(ctx, userDomain,
			&godo.ListOptions{})
		if err != nil {
			return err
		}

		for _, rec := range recs {
			if rec.Name == userSubdomain {
				fullPathFound = true
				record = rec
			}
		}
	}

	if !fullPathFound {
		_, _, err := dp.client.Domains.CreateRecord(ctx, userDomain,
			&godo.DomainRecordEditRequest{
				Name: userSubdomain,
				Data: ip,
				Type: "A",
			})
		if err != nil {
			return err
		}
	}

	if fullPathFound {
		_, _, err := dp.client.Domains.EditRecord(ctx, userDomain, record.ID,
			&godo.DomainRecordEditRequest{
				Name: userSubdomain,
				Data: ip,
				Type: "A",
			})
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// VarHandler genrate wrappers for variing http handler funcs
//
// Given a pkg :
// 	//go:generate varhandler
//  package server
//
//  func F([ctx context.Context], x X, y Y, z *Z, zz z.Z) ([resp interface{},] [status int,] err error) {...}
//  // and funcs
//  func HTTPX  (w http.ResponseWriter, r *http.Request) X          {...}
//  func HTTPY  (w http.ResponseWriter, r *http.Request) (Y, error) {...}
//  func HTTPZ  (w http.ResponseWriter, r *http.Request) *Z         {...}
//  func z.HTTPZ(w http.ResponseWriter, r *http.Request) z.Z        {...}
//
// VarHandler will generate an http handler :
//   func FVarHandler(w http.ResponseWriter, r *http.Request) {
//       x := HTTPX(w, r)
//       y, err := HTTPY(w, r)
//       if err != nil {
//       	HandleHttpErrorWithDefaultStatus(http.StatusBadRequest, err)
//       	return
//       }
//       z := HTTPZ(w, r)
//       zz := z.HTTPZ(w, r)
//       resp, status, err := F([context.Background()], x, y, z, zz)
//       if err != nil {
//       	HandleHttpErrorWithDefaultStatus(http.InternalServerError, err)
//       	return
//       }
//       if status != 0 { // code generated if status is returned
//       	w.WriteHeader(status)
//       }
//       if resp != nil { // code generated if resp object is returned
//       	HandleHttpResponse()
//       }
//   }
//
//   func HandleHttpErrorWithDefaultStatus(w http.ResponseWriter, status int, err error) {
//       type HttpError interface {
//       	HttpError() (error string, code int)
//       }
//       type SelfHttpError interface {
//       	HttpError(w http.ResponseWriter)
//       }
//       switch t := err.(type) {
//       default:
//       	w.WriteHeader(status)
//       case HttpError:
//       	err, code := t.HttpError()
//       	http.Error(w, err, code)
//       case SelfHttpError:
//       	t.HttpError(w)
//       }
//   }
//
//   func HandleHttpResponse(w http.ResponseWriter, r *http.Request, resp interface{}) {
//       switch t := resp.(type) {
//       default:
//       	// I don't know that type !
//       case http.Handler:
//       	t.ServeHTTP(w, r)
//       case []byte:
//       	w.Write(t)
//       }
//   }
//
package main // import "github.com/azr/generators/varhandler"

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"golang.org/x/tools/go/types"

	_ "golang.org/x/tools/go/gcimporter"

	"github.com/azr/generators/utils"
)

// Usage is a replacement usage function for the flags package.
func Usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "\tvarhandler [flags] -func F [directory]\n")
	fmt.Fprintf(os.Stderr, "\tvarhandler [flags] -func F files... # Must be a single package\n")
	fmt.Fprintf(os.Stderr, "For more information, see:\n")
	fmt.Fprintf(os.Stderr, "\thttp://godoc.org/github.com/azr/generators/varhandler\n")
	fmt.Fprintf(os.Stderr, "Flags:\n")
	flag.PrintDefaults()
}

func main() {
	{ //setup logs
		log.SetFlags(0)
		log.SetPrefix("handler: ")
	}

	var funcNames, output string
	{ // init
		flag.StringVar(&funcNames, "func", "", "comma-separated list of func names; must be set")
		flag.StringVar(&output, "output", "", "output file name; default srcdir/generated_varhandlers.go")
		flag.Usage = Usage
		flag.Parse()
	}
	if len(funcNames) == 0 {
		flag.Usage()
		os.Exit(2)
	}

	funcs := strings.Split(funcNames, ",")

	// We accept either one directory or a list of files. Which do we have?
	args := flag.Args()
	if len(args) == 0 {
		// Default: process whole package in current directory.
		args = []string{"."}
	}

	// Parse the package once.
	var (
		dir string
		g   Generator
	)
	if len(args) == 1 && utils.IsDirectory(args[0]) {
		dir = args[0]
		g.parsePackageDir(args[0])
	} else {
		dir = filepath.Dir(args[0])
		g.parsePackageFiles(args)
	}

	// Print the header and package clause.
	g.Printf("// Code generated by \"handler %s\"; DO NOT EDIT\n", strings.Join(os.Args[1:], " "))
	g.Printf("\n")
	g.Printf("package %s\n", g.pkg.name)
	g.Printf("\n")
	g.Printf("import \"net/http\"\n") // Used by all methods.

	// Run generate for each func.
	for _, funcName := range funcs {
		g.generate(funcName)
	}

	g.Printf(utilFuncs)

	// Format the output.
	src := g.format()

	// Write to file.
	outputName := output
	if outputName == "" {
		outputName = filepath.Join(dir, "generated_handlers.go")
	}
	err := ioutil.WriteFile(outputName, src, 0644)
	if err != nil {
		log.Fatalf("writing output: %s", err)
	}
}

// Generator holds the state of the analysis. Primarily used to buffer
// the output for format.Source.
type Generator struct {
	buf bytes.Buffer // Accumulated output.
	pkg *Package     // Package we are scanning.
}

func (g *Generator) Printf(format string, args ...interface{}) {
	fmt.Fprintf(&g.buf, format, args...)
}

// File holds a single parsed file and associated data.
type File struct {
	pkg  *Package  // Package to which this file belongs.
	file *ast.File // Parsed AST.

	// These fields are reset for each func being generated.
	funcDefinition FuncDefinition
	found          bool
}

type Package struct {
	dir      string
	name     string
	defs     map[*ast.Ident]types.Object
	files    []*File
	typesPkg *types.Package
}

// parsePackageDir parses the package residing in the directory.
func (g *Generator) parsePackageDir(directory string) {
	pkg, err := build.Default.ImportDir(directory, 0)
	if err != nil {
		log.Fatalf("cannot process directory %s: %s", directory, err)
	}
	var names []string
	names = append(names, pkg.GoFiles...)
	names = append(names, pkg.CgoFiles...)
	// TODO: Need to think about constants in test files. Maybe write type_string_test.go
	// in a separate pass? For later.
	// names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
	names = append(names, pkg.SFiles...)
	names = prefixDirectory(directory, names)
	g.parsePackage(directory, names, nil)
}

// parsePackageFiles parses the package occupying the named files.
func (g *Generator) parsePackageFiles(names []string) {
	g.parsePackage(".", names, nil)
}

// prefixDirectory places the directory name on the beginning of each name in the list.
func prefixDirectory(directory string, names []string) []string {
	if directory == "." {
		return names
	}
	ret := make([]string, len(names))
	for i, name := range names {
		ret[i] = filepath.Join(directory, name)
	}
	return ret
}

// parsePackage analyzes the single package constructed from the named files.
// If text is non-nil, it is a string to be used instead of the content of the file,
// to be used for testing. parsePackage exits if there is an error.
func (g *Generator) parsePackage(directory string, names []string, text interface{}) {
	var files []*File
	var astFiles []*ast.File
	g.pkg = new(Package)
	fs := token.NewFileSet()
	for _, name := range names {
		if !strings.HasSuffix(name, ".go") {
			continue
		}
		parsedFile, err := parser.ParseFile(fs, name, text, 0)
		if err != nil {
			log.Fatalf("parsing package: %s: %s", name, err)
		}
		astFiles = append(astFiles, parsedFile)
		files = append(files, &File{
			file: parsedFile,
			pkg:  g.pkg,
		})
	}
	if len(astFiles) == 0 {
		log.Fatalf("%s: no buildable Go files", directory)
	}
	g.pkg.name = astFiles[0].Name.Name
	g.pkg.files = files
	g.pkg.dir = directory
	// Type check the package.
	g.pkg.check(fs, astFiles)
}

// check type-checks the package. The package must be OK to proceed.
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) {
	pkg.defs = make(map[*ast.Ident]types.Object)
	config := types.Config{FakeImportC: true}
	info := &types.Info{
		Defs: pkg.defs,
	}
	log.Printf("check pkg: %s", pkg.dir)
	typesPkg, err := config.Check(pkg.dir, fs, astFiles, info)
	if err != nil {
		log.Fatalf("checking package: %s", err)
	}
	pkg.typesPkg = typesPkg
}

// generate produces the Http varing handler method calling the func
func (g *Generator) generate(funcName string) {
	found := false
	for _, file := range g.pkg.files {
		// Set the state for this run of the walker.
		file.funcDefinition.Name = funcName
		if file.file != nil {
			ast.Inspect(file.file, file.genDecl)
			if file.found {
				found = true
				g.buildFunc(file.funcDefinition)
				break
			}
		}
	}

	if !found {
		fmt.Printf("Func not found: %s", funcName)
	}
}

// format returns the gofmt-ed contents of the Generator's buffer.
func (g *Generator) format() []byte {
	src, err := format.Source(g.buf.Bytes())
	if err != nil {
		// Should never happen, but can arise when developing this code.
		// The user can compile the output to see the error.
		log.Printf("warning: internal error: invalid Go generated: %s", err)
		log.Printf("warning: compile the package to analyze the error")
		return g.buf.Bytes()
	}
	return src
}

// genDecl processes one declaration clause.
func (f *File) genDecl(node ast.Node) bool {
	decl, ok := node.(*ast.FuncDecl)
	if !ok {
		// We only care about func declarations.
		return true
	}
	if decl.Name.Name == f.funcDefinition.Name {
		if len(decl.Type.Params.List) == 0 {
			log.Printf("%s should take at least one parameter, found %d instead", f.funcDefinition.Name, len(decl.Type.Params.List))
			return false
		}
		print("found it !")

		ok := f.funcDefinition.Parse(decl.Type.Params.List)

		f.found = ok
	}
	return false
}

// buildFunc generates an handler func
func (g *Generator) buildFunc(fd FuncDefinition) {
	funcMap := template.FuncMap{
		"ToLower": strings.ToLower,
	}

	t := template.Must(template.New("handler").Funcs(funcMap).Parse(handlerWrap))

	err := t.Execute(&g.buf, fd)
	checkError(err)
}

const handlerWrap = `
func {{.Name}}Handler(w http.ResponseWriter, r *http.Request) {
//:D
}
`

const utilFuncs = `
func HandleHttpErrorWithDefaultStatus(w http.ResponseWriter, status int, err error) {
    type HttpError interface {
    	HttpError() (error string, code int)
    }
    type SelfHttpError interface {
    	HttpError(w http.ResponseWriter)
    }
    switch t := err.(type) {
    default:
    	w.WriteHeader(status)
    case HttpError:
    	err, code := t.HttpError()
    	http.Error(w, err, code)
    case SelfHttpError:
    	t.HttpError(w)
    }
}

func HandleHttpResponse(w http.ResponseWriter, r *http.Request, resp interface{}) {
    switch t := resp.(type) {
    default:
    	// I don't know that type !
    case http.Handler:
    	t.ServeHTTP(w, r)
    case []byte:
    	w.Write(t)
    }
}
`

func checkError(err error) {
	if err != nil {
		fmt.Println("Fatal error ", err.Error())
		os.Exit(1)
	}
}
<commit_msg>chore(varhandler): be nicer on specs<commit_after>// VarHandler genrate wrappers for variing http handler funcs
//
// Given a pkg :
// 	//go:generate varhandler
//  package server
//
//  func F([ctx context.Context], x X, y Y, z *Z, zz z.Z) ([resp interface{},] [status int,] err error) {...}
//  // and funcs
//  func HTTPX  (w http.ResponseWriter, r *http.Request) (X, error)   {...}
//  func HTTPY  (w http.ResponseWriter, r *http.Request) (Y, error)   {...}
//  func HTTPZ  (w http.ResponseWriter, r *http.Request) (*Z, error)  {...}
//  func z.HTTPZ(w http.ResponseWriter, r *http.Request) (z.Z, error) {...}
//
// VarHandler will generate an http handler :
//   func FVarHandler(w http.ResponseWriter, r *http.Request) {
//       var err error
//       x, err := HTTPX(w, r)
//       if err != nil {
//       	HandleHttpErrorWithDefaultStatus(http.StatusBadRequest, err)
//       	return
//       }
//       y, err := HTTPY(w, r)
//       if err != nil {
//       	HandleHttpErrorWithDefaultStatus(http.StatusBadRequest, err)
//       	return
//       }
//       z, err := HTTPZ(w, r)
//       if err != nil {
//       	HandleHttpErrorWithDefaultStatus(http.StatusBadRequest, err)
//       	return
//       }
//       zz, err := z.HTTPZ(w, r)
//       if err != nil {
//       	HandleHttpErrorWithDefaultStatus(http.StatusBadRequest, err)
//       	return
//       }
//       resp, status, err := F([context.Background()], x, y, z, zz)
//       if err != nil {
//       	HandleHttpErrorWithDefaultStatus(http.InternalServerError, err)
//       	return
//       }
//       if status != 0 { // code generated if status is returned
//       	w.WriteHeader(status)
//       }
//       if resp != nil { // code generated if resp object is returned
//       	HandleHttpResponse()
//       }
//   }
//
//   func HandleHttpErrorWithDefaultStatus(w http.ResponseWriter, status int, err error) {
//       type HttpError interface {
//       	HttpError() (error string, code int)
//       }
//       type SelfHttpError interface {
//       	HttpError(w http.ResponseWriter)
//       }
//       switch t := err.(type) {
//       default:
//       	w.WriteHeader(status)
//       case HttpError:
//       	err, code := t.HttpError()
//       	http.Error(w, err, code)
//       case SelfHttpError:
//       	t.HttpError(w)
//       }
//   }
//
//   func HandleHttpResponse(w http.ResponseWriter, r *http.Request, resp interface{}) {
//       switch t := resp.(type) {
//       default:
//       	// I don't know that type !
//       case http.Handler:
//       	t.ServeHTTP(w, r)
//       case []byte:
//       	w.Write(t)
//       }
//   }
//
package main // import "github.com/azr/generators/varhandler"

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"golang.org/x/tools/go/types"

	_ "golang.org/x/tools/go/gcimporter"

	"github.com/azr/generators/utils"
)

// Usage is a replacement usage function for the flags package.
func Usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "\tvarhandler [flags] -func F [directory]\n")
	fmt.Fprintf(os.Stderr, "\tvarhandler [flags] -func F files... # Must be a single package\n")
	fmt.Fprintf(os.Stderr, "For more information, see:\n")
	fmt.Fprintf(os.Stderr, "\thttp://godoc.org/github.com/azr/generators/varhandler\n")
	fmt.Fprintf(os.Stderr, "Flags:\n")
	flag.PrintDefaults()
}

func main() {
	{ //setup logs
		log.SetFlags(0)
		log.SetPrefix("handler: ")
	}

	var funcNames, output string
	{ // init
		flag.StringVar(&funcNames, "func", "", "comma-separated list of func names; must be set")
		flag.StringVar(&output, "output", "", "output file name; default srcdir/generated_varhandlers.go")
		flag.Usage = Usage
		flag.Parse()
	}
	if len(funcNames) == 0 {
		flag.Usage()
		os.Exit(2)
	}

	funcs := strings.Split(funcNames, ",")

	// We accept either one directory or a list of files. Which do we have?
	args := flag.Args()
	if len(args) == 0 {
		// Default: process whole package in current directory.
		args = []string{"."}
	}

	// Parse the package once.
	var (
		dir string
		g   Generator
	)
	if len(args) == 1 && utils.IsDirectory(args[0]) {
		dir = args[0]
		g.parsePackageDir(args[0])
	} else {
		dir = filepath.Dir(args[0])
		g.parsePackageFiles(args)
	}

	// Print the header and package clause.
	g.Printf("// Code generated by \"handler %s\"; DO NOT EDIT\n", strings.Join(os.Args[1:], " "))
	g.Printf("\n")
	g.Printf("package %s\n", g.pkg.name)
	g.Printf("\n")
	g.Printf("import \"net/http\"\n") // Used by all methods.

	// Run generate for each func.
	for _, funcName := range funcs {
		g.generate(funcName)
	}

	g.Printf(utilFuncs)

	// Format the output.
	src := g.format()

	// Write to file.
	outputName := output
	if outputName == "" {
		outputName = filepath.Join(dir, "generated_handlers.go")
	}
	err := ioutil.WriteFile(outputName, src, 0644)
	if err != nil {
		log.Fatalf("writing output: %s", err)
	}
}

// Generator holds the state of the analysis. Primarily used to buffer
// the output for format.Source.
type Generator struct {
	buf bytes.Buffer // Accumulated output.
	pkg *Package     // Package we are scanning.
}

func (g *Generator) Printf(format string, args ...interface{}) {
	fmt.Fprintf(&g.buf, format, args...)
}

// File holds a single parsed file and associated data.
type File struct {
	pkg  *Package  // Package to which this file belongs.
	file *ast.File // Parsed AST.

	// These fields are reset for each func being generated.
	funcDefinition FuncDefinition
	found          bool
}

type Package struct {
	dir      string
	name     string
	defs     map[*ast.Ident]types.Object
	files    []*File
	typesPkg *types.Package
}

// parsePackageDir parses the package residing in the directory.
func (g *Generator) parsePackageDir(directory string) {
	pkg, err := build.Default.ImportDir(directory, 0)
	if err != nil {
		log.Fatalf("cannot process directory %s: %s", directory, err)
	}
	var names []string
	names = append(names, pkg.GoFiles...)
	names = append(names, pkg.CgoFiles...)
	// TODO: Need to think about constants in test files. Maybe write type_string_test.go
	// in a separate pass? For later.
	// names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
	names = append(names, pkg.SFiles...)
	names = prefixDirectory(directory, names)
	g.parsePackage(directory, names, nil)
}

// parsePackageFiles parses the package occupying the named files.
func (g *Generator) parsePackageFiles(names []string) {
	g.parsePackage(".", names, nil)
}

// prefixDirectory places the directory name on the beginning of each name in the list.
func prefixDirectory(directory string, names []string) []string {
	if directory == "." {
		return names
	}
	ret := make([]string, len(names))
	for i, name := range names {
		ret[i] = filepath.Join(directory, name)
	}
	return ret
}

// parsePackage analyzes the single package constructed from the named files.
// If text is non-nil, it is a string to be used instead of the content of the file,
// to be used for testing. parsePackage exits if there is an error.
func (g *Generator) parsePackage(directory string, names []string, text interface{}) {
	var files []*File
	var astFiles []*ast.File
	g.pkg = new(Package)
	fs := token.NewFileSet()
	for _, name := range names {
		if !strings.HasSuffix(name, ".go") {
			continue
		}
		parsedFile, err := parser.ParseFile(fs, name, text, 0)
		if err != nil {
			log.Fatalf("parsing package: %s: %s", name, err)
		}
		astFiles = append(astFiles, parsedFile)
		files = append(files, &File{
			file: parsedFile,
			pkg:  g.pkg,
		})
	}
	if len(astFiles) == 0 {
		log.Fatalf("%s: no buildable Go files", directory)
	}
	g.pkg.name = astFiles[0].Name.Name
	g.pkg.files = files
	g.pkg.dir = directory
	// Type check the package.
	g.pkg.check(fs, astFiles)
}

// check type-checks the package. The package must be OK to proceed.
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) {
	pkg.defs = make(map[*ast.Ident]types.Object)
	config := types.Config{FakeImportC: true}
	info := &types.Info{
		Defs: pkg.defs,
	}
	log.Printf("check pkg: %s", pkg.dir)
	typesPkg, err := config.Check(pkg.dir, fs, astFiles, info)
	if err != nil {
		log.Fatalf("checking package: %s", err)
	}
	pkg.typesPkg = typesPkg
}

// generate produces the Http varing handler method calling the func
func (g *Generator) generate(funcName string) {
	found := false
	for _, file := range g.pkg.files {
		// Set the state for this run of the walker.
		file.funcDefinition.Name = funcName
		if file.file != nil {
			ast.Inspect(file.file, file.genDecl)
			if file.found {
				found = true
				g.buildFunc(file.funcDefinition)
				break
			}
		}
	}

	if !found {
		fmt.Printf("Func not found: %s", funcName)
	}
}

// format returns the gofmt-ed contents of the Generator's buffer.
func (g *Generator) format() []byte {
	src, err := format.Source(g.buf.Bytes())
	if err != nil {
		// Should never happen, but can arise when developing this code.
		// The user can compile the output to see the error.
		log.Printf("warning: internal error: invalid Go generated: %s", err)
		log.Printf("warning: compile the package to analyze the error")
		return g.buf.Bytes()
	}
	return src
}

// genDecl processes one declaration clause.
func (f *File) genDecl(node ast.Node) bool {
	decl, ok := node.(*ast.FuncDecl)
	if !ok {
		// We only care about func declarations.
		return true
	}
	if decl.Name.Name == f.funcDefinition.Name {
		if len(decl.Type.Params.List) == 0 {
			log.Printf("%s should take at least one parameter, found %d instead", f.funcDefinition.Name, len(decl.Type.Params.List))
			return false
		}
		print("found it !")

		ok := f.funcDefinition.Parse(decl.Type.Params.List)

		f.found = ok
	}
	return false
}

// buildFunc generates an handler func
func (g *Generator) buildFunc(fd FuncDefinition) {
	funcMap := template.FuncMap{
		"ToLower": strings.ToLower,
	}

	t := template.Must(template.New("handler").Funcs(funcMap).Parse(handlerWrap))

	err := t.Execute(&g.buf, fd)
	checkError(err)
}

const handlerWrap = `
func {{.Name}}Handler(w http.ResponseWriter, r *http.Request) {
//:D
}
`

const utilFuncs = `
func HandleHttpErrorWithDefaultStatus(w http.ResponseWriter, status int, err error) {
    type HttpError interface {
    	HttpError() (error string, code int)
    }
    type SelfHttpError interface {
    	HttpError(w http.ResponseWriter)
    }
    switch t := err.(type) {
    default:
    	w.WriteHeader(status)
    case HttpError:
    	err, code := t.HttpError()
    	http.Error(w, err, code)
    case SelfHttpError:
    	t.HttpError(w)
    }
}

func HandleHttpResponse(w http.ResponseWriter, r *http.Request, resp interface{}) {
    switch t := resp.(type) {
    default:
    	// I don't know that type !
    case http.Handler:
    	t.ServeHTTP(w, r)
    case []byte:
    	w.Write(t)
    }
}
`

func checkError(err error) {
	if err != nil {
		fmt.Println("Fatal error ", err.Error())
		os.Exit(1)
	}
}
<|endoftext|>


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

package main

import (
	_ "expvar" // For HTTP server registration
	"flag"
	"fmt"
	"net/http"
	"os"

	"time"

	"github.com/golang/glog"
	"github.com/google/trillian"
	"github.com/google/trillian/examples/ct"
	"github.com/google/trillian/util"
	"google.golang.org/grpc"
)

// Global flags that affect all log instances.
var serverHostFlag = flag.String("host", "localhost", "Address to serve CT log requests on")
var serverPortFlag = flag.Int("port", 6962, "Port to serve CT log requests on")
var rpcBackendFlag = flag.String("log_rpc_server", "localhost:8090", "Backend Log RPC servers to use, comma separated")
var rpcDeadlineFlag = flag.Duration("rpc_deadline", time.Second*10, "Deadline for backend RPC requests")
var logConfigFlag = flag.String("log_config", "", "File holding log config in JSON")
var maxGetEntriesFlag = flag.Int64("maxGetEntriesAllowed", 0, "Max number of entries we allow in a get-entries request (default 50)")

func main() {
	flag.Parse()

	if *maxGetEntriesFlag > 0 {
		ct.MaxGetEntriesAllowed = *maxGetEntriesFlag
	}

	// Get log config from file before we start.
	cfg, err := ct.LogConfigFromFile(*logConfigFlag)
	if err != nil {
		glog.Exitf("Failed to read log config: %v", err)
	}

	glog.CopyStandardLogTo("WARNING")
	glog.Info("**** CT HTTP Server Starting ****")

	// TODO(Martin2112): Support TLS and other stuff for RPC client and http server, this is just to
	// get started. Uses a blocking connection so we don't start serving before we're connected
	// to backend.
	var res util.FixedBackendResolver
	bal := grpc.RoundRobin(res)
	conn, err := grpc.Dial(*rpcBackendFlag, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithBalancer(bal))
	if err != nil {
		glog.Exitf("Could not connect to rpc server: %v", err)
	}
	defer conn.Close()
	client := trillian.NewTrillianLogClient(conn)

	for _, c := range cfg {
		handlers, err := c.SetUpInstance(client, *rpcDeadlineFlag)
		if err != nil {
			glog.Exitf("Failed to set up log instance for %+v: %v", cfg, err)
		}
		for path, handler := range *handlers {
			http.Handle(path, handler)
		}
	}

	// Bring up the HTTP server and serve until we get a signal not to.
	go util.AwaitSignal(func() {
		os.Exit(1)
	})
	server := http.Server{Addr: fmt.Sprintf("%s:%d", *serverHostFlag, *serverPortFlag), Handler: nil}
	err = server.ListenAndServe()
	glog.Warningf("Server exited: %v", err)
	glog.Flush()
}
<commit_msg>examples/ct: look for log servers via etcd<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	_ "expvar" // For HTTP server registration
	"flag"
	"fmt"
	"net/http"
	"os"
	"strings"

	"time"

	"github.com/coreos/etcd/clientv3"
	etcdnaming "github.com/coreos/etcd/clientv3/naming"
	"github.com/golang/glog"
	"github.com/google/trillian"
	"github.com/google/trillian/examples/ct"
	"github.com/google/trillian/util"
	"google.golang.org/grpc"
	"google.golang.org/grpc/naming"
)

// Global flags that affect all log instances.
var (
	serverHostFlag    = flag.String("host", "localhost", "Address to serve CT log requests on")
	serverPortFlag    = flag.Int("port", 6962, "Port to serve CT log requests on")
	rpcBackendFlag    = flag.String("log_rpc_server", "localhost:8090", "Backend specification; comma-separated list or etcd service name (if --etcd_servers specified)")
	rpcDeadlineFlag   = flag.Duration("rpc_deadline", time.Second*10, "Deadline for backend RPC requests")
	logConfigFlag     = flag.String("log_config", "", "File holding log config in JSON")
	maxGetEntriesFlag = flag.Int64("maxGetEntriesAllowed", 0, "Max number of entries we allow in a get-entries request (default 50)")
	etcdServers       = flag.String("etcd_servers", "", "A comma-separated list of etcd servers")
)

func main() {
	flag.Parse()

	if *maxGetEntriesFlag > 0 {
		ct.MaxGetEntriesAllowed = *maxGetEntriesFlag
	}

	// Get log config from file before we start.
	cfg, err := ct.LogConfigFromFile(*logConfigFlag)
	if err != nil {
		glog.Exitf("Failed to read log config: %v", err)
	}

	glog.CopyStandardLogTo("WARNING")
	glog.Info("**** CT HTTP Server Starting ****")

	// TODO(Martin2112): Support TLS and other stuff for RPC client and http server, this is just to
	// get started. Uses a blocking connection so we don't start serving before we're connected
	// to backend.
	var res naming.Resolver
	if len(*etcdServers) > 0 {
		// Use etcd to provide endpoint resolution.
		cfg := clientv3.Config{Endpoints: strings.Split(*etcdServers, ","), DialTimeout: 5 * time.Second}
		client, err := clientv3.New(cfg)
		if err != nil {
			glog.Exitf("Failed to connect to etcd at %v: %v", *etcdServers, err)
		}
		res = &etcdnaming.GRPCResolver{Client: client}
	} else {
		// Use a fixed endpoint resolution that just returns the addresses configured on the command line.
		res = util.FixedBackendResolver{}
	}
	bal := grpc.RoundRobin(res)
	conn, err := grpc.Dial(*rpcBackendFlag, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithBalancer(bal))
	if err != nil {
		glog.Exitf("Could not connect to rpc server: %v", err)
	}
	defer conn.Close()
	client := trillian.NewTrillianLogClient(conn)

	for _, c := range cfg {
		handlers, err := c.SetUpInstance(client, *rpcDeadlineFlag)
		if err != nil {
			glog.Exitf("Failed to set up log instance for %+v: %v", cfg, err)
		}
		for path, handler := range *handlers {
			http.Handle(path, handler)
		}
	}

	// Bring up the HTTP server and serve until we get a signal not to.
	go util.AwaitSignal(func() {
		os.Exit(1)
	})
	server := http.Server{Addr: fmt.Sprintf("%s:%d", *serverHostFlag, *serverPortFlag), Handler: nil}
	err = server.ListenAndServe()
	glog.Warningf("Server exited: %v", err)
	glog.Flush()
}
<|endoftext|>


<commit_before>package atmel8

import (
	"errors"
	"fmt"
)

var (
	ErrLDILowRegister        = errors.New("ldi can only use a high register (r16 - r31)")
	ErrLDIInvalidRegisterStr = "R%v is not a valid register"
)

type CPU struct {
	registers []uint8
	rMin      int
	rMax      int
}

func NewCPU() *CPU {
	return &CPU{
		registers: make([]uint8, 16, 16),
		rMin:      16,
		rMax:      31,
	}
}

// LDI is 'Load Immediate'; it loads given value into given register.
// rIndex must be: 16 <= d <= 31.
func (c *CPU) LDI(rIndex int, value uint8) error {
	if rIndex < 16 {
		return ErrLDILowRegister
	}

	if rIndex > 31 {
		return fmt.Errorf(ErrLDIInvalidRegisterStr, rIndex)
	}

	c.registers[rIndex-c.rMin] = value

	return nil
}
<commit_msg>use CPU#rMin, rMax instead of hardcoded values in CPU#LDI<commit_after>package atmel8

import (
	"errors"
	"fmt"
)

var (
	ErrLDILowRegister        = errors.New("ldi can only use a high register (r16 - r31)")
	ErrLDIInvalidRegisterStr = "R%v is not a valid register"
)

type CPU struct {
	registers []uint8
	rMin      int
	rMax      int
}

func NewCPU() *CPU {
	return &CPU{
		registers: make([]uint8, 16, 16),
		rMin:      16,
		rMax:      31,
	}
}

// LDI is 'Load Immediate'; it loads given value into given register.
// rIndex must be: 16 <= d <= 31.
func (c *CPU) LDI(rIndex int, value uint8) error {
	if rIndex < c.rMin {
		return ErrLDILowRegister
	}

	if rIndex > c.rMax {
		return fmt.Errorf(ErrLDIInvalidRegisterStr, rIndex)
	}

	c.registers[rIndex-c.rMin] = value

	return nil
}
<|endoftext|>


<commit_before>package gocommend

import "github.com/garyburd/redigo/redis"

// output, get data what you want
type Output struct {
	recNum int
	cSet   collectionSet
}

// init the params and set the cSet
func (this *Output) Init(collection string, recNum int) error {
	if collection == "" {
		return gocommendError{emptyCollection}
	}
	this.recNum = recNum
	this.cSet = collectionSet{}
	this.cSet.init(collection)
	return nil
}

// convert interface slice to string slice
func (this *Output) toStrings(arrayInterface []interface{}) (strings []string) {
	for _, rs := range arrayInterface {
		s, _ := redis.String(rs, nil)
		strings = append(strings, s)
	}
	return
}

// get recommend items for user
func (this *Output) RecommendedItemForUser(userId string) ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.recommendedItem(userId), 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

// get recommend items by item similarty
func (this *Output) RecommendedItemForItem(itemId string) ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.itemSimilarity(itemId), 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

// get recommend items by item similarty except users had rated
// todo: zrange return set ?
func (this *Output) RecommendedItemForItemByUser(itemId string, userId string) ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.itemSimilarity(itemId), 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

// get the best rated items
func (this *Output) BestRated() ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.scoreRank, 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

func (this *Output) MostLiked() ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.mostLiked, 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

func (this *Output) MostSimilarUsers(userId string) ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.userSimilarity(userId), 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

//func (this *Output) MostSimilarItems(itemId string) ([]string, error) {
//	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.userSimilarity(itemId), 0, this.recNum))
//	if err != nil {
//		return nil, err
//	}
//	return this.toStrings(arrayInterface), err
//}
<commit_msg>edit output<commit_after>package gocommend

import "github.com/garyburd/redigo/redis"

// output, get data what you want
type Output struct {
	recNum int
	cSet   collectionSet
}

// init the params and set the cSet
func (this *Output) Init(collection string, recNum int) error {
	if collection == "" {
		return gocommendError{emptyCollection}
	}
	this.recNum = recNum
	this.cSet = collectionSet{}
	this.cSet.init(collection)
	return nil
}

// convert interface slice to string slice
func (this *Output) toStrings(arrayInterface []interface{}) (strings []string) {
	for _, rs := range arrayInterface {
		s, _ := redis.String(rs, nil)
		strings = append(strings, s)
	}
	return
}

// get recommend items for user
func (this *Output) RecommendedItemForUser(userId string) ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.recommendedItem(userId), 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

// get recommend items by item similarty
func (this *Output) RecommendedItemForItem(itemId string) ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.itemSimilarity(itemId), 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

// get the best rated items
func (this *Output) BestRated() ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.scoreRank, 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

func (this *Output) MostLiked() ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.mostLiked, 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}

func (this *Output) MostSimilarUsers(userId string) ([]string, error) {
	arrayInterface, err := redis.Values(redisClient.Do("ZREVRANGE", this.cSet.userSimilarity(userId), 0, this.recNum))
	if err != nil {
		return nil, err
	}
	return this.toStrings(arrayInterface), err
}
<|endoftext|>


<commit_before>package revel

import (
	"mime/multipart"
	"net/url"
	"os"
	"reflect"

	"github.com/hongrich/glog"
)

// Params provides a unified view of the request params.
// Includes:
// - URL query string
// - Form values
// - File uploads
//
// Warning: param maps other than Values may be nil if there were none.
type Params struct {
	url.Values // A unified view of all the individual param maps below.

	// Set by the router
	Fixed url.Values // Fixed parameters from the route, e.g. App.Action("fixed param")
	Route url.Values // Parameters extracted from the route,  e.g. /customers/{id}

	// Set by the ParamsFilter
	Query url.Values // Parameters from the query string, e.g. /index?limit=10
	Form  url.Values // Parameters from the request body.

	Files    map[string][]*multipart.FileHeader // Files uploaded in a multipart form
	tmpFiles []*os.File                         // Temp files used during the request.
}

func ParseParams(params *Params, req *Request) {
	params.Query = req.URL.Query()

	// Parse the body depending on the content type.
	switch req.ContentType {
	case "application/x-www-form-urlencoded":
		// Typical form.
		if err := req.ParseForm(); err != nil {
			glog.Warningln("Error parsing request body:", err)
		} else {
			params.Form = req.Form
		}

	case "multipart/form-data":
		// Multipart form.
		// TODO: Extract the multipart form param so app can set it.
		if err := req.ParseMultipartForm(32 << 20 /* 32 MB */); err != nil {
			glog.Warningln("Error parsing request body:", err)
		} else {
			params.Form = req.MultipartForm.Value
			params.Files = req.MultipartForm.File
		}
	}

	params.Values = params.calcValues()
}

// Bind looks for the named parameter, converts it to the requested type, and
// writes it into "dest", which must be settable.  If the value can not be
// parsed, "dest" is set to the zero value.
func (p *Params) Bind(dest interface{}, name string) {
	value := reflect.ValueOf(dest)
	if value.Kind() != reflect.Ptr {
		panic("revel/params: non-pointer passed to Bind: " + name)
	}
	value = value.Elem()
	if !value.CanSet() {
		panic("revel/params: non-settable variable passed to Bind: " + name)
	}
	value.Set(Bind(p, name, value.Type()))
}

// calcValues returns a unified view of the component param maps.
func (p *Params) calcValues() url.Values {
	numParams := len(p.Query) + len(p.Fixed) + len(p.Route) + len(p.Form)

	// If there were no params, return an empty map.
	if numParams == 0 {
		return make(url.Values, 0)
	}

	// If only one of the param sources has anything, return that directly.
	switch numParams {
	case len(p.Query):
		return p.Query
	case len(p.Route):
		return p.Route
	case len(p.Fixed):
		return p.Fixed
	case len(p.Form):
		return p.Form
	}

	// Copy everything into the same map.
	values := make(url.Values, numParams)
	for k, v := range p.Query {
		values[k] = append(values[k], v...)
	}
	for k, v := range p.Fixed {
		values[k] = append(values[k], v...)
	}
	for k, v := range p.Route {
		values[k] = append(values[k], v...)
	}
	for k, v := range p.Form {
		values[k] = append(values[k], v...)
	}
	return values
}

func ParamsFilter(c *Controller, fc []Filter) {
	ParseParams(c.Params, c.Request)

	// Clean up from the request.
	defer func() {
		// Delete temp files.
		if c.Request.MultipartForm != nil {
			err := c.Request.MultipartForm.RemoveAll()
			if err != nil {
				glog.Warningln("Error removing temporary files:", err)
			}
		}

		for _, tmpFile := range c.Params.tmpFiles {
			err := os.Remove(tmpFile.Name())
			if err != nil {
				glog.Warningln("Could not remove upload temp file:", err)
			}
		}
	}()

	fc[0](c, fc[1:])
}
<commit_msg>Fix file access security issue<commit_after>package revel

import (
	"mime/multipart"
	"net/url"
	"os"
	"reflect"

	"github.com/hongrich/glog"
)

// Params provides a unified view of the request params.
// Includes:
// - URL query string
// - Form values
// - File uploads
//
// Warning: param maps other than Values may be nil if there were none.
type Params struct {
	url.Values // A unified view of all the individual param maps below.

	// Set by the router
	Fixed url.Values // Fixed parameters from the route, e.g. App.Action("fixed param")
	Route url.Values // Parameters extracted from the route,  e.g. /customers/{id}

	// Set by the ParamsFilter
	Query url.Values // Parameters from the query string, e.g. /index?limit=10
	Form  url.Values // Parameters from the request body.

	Files    map[string][]*multipart.FileHeader // Files uploaded in a multipart form
	tmpFiles []*os.File                         // Temp files used during the request.
}

func ParseParams(params *Params, req *Request) {
	params.Query = req.URL.Query()

	// Parse the body depending on the content type.
	switch req.ContentType {
	case "application/x-www-form-urlencoded":
		// Typical form.
		if err := req.ParseForm(); err != nil {
			glog.Warningln("Error parsing request body:", err)
		} else {
			params.Form = req.Form
		}

	case "multipart/form-data":
		// Multipart form.
		// TODO: Extract the multipart form param so app can set it.
		if err := req.ParseMultipartForm(32 << 20 /* 32 MB */); err != nil {
			glog.Warningln("Error parsing request body:", err)
		} else {
			params.Form = req.MultipartForm.Value
			params.Files = req.MultipartForm.File
		}
	}

	params.Values = params.calcValues()
}

// Bind looks for the named parameter, converts it to the requested type, and
// writes it into "dest", which must be settable.  If the value can not be
// parsed, "dest" is set to the zero value.
func (p *Params) Bind(dest interface{}, name string) {
	value := reflect.ValueOf(dest)
	if value.Kind() != reflect.Ptr {
		panic("revel/params: non-pointer passed to Bind: " + name)
	}
	value = value.Elem()
	if !value.CanSet() {
		panic("revel/params: non-settable variable passed to Bind: " + name)
	}
	value.Set(Bind(p, name, value.Type()))
}

// calcValues returns a unified view of the component param maps.
func (p *Params) calcValues() url.Values {
	numParams := len(p.Query) + len(p.Fixed) + len(p.Route) + len(p.Form)

	// If there were no params, return an empty map.
	if numParams == 0 {
		return make(url.Values, 0)
	}

	// If only one of the param sources has anything, return that directly.
	switch numParams {
	case len(p.Query):
		return p.Query
	case len(p.Route):
		return p.Route
	case len(p.Fixed):
		return p.Fixed
	case len(p.Form):
		return p.Form
	}

	// Copy everything into the same map.
	values := make(url.Values, numParams)
	for k, v := range p.Fixed {
		values[k] = append(values[k], v...)
	}
	for k, v := range p.Query {
		values[k] = append(values[k], v...)
	}
	for k, v := range p.Route {
		values[k] = append(values[k], v...)
	}
	for k, v := range p.Form {
		values[k] = append(values[k], v...)
	}
	return values
}

func ParamsFilter(c *Controller, fc []Filter) {
	ParseParams(c.Params, c.Request)

	// Clean up from the request.
	defer func() {
		// Delete temp files.
		if c.Request.MultipartForm != nil {
			err := c.Request.MultipartForm.RemoveAll()
			if err != nil {
				glog.Warningln("Error removing temporary files:", err)
			}
		}

		for _, tmpFile := range c.Params.tmpFiles {
			err := os.Remove(tmpFile.Name())
			if err != nil {
				glog.Warningln("Could not remove upload temp file:", err)
			}
		}
	}()

	fc[0](c, fc[1:])
}
<|endoftext|>


<commit_before>package forge

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"

	"github.com/brettlangdon/forge/token"
)

// Parser is a struct to hold data necessary for parsing a config from a scanner
type Parser struct {
	files      []string
	settings   *Section
	scanner    *Scanner
	curTok     token.Token
	curSection *Section
	previous   []*Section
}

// NewParser will create and initialize a new Parser from a provided io.Reader
func NewParser(reader io.Reader) *Parser {
	settings := NewSection()
	return &Parser{
		files:      make([]string, 0),
		scanner:    NewScanner(reader),
		settings:   settings,
		curSection: settings,
		previous:   make([]*Section, 0),
	}
}

// NewFileParser will create and initialize a new Parser from a provided from a filename string
func NewFileParser(filename string) (*Parser, error) {
	reader, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	parser := NewParser(reader)
	parser.addFile(filename)
	return parser, nil
}

func (parser *Parser) addFile(filename string) {
	parser.files = append(parser.files, filename)
}

func (parser *Parser) hasParsed(search string) bool {
	for _, filename := range parser.files {
		if filename == search {
			return true
		}
	}
	return false
}

func (parser *Parser) syntaxError(msg string) error {
	msg = fmt.Sprintf(
		"syntax error line <%d> column <%d>: %s",
		parser.curTok.Line,
		parser.curTok.Column,
		msg,
	)
	return errors.New(msg)
}

func (parser *Parser) readToken() token.Token {
	parser.curTok = parser.scanner.NextToken()
	return parser.curTok
}

func (parser *Parser) parseReference(startingSection *Section, period bool) (Value, error) {
	name := ""
	if period == false {
		name = parser.curTok.Literal
	}
	for {
		parser.readToken()
		if parser.curTok.ID == token.PERIOD && period == false {
			period = true
		} else if period && parser.curTok.ID == token.IDENTIFIER {
			if len(name) > 0 {
				name += "."
			}
			name += parser.curTok.Literal
			period = false
		} else if parser.curTok.ID == token.SEMICOLON {
			break
		} else {
			msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal)
			return nil, parser.syntaxError(msg)
		}
	}
	if len(name) == 0 {
		return nil, parser.syntaxError(
			fmt.Sprintf("expected IDENTIFIER instead found %s", parser.curTok.Literal),
		)
	}

	if period {
		return nil, parser.syntaxError(fmt.Sprintf("expected IDENTIFIER after PERIOD"))
	}

	value, err := startingSection.Resolve(name)
	if err != nil {
		err = errors.New("reference error, " + err.Error())
	}
	return value, nil
}

func (parser *Parser) parseSetting(name string) error {
	var value Value
	parser.readToken()

	readNext := true
	switch parser.curTok.ID {
	case token.STRING:
		value = NewString(parser.curTok.Literal)
	case token.BOOLEAN:
		boolVal, err := strconv.ParseBool(parser.curTok.Literal)
		if err != nil {
			return nil
		}
		value = NewBoolean(boolVal)
	case token.NULL:
		value = NewNull()
	case token.INTEGER:
		intVal, err := strconv.ParseInt(parser.curTok.Literal, 10, 64)
		if err != nil {
			return err
		}
		value = NewInteger(intVal)
	case token.FLOAT:
		floatVal, err := strconv.ParseFloat(parser.curTok.Literal, 64)
		if err != nil {
			return err
		}
		value = NewFloat(floatVal)
	case token.PERIOD:
		reference, err := parser.parseReference(parser.curSection, true)
		if err != nil {
			return err
		}
		value = reference
		readNext = false
	case token.IDENTIFIER:
		reference, err := parser.parseReference(parser.settings, false)
		if err != nil {
			return err
		}
		value = reference
		readNext = false
	default:
		return parser.syntaxError(
			fmt.Sprintf("expected STRING, INTEGER, FLOAT, BOOLEAN or IDENTIFIER, instead found %s", parser.curTok.ID),
		)
	}

	if readNext {
		parser.readToken()
	}
	if parser.curTok.ID != token.SEMICOLON {
		msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal)
		return parser.syntaxError(msg)
	}
	parser.readToken()

	parser.curSection.Set(name, value)
	return nil
}

func (parser *Parser) parseInclude() error {
	if parser.curTok.ID != token.STRING {
		msg := fmt.Sprintf("expected STRING instead found '%s'", parser.curTok.ID)
		return parser.syntaxError(msg)
	}
	pattern := parser.curTok.Literal

	parser.readToken()
	if parser.curTok.ID != token.SEMICOLON {
		msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal)
		return parser.syntaxError(msg)
	}

	filenames, err := filepath.Glob(pattern)
	if err != nil {
		return err
	}
	oldScanner := parser.scanner
	for _, filename := range filenames {
		// We have already visited this file, don't include again
		// DEV: This can cause recursive includes if this isn't here :o
		if parser.hasParsed(filename) {
			continue
		}
		reader, err := os.Open(filename)
		if err != nil {
			return err
		}
		parser.curSection.AddInclude(filename)
		parser.scanner = NewScanner(reader)
		parser.parse()
		// Make sure to add the filename to the internal list to ensure we don't
		// accidentally recursively include config files
		parser.addFile(filename)
	}
	parser.scanner = oldScanner
	parser.readToken()
	return nil
}

func (parser *Parser) parseSection(name string) error {
	section := parser.curSection.AddSection(name)
	parser.previous = append(parser.previous, parser.curSection)
	parser.curSection = section
	return nil
}

func (parser *Parser) endSection() error {
	if len(parser.previous) == 0 {
		return parser.syntaxError("unexpected section end '}'")
	}

	pLen := len(parser.previous)
	previous := parser.previous[pLen-1]
	parser.previous = parser.previous[0 : pLen-1]
	parser.curSection = previous
	return nil
}

func (parser *Parser) parse() error {
	parser.readToken()
	for {
		if parser.curTok.ID == token.EOF {
			break
		}
		tok := parser.curTok
		parser.readToken()
		switch tok.ID {
		case token.COMMENT:
			parser.curSection.AddComment(tok.Literal)
		case token.INCLUDE:
			parser.parseInclude()
		case token.IDENTIFIER:
			if parser.curTok.ID == token.LBRACKET {
				err := parser.parseSection(tok.Literal)
				if err != nil {
					return err
				}
				parser.readToken()
			} else if parser.curTok.ID == token.EQUAL {
				err := parser.parseSetting(tok.Literal)
				if err != nil {
					return err
				}
			}
		case token.RBRACKET:
			err := parser.endSection()
			if err != nil {
				return err
			}
		default:
			return parser.syntaxError(fmt.Sprintf("unexpected token %s", tok))
		}
	}
	return nil
}

// GetSettings will fetch the parsed settings from this Parser
func (parser *Parser) GetSettings() *Section {
	return parser.settings
}

// Parse will tell the Parser to parse all settings from the config
func (parser *Parser) Parse() error {
	err := parser.parse()
	if err != nil {
		return err
	}

	if len(parser.previous) > 0 {
		return parser.syntaxError("expected end of section, instead found EOF")
	}

	return nil
}
<commit_msg>make sure to close open files<commit_after>package forge

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"

	"github.com/brettlangdon/forge/token"
)

// Parser is a struct to hold data necessary for parsing a config from a scanner
type Parser struct {
	files      []string
	settings   *Section
	scanner    *Scanner
	curTok     token.Token
	curSection *Section
	previous   []*Section
}

// NewParser will create and initialize a new Parser from a provided io.Reader
func NewParser(reader io.Reader) *Parser {
	settings := NewSection()
	return &Parser{
		files:      make([]string, 0),
		scanner:    NewScanner(reader),
		settings:   settings,
		curSection: settings,
		previous:   make([]*Section, 0),
	}
}

// NewFileParser will create and initialize a new Parser from a provided from a filename string
func NewFileParser(filename string) (*Parser, error) {
	reader, err := os.Open(filename)
	defer reader.Close()
	if err != nil {
		return nil, err
	}
	parser := NewParser(reader)
	parser.addFile(filename)
	return parser, nil
}

func (parser *Parser) addFile(filename string) {
	parser.files = append(parser.files, filename)
}

func (parser *Parser) hasParsed(search string) bool {
	for _, filename := range parser.files {
		if filename == search {
			return true
		}
	}
	return false
}

func (parser *Parser) syntaxError(msg string) error {
	msg = fmt.Sprintf(
		"syntax error line <%d> column <%d>: %s",
		parser.curTok.Line,
		parser.curTok.Column,
		msg,
	)
	return errors.New(msg)
}

func (parser *Parser) readToken() token.Token {
	parser.curTok = parser.scanner.NextToken()
	return parser.curTok
}

func (parser *Parser) parseReference(startingSection *Section, period bool) (Value, error) {
	name := ""
	if period == false {
		name = parser.curTok.Literal
	}
	for {
		parser.readToken()
		if parser.curTok.ID == token.PERIOD && period == false {
			period = true
		} else if period && parser.curTok.ID == token.IDENTIFIER {
			if len(name) > 0 {
				name += "."
			}
			name += parser.curTok.Literal
			period = false
		} else if parser.curTok.ID == token.SEMICOLON {
			break
		} else {
			msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal)
			return nil, parser.syntaxError(msg)
		}
	}
	if len(name) == 0 {
		return nil, parser.syntaxError(
			fmt.Sprintf("expected IDENTIFIER instead found %s", parser.curTok.Literal),
		)
	}

	if period {
		return nil, parser.syntaxError(fmt.Sprintf("expected IDENTIFIER after PERIOD"))
	}

	value, err := startingSection.Resolve(name)
	if err != nil {
		err = errors.New("reference error, " + err.Error())
	}
	return value, nil
}

func (parser *Parser) parseSetting(name string) error {
	var value Value
	parser.readToken()

	readNext := true
	switch parser.curTok.ID {
	case token.STRING:
		value = NewString(parser.curTok.Literal)
	case token.BOOLEAN:
		boolVal, err := strconv.ParseBool(parser.curTok.Literal)
		if err != nil {
			return nil
		}
		value = NewBoolean(boolVal)
	case token.NULL:
		value = NewNull()
	case token.INTEGER:
		intVal, err := strconv.ParseInt(parser.curTok.Literal, 10, 64)
		if err != nil {
			return err
		}
		value = NewInteger(intVal)
	case token.FLOAT:
		floatVal, err := strconv.ParseFloat(parser.curTok.Literal, 64)
		if err != nil {
			return err
		}
		value = NewFloat(floatVal)
	case token.PERIOD:
		reference, err := parser.parseReference(parser.curSection, true)
		if err != nil {
			return err
		}
		value = reference
		readNext = false
	case token.IDENTIFIER:
		reference, err := parser.parseReference(parser.settings, false)
		if err != nil {
			return err
		}
		value = reference
		readNext = false
	default:
		return parser.syntaxError(
			fmt.Sprintf("expected STRING, INTEGER, FLOAT, BOOLEAN or IDENTIFIER, instead found %s", parser.curTok.ID),
		)
	}

	if readNext {
		parser.readToken()
	}
	if parser.curTok.ID != token.SEMICOLON {
		msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal)
		return parser.syntaxError(msg)
	}
	parser.readToken()

	parser.curSection.Set(name, value)
	return nil
}

func (parser *Parser) parseInclude() error {
	if parser.curTok.ID != token.STRING {
		msg := fmt.Sprintf("expected STRING instead found '%s'", parser.curTok.ID)
		return parser.syntaxError(msg)
	}
	pattern := parser.curTok.Literal

	parser.readToken()
	if parser.curTok.ID != token.SEMICOLON {
		msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal)
		return parser.syntaxError(msg)
	}

	filenames, err := filepath.Glob(pattern)
	if err != nil {
		return err
	}
	oldScanner := parser.scanner
	for _, filename := range filenames {
		// We have already visited this file, don't include again
		// DEV: This can cause recursive includes if this isn't here :o
		if parser.hasParsed(filename) {
			continue
		}
		reader, err := os.Open(filename)
		defer reader.Close()
		if err != nil {
			return err
		}
		parser.curSection.AddInclude(filename)
		parser.scanner = NewScanner(reader)
		parser.parse()
		// Make sure to add the filename to the internal list to ensure we don't
		// accidentally recursively include config files
		parser.addFile(filename)
	}
	parser.scanner = oldScanner
	parser.readToken()
	return nil
}

func (parser *Parser) parseSection(name string) error {
	section := parser.curSection.AddSection(name)
	parser.previous = append(parser.previous, parser.curSection)
	parser.curSection = section
	return nil
}

func (parser *Parser) endSection() error {
	if len(parser.previous) == 0 {
		return parser.syntaxError("unexpected section end '}'")
	}

	pLen := len(parser.previous)
	previous := parser.previous[pLen-1]
	parser.previous = parser.previous[0 : pLen-1]
	parser.curSection = previous
	return nil
}

func (parser *Parser) parse() error {
	parser.readToken()
	for {
		if parser.curTok.ID == token.EOF {
			break
		}
		tok := parser.curTok
		parser.readToken()
		switch tok.ID {
		case token.COMMENT:
			parser.curSection.AddComment(tok.Literal)
		case token.INCLUDE:
			parser.parseInclude()
		case token.IDENTIFIER:
			if parser.curTok.ID == token.LBRACKET {
				err := parser.parseSection(tok.Literal)
				if err != nil {
					return err
				}
				parser.readToken()
			} else if parser.curTok.ID == token.EQUAL {
				err := parser.parseSetting(tok.Literal)
				if err != nil {
					return err
				}
			}
		case token.RBRACKET:
			err := parser.endSection()
			if err != nil {
				return err
			}
		default:
			return parser.syntaxError(fmt.Sprintf("unexpected token %s", tok))
		}
	}
	return nil
}

// GetSettings will fetch the parsed settings from this Parser
func (parser *Parser) GetSettings() *Section {
	return parser.settings
}

// Parse will tell the Parser to parse all settings from the config
func (parser *Parser) Parse() error {
	err := parser.parse()
	if err != nil {
		return err
	}

	if len(parser.previous) > 0 {
		return parser.syntaxError("expected end of section, instead found EOF")
	}

	return nil
}
<|endoftext|>