<commit_before>//
// Copyright 2018, Patrick Webster
//
// 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 gitlab

import (
	"fmt"
	"net/url"
)

// GroupVariablesService handles communication with the
// group variables related methods of the GitLab API.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html
type GroupVariablesService struct {
	client *Client
}

// GroupVariable represents a GitLab group Variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html
type GroupVariable struct {
	Key          string            `json:"key"`
	Value        string            `json:"value"`
	VariableType VariableTypeValue `json:"variable_type"`
	Protected    bool              `json:"protected"`
}

func (v GroupVariable) String() string {
	return Stringify(v)
}

// ListGroupVariablesOptions represents the available options for listing variables
// for a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#list-group-variables
type ListGroupVariablesOptions ListOptions

// ListVariables gets a list of all variables for a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#list-group-variables
func (s *GroupVariablesService) ListVariables(gid interface{}, opt *ListGroupVariablesOptions, options ...OptionFunc) ([]*GroupVariable, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/variables", pathEscape(group))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var vs []*GroupVariable
	resp, err := s.client.Do(req, &vs)
	if err != nil {
		return nil, resp, err
	}

	return vs, resp, err
}

// GetVariable gets a variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#show-variable-details
func (s *GroupVariablesService) GetVariable(gid interface{}, key string, options ...OptionFunc) (*GroupVariable, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	v := new(GroupVariable)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// CreateGroupVariableOptions represents the available CreateVariable()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#create-variable
type CreateGroupVariableOptions struct {
	Key          *string            `url:"key,omitempty" json:"key,omitempty"`
	Value        *string            `url:"value,omitempty" json:"value,omitempty"`
	VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
	Protected    *bool              `url:"protected,omitempty" json:"protected,omitempty"`
}

// CreateVariable creates a new group variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#create-variable
func (s *GroupVariablesService) CreateVariable(gid interface{}, opt *CreateGroupVariableOptions, options ...OptionFunc) (*GroupVariable, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/variables", pathEscape(group))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	v := new(GroupVariable)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// UpdateGroupVariableOptions represents the available UpdateVariable()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#update-variable
type UpdateGroupVariableOptions struct {
	Value        *string            `url:"value,omitempty" json:"value,omitempty"`
	VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
	Protected    *bool              `url:"protected,omitempty" json:"protected,omitempty"`
}

// UpdateVariable updates the position of an existing
// group issue board list.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#update-variable
func (s *GroupVariablesService) UpdateVariable(gid interface{}, key string, opt *UpdateGroupVariableOptions, options ...OptionFunc) (*GroupVariable, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	v := new(GroupVariable)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// RemoveVariable removes a group's variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#remove-variable
func (s *GroupVariablesService) RemoveVariable(gid interface{}, key string, options ...OptionFunc) (*Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<commit_msg>enhc(group_variable): add masked property<commit_after>//
// Copyright 2018, Patrick Webster
//
// 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 gitlab

import (
	"fmt"
	"net/url"
)

// GroupVariablesService handles communication with the
// group variables related methods of the GitLab API.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html
type GroupVariablesService struct {
	client *Client
}

// GroupVariable represents a GitLab group Variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html
type GroupVariable struct {
	Key          string            `json:"key"`
	Value        string            `json:"value"`
	VariableType VariableTypeValue `json:"variable_type"`
	Protected    bool              `json:"protected"`
	Masked       bool              `json:"masked"`
}

func (v GroupVariable) String() string {
	return Stringify(v)
}

// ListGroupVariablesOptions represents the available options for listing variables
// for a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#list-group-variables
type ListGroupVariablesOptions ListOptions

// ListVariables gets a list of all variables for a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#list-group-variables
func (s *GroupVariablesService) ListVariables(gid interface{}, opt *ListGroupVariablesOptions, options ...OptionFunc) ([]*GroupVariable, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/variables", pathEscape(group))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var vs []*GroupVariable
	resp, err := s.client.Do(req, &vs)
	if err != nil {
		return nil, resp, err
	}

	return vs, resp, err
}

// GetVariable gets a variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#show-variable-details
func (s *GroupVariablesService) GetVariable(gid interface{}, key string, options ...OptionFunc) (*GroupVariable, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	v := new(GroupVariable)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// CreateGroupVariableOptions represents the available CreateVariable()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#create-variable
type CreateGroupVariableOptions struct {
	Key          *string            `url:"key,omitempty" json:"key,omitempty"`
	Value        *string            `url:"value,omitempty" json:"value,omitempty"`
	VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
	Protected    *bool              `url:"protected,omitempty" json:"protected,omitempty"`
	Masked       *bool              `url:"masked,omitempty" json:"masked,omitempty"`
}

// CreateVariable creates a new group variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#create-variable
func (s *GroupVariablesService) CreateVariable(gid interface{}, opt *CreateGroupVariableOptions, options ...OptionFunc) (*GroupVariable, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/variables", pathEscape(group))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	v := new(GroupVariable)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// UpdateGroupVariableOptions represents the available UpdateVariable()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#update-variable
type UpdateGroupVariableOptions struct {
	Value        *string            `url:"value,omitempty" json:"value,omitempty"`
	VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
	Protected    *bool              `url:"protected,omitempty" json:"protected,omitempty"`
	Masked       *bool              `url:"masked,omitempty" json:"masked,omitempty"`
}

// UpdateVariable updates the position of an existing
// group issue board list.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#update-variable
func (s *GroupVariablesService) UpdateVariable(gid interface{}, key string, opt *UpdateGroupVariableOptions, options ...OptionFunc) (*GroupVariable, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	v := new(GroupVariable)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// RemoveVariable removes a group's variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/group_level_variables.html#remove-variable
func (s *GroupVariablesService) RemoveVariable(gid interface{}, key string, options ...OptionFunc) (*Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<|endoftext|>


<commit_before>// This file is used only when compiled with GAE support.
// GAE-based backend serves only templates.

// +build appengine

package main

import (
	"net/http"
	"path"
	"strings"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
	"google.golang.org/appengine/user"
)

// allow requests prefixed with passthruPrefix to bypass checkWhitelist
var passthruPrefix string

func init() {
	if err := initConfig("server.config", ""); err != nil {
		panic("initConfig: " + err.Error())
	}
	passthruPrefix = path.Join(config.Prefix, "/sync") + "/"
	// use built-in memcache service
	cache = &gaeMemcache{}
	// apps hosted on GAE use a different HTTP transport
	httpTransport = func(c context.Context) http.RoundTripper {
		return &urlfetch.Transport{
			Context:  c,
			Deadline: 10 * time.Second,
		}
	}
	// staging instance is accessed only by whitelisted people/domains
	if len(config.Whitelist) > 0 {
		wrapHandler = checkWhitelist
	}
	rootHandleFn = serveTemplate
	registerHandlers()
}

// allowPassthrough returns true if the request r can be handled w/o whitelist check.
// Currently, only GAE Cron and Task Queue jobs are allowed.
func allowPassthrough(r *http.Request) bool {
	return strings.HasPrefix(r.URL.Path, passthruPrefix) ||
		r.Header.Get("x-appengine-cron") == "true" ||
		r.Header.Get("x-appengine-taskname") != ""
}

// checkWhitelist checks whether the current user is allowed to access
// handler h using isWhitelisted() func before handing over in-flight request.
// It redirects to GAE login URL if no user found or responds with 403
// (Forbidden) HTTP error code if the current user is not whitelisted.
func checkWhitelist(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if allowPassthrough(r) {
			h.ServeHTTP(w, r)
			return
		}
		c := appengine.NewContext(r)
		u := user.Current(c)
		switch {
		case u != nil && isWhitelisted(u.Email):
			h.ServeHTTP(w, r)
		case u != nil:
			errorf(c, "%s is not whitelisted", u.Email)
			http.Error(w, "Access denied, sorry. Try with a different account.", http.StatusForbidden)
		default:
			url, err := user.LoginURL(c, r.URL.Path)
			if err != nil {
				errorf(c, "user.LoginURL(%q): %v", r.URL.Path, err)
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			http.Redirect(w, r, url, http.StatusFound)
		}
	})
}

// newContext returns a context of the in-flight request r.
func newContext(r *http.Request) context.Context {
	return appengine.NewContext(r)
}

// logf logs an info message using appengine's context.
func logf(c context.Context, format string, args ...interface{}) {
	log.Infof(c, format, args...)
}

// errorf logs an error message using appengine's context.
func errorf(c context.Context, format string, args ...interface{}) {
	log.Errorf(c, format, args...)
}
<commit_msg>Multiple bypass prefixes for staging server<commit_after>// This file is used only when compiled with GAE support.
// GAE-based backend serves only templates.

// +build appengine

package main

import (
	"net/http"
	"path"
	"strings"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
	"google.golang.org/appengine/user"
)

// allow requests prefixed with passthruPrefixes to bypass checkWhitelist
var passthruPrefixes = []string{
	"/sync",
	"/api/v1/user",
}

func init() {
	if err := initConfig("server.config", ""); err != nil {
		panic("initConfig: " + err.Error())
	}
	// prepend config.Prefix to bypass prefixes
	for i, p := range passthruPrefixes {
		passthruPrefixes[i] = path.Join(config.Prefix, p)
	}
	// use built-in memcache service
	cache = &gaeMemcache{}
	// apps hosted on GAE use a different HTTP transport
	httpTransport = func(c context.Context) http.RoundTripper {
		return &urlfetch.Transport{
			Context:  c,
			Deadline: 10 * time.Second,
		}
	}
	// allow access only by whitelisted people/domains if not empty
	if len(config.Whitelist) > 0 {
		wrapHandler = checkWhitelist
	}
	rootHandleFn = serveTemplate
	registerHandlers()
}

// allowPassthrough returns true if the request r can be handled w/o whitelist check.
// Currently, only GAE Cron and Task Queue jobs are allowed.
func allowPassthrough(r *http.Request) bool {
	if r.Header.Get("x-appengine-cron") == "true" || r.Header.Get("x-appengine-taskname") != "" {
		return true
	}
	for _, p := range passthruPrefixes {
		if strings.HasPrefix(r.URL.Path, p) {
			return true
		}
	}
	return false
}

// checkWhitelist checks whether the current user is allowed to access
// handler h using isWhitelisted() func before handing over in-flight request.
// It redirects to GAE login URL if no user found or responds with 403
// (Forbidden) HTTP error code if the current user is not whitelisted.
func checkWhitelist(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if allowPassthrough(r) {
			h.ServeHTTP(w, r)
			return
		}
		c := appengine.NewContext(r)
		u := user.Current(c)
		switch {
		case u != nil && isWhitelisted(u.Email):
			h.ServeHTTP(w, r)
		case u != nil:
			errorf(c, "%s is not whitelisted", u.Email)
			http.Error(w, "Access denied, sorry. Try with a different account.", http.StatusForbidden)
		default:
			url, err := user.LoginURL(c, r.URL.Path)
			if err != nil {
				errorf(c, "user.LoginURL(%q): %v", r.URL.Path, err)
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			http.Redirect(w, r, url, http.StatusFound)
		}
	})
}

// newContext returns a context of the in-flight request r.
func newContext(r *http.Request) context.Context {
	return appengine.NewContext(r)
}

// logf logs an info message using appengine's context.
func logf(c context.Context, format string, args ...interface{}) {
	log.Infof(c, format, args...)
}

// errorf logs an error message using appengine's context.
func errorf(c context.Context, format string, args ...interface{}) {
	log.Errorf(c, format, args...)
}
<|endoftext|>


<commit_before>package handlers

import (
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"sort"
	"strings"

	"github.com/nelsonleduc/calmanbot/config"

	"github.com/nelsonleduc/calmanbot/cache"
	"github.com/nelsonleduc/calmanbot/handlers/models"
	"github.com/nelsonleduc/calmanbot/service"
	"github.com/nelsonleduc/calmanbot/utility"
)

func HandleCalman(message service.Message, providedService service.Service, cache cache.QueryCache, repo models.Repo) {
	verboseLog := config.Configuration().VerboseMode()

	if verboseLog {
		fmt.Printf("[BEGIN] Message came in: \"%v\"\n", message.Text())
	}

	if message.UserType() != "user" {
		if verboseLog {
			fmt.Println("Not from user -- aborting!")
		}
		return
	}

	bot, _ := repo.FetchBot(message.BotGroupID())
	if overrideBotName := config.Configuration().OverrideBotName(); len(overrideBotName) > 0 {
		bot = models.Bot{
			GroupName:     "DEBUG_OVERRIDE",
			GroupID:       "DEBUG_OVERRIDE",
			BotNameString: overrideBotName,
			Key:           "DEBUG_OVERRIDE",
		}
	}

	if verboseLog {
		fmt.Printf("Fetched bot %#v\n", bot)
		fmt.Printf("---> Names %#v\n", bot.SanitizedBotNames())
	}

	// Make sure the message has the bot's name with a preceeding character, and that it isn't escaped
	index := -1
	for _, name := range bot.SanitizedBotNames() {
		nameIndex := strings.Index(strings.ToLower(message.Text()), strings.ToLower(name))
		if nameIndex != -1 {
			index = nameIndex
		}
	}
	isEscaped := (index >= 2 && message.Text()[index-2] == '\\')
	if isEscaped {
		if verboseLog {
			fmt.Println("Usable name not found -- aborting!")
		}
		return
	}

	var (
		postString    string
		rawPostString string
		act           models.Action
	)

	if handled, result := processBuiltins(providedService, message, bot, cache, repo); handled {
		providedService.NoteProcessing(message)
		postString = result
	} else {
		if cached := cache.CachedResponse(message.Text()); cached != nil {
			postString = *cached
		} else {
			postString, act = responseForMessage(providedService, message, bot, repo)
		}

		rawPostString = postString
		postString = updatedPostText(act, postString)
		postString = utility.ProcessedString(postString)
	}

	cacheID := cache.CacheQuery(message.Text(), postString)

	fmt.Printf("Query: %v\n", message.Text())
	if postString != "" {
		postType := postTypeForAction(act)
		fmt.Printf("Action: %v\n", act.Content)
		fmt.Printf("Type: %v\n", postType)
		fmt.Printf("Posting: %v\n\n", postString)
		providedService.Post(service.Post{Key: bot.Key, Text: postString, RawText: rawPostString, Type: postType, CacheID: cacheID}, message)
	} else if verboseLog {
		fmt.Print("Empty post string -- aborting!\n\n")
	}
}

func processBuiltins(service service.Service, message service.Message, bot models.Bot, cache cache.QueryCache, repo models.Repo) (bool, string) {
	verboseMode := config.Configuration().VerboseMode()
	if verboseMode {
		fmt.Println("Running builtins")
	}
	for _, b := range builtins {
		if !b.featureChecker(service) {
			if verboseMode {
				fmt.Printf("   Service does not support \"%v\"\n", b.trigger)
			}
			continue
		}
		if verboseMode {
			fmt.Printf("   Check \"%v\"\n", b.trigger)
		}
		for _, name := range bot.BotNames() {
			reg, _ := regexp.Compile("(?i)@" + name + " * !" + b.trigger)
			matched := reg.FindStringSubmatch(message.Text())
			if len(matched) > 1 && matched[1] != "" {
				if verboseMode {
					fmt.Printf("      Matched \"%+v\"\n", reg)
					fmt.Printf("      Name \"%v\"\n", name)
				}
				return true, b.handler(matched, builtInParams{bot, cache, repo, service})
			}
		}
	}

	if verboseMode {
		fmt.Println("   None match")
	}
	return false, ""
}

func responseForMessage(service service.Service, message service.Message, bot models.Bot, repo models.Repo) (string, models.Action) {
	triggerHandler, triggerErr := service.ServiceTriggerWrangler()
	actions, _ := repo.FetchActions(true, triggerErr == nil)
	sort.Sort(models.ByPriority(actions))

	verboseMode := config.Configuration().VerboseMode()
	if verboseMode {
		fmt.Println("Running actions")
	}

	var (
		act    models.Action
		sMatch string
	)
	found := false
	for _, a := range actions {
		if verboseMode {
			fmt.Printf("   Check \"%v\"\n", *a.Pattern)
		}
		for _, name := range bot.BotNames() {
			regexString := strings.Replace(*a.Pattern, "{_botname_}", name+" *", -1)
			r, _ := regexp.Compile("(?i)" + regexString)
			matched := r.FindStringSubmatch(message.Text())
			if len(matched) > 1 && matched[1] != "" {
				if verboseMode {
					fmt.Printf("      Matched \"%+v\"\n", r)
					fmt.Printf("      Name \"%v\"\n", name)
				}
				sMatch = matched[1]
				act = a
				found = true
				goto Exit
			}
		}
	}
Exit:

	if found && act.WantsImmediateNote() {
		service.NoteProcessing(message)
	}

	var (
		postString string
		err        error
	)
	for {
		if verboseMode {
			fmt.Printf("                 Start: \"%v\"\n", act.Content)
		}
		updateAction(&act, sMatch, message)
		if verboseMode {
			fmt.Printf("                Update: \"%v\"\n", act.Content)
		}
		if act.IsURLType() {
			postString, err = handleURLAction(act, triggerHandler, bot, message)
		} else if act.IsTriggerType() {
			postString, err = handleTriggerAction(act, triggerHandler, message)
		} else {
			postString = act.Content
		}

		if (err == nil && postString != "") || act.FallbackAction == nil {
			break
		} else {
			log.Printf("Failed: %s", act.Content)
			act, _ = repo.FetchAction(*act.FallbackAction)
			log.Printf("Fallback to: %s because %v", act.Content, err)
		}
	}

	if len(postString) > 0 && act.WantsPostingNote() {
		service.NoteProcessing(message)
	}

	return postString, act
}

func handleTriggerAction(action models.Action, triggerHandler service.TriggerWrangler, message service.Message) (string, error) {
	enableAction := strings.HasSuffix(action.ContentType, "ENABLE")
	disableAction := strings.HasSuffix(action.ContentType, "DISABLE")
	statusAction := strings.HasSuffix(action.ContentType, "STATUS")
	if !enableAction && !disableAction && !statusAction {
		return "", errors.New("invalid trigger type")
	}

	triggerName := action.Content
	r, _ := regexp.Compile(`(?i){_trigger\(([^\)]+)\)_}`)
	matched := r.FindStringSubmatch(action.Content)
	if len(matched) >= 2 {
		triggerName = matched[1]
	}

	verboseMode := config.Configuration().VerboseMode()

	if enableAction {
		if verboseMode {
			fmt.Printf("   EnableTrigger \"%v\"\n", triggerName)
		}
		triggerHandler.EnableTrigger(triggerName, message)
		return "Enabled", nil
	} else if statusAction {
		if verboseMode {
			fmt.Printf("   IsTriggerConfigured \"%v\"\n", triggerName)
		}
		if triggerHandler.IsTriggerConfiguredForGroup(triggerName, message) {
			return "Enabled", nil
		}
		return "Disabled", nil
	}

	if verboseMode {
		fmt.Printf("   DisableTrigger \"%v\"\n", triggerName)
	}
	triggerHandler.DisableTrigger(triggerName, message)
	return "Disabled", nil
}

func handleURLAction(a models.Action, triggerHandler service.TriggerWrangler, b models.Bot, message service.Message) (string, error) {
	url := a.Content
	r, _ := regexp.Compile(`(?i){_url\(([^\)]+)\)_}`)
	matched := r.FindStringSubmatch(a.Content)
	if len(matched) >= 2 {
		url = matched[1]
	}

	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	req.Header.Set("User-Agent", "CalmanBot/2.5.3")

	verboseMode := config.Configuration().VerboseMode()
	if verboseMode {
		fmt.Println("Sending URL request")
		fmt.Printf("   URL: %+v\n", *req)
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	content, _ := io.ReadAll(resp.Body)

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		log.Printf("Bad Response: %d length: %d", resp.StatusCode, len(content))
		return "", errors.New("bad response from server")
	}

	if verboseMode {
		fmt.Printf("   Content: %d bytes\n", len(content))
	}

	if a.IsTriggerType() && triggerHandler != nil {
		return handleTriggerAction(a, triggerHandler, message)
	}

	pathString := *a.DataPath

	if verboseMode {
		fmt.Printf("   Path: %v\n", pathString)
	}

	str := utility.ParseJSON(content, pathString, utility.LinearProvider)
	for i := 0; i < 3; i++ {
		if !utility.ValidateURL(str, a.IsImageType()) {
			fmt.Printf("Invalid URL: %v\n", str)
			str = utility.ParseJSON(content, pathString, utility.LinearProvider)
		} else {
			return str, nil
		}
	}

	return "", errors.New("failed to handle URL action")
}

func updateAction(a *models.Action, text string, message service.Message) {
	text = url.QueryEscape(text)

	a.Content = strings.Replace(a.Content, "{_text_}", text, -1)
	a.Content = strings.Replace(a.Content, "{_me_}", "localhost:"+config.Configuration().Port(), -1)
	a.Content = strings.Replace(a.Content, "{_groupid_}", url.QueryEscape(message.GroupID()), -1)
	a.Content = strings.Replace(a.Content, "{_serverid_}", url.QueryEscape(message.ServerID()), -1)
	a.Content = strings.Replace(a.Content, "{_groupname_}", url.QueryEscape(message.GroupName()), -1)

	r, _ := regexp.Compile(`(?i){_key\((.+)\)_}`)
	matched := r.FindStringSubmatch(a.Content)
	if len(matched) >= 2 {
		keyVal := os.Getenv(matched[1] + "_key")
		a.Content = strings.Replace(a.Content, matched[0], keyVal, -1)
	}
}

func updatedPostText(a models.Action, text string) string {
	if a.PostText == nil {
		return text
	}

	if len(text) == 0 && a.IsURLPostType() {
		return text
	}

	var updated string
	if strings.Contains(*a.PostText, "{_text_}") {
		updated = strings.Replace(*a.PostText, "{_text_}", text, -1)
	} else {
		updated = *a.PostText
	}

	return updated
}

func postTypeForAction(a models.Action) service.PostType {
	if a.IsImageType() {
		return service.PostTypeImage
	} else if a.IsURLPostType() {
		return service.PostTypeURL
	} else {
		return service.PostTypeText
	}
}
<commit_msg>Fix issue around reddit TLS<commit_after>package handlers

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"sort"
	"strings"

	"github.com/nelsonleduc/calmanbot/config"

	"github.com/nelsonleduc/calmanbot/cache"
	"github.com/nelsonleduc/calmanbot/handlers/models"
	"github.com/nelsonleduc/calmanbot/service"
	"github.com/nelsonleduc/calmanbot/utility"
)

func HandleCalman(message service.Message, providedService service.Service, cache cache.QueryCache, repo models.Repo) {
	verboseLog := config.Configuration().VerboseMode()

	if verboseLog {
		fmt.Printf("[BEGIN] Message came in: \"%v\"\n", message.Text())
	}

	if message.UserType() != "user" {
		if verboseLog {
			fmt.Println("Not from user -- aborting!")
		}
		return
	}

	bot, _ := repo.FetchBot(message.BotGroupID())
	if overrideBotName := config.Configuration().OverrideBotName(); len(overrideBotName) > 0 {
		bot = models.Bot{
			GroupName:     "DEBUG_OVERRIDE",
			GroupID:       "DEBUG_OVERRIDE",
			BotNameString: overrideBotName,
			Key:           "DEBUG_OVERRIDE",
		}
	}

	if verboseLog {
		fmt.Printf("Fetched bot %#v\n", bot)
		fmt.Printf("---> Names %#v\n", bot.SanitizedBotNames())
	}

	// Make sure the message has the bot's name with a preceeding character, and that it isn't escaped
	index := -1
	for _, name := range bot.SanitizedBotNames() {
		nameIndex := strings.Index(strings.ToLower(message.Text()), strings.ToLower(name))
		if nameIndex != -1 {
			index = nameIndex
		}
	}
	isEscaped := (index >= 2 && message.Text()[index-2] == '\\')
	if isEscaped {
		if verboseLog {
			fmt.Println("Usable name not found -- aborting!")
		}
		return
	}

	var (
		postString    string
		rawPostString string
		act           models.Action
	)

	if handled, result := processBuiltins(providedService, message, bot, cache, repo); handled {
		providedService.NoteProcessing(message)
		postString = result
	} else {
		if cached := cache.CachedResponse(message.Text()); cached != nil {
			postString = *cached
		} else {
			postString, act = responseForMessage(providedService, message, bot, repo)
		}

		rawPostString = postString
		postString = updatedPostText(act, postString)
		postString = utility.ProcessedString(postString)
	}

	cacheID := cache.CacheQuery(message.Text(), postString)

	fmt.Printf("Query: %v\n", message.Text())
	if postString != "" {
		postType := postTypeForAction(act)
		fmt.Printf("Action: %v\n", act.Content)
		fmt.Printf("Type: %v\n", postType)
		fmt.Printf("Posting: %v\n\n", postString)
		providedService.Post(service.Post{Key: bot.Key, Text: postString, RawText: rawPostString, Type: postType, CacheID: cacheID}, message)
	} else if verboseLog {
		fmt.Print("Empty post string -- aborting!\n\n")
	}
}

func processBuiltins(service service.Service, message service.Message, bot models.Bot, cache cache.QueryCache, repo models.Repo) (bool, string) {
	verboseMode := config.Configuration().VerboseMode()
	if verboseMode {
		fmt.Println("Running builtins")
	}
	for _, b := range builtins {
		if !b.featureChecker(service) {
			if verboseMode {
				fmt.Printf("   Service does not support \"%v\"\n", b.trigger)
			}
			continue
		}
		if verboseMode {
			fmt.Printf("   Check \"%v\"\n", b.trigger)
		}
		for _, name := range bot.BotNames() {
			reg, _ := regexp.Compile("(?i)@" + name + " * !" + b.trigger)
			matched := reg.FindStringSubmatch(message.Text())
			if len(matched) > 1 && matched[1] != "" {
				if verboseMode {
					fmt.Printf("      Matched \"%+v\"\n", reg)
					fmt.Printf("      Name \"%v\"\n", name)
				}
				return true, b.handler(matched, builtInParams{bot, cache, repo, service})
			}
		}
	}

	if verboseMode {
		fmt.Println("   None match")
	}
	return false, ""
}

func responseForMessage(service service.Service, message service.Message, bot models.Bot, repo models.Repo) (string, models.Action) {
	triggerHandler, triggerErr := service.ServiceTriggerWrangler()
	actions, _ := repo.FetchActions(true, triggerErr == nil)
	sort.Sort(models.ByPriority(actions))

	verboseMode := config.Configuration().VerboseMode()
	if verboseMode {
		fmt.Println("Running actions")
	}

	var (
		act    models.Action
		sMatch string
	)
	found := false
	for _, a := range actions {
		if verboseMode {
			fmt.Printf("   Check \"%v\"\n", *a.Pattern)
		}
		for _, name := range bot.BotNames() {
			regexString := strings.Replace(*a.Pattern, "{_botname_}", name+" *", -1)
			r, _ := regexp.Compile("(?i)" + regexString)
			matched := r.FindStringSubmatch(message.Text())
			if len(matched) > 1 && matched[1] != "" {
				if verboseMode {
					fmt.Printf("      Matched \"%+v\"\n", r)
					fmt.Printf("      Name \"%v\"\n", name)
				}
				sMatch = matched[1]
				act = a
				found = true
				goto Exit
			}
		}
	}
Exit:

	if found && act.WantsImmediateNote() {
		service.NoteProcessing(message)
	}

	var (
		postString string
		err        error
	)
	for {
		if verboseMode {
			fmt.Printf("                 Start: \"%v\"\n", act.Content)
		}
		updateAction(&act, sMatch, message)
		if verboseMode {
			fmt.Printf("                Update: \"%v\"\n", act.Content)
		}
		if act.IsURLType() {
			postString, err = handleURLAction(act, triggerHandler, bot, message)
		} else if act.IsTriggerType() {
			postString, err = handleTriggerAction(act, triggerHandler, message)
		} else {
			postString = act.Content
		}

		if (err == nil && postString != "") || act.FallbackAction == nil {
			break
		} else {
			log.Printf("Failed: %s", act.Content)
			act, _ = repo.FetchAction(*act.FallbackAction)
			log.Printf("Fallback to: %s because %v", act.Content, err)
		}
	}

	if len(postString) > 0 && act.WantsPostingNote() {
		service.NoteProcessing(message)
	}

	return postString, act
}

func handleTriggerAction(action models.Action, triggerHandler service.TriggerWrangler, message service.Message) (string, error) {
	enableAction := strings.HasSuffix(action.ContentType, "ENABLE")
	disableAction := strings.HasSuffix(action.ContentType, "DISABLE")
	statusAction := strings.HasSuffix(action.ContentType, "STATUS")
	if !enableAction && !disableAction && !statusAction {
		return "", errors.New("invalid trigger type")
	}

	triggerName := action.Content
	r, _ := regexp.Compile(`(?i){_trigger\(([^\)]+)\)_}`)
	matched := r.FindStringSubmatch(action.Content)
	if len(matched) >= 2 {
		triggerName = matched[1]
	}

	verboseMode := config.Configuration().VerboseMode()

	if enableAction {
		if verboseMode {
			fmt.Printf("   EnableTrigger \"%v\"\n", triggerName)
		}
		triggerHandler.EnableTrigger(triggerName, message)
		return "Enabled", nil
	} else if statusAction {
		if verboseMode {
			fmt.Printf("   IsTriggerConfigured \"%v\"\n", triggerName)
		}
		if triggerHandler.IsTriggerConfiguredForGroup(triggerName, message) {
			return "Enabled", nil
		}
		return "Disabled", nil
	}

	if verboseMode {
		fmt.Printf("   DisableTrigger \"%v\"\n", triggerName)
	}
	triggerHandler.DisableTrigger(triggerName, message)
	return "Disabled", nil
}

func handleURLAction(a models.Action, triggerHandler service.TriggerWrangler, b models.Bot, message service.Message) (string, error) {
	url := a.Content
	r, _ := regexp.Compile(`(?i){_url\(([^\)]+)\)_}`)
	matched := r.FindStringSubmatch(a.Content)
	if len(matched) >= 2 {
		url = matched[1]
	}

	// Workaround TLS incompatibility with latest go and reddit
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{},
		},
	}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	req.Header.Set("User-Agent", "CalmanBot/2.5.3")

	verboseMode := config.Configuration().VerboseMode()
	if verboseMode {
		fmt.Println("Sending URL request")
		fmt.Printf("   URL: %+v\n", *req)
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	content, _ := io.ReadAll(resp.Body)

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		log.Printf("Bad Response: %d length: %d", resp.StatusCode, len(content))
		return "", errors.New("bad response from server")
	}

	if verboseMode {
		fmt.Printf("   Content: %d bytes\n", len(content))
	}

	if a.IsTriggerType() && triggerHandler != nil {
		return handleTriggerAction(a, triggerHandler, message)
	}

	pathString := *a.DataPath

	if verboseMode {
		fmt.Printf("   Path: %v\n", pathString)
	}

	str := utility.ParseJSON(content, pathString, utility.LinearProvider)
	for i := 0; i < 3; i++ {
		if !utility.ValidateURL(str, a.IsImageType()) {
			fmt.Printf("Invalid URL: %v\n", str)
			str = utility.ParseJSON(content, pathString, utility.LinearProvider)
		} else {
			return str, nil
		}
	}

	return "", errors.New("failed to handle URL action")
}

func updateAction(a *models.Action, text string, message service.Message) {
	text = url.QueryEscape(text)

	a.Content = strings.Replace(a.Content, "{_text_}", text, -1)
	a.Content = strings.Replace(a.Content, "{_me_}", "localhost:"+config.Configuration().Port(), -1)
	a.Content = strings.Replace(a.Content, "{_groupid_}", url.QueryEscape(message.GroupID()), -1)
	a.Content = strings.Replace(a.Content, "{_serverid_}", url.QueryEscape(message.ServerID()), -1)
	a.Content = strings.Replace(a.Content, "{_groupname_}", url.QueryEscape(message.GroupName()), -1)

	r, _ := regexp.Compile(`(?i){_key\((.+)\)_}`)
	matched := r.FindStringSubmatch(a.Content)
	if len(matched) >= 2 {
		keyVal := os.Getenv(matched[1] + "_key")
		a.Content = strings.Replace(a.Content, matched[0], keyVal, -1)
	}
}

func updatedPostText(a models.Action, text string) string {
	if a.PostText == nil {
		return text
	}

	if len(text) == 0 && a.IsURLPostType() {
		return text
	}

	var updated string
	if strings.Contains(*a.PostText, "{_text_}") {
		updated = strings.Replace(*a.PostText, "{_text_}", text, -1)
	} else {
		updated = *a.PostText
	}

	return updated
}

func postTypeForAction(a models.Action) service.PostType {
	if a.IsImageType() {
		return service.PostTypeImage
	} else if a.IsURLPostType() {
		return service.PostTypeURL
	} else {
		return service.PostTypeText
	}
}
<|endoftext|>


<commit_before>package helpers

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, bool) {
	if strings.HasPrefix(signature, "sha1=") {
		signature = signature[5:]
	}

	mac := hmac.New(sha1.New, []byte(secret))
	mac.Write(payload)
	expectedMAC := hex.EncodeToString(mac.Sum(nil))

	return expectedMAC, hmac.Equal([]byte(signature), []byte(expectedMAC))
}

// ValuesToMap converts map[string][]string to a map[string]string object
func ValuesToMap(values map[string][]string) map[string]interface{} {
	ret := make(map[string]interface{})

	for key, value := range values {
		if len(value) > 0 {
			ret[key] = value[0]
		}
	}

	return ret
}

// ExtractParameter extracts value from interface{} based on the passed string
func ExtractParameter(s string, params interface{}) (string, bool) {
	if params == nil {
		return "", false
	}

	if paramsValue := reflect.ValueOf(params); paramsValue.Kind() == reflect.Slice {
		if paramsValueSliceLength := paramsValue.Len(); paramsValueSliceLength > 0 {

			if p := strings.SplitN(s, ".", 3); len(p) > 3 {
				index, err := strconv.ParseInt(p[1], 10, 64)

				if err != nil {
					return "", false
				} else if paramsValueSliceLength <= int(index) {
					return "", false
				}

				return ExtractParameter(p[2], params.([]map[string]interface{})[index])
			}
		}

		return "", false
	}

	if p := strings.SplitN(s, ".", 2); len(p) > 1 {
		if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
			return ExtractParameter(p[1], pValue)
		}
	} else {
		if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
			return fmt.Sprintf("%v", pValue), true
		}
	}

	return "", false
}
<commit_msg>Fix slice traversal in ExtractParameter<commit_after>package helpers

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, bool) {
	if strings.HasPrefix(signature, "sha1=") {
		signature = signature[5:]
	}

	mac := hmac.New(sha1.New, []byte(secret))
	mac.Write(payload)
	expectedMAC := hex.EncodeToString(mac.Sum(nil))

	return expectedMAC, hmac.Equal([]byte(signature), []byte(expectedMAC))
}

// ValuesToMap converts map[string][]string to a map[string]string object
func ValuesToMap(values map[string][]string) map[string]interface{} {
	ret := make(map[string]interface{})

	for key, value := range values {
		if len(value) > 0 {
			ret[key] = value[0]
		}
	}

	return ret
}

// ExtractParameter extracts value from interface{} based on the passed string
func ExtractParameter(s string, params interface{}) (string, bool) {
	if params == nil {
		return "", false
	}

	if paramsValue := reflect.ValueOf(params); paramsValue.Kind() == reflect.Slice {
		if paramsValueSliceLength := paramsValue.Len(); paramsValueSliceLength > 0 {

			if p := strings.SplitN(s, ".", 2); len(p) > 1 {
				index, err := strconv.ParseInt(p[0], 10, 64)

				if err != nil {
					return "", false
				} else if paramsValueSliceLength <= int(index) {
					return "", false
				}

				return ExtractParameter(p[1], params.([]interface{})[index])
			}
		}

		return "", false
	}

	if p := strings.SplitN(s, ".", 2); len(p) > 1 {
		if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
			return ExtractParameter(p[1], pValue)
		}
	} else {
		if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
			return fmt.Sprintf("%v", pValue), true
		}
	}

	return "", false
}
<|endoftext|>


<commit_before>package history

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strconv"
	"strings"

	"../conio"
	"../interpreter"
)

func atoi_(reader *strings.Reader) (int, int) {
	n := 0
	count := 0
	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		index := strings.IndexRune("0123456789", ch)
		if index >= 0 {
			n = n*10 + index
			count++
		} else {
			reader.UnreadRune()
			break
		}
	}
	return n, count
}

func Replace(line string) (string, bool) {
	var buffer bytes.Buffer
	isReplaced := false
	reader := strings.NewReader(line)
	history_count := conio.DefaultEditor.HistoryLen()

	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		if ch != '!' || reader.Len() <= 0 {
			buffer.WriteRune(ch)
			continue
		}
		ch, _, _ = reader.ReadRune()
		if n := strings.IndexRune("^$:*", ch); n >= 0 {
			reader.UnreadRune()
			if history_count >= 2 {
				insertHistory(&buffer, reader, history_count-2)
				isReplaced = true
			}
			continue
		}
		if ch == '!' { // !!
			if history_count >= 2 {
				insertHistory(&buffer, reader, history_count-2)
				isReplaced = true
				continue
			} else {
				buffer.WriteRune('!')
				continue
			}
		}
		if strings.IndexRune("0123456789", ch) >= 0 { // !n
			reader.UnreadRune()
			backno, _ := atoi_(reader)
			backno = backno % history_count
			if 0 <= backno && backno < history_count {
				insertHistory(&buffer, reader, backno)
				isReplaced = true
			}
			continue
		}
		if ch == '-' && reader.Len() > 0 { // !-n
			if number, count := atoi_(reader); count > 0 {
				backno := history_count - number - 1
				for backno < 0 {
					backno += history_count
				}
				if 0 <= backno && backno < history_count {
					insertHistory(&buffer, reader, backno)
					isReplaced = true
				} else {
					buffer.WriteString("!-0")
				}
				continue
			} else {
				reader.UnreadRune() // next char of '-'
			}
		}
		if ch == '?' { // !?str?
			var seekStrBuf bytes.Buffer
			lastCharIsQuestionMark := false
			for reader.Len() > 0 {
				ch, _, _ := reader.ReadRune()
				if ch == '?' {
					lastCharIsQuestionMark = true
					break
				}
				seekStrBuf.WriteRune(ch)
			}
			seekStr := seekStrBuf.String()
			found := false
			for i := history_count - 2; i >= 0; i-- {
				if strings.Contains(conio.DefaultEditor.Histories[i].Line, seekStr) {
					buffer.WriteString(conio.DefaultEditor.Histories[i].Line)
					isReplaced = true
					found = true
					break
				}
			}
			if !found {
				buffer.WriteRune('?')
				buffer.WriteString(seekStr)
				if lastCharIsQuestionMark {
					buffer.WriteRune('?')
				}
			}
			continue
		}
		// !str
		var seekStrBuf bytes.Buffer
		seekStrBuf.WriteRune(ch)
		for reader.Len() > 0 {
			ch, _, _ := reader.ReadRune()
			if ch == ' ' {
				reader.UnreadRune()
				break
			}
			seekStrBuf.WriteRune(ch)
		}
		seekStr := seekStrBuf.String()
		found := false
		for i := history_count - 2; i >= 0; i-- {
			if strings.HasPrefix(conio.DefaultEditor.Histories[i].Line, seekStr) {
				buffer.WriteString(conio.DefaultEditor.Histories[i].Line)
				isReplaced = true
				found = true
				break
			}
		}
		if !found {
			buffer.WriteRune('!')
			buffer.WriteRune(ch)
		}
	}
	result := conio.NewHistoryLine(buffer.String())
	if isReplaced {
		if history_count > 0 {
			conio.DefaultEditor.Histories[history_count-1] = result
		} else {
			conio.DefaultEditor.Histories = append(conio.DefaultEditor.Histories, result)
		}
	}
	return result.Line, isReplaced
}

func insertHistory(buffer *bytes.Buffer, reader *strings.Reader, historyNo int) {
	history1 := conio.DefaultEditor.Histories[historyNo]
	ch, siz, _ := reader.ReadRune()
	if siz > 0 && ch == '^' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(history1.At(1))
		}
	} else if siz > 0 && ch == '$' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(history1.At(-1))
		}
	} else if siz > 0 && ch == '*' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(strings.Join(history1.Word[1:], " "))
		}
	} else if siz > 0 && ch == ':' {
		n, count := atoi_(reader)
		if count <= 0 {
			buffer.WriteRune(':')
		} else if n < len(history1.Word) {
			buffer.WriteString(history1.Word[n])
		}
	} else {
		if siz > 0 {
			reader.UnreadRune()
		}
		buffer.WriteString(history1.Line)
	}
}

func CmdHistory(cmd *interpreter.Interpreter) (interpreter.NextT, error) {
	var num int
	if len(cmd.Args) >= 2 {
		num64, err := strconv.ParseInt(cmd.Args[1], 0, 32)
		if err != nil {
			switch err.(type) {
			case *strconv.NumError:
				return interpreter.CONTINUE, fmt.Errorf(
					"history: %s not a number", cmd.Args[1])
			default:
				return interpreter.CONTINUE, err
			}
		}
		num = int(num64)
		if num < 0 {
			num = -num
		}
	} else {
		num = 10
	}
	var start int
	if conio.DefaultEditor.HistoryLen() > num {
		start = conio.DefaultEditor.HistoryLen() - num
	} else {
		start = 0
	}
	for i, s := range conio.DefaultEditor.Histories[start:] {
		fmt.Fprintf(cmd.Stdout, "%3d : %-s\n", start+i, s.Line)
	}
	return interpreter.CONTINUE, nil
}

const max_histories = 2000

func Save(path string) error {
	var hist_ []*conio.HistoryLine
	if conio.DefaultEditor.HistoryLen() > max_histories {
		hist_ = conio.DefaultEditor.Histories[(conio.DefaultEditor.HistoryLen() - max_histories):]
	} else {
		hist_ = conio.DefaultEditor.Histories
	}
	fd, err := os.Create(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	for _, s := range hist_ {
		fmt.Fprintln(fd, s.Line)
	}
	return nil
}

func Load(path string) error {
	fd, err := os.Open(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	sc := bufio.NewScanner(fd)
	for sc.Scan() {
		conio.DefaultEditor.Histories = append(conio.DefaultEditor.Histories, conio.NewHistoryLine(sc.Text()))
	}
	return nil
}
<commit_msg>Shrink history on save/load.<commit_after>package history

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strconv"
	"strings"

	"../conio"
	"../interpreter"
)

func atoi_(reader *strings.Reader) (int, int) {
	n := 0
	count := 0
	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		index := strings.IndexRune("0123456789", ch)
		if index >= 0 {
			n = n*10 + index
			count++
		} else {
			reader.UnreadRune()
			break
		}
	}
	return n, count
}

func Replace(line string) (string, bool) {
	var buffer bytes.Buffer
	isReplaced := false
	reader := strings.NewReader(line)
	history_count := conio.DefaultEditor.HistoryLen()

	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		if ch != '!' || reader.Len() <= 0 {
			buffer.WriteRune(ch)
			continue
		}
		ch, _, _ = reader.ReadRune()
		if n := strings.IndexRune("^$:*", ch); n >= 0 {
			reader.UnreadRune()
			if history_count >= 2 {
				insertHistory(&buffer, reader, history_count-2)
				isReplaced = true
			}
			continue
		}
		if ch == '!' { // !!
			if history_count >= 2 {
				insertHistory(&buffer, reader, history_count-2)
				isReplaced = true
				continue
			} else {
				buffer.WriteRune('!')
				continue
			}
		}
		if strings.IndexRune("0123456789", ch) >= 0 { // !n
			reader.UnreadRune()
			backno, _ := atoi_(reader)
			backno = backno % history_count
			if 0 <= backno && backno < history_count {
				insertHistory(&buffer, reader, backno)
				isReplaced = true
			}
			continue
		}
		if ch == '-' && reader.Len() > 0 { // !-n
			if number, count := atoi_(reader); count > 0 {
				backno := history_count - number - 1
				for backno < 0 {
					backno += history_count
				}
				if 0 <= backno && backno < history_count {
					insertHistory(&buffer, reader, backno)
					isReplaced = true
				} else {
					buffer.WriteString("!-0")
				}
				continue
			} else {
				reader.UnreadRune() // next char of '-'
			}
		}
		if ch == '?' { // !?str?
			var seekStrBuf bytes.Buffer
			lastCharIsQuestionMark := false
			for reader.Len() > 0 {
				ch, _, _ := reader.ReadRune()
				if ch == '?' {
					lastCharIsQuestionMark = true
					break
				}
				seekStrBuf.WriteRune(ch)
			}
			seekStr := seekStrBuf.String()
			found := false
			for i := history_count - 2; i >= 0; i-- {
				if strings.Contains(conio.DefaultEditor.Histories[i].Line, seekStr) {
					buffer.WriteString(conio.DefaultEditor.Histories[i].Line)
					isReplaced = true
					found = true
					break
				}
			}
			if !found {
				buffer.WriteRune('?')
				buffer.WriteString(seekStr)
				if lastCharIsQuestionMark {
					buffer.WriteRune('?')
				}
			}
			continue
		}
		// !str
		var seekStrBuf bytes.Buffer
		seekStrBuf.WriteRune(ch)
		for reader.Len() > 0 {
			ch, _, _ := reader.ReadRune()
			if ch == ' ' {
				reader.UnreadRune()
				break
			}
			seekStrBuf.WriteRune(ch)
		}
		seekStr := seekStrBuf.String()
		found := false
		for i := history_count - 2; i >= 0; i-- {
			if strings.HasPrefix(conio.DefaultEditor.Histories[i].Line, seekStr) {
				buffer.WriteString(conio.DefaultEditor.Histories[i].Line)
				isReplaced = true
				found = true
				break
			}
		}
		if !found {
			buffer.WriteRune('!')
			buffer.WriteRune(ch)
		}
	}
	result := conio.NewHistoryLine(buffer.String())
	if isReplaced {
		if history_count > 0 {
			conio.DefaultEditor.Histories[history_count-1] = result
		} else {
			conio.DefaultEditor.Histories = append(conio.DefaultEditor.Histories, result)
		}
	}
	return result.Line, isReplaced
}

func insertHistory(buffer *bytes.Buffer, reader *strings.Reader, historyNo int) {
	history1 := conio.DefaultEditor.Histories[historyNo]
	ch, siz, _ := reader.ReadRune()
	if siz > 0 && ch == '^' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(history1.At(1))
		}
	} else if siz > 0 && ch == '$' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(history1.At(-1))
		}
	} else if siz > 0 && ch == '*' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(strings.Join(history1.Word[1:], " "))
		}
	} else if siz > 0 && ch == ':' {
		n, count := atoi_(reader)
		if count <= 0 {
			buffer.WriteRune(':')
		} else if n < len(history1.Word) {
			buffer.WriteString(history1.Word[n])
		}
	} else {
		if siz > 0 {
			reader.UnreadRune()
		}
		buffer.WriteString(history1.Line)
	}
}

func CmdHistory(cmd *interpreter.Interpreter) (interpreter.NextT, error) {
	var num int
	if len(cmd.Args) >= 2 {
		num64, err := strconv.ParseInt(cmd.Args[1], 0, 32)
		if err != nil {
			switch err.(type) {
			case *strconv.NumError:
				return interpreter.CONTINUE, fmt.Errorf(
					"history: %s not a number", cmd.Args[1])
			default:
				return interpreter.CONTINUE, err
			}
		}
		num = int(num64)
		if num < 0 {
			num = -num
		}
	} else {
		num = 10
	}
	var start int
	if conio.DefaultEditor.HistoryLen() > num {
		start = conio.DefaultEditor.HistoryLen() - num
	} else {
		start = 0
	}
	for i, s := range conio.DefaultEditor.Histories[start:] {
		fmt.Fprintf(cmd.Stdout, "%3d : %-s\n", start+i, s.Line)
	}
	return interpreter.CONTINUE, nil
}

const max_histories = 2000

func Save(path string) error {
	conio.DefaultEditor.ShrinkHistory()

	var hist_ []*conio.HistoryLine
	if conio.DefaultEditor.HistoryLen() > max_histories {
		hist_ = conio.DefaultEditor.Histories[(conio.DefaultEditor.HistoryLen() - max_histories):]
	} else {
		hist_ = conio.DefaultEditor.Histories
	}
	fd, err := os.Create(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	for _, s := range hist_ {
		fmt.Fprintln(fd, s.Line)
	}
	return nil
}

func Load(path string) error {
	fd, err := os.Open(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	sc := bufio.NewScanner(fd)
	for sc.Scan() {
		conio.DefaultEditor.Histories = append(conio.DefaultEditor.Histories, conio.NewHistoryLine(sc.Text()))
	}
	conio.DefaultEditor.ShrinkHistory()
	return nil
}
<|endoftext|>


<commit_before>package http

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"time"

	gostLog "github.com/gost/server/log"
	"github.com/gost/server/sensorthings/models"
	log "github.com/sirupsen/logrus"
)

var logger *log.Entry

func setupLogger() {
	l, err := gostLog.GetLoggerInstance()
	if err != nil {
		log.Error(err)
	}

	//Setting default fields for main logger
	logger = l.WithFields(log.Fields{"package": "gost.server.http"})
}

// Server interface for starting and stopping the HTTP server
type Server interface {
	Start()
	Stop()
}

// GostServer is the type that contains all of the relevant information to set
// up the GOST HTTP Server
type GostServer struct {
	host       string      // Hostname for example "localhost" or "192.168.1.14"
	port       int         // Port number where you want to run your http server on
	api        *models.API // SensorThings api to interact with from the HttpServer
	https      bool
	httpsCert  string
	httpsKey   string
	httpServer *http.Server
}

// CreateServer initialises a new GOST HTTPServer based on the given parameters
func CreateServer(host string, port int, api *models.API, https bool, httpsCert, httpsKey string) Server {
	setupLogger()
	a := *api
	router := CreateRouter(api)
	return &GostServer{
		host:      host,
		port:      port,
		api:       api,
		https:     https,
		httpsCert: httpsCert,
		httpsKey:  httpsKey,
		httpServer: &http.Server{
			Addr:         fmt.Sprintf("%s:%s", host, strconv.Itoa(port)),
			Handler:      PostProcessHandler(LowerCaseURI(router), a.GetConfig().Server.ExternalURI),
			ReadTimeout:  30 * time.Second,
			WriteTimeout: 30 * time.Second,
		},
	}
}

// Start command to start the GOST HTTPServer
func (s *GostServer) Start() {
	t := "HTTP"
	if s.https {
		t = "HTTPS"
	}

	logger.Infof("Started GOST %v Server on %v:%v", t, s.host, s.port)

	var err error
	if s.https {
		err = s.httpServer.ListenAndServeTLS(s.httpsCert, s.httpsKey)
	} else {
		err = s.httpServer.ListenAndServe()
	}

	if err != nil {
		logger.Panicf("GOST server not properly stopped: %v", err)
	}
}

// Stop command to stop the GOST HTTP server
func (s *GostServer) Stop() {
	if s.httpServer != nil {
		logger.Info("Stopping HTTP(S) Server")
		s.httpServer.Shutdown(context.Background())
	}
}

// LowerCaseURI is a middleware function that lower cases the url path
func LowerCaseURI(h http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		lowerCasePath := strings.ToLower(r.URL.Path)
		// temporarily disabled checking on paths due to problems in serving with /$value
		/*
			api := *s.api
			split := strings.Split(lowerCasePath, "/")
			for i, s := range split {
				if len(s) == 0 || i+1 == len(split) {
					continue
				}

				found := false
				for _, a := range api.GetAcceptedPaths() {
					if strings.HasPrefix(s, a) {
						found = true
					}
				}

				if !found {
					w.Header().Set("Content-Type", "application/json; charset=UTF-8")
					w.WriteHeader(http.StatusNotFound)
					w.Write([]byte(""))
					return
				}
			}
		*/

		r.URL.RawPath = r.URL.Path
		r.URL.Path = lowerCasePath
		h.ServeHTTP(w, r)
	}

	return http.HandlerFunc(fn)
}

// PostProcessHandler runs after all the other handlers and can be used to modify
// the response. In this case modify links (due to proxy running) or handle CORS functionality
// Basically we catch all the response using httptest.NewRecorder (headers + body), modify and
// write to response. Is this a right approach?
func PostProcessHandler(h http.Handler, externalURI string) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {

		if logger.Logger.Level == log.DebugLevel {
			logger.Debugf("%s start: %s", r.Method, r.URL.Path)
			defer gostLog.DebugfWithElapsedTime(logger, time.Now(), "%s done: %s", r.Method, r.URL.Path)
		}

		origURI := externalURI
		forwardedURI := r.Header.Get("X-Forwarded-For")

		rec := httptest.NewRecorder()

		// first run the next handler and get results
		h.ServeHTTP(rec, r)

		// read response body and replace links
		bytes := rec.Body.Bytes()
		s := string(bytes)
		if len(s) > 0 {
			if len(forwardedURI) > 0 {
				// if both are changed (X-Forwarded-For and External uri environment variabele) use the last one
				if origURI == "http://localhost:8080/" {
					s = strings.Replace(s, "localhost", forwardedURI, -1)
				}
			}

		}
		// handle headers too...
		for k, v := range rec.HeaderMap {
			val := v[0]

			// if there is a location header and a proxy running, change
			// the location url too
			if k == "Location" && len(forwardedURI) > 0 {
				if origURI == "http://localhost:8080/" {
					logger.Debugf("proxy + location header detected. forwarded uri: %s", forwardedURI)
					// idea: run net.LookupAddr(forwardeduri) to get hostname instead of ip address?
					val = strings.Replace(val, "localhost", forwardedURI, -1)
				}
			}

			// add the header to response
			w.Header().Add(k, val)
		}

		// handle status code
		w.WriteHeader(rec.Code)

		// write modified response
		w.Write([]byte(s))
	}
	return http.HandlerFunc(fn)
}
<commit_msg>initial code for checking query string on odata keywords<commit_after>package http

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"time"

	gostLog "github.com/gost/server/log"
	"github.com/gost/server/sensorthings/models"
	log "github.com/sirupsen/logrus"
)

var logger *log.Entry

func setupLogger() {
	l, err := gostLog.GetLoggerInstance()
	if err != nil {
		log.Error(err)
	}

	//Setting default fields for main logger
	logger = l.WithFields(log.Fields{"package": "gost.server.http"})
}

// Server interface for starting and stopping the HTTP server
type Server interface {
	Start()
	Stop()
}

// GostServer is the type that contains all of the relevant information to set
// up the GOST HTTP Server
type GostServer struct {
	host       string      // Hostname for example "localhost" or "192.168.1.14"
	port       int         // Port number where you want to run your http server on
	api        *models.API // SensorThings api to interact with from the HttpServer
	https      bool
	httpsCert  string
	httpsKey   string
	httpServer *http.Server
}

// CreateServer initialises a new GOST HTTPServer based on the given parameters
func CreateServer(host string, port int, api *models.API, https bool, httpsCert, httpsKey string) Server {
	setupLogger()
	a := *api
	router := CreateRouter(api)
	return &GostServer{
		host:      host,
		port:      port,
		api:       api,
		https:     https,
		httpsCert: httpsCert,
		httpsKey:  httpsKey,
		httpServer: &http.Server{
			Addr:         fmt.Sprintf("%s:%s", host, strconv.Itoa(port)),
			Handler:      PostProcessHandler(RequestErrorHandler(LowerCaseURI(router)), a.GetConfig().Server.ExternalURI),
			ReadTimeout:  30 * time.Second,
			WriteTimeout: 30 * time.Second,
		},
	}
}

// Start command to start the GOST HTTPServer
func (s *GostServer) Start() {
	t := "HTTP"
	if s.https {
		t = "HTTPS"
	}

	logger.Infof("Started GOST %v Server on %v:%v", t, s.host, s.port)

	var err error
	if s.https {
		err = s.httpServer.ListenAndServeTLS(s.httpsCert, s.httpsKey)
	} else {
		err = s.httpServer.ListenAndServe()
	}

	if err != nil {
		logger.Panicf("GOST server not properly stopped: %v", err)
	}
}

// Stop command to stop the GOST HTTP server
func (s *GostServer) Stop() {
	if s.httpServer != nil {
		logger.Info("Stopping HTTP(S) Server")
		s.httpServer.Shutdown(context.Background())
	}
}

// RequestErrorHandler is a middleware function that lower cases the url path
func RequestErrorHandler(h http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		query := r.URL.RawQuery
		if query != "" {
			logger.Info("query given:" + query)

			// todo: maybe add some other checks
			isValid := IsValidOdataQuery(query)
			if !isValid {
				// return godata.BadRequestError("Error: Not a valid Odata query given")
				w.Header().Set("Content-Type", "application/json; charset=UTF-8")
				w.WriteHeader(http.StatusNotFound)
				w.Write([]byte("Error: Not a valid Odata query given"))
				return
			}
		}
		h.ServeHTTP(w, r)
	}
	return http.HandlerFunc(fn)
}

func partHasKeyword(part string) bool {
	keywords := []string{"$filter", "$select", "$expand", "$order_by", "$top", "$skip", "$count"}
	part1 := strings.Split(part, "=")[0]

	for _, kw := range keywords {
		if part1 == kw {
			return true
		}
	}
	return false
}

func IsValidOdataQuery(query string) bool {
	res := true
	logger.Debugf("Query %s", query)
	parts := strings.Split(query, "&")
	for _, element := range parts {
		if !partHasKeyword(element) {
			return false
		}
	}
	return res
}

// LowerCaseURI is a middleware function that lower cases the url path
func LowerCaseURI(h http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		lowerCasePath := strings.ToLower(r.URL.Path)
		// temporarily disabled checking on paths due to problems in serving with /$value
		/*
			api := *s.api
			split := strings.Split(lowerCasePath, "/")
			for i, s := range split {
				if len(s) == 0 || i+1 == len(split) {
					continue
				}

				found := false
				for _, a := range api.GetAcceptedPaths() {
					if strings.HasPrefix(s, a) {
						found = true
					}
				}

				if !found {
					w.Header().Set("Content-Type", "application/json; charset=UTF-8")
					w.WriteHeader(http.StatusNotFound)
					w.Write([]byte(""))
					return
				}
			}
		*/

		r.URL.RawPath = r.URL.Path
		r.URL.Path = lowerCasePath
		h.ServeHTTP(w, r)
	}

	return http.HandlerFunc(fn)
}

// PostProcessHandler runs after all the other handlers and can be used to modify
// the response. In this case modify links (due to proxy running) or handle CORS functionality
// Basically we catch all the response using httptest.NewRecorder (headers + body), modify and
// write to response. Is this a right approach?
func PostProcessHandler(h http.Handler, externalURI string) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {

		if logger.Logger.Level == log.DebugLevel {
			logger.Debugf("%s start: %s", r.Method, r.URL.Path)
			defer gostLog.DebugfWithElapsedTime(logger, time.Now(), "%s done: %s", r.Method, r.URL.Path)
		}

		origURI := externalURI
		forwardedURI := r.Header.Get("X-Forwarded-For")

		rec := httptest.NewRecorder()

		// first run the next handler and get results
		h.ServeHTTP(rec, r)

		// read response body and replace links
		bytes := rec.Body.Bytes()
		s := string(bytes)
		if len(s) > 0 {
			if len(forwardedURI) > 0 {
				// if both are changed (X-Forwarded-For and External uri environment variabele) use the last one
				if origURI == "http://localhost:8080/" {
					s = strings.Replace(s, "localhost", forwardedURI, -1)
				}
			}

		}
		// handle headers too...
		for k, v := range rec.HeaderMap {
			val := v[0]

			// if there is a location header and a proxy running, change
			// the location url too
			if k == "Location" && len(forwardedURI) > 0 {
				if origURI == "http://localhost:8080/" {
					logger.Debugf("proxy + location header detected. forwarded uri: %s", forwardedURI)
					// idea: run net.LookupAddr(forwardeduri) to get hostname instead of ip address?
					val = strings.Replace(val, "localhost", forwardedURI, -1)
				}
			}

			// add the header to response
			w.Header().Add(k, val)
		}

		// handle status code
		w.WriteHeader(rec.Code)

		// write modified response
		w.Write([]byte(s))
	}
	return http.HandlerFunc(fn)
}
<|endoftext|>


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

package parts

import (
	"github.com/google/gxui"
	"github.com/google/gxui/math"
	"github.com/google/gxui/mixins/outer"
)

type LinearLayoutOuter interface {
	gxui.Container
	outer.Bounds
}

type LinearLayout struct {
	outer               LinearLayoutOuter
	direction           gxui.Direction
	sizeMode            gxui.SizeMode
	horizontalAlignment gxui.HorizontalAlignment
	verticalAlignment   gxui.VerticalAlignment
}

func (l *LinearLayout) Init(outer LinearLayoutOuter) {
	l.outer = outer
}

func (l *LinearLayout) LayoutChildren() {
	s := l.outer.Bounds().Size().Contract(l.outer.Padding())
	o := l.outer.Padding().LT()
	children := l.outer.Children()
	start, end, step, major := 0, len(children), 1, 0
	if l.direction.RightToLeft() || l.direction.BottomToTop() {
		// reverse layout
		start, end, step = end-1, -1, -1
		if l.direction.RightToLeft() {
			major = s.W
		} else {
			major = s.H
		}
	}
	for i := start; i != end; i += step {
		c := children[i]
		cm := c.Margin()
		cs := c.DesiredSize(math.ZeroSize, s.Contract(cm).Max(math.ZeroSize))

		// Calculate minor-axis alignment
		var minor int
		switch l.direction.Orientation() {
		case gxui.Horizontal:
			switch l.verticalAlignment {
			case gxui.AlignTop:
				minor = cm.T
			case gxui.AlignMiddle:
				minor = (s.H - cs.H) / 2
			case gxui.AlignBottom:
				minor = s.H - cs.H
			}
		case gxui.Vertical:
			switch l.horizontalAlignment {
			case gxui.AlignLeft:
				minor = cm.L
			case gxui.AlignCenter:
				minor = (s.W - cs.W) / 2
			case gxui.AlignRight:
				minor = s.W - cs.W
			}
		}

		// Peform layout
		switch l.direction {
		case gxui.LeftToRight:
			major += cm.L
			c.Layout(math.CreateRect(major, minor, major+cs.W, minor+cs.H).Offset(o))
			major += cs.W
			major += cm.R
			s.W -= cs.W + cm.W()
		case gxui.RightToLeft:
			major -= cm.R
			c.Layout(math.CreateRect(major-cs.W, minor, major, minor+cs.H).Offset(o))
			major -= cs.W
			major -= cm.L
			s.W -= cs.W + cm.W()
		case gxui.TopToBottom:
			major += cm.T
			c.Layout(math.CreateRect(minor, major, minor+cs.W, major+cs.H).Offset(o))
			major += cs.H
			major += cm.B
			s.H -= cs.H + cm.H()
		case gxui.BottomToTop:
			major -= cm.B
			c.Layout(math.CreateRect(minor, major-cs.H, minor+cs.W, major).Offset(o))
			major -= cs.H
			major -= cm.T
			s.H -= cs.H + cm.H()
		}
	}
}

func (l *LinearLayout) DesiredSize(min, max math.Size) math.Size {
	if l.sizeMode.Fill() {
		return max
	}

	bounds := min.Rect()
	children := l.outer.Children()

	horizontal := l.direction.Orientation().Horizontal()
	offset := math.Point{X: 0, Y: 0}
	for _, c := range children {
		cs := c.DesiredSize(math.ZeroSize, max)
		cm := c.Margin()
		cb := cs.Expand(cm).Rect().Offset(offset)
		if horizontal {
			offset.X += cb.W()
		} else {
			offset.Y += cb.H()
		}
		bounds = bounds.Union(cb)
	}

	return bounds.Size().Expand(l.outer.Padding()).Clamp(min, max)
}

func (l *LinearLayout) Direction() gxui.Direction {
	return l.direction
}

func (l *LinearLayout) SetDirection(d gxui.Direction) {
	if l.direction != d {
		l.direction = d
		l.outer.Relayout()
	}
}

func (l *LinearLayout) SizeMode() gxui.SizeMode {
	return l.sizeMode
}

func (l *LinearLayout) SetSizeMode(mode gxui.SizeMode) {
	if l.sizeMode != mode {
		l.sizeMode = mode
		l.outer.Relayout()
	}
}

func (l *LinearLayout) HorizontalAlignment() gxui.HorizontalAlignment {
	return l.horizontalAlignment
}

func (l *LinearLayout) SetHorizontalAlignment(alignment gxui.HorizontalAlignment) {
	if l.horizontalAlignment != alignment {
		l.horizontalAlignment = alignment
		l.outer.Relayout()
	}
}

func (l *LinearLayout) VerticalAlignment() gxui.VerticalAlignment {
	return l.verticalAlignment
}

func (l *LinearLayout) SetVerticalAlignment(alignment gxui.VerticalAlignment) {
	if l.verticalAlignment != alignment {
		l.verticalAlignment = alignment
		l.outer.Relayout()
	}
}
<commit_msg>Don't reverse control order when layout right->left or bottom->top<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package parts

import (
	"github.com/google/gxui"
	"github.com/google/gxui/math"
	"github.com/google/gxui/mixins/outer"
)

type LinearLayoutOuter interface {
	gxui.Container
	outer.Bounds
}

type LinearLayout struct {
	outer               LinearLayoutOuter
	direction           gxui.Direction
	sizeMode            gxui.SizeMode
	horizontalAlignment gxui.HorizontalAlignment
	verticalAlignment   gxui.VerticalAlignment
}

func (l *LinearLayout) Init(outer LinearLayoutOuter) {
	l.outer = outer
}

func (l *LinearLayout) LayoutChildren() {
	s := l.outer.Bounds().Size().Contract(l.outer.Padding())
	o := l.outer.Padding().LT()
	children := l.outer.Children()
	major := 0
	if l.direction.RightToLeft() || l.direction.BottomToTop() {
		if l.direction.RightToLeft() {
			major = s.W
		} else {
			major = s.H
		}
	}
	for _, c := range children {
		cm := c.Margin()
		cs := c.DesiredSize(math.ZeroSize, s.Contract(cm).Max(math.ZeroSize))

		// Calculate minor-axis alignment
		var minor int
		switch l.direction.Orientation() {
		case gxui.Horizontal:
			switch l.verticalAlignment {
			case gxui.AlignTop:
				minor = cm.T
			case gxui.AlignMiddle:
				minor = (s.H - cs.H) / 2
			case gxui.AlignBottom:
				minor = s.H - cs.H
			}
		case gxui.Vertical:
			switch l.horizontalAlignment {
			case gxui.AlignLeft:
				minor = cm.L
			case gxui.AlignCenter:
				minor = (s.W - cs.W) / 2
			case gxui.AlignRight:
				minor = s.W - cs.W
			}
		}

		// Peform layout
		switch l.direction {
		case gxui.LeftToRight:
			major += cm.L
			c.Layout(math.CreateRect(major, minor, major+cs.W, minor+cs.H).Offset(o))
			major += cs.W
			major += cm.R
			s.W -= cs.W + cm.W()
		case gxui.RightToLeft:
			major -= cm.R
			c.Layout(math.CreateRect(major-cs.W, minor, major, minor+cs.H).Offset(o))
			major -= cs.W
			major -= cm.L
			s.W -= cs.W + cm.W()
		case gxui.TopToBottom:
			major += cm.T
			c.Layout(math.CreateRect(minor, major, minor+cs.W, major+cs.H).Offset(o))
			major += cs.H
			major += cm.B
			s.H -= cs.H + cm.H()
		case gxui.BottomToTop:
			major -= cm.B
			c.Layout(math.CreateRect(minor, major-cs.H, minor+cs.W, major).Offset(o))
			major -= cs.H
			major -= cm.T
			s.H -= cs.H + cm.H()
		}
	}
}

func (l *LinearLayout) DesiredSize(min, max math.Size) math.Size {
	if l.sizeMode.Fill() {
		return max
	}

	bounds := min.Rect()
	children := l.outer.Children()

	horizontal := l.direction.Orientation().Horizontal()
	offset := math.Point{X: 0, Y: 0}
	for _, c := range children {
		cs := c.DesiredSize(math.ZeroSize, max)
		cm := c.Margin()
		cb := cs.Expand(cm).Rect().Offset(offset)
		if horizontal {
			offset.X += cb.W()
		} else {
			offset.Y += cb.H()
		}
		bounds = bounds.Union(cb)
	}

	return bounds.Size().Expand(l.outer.Padding()).Clamp(min, max)
}

func (l *LinearLayout) Direction() gxui.Direction {
	return l.direction
}

func (l *LinearLayout) SetDirection(d gxui.Direction) {
	if l.direction != d {
		l.direction = d
		l.outer.Relayout()
	}
}

func (l *LinearLayout) SizeMode() gxui.SizeMode {
	return l.sizeMode
}

func (l *LinearLayout) SetSizeMode(mode gxui.SizeMode) {
	if l.sizeMode != mode {
		l.sizeMode = mode
		l.outer.Relayout()
	}
}

func (l *LinearLayout) HorizontalAlignment() gxui.HorizontalAlignment {
	return l.horizontalAlignment
}

func (l *LinearLayout) SetHorizontalAlignment(alignment gxui.HorizontalAlignment) {
	if l.horizontalAlignment != alignment {
		l.horizontalAlignment = alignment
		l.outer.Relayout()
	}
}

func (l *LinearLayout) VerticalAlignment() gxui.VerticalAlignment {
	return l.verticalAlignment
}

func (l *LinearLayout) SetVerticalAlignment(alignment gxui.VerticalAlignment) {
	if l.verticalAlignment != alignment {
		l.verticalAlignment = alignment
		l.outer.Relayout()
	}
}
<|endoftext|>


<commit_before>package reviewboard

import (
	// Stdlib
	"bytes"
	"errors"
	"fmt"
	"regexp"
	"strconv"

	// Internal
	"github.com/salsita/salsaflow/errs"
	"github.com/salsita/salsaflow/git"
	"github.com/salsita/salsaflow/log"
	"github.com/salsita/salsaflow/modules/common"
	"github.com/salsita/salsaflow/repo"
	"github.com/salsita/salsaflow/shell"
)

const Id = "review_board"

type codeReviewTool struct{}

func Factory() (common.CodeReviewTool, error) {
	repo.AddInitHook(ensureRbtVersion)
	return &codeReviewTool{}, nil
}

func (tool *codeReviewTool) PostReviewRequest(commit *git.Commit, opts map[string]interface{}) error {
	// Assert that certain field are set.
	switch {
	case commit.SHA == "":
		panic("SHA not set for the commit being posted")
	case commit.StoryId == "":
		panic("story ID not set for the commit being posted")
	}

	// Post the review request.
	var (
		fixes  = formatOptInteger(opts["fixes"])
		update = formatOptInteger(opts["update"])
	)
	args := []string{"rbt", "post", "--guess-fields", "yes", "--bugs-closed", commit.StoryId}
	if fixes != "" {
		args = append(args, "--depends-on", fixes)
	}
	if update != "" {
		args = append(args, "--review-request-id", update)
	}
	args = append(args, commit.SHA)

	msg := "Post review request for commit " + commit.SHA
	stdout, stderr, err := shell.Run(args...)
	if err != nil {
		return errs.NewError(msg, stderr, err)
	}
	logger := log.V(log.Info)
	logger.Lock()
	logger.UnsafeNewLine("")
	logger.UnsafeOk(msg)
	fmt.Print(stdout)
	logger.Unlock()
	return nil
}

func (tool *codeReviewTool) PrintPostReviewRequestFollowup() {
	log.Println(`
Now, please, take some time to go through all the review requests,
check and annotate them for the reviewers to make them more happy (less sad).

If you find any issues you want to fix right before publishing, fix them now,
and if you still can, amend the relevant commits and use:

  $ rbt post -r <RB request id> <commit SHA>

to update (replace) the relevant review request.

In case you cannot amend the relevant commits any more, use the usual
review update command to update the review requests.

When you think that you are ready, publish the review requests in Review Board.
`)
}

func formatOptInteger(value interface{}) string {
	// Return an empty string on nil.
	if value == nil {
		return ""
	}

	// Return an empty string in case the value is not an integer.
	switch value := value.(type) {
	case string:
		return value
	case int:
	case int32:
	case int64:
	case uint:
	case uint32:
	case uint64:
	default:
		return ""
	}

	// Format the integer and return the string representation.
	return fmt.Sprintf("%v", value)
}

func ensureRbtVersion() error {
	msg := "Check the RBTools version being used"
	log.Run(msg)

	// rbt prints the version string to stderr. WHY? Who knows...
	_, stderr, err := shell.Run("rbt", "--version")
	if err != nil {
		return errs.NewError(msg, stderr, err)
	}

	pattern := regexp.MustCompile("^RBTools (([0-9]+)[.]([0-9]+).*)")
	parts := pattern.FindStringSubmatch(stderr.String())
	if len(parts) != 4 {
		err := errors.New("failed to parse 'rbt --version' output: " + stderr.String())
		return errs.NewError(msg, nil, err)
	}
	rbtVersion := parts[1]
	// No need to check errors, we know the format is correct.
	major, _ := strconv.Atoi(parts[2])
	minor, _ := strconv.Atoi(parts[3])

	if !(major == 0 && minor == 6) {
		hint := `
You need RBTools version 0.6. Please run

  $ pip install rbtools~=0.6 --allow-external rbtools --allow-unverified rbtools

to install the correct version.

`
		return errs.NewError(
			msg,
			bytes.NewBufferString(hint),
			errors.New("unsupported rbt version detected: "+rbtVersion))
	}

	return nil
}
<commit_msg>modules/reviewboard: Update the review post msg<commit_after>package reviewboard

import (
	// Stdlib
	"bytes"
	"errors"
	"fmt"
	"regexp"
	"strconv"

	// Internal
	"github.com/salsita/salsaflow/errs"
	"github.com/salsita/salsaflow/git"
	"github.com/salsita/salsaflow/log"
	"github.com/salsita/salsaflow/modules/common"
	"github.com/salsita/salsaflow/repo"
	"github.com/salsita/salsaflow/shell"
)

const Id = "review_board"

type codeReviewTool struct{}

func Factory() (common.CodeReviewTool, error) {
	repo.AddInitHook(ensureRbtVersion)
	return &codeReviewTool{}, nil
}

func (tool *codeReviewTool) PostReviewRequest(commit *git.Commit, opts map[string]interface{}) error {
	// Assert that certain field are set.
	switch {
	case commit.SHA == "":
		panic("SHA not set for the commit being posted")
	case commit.StoryId == "":
		panic("story ID not set for the commit being posted")
	}

	// Post the review request.
	var (
		fixes  = formatOptInteger(opts["fixes"])
		update = formatOptInteger(opts["update"])
	)
	args := []string{"rbt", "post", "--guess-fields", "yes", "--bugs-closed", commit.StoryId}
	if fixes != "" {
		args = append(args, "--depends-on", fixes)
	}
	if update != "" {
		args = append(args, "--review-request-id", update)
	}
	args = append(args, commit.SHA)

	msg := "Post review request for commit " + commit.SHA
	stdout, stderr, err := shell.Run(args...)
	if err != nil {
		return errs.NewError(msg, stderr, err)
	}
	logger := log.V(log.Info)
	logger.Lock()
	logger.UnsafeNewLine("")
	logger.UnsafeOk(msg)
	fmt.Print(stdout)
	logger.Unlock()
	return nil
}

func (tool *codeReviewTool) PrintPostReviewRequestFollowup() {
	log.Println(`
Now, take some time to go through all the review requests, please,
to check and annotate them for the reviewers to make their part easier.

If you find any issues you want to fix (even before publishing), do so now,
and if you haven't merged or pushed yet, amend the relevant commit and use

  $ salsaflow review post -update REVIEW_REQUEST_ID [REVISION]

to update (replace) the associated review request. Do this for every review
request you want to overwrite.

In case you cannot amend the relevant commits any more, make sure the affected
review request is published and use the usual process for fixing review issues

  $ salsaflow review post -fixes REVIEW_REQUEST_ID [REVISION]

to create a new review request that is linked to the one being fixed.
`)
}

func formatOptInteger(value interface{}) string {
	// Return an empty string on nil.
	if value == nil {
		return ""
	}

	// Return an empty string in case the value is not an integer.
	switch value := value.(type) {
	case string:
		return value
	case int:
	case int32:
	case int64:
	case uint:
	case uint32:
	case uint64:
	default:
		return ""
	}

	// Format the integer and return the string representation.
	return fmt.Sprintf("%v", value)
}

func ensureRbtVersion() error {
	msg := "Check the RBTools version being used"
	log.Run(msg)

	// rbt prints the version string to stderr. WHY? Who knows...
	_, stderr, err := shell.Run("rbt", "--version")
	if err != nil {
		return errs.NewError(msg, stderr, err)
	}

	pattern := regexp.MustCompile("^RBTools (([0-9]+)[.]([0-9]+).*)")
	parts := pattern.FindStringSubmatch(stderr.String())
	if len(parts) != 4 {
		err := errors.New("failed to parse 'rbt --version' output: " + stderr.String())
		return errs.NewError(msg, nil, err)
	}
	rbtVersion := parts[1]
	// No need to check errors, we know the format is correct.
	major, _ := strconv.Atoi(parts[2])
	minor, _ := strconv.Atoi(parts[3])

	if !(major == 0 && minor == 6) {
		hint := `
You need RBTools version 0.6. Please run

  $ pip install rbtools~=0.6 --allow-external rbtools --allow-unverified rbtools

to install the correct version.

`
		return errs.NewError(
			msg,
			bytes.NewBufferString(hint),
			errors.New("unsupported rbt version detected: "+rbtVersion))
	}

	return nil
}
<|endoftext|>


<commit_before>package display

import (
	gv "github.com/Virepri/Shoraldele/GlobalVars"
	"github.com/Virepri/Shoraldele/Buffer"
	"github.com/jonvaldes/termo"
	"strings"
	"fmt"
)

var running bool = true

func StopDisplay() {
	running = false
}

func DisplayInit() {
	w,h,_ := termo.Size()
	f := termo.NewFramebuffer(w,h)

	var State termo.CellState

	State.Attrib = 0
	State.FGColor = termo.ColorGreen
	State.BGColor = termo.ColorDefault

	f.Clear()
	f.Flush()
	//termo.Init() //Init stuff
	for running {
		if _w, _h, _ := termo.Size(); w != _w || h != _h {
			fmt.Printf("\033[H\033[2J")
			w = _w
			h = _h
			f = termo.NewFramebuffer(w, h)
		}
		f.Clear()

		//f.AttribText(1, 1, State, string(buffer.GetBufferContents(0, -1)))

		f.ASCIIRect(0,0,w-1,h-1,true,false)

		f.AttribText(1,h-2, State, gv.MString)

		for k,v := range GetWraps(string(buffer.GetBufferContents(0,-1)), w - 2, h - 2) {
			if k <= h - 2 {
				f.AttribText(1,1+k, State, v)
			}
		}

		f.Flush()
	}
}

func Dummy(_ string){

}

func GetWraps(dat string, w, h int) []string {
	o := []string{}
	for _,v := range strings.Split(dat,"\n") {
		o = append(o,SplitNLen(v,w)...)
	}
	return o
}

func SplitNLen(s string, n int) []string {
	tmpstr := s
	o := []string{}
	for int(len(tmpstr) / n) != 0 {
		o = append(o,tmpstr[:n])
		tmpstr = tmpstr[n:]
	}
	if len(tmpstr) != 0 {
		//o = PadRight(o, n,' ')
		o = append(o, PadRight(tmpstr,n,' '))
	}
	return o
}

func PadRight(s string, n int, padding rune) string {
	o := s
	for len(o) != n {
		o += string(padding)
	}
	return o;
}
<commit_msg>Use proper framebuffer clearing functions on display resize<commit_after>package display

import (
	gv "github.com/Virepri/Shoraldele/GlobalVars"
	"github.com/Virepri/Shoraldele/Buffer"
	"github.com/jonvaldes/termo"
	"strings"
	"fmt"
)

var running bool = true

func StopDisplay() {
	running = false
}

func DisplayInit() {
	w,h,_ := termo.Size()
	f := termo.NewFramebuffer(w,h)

	var State termo.CellState

	State.Attrib = 0
	State.FGColor = termo.ColorGreen
	State.BGColor = termo.ColorDefault

	f.Clear()
	f.Flush()
	//termo.Init() //Init stuff
	for running {
		if _w, _h, _ := termo.Size(); w != _w || h != _h {
			f.Clear()
			f.Flush()
			w = _w
			h = _h
			f = termo.NewFramebuffer(w, h)
		}
		f.Clear()

		//f.AttribText(1, 1, State, string(buffer.GetBufferContents(0, -1)))

		f.ASCIIRect(0,0,w-1,h-1,true,false)

		f.AttribText(1,h-2, State, gv.MString)

		for k,v := range GetWraps(string(buffer.GetBufferContents(0,-1)), w - 2, h - 2) {
			if k <= h - 2 {
				f.AttribText(1,1+k, State, v)
			}
		}

		f.Flush()
	}
}

func Dummy(_ string){

}

func GetWraps(dat string, w, h int) []string {
	o := []string{}
	for _,v := range strings.Split(dat,"\n") {
		o = append(o,SplitNLen(v,w)...)
	}
	return o
}

func SplitNLen(s string, n int) []string {
	tmpstr := s
	o := []string{}
	for int(len(tmpstr) / n) != 0 {
		o = append(o,tmpstr[:n])
		tmpstr = tmpstr[n:]
	}
	if len(tmpstr) != 0 {
		//o = PadRight(o, n,' ')
		o = append(o, PadRight(tmpstr,n,' '))
	}
	return o
}

func PadRight(s string, n int, padding rune) string {
	o := s
	for len(o) != n {
		o += string(padding)
	}
	return o;
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 mungers

import (
	"fmt"

	"k8s.io/contrib/mungegithub/features"
	"k8s.io/contrib/mungegithub/github"

	"github.com/spf13/cobra"
)

const (
	releaseNoteLabeler = "release-note-label"

	releaseNoteLabelNeeded    = "release-note-label-needed"
	releaseNote               = "release-note"
	releaseNoteNone           = "release-note-none"
	releaseNoteActionRequired = "release-note-action-required"
)

// ReleaseNoteLabel will remove the LGTM label from an PR which has not
// set one of the appropriete 'release-note-*' labels.
type ReleaseNoteLabel struct{}

func init() {
	RegisterMungerOrDie(ReleaseNoteLabel{})
}

// Name is the name usable in --pr-mungers
func (ReleaseNoteLabel) Name() string { return releaseNoteLabeler }

// RequiredFeatures is a slice of 'features' that must be provided
func (ReleaseNoteLabel) RequiredFeatures() []string { return []string{} }

// Initialize will initialize the munger
func (ReleaseNoteLabel) Initialize(config *github.Config, features *features.Features) error {
	return nil
}

// EachLoop is called at the start of every munge loop
func (ReleaseNoteLabel) EachLoop() error { return nil }

// AddFlags will add any request flags to the cobra `cmd`
func (ReleaseNoteLabel) AddFlags(cmd *cobra.Command, config *github.Config) {}

// Munge is the workhorse the will actually make updates to the PR
func (ReleaseNoteLabel) Munge(obj *github.MungeObject) {
	if !obj.IsPR() {
		return
	}

	if !obj.IsForBranch("master") {
		return
	}

	if obj.HasLabel(releaseNote) || obj.HasLabel(releaseNoteActionRequired) || obj.HasLabel(releaseNoteNone) {
		if obj.HasLabel(releaseNoteLabelNeeded) {
			obj.RemoveLabel(releaseNoteLabelNeeded)
		}
		return
	}

	if !obj.HasLabel(releaseNoteLabelNeeded) {
		obj.AddLabel(releaseNoteLabelNeeded)
	}

	if !obj.HasLabel("lgtm") {
		return
	}

	msgFmt := `Removing LGTM because the release note process has not been followed.
One of the following labels is required %q, %q, or %q
Please see: https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/release-notes.md`
	msg := fmt.Sprintf(msgFmt, releaseNote, releaseNoteNone, releaseNoteActionRequired)
	obj.WriteComment(msg)
	obj.RemoveLabel("lgtm")
}
<commit_msg>Update pointer to release note process<commit_after>/*
Copyright 2016 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 mungers

import (
	"fmt"

	"k8s.io/contrib/mungegithub/features"
	"k8s.io/contrib/mungegithub/github"

	"github.com/spf13/cobra"
)

const (
	releaseNoteLabeler = "release-note-label"

	releaseNoteLabelNeeded    = "release-note-label-needed"
	releaseNote               = "release-note"
	releaseNoteNone           = "release-note-none"
	releaseNoteActionRequired = "release-note-action-required"
)

// ReleaseNoteLabel will remove the LGTM label from an PR which has not
// set one of the appropriete 'release-note-*' labels.
type ReleaseNoteLabel struct{}

func init() {
	RegisterMungerOrDie(ReleaseNoteLabel{})
}

// Name is the name usable in --pr-mungers
func (ReleaseNoteLabel) Name() string { return releaseNoteLabeler }

// RequiredFeatures is a slice of 'features' that must be provided
func (ReleaseNoteLabel) RequiredFeatures() []string { return []string{} }

// Initialize will initialize the munger
func (ReleaseNoteLabel) Initialize(config *github.Config, features *features.Features) error {
	return nil
}

// EachLoop is called at the start of every munge loop
func (ReleaseNoteLabel) EachLoop() error { return nil }

// AddFlags will add any request flags to the cobra `cmd`
func (ReleaseNoteLabel) AddFlags(cmd *cobra.Command, config *github.Config) {}

// Munge is the workhorse the will actually make updates to the PR
func (ReleaseNoteLabel) Munge(obj *github.MungeObject) {
	if !obj.IsPR() {
		return
	}

	if !obj.IsForBranch("master") {
		return
	}

	if obj.HasLabel(releaseNote) || obj.HasLabel(releaseNoteActionRequired) || obj.HasLabel(releaseNoteNone) {
		if obj.HasLabel(releaseNoteLabelNeeded) {
			obj.RemoveLabel(releaseNoteLabelNeeded)
		}
		return
	}

	if !obj.HasLabel(releaseNoteLabelNeeded) {
		obj.AddLabel(releaseNoteLabelNeeded)
	}

	if !obj.HasLabel("lgtm") {
		return
	}

	msgFmt := `Removing LGTM because the release note process has not been followed.
One of the following labels is required %q, %q, or %q
Please see: https://github.com/kubernetes/kubernetes/blob/master/docs/devel/pull-requests.md#release-notes`
	msg := fmt.Sprintf(msgFmt, releaseNote, releaseNoteNone, releaseNoteActionRequired)
	obj.WriteComment(msg)
	obj.RemoveLabel("lgtm")
}
<|endoftext|>