<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package lang

import (
	"context"
	"fmt"
	"log"

	"os"

	"encoding/json"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/api/infoGatherer"
	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/gauge"
	gm "github.com/getgauge/gauge/gauge_messages"
	"github.com/getgauge/gauge/logger"
	"github.com/getgauge/gauge/util"
	"github.com/op/go-logging"
	"github.com/sourcegraph/go-langserver/pkg/lsp"
	"github.com/sourcegraph/jsonrpc2"
)

type server struct{}

type infoProvider interface {
	Init()
	Steps() []*gauge.Step
	AllSteps() []*gauge.Step
	Concepts() []*gm.ConceptInfo
	Params(file string, argType gauge.ArgType) []gauge.StepArg
	Tags() []string
	SearchConceptDictionary(string) *gauge.Concept
	GetAvailableSpecDetails(specs []string) []*infoGatherer.SpecDetail
}

var provider infoProvider

func Server(p infoProvider) *server {
	provider = p
	provider.Init()
	return &server{}
}

type lspHandler struct {
	jsonrpc2.Handler
}

type LangHandler struct {
}

type registrationParams struct {
	Registrations []registration `json:"registrations"`
}

type registration struct {
	Id              string      `json:"id"`
	Method          string      `json:"method"`
	RegisterOptions interface{} `json:"registerOptions"`
}

type textDocumentRegistrationOptions struct {
	DocumentSelector documentSelector `json:"documentSelector"`
}

type textDocumentChangeRegistrationOptions struct {
	textDocumentRegistrationOptions
	SyncKind lsp.TextDocumentSyncKind `json:"syncKind,omitempty"`
}

type codeLensRegistrationOptions struct {
	textDocumentRegistrationOptions
	ResolveProvider bool `json:"resolveProvider,omitempty"`
}

type documentSelector struct {
	Scheme   string `json:"scheme"`
	Language string `json:"language"`
	Pattern  string `json:"pattern"`
}

func newHandler() jsonrpc2.Handler {
	return lspHandler{jsonrpc2.HandlerWithError((&LangHandler{}).handle)}
}

func (h lspHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	go h.Handler.Handle(ctx, conn, req)
}

func (h *LangHandler) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
	return h.Handle(ctx, conn, req)
}

func (h *LangHandler) Handle(ctx context.Context, conn jsonrpc2.JSONRPC2, req *jsonrpc2.Request) (interface{}, error) {
	switch req.Method {
	case "initialize":
		return gaugeLSPCapabilities(), nil
	case "initialized":
		registerRunnerCapabilities(conn, ctx)
		go publishDiagnostics(ctx, conn)
		return nil, nil
	case "shutdown":
		killRunner()
		return nil, nil
	case "exit":
		if c, ok := conn.(*jsonrpc2.Conn); ok {
			c.Close()
			os.Exit(0)
		}
		return nil, nil
	case "$/cancelRequest":
		return nil, nil
	case "textDocument/didOpen":
		return nil, documentOpened(req, ctx, conn)
	case "textDocument/didClose":
		return nil, documentClosed(req, ctx, conn)
	case "textDocument/didChange":
		return nil, documentChange(req, ctx, conn)
	case "textDocument/completion":
		return completion(req)
	case "completionItem/resolve":
		return resolveCompletion(req)
	case "textDocument/definition":
		return definition(req)
	case "textDocument/formatting":
		data, err := format(req)
		if err != nil {
			conn.Notify(ctx, "window/showMessage", lsp.ShowMessageParams{Type: 1, Message: err.Error()})
		}
		return data, err
	case "textDocument/codeLens":
		return codeLenses(req)
	case "textDocument/codeAction":
		return codeActions(req)
	case "textDocument/rename":
		return rename(req)
	case "textDocument/documentSymbol":
		return documentSymbols(req)
	case "workspace/symbol":
		return workspaceSymbols(req)
	case "gauge/stepReferences":
		return stepReferences(req)
	case "gauge/stepValueAt":
		return stepValueAt(req)
	case "gauge/scenarios":
		return scenarios(req)
	case "gauge/specs":
		return specs()
	default:
		return nil, nil
	}
}

func gaugeLSPCapabilities() lsp.InitializeResult {
	kind := lsp.TDSKFull
	return lsp.InitializeResult{
		Capabilities: lsp.ServerCapabilities{
			TextDocumentSync:           lsp.TextDocumentSyncOptionsOrKind{Kind: &kind, Options: &lsp.TextDocumentSyncOptions{Save: &lsp.SaveOptions{IncludeText: true}}},
			CompletionProvider:         &lsp.CompletionOptions{ResolveProvider: true, TriggerCharacters: []string{"*", "* ", "\"", "<", ":", ","}},
			DocumentFormattingProvider: true,
			CodeLensProvider:           &lsp.CodeLensOptions{ResolveProvider: false},
			DefinitionProvider:         true,
			CodeActionProvider:         true,
			DocumentSymbolProvider:     true,
			WorkspaceSymbolProvider:    true,
			RenameProvider:             true,
		},
	}
}

func documentOpened(req *jsonrpc2.Request, ctx context.Context, conn jsonrpc2.JSONRPC2) error {
	var params lsp.DidOpenTextDocumentParams
	var err error
	if err = json.Unmarshal(*req.Params, &params); err != nil {
		logger.APILog.Debugf("failed to parse request %s", err.Error())
		return err
	}
	if util.IsGaugeFile(params.TextDocument.URI) {
		openFile(params)
	} else if lRunner.runner != nil {
		err = cacheFileOnRunner(params.TextDocument.URI, params.TextDocument.Text)
	}
	go publishDiagnostics(ctx, conn)
	return err
}

func documentChange(req *jsonrpc2.Request, ctx context.Context, conn jsonrpc2.JSONRPC2) error {
	var params lsp.DidChangeTextDocumentParams
	var err error
	if err = json.Unmarshal(*req.Params, &params); err != nil {
		logger.APILog.Debugf("failed to parse request %s", err.Error())
		return err
	}
	if util.IsGaugeFile(params.TextDocument.URI) {
		changeFile(params)
	} else if lRunner.runner != nil {
		err = cacheFileOnRunner(params.TextDocument.URI, params.ContentChanges[0].Text)
	}
	go publishDiagnostics(ctx, conn)
	return err
}

func documentClosed(req *jsonrpc2.Request, ctx context.Context, conn jsonrpc2.JSONRPC2) error {
	var params lsp.DidCloseTextDocumentParams
	var err error
	if err := json.Unmarshal(*req.Params, &params); err != nil {
		logger.APILog.Debugf("failed to parse request %s", err.Error())
		return err
	}
	if util.IsGaugeFile(params.TextDocument.URI) {
		closeFile(params)
		if !common.FileExists(util.ConvertPathToURI(params.TextDocument.URI)) {
			publishDiagnostic(params.TextDocument.URI, []lsp.Diagnostic{}, conn, ctx)
		}
	} else if lRunner.runner != nil {
		cacheFileRequest := &gm.Message{MessageType: gm.Message_CacheFileRequest, CacheFileRequest: &gm.CacheFileRequest{FilePath: util.ConvertURItoFilePath(params.TextDocument.URI), IsClosed: true}}
		err = sendMessageToRunner(cacheFileRequest)
	}
	go publishDiagnostics(ctx, conn)
	return err
}

func registerRunnerCapabilities(conn jsonrpc2.JSONRPC2, ctx context.Context) {
	var result string
	id, err := getLanugeIdetifier()
	if err != nil || id == "" {
		return
	}
	startRunner()
	ds := documentSelector{"file", id, fmt.Sprintf("%s/*/**", config.ProjectRoot)}
	conn.Call(ctx, "client/registerCapability", registrationParams{[]registration{
		{Id: "gauge-runner-didOpen", Method: "textDocument/didOpen", RegisterOptions: textDocumentRegistrationOptions{DocumentSelector: ds}},
		{Id: "gauge-runner-didClose", Method: "textDocument/didClose", RegisterOptions: textDocumentRegistrationOptions{DocumentSelector: ds}},
		{Id: "gauge-runner-didChange", Method: "textDocument/didChange", RegisterOptions: textDocumentChangeRegistrationOptions{textDocumentRegistrationOptions: textDocumentRegistrationOptions{DocumentSelector: ds}, SyncKind: lsp.TDSKFull}},
		{Id: "gauge-runner-codelens", Method: "textDocument/codeLens", RegisterOptions: codeLensRegistrationOptions{textDocumentRegistrationOptions: textDocumentRegistrationOptions{DocumentSelector: ds}, ResolveProvider: false}},
	}}, result)
}

type lspWriter struct {
}

func (w lspWriter) Write(p []byte) (n int, err error) {
	logger.LspLog.Debug(string(p))
	return os.Stderr.Write(p)
}

func (s *server) Start(logLevel string) {
	logger.APILog.Info("LangServer: reading on stdin, writing on stdout")
	var connOpt []jsonrpc2.ConnOpt
	if logLevel == "debug" {
		connOpt = append(connOpt, jsonrpc2.LogMessages(log.New(lspWriter{}, "", 0)))
	}
	ctx := context.Background()
	conn := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(stdRWC{}, jsonrpc2.VSCodeObjectCodec{}), newHandler(), connOpt...)
	logger.SetCustomLogger(lspLogger{conn, ctx})
	<-conn.DisconnectNotify()
	logger.APILog.Info("Connection closed")
}

type stdRWC struct{}

func (stdRWC) Read(p []byte) (int, error) {
	return os.Stdin.Read(p)
}

func (stdRWC) Write(p []byte) (int, error) {
	return os.Stdout.Write(p)
}

func (stdRWC) Close() error {
	if err := os.Stdin.Close(); err != nil {
		return err
	}
	return os.Stdout.Close()
}

type lspLogger struct {
	conn *jsonrpc2.Conn
	ctx  context.Context
}

func (c lspLogger) Log(logLevel logging.Level, msg string) {
	logger.APILog.Info(logLevel)
	var level lsp.MessageType
	switch logLevel {
	case logging.DEBUG:
		level = lsp.Log
	case logging.INFO:
		level = lsp.Info
	case logging.WARNING:
		level = lsp.MTWarning
	case logging.ERROR:
		level = lsp.MTError
	case logging.CRITICAL:
		level = lsp.MTError
	default:
		level = lsp.Info
	}
	c.conn.Notify(c.ctx, "window/logMessage", lsp.LogMessageParams{Type: level, Message: msg})
}
<commit_msg>Reverting the glob pattern temp fix as vscode has fixed it. getgauge/gauge-vscode#96<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package lang

import (
	"context"
	"fmt"
	"log"

	"os"

	"encoding/json"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/api/infoGatherer"
	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/gauge"
	gm "github.com/getgauge/gauge/gauge_messages"
	"github.com/getgauge/gauge/logger"
	"github.com/getgauge/gauge/util"
	"github.com/op/go-logging"
	"github.com/sourcegraph/go-langserver/pkg/lsp"
	"github.com/sourcegraph/jsonrpc2"
)

type server struct{}

type infoProvider interface {
	Init()
	Steps() []*gauge.Step
	AllSteps() []*gauge.Step
	Concepts() []*gm.ConceptInfo
	Params(file string, argType gauge.ArgType) []gauge.StepArg
	Tags() []string
	SearchConceptDictionary(string) *gauge.Concept
	GetAvailableSpecDetails(specs []string) []*infoGatherer.SpecDetail
}

var provider infoProvider

func Server(p infoProvider) *server {
	provider = p
	provider.Init()
	return &server{}
}

type lspHandler struct {
	jsonrpc2.Handler
}

type LangHandler struct {
}

type registrationParams struct {
	Registrations []registration `json:"registrations"`
}

type registration struct {
	Id              string      `json:"id"`
	Method          string      `json:"method"`
	RegisterOptions interface{} `json:"registerOptions"`
}

type textDocumentRegistrationOptions struct {
	DocumentSelector documentSelector `json:"documentSelector"`
}

type textDocumentChangeRegistrationOptions struct {
	textDocumentRegistrationOptions
	SyncKind lsp.TextDocumentSyncKind `json:"syncKind,omitempty"`
}

type codeLensRegistrationOptions struct {
	textDocumentRegistrationOptions
	ResolveProvider bool `json:"resolveProvider,omitempty"`
}

type documentSelector struct {
	Scheme   string `json:"scheme"`
	Language string `json:"language"`
	Pattern  string `json:"pattern"`
}

func newHandler() jsonrpc2.Handler {
	return lspHandler{jsonrpc2.HandlerWithError((&LangHandler{}).handle)}
}

func (h lspHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	go h.Handler.Handle(ctx, conn, req)
}

func (h *LangHandler) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
	return h.Handle(ctx, conn, req)
}

func (h *LangHandler) Handle(ctx context.Context, conn jsonrpc2.JSONRPC2, req *jsonrpc2.Request) (interface{}, error) {
	switch req.Method {
	case "initialize":
		return gaugeLSPCapabilities(), nil
	case "initialized":
		registerRunnerCapabilities(conn, ctx)
		go publishDiagnostics(ctx, conn)
		return nil, nil
	case "shutdown":
		killRunner()
		return nil, nil
	case "exit":
		if c, ok := conn.(*jsonrpc2.Conn); ok {
			c.Close()
			os.Exit(0)
		}
		return nil, nil
	case "$/cancelRequest":
		return nil, nil
	case "textDocument/didOpen":
		return nil, documentOpened(req, ctx, conn)
	case "textDocument/didClose":
		return nil, documentClosed(req, ctx, conn)
	case "textDocument/didChange":
		return nil, documentChange(req, ctx, conn)
	case "textDocument/completion":
		return completion(req)
	case "completionItem/resolve":
		return resolveCompletion(req)
	case "textDocument/definition":
		return definition(req)
	case "textDocument/formatting":
		data, err := format(req)
		if err != nil {
			conn.Notify(ctx, "window/showMessage", lsp.ShowMessageParams{Type: 1, Message: err.Error()})
		}
		return data, err
	case "textDocument/codeLens":
		return codeLenses(req)
	case "textDocument/codeAction":
		return codeActions(req)
	case "textDocument/rename":
		return rename(req)
	case "textDocument/documentSymbol":
		return documentSymbols(req)
	case "workspace/symbol":
		return workspaceSymbols(req)
	case "gauge/stepReferences":
		return stepReferences(req)
	case "gauge/stepValueAt":
		return stepValueAt(req)
	case "gauge/scenarios":
		return scenarios(req)
	case "gauge/specs":
		return specs()
	default:
		return nil, nil
	}
}

func gaugeLSPCapabilities() lsp.InitializeResult {
	kind := lsp.TDSKFull
	return lsp.InitializeResult{
		Capabilities: lsp.ServerCapabilities{
			TextDocumentSync:           lsp.TextDocumentSyncOptionsOrKind{Kind: &kind, Options: &lsp.TextDocumentSyncOptions{Save: &lsp.SaveOptions{IncludeText: true}}},
			CompletionProvider:         &lsp.CompletionOptions{ResolveProvider: true, TriggerCharacters: []string{"*", "* ", "\"", "<", ":", ","}},
			DocumentFormattingProvider: true,
			CodeLensProvider:           &lsp.CodeLensOptions{ResolveProvider: false},
			DefinitionProvider:         true,
			CodeActionProvider:         true,
			DocumentSymbolProvider:     true,
			WorkspaceSymbolProvider:    true,
			RenameProvider:             true,
		},
	}
}

func documentOpened(req *jsonrpc2.Request, ctx context.Context, conn jsonrpc2.JSONRPC2) error {
	var params lsp.DidOpenTextDocumentParams
	var err error
	if err = json.Unmarshal(*req.Params, &params); err != nil {
		logger.APILog.Debugf("failed to parse request %s", err.Error())
		return err
	}
	if util.IsGaugeFile(params.TextDocument.URI) {
		openFile(params)
	} else if lRunner.runner != nil {
		err = cacheFileOnRunner(params.TextDocument.URI, params.TextDocument.Text)
	}
	go publishDiagnostics(ctx, conn)
	return err
}

func documentChange(req *jsonrpc2.Request, ctx context.Context, conn jsonrpc2.JSONRPC2) error {
	var params lsp.DidChangeTextDocumentParams
	var err error
	if err = json.Unmarshal(*req.Params, &params); err != nil {
		logger.APILog.Debugf("failed to parse request %s", err.Error())
		return err
	}
	if util.IsGaugeFile(params.TextDocument.URI) {
		changeFile(params)
	} else if lRunner.runner != nil {
		err = cacheFileOnRunner(params.TextDocument.URI, params.ContentChanges[0].Text)
	}
	go publishDiagnostics(ctx, conn)
	return err
}

func documentClosed(req *jsonrpc2.Request, ctx context.Context, conn jsonrpc2.JSONRPC2) error {
	var params lsp.DidCloseTextDocumentParams
	var err error
	if err := json.Unmarshal(*req.Params, &params); err != nil {
		logger.APILog.Debugf("failed to parse request %s", err.Error())
		return err
	}
	if util.IsGaugeFile(params.TextDocument.URI) {
		closeFile(params)
		if !common.FileExists(util.ConvertPathToURI(params.TextDocument.URI)) {
			publishDiagnostic(params.TextDocument.URI, []lsp.Diagnostic{}, conn, ctx)
		}
	} else if lRunner.runner != nil {
		cacheFileRequest := &gm.Message{MessageType: gm.Message_CacheFileRequest, CacheFileRequest: &gm.CacheFileRequest{FilePath: util.ConvertURItoFilePath(params.TextDocument.URI), IsClosed: true}}
		err = sendMessageToRunner(cacheFileRequest)
	}
	go publishDiagnostics(ctx, conn)
	return err
}

func registerRunnerCapabilities(conn jsonrpc2.JSONRPC2, ctx context.Context) {
	var result string
	id, err := getLanugeIdetifier()
	if err != nil || id == "" {
		return
	}
	startRunner()
	ds := documentSelector{"file", id, fmt.Sprintf("%s/**/*", config.ProjectRoot)}
	conn.Call(ctx, "client/registerCapability", registrationParams{[]registration{
		{Id: "gauge-runner-didOpen", Method: "textDocument/didOpen", RegisterOptions: textDocumentRegistrationOptions{DocumentSelector: ds}},
		{Id: "gauge-runner-didClose", Method: "textDocument/didClose", RegisterOptions: textDocumentRegistrationOptions{DocumentSelector: ds}},
		{Id: "gauge-runner-didChange", Method: "textDocument/didChange", RegisterOptions: textDocumentChangeRegistrationOptions{textDocumentRegistrationOptions: textDocumentRegistrationOptions{DocumentSelector: ds}, SyncKind: lsp.TDSKFull}},
		{Id: "gauge-runner-codelens", Method: "textDocument/codeLens", RegisterOptions: codeLensRegistrationOptions{textDocumentRegistrationOptions: textDocumentRegistrationOptions{DocumentSelector: ds}, ResolveProvider: false}},
	}}, result)
}

type lspWriter struct {
}

func (w lspWriter) Write(p []byte) (n int, err error) {
	logger.LspLog.Debug(string(p))
	return os.Stderr.Write(p)
}

func (s *server) Start(logLevel string) {
	logger.APILog.Info("LangServer: reading on stdin, writing on stdout")
	var connOpt []jsonrpc2.ConnOpt
	if logLevel == "debug" {
		connOpt = append(connOpt, jsonrpc2.LogMessages(log.New(lspWriter{}, "", 0)))
	}
	ctx := context.Background()
	conn := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(stdRWC{}, jsonrpc2.VSCodeObjectCodec{}), newHandler(), connOpt...)
	logger.SetCustomLogger(lspLogger{conn, ctx})
	<-conn.DisconnectNotify()
	logger.APILog.Info("Connection closed")
}

type stdRWC struct{}

func (stdRWC) Read(p []byte) (int, error) {
	return os.Stdin.Read(p)
}

func (stdRWC) Write(p []byte) (int, error) {
	return os.Stdout.Write(p)
}

func (stdRWC) Close() error {
	if err := os.Stdin.Close(); err != nil {
		return err
	}
	return os.Stdout.Close()
}

type lspLogger struct {
	conn *jsonrpc2.Conn
	ctx  context.Context
}

func (c lspLogger) Log(logLevel logging.Level, msg string) {
	logger.APILog.Info(logLevel)
	var level lsp.MessageType
	switch logLevel {
	case logging.DEBUG:
		level = lsp.Log
	case logging.INFO:
		level = lsp.Info
	case logging.WARNING:
		level = lsp.MTWarning
	case logging.ERROR:
		level = lsp.MTError
	case logging.CRITICAL:
		level = lsp.MTError
	default:
		level = lsp.Info
	}
	c.conn.Notify(c.ctx, "window/logMessage", lsp.LogMessageParams{Type: level, Message: msg})
}
<|endoftext|>


<commit_before>// Copyright 2018 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package lang

import (
	"context"
	"log"
	"runtime/debug"

	"os"

	"encoding/json"

	"github.com/getgauge/gauge/api/infoGatherer"
	"github.com/getgauge/gauge/execution"
	"github.com/getgauge/gauge/gauge"
	gm "github.com/getgauge/gauge/gauge_messages"
	"github.com/sourcegraph/jsonrpc2"
)

type infoProvider interface {
	Init()
	Steps() []*gauge.Step
	AllSteps() []*gauge.Step
	Concepts() []*gm.ConceptInfo
	Params(file string, argType gauge.ArgType) []gauge.StepArg
	Tags() []string
	SearchConceptDictionary(string) *gauge.Concept
	GetAvailableSpecDetails(specs []string) []*infoGatherer.SpecDetail
}

var provider infoProvider

type lspHandler struct {
	jsonrpc2.Handler
}

type LangHandler struct {
}

type InitializeParams struct {
	RootPath     string             `json:"rootPath,omitempty"`
	Capabilities ClientCapabilities `json:"capabilities,omitempty"`
}

func newHandler() jsonrpc2.Handler {
	return lspHandler{jsonrpc2.HandlerWithError((&LangHandler{}).handle)}
}

func (h lspHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	go h.Handler.Handle(ctx, conn, req)
}

func (h *LangHandler) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
	defer recoverPanic(req)
	return h.Handle(ctx, conn, req)
}

func (h *LangHandler) Handle(ctx context.Context, conn jsonrpc2.JSONRPC2, req *jsonrpc2.Request) (interface{}, error) {
	switch req.Method {
	case "initialize":
		if err := cacheInitializeParams(req); err != nil {
			logError(req, err.Error())
			return nil, err
		}
		return gaugeLSPCapabilities(), nil
	case "initialized":
		registerFileWatcher(conn, ctx)
		err := registerRunnerCapabilities(conn, ctx)
		if err != nil {
			logError(req, err.Error())
		}
		go publishDiagnostics(ctx, conn)
		return nil, nil
	case "shutdown":
		killRunner()
		return nil, nil
	case "exit":
		if c, ok := conn.(*jsonrpc2.Conn); ok {
			c.Close()
			os.Exit(0)
		}
		return nil, nil
	case "$/cancelRequest":
		return nil, nil
	case "textDocument/didOpen":
		err := documentOpened(req, ctx, conn)
		if err != nil {
			logDebug(req, err.Error())
		}
		return nil, err
	case "textDocument/didClose":
		err := documentClosed(req, ctx, conn)
		if err != nil {
			logDebug(req, err.Error())
		}
		return nil, err
	case "textDocument/didChange":
		err := documentChange(req, ctx, conn)
		if err != nil {
			logDebug(req, err.Error())
		}
		return nil, err
	case "workspace/didChangeWatchedFiles":
		err := documentChangeWatchedFiles(req, ctx, conn)
		if err != nil {
			logDebug(req, err.Error())
		}
		return nil, err
	case "textDocument/completion":
		val, err := completion(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "completionItem/resolve":
		val, err := resolveCompletion(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "textDocument/definition":
		val, err := definition(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "textDocument/formatting":
		data, err := format(req)
		if err != nil {
			logDebug(req, err.Error())
			showErrorMessageOnClient(ctx, conn, err)
		}
		return data, err
	case "textDocument/codeLens":
		val, err := codeLenses(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "textDocument/codeAction":
		val, err := codeActions(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "textDocument/rename":
		result, err := rename(ctx, conn, req)
		if err != nil {
			logDebug(req, err.Error())
			showErrorMessageOnClient(ctx, conn, err)
			return nil, err
		}
		return result, nil
	case "textDocument/documentSymbol":
		val, err := documentSymbols(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "workspace/symbol":
		val, err := workspaceSymbols(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/stepReferences":
		val, err := stepReferences(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/stepValueAt":
		val, err := stepValueAt(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/scenarios":
		val, err := scenarios(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/getImplFiles":
		val, err := getImplFiles(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/putStubImpl":
		if err := sendSaveFilesRequest(ctx, conn); err != nil {
			logDebug(req, err.Error())
			showErrorMessageOnClient(ctx, conn, err)
			return nil, err
		}
		val, err := putStubImpl(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/specs":
		val, err := specs()
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/executionStatus":
		val, err := execution.ReadExecutionStatus()
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/generateConcept":
		if err := sendSaveFilesRequest(ctx, conn); err != nil {
			showErrorMessageOnClient(ctx, conn, err)
			return nil, err
		}
		return generateConcept(req)
	case "gauge/getRunnerLanguage":
		return lRunner.lspID, nil
	default:
		return nil, nil
	}
}

func cacheInitializeParams(req *jsonrpc2.Request) error {
	var params InitializeParams
	var err error
	if err = json.Unmarshal(*req.Params, &params); err != nil {
		return err
	}
	clientCapabilities = params.Capabilities
	return nil
}

func startLsp(logLevel string) (context.Context, *jsonrpc2.Conn) {
	logInfo(nil, "LangServer: reading on stdin, writing on stdout")
	var connOpt []jsonrpc2.ConnOpt
	if logLevel == "debug" {
		connOpt = append(connOpt, jsonrpc2.LogMessages(log.New(lspWriter{}, "", 0)))
	}
	ctx := context.Background()
	return ctx, jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(stdRWC{}, jsonrpc2.VSCodeObjectCodec{}), newHandler(), connOpt...)
}

func initializeRunner() {
	id, err := getLanguageIdentifier()
	if err != nil || id == "" {
		logDebug(nil, "Current runner is not compatible with gauge LSP.")
	}
	err = startRunner()
	if err != nil {
		logDebug(nil, "%s\nSome of the gauge lsp feature will not work as expected.", err.Error())
	}
	lRunner.lspID = id
}

func Start(p infoProvider, logLevel string) {
	provider = p
	provider.Init()
	initializeRunner()
	ctx, conn := startLsp(logLevel)
	initialize(ctx, conn)
	<-conn.DisconnectNotify()
	logInfo(nil, "Connection closed")
}

func recoverPanic(req *jsonrpc2.Request) {
	if r := recover(); r != nil {
		logFatal(req, "%v\n%s", r, string(debug.Stack()))
	}
}
<commit_msg>Not starting runner if not lsp comapitable.<commit_after>// Copyright 2018 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package lang

import (
	"context"
	"log"
	"runtime/debug"

	"os"

	"encoding/json"

	"github.com/getgauge/gauge/api/infoGatherer"
	"github.com/getgauge/gauge/execution"
	"github.com/getgauge/gauge/gauge"
	gm "github.com/getgauge/gauge/gauge_messages"
	"github.com/sourcegraph/jsonrpc2"
)

type infoProvider interface {
	Init()
	Steps() []*gauge.Step
	AllSteps() []*gauge.Step
	Concepts() []*gm.ConceptInfo
	Params(file string, argType gauge.ArgType) []gauge.StepArg
	Tags() []string
	SearchConceptDictionary(string) *gauge.Concept
	GetAvailableSpecDetails(specs []string) []*infoGatherer.SpecDetail
}

var provider infoProvider

type lspHandler struct {
	jsonrpc2.Handler
}

type LangHandler struct {
}

type InitializeParams struct {
	RootPath     string             `json:"rootPath,omitempty"`
	Capabilities ClientCapabilities `json:"capabilities,omitempty"`
}

func newHandler() jsonrpc2.Handler {
	return lspHandler{jsonrpc2.HandlerWithError((&LangHandler{}).handle)}
}

func (h lspHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	go h.Handler.Handle(ctx, conn, req)
}

func (h *LangHandler) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
	defer recoverPanic(req)
	return h.Handle(ctx, conn, req)
}

func (h *LangHandler) Handle(ctx context.Context, conn jsonrpc2.JSONRPC2, req *jsonrpc2.Request) (interface{}, error) {
	switch req.Method {
	case "initialize":
		if err := cacheInitializeParams(req); err != nil {
			logError(req, err.Error())
			return nil, err
		}
		return gaugeLSPCapabilities(), nil
	case "initialized":
		registerFileWatcher(conn, ctx)
		err := registerRunnerCapabilities(conn, ctx)
		if err != nil {
			logError(req, err.Error())
		}
		go publishDiagnostics(ctx, conn)
		return nil, nil
	case "shutdown":
		killRunner()
		return nil, nil
	case "exit":
		if c, ok := conn.(*jsonrpc2.Conn); ok {
			c.Close()
			os.Exit(0)
		}
		return nil, nil
	case "$/cancelRequest":
		return nil, nil
	case "textDocument/didOpen":
		err := documentOpened(req, ctx, conn)
		if err != nil {
			logDebug(req, err.Error())
		}
		return nil, err
	case "textDocument/didClose":
		err := documentClosed(req, ctx, conn)
		if err != nil {
			logDebug(req, err.Error())
		}
		return nil, err
	case "textDocument/didChange":
		err := documentChange(req, ctx, conn)
		if err != nil {
			logDebug(req, err.Error())
		}
		return nil, err
	case "workspace/didChangeWatchedFiles":
		err := documentChangeWatchedFiles(req, ctx, conn)
		if err != nil {
			logDebug(req, err.Error())
		}
		return nil, err
	case "textDocument/completion":
		val, err := completion(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "completionItem/resolve":
		val, err := resolveCompletion(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "textDocument/definition":
		val, err := definition(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "textDocument/formatting":
		data, err := format(req)
		if err != nil {
			logDebug(req, err.Error())
			showErrorMessageOnClient(ctx, conn, err)
		}
		return data, err
	case "textDocument/codeLens":
		val, err := codeLenses(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "textDocument/codeAction":
		val, err := codeActions(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "textDocument/rename":
		result, err := rename(ctx, conn, req)
		if err != nil {
			logDebug(req, err.Error())
			showErrorMessageOnClient(ctx, conn, err)
			return nil, err
		}
		return result, nil
	case "textDocument/documentSymbol":
		val, err := documentSymbols(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "workspace/symbol":
		val, err := workspaceSymbols(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/stepReferences":
		val, err := stepReferences(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/stepValueAt":
		val, err := stepValueAt(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/scenarios":
		val, err := scenarios(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/getImplFiles":
		val, err := getImplFiles(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/putStubImpl":
		if err := sendSaveFilesRequest(ctx, conn); err != nil {
			logDebug(req, err.Error())
			showErrorMessageOnClient(ctx, conn, err)
			return nil, err
		}
		val, err := putStubImpl(req)
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/specs":
		val, err := specs()
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/executionStatus":
		val, err := execution.ReadExecutionStatus()
		if err != nil {
			logDebug(req, err.Error())
		}
		return val, err
	case "gauge/generateConcept":
		if err := sendSaveFilesRequest(ctx, conn); err != nil {
			showErrorMessageOnClient(ctx, conn, err)
			return nil, err
		}
		return generateConcept(req)
	case "gauge/getRunnerLanguage":
		return lRunner.lspID, nil
	default:
		return nil, nil
	}
}

func cacheInitializeParams(req *jsonrpc2.Request) error {
	var params InitializeParams
	var err error
	if err = json.Unmarshal(*req.Params, &params); err != nil {
		return err
	}
	clientCapabilities = params.Capabilities
	return nil
}

func startLsp(logLevel string) (context.Context, *jsonrpc2.Conn) {
	logInfo(nil, "LangServer: reading on stdin, writing on stdout")
	var connOpt []jsonrpc2.ConnOpt
	if logLevel == "debug" {
		connOpt = append(connOpt, jsonrpc2.LogMessages(log.New(lspWriter{}, "", 0)))
	}
	ctx := context.Background()
	return ctx, jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(stdRWC{}, jsonrpc2.VSCodeObjectCodec{}), newHandler(), connOpt...)
}

func initializeRunner() {
	id, err := getLanguageIdentifier()
	if err != nil || id == "" {
		logDebug(nil, "Current runner is not compatible with gauge LSP.")
		return
	}
	err = startRunner()
	if err != nil {
		logDebug(nil, "%s\nSome of the gauge lsp feature will not work as expected.", err.Error())
	}
	lRunner.lspID = id
}

func Start(p infoProvider, logLevel string) {
	provider = p
	provider.Init()
	initializeRunner()
	ctx, conn := startLsp(logLevel)
	initialize(ctx, conn)
	<-conn.DisconnectNotify()
	logInfo(nil, "Connection closed")
}

func recoverPanic(req *jsonrpc2.Request) {
	if r := recover(); r != nil {
		logFatal(req, "%v\n%s", r, string(debug.Stack()))
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"

	sys "golang.org/x/sys/unix"

	"github.com/derekparker/delve/command"
	"github.com/derekparker/delve/proctl"

	"github.com/peterh/liner"
)

const historyFile string = ".dbg_history"

func Run(args []string) {
	var (
		dbp  *proctl.DebuggedProcess
		err  error
		line = liner.NewLiner()
	)
	defer line.Close()

	switch args[0] {
	case "run":
		const debugname = "debug"
		cmd := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
		err := cmd.Run()
		if err != nil {
			die(1, "Could not compile program:", err)
		}
		defer os.Remove(debugname)

		dbp, err = proctl.Launch(append([]string{"./" + debugname}, args...))
		if err != nil {
			die(1, "Could not launch program:", err)
		}
	case "test":
		wd, err := os.Getwd()
		if err != nil {
			die(1, err)
		}
		base := filepath.Base(wd)
		cmd := exec.Command("go", "test", "-c", "-gcflags", "-N -l")
		err = cmd.Run()
		if err != nil {
			die(1, "Could not compile program:", err)
		}
		debugname := "./" + base + ".test"
		defer os.Remove(debugname)

		dbp, err = proctl.Launch(append([]string{debugname}, args...))
		if err != nil {
			die(1, "Could not launch program:", err)
		}
	case "attach":
		pid, err := strconv.Atoi(args[1])
		if err != nil {
			die(1, "Invalid pid", args[1])
		}
		dbp, err = proctl.Attach(pid)
		if err != nil {
			die(1, "Could not attach to process:", err)
		}
	default:
		dbp, err = proctl.Launch(args)
		if err != nil {
			die(1, "Could not launch program:", err)
		}
	}

	ch := make(chan os.Signal)
	signal.Notify(ch, sys.SIGINT)
	go func() {
		for _ = range ch {
			if dbp.Running() {
				dbp.RequestManualStop()
			}
		}
	}()

	cmds := command.DebugCommands()
	f, err := os.Open(historyFile)
	if err != nil {
		f, _ = os.Create(historyFile)
	}
	line.ReadHistory(f)
	f.Close()
	fmt.Println("Type 'help' for list of commands.")

	for {
		cmdstr, err := promptForInput(line)
		if err != nil {
			if err == io.EOF {
				handleExit(dbp, line, 0)
			}
			die(1, "Prompt for input failed.\n")
		}

		cmdstr, args := parseCommand(cmdstr)

		if cmdstr == "exit" {
			handleExit(dbp, line, 0)
		}

		cmd := cmds.Find(cmdstr)
		if err := cmd(dbp, args...); err != nil {
			switch err.(type) {
			case proctl.ProcessExitedError:
				pe := err.(proctl.ProcessExitedError)
				fmt.Fprintf(os.Stderr, "Process exited with status %d\n", pe.Status)
			default:
				fmt.Fprintf(os.Stderr, "Command failed: %s\n", err)
			}
		}
	}
}

func handleExit(dbp *proctl.DebuggedProcess, line *liner.State, status int) {
	if f, err := os.OpenFile(historyFile, os.O_RDWR, 0666); err == nil {
		_, err := line.WriteHistory(f)
		if err != nil {
			fmt.Println("readline history error: ", err)
		}
		f.Close()
	}

	answer, err := line.Prompt("Would you like to kill the process? [y/n]")
	if err != nil {
		die(2, io.EOF)
	}
	answer = strings.TrimSuffix(answer, "\n")

	for _, bp := range dbp.HWBreakPoints {
		if bp == nil {
			continue
		}
		if _, err := dbp.Clear(bp.Addr); err != nil {
			fmt.Printf("Can't clear breakpoint @%x: %s\n", bp.Addr, err)
		}
	}

	for pc := range dbp.BreakPoints {
		if _, err := dbp.Clear(pc); err != nil {
			fmt.Printf("Can't clear breakpoint @%x: %s\n", pc, err)
		}
	}

	fmt.Println("Detaching from process...")
	err = sys.PtraceDetach(dbp.Process.Pid)
	if err != nil {
		die(2, "Could not detach", err)
	}

	if answer == "y" {
		fmt.Println("Killing process", dbp.Process.Pid)

		err := dbp.Process.Kill()
		if err != nil {
			fmt.Println("Could not kill process", err)
		}
	}

	die(status, "Hope I was of service hunting your bug!")
}

func die(status int, args ...interface{}) {
	fmt.Fprint(os.Stderr, args)
	fmt.Fprint(os.Stderr, "\n")
	os.Exit(status)
}

func parseCommand(cmdstr string) (string, []string) {
	vals := strings.Split(cmdstr, " ")
	return vals[0], vals[1:]
}

func promptForInput(line *liner.State) (string, error) {
	l, err := line.Prompt("(dlv) ")
	if err != nil {
		return "", err
	}

	l = strings.TrimSuffix(l, "\n")
	if l != "" {
		line.AppendHistory(l)
	}

	return l, nil
}
<commit_msg>Added Term object (terminal or terminator) that wraps the liner.State object so that it can be closed properly on exit. Changed a few functions (die, promptForInput, etc.) to be methods for Term.<commit_after>package cli

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"

	sys "golang.org/x/sys/unix"

	"github.com/derekparker/delve/command"
	"github.com/derekparker/delve/proctl"

	"github.com/peterh/liner"
)

const historyFile string = ".dbg_history"

func Run(args []string) {
	var (
		dbp *proctl.DebuggedProcess
		err error
		t   = &Term{prompt: "(dlv) ", line: liner.NewLiner()}
	)
	defer t.line.Close()

	switch args[0] {
	case "run":
		const debugname = "debug"
		cmd := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
		err := cmd.Run()
		if err != nil {
			t.die(1, "Could not compile program:", err)
		}
		defer os.Remove(debugname)

		dbp, err = proctl.Launch(append([]string{"./" + debugname}, args...))
		if err != nil {
			t.die(1, "Could not launch program:", err)
		}
	case "test":
		wd, err := os.Getwd()
		if err != nil {
			t.die(1, err)
		}
		base := filepath.Base(wd)
		cmd := exec.Command("go", "test", "-c", "-gcflags", "-N -l")
		err = cmd.Run()
		if err != nil {
			t.die(1, "Could not compile program:", err)
		}
		debugname := "./" + base + ".test"
		defer os.Remove(debugname)

		dbp, err = proctl.Launch(append([]string{debugname}, args...))
		if err != nil {
			t.die(1, "Could not launch program:", err)
		}
	case "attach":
		pid, err := strconv.Atoi(args[1])
		if err != nil {
			t.die(1, "Invalid pid", args[1])
		}
		dbp, err = proctl.Attach(pid)
		if err != nil {
			t.die(1, "Could not attach to process:", err)
		}
	default:
		dbp, err = proctl.Launch(args)
		if err != nil {
			t.die(1, "Could not launch program:", err)
		}
	}

	ch := make(chan os.Signal)
	signal.Notify(ch, sys.SIGINT)
	go func() {
		for _ = range ch {
			if dbp.Running() {
				dbp.RequestManualStop()
			}
		}
	}()

	cmds := command.DebugCommands()
	f, err := os.Open(historyFile)
	if err != nil {
		f, _ = os.Create(historyFile)
	}
	t.line.ReadHistory(f)
	f.Close()
	fmt.Println("Type 'help' for list of commands.")

	for {
		cmdstr, err := t.promptForInput()
		if err != nil {
			if err == io.EOF {
				handleExit(dbp, t, 0)
			}
			t.die(1, "Prompt for input failed.\n")
		}

		cmdstr, args := parseCommand(cmdstr)

		if cmdstr == "exit" {
			handleExit(dbp, t, 0)
		}

		cmd := cmds.Find(cmdstr)
		if err := cmd(dbp, args...); err != nil {
			switch err.(type) {
			case proctl.ProcessExitedError:
				pe := err.(proctl.ProcessExitedError)
				fmt.Fprintf(os.Stderr, "Process exited with status %d\n", pe.Status)
			default:
				fmt.Fprintf(os.Stderr, "Command failed: %s\n", err)
			}
		}
	}
}

func handleExit(dbp *proctl.DebuggedProcess, t *Term, status int) {
	if f, err := os.OpenFile(historyFile, os.O_RDWR, 0666); err == nil {
		_, err := t.line.WriteHistory(f)
		if err != nil {
			fmt.Println("readline history error: ", err)
		}
		f.Close()
	}

	answer, err := t.line.Prompt("Would you like to kill the process? [y/n]")
	if err != nil {
		t.die(2, io.EOF)
	}
	answer = strings.TrimSuffix(answer, "\n")

	for _, bp := range dbp.HWBreakPoints {
		if bp == nil {
			continue
		}
		if _, err := dbp.Clear(bp.Addr); err != nil {
			fmt.Printf("Can't clear breakpoint @%x: %s\n", bp.Addr, err)
		}
	}

	for pc := range dbp.BreakPoints {
		if _, err := dbp.Clear(pc); err != nil {
			fmt.Printf("Can't clear breakpoint @%x: %s\n", pc, err)
		}
	}

	fmt.Println("Detaching from process...")
	err = sys.PtraceDetach(dbp.Process.Pid)
	if err != nil {
		t.die(2, "Could not detach", err)
	}

	if answer == "y" {
		fmt.Println("Killing process", dbp.Process.Pid)

		err := dbp.Process.Kill()
		if err != nil {
			fmt.Println("Could not kill process", err)
		}
	}

	t.die(status, "Hope I was of service hunting your bug!")
}

type Term struct {
	prompt string
	line   *liner.State
}

func (t *Term) die(status int, args ...interface{}) {
	if t.line != nil {
		t.line.Close()
	}

	fmt.Fprint(os.Stderr, args)
	fmt.Fprint(os.Stderr, "\n")
	os.Exit(status)
}

func (t *Term) promptForInput() (string, error) {
	l, err := t.line.Prompt(t.prompt)
	if err != nil {
		return "", err
	}

	l = strings.TrimSuffix(l, "\n")
	if l != "" {
		t.line.AppendHistory(l)
	}

	return l, nil
}

func parseCommand(cmdstr string) (string, []string) {
	vals := strings.Split(cmdstr, " ")
	return vals[0], vals[1:]
}
<|endoftext|>


<commit_before>package registry

import (
	"fmt"
	"os"
	"path"

	"github.com/openshift/origin/pkg/oc/bootstrap/clusteradd/componentinstall"
	"github.com/openshift/origin/pkg/oc/bootstrap/clusterup/kubeapiserver"
	"github.com/openshift/origin/pkg/oc/bootstrap/docker/dockerhelper"
	"github.com/openshift/origin/pkg/oc/bootstrap/docker/host"
	"github.com/openshift/origin/pkg/oc/bootstrap/docker/run"
	"github.com/openshift/origin/pkg/oc/errors"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

const (
	DefaultNamespace  = "default"
	SvcDockerRegistry = "docker-registry"
	// This is needed because of NO_PROXY cannot handle the CIDR range
	RegistryServiceClusterIP = "172.30.1.1"
)

type RegistryComponentOptions struct {
	InstallContext componentinstall.Context
}

func (r *RegistryComponentOptions) Name() string {
	return "openshift-image-registry"
}

func (r *RegistryComponentOptions) Install(dockerClient dockerhelper.Interface, logdir string) error {
	kubeAdminClient, err := kubernetes.NewForConfig(r.InstallContext.ClusterAdminClientConfig())
	if err != nil {
		return err
	}
	_, err = kubeAdminClient.Core().Services(DefaultNamespace).Get(SvcDockerRegistry, metav1.GetOptions{})
	if err == nil {
		// If there's no error, the registry already exists
		return nil
	}
	if !apierrors.IsNotFound(err) {
		return errors.NewError("error retrieving docker registry service").WithCause(err)
	}

	imageRunHelper := run.NewRunHelper(dockerhelper.NewHelper(dockerClient)).New()
	if err := componentinstall.AddPrivilegedUser(r.InstallContext.ClusterAdminClientConfig(), DefaultNamespace, "registry"); err != nil {
		return err
	}

	// If docker is on remote host, the base dir is different.
	baseDir := r.InstallContext.BaseDir()
	if len(os.Getenv("DOCKER_HOST")) > 0 {
		baseDir = path.Join(host.RemoteHostOriginDir, r.InstallContext.BaseDir())
	}

	masterConfigDir := path.Join(baseDir, kubeapiserver.KubeAPIServerDirName)
	flags := []string{
		"adm",
		"registry",
		fmt.Sprintf("--loglevel=%d", r.InstallContext.ComponentLogLevel()),
		// We need to set the ClusterIP for registry in order to be able to set the NO_PROXY no predicable
		// IP address as NO_PROXY does not support CIDR format.
		// TODO: We should switch the cluster up registry to use DNS.
		fmt.Sprintf("--cluster-ip=%s", RegistryServiceClusterIP),
		fmt.Sprintf("--config=%s", path.Join(masterConfigDir, "admin.kubeconfig")),
		fmt.Sprintf("--images=%s", r.InstallContext.ImageFormat()),
		fmt.Sprintf("--mount-host=%s", path.Join(r.InstallContext.BaseDir(), "openshift.local.pv", "registry")),
	}
	_, rc, err := imageRunHelper.Image(r.InstallContext.ClientImage()).
		Privileged().
		DiscardContainer().
		HostNetwork().
		HostPid().
		SaveContainerLogs(r.Name(), logdir).
		Bind(masterConfigDir + ":" + masterConfigDir).
		Entrypoint("oc").
		Command(flags...).Run()

	if rc != 0 {
		return errors.NewError("could not run %q: rc=%d", r.Name(), rc)
	}

	return nil
}
<commit_msg>up: add remoteHostDir to baseDir for docker-registry mount-path<commit_after>package registry

import (
	"fmt"
	"os"
	"path"

	"github.com/openshift/origin/pkg/oc/bootstrap/clusteradd/componentinstall"
	"github.com/openshift/origin/pkg/oc/bootstrap/clusterup/kubeapiserver"
	"github.com/openshift/origin/pkg/oc/bootstrap/docker/dockerhelper"
	"github.com/openshift/origin/pkg/oc/bootstrap/docker/host"
	"github.com/openshift/origin/pkg/oc/bootstrap/docker/run"
	"github.com/openshift/origin/pkg/oc/errors"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

const (
	DefaultNamespace  = "default"
	SvcDockerRegistry = "docker-registry"
	// This is needed because of NO_PROXY cannot handle the CIDR range
	RegistryServiceClusterIP = "172.30.1.1"
)

type RegistryComponentOptions struct {
	InstallContext componentinstall.Context
}

func (r *RegistryComponentOptions) Name() string {
	return "openshift-image-registry"
}

func (r *RegistryComponentOptions) Install(dockerClient dockerhelper.Interface, logdir string) error {
	kubeAdminClient, err := kubernetes.NewForConfig(r.InstallContext.ClusterAdminClientConfig())
	if err != nil {
		return err
	}
	_, err = kubeAdminClient.Core().Services(DefaultNamespace).Get(SvcDockerRegistry, metav1.GetOptions{})
	if err == nil {
		// If there's no error, the registry already exists
		return nil
	}
	if !apierrors.IsNotFound(err) {
		return errors.NewError("error retrieving docker registry service").WithCause(err)
	}

	imageRunHelper := run.NewRunHelper(dockerhelper.NewHelper(dockerClient)).New()
	if err := componentinstall.AddPrivilegedUser(r.InstallContext.ClusterAdminClientConfig(), DefaultNamespace, "registry"); err != nil {
		return err
	}

	// If docker is on remote host, the base dir is different.
	baseDir := r.InstallContext.BaseDir()
	if len(os.Getenv("DOCKER_HOST")) > 0 {
		baseDir = path.Join(host.RemoteHostOriginDir, r.InstallContext.BaseDir())
	}

	masterConfigDir := path.Join(baseDir, kubeapiserver.KubeAPIServerDirName)
	flags := []string{
		"adm",
		"registry",
		fmt.Sprintf("--loglevel=%d", r.InstallContext.ComponentLogLevel()),
		// We need to set the ClusterIP for registry in order to be able to set the NO_PROXY no predicable
		// IP address as NO_PROXY does not support CIDR format.
		// TODO: We should switch the cluster up registry to use DNS.
		fmt.Sprintf("--cluster-ip=%s", RegistryServiceClusterIP),
		fmt.Sprintf("--config=%s", path.Join(masterConfigDir, "admin.kubeconfig")),
		fmt.Sprintf("--images=%s", r.InstallContext.ImageFormat()),
		fmt.Sprintf("--mount-host=%s", path.Join(baseDir, "openshift.local.pv", "registry")),
	}
	_, rc, err := imageRunHelper.Image(r.InstallContext.ClientImage()).
		Privileged().
		DiscardContainer().
		HostNetwork().
		HostPid().
		SaveContainerLogs(r.Name(), logdir).
		Bind(masterConfigDir + ":" + masterConfigDir).
		Entrypoint("oc").
		Command(flags...).Run()

	if rc != 0 {
		return errors.NewError("could not run %q: rc=%d", r.Name(), rc)
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2018 johandorland ( https://github.com/johandorland )
//
// 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 gojsonschema

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestSchemaLoaderWithReferenceToAddedSchema(t *testing.T) {
	sl := NewSchemaLoader()
	err := sl.AddSchemas(NewStringLoader(`{
		"$id" : "http://localhost:1234/test1.json",
		"type" : "integer"
		}`))

	assert.Nil(t, err)
	schema, err := sl.Compile(NewReferenceLoader("http://localhost:1234/test1.json"))
	assert.Nil(t, err)
	result, err := schema.Validate(NewStringLoader(`"hello"`))
	assert.Nil(t, err)
	if len(result.Errors()) != 1 || result.Errors()[0].Type() != "invalid_type" {
		t.Errorf("Expected invalid type erorr, instead got %v", result.Errors())
	}
}

func TestCrossReference(t *testing.T) {
	schema1 := NewStringLoader(`{
		"$ref" : "http://localhost:1234/test3.json",
		"definitions" : {
			"foo" : {
				"type" : "integer"
			}
		}
	}`)
	schema2 := NewStringLoader(`{
		"$ref" : "http://localhost:1234/test2.json#/definitions/foo"
	}`)

	sl := NewSchemaLoader()
	err := sl.AddSchema("http://localhost:1234/test2.json", schema1)
	assert.Nil(t, err)
	err = sl.AddSchema("http://localhost:1234/test3.json", schema2)
	assert.Nil(t, err)
	schema, err := sl.Compile(NewStringLoader(`{"$ref" : "http://localhost:1234/test2.json"}`))
	assert.Nil(t, err)
	result, err := schema.Validate(NewStringLoader(`"hello"`))
	assert.Nil(t, err)
	if len(result.Errors()) != 1 || result.Errors()[0].Type() != "invalid_type" {
		t.Errorf("Expected invalid type erorr, instead got %v", result.Errors())
	}
}

// Multiple schemas identifying under the same $id should throw an error
func TestDoubleIDReference(t *testing.T) {
	sl := NewSchemaLoader()
	err := sl.AddSchema("http://localhost:1234/test4.json", NewStringLoader("{}"))
	assert.Nil(t, err)
	err = sl.AddSchemas(NewStringLoader(`{ "$id" : "http://localhost:1234/test4.json"}`))
	assert.NotNil(t, err)
}

func TestCustomMetaSchema(t *testing.T) {

	loader := NewStringLoader(`{
		"$id" : "http://localhost:1234/test5.json",
		"properties" : {
			"multipleOf" : false
		}
	}`)

	// Test a custom metaschema in which we disallow the use of the keyword "multipleOf"
	sl := NewSchemaLoader()
	sl.Validate = true

	err := sl.AddSchemas(loader)
	assert.Nil(t, err)
	_, err = sl.Compile(NewStringLoader(`{
		"$id" : "http://localhost:1234/test6.json",
		"$schema" : "http://localhost:1234/test5.json",
		"type" : "string"
	}`))
	assert.Nil(t, err)

	sl = NewSchemaLoader()
	sl.Validate = true
	err = sl.AddSchemas(loader)
	_, err = sl.Compile(NewStringLoader(`{
		"$id" : "http://localhost:1234/test7.json",
		"$schema" : "http://localhost:1234/test5.json",
		"multipleOf" : 5
	}`))
	assert.NotNil(t, err)
}

func TestSchemaDetection(t *testing.T) {
	loader := NewStringLoader(`{
		"$schema" : "http://json-schema.org/draft-04/schema#",
		"exclusiveMinimum" : 5
	}`)

	// The schema should produce an error in draft-04 mode
	_, err := NewSchema(loader)
	assert.NotNil(t, err)

	// With schema detection disabled the schema should not produce an error in hybrid mode
	sl := NewSchemaLoader()
	sl.AutoDetect = false

	_, err = sl.Compile(loader)
	assert.Nil(t, err)
}

func TestDraftCrossReferencing(t *testing.T) {

	// Tests the following cross referencing with any combination
	// of autodetection and preset draft version.

	loader1 := NewStringLoader(`{
		"$schema" : "http://json-schema.org/draft-04/schema#",
		"id" : "http://localhost:1234/file.json",
		"$id" : "http://localhost:1234/file.json",
		"exclusiveMinimum" : 5
	}`)
	loader2 := NewStringLoader(`{
		"$schema" : "http://json-schema.org/draft-07/schema#",
		"id" : "http://localhost:1234/main.json",
		"$id" : "http://localhost:1234/main.json",
		"$ref" : "file.json"
	}`)

	for _, b := range []bool{true, false} {
		for _, draft := range []Draft{Draft4, Draft6, Draft7} {
			sl := NewSchemaLoader()
			sl.Draft = draft
			sl.AutoDetect = b

			err := sl.AddSchemas(loader1)
			assert.Nil(t, err)
			_, err = sl.Compile(loader2)

			// It will always fail with autodetection on as "exclusiveMinimum" : 5
			// is only valid since draft-06. With autodetection off it will pass if
			// draft-06 or newer is used

			assert.Equal(t, err == nil, !b && draft >= Draft6)
		}
	}
}
<commit_msg>ineffassign: ineffectual assignment to err<commit_after>// Copyright 2018 johandorland ( https://github.com/johandorland )
//
// 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 gojsonschema

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestSchemaLoaderWithReferenceToAddedSchema(t *testing.T) {
	sl := NewSchemaLoader()
	err := sl.AddSchemas(NewStringLoader(`{
		"$id" : "http://localhost:1234/test1.json",
		"type" : "integer"
		}`))

	assert.Nil(t, err)
	schema, err := sl.Compile(NewReferenceLoader("http://localhost:1234/test1.json"))
	assert.Nil(t, err)
	result, err := schema.Validate(NewStringLoader(`"hello"`))
	assert.Nil(t, err)
	if len(result.Errors()) != 1 || result.Errors()[0].Type() != "invalid_type" {
		t.Errorf("Expected invalid type erorr, instead got %v", result.Errors())
	}
}

func TestCrossReference(t *testing.T) {
	schema1 := NewStringLoader(`{
		"$ref" : "http://localhost:1234/test3.json",
		"definitions" : {
			"foo" : {
				"type" : "integer"
			}
		}
	}`)
	schema2 := NewStringLoader(`{
		"$ref" : "http://localhost:1234/test2.json#/definitions/foo"
	}`)

	sl := NewSchemaLoader()
	err := sl.AddSchema("http://localhost:1234/test2.json", schema1)
	assert.Nil(t, err)
	err = sl.AddSchema("http://localhost:1234/test3.json", schema2)
	assert.Nil(t, err)
	schema, err := sl.Compile(NewStringLoader(`{"$ref" : "http://localhost:1234/test2.json"}`))
	assert.Nil(t, err)
	result, err := schema.Validate(NewStringLoader(`"hello"`))
	assert.Nil(t, err)
	if len(result.Errors()) != 1 || result.Errors()[0].Type() != "invalid_type" {
		t.Errorf("Expected invalid type erorr, instead got %v", result.Errors())
	}
}

// Multiple schemas identifying under the same $id should throw an error
func TestDoubleIDReference(t *testing.T) {
	sl := NewSchemaLoader()
	err := sl.AddSchema("http://localhost:1234/test4.json", NewStringLoader("{}"))
	assert.Nil(t, err)
	err = sl.AddSchemas(NewStringLoader(`{ "$id" : "http://localhost:1234/test4.json"}`))
	assert.NotNil(t, err)
}

func TestCustomMetaSchema(t *testing.T) {

	loader := NewStringLoader(`{
		"$id" : "http://localhost:1234/test5.json",
		"properties" : {
			"multipleOf" : false
		}
	}`)

	// Test a custom metaschema in which we disallow the use of the keyword "multipleOf"
	sl := NewSchemaLoader()
	sl.Validate = true

	err := sl.AddSchemas(loader)
	assert.Nil(t, err)
	_, err = sl.Compile(NewStringLoader(`{
		"$id" : "http://localhost:1234/test6.json",
		"$schema" : "http://localhost:1234/test5.json",
		"type" : "string"
	}`))
	assert.Nil(t, err)

	sl = NewSchemaLoader()
	sl.Validate = true
	err = sl.AddSchemas(loader)
	assert.Nil(t, err)
	_, err = sl.Compile(NewStringLoader(`{
		"$id" : "http://localhost:1234/test7.json",
		"$schema" : "http://localhost:1234/test5.json",
		"multipleOf" : 5
	}`))
	assert.NotNil(t, err)
}

func TestSchemaDetection(t *testing.T) {
	loader := NewStringLoader(`{
		"$schema" : "http://json-schema.org/draft-04/schema#",
		"exclusiveMinimum" : 5
	}`)

	// The schema should produce an error in draft-04 mode
	_, err := NewSchema(loader)
	assert.NotNil(t, err)

	// With schema detection disabled the schema should not produce an error in hybrid mode
	sl := NewSchemaLoader()
	sl.AutoDetect = false

	_, err = sl.Compile(loader)
	assert.Nil(t, err)
}

func TestDraftCrossReferencing(t *testing.T) {

	// Tests the following cross referencing with any combination
	// of autodetection and preset draft version.

	loader1 := NewStringLoader(`{
		"$schema" : "http://json-schema.org/draft-04/schema#",
		"id" : "http://localhost:1234/file.json",
		"$id" : "http://localhost:1234/file.json",
		"exclusiveMinimum" : 5
	}`)
	loader2 := NewStringLoader(`{
		"$schema" : "http://json-schema.org/draft-07/schema#",
		"id" : "http://localhost:1234/main.json",
		"$id" : "http://localhost:1234/main.json",
		"$ref" : "file.json"
	}`)

	for _, b := range []bool{true, false} {
		for _, draft := range []Draft{Draft4, Draft6, Draft7} {
			sl := NewSchemaLoader()
			sl.Draft = draft
			sl.AutoDetect = b

			err := sl.AddSchemas(loader1)
			assert.Nil(t, err)
			_, err = sl.Compile(loader2)

			// It will always fail with autodetection on as "exclusiveMinimum" : 5
			// is only valid since draft-06. With autodetection off it will pass if
			// draft-06 or newer is used

			assert.Equal(t, err == nil, !b && draft >= Draft6)
		}
	}
}
<|endoftext|>


<commit_before>//Package amazonproduct provides methods for interacting with the Amazon Product Advertising API
package amazonproduct

import (
	"fmt"
	"strconv"
	"strings"
)

/*
ItemLookup takes a product ID (ASIN) and returns the result
*/
func (api AmazonProductAPI) ItemLookup(ItemId string) (string, error) {
	params := map[string]string{
		"ItemId":        ItemId,
		"ResponseGroup": "Images,ItemAttributes,Small,EditorialReview",
	}

	return api.genSignAndFetch("ItemLookup", params)
}

/*
MultipleItemLookup takes an array of product IDs (ASIN) and returns the result
*/
func (api AmazonProductAPI) MultipleItemLookup(ItemIds []string) (string, error) {
	params := map[string]string{
		"ItemId":        strings.Join(ItemIds, ","),
		"ResponseGroup": "Images,ItemAttributes,Small,EditorialReview",
	}

	return api.genSignAndFetch("ItemLookup", params)
}

/*
ItemSearchByKeyword takes a string containg keywords and returns the search results
*/
func (api AmazonProductAPI) ItemSearchByKeyword(Keywords string, page int) (string, error) {
	params := map[string]string{
		"Keywords":      Keywords,
		"ResponseGroup": "Images,ItemAttributes,Small,EditorialReview",
		"ItemPage":      strconv.FormatInt(int64(page), 10),
	}
	return api.ItemSearch("All", params)
}

func (api AmazonProductAPI) ItemSearchByKeywordWithResponseGroup(Keywords string, ResponseGroup string) (string, error) {
	params := map[string]string{
		"Keywords":      Keywords,
		"ResponseGroup": ResponseGroup,
	}
	return api.ItemSearch("All", params)
}

func (api AmazonProductAPI) ItemSearch(SearchIndex string, Parameters map[string]string) (string, error) {
	Parameters["SearchIndex"] = SearchIndex
	return api.genSignAndFetch("ItemSearch", Parameters)
}

/*
CartCreate takes a map containing ASINs and quantities. Up to 10 items are allowed
*/
func (api AmazonProductAPI) CartCreate(items map[string]int) (string, error) {

	params := make(map[string]string)

	i := 1
	for k, v := range items {
		if i < 11 {
			key := fmt.Sprintf("Item.%d.ASIN", i)
			params[key] = string(k)

			key = fmt.Sprintf("Item.%d.Quantity", i)
			params[key] = strconv.Itoa(v)

			i++
		} else {
			break
		}
	}

	return api.genSignAndFetch("CartCreate", params)
}

/*
CartClear takes a CartId and HMAC that were returned when generating a cart
It then removes the contents of the cart
*/
func (api AmazonProductAPI) CartClear(CartId, HMAC string) (string, error) {

	params := map[string]string{
		"CartId": CartId,
		"HMAC":   HMAC,
	}

	return api.genSignAndFetch("CartClear", params)
}

/*
Cart get takes a CartID and HMAC that were returned when generaing a cart
Returns the contents of the specified cart
*/
func (api AmazonProductAPI) CartGet(CartId, HMAC string) (string, error) {

	params := map[string]string{
		"CartId": CartId,
		"HMAC":   HMAC,
	}

	return api.genSignAndFetch("CartGet", params)
}
<commit_msg>Make it possible to specify response group during lookup.<commit_after>//Package amazonproduct provides methods for interacting with the Amazon Product Advertising API
package amazonproduct

import (
	"fmt"
	"strconv"
	"strings"
)

/*
ItemLookup takes a product ID (ASIN) and returns the result
*/
func (api AmazonProductAPI) ItemLookup(ItemId string) (string, error) {
	params := map[string]string{
		"ItemId":        ItemId,
		"ResponseGroup": "Images,ItemAttributes,Small,EditorialReview",
	}

	return api.genSignAndFetch("ItemLookup", params)
}

/*
ItemLookupWithResponseGroup takes a product ID (ASIN) and a ResponseGroup and returns the result
*/
func (api AmazonProductAPI) ItemLookupWithResponseGroup(ItemId string, ResponseGroup string) (string, error) {
	params := map[string]string{
		"ItemId":        ItemId,
		"ResponseGroup": ResponseGroup,
	}

	return api.genSignAndFetch("ItemLookup", params)
}

/*
MultipleItemLookup takes an array of product IDs (ASIN) and returns the result
*/
func (api AmazonProductAPI) MultipleItemLookup(ItemIds []string) (string, error) {
	params := map[string]string{
		"ItemId":        strings.Join(ItemIds, ","),
		"ResponseGroup": "Images,ItemAttributes,Small,EditorialReview",
	}

	return api.genSignAndFetch("ItemLookup", params)
}

/*
MultipleItemLookupWithResponseGroup takes an array of product IDs (ASIN) as well as a ResponseGroup and returns the result
*/
func (api AmazonProductAPI) MultipleItemLookupWithResponseGroup(ItemIds []string, ResponseGroup string) (string, error) {
	params := map[string]string{
		"ItemId":        strings.Join(ItemIds, ","),
		"ResponseGroup": ResponseGroup,
	}

	return api.genSignAndFetch("ItemLookup", params)
}

/*
ItemSearchByKeyword takes a string containg keywords and returns the search results
*/
func (api AmazonProductAPI) ItemSearchByKeyword(Keywords string, page int) (string, error) {
	params := map[string]string{
		"Keywords":      Keywords,
		"ResponseGroup": "Images,ItemAttributes,Small,EditorialReview",
		"ItemPage":      strconv.FormatInt(int64(page), 10),
	}
	return api.ItemSearch("All", params)
}

func (api AmazonProductAPI) ItemSearchByKeywordWithResponseGroup(Keywords string, ResponseGroup string) (string, error) {
	params := map[string]string{
		"Keywords":      Keywords,
		"ResponseGroup": ResponseGroup,
	}
	return api.ItemSearch("All", params)
}

func (api AmazonProductAPI) ItemSearch(SearchIndex string, Parameters map[string]string) (string, error) {
	Parameters["SearchIndex"] = SearchIndex
	return api.genSignAndFetch("ItemSearch", Parameters)
}

/*
CartCreate takes a map containing ASINs and quantities. Up to 10 items are allowed
*/
func (api AmazonProductAPI) CartCreate(items map[string]int) (string, error) {

	params := make(map[string]string)

	i := 1
	for k, v := range items {
		if i < 11 {
			key := fmt.Sprintf("Item.%d.ASIN", i)
			params[key] = string(k)

			key = fmt.Sprintf("Item.%d.Quantity", i)
			params[key] = strconv.Itoa(v)

			i++
		} else {
			break
		}
	}

	return api.genSignAndFetch("CartCreate", params)
}

/*
CartClear takes a CartId and HMAC that were returned when generating a cart
It then removes the contents of the cart
*/
func (api AmazonProductAPI) CartClear(CartId, HMAC string) (string, error) {

	params := map[string]string{
		"CartId": CartId,
		"HMAC":   HMAC,
	}

	return api.genSignAndFetch("CartClear", params)
}

/*
Cart get takes a CartID and HMAC that were returned when generaing a cart
Returns the contents of the specified cart
*/
func (api AmazonProductAPI) CartGet(CartId, HMAC string) (string, error) {

	params := map[string]string{
		"CartId": CartId,
		"HMAC":   HMAC,
	}

	return api.genSignAndFetch("CartGet", params)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/modcloth-labs/amqp-tee"
	"github.com/streadway/amqp"
)

var (
	logger = log.New(os.Stderr, "[amqp-tee] ", log.LstdFlags)

	databaseDriverFlag string
	databaseUriFlag    string
	amqpUriFlag        string
	queueNameFlag      string
)

func init() {
	usageString := `Usage: %s [options]
	Consumes messages from RabbitMQ and writes to database.
`
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, usageString, filepath.Base(os.Args[0]))
		flag.PrintDefaults()
	}
	flag.StringVar(&databaseDriverFlag, "database-driver", "sqlite3", "Database driver to use (possible values: sqlite3, mysql, postgres)")
	flag.StringVar(&databaseUriFlag, "database-uri", "messages.db", "Database uri")
	flag.StringVar(&amqpUriFlag, "amqp-uri", "amqp://guest:guest@localhost:5672/", "AMQP connection URI")
	flag.StringVar(&queueNameFlag, "queue", "default", "Queue to consume from (also name of table written to)")
	flag.Parse()
}

func main() {
	var (
		amqpConsumer  *amqptee.AMQPConsumer
		deliveryStore *amqptee.DeliveryStore
		err           error
	)

	if deliveryStore, err = amqptee.NewDeliveryStore(databaseDriverFlag, databaseUriFlag, queueNameFlag); err != nil {
		log.Printf("Could not create message store: %s", err)
		os.Exit(1)
	}

	defer deliveryStore.Close()

	if amqpConsumer, err = amqptee.NewAMQPConsumer(amqpUriFlag, queueNameFlag); err != nil {
		log.Printf("Could not connect to RabbitMQ: %s", err)
		os.Exit(1)
	}
	defer amqpConsumer.Close()

	amqpConsumer.Consume(func(delivery *amqp.Delivery) (err error) {
		log.Printf("Consuming %+v", delivery)

		if err = deliveryStore.Store(delivery); err != nil {
			return err
		}

		return nil
	})

}
<commit_msg>Log errors occurring during message consumption<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/modcloth-labs/amqp-tee"
	"github.com/streadway/amqp"
)

var (
	logger = log.New(os.Stderr, "[amqp-tee] ", log.LstdFlags)

	databaseDriverFlag string
	databaseUriFlag    string
	amqpUriFlag        string
	queueNameFlag      string
)

func init() {
	usageString := `Usage: %s [options]
	Consumes messages from RabbitMQ and writes to database.
`
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, usageString, filepath.Base(os.Args[0]))
		flag.PrintDefaults()
	}
	flag.StringVar(&databaseDriverFlag, "database-driver", "sqlite3", "Database driver to use (possible values: sqlite3, mysql, postgres)")
	flag.StringVar(&databaseUriFlag, "database-uri", "messages.db", "Database uri")
	flag.StringVar(&amqpUriFlag, "amqp-uri", "amqp://guest:guest@localhost:5672/", "AMQP connection URI")
	flag.StringVar(&queueNameFlag, "queue", "default", "Queue to consume from (also name of table written to)")
	flag.Parse()
}

func main() {
	var (
		amqpConsumer  *amqptee.AMQPConsumer
		deliveryStore *amqptee.DeliveryStore
		err           error
	)

	if deliveryStore, err = amqptee.NewDeliveryStore(databaseDriverFlag, databaseUriFlag, queueNameFlag); err != nil {
		log.Printf("Could not create message store: %s", err)
		os.Exit(1)
	}

	defer deliveryStore.Close()

	if amqpConsumer, err = amqptee.NewAMQPConsumer(amqpUriFlag, queueNameFlag); err != nil {
		log.Printf("Could not connect to RabbitMQ: %s", err)
		os.Exit(1)
	}
	defer amqpConsumer.Close()

	if err = amqpConsumer.Consume(func(delivery *amqp.Delivery) (err error) {
		log.Printf("Consuming %+v", delivery)

		if err = deliveryStore.Store(delivery); err != nil {
			return err
		}

		return nil
	}); err != nil {
		log.Printf("Could not store message: %s", err)
		os.Exit(1)
	}

}
<|endoftext|>


<commit_before>package app

import (
	"encoding/json"
	"log"
	"math/rand"
	"net"
	"net/http"
	"time"
)

type Constraint struct {
	Constraint string `json:"constraint"`
	Ip         string `json:"ip"`
}

var loadedConstraints []*Constraint
var globalConstraint = &Constraint{Ip: "all", Constraint: "none"}

func findConstraint(ip string, defaultGlobal bool) (*Constraint, error) {
	for _, v := range loadedConstraints {
		if v.Ip == ip {
			return v, nil
		}
	}

	if defaultGlobal {
		return globalConstraint, nil
	} else {
		constraint := &Constraint{Ip: ip, Constraint: "none"}
		loadedConstraints = append(loadedConstraints, constraint)
		return constraint, nil
	}
}

func applyConstraints(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ip, _, _ := net.SplitHostPort(r.RemoteAddr)
		constraint, _ := findConstraint(ip, true)

		if r.URL.Path == "/upgrade" {
			fn(w, r)
			return
		}

		request_id := requestId(r)
		log.Printf("count#constraints method=%s path=%s constraint=%s request_id=%s ip=%s", r.Method, r.URL.Path, constraint.Constraint, request_id, constraint.Ip)
		switch constraint.Constraint {
		case "maintenance":
			maintenance(w, r)
			return
		case "slow":
			slow(fn, w, r)
			return
		case "erroring":
			erroring(fn, w, r)
			return
		}
		fn(w, r)
	}
}

func maintenance(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(503)
	response := &ErrorResponse{Id: "maintenance", Message: "API is temporarily unavailable."}

	if err := json.NewEncoder(w).Encode(response); err != nil {
		panic(err)
	}
	request_id := requestId(r)
	log.Printf("count#http.maintenance method=%s path=%s request_id=%s", r.Method, r.URL.Path, request_id)
}

func slow(fn http.HandlerFunc, w http.ResponseWriter, r *http.Request) {

	rand.Seed(time.Now().Unix())
	duration := rand.Intn(60-30) + 30

	request_id := requestId(r)
	log.Printf("count#http.slow method=%s path=%s duration=%d request_id=&s", r.Method, r.URL.Path, duration, request_id)
	time.Sleep(time.Duration(duration) * time.Second)
	fn(w, r)
}

func erroring(fn http.HandlerFunc, w http.ResponseWriter, r *http.Request) {

	rand.Seed(time.Now().Unix())
	randomizer := rand.Intn(10)

	if randomizer >= 7 {
		fn(w, r)
	} else {
		request_id := requestId(r)
		log.Printf("count#http.error method=%s path=%s request_id=%s", r.Method, r.URL.Path, request_id)

		w.WriteHeader(500)
		response := &ErrorResponse{Id: "error", Message: "An unknown error occured."}
		if err := json.NewEncoder(w).Encode(response); err != nil {
			panic(err)
		}
	}
}
<commit_msg>s/&/%<commit_after>package app

import (
	"encoding/json"
	"log"
	"math/rand"
	"net"
	"net/http"
	"time"
)

type Constraint struct {
	Constraint string `json:"constraint"`
	Ip         string `json:"ip"`
}

var loadedConstraints []*Constraint
var globalConstraint = &Constraint{Ip: "all", Constraint: "none"}

func findConstraint(ip string, defaultGlobal bool) (*Constraint, error) {
	for _, v := range loadedConstraints {
		if v.Ip == ip {
			return v, nil
		}
	}

	if defaultGlobal {
		return globalConstraint, nil
	} else {
		constraint := &Constraint{Ip: ip, Constraint: "none"}
		loadedConstraints = append(loadedConstraints, constraint)
		return constraint, nil
	}
}

func applyConstraints(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ip, _, _ := net.SplitHostPort(r.RemoteAddr)
		constraint, _ := findConstraint(ip, true)

		if r.URL.Path == "/upgrade" {
			fn(w, r)
			return
		}

		request_id := requestId(r)
		log.Printf("count#constraints method=%s path=%s constraint=%s request_id=%s ip=%s", r.Method, r.URL.Path, constraint.Constraint, request_id, constraint.Ip)
		switch constraint.Constraint {
		case "maintenance":
			maintenance(w, r)
			return
		case "slow":
			slow(fn, w, r)
			return
		case "erroring":
			erroring(fn, w, r)
			return
		}
		fn(w, r)
	}
}

func maintenance(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(503)
	response := &ErrorResponse{Id: "maintenance", Message: "API is temporarily unavailable."}

	if err := json.NewEncoder(w).Encode(response); err != nil {
		panic(err)
	}
	request_id := requestId(r)
	log.Printf("count#http.maintenance method=%s path=%s request_id=%s", r.Method, r.URL.Path, request_id)
}

func slow(fn http.HandlerFunc, w http.ResponseWriter, r *http.Request) {

	rand.Seed(time.Now().Unix())
	duration := rand.Intn(60-30) + 30

	request_id := requestId(r)
	log.Printf("count#http.slow method=%s path=%s duration=%d request_id=%s", r.Method, r.URL.Path, duration, request_id)
	time.Sleep(time.Duration(duration) * time.Second)
	fn(w, r)
}

func erroring(fn http.HandlerFunc, w http.ResponseWriter, r *http.Request) {

	rand.Seed(time.Now().Unix())
	randomizer := rand.Intn(10)

	if randomizer >= 7 {
		fn(w, r)
	} else {
		request_id := requestId(r)
		log.Printf("count#http.error method=%s path=%s request_id=%s", r.Method, r.URL.Path, request_id)

		w.WriteHeader(500)
		response := &ErrorResponse{Id: "error", Message: "An unknown error occured."}
		if err := json.NewEncoder(w).Encode(response); err != nil {
			panic(err)
		}
	}
}
<|endoftext|>


<commit_before>package feed

// based on http://siongui.github.io/2015/03/03/go-parse-web-feed-rss-atom/

import (
	"bufio"
	"bytes"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"strings"
	"time"

	log "github.com/go-pkgz/lgr"
	"github.com/pkg/errors"
)

// Rss2 feed
type Rss2 struct {
	XMLName xml.Name `xml:"rss"`
	Version string   `xml:"version,attr"`

	Title         string `xml:"channel>title"`
	Language      string `xml:"channel>lang"`
	Link          string `xml:"channel>link"`
	Description   string `xml:"channel>description"`
	PubDate       string `xml:"channel>pubDate"`
	LastBuildDate string `xml:"channel>lastBuildDate"`

	ItemList []Item `xml:"channel>item"`
}

// Item for rss
type Item struct {
	// Required
	Title       string        `xml:"title"`
	Link        string        `xml:"link"`
	Description template.HTML `xml:"description"`
	// Optional
	Content   template.HTML `xml:"encoded"`
	PubDate   string        `xml:"pubDate"`
	Comments  string        `xml:"comments"`
	Enclosure Enclosure     `xml:"enclosure"`
	GUID      string        `xml:"guid"`

	// Internal
	DT time.Time `xml:"-"`
}

// Enclosure element from item
type Enclosure struct {
	URL    string `xml:"url,attr"`
	Length int    `xml:"length,attr"`
	Type   string `xml:"type,attr"`
}

// Atom1 is atom feed
type Atom1 struct {
	XMLName   xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
	Title     string   `xml:"title"`
	Subtitle  string   `xml:"subtitle"`
	ID        string   `xml:"id"`
	Updated   string   `xml:"updated"`
	Rights    string   `xml:"rights"`
	Link      Link     `xml:"link"`
	Author    Author   `xml:"author"`
	EntryList []Entry  `xml:"entry"`
}

// Link element for xml
type Link struct {
	Href string `xml:"href,attr"`
}

// Author element for xml
type Author struct {
	Name  string `xml:"name"`
	Email string `xml:"email"`
}

// Entry from atom
type Entry struct {
	Title     string    `xml:"title"`
	Summary   string    `xml:"summary"`
	Content   string    `xml:"content"`
	ID        string    `xml:"id"`
	Updated   string    `xml:"updated"`
	Link      Link      `xml:"link"`
	Author    Author    `xml:"author"`
	Enclosure Enclosure `xml:"enclosure"`
}

// Parse gets url to rss feed and returns Rss2 items
func Parse(uri string) (result Rss2, err error) {
	resp, err := http.Get(uri) // nolint
	if err != nil {
		return result, err
	}
	defer func() {
		if e := resp.Body.Close(); e != nil {
			log.Printf("[WARN] failed to close body, %s", e)
		}
	}()

	var b bytes.Buffer
	bwriter := bufio.NewWriter(&b)
	if _, err = io.Copy(bwriter, resp.Body); err != nil {
		return result, err
	}

	result, e := parseFeedContent(b.Bytes())
	if e != nil {
		return Rss2{}, errors.Wrap(err, "parsing error")
	}

	return result.Normalize()
}

func atom1ToRss2(a Atom1) Rss2 {
	r := Rss2{
		Title:       a.Title,
		Link:        a.Link.Href,
		Description: a.Subtitle,
		PubDate:     a.Updated,
	}
	r.ItemList = make([]Item, len(a.EntryList))
	for i, entry := range a.EntryList {
		r.ItemList[i].Title = entry.Title
		r.ItemList[i].Link = entry.Link.Href
		if entry.Content == "" {
			r.ItemList[i].Description = template.HTML(entry.Summary) // nolint
		} else {
			r.ItemList[i].Description = template.HTML(entry.Content) // nolint
		}
	}
	return r
}

const atomErrStr = "expected element type <rss> but have <feed>"

func parseAtom(content []byte) (Rss2, error) {
	a := Atom1{}
	err := xml.Unmarshal(content, &a)
	if err != nil {
		return Rss2{}, errors.Wrap(err, "can't parse atom1")
	}
	return atom1ToRss2(a), nil
}

func parseFeedContent(content []byte) (Rss2, error) {
	v := Rss2{}
	err := xml.Unmarshal(content, &v)
	if err != nil {
		if err.Error() == atomErrStr {
			// try Atom 1.0
			return parseAtom(content)
		}
		return v, errors.Wrap(err, "can't parse feed content")
	}

	if v.Version == "2.0" {
		// RSS 2.0
		for i := range v.ItemList {
			if v.ItemList[i].Content != "" {
				v.ItemList[i].Description = v.ItemList[i].Content
			}
		}
		return v, nil
	}

	return v, errors.New("not RSS 2.0")
}

// Normalize converts to RFC822 = "02 Jan 06 15:04 MST"
func (rss *Rss2) Normalize() (Rss2, error) {

	dt, err := rss.normalizeDate(rss.LastBuildDate)
	if err != nil {
		dt, err = rss.normalizeDate(rss.PubDate)
	}

	if err == nil {
		rss.PubDate = dt.Format(time.RFC1123Z)
	}

	for i, item := range rss.ItemList {
		if dt, err := rss.normalizeDate(item.PubDate); err == nil {
			rss.ItemList[i].DT = dt
			rss.ItemList[i].PubDate = dt.Format(time.RFC1123Z)
		}
		rss.ItemList[i].Title = strings.Replace(item.Title, "\n", "", -1)
		rss.ItemList[i].Title = strings.TrimSpace(rss.ItemList[i].Title)
	}
	return *rss, nil
}

func (rss *Rss2) normalizeDate(dt string) (time.Time, error) {
	if dt == "" {
		return time.Now(), fmt.Errorf("can't normalize empty pubDate")
	}
	if ts, err := time.Parse(time.RFC1123, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse(time.RFC822, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse(time.RFC822Z, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse(time.RFC1123Z, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse(time.RFC1123, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse("2006-01-02 15:04:05 -0700", dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse("02 Jan 06 15:04 -0700", dt); err == nil {
		return ts, nil
	}
	log.Printf("[DEBUG] can't normalize %s", dt)
	return time.Now(), fmt.Errorf("can't normalize %s", dt)
}
<commit_msg>lint: feed package comment<commit_after>// Package feed describes RSS format and provides parsing
package feed

// based on http://siongui.github.io/2015/03/03/go-parse-web-feed-rss-atom/

import (
	"bufio"
	"bytes"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"strings"
	"time"

	log "github.com/go-pkgz/lgr"
	"github.com/pkg/errors"
)

// Rss2 feed
type Rss2 struct {
	XMLName       xml.Name `xml:"rss"`
	Version       string   `xml:"version,attr"`
	Title         string   `xml:"channel>title"`
	Language      string   `xml:"channel>lang"`
	Link          string   `xml:"channel>link"`
	Description   string   `xml:"channel>description"`
	PubDate       string   `xml:"channel>pubDate"`
	LastBuildDate string   `xml:"channel>lastBuildDate"`

	ItemList []Item `xml:"channel>item"`
}

// Item for rss
type Item struct {
	// Required
	Title       string        `xml:"title"`
	Link        string        `xml:"link"`
	Description template.HTML `xml:"description"`
	// Optional
	Content   template.HTML `xml:"encoded"`
	PubDate   string        `xml:"pubDate"`
	Comments  string        `xml:"comments"`
	Enclosure Enclosure     `xml:"enclosure"`
	GUID      string        `xml:"guid"`

	// Internal
	DT time.Time `xml:"-"`
}

// Enclosure element from item
type Enclosure struct {
	URL    string `xml:"url,attr"`
	Length int    `xml:"length,attr"`
	Type   string `xml:"type,attr"`
}

// Atom1 is atom feed
type Atom1 struct {
	XMLName   xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
	Title     string   `xml:"title"`
	Subtitle  string   `xml:"subtitle"`
	ID        string   `xml:"id"`
	Updated   string   `xml:"updated"`
	Rights    string   `xml:"rights"`
	Link      Link     `xml:"link"`
	Author    Author   `xml:"author"`
	EntryList []Entry  `xml:"entry"`
}

// Link element for xml
type Link struct {
	Href string `xml:"href,attr"`
}

// Author element for xml
type Author struct {
	Name  string `xml:"name"`
	Email string `xml:"email"`
}

// Entry from atom
type Entry struct {
	Title     string    `xml:"title"`
	Summary   string    `xml:"summary"`
	Content   string    `xml:"content"`
	ID        string    `xml:"id"`
	Updated   string    `xml:"updated"`
	Link      Link      `xml:"link"`
	Author    Author    `xml:"author"`
	Enclosure Enclosure `xml:"enclosure"`
}

// Parse gets url to rss feed and returns Rss2 items
func Parse(uri string) (result Rss2, err error) {
	resp, err := http.Get(uri) // nolint
	if err != nil {
		return result, err
	}
	defer func() {
		if e := resp.Body.Close(); e != nil {
			log.Printf("[WARN] failed to close body, %s", e)
		}
	}()

	var b bytes.Buffer
	bwriter := bufio.NewWriter(&b)
	if _, err = io.Copy(bwriter, resp.Body); err != nil {
		return result, err
	}

	result, e := parseFeedContent(b.Bytes())
	if e != nil {
		return Rss2{}, errors.Wrap(err, "parsing error")
	}

	return result.Normalize()
}

func atom1ToRss2(a Atom1) Rss2 {
	r := Rss2{
		Title:       a.Title,
		Link:        a.Link.Href,
		Description: a.Subtitle,
		PubDate:     a.Updated,
	}
	r.ItemList = make([]Item, len(a.EntryList))
	for i, entry := range a.EntryList {
		r.ItemList[i].Title = entry.Title
		r.ItemList[i].Link = entry.Link.Href
		if entry.Content == "" {
			r.ItemList[i].Description = template.HTML(entry.Summary) // nolint
		} else {
			r.ItemList[i].Description = template.HTML(entry.Content) // nolint
		}
	}
	return r
}

const atomErrStr = "expected element type <rss> but have <feed>"

func parseAtom(content []byte) (Rss2, error) {
	a := Atom1{}
	err := xml.Unmarshal(content, &a)
	if err != nil {
		return Rss2{}, errors.Wrap(err, "can't parse atom1")
	}
	return atom1ToRss2(a), nil
}

func parseFeedContent(content []byte) (Rss2, error) {
	v := Rss2{}
	err := xml.Unmarshal(content, &v)
	if err != nil {
		if err.Error() == atomErrStr {
			// try Atom 1.0
			return parseAtom(content)
		}
		return v, errors.Wrap(err, "can't parse feed content")
	}

	if v.Version == "2.0" {
		// RSS 2.0
		for i := range v.ItemList {
			if v.ItemList[i].Content != "" {
				v.ItemList[i].Description = v.ItemList[i].Content
			}
		}
		return v, nil
	}

	return v, errors.New("not RSS 2.0")
}

// Normalize converts to RFC822 = "02 Jan 06 15:04 MST"
func (rss *Rss2) Normalize() (Rss2, error) {

	dt, err := rss.normalizeDate(rss.LastBuildDate)
	if err != nil {
		dt, err = rss.normalizeDate(rss.PubDate)
	}

	if err == nil {
		rss.PubDate = dt.Format(time.RFC1123Z)
	}

	for i, item := range rss.ItemList {
		if dt, err := rss.normalizeDate(item.PubDate); err == nil {
			rss.ItemList[i].DT = dt
			rss.ItemList[i].PubDate = dt.Format(time.RFC1123Z)
		}
		rss.ItemList[i].Title = strings.Replace(item.Title, "\n", "", -1)
		rss.ItemList[i].Title = strings.TrimSpace(rss.ItemList[i].Title)
	}
	return *rss, nil
}

func (rss *Rss2) normalizeDate(dt string) (time.Time, error) {
	if dt == "" {
		return time.Now(), fmt.Errorf("can't normalize empty pubDate")
	}
	if ts, err := time.Parse(time.RFC1123, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse(time.RFC822, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse(time.RFC822Z, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse(time.RFC1123Z, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse(time.RFC1123, dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse("2006-01-02 15:04:05 -0700", dt); err == nil {
		return ts, nil
	}
	if ts, err := time.Parse("02 Jan 06 15:04 -0700", dt); err == nil {
		return ts, nil
	}
	log.Printf("[DEBUG] can't normalize %s", dt)
	return time.Now(), fmt.Errorf("can't normalize %s", dt)
}
<|endoftext|>