<commit_before>// Windows filesystem monitoring implementation based on
// golang.org/x/exp/winfsnotify
// (https://github.com/golang/exp/tree/master/winfsnotify) but modified to
// remove import path enforcement, support recursive watching, and use more
// idiomatic filesystem path joins.
//
// The original code license:
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The original license header inside the code itself:
//
// Copyright 2011 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.

// +build windows

package winfsnotify

import (
	"io/ioutil"
	"os"
	"sync/atomic"
	"testing"
	"time"
)

func expect(t *testing.T, eventstream <-chan *Event, name string, mask uint32) {
	t.Logf(`expected: "%s": 0x%x`, name, mask)
	select {
	case event := <-eventstream:
		if event == nil {
			t.Fatal("nil event received")
		}
		t.Logf("received: %s", event)
		if event.Name != name || event.Mask != mask {
			t.Fatal("did not receive expected event")
		}
	case <-time.After(1 * time.Second):
		t.Fatal("timed out waiting for event")
	}
}

func TestNotifyEvents(t *testing.T) {
	watcher, err := NewWatcher()
	if err != nil {
		t.Fatalf("NewWatcher() failed: %s", err)
	}

	testDir := "TestNotifyEvents.testdirectory"
	testFile := testDir + "/TestNotifyEvents.testfile"
	testFile2 := testFile + ".new"
	const mask = FS_ALL_EVENTS & ^(FS_ATTRIB|FS_CLOSE) | FS_IGNORED

	// Add a watch for testDir
	os.RemoveAll(testDir)
	if err = os.Mkdir(testDir, 0777); err != nil {
		t.Fatalf("Failed to create test directory: %s", err)
	}
	defer os.RemoveAll(testDir)
	err = watcher.AddWatch(testDir, mask)
	if err != nil {
		t.Fatalf("Watcher.Watch() failed: %s", err)
	}

	// Receive errors on the error channel on a separate goroutine
	go func() {
		for err := range watcher.Error {
			t.Fatalf("error received: %s", err)
		}
	}()

	// Create a file
	file, err := os.Create(testFile)
	if err != nil {
		t.Fatalf("creating test file failed: %s", err)
	}
	expect(t, watcher.Event, testFile, FS_CREATE)

	err = watcher.AddWatch(testFile, mask)
	if err != nil {
		t.Fatalf("Watcher.Watch() failed: %s", err)
	}

	if _, err = file.WriteString("hello, world"); err != nil {
		t.Fatalf("failed to write to test file: %s", err)
	}
	if err = file.Close(); err != nil {
		t.Fatalf("failed to close test file: %s", err)
	}
	expect(t, watcher.Event, testFile, FS_MODIFY)
	expect(t, watcher.Event, testFile, FS_MODIFY)

	if err = os.Rename(testFile, testFile2); err != nil {
		t.Fatalf("failed to rename test file: %s", err)
	}
	expect(t, watcher.Event, testFile, FS_MOVED_FROM)
	expect(t, watcher.Event, testFile2, FS_MOVED_TO)
	expect(t, watcher.Event, testFile, FS_MOVE_SELF)

	if err = os.RemoveAll(testDir); err != nil {
		t.Fatalf("failed to remove test directory: %s", err)
	}
	expect(t, watcher.Event, testFile2, FS_DELETE_SELF)
	expect(t, watcher.Event, testFile2, FS_IGNORED)
	expect(t, watcher.Event, testFile2, FS_DELETE)
	expect(t, watcher.Event, testDir, FS_DELETE_SELF)
	expect(t, watcher.Event, testDir, FS_IGNORED)

	t.Log("calling Close()")
	if err = watcher.Close(); err != nil {
		t.Fatalf("failed to close watcher: %s", err)
	}
}

func TestNotifyClose(t *testing.T) {
	watcher, _ := NewWatcher()
	watcher.Close()

	var done int32
	go func() {
		watcher.Close()
		atomic.StoreInt32(&done, 1)
	}()

	time.Sleep(50 * time.Millisecond)
	if atomic.LoadInt32(&done) == 0 {
		t.Fatal("double Close() test failed: second Close() call didn't return")
	}

	dir, err := ioutil.TempDir("", "wininotify")
	if err != nil {
		t.Fatalf("TempDir failed: %s", err)
	}
	defer os.RemoveAll(dir)

	err = watcher.Watch(dir)
	if err == nil {
		t.Fatal("expected error on Watch() after Close(), got nil")
	}
}
<commit_msg>Fixed winfsnotify test for new path joins.<commit_after>// Windows filesystem monitoring implementation based on
// golang.org/x/exp/winfsnotify
// (https://github.com/golang/exp/tree/master/winfsnotify) but modified to
// remove import path enforcement, support recursive watching, and use more
// idiomatic filesystem path joins.
//
// The original code license:
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The original license header inside the code itself:
//
// Copyright 2011 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.

// +build windows

package winfsnotify

import (
	"io/ioutil"
	"os"
	"sync/atomic"
	"testing"
	"time"
)

func expect(t *testing.T, eventstream <-chan *Event, name string, mask uint32) {
	t.Logf(`expected: "%s": 0x%x`, name, mask)
	select {
	case event := <-eventstream:
		if event == nil {
			t.Fatal("nil event received")
		}
		t.Logf("received: %s", event)
		if event.Name != name || event.Mask != mask {
			t.Fatal("did not receive expected event")
		}
	case <-time.After(1 * time.Second):
		t.Fatal("timed out waiting for event")
	}
}

func TestNotifyEvents(t *testing.T) {
	watcher, err := NewWatcher()
	if err != nil {
		t.Fatalf("NewWatcher() failed: %s", err)
	}

	testDir := "TestNotifyEvents.testdirectory"
	testFile := filepath.Join(testDir, "TestNotifyEvents.testfile")
	testFile2 := testFile + ".new"
	const mask = FS_ALL_EVENTS & ^(FS_ATTRIB|FS_CLOSE) | FS_IGNORED

	// Add a watch for testDir
	os.RemoveAll(testDir)
	if err = os.Mkdir(testDir, 0777); err != nil {
		t.Fatalf("Failed to create test directory: %s", err)
	}
	defer os.RemoveAll(testDir)
	err = watcher.AddWatch(testDir, mask)
	if err != nil {
		t.Fatalf("Watcher.Watch() failed: %s", err)
	}

	// Receive errors on the error channel on a separate goroutine
	go func() {
		for err := range watcher.Error {
			t.Fatalf("error received: %s", err)
		}
	}()

	// Create a file
	file, err := os.Create(testFile)
	if err != nil {
		t.Fatalf("creating test file failed: %s", err)
	}
	expect(t, watcher.Event, testFile, FS_CREATE)

	err = watcher.AddWatch(testFile, mask)
	if err != nil {
		t.Fatalf("Watcher.Watch() failed: %s", err)
	}

	if _, err = file.WriteString("hello, world"); err != nil {
		t.Fatalf("failed to write to test file: %s", err)
	}
	if err = file.Close(); err != nil {
		t.Fatalf("failed to close test file: %s", err)
	}
	expect(t, watcher.Event, testFile, FS_MODIFY)
	expect(t, watcher.Event, testFile, FS_MODIFY)

	if err = os.Rename(testFile, testFile2); err != nil {
		t.Fatalf("failed to rename test file: %s", err)
	}
	expect(t, watcher.Event, testFile, FS_MOVED_FROM)
	expect(t, watcher.Event, testFile2, FS_MOVED_TO)
	expect(t, watcher.Event, testFile, FS_MOVE_SELF)

	if err = os.RemoveAll(testDir); err != nil {
		t.Fatalf("failed to remove test directory: %s", err)
	}
	expect(t, watcher.Event, testFile2, FS_DELETE_SELF)
	expect(t, watcher.Event, testFile2, FS_IGNORED)
	expect(t, watcher.Event, testFile2, FS_DELETE)
	expect(t, watcher.Event, testDir, FS_DELETE_SELF)
	expect(t, watcher.Event, testDir, FS_IGNORED)

	t.Log("calling Close()")
	if err = watcher.Close(); err != nil {
		t.Fatalf("failed to close watcher: %s", err)
	}
}

func TestNotifyClose(t *testing.T) {
	watcher, _ := NewWatcher()
	watcher.Close()

	var done int32
	go func() {
		watcher.Close()
		atomic.StoreInt32(&done, 1)
	}()

	time.Sleep(50 * time.Millisecond)
	if atomic.LoadInt32(&done) == 0 {
		t.Fatal("double Close() test failed: second Close() call didn't return")
	}

	dir, err := ioutil.TempDir("", "wininotify")
	if err != nil {
		t.Fatalf("TempDir failed: %s", err)
	}
	defer os.RemoveAll(dir)

	err = watcher.Watch(dir)
	if err == nil {
		t.Fatal("expected error on Watch() after Close(), got nil")
	}
}
<|endoftext|>


<commit_before>package cruncy

// VERSION of the application
const VERSION = "0.4.2"
<commit_msg>0.4.3<commit_after>package cruncy

// VERSION of the application
const VERSION = "0.4.3"
<|endoftext|>


<commit_before>// Copyright 2016 The Upspin 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 main

import (
	"flag"

	"upspin.io/errors"
	"upspin.io/path"
	"upspin.io/upspin"
	"upspin.io/user"
)

func (s *State) snapshot(args ...string) {
	const help = `
Snapshot requests the system to take a snapshot of the user's
directory tree as soon as possible. Snapshots are created only if
the directory server for the user's root supports them.
`
	fs := flag.NewFlagSet("snapshot", flag.ExitOnError)
	s.ParseFlags(fs, args, help, "snapshot")
	if fs.NArg() > 0 {
		fs.Usage()
	}

	u, suffix, domain, err := user.Parse(s.Config.UserName())
	if err != nil {
		s.Exit(err)
	}
	var suffixedUser string
	if suffix == "" {
		suffixedUser = u + "+snapshot@" + domain
	} else {
		suffixedUser = u[:len(u)-len(suffix)-1] + "+snapshot@" + domain
	}

	// Is the root for the snapshot already created?
	_, err = s.Client.Lookup(upspin.PathName(suffixedUser), false)
	if err != nil && errors.Match(errors.E(errors.NotExist), err) {
		_, err = s.Client.MakeDirectory(upspin.PathName(suffixedUser + "/"))
		if err != nil {
			s.Exit(err)
		}
	} else if err != nil {
		s.Exit(err)
	}

	// Put a new DirEntry that triggers the snapshotting process.
	// Note: This is a hack, but it works. See dir/server/snapshot.go for
	// the mechanism.
	// TODO: Find a cleaner mechanism?
	name := path.Join(upspin.PathName(suffixedUser), "TakeSnapshot")
	entry := &upspin.DirEntry{
		Name:       name,
		SignedName: name,
		Packing:    upspin.PlainPack,
	}
	s.DirServer(entry.Name).Put(entry)
}
<commit_msg>cmd/upspin: snapshot creates the snapshot user if needed<commit_after>// Copyright 2016 The Upspin 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 main

import (
	"flag"

	"upspin.io/errors"
	"upspin.io/path"
	"upspin.io/upspin"
	"upspin.io/user"
)

func (s *State) snapshot(args ...string) {
	const help = `
Snapshot requests the system to take a snapshot of the user's
directory tree as soon as possible. Snapshots are created only if
the directory server for the user's root supports them.
`
	fs := flag.NewFlagSet("snapshot", flag.ExitOnError)
	s.ParseFlags(fs, args, help, "snapshot")
	if fs.NArg() > 0 {
		fs.Usage()
	}

	u, suffix, domain, err := user.Parse(s.Config.UserName())
	if err != nil {
		s.Exit(err)
	}
	var snapshotUser upspin.UserName
	if suffix == "" {
		snapshotUser = upspin.UserName(u + "+snapshot@" + domain)
	} else if suffix == "snapshot" {
		// Okay -- snapshot user is allowed to trigger snapshots.
	} else {
		s.Exitf("Only the snapshot user or the canonical user %q can trigger a snapshot", u+"@"+domain)
	}

	// Does the snapshot user exist? If not, create it.
	keyServer := s.KeyServer()
	_, err = keyServer.Lookup(snapshotUser)
	switch {
	case err == nil:
		// Okay -- user exists.
	case errors.Match(errors.E(errors.NotExist), err):
		// User must be created. This should succeed because the current
		// user is either the canonical user or the snapshot user.
		err = keyServer.Put(&upspin.User{
			Name:      snapshotUser,
			Dirs:      []upspin.Endpoint{s.Config.DirEndpoint()},
			Stores:    []upspin.Endpoint{s.Config.StoreEndpoint()},
			PublicKey: s.Config.Factotum().PublicKey(),
		})
		if err != nil {
			s.Exit(err)
		}
	default:
		s.Exit(err)
	}

	// Is the root for the snapshot already created?
	_, err = s.Client.Lookup(upspin.PathName(snapshotUser), false)
	if err != nil && errors.Match(errors.E(errors.NotExist), err) {
		_, err = s.Client.MakeDirectory(upspin.PathName(snapshotUser + "/"))
		if err != nil {
			s.Exit(err)
		}
	} else if err != nil {
		s.Exit(err)
	}

	// Put a new DirEntry that triggers the snapshotting process.
	// Note: This is a hack, but it works. See dir/server/snapshot.go for
	// the mechanism.
	// TODO: Find a cleaner mechanism?
	name := path.Join(upspin.PathName(snapshotUser), "TakeSnapshot")
	entry := &upspin.DirEntry{
		Name:       name,
		SignedName: name,
		Packing:    upspin.PlainPack,
	}
	s.DirServer(entry.Name).Put(entry)
}
<|endoftext|>


<commit_before><commit_msg>Add router /done<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	arrow "github.com/bmuller/arrow/lib"
	gorelic "github.com/brandfolder/gin-gorelic"
	"github.com/carlescere/scheduler"
	"github.com/gin-gonic/contrib/ginrus"
	"github.com/gin-gonic/gin"
	"github.com/nlopes/slack"
	"gopkg.in/mgo.v2/bson"

	"github.com/dwarvesf/working-on/db"
)

type Item struct {
	ID        bson.ObjectId `json:"id" bson:"_id"`
	UserID    string        `json:"user_id" bson:"user_id"`
	Name      string        `json:"user_name" bson:"user_name"`
	Text      string        `json:"text" bson:"text"`
	CreatedAt time.Time     `json:"created_at" bson:"created_at"`
}

func main() {

	// Read configuration from file and env
	port := os.Getenv("PORT")
	digestTime := os.Getenv("DIGEST_TIME")
	gorelic.InitNewrelicAgent(os.Getenv("NEW_RELIC_LICENSE_KEY"), "working", false)

	digestConfig, err := parseConfig("digest.json")
	if err != nil {
		log.Fatalln(err)
	}

	// Setup schedule digest jobs
	for _, i := range digestConfig.Items {
		digestJob := postDigest(i.Channel, i.Token, i.Tags)
		_, err := scheduler.Every().Day().At(digestTime).Run(digestJob)
		if err != nil {
			log.Infoln(err)
		}
	}

	settingConfig, err := parseConfig("setting.json")
	if err != nil {
		log.Fatalln(err)
	}

	// Prepare router
	router := gin.New()
	router.Use(gorelic.Handler)
	router.Use(ginrus.Ginrus(log.StandardLogger(), time.RFC3339, true))

	// router.LoadHTMLGlob("templates/*.tmpl.html")
	router.Static("/static", "static")
	router.POST("/on", on(*settingConfig))
	router.POST("/til", til(*settingConfig))
	router.POST("/done", done(*settingConfig))

	// Start server
	router.Run(":" + port)
}

func done(config Configuration) func(c *gin.Context) {
	return func(c *gin.Context) {
		text := c.PostForm("text")
		text = strings.TrimSpace(text)

		if text == "" {
			log.Fatalln("Message is nil")
			return
		}

		userID := c.PostForm("user_id")
		userName := c.PostForm("user_name")

		addItem(text, userID, userName, config, ":cantboiroi: *%s* has *done*: %s")
	}
}

func til(config Configuration) func(c *gin.Context) {
	return func(c *gin.Context) {
		text := c.PostForm("text")
		text = strings.TrimSpace(text)

		if text == "" {
			log.Fatalln("Message is nil")
			return
		}

		userID := c.PostForm("user_id")
		userName := c.PostForm("user_name")

		addItem(text, userID, userName, config, "*%s* #til - Today I learned: %s :adore:")
	}
}

func on(config Configuration) func(c *gin.Context) {
	return func(c *gin.Context) {
		text := c.PostForm("text")
		text = strings.TrimSpace(text)

		if text == "" {
			log.Fatalln("Message is nil")
			return
		}

		userID := c.PostForm("user_id")
		userName := c.PostForm("user_name")

		addItem(text, userID, userName, config, "*%s* is *working* on: %s")
	}
}

// Message will be passed to server with '-' prefix via various way
//	+ Direct message with the bots
//	+ Use slash command `/working <message>`
//	+ Use cli `working-on <message>`
//	+ Might use Chrome plugin
//	+ ...
// Token is secondary param to indicate the user
func addItem(text string, userID string, userName string, configuration Configuration, format string) {

	// Parse token and message
	var item Item

	item.ID = bson.NewObjectId()
	item.CreatedAt = time.Now()
	item.Name = userName
	item.UserID = userID
	item.Text = text

	ctx, err := db.NewContext()
	if err != nil {
		log.Fatal(err)
	}

	defer ctx.Close()

	// Add Item to database
	err = ctx.C("items").Insert(item)
	if err != nil {
		log.Fatalln(err)
	}

	// Repost to the target channel
	channel := os.Getenv("WORKING_CHANNEL")
	botToken := os.Getenv("BOT_TOKEN")

	if botToken == "" {
		log.Fatal("No token provided")
	}

	// <@U024BE7LH|bob>: format text to match Slack format
	userName = fmt.Sprintf("@<%s|%s>", userID, userName)
	title := fmt.Sprintf(format, userName, text)

	postItem(botToken, channel, title)

	// Post item to project group
	for _, config := range configuration.Items {
		for _, tag := range config.Tags {
			if strings.Contains(text, tag) {
				log.Infof("Hit %s", tag)
				log.Infof("Token: %s", config.Token)

				// Post to target channel
				postItem(config.Token, config.Channel, title)
			}
		}
	}
}

func postItem(token string, channel string, text string) {
	s := slack.New(token)
	params := slack.PostMessageParameters{}
	params.IconURL = "http://i.imgur.com/fLcxkel.png"
	params.Username = "oshin"
	s.PostMessage(channel, text, params)
}

type Configuration struct {
	Items []ConfigurationItem `json:"items"`
}

type ConfigurationItem struct {
	Channel string   `json:"channel"`
	Tags    []string `json:"tags"`
	Token   string   `json:"token"`
}

func parseConfig(path string) (*Configuration, error) {
	var configuration Configuration

	// Parse configuration
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, errors.New("Cannot read setting file")
	}

	err = json.Unmarshal(bytes, &configuration)
	if err != nil {
		return nil, errors.New("Cannot parse setting")
	}

	return &configuration, nil
}

// Post summary to Slack channel.
// Only post to specific channel when tags are met.
func postDigest(channel, botToken string, tags []string) func() {
	return func() {
		if botToken == "" {
			log.Fatal("No token provided")
			os.Exit(1)
		}

		s := slack.New(botToken)
		users, err := s.GetUsers()

		if err != nil {
			log.Fatal("Cannot get users")
			os.Exit(1)
		}

		ctx, err := db.NewContext()
		if err != nil {
			log.Fatal(err)
		}

		defer ctx.Close()

		log.Info("Preparing data")
		// If count > 0, it means there is data to show
		count := 0
		title := " >> Yesterday I did: "
		params := slack.PostMessageParameters{}
		fields := []slack.AttachmentField{}

		yesterday := arrow.Yesterday().UTC()
		toDate := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, time.UTC)

		// Prepare attachment of done items
		for _, user := range users {

			if user.IsBot || user.Deleted {
				continue
			}

			// log.Infof("Process user: %s - %s", user.Name, user.Id)

			// Query done items from Database
			var values []string
			var items []Item

			err = ctx.C("items").Find(
				bson.M{
					"$and": []bson.M{
						bson.M{"user_name": user.Name},
						bson.M{"created_at": bson.M{"$gt": toDate}},
					},
				}).All(&items)

			if err != nil {
				log.Fatal("Cannot query done items.")
				os.Exit(1)
			}

			for _, item := range items {
				log.Infof("User: %s, item: %s, tags: %+v", user.Name, item.Text, tags)

				// delete items which text does not contain tags
				if tags != nil {
					// check if item contains any tags
					var containTag bool
					for _, tag := range tags {
						if !strings.Contains(item.Text, tag) {
							continue
						}
						containTag = true
						break
					}

					// if item.Text doesn't contains any tags then don't
					// add it to the digest message (values)
					if !containTag {
						continue
					}
				}

				// construct text format
				values = append(values, fmt.Sprintf("+ %s", item.Text))
			}

			// <@U024BE7LH|bob>
			if len(values) > 0 {
				count++
				field := slack.AttachmentField{
					Title: fmt.Sprintf("@<%s|%s>", user.Id, user.Name),
					Value: strings.Join(values, "\n"),
				}

				fields = append(fields, field)
			}
		}

		params.Attachments = []slack.Attachment{
			slack.Attachment{
				Color:  "#7CD197",
				Fields: fields,
			},
		}

		params.IconURL = "http://i.imgur.com/fLcxkel.png"
		params.Username = "oshin"

		if count > 0 {
			s.PostMessage(channel, title, params)
		}
	}
}
<|endoftext|>


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

package cmd

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"

	composer2 "github.com/fubarhouse/golang-drush/composer"
	"github.com/fubarhouse/golang-drush/make"
	"github.com/spf13/viper"
)

// buildCmd represents the build command
var buildCmd = &cobra.Command{
	Use:   "build",
	Short: "The build process for Yoink",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {

		db_user = viper.GetString("db_user")
		db_pass = viper.GetString("db_pass")
		db_host = viper.GetString("db_host")
		db_port = viper.GetInt("db_port")

		webserver = viper.GetString("webserver")

		alias_template = viper.GetString("alias_template")
		sites_php_template = viper.GetString("sites_php_template")
		virtualhost_path = viper.GetString("virtualhost_path")
		virtualhost_template = viper.GetString("virtualhost_template")

		log.Println(alias_template, sites_php_template, virtualhost_path, virtualhost_template)

		if ok, err := os.Stat(sites_php_template); err == nil {
			log.Infof("Found template %v", ok.Name())
		}

		if docroot == "" {
			log.Printf("docroot value is emptied, sub-folders will not be used.")
			timestamp = 0
		} else if timestamp == 0 {
			timestamp, _ = strconv.ParseInt(time.Now().Format("20060102150405"), 0, 0)
			log.Printf("Timestamp not specified, using %v", timestamp)
		}

		x := make.NewSite("", name, destination, alias, webserver, domain, virtualhost_path, virtualhost_template)
		x.AliasTemplate = alias_template
		x.Docroot = docroot
		if alias == "" {
			x.Alias = domain
		}

		y := make.NewmakeDB(db_host, db_user, db_pass, db_port)
		x.DatabaseSet(y)

		if workingCopy {
			x.WorkingCopy = true
		}

		if composer != "" {
			x.Composer = true
			composer2.InstallComposerCodebase(x.Name, x.TimeStampGet(), composer, x.Path, workingCopy)
		} else if makes != "" {
			x.Make = makes
			MakefilesFormatted := strings.Replace(makes, " ", "", -1)
			MakeFiles := strings.Split(MakefilesFormatted, ",")

			if rewriteSource != "" && rewriteDestination != "" {
				x.MakeFileRewriteSource = rewriteSource
				x.MakeFileRewriteDestination = rewriteDestination
			}
			x.ActionRebuildCodebase(MakeFiles)
		} else {
			cmd.Usage()
			fmt.Println()
			log.Fatalln("makes and/or composer values were not specified")
			os.Exit(1)
		}

		x.InstallSiteRef(sites_php_template)
		if timestamp != 0 {
			x.SymReinstall()
		}

		if !noInstall {
			x.ActionInstall()
		}

		if virtualhost_template != "" {
			if ok, err := os.Stat(virtualhost_template); err == nil {
				log.Infof("Found template %v for usage", ok.Name())
				x.Template = ok.Name()
			} else {
				log.Println("Could not find configured or default virtual host template.")
			}
		}

		if vhost {
			x.VhostInstall()
		}

		if alias_template != "" {
			if ok, err := os.Stat(alias_template); err == nil {
				log.Infof("Found template %v", ok.Name())
				x.AliasTemplate = ok.Name()
			} else {
				t := fmt.Sprintf("%v/src/github.com/fubarhouse/golang-drush/cmd/yoink/templates/alias.gotpl", os.Getenv("GOPATH"))
				log.Infof("Could not find template %v, using %v", ok.Name(), t)
			}
		}
		if alias == "" {
			x.Alias = domain
		}

		if drupal {
			x.AliasInstall(docroot)
		}
	},
}

func init() {
	RootCmd.AddCommand(buildCmd)
	// Required flags
	buildCmd.Flags().StringVarP(&name, "name", "n", "", "The human-readable name for this site")
	buildCmd.Flags().StringVarP(&alias, "alias", "a", "", "The drush alias for this site")
	buildCmd.Flags().StringVarP(&destination, "destination", "p", "", "The path to where the site(s) exist.")
	buildCmd.Flags().StringVarP(&domain, "domain", "d", "", "The domain this site is to use")
	// Very important but not completely needed > 0 is needed though.
	buildCmd.Flags().StringVarP(&makes, "makes", "m", "", "A comma-separated list of make files for use")
	buildCmd.Flags().StringVarP(&composer, "composer", "c", "", "Path to the composer.json file.")
	// Optional flags
	buildCmd.Flags().StringVarP(&docroot, "docroot", "o", "docroot", "The folder to use for the built codebase.")
	buildCmd.Flags().BoolVarP(&drupal, "drupal", "r", true, "Mark the build process as a Drupal build.")
	buildCmd.Flags().BoolVarP(&noInstall, "no-install", "i", false, "Mark this build so that installation doesn't happen.")
	buildCmd.Flags().Int64VarP(&timestamp, "timestamp", "t", 0, "Optional timestamp in the format YYYYMMDDHHMMSS")
	buildCmd.Flags().BoolVarP(&vhost, "vhost", "v", true, "Include a virtual host as configured with this build.")
	// Deprecated flags
	buildCmd.Flags().StringVarP(&rewriteSource, "rewrite-source", "x", "", "The rewrite string source")
	buildCmd.Flags().StringVarP(&rewriteDestination, "rewrite-destination", "y", "", "The rewrite string destination")
	buildCmd.Flags().BoolVarP(&workingCopy, "working-copy", "w", false, "Mark as a working-copy during the build process.")
	// Hide deprecated fields.
	buildCmd.Flags().MarkHidden("rewrite-source")
	buildCmd.Flags().MarkHidden("rewrite-destination")
	// Mark required flags.
	buildCmd.MarkFlagRequired("name")
	buildCmd.MarkFlagRequired("destination")
	buildCmd.MarkFlagRequired("domain")

	// Database
	db_user = viper.GetString("db_user")
	db_pass = viper.GetString("db_pass")
	db_host = viper.GetString("db_host")
	db_port = viper.GetInt("db_port")

	// Sites.php template
	sites_php_template = viper.GetString("sites_php_template")

	// Alias template
	alias_template = viper.GetString("alias_template")

	// Virtual host
	webserver = viper.GetString("webserver")
	virtualhost_path = viper.GetString("virtualhost_path")
	virtualhost_template = viper.GetString("virtualhost_template")
}
<commit_msg>cleanup output<commit_after>// Copyright © 2017 Karl Hepworth <Karl.Hepworth@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"

	composer2 "github.com/fubarhouse/golang-drush/composer"
	"github.com/fubarhouse/golang-drush/make"
	"github.com/spf13/viper"
)

// buildCmd represents the build command
var buildCmd = &cobra.Command{
	Use:   "build",
	Short: "The build process for Yoink",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {

		db_user = viper.GetString("db_user")
		db_pass = viper.GetString("db_pass")
		db_host = viper.GetString("db_host")
		db_port = viper.GetInt("db_port")

		webserver = viper.GetString("webserver")

		alias_template = viper.GetString("alias_template")
		sites_php_template = viper.GetString("sites_php_template")
		virtualhost_path = viper.GetString("virtualhost_path")
		virtualhost_template = viper.GetString("virtualhost_template")

		if ok, err := os.Stat(sites_php_template); err == nil {
			log.Infof("Found template %v", ok.Name())
		}

		if docroot == "" {
			log.Printf("docroot value is emptied, sub-folders will not be used.")
			timestamp = 0
		} else if timestamp == 0 {
			timestamp, _ = strconv.ParseInt(time.Now().Format("20060102150405"), 0, 0)
			log.Printf("Timestamp not specified, using %v", timestamp)
		}

		x := make.NewSite("", name, destination, alias, webserver, domain, virtualhost_path, virtualhost_template)
		x.AliasTemplate = alias_template
		x.Docroot = docroot
		if alias == "" {
			x.Alias = domain
		}

		y := make.NewmakeDB(db_host, db_user, db_pass, db_port)
		x.DatabaseSet(y)

		if workingCopy {
			x.WorkingCopy = true
		}

		if composer != "" {
			x.Composer = true
			composer2.InstallComposerCodebase(x.Name, x.TimeStampGet(), composer, x.Path, workingCopy)
		} else if makes != "" {
			x.Make = makes
			MakefilesFormatted := strings.Replace(makes, " ", "", -1)
			MakeFiles := strings.Split(MakefilesFormatted, ",")

			if rewriteSource != "" && rewriteDestination != "" {
				x.MakeFileRewriteSource = rewriteSource
				x.MakeFileRewriteDestination = rewriteDestination
			}
			x.ActionRebuildCodebase(MakeFiles)
		} else {
			cmd.Usage()
			fmt.Println()
			log.Fatalln("makes and/or composer values were not specified")
			os.Exit(1)
		}

		x.InstallSiteRef(sites_php_template)
		if timestamp != 0 {
			x.SymReinstall()
		}

		if !noInstall {
			x.ActionInstall()
		}

		if virtualhost_template != "" {
			if ok, err := os.Stat(virtualhost_template); err == nil {
				log.Infof("Found template %v for usage", ok.Name())
				x.Template = ok.Name()
			} else {
				log.Println("Could not find configured or default virtual host template.")
			}
		}

		if vhost {
			x.VhostInstall()
		}

		if alias_template != "" {
			if ok, err := os.Stat(alias_template); err == nil {
				log.Infof("Found template %v", ok.Name())
				x.AliasTemplate = ok.Name()
			} else {
				t := fmt.Sprintf("%v/src/github.com/fubarhouse/golang-drush/cmd/yoink/templates/alias.gotpl", os.Getenv("GOPATH"))
				log.Infof("Could not find template %v, using %v", ok.Name(), t)
			}
		}
		if alias == "" {
			x.Alias = domain
		}

		if drupal {
			x.AliasInstall(docroot)
		}
	},
}

func init() {
	RootCmd.AddCommand(buildCmd)
	// Required flags
	buildCmd.Flags().StringVarP(&name, "name", "n", "", "The human-readable name for this site")
	buildCmd.Flags().StringVarP(&alias, "alias", "a", "", "The drush alias for this site")
	buildCmd.Flags().StringVarP(&destination, "destination", "p", "", "The path to where the site(s) exist.")
	buildCmd.Flags().StringVarP(&domain, "domain", "d", "", "The domain this site is to use")
	// Very important but not completely needed > 0 is needed though.
	buildCmd.Flags().StringVarP(&makes, "makes", "m", "", "A comma-separated list of make files for use")
	buildCmd.Flags().StringVarP(&composer, "composer", "c", "", "Path to the composer.json file.")
	// Optional flags
	buildCmd.Flags().StringVarP(&docroot, "docroot", "o", "docroot", "The folder to use for the built codebase.")
	buildCmd.Flags().BoolVarP(&drupal, "drupal", "r", true, "Mark the build process as a Drupal build.")
	buildCmd.Flags().BoolVarP(&noInstall, "no-install", "i", false, "Mark this build so that installation doesn't happen.")
	buildCmd.Flags().Int64VarP(&timestamp, "timestamp", "t", 0, "Optional timestamp in the format YYYYMMDDHHMMSS")
	buildCmd.Flags().BoolVarP(&vhost, "vhost", "v", true, "Include a virtual host as configured with this build.")
	// Deprecated flags
	buildCmd.Flags().StringVarP(&rewriteSource, "rewrite-source", "x", "", "The rewrite string source")
	buildCmd.Flags().StringVarP(&rewriteDestination, "rewrite-destination", "y", "", "The rewrite string destination")
	buildCmd.Flags().BoolVarP(&workingCopy, "working-copy", "w", false, "Mark as a working-copy during the build process.")
	// Hide deprecated fields.
	buildCmd.Flags().MarkHidden("rewrite-source")
	buildCmd.Flags().MarkHidden("rewrite-destination")
	// Mark required flags.
	buildCmd.MarkFlagRequired("name")
	buildCmd.MarkFlagRequired("destination")
	buildCmd.MarkFlagRequired("domain")

	// Database
	db_user = viper.GetString("db_user")
	db_pass = viper.GetString("db_pass")
	db_host = viper.GetString("db_host")
	db_port = viper.GetInt("db_port")

	// Sites.php template
	sites_php_template = viper.GetString("sites_php_template")

	// Alias template
	alias_template = viper.GetString("alias_template")

	// Virtual host
	webserver = viper.GetString("webserver")
	virtualhost_path = viper.GetString("virtualhost_path")
	virtualhost_template = viper.GetString("virtualhost_template")
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"strings"

	"code.cloudfoundry.org/routing-api"
	cfclient "github.com/cloudfoundry-community/go-cfclient"
	"github.com/pivotalservices/cf-mgmt/config"
	"github.com/pivotalservices/cf-mgmt/configcommands"
	"github.com/pivotalservices/cf-mgmt/isosegment"
	"github.com/pivotalservices/cf-mgmt/organization"
	"github.com/pivotalservices/cf-mgmt/privatedomain"
	"github.com/pivotalservices/cf-mgmt/quota"
	"github.com/pivotalservices/cf-mgmt/securitygroup"
	"github.com/pivotalservices/cf-mgmt/serviceaccess"
	"github.com/pivotalservices/cf-mgmt/shareddomain"
	"github.com/pivotalservices/cf-mgmt/space"
	"github.com/pivotalservices/cf-mgmt/uaa"
	"github.com/pivotalservices/cf-mgmt/user"
	"github.com/xchapter7x/lo"
)

type CFMgmt struct {
	UAAManager              uaa.Manager
	OrgManager              organization.Manager
	SpaceManager            space.Manager
	UserManager             user.Manager
	QuotaManager            *quota.Manager
	PrivateDomainManager    privatedomain.Manager
	ConfigManager           config.Updater
	ConfigDirectory         string
	SystemDomain            string
	SecurityGroupManager    securitygroup.Manager
	IsolationSegmentManager isosegment.Manager
	ServiceAccessManager    *serviceaccess.Manager
	SharedDomainManager     *shareddomain.Manager
}

type Initialize struct {
	ConfigDir, SystemDomain, UserID, Password, ClientSecret, LdapPwd string
	Peek                                                             bool
}

func InitializeManagers(baseCommand BaseCFConfigCommand) (*CFMgmt, error) {
	return InitializePeekManagers(baseCommand, false)
}

func InitializePeekManagers(baseCommand BaseCFConfigCommand, peek bool) (*CFMgmt, error) {
	if baseCommand.SystemDomain == "" ||
		baseCommand.UserID == "" ||
		baseCommand.ClientSecret == "" {
		return nil, fmt.Errorf("must set system-domain, user-id, client-secret properties")
	}

	cfg := config.NewManager(baseCommand.ConfigDirectory)
	var err error
	cfMgmt := &CFMgmt{}
	cfMgmt.ConfigDirectory = baseCommand.ConfigDirectory
	cfMgmt.SystemDomain = baseCommand.SystemDomain
	cfMgmt.ConfigManager = config.NewManager(cfMgmt.ConfigDirectory)

	userAgent := fmt.Sprintf("cf-mgmt/%s", configcommands.VERSION)
	uaaMgr, err := uaa.NewDefaultUAAManager(cfMgmt.SystemDomain, baseCommand.UserID, baseCommand.ClientSecret, userAgent, peek)
	if err != nil {
		return nil, err
	}
	cfMgmt.UAAManager = uaaMgr

	var c *cfclient.Config
	if baseCommand.Password != "" {
		lo.G.Warning("Password parameter is deprecated, create uaa client and client-secret instead")
		c = &cfclient.Config{
			ApiAddress:        fmt.Sprintf("https://api.%s", cfMgmt.SystemDomain),
			SkipSslValidation: true,
			Username:          baseCommand.UserID,
			Password:          baseCommand.Password,
			UserAgent:         userAgent,
		}
	} else {
		c = &cfclient.Config{
			ApiAddress:        fmt.Sprintf("https://api.%s", cfMgmt.SystemDomain),
			SkipSslValidation: true,
			ClientID:          baseCommand.UserID,
			ClientSecret:      baseCommand.ClientSecret,
			UserAgent:         userAgent,
		}
	}
	// if strings.EqualFold(os.Getenv("LOG_LEVEL"), "debug") {
	// 	c.Debug = true
	// }
	client, err := cfclient.NewClient(c)
	if err != nil {
		return nil, err
	}
	cfMgmt.OrgManager = organization.NewManager(client, cfg, peek)
	cfMgmt.SpaceManager = space.NewManager(client, cfMgmt.UAAManager, cfMgmt.OrgManager, cfg, peek)
	cfMgmt.UserManager = user.NewManager(client, cfg, cfMgmt.SpaceManager, cfMgmt.OrgManager, cfMgmt.UAAManager, peek)
	cfMgmt.SecurityGroupManager = securitygroup.NewManager(client, cfMgmt.SpaceManager, cfg, peek)
	cfMgmt.QuotaManager = quota.NewManager(client, cfMgmt.SpaceManager, cfMgmt.OrgManager, cfg, peek)
	cfMgmt.PrivateDomainManager = privatedomain.NewManager(client, cfMgmt.OrgManager, cfg, peek)
	if isoSegmentManager, err := isosegment.NewManager(client, cfg, cfMgmt.OrgManager, cfMgmt.SpaceManager, peek); err == nil {
		cfMgmt.IsolationSegmentManager = isoSegmentManager
	} else {
		return nil, err
	}
	cfMgmt.ServiceAccessManager = serviceaccess.NewManager(client, cfMgmt.OrgManager, cfg, peek)
	token, err := client.GetToken()
	if err != nil {
		return nil, err
	}
	//needs to not include bearer prefix
	token = strings.Replace(token, "bearer ", "", 1)
	routingAPIClient := routing_api.NewClient(c.ApiAddress, true)
	routingAPIClient.SetToken(token)
	cfMgmt.SharedDomainManager = shareddomain.NewManager(client, routingAPIClient, cfg, peek)
	return cfMgmt, nil
}
<commit_msg>adding version info to info logging output<commit_after>package commands

import (
	"fmt"
	"strings"

	"code.cloudfoundry.org/routing-api"
	cfclient "github.com/cloudfoundry-community/go-cfclient"
	"github.com/pivotalservices/cf-mgmt/config"
	"github.com/pivotalservices/cf-mgmt/configcommands"
	"github.com/pivotalservices/cf-mgmt/isosegment"
	"github.com/pivotalservices/cf-mgmt/organization"
	"github.com/pivotalservices/cf-mgmt/privatedomain"
	"github.com/pivotalservices/cf-mgmt/quota"
	"github.com/pivotalservices/cf-mgmt/securitygroup"
	"github.com/pivotalservices/cf-mgmt/serviceaccess"
	"github.com/pivotalservices/cf-mgmt/shareddomain"
	"github.com/pivotalservices/cf-mgmt/space"
	"github.com/pivotalservices/cf-mgmt/uaa"
	"github.com/pivotalservices/cf-mgmt/user"
	"github.com/xchapter7x/lo"
)

type CFMgmt struct {
	UAAManager              uaa.Manager
	OrgManager              organization.Manager
	SpaceManager            space.Manager
	UserManager             user.Manager
	QuotaManager            *quota.Manager
	PrivateDomainManager    privatedomain.Manager
	ConfigManager           config.Updater
	ConfigDirectory         string
	SystemDomain            string
	SecurityGroupManager    securitygroup.Manager
	IsolationSegmentManager isosegment.Manager
	ServiceAccessManager    *serviceaccess.Manager
	SharedDomainManager     *shareddomain.Manager
}

type Initialize struct {
	ConfigDir, SystemDomain, UserID, Password, ClientSecret, LdapPwd string
	Peek                                                             bool
}

func InitializeManagers(baseCommand BaseCFConfigCommand) (*CFMgmt, error) {
	return InitializePeekManagers(baseCommand, false)
}

func InitializePeekManagers(baseCommand BaseCFConfigCommand, peek bool) (*CFMgmt, error) {
	lo.G.Infof("Using version %s of cf-mgmt", configcommands.VERSION)
	if baseCommand.SystemDomain == "" ||
		baseCommand.UserID == "" ||
		baseCommand.ClientSecret == "" {
		return nil, fmt.Errorf("must set system-domain, user-id, client-secret properties")
	}

	cfg := config.NewManager(baseCommand.ConfigDirectory)
	var err error
	cfMgmt := &CFMgmt{}
	cfMgmt.ConfigDirectory = baseCommand.ConfigDirectory
	cfMgmt.SystemDomain = baseCommand.SystemDomain
	cfMgmt.ConfigManager = config.NewManager(cfMgmt.ConfigDirectory)

	userAgent := fmt.Sprintf("cf-mgmt/%s", configcommands.VERSION)
	uaaMgr, err := uaa.NewDefaultUAAManager(cfMgmt.SystemDomain, baseCommand.UserID, baseCommand.ClientSecret, userAgent, peek)
	if err != nil {
		return nil, err
	}
	cfMgmt.UAAManager = uaaMgr

	var c *cfclient.Config
	if baseCommand.Password != "" {
		lo.G.Warning("Password parameter is deprecated, create uaa client and client-secret instead")
		c = &cfclient.Config{
			ApiAddress:        fmt.Sprintf("https://api.%s", cfMgmt.SystemDomain),
			SkipSslValidation: true,
			Username:          baseCommand.UserID,
			Password:          baseCommand.Password,
			UserAgent:         userAgent,
		}
	} else {
		c = &cfclient.Config{
			ApiAddress:        fmt.Sprintf("https://api.%s", cfMgmt.SystemDomain),
			SkipSslValidation: true,
			ClientID:          baseCommand.UserID,
			ClientSecret:      baseCommand.ClientSecret,
			UserAgent:         userAgent,
		}
	}
	// if strings.EqualFold(os.Getenv("LOG_LEVEL"), "debug") {
	// 	c.Debug = true
	// }
	client, err := cfclient.NewClient(c)
	if err != nil {
		return nil, err
	}
	cfMgmt.OrgManager = organization.NewManager(client, cfg, peek)
	cfMgmt.SpaceManager = space.NewManager(client, cfMgmt.UAAManager, cfMgmt.OrgManager, cfg, peek)
	cfMgmt.UserManager = user.NewManager(client, cfg, cfMgmt.SpaceManager, cfMgmt.OrgManager, cfMgmt.UAAManager, peek)
	cfMgmt.SecurityGroupManager = securitygroup.NewManager(client, cfMgmt.SpaceManager, cfg, peek)
	cfMgmt.QuotaManager = quota.NewManager(client, cfMgmt.SpaceManager, cfMgmt.OrgManager, cfg, peek)
	cfMgmt.PrivateDomainManager = privatedomain.NewManager(client, cfMgmt.OrgManager, cfg, peek)
	if isoSegmentManager, err := isosegment.NewManager(client, cfg, cfMgmt.OrgManager, cfMgmt.SpaceManager, peek); err == nil {
		cfMgmt.IsolationSegmentManager = isoSegmentManager
	} else {
		return nil, err
	}
	cfMgmt.ServiceAccessManager = serviceaccess.NewManager(client, cfMgmt.OrgManager, cfg, peek)
	token, err := client.GetToken()
	if err != nil {
		return nil, err
	}
	//needs to not include bearer prefix
	token = strings.Replace(token, "bearer ", "", 1)
	routingAPIClient := routing_api.NewClient(c.ApiAddress, true)
	routingAPIClient.SetToken(token)
	cfMgmt.SharedDomainManager = shareddomain.NewManager(client, routingAPIClient, cfg, peek)
	return cfMgmt, nil
}
<|endoftext|>


<commit_before>package components

import (
	"sort"

	"github.com/erroneousboat/termui"
	"github.com/renstrom/fuzzysearch/fuzzy"
)

const (
	IconOnline       = "●"
	IconOffline      = "○"
	IconChannel      = "#"
	IconGroup        = "☰"
	IconIM           = "●"
	IconNotification = "*"
)

// Channels is the definition of a Channels component
type Channels struct {
	List            *termui.List
	SelectedChannel int // index of which channel is selected from the List
	Offset          int // from what offset are channels rendered
	CursorPosition  int // the y position of the 'cursor'

	SearchMatches  []int // index of the search matches
	SearchPosition int   // current position of a search match
}

// CreateChannels is the constructor for the Channels component
func CreateChannelsComponent(inputHeight int) *Channels {
	channels := &Channels{
		List: termui.NewList(),
	}

	channels.List.BorderLabel = "Channels"
	channels.List.Height = termui.TermHeight() - inputHeight

	channels.SelectedChannel = 0
	channels.Offset = 0
	channels.CursorPosition = channels.List.InnerBounds().Min.Y

	return channels
}

// Buffer implements interface termui.Bufferer
func (c *Channels) Buffer() termui.Buffer {
	buf := c.List.Buffer()

	for i, item := range c.List.Items[c.Offset:] {

		y := c.List.InnerBounds().Min.Y + i

		if y > c.List.InnerBounds().Max.Y-1 {
			break
		}

		var cells []termui.Cell
		if y == c.CursorPosition {
			cells = termui.DefaultTxBuilder.Build(
				item, c.List.ItemBgColor, c.List.ItemFgColor)
		} else {
			cells = termui.DefaultTxBuilder.Build(
				item, c.List.ItemFgColor, c.List.ItemBgColor)
		}

		cells = termui.DTrimTxCls(cells, c.List.InnerWidth())

		x := 0
		for _, cell := range cells {
			width := cell.Width()
			buf.Set(c.List.InnerBounds().Min.X+x, y, cell)
			x += width
		}

		// When not at the end of the pane fill it up empty characters
		for x < c.List.InnerBounds().Max.X-1 {
			if y == c.CursorPosition {
				buf.Set(x+1, y,
					termui.Cell{
						Ch: ' ',
						Fg: c.List.ItemBgColor,
						Bg: c.List.ItemFgColor,
					},
				)
			} else {
				buf.Set(
					x+1, y,
					termui.Cell{
						Ch: ' ',
						Fg: c.List.ItemFgColor,
						Bg: c.List.ItemBgColor,
					},
				)
			}
			x++
		}
	}

	return buf
}

// GetHeight implements interface termui.GridBufferer
func (c *Channels) GetHeight() int {
	return c.List.Block.GetHeight()
}

// SetWidth implements interface termui.GridBufferer
func (c *Channels) SetWidth(w int) {
	c.List.SetWidth(w)
}

// SetX implements interface termui.GridBufferer
func (c *Channels) SetX(x int) {
	c.List.SetX(x)
}

// SetY implements interface termui.GridBufferer
func (c *Channels) SetY(y int) {
	c.List.SetY(y)
}

func (c *Channels) SetChannels(channels []string) {
	c.List.Items = channels
}

// SetSelectedChannel sets the SelectedChannel given the index
func (c *Channels) SetSelectedChannel(index int) {
	c.SelectedChannel = index
}

// GetSelectedChannel returns the SelectedChannel
func (c *Channels) GetSelectedChannel() string {
	return c.List.Items[c.SelectedChannel]
}

// MoveCursorUp will decrease the SelectedChannel by 1
func (c *Channels) MoveCursorUp() {
	if c.SelectedChannel > 0 {
		c.SetSelectedChannel(c.SelectedChannel - 1)
		c.ScrollUp()
	}
}

// MoveCursorDown will increase the SelectedChannel by 1
func (c *Channels) MoveCursorDown() {
	if c.SelectedChannel < len(c.List.Items)-1 {
		c.SetSelectedChannel(c.SelectedChannel + 1)
		c.ScrollDown()
	}
}

// MoveCursorTop will move the cursor to the top of the channels
func (c *Channels) MoveCursorTop() {
	c.SetSelectedChannel(0)
	c.CursorPosition = c.List.InnerBounds().Min.Y
	c.Offset = 0
}

// MoveCursorBottom will move the cursor to the bottom of the channels
func (c *Channels) MoveCursorBottom() {
	c.SetSelectedChannel(len(c.List.Items) - 1)

	offset := len(c.List.Items) - (c.List.InnerBounds().Max.Y - 1)

	if offset < 0 {
		c.Offset = 0
		c.CursorPosition = c.SelectedChannel + 1
	} else {
		c.Offset = offset
		c.CursorPosition = c.List.InnerBounds().Max.Y - 1
	}
}

// ScrollUp enables us to scroll through the channel list when it overflows
func (c *Channels) ScrollUp() {
	// Is cursor at the top of the channel view?
	if c.CursorPosition == c.List.InnerBounds().Min.Y {
		if c.Offset > 0 {
			c.Offset--
		}
	} else {
		c.CursorPosition--
	}
}

// ScrollDown enables us to scroll through the channel list when it overflows
func (c *Channels) ScrollDown() {
	// Is the cursor at the bottom of the channel view?
	if c.CursorPosition == c.List.InnerBounds().Max.Y-1 {
		if c.Offset < len(c.List.Items)-1 {
			c.Offset++
		}
	} else {
		c.CursorPosition++
	}
}

// Search will search through the channels to find a channel,
// when a match has been found the selected channel will then
// be the channel that has been found
func (c *Channels) Search(term string) {
	c.SearchMatches = make([]int, 0)

	matches := fuzzy.RankFind(term, c.List.Items)
	sort.Sort(matches)

	for _, m := range matches {
		for i, item := range c.List.Items {
			if m.Target == item {
				c.SearchMatches = append(c.SearchMatches, i)
				break
			}
		}
	}

	c.GotoPosition(0)
	c.SearchPosition = 0
}

// GotoPosition is used by the search functionality to automatically
// scroll to a specific location in the channels component
func (c *Channels) GotoPosition(position int) {

	// The new position
	newPos := c.SearchMatches[position]

	// Is the new position in range of the current view?
	minRange := c.Offset
	maxRange := c.Offset + (c.List.InnerBounds().Max.Y - 2)

	if newPos < minRange {
		// newPos is above, we need to scroll up.
		c.SetSelectedChannel(newPos)

		// How much do we need to scroll to get it into range?
		c.Offset = c.Offset - (minRange - newPos)
	} else if newPos > maxRange {
		// newPos is below, we need to scroll down
		c.SetSelectedChannel(newPos)

		// How much do we need to scroll to get it into range?
		c.Offset = c.Offset + (newPos - maxRange)
	} else {
		// newPos is inside range
		c.SetSelectedChannel(newPos)
	}

	// Set cursor to correct position
	c.CursorPosition = (newPos - c.Offset) + 1
}

// SearchNext allows us to cycle through the c.SearchMatches
func (c *Channels) SearchNext() {
	newPosition := c.SearchPosition + 1
	if newPosition <= len(c.SearchMatches)-1 {
		c.GotoPosition(newPosition)
		c.SearchPosition = newPosition
	}
}

// SearchPrev allows us to cycle through the c.SearchMatches
func (c *Channels) SearchPrev() {
	newPosition := c.SearchPosition - 1
	if newPosition >= 0 {
		c.GotoPosition(newPosition)
		c.SearchPosition = newPosition
	}
}
<commit_msg>Remove RankFind<commit_after>package components

import (
	"github.com/erroneousboat/termui"
	"github.com/renstrom/fuzzysearch/fuzzy"
)

const (
	IconOnline       = "●"
	IconOffline      = "○"
	IconChannel      = "#"
	IconGroup        = "☰"
	IconIM           = "●"
	IconNotification = "*"
)

// Channels is the definition of a Channels component
type Channels struct {
	List            *termui.List
	SelectedChannel int // index of which channel is selected from the List
	Offset          int // from what offset are channels rendered
	CursorPosition  int // the y position of the 'cursor'

	SearchMatches  []int // index of the search matches
	SearchPosition int   // current position of a search match
}

// CreateChannels is the constructor for the Channels component
func CreateChannelsComponent(inputHeight int) *Channels {
	channels := &Channels{
		List: termui.NewList(),
	}

	channels.List.BorderLabel = "Channels"
	channels.List.Height = termui.TermHeight() - inputHeight

	channels.SelectedChannel = 0
	channels.Offset = 0
	channels.CursorPosition = channels.List.InnerBounds().Min.Y

	return channels
}

// Buffer implements interface termui.Bufferer
func (c *Channels) Buffer() termui.Buffer {
	buf := c.List.Buffer()

	for i, item := range c.List.Items[c.Offset:] {

		y := c.List.InnerBounds().Min.Y + i

		if y > c.List.InnerBounds().Max.Y-1 {
			break
		}

		var cells []termui.Cell
		if y == c.CursorPosition {
			cells = termui.DefaultTxBuilder.Build(
				item, c.List.ItemBgColor, c.List.ItemFgColor)
		} else {
			cells = termui.DefaultTxBuilder.Build(
				item, c.List.ItemFgColor, c.List.ItemBgColor)
		}

		cells = termui.DTrimTxCls(cells, c.List.InnerWidth())

		x := 0
		for _, cell := range cells {
			width := cell.Width()
			buf.Set(c.List.InnerBounds().Min.X+x, y, cell)
			x += width
		}

		// When not at the end of the pane fill it up empty characters
		for x < c.List.InnerBounds().Max.X-1 {
			if y == c.CursorPosition {
				buf.Set(x+1, y,
					termui.Cell{
						Ch: ' ',
						Fg: c.List.ItemBgColor,
						Bg: c.List.ItemFgColor,
					},
				)
			} else {
				buf.Set(
					x+1, y,
					termui.Cell{
						Ch: ' ',
						Fg: c.List.ItemFgColor,
						Bg: c.List.ItemBgColor,
					},
				)
			}
			x++
		}
	}

	return buf
}

// GetHeight implements interface termui.GridBufferer
func (c *Channels) GetHeight() int {
	return c.List.Block.GetHeight()
}

// SetWidth implements interface termui.GridBufferer
func (c *Channels) SetWidth(w int) {
	c.List.SetWidth(w)
}

// SetX implements interface termui.GridBufferer
func (c *Channels) SetX(x int) {
	c.List.SetX(x)
}

// SetY implements interface termui.GridBufferer
func (c *Channels) SetY(y int) {
	c.List.SetY(y)
}

func (c *Channels) SetChannels(channels []string) {
	c.List.Items = channels
}

// SetSelectedChannel sets the SelectedChannel given the index
func (c *Channels) SetSelectedChannel(index int) {
	c.SelectedChannel = index
}

// GetSelectedChannel returns the SelectedChannel
func (c *Channels) GetSelectedChannel() string {
	return c.List.Items[c.SelectedChannel]
}

// MoveCursorUp will decrease the SelectedChannel by 1
func (c *Channels) MoveCursorUp() {
	if c.SelectedChannel > 0 {
		c.SetSelectedChannel(c.SelectedChannel - 1)
		c.ScrollUp()
	}
}

// MoveCursorDown will increase the SelectedChannel by 1
func (c *Channels) MoveCursorDown() {
	if c.SelectedChannel < len(c.List.Items)-1 {
		c.SetSelectedChannel(c.SelectedChannel + 1)
		c.ScrollDown()
	}
}

// MoveCursorTop will move the cursor to the top of the channels
func (c *Channels) MoveCursorTop() {
	c.SetSelectedChannel(0)
	c.CursorPosition = c.List.InnerBounds().Min.Y
	c.Offset = 0
}

// MoveCursorBottom will move the cursor to the bottom of the channels
func (c *Channels) MoveCursorBottom() {
	c.SetSelectedChannel(len(c.List.Items) - 1)

	offset := len(c.List.Items) - (c.List.InnerBounds().Max.Y - 1)

	if offset < 0 {
		c.Offset = 0
		c.CursorPosition = c.SelectedChannel + 1
	} else {
		c.Offset = offset
		c.CursorPosition = c.List.InnerBounds().Max.Y - 1
	}
}

// ScrollUp enables us to scroll through the channel list when it overflows
func (c *Channels) ScrollUp() {
	// Is cursor at the top of the channel view?
	if c.CursorPosition == c.List.InnerBounds().Min.Y {
		if c.Offset > 0 {
			c.Offset--
		}
	} else {
		c.CursorPosition--
	}
}

// ScrollDown enables us to scroll through the channel list when it overflows
func (c *Channels) ScrollDown() {
	// Is the cursor at the bottom of the channel view?
	if c.CursorPosition == c.List.InnerBounds().Max.Y-1 {
		if c.Offset < len(c.List.Items)-1 {
			c.Offset++
		}
	} else {
		c.CursorPosition++
	}
}

// Search will search through the channels to find a channel,
// when a match has been found the selected channel will then
// be the channel that has been found
func (c *Channels) Search(term string) {
	c.SearchMatches = make([]int, 0)

	matches := fuzzy.Find(term, c.List.Items)

	for _, m := range matches {
		for i, item := range c.List.Items {
			if m == item {
				c.SearchMatches = append(c.SearchMatches, i)
				break
			}
		}
	}

	c.GotoPosition(0)
	c.SearchPosition = 0
}

// GotoPosition is used by the search functionality to automatically
// scroll to a specific location in the channels component
func (c *Channels) GotoPosition(position int) {

	// The new position
	newPos := c.SearchMatches[position]

	// Is the new position in range of the current view?
	minRange := c.Offset
	maxRange := c.Offset + (c.List.InnerBounds().Max.Y - 2)

	if newPos < minRange {
		// newPos is above, we need to scroll up.
		c.SetSelectedChannel(newPos)

		// How much do we need to scroll to get it into range?
		c.Offset = c.Offset - (minRange - newPos)
	} else if newPos > maxRange {
		// newPos is below, we need to scroll down
		c.SetSelectedChannel(newPos)

		// How much do we need to scroll to get it into range?
		c.Offset = c.Offset + (newPos - maxRange)
	} else {
		// newPos is inside range
		c.SetSelectedChannel(newPos)
	}

	// Set cursor to correct position
	c.CursorPosition = (newPos - c.Offset) + 1
}

// SearchNext allows us to cycle through the c.SearchMatches
func (c *Channels) SearchNext() {
	newPosition := c.SearchPosition + 1
	if newPosition <= len(c.SearchMatches)-1 {
		c.GotoPosition(newPosition)
		c.SearchPosition = newPosition
	}
}

// SearchPrev allows us to cycle through the c.SearchMatches
func (c *Channels) SearchPrev() {
	newPosition := c.SearchPosition - 1
	if newPosition >= 0 {
		c.GotoPosition(newPosition)
		c.SearchPosition = newPosition
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package server

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"testing"

	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/server/healthz"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/rest"
)

func TestNewWithDelegate(t *testing.T) {
	delegateConfig := NewConfig(codecs)
	delegateConfig.ExternalAddress = "192.168.10.4:443"
	delegateConfig.PublicAddress = net.ParseIP("192.168.10.4")
	delegateConfig.LegacyAPIGroupPrefixes = sets.NewString("/api")
	delegateConfig.LoopbackClientConfig = &rest.Config{}
	delegateConfig.SwaggerConfig = DefaultSwaggerConfig()
	clientset := fake.NewSimpleClientset()
	if clientset == nil {
		t.Fatal("unable to create fake client set")
	}

	delegateConfig.HealthzChecks = append(delegateConfig.HealthzChecks, healthz.NamedCheck("delegate-health", func(r *http.Request) error {
		return fmt.Errorf("delegate failed healthcheck")
	}))

	sharedInformers := informers.NewSharedInformerFactory(clientset, delegateConfig.LoopbackClientConfig.Timeout)
	delegateServer, err := delegateConfig.Complete(sharedInformers).New("test", NewEmptyDelegate())
	if err != nil {
		t.Fatal(err)
	}
	delegateServer.Handler.NonGoRestfulMux.HandleFunc("/foo", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusForbidden)
	})

	delegateServer.AddPostStartHook("delegate-post-start-hook", func(context PostStartHookContext) error {
		return nil
	})

	// this wires up swagger
	delegateServer.PrepareRun()

	wrappingConfig := NewConfig(codecs)
	wrappingConfig.ExternalAddress = "192.168.10.4:443"
	wrappingConfig.PublicAddress = net.ParseIP("192.168.10.4")
	wrappingConfig.LegacyAPIGroupPrefixes = sets.NewString("/api")
	wrappingConfig.LoopbackClientConfig = &rest.Config{}
	wrappingConfig.SwaggerConfig = DefaultSwaggerConfig()

	wrappingConfig.HealthzChecks = append(wrappingConfig.HealthzChecks, healthz.NamedCheck("wrapping-health", func(r *http.Request) error {
		return fmt.Errorf("wrapping failed healthcheck")
	}))

	sharedInformers = informers.NewSharedInformerFactory(clientset, wrappingConfig.LoopbackClientConfig.Timeout)
	wrappingServer, err := wrappingConfig.Complete(sharedInformers).New("test", delegateServer)
	if err != nil {
		t.Fatal(err)
	}
	wrappingServer.Handler.NonGoRestfulMux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusUnauthorized)
	})

	wrappingServer.AddPostStartHook("wrapping-post-start-hook", func(context PostStartHookContext) error {
		return nil
	})

	stopCh := make(chan struct{})
	defer close(stopCh)
	wrappingServer.PrepareRun()
	wrappingServer.RunPostStartHooks(stopCh)

	server := httptest.NewServer(wrappingServer.Handler)
	defer server.Close()

	checkPath(server.URL, http.StatusOK, `{
  "paths": [
    "/apis",
    "/bar",
    "/foo",
    "/healthz",
    "/healthz/delegate-health",
    "/healthz/log",
    "/healthz/ping",
    "/healthz/poststarthook/delegate-post-start-hook",
    "/healthz/poststarthook/generic-apiserver-start-informers",
    "/healthz/poststarthook/wrapping-post-start-hook",
    "/healthz/wrapping-health",
    "/metrics",
    "/swaggerapi"
  ]
}`, t)
	checkPath(server.URL+"/healthz", http.StatusInternalServerError, `[+]ping ok
[+]log ok
[-]wrapping-health failed: reason withheld
[-]delegate-health failed: reason withheld
[+]poststarthook/generic-apiserver-start-informers ok
[+]poststarthook/delegate-post-start-hook ok
[+]poststarthook/wrapping-post-start-hook ok
healthz check failed
`, t)

	checkPath(server.URL+"/healthz/delegate-health", http.StatusInternalServerError, `internal server error: delegate failed healthcheck
`, t)
	checkPath(server.URL+"/healthz/wrapping-health", http.StatusInternalServerError, `internal server error: wrapping failed healthcheck
`, t)
	checkPath(server.URL+"/healthz/poststarthook/delegate-post-start-hook", http.StatusOK, `ok`, t)
	checkPath(server.URL+"/healthz/poststarthook/wrapping-post-start-hook", http.StatusOK, `ok`, t)
	checkPath(server.URL+"/foo", http.StatusForbidden, ``, t)
	checkPath(server.URL+"/bar", http.StatusUnauthorized, ``, t)
}

func checkPath(url string, expectedStatusCode int, expectedBody string, t *testing.T) {
	resp, err := http.Get(url)
	if err != nil {
		t.Fatal(err)
	}
	dump, _ := httputil.DumpResponse(resp, true)
	t.Log(string(dump))

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}

	if e, a := expectedBody, string(body); e != a {
		t.Errorf("%q expected %v, got %v", url, e, a)
	}
	if e, a := expectedStatusCode, resp.StatusCode; e != a {
		t.Errorf("%q expected %v, got %v", url, e, a)
	}
}
<commit_msg>UPSTREAM: 73516: Wait for post-hooks to run before checking the healthz output<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package server

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"testing"

	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/server/healthz"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/rest"
)

func TestNewWithDelegate(t *testing.T) {
	delegateConfig := NewConfig(codecs)
	delegateConfig.ExternalAddress = "192.168.10.4:443"
	delegateConfig.PublicAddress = net.ParseIP("192.168.10.4")
	delegateConfig.LegacyAPIGroupPrefixes = sets.NewString("/api")
	delegateConfig.LoopbackClientConfig = &rest.Config{}
	delegateConfig.SwaggerConfig = DefaultSwaggerConfig()
	clientset := fake.NewSimpleClientset()
	if clientset == nil {
		t.Fatal("unable to create fake client set")
	}

	delegateConfig.HealthzChecks = append(delegateConfig.HealthzChecks, healthz.NamedCheck("delegate-health", func(r *http.Request) error {
		return fmt.Errorf("delegate failed healthcheck")
	}))

	sharedInformers := informers.NewSharedInformerFactory(clientset, delegateConfig.LoopbackClientConfig.Timeout)
	delegateServer, err := delegateConfig.Complete(sharedInformers).New("test", NewEmptyDelegate())
	if err != nil {
		t.Fatal(err)
	}
	delegateServer.Handler.NonGoRestfulMux.HandleFunc("/foo", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusForbidden)
	})

	delegatePostStartHookChan := make(chan struct{})
	delegateServer.AddPostStartHook("delegate-post-start-hook", func(context PostStartHookContext) error {
		defer close(delegatePostStartHookChan)
		return nil
	})

	// this wires up swagger
	delegateServer.PrepareRun()

	wrappingConfig := NewConfig(codecs)
	wrappingConfig.ExternalAddress = "192.168.10.4:443"
	wrappingConfig.PublicAddress = net.ParseIP("192.168.10.4")
	wrappingConfig.LegacyAPIGroupPrefixes = sets.NewString("/api")
	wrappingConfig.LoopbackClientConfig = &rest.Config{}
	wrappingConfig.SwaggerConfig = DefaultSwaggerConfig()

	wrappingConfig.HealthzChecks = append(wrappingConfig.HealthzChecks, healthz.NamedCheck("wrapping-health", func(r *http.Request) error {
		return fmt.Errorf("wrapping failed healthcheck")
	}))

	sharedInformers = informers.NewSharedInformerFactory(clientset, wrappingConfig.LoopbackClientConfig.Timeout)
	wrappingServer, err := wrappingConfig.Complete(sharedInformers).New("test", delegateServer)
	if err != nil {
		t.Fatal(err)
	}
	wrappingServer.Handler.NonGoRestfulMux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusUnauthorized)
	})

	wrappingPostStartHookChan := make(chan struct{})
	wrappingServer.AddPostStartHook("wrapping-post-start-hook", func(context PostStartHookContext) error {
		defer close(wrappingPostStartHookChan)
		return nil
	})

	stopCh := make(chan struct{})
	defer close(stopCh)
	wrappingServer.PrepareRun()
	wrappingServer.RunPostStartHooks(stopCh)

	server := httptest.NewServer(wrappingServer.Handler)
	defer server.Close()

	// Wait for the hooks to finish before checking the response
	<-delegatePostStartHookChan
	<-wrappingPostStartHookChan

	checkPath(server.URL, http.StatusOK, `{
  "paths": [
    "/apis",
    "/bar",
    "/foo",
    "/healthz",
    "/healthz/delegate-health",
    "/healthz/log",
    "/healthz/ping",
    "/healthz/poststarthook/delegate-post-start-hook",
    "/healthz/poststarthook/generic-apiserver-start-informers",
    "/healthz/poststarthook/wrapping-post-start-hook",
    "/healthz/wrapping-health",
    "/metrics",
    "/swaggerapi"
  ]
}`, t)
	checkPath(server.URL+"/healthz", http.StatusInternalServerError, `[+]ping ok
[+]log ok
[-]wrapping-health failed: reason withheld
[-]delegate-health failed: reason withheld
[+]poststarthook/generic-apiserver-start-informers ok
[+]poststarthook/delegate-post-start-hook ok
[+]poststarthook/wrapping-post-start-hook ok
healthz check failed
`, t)

	checkPath(server.URL+"/healthz/delegate-health", http.StatusInternalServerError, `internal server error: delegate failed healthcheck
`, t)
	checkPath(server.URL+"/healthz/wrapping-health", http.StatusInternalServerError, `internal server error: wrapping failed healthcheck
`, t)
	checkPath(server.URL+"/healthz/poststarthook/delegate-post-start-hook", http.StatusOK, `ok`, t)
	checkPath(server.URL+"/healthz/poststarthook/wrapping-post-start-hook", http.StatusOK, `ok`, t)
	checkPath(server.URL+"/foo", http.StatusForbidden, ``, t)
	checkPath(server.URL+"/bar", http.StatusUnauthorized, ``, t)
}

func checkPath(url string, expectedStatusCode int, expectedBody string, t *testing.T) {
	resp, err := http.Get(url)
	if err != nil {
		t.Fatal(err)
	}
	dump, _ := httputil.DumpResponse(resp, true)
	t.Log(string(dump))

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}

	if e, a := expectedBody, string(body); e != a {
		t.Errorf("%q expected %v, got %v", url, e, a)
	}
	if e, a := expectedStatusCode, resp.StatusCode; e != a {
		t.Errorf("%q expected %v, got %v", url, e, a)
	}
}
<|endoftext|>


<commit_before>package geo

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math"
)

// Represents a Physical Point in geographic notation [lat, lng].
type Point struct {
	lat float64
	lng float64
}

const (
	// According to Wikipedia, the Earth's radius is about 6,371km
	EARTH_RADIUS = 6371
)

// Returns a new Point populated by the passed in latitude (lat) and longitude (lng) values.
func NewPoint(lat float64, lng float64) *Point {
	return &Point{lat: lat, lng: lng}
}

// Returns Point p's latitude.
func (p *Point) Lat() float64 {
	return p.lat
}

// Returns Point p's longitude.
func (p *Point) Lng() float64 {
	return p.lng
}

// Returns a Point populated with the lat and lng coordinates
// by transposing the origin point the passed in distance (in kilometers)
// by the passed in compass bearing (in degrees).
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) PointAtDistanceAndBearing(dist float64, bearing float64) *Point {

	dr := dist / EARTH_RADIUS

	bearing = (bearing * (math.Pi / 180.0))

	lat1 := (p.lat * (math.Pi / 180.0))
	lng1 := (p.lng * (math.Pi / 180.0))

	lat2_part1 := math.Sin(lat1) * math.Cos(dr)
	lat2_part2 := math.Cos(lat1) * math.Sin(dr) * math.Cos(bearing)

	lat2 := math.Asin(lat2_part1 + lat2_part2)

	lng2_part1 := math.Sin(bearing) * math.Sin(dr) * math.Cos(lat1)
	lng2_part2 := math.Cos(dr) - (math.Sin(lat1) * math.Sin(lat2))

	lng2 := lng1 + math.Atan2(lng2_part1, lng2_part2)
	lng2 = math.Mod((lng2+3*math.Pi), (2*math.Pi)) - math.Pi

	lat2 = lat2 * (180.0 / math.Pi)
	lng2 = lng2 * (180.0 / math.Pi)

	return &Point{lat: lat2, lng: lng2}
}

// Calculates the Haversine distance between two points.
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) GreatCircleDistance(p2 *Point) float64 {
	dLat := (p2.lat - p.lat) * (math.Pi / 180.0)
	dLon := (p2.lng - p.lng) * (math.Pi / 180.0)

	lat1 := p.lat * (math.Pi / 180.0)
	lat2 := p2.lat * (math.Pi / 180.0)

	a1 := math.Sin(dLat/2) * math.Sin(dLat/2)
	a2 := math.Sin(dLon/2) * math.Sin(dLon/2) * math.Cos(lat1) * math.Cos(lat2)

	a := a1 + a2

	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))

	return EARTH_RADIUS * c
}

// Calculates the initial bearing (sometimes referred to as forward azimuth)
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) BearingTo(p2 *Point) float64 {

	dLon := (p2.lng - p.lng) * math.Pi / 180.0

	lat1 := p.lat * math.Pi / 180.0
	lat2 := p2.lat * math.Pi / 180.0

	y := math.Sin(dLon) * math.Cos(lat2)
	x := math.Cos(lat1)*math.Sin(lat2) -
		math.Sin(lat1)*math.Cos(lat2)*math.Cos(dLon)
	brng := math.Atan2(y, x) * 180.0 / math.Pi

	return brng
}

// Renders the current Point to valid JSON.
// Implements the json.Marshaller Interface.
func (p *Point) MarshalJSON() ([]byte, error) {
	res := fmt.Sprintf(`{"lat":%v, "lng":%v}`, p.Lat(), p.Lng())
	return []byte(res), nil
}

// Decodes the current Point from a JSON body.
// Throws an error if the body of the point cannot be interpreted by the JSON body
func (p *Point) UnmarshalJSON(data []byte) error {
	// TODO throw an error if there is an issue parsing the body.
	dec := json.NewDecoder(bytes.NewReader(data))
	var values map[string]float64
	err := dec.Decode(&values)

	if err != nil {
		log.Print(err)
		return err
	}

	*p = *NewPoint(values["lat"], values["lng"])

	return nil
}
<commit_msg>added return-unit to Point.GreatCircleDistance<commit_after>package geo

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math"
)

// Represents a Physical Point in geographic notation [lat, lng].
type Point struct {
	lat float64
	lng float64
}

const (
	// According to Wikipedia, the Earth's radius is about 6,371km
	EARTH_RADIUS = 6371
)

// Returns a new Point populated by the passed in latitude (lat) and longitude (lng) values.
func NewPoint(lat float64, lng float64) *Point {
	return &Point{lat: lat, lng: lng}
}

// Returns Point p's latitude.
func (p *Point) Lat() float64 {
	return p.lat
}

// Returns Point p's longitude.
func (p *Point) Lng() float64 {
	return p.lng
}

// Returns a Point populated with the lat and lng coordinates
// by transposing the origin point the passed in distance (in kilometers)
// by the passed in compass bearing (in degrees).
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) PointAtDistanceAndBearing(dist float64, bearing float64) *Point {

	dr := dist / EARTH_RADIUS

	bearing = (bearing * (math.Pi / 180.0))

	lat1 := (p.lat * (math.Pi / 180.0))
	lng1 := (p.lng * (math.Pi / 180.0))

	lat2_part1 := math.Sin(lat1) * math.Cos(dr)
	lat2_part2 := math.Cos(lat1) * math.Sin(dr) * math.Cos(bearing)

	lat2 := math.Asin(lat2_part1 + lat2_part2)

	lng2_part1 := math.Sin(bearing) * math.Sin(dr) * math.Cos(lat1)
	lng2_part2 := math.Cos(dr) - (math.Sin(lat1) * math.Sin(lat2))

	lng2 := lng1 + math.Atan2(lng2_part1, lng2_part2)
	lng2 = math.Mod((lng2+3*math.Pi), (2*math.Pi)) - math.Pi

	lat2 = lat2 * (180.0 / math.Pi)
	lng2 = lng2 * (180.0 / math.Pi)

	return &Point{lat: lat2, lng: lng2}
}

// Calculates the Haversine distance between two points in kilometers.
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) GreatCircleDistance(p2 *Point) float64 {
	dLat := (p2.lat - p.lat) * (math.Pi / 180.0)
	dLon := (p2.lng - p.lng) * (math.Pi / 180.0)

	lat1 := p.lat * (math.Pi / 180.0)
	lat2 := p2.lat * (math.Pi / 180.0)

	a1 := math.Sin(dLat/2) * math.Sin(dLat/2)
	a2 := math.Sin(dLon/2) * math.Sin(dLon/2) * math.Cos(lat1) * math.Cos(lat2)

	a := a1 + a2

	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))

	return EARTH_RADIUS * c
}

// Calculates the initial bearing (sometimes referred to as forward azimuth)
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) BearingTo(p2 *Point) float64 {

	dLon := (p2.lng - p.lng) * math.Pi / 180.0

	lat1 := p.lat * math.Pi / 180.0
	lat2 := p2.lat * math.Pi / 180.0

	y := math.Sin(dLon) * math.Cos(lat2)
	x := math.Cos(lat1)*math.Sin(lat2) -
		math.Sin(lat1)*math.Cos(lat2)*math.Cos(dLon)
	brng := math.Atan2(y, x) * 180.0 / math.Pi

	return brng
}

// Renders the current Point to valid JSON.
// Implements the json.Marshaller Interface.
func (p *Point) MarshalJSON() ([]byte, error) {
	res := fmt.Sprintf(`{"lat":%v, "lng":%v}`, p.Lat(), p.Lng())
	return []byte(res), nil
}

// Decodes the current Point from a JSON body.
// Throws an error if the body of the point cannot be interpreted by the JSON body
func (p *Point) UnmarshalJSON(data []byte) error {
	// TODO throw an error if there is an issue parsing the body.
	dec := json.NewDecoder(bytes.NewReader(data))
	var values map[string]float64
	err := dec.Decode(&values)

	if err != nil {
		log.Print(err)
		return err
	}

	*p = *NewPoint(values["lat"], values["lng"])

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// Chunk size that we split stored data into so it's easier to resume uploads/downloads
// This used to be configurable, but it caused too many issues if different people had different
// settings in a shared repository
// This is only 'var' rather than 'const' to allow tests to modify
var ChunkSize = int64(32 * 1024 * 1024)

// Options (command line or config file)
// Only general options, command-specific ones dealt with in commands
type Options struct {
	// Help option was requested
	HelpRequested bool
	// Output verbosely (to console & log)
	Verbose bool
	// Output quietly (to console)
	Quiet bool
	// Don't actually perform any tasks
	DryRun bool
	// Never prompt for user input, rely on command line options only
	NonInteractive bool
	// The command to run
	Command string
	// Other value options not converted
	StringOpts map[string]string
	// Other boolean options not converted
	BoolOpts StringSet
	// Other arguments to the command
	Args []string
	// Whether to write output to a log
	LogEnabled bool
	// Log file (optional, defaults to ~/git-lob.log if not specified)
	LogFile string
	// Log verbosely even if main Verbose option is disabled for console
	VerboseLog bool
	// Shared folder in which to store binary files for all repos
	SharedStore string
	// Auto fetch (download) on checkout?
	AutoFetchEnabled bool
	// 'Recent' window in days for all refs (branches/tags) compared to current date
	RecentRefsPeriodDays int
	// 'Recent' window in days for commits on HEAD compared to latest commit date
	RecentCommitsPeriodHEAD int
	// 'Recent' window in days for commits on other branches/tags compared to latest commit date
	RecentCommitsPeriodOther int
	// List of paths to include when fetching
	FetchIncludePaths []string
	// List of paths to exclude when fetching
	FetchExcludePaths []string
	// Combination of root .gitconfig and repository config as map
	GitConfig map[string]string
}

func NewOptions() *Options {
	return &Options{
		StringOpts:               make(map[string]string),
		BoolOpts:                 NewStringSet(),
		Args:                     make([]string, 0, 5),
		GitConfig:                make(map[string]string),
		RecentRefsPeriodDays:     90,
		RecentCommitsPeriodHEAD:  30,
		RecentCommitsPeriodOther: 0,
		FetchIncludePaths:        []string{},
		FetchExcludePaths:        []string{},
	}
}

// Load config from gitconfig and populate opts
func LoadConfig(opts *Options) {
	configmap := ReadConfig()
	parseConfig(configmap, opts)
}

// Parse a loaded config map and populate opts
func parseConfig(configmap map[string]string, opts *Options) {
	opts.GitConfig = configmap

	// Translate our settings to config
	if strings.ToLower(configmap["git-lob.verbose"]) == "true" {
		opts.Verbose = true
	}
	if strings.ToLower(configmap["git-lob.quiet"]) == "true" {
		opts.Quiet = true
	}
	if strings.ToLower(configmap["git-lob.logenabled"]) == "true" {
		opts.LogEnabled = true
	}
	logfile := configmap["git-lob.logfile"]
	if logfile != "" {
		opts.LogFile = logfile
	}
	if strings.ToLower(configmap["git-lob.logverbose"]) == "true" {
		opts.VerboseLog = true
	}
	if sharedStore := configmap["git-lob.sharedstore"]; sharedStore != "" {
		sharedStore = filepath.Clean(sharedStore)
		exists, isDir := FileOrDirExists(sharedStore)
		if exists && !isDir {
			LogErrorf("Invalid path for git-lob.sharedstore: %v\n", sharedStore)
		} else {
			if !exists {
				err := os.MkdirAll(sharedStore, 0755)
				if err != nil {
					LogErrorf("Unable to create path for git-lob.sharedstore: %v\n", sharedStore)
				} else {
					exists = true
					isDir = true
				}
			}

			if exists && isDir {
				opts.SharedStore = sharedStore
			}
		}
	}
	if strings.ToLower(configmap["git-lob.autofetch"]) == "true" {
		opts.AutoFetchEnabled = true
	}

	//git-lob.recent-refs          default: 90 days
	//git-lob.recent-commits-head  default: 30 days
	//git-lob.recent-commits-other default: 0 days
	if recentrefs := configmap["git-lob.recent-refs"]; recentrefs != "" {
		n, err := strconv.ParseInt(recentrefs, 10, 0)
		if err != nil {
			opts.RecentRefsPeriodDays = int(n)
		}
	}
	if recent := configmap["git-lob.recent-commits-head"]; recent != "" {
		n, err := strconv.ParseInt(recent, 10, 0)
		if err != nil {
			opts.RecentCommitsPeriodHEAD = int(n)
		}
	}
	if recent := configmap["git-lob.recent-commits-other"]; recent != "" {
		n, err := strconv.ParseInt(recent, 10, 0)
		if err != nil {
			opts.RecentCommitsPeriodOther = int(n)
		}
	}
	if fetchincludes := configmap["git-lob.fetch-include"]; fetchincludes != "" {
		// Split on comma
		for _, inc := range strings.Split(fetchincludes, ",") {
			inc = strings.TrimSpace(inc)
			opts.FetchIncludePaths = append(opts.FetchIncludePaths, inc)
		}
	}
	if fetchexcludes := configmap["git-lob.fetch-exclude"]; fetchexcludes != "" {
		// Split on comma
		for _, ex := range strings.Split(fetchexcludes, ",") {
			ex = strings.TrimSpace(ex)
			opts.FetchExcludePaths = append(opts.FetchExcludePaths, ex)
		}
	}

}

// Read .gitconfig / .git/config for specific options to override
// Returns a map of setting=value, where group levels are indicated by dot-notation
// e.g. git-lob.logfile=blah
// all keys are converted to lower case for easier matching
func ReadConfig() map[string]string {
	// Don't call out to 'git config' to read file, that's slower and forces a dependency on git
	// which we may not want to have (e.g. support for libgit clients)
	// Read files directly, it's a simple format anyway

	// TODO system git config?

	var ret map[string]string = nil

	// User config
	usr, err := user.Current()
	if err != nil {
		LogError("Unable to access user home directory")
		// continue anyway
	} else {
		userConfigFile := path.Join(usr.HomeDir, ".gitconfig")
		userConfig, err := ReadConfigFile(userConfigFile)
		if err == nil {
			if ret == nil {
				ret = userConfig
			} else {
				for key, val := range userConfig {
					ret[key] = val
				}
			}
		}
	}

	// repo config
	gitDir := GetGitDir()
	repoConfigFile := path.Join(gitDir, "config")
	repoConfig, err := ReadConfigFile(repoConfigFile)
	if err == nil {
		if ret == nil {
			ret = repoConfig
		} else {
			for key, val := range repoConfig {
				ret[key] = val
			}
		}
	}

	if ret == nil {
		ret = make(map[string]string)
	}

	return ret

}

// Read a specific .gitconfig-formatted config file
// Returns a map of setting=value, where group levels are indicated by dot-notation
// e.g. git-lob.logfile=blah
// all keys are converted to lower case for easier matching
func ReadConfigFile(filepath string) (map[string]string, error) {
	f, err := os.OpenFile(filepath, os.O_RDONLY, 0644)
	if err != nil {
		return make(map[string]string), err
	}
	defer f.Close()

	// Need the directory for relative path includes
	dir := path.Dir(filepath)
	return ReadConfigStream(f, dir)

}
func ReadConfigStream(in io.Reader, dir string) (map[string]string, error) {
	ret := make(map[string]string, 10)
	sectionRegex := regexp.MustCompile(`^\[(.*)\]$`)                    // simple section regex ie [section]
	namedSectionRegex := regexp.MustCompile(`^\[(.*)\s+\"(.*)\"\s*\]$`) // named section regex ie [section "name"]

	scanner := bufio.NewScanner(in)
	var currentSection string
	var currentSectionName string
	for scanner.Scan() {
		// Reads lines by default, \n is already stripped
		line := strings.TrimSpace(scanner.Text())
		// Detect comments - discard any of the line after the comment but keep anything before
		commentPos := strings.IndexAny(line, "#;")
		if commentPos != -1 {
			// skip comments
			if commentPos == 0 {
				continue
			} else {
				// just strip rest of line after the comment
				line = strings.TrimSpace(line[0:commentPos])
				if len(line) == 0 {
					continue
				}
			}
		}

		// Check for sections
		if secmatch := sectionRegex.FindStringSubmatch(line); secmatch != nil {
			// named section? [section "name"]
			if namedsecmatch := namedSectionRegex.FindStringSubmatch(line); namedsecmatch != nil {
				// Named section
				currentSection = namedsecmatch[1]
				currentSectionName = namedsecmatch[2]

			} else {
				// Normal section
				currentSection = secmatch[1]
				currentSectionName = ""
			}
			continue
		}

		// Otherwise, probably a standard setting
		equalPos := strings.Index(line, "=")
		if equalPos != -1 {
			name := strings.TrimSpace(line[0:equalPos])
			value := strings.TrimSpace(line[equalPos+1:])
			if currentSection != "" {
				if currentSectionName != "" {
					name = fmt.Sprintf("%v.%v.%v", currentSection, currentSectionName, name)
				} else {
					name = fmt.Sprintf("%v.%v", currentSection, name)
				}
			}
			// convert key to lower case for easier matching
			name = strings.ToLower(name)

			// Check for includes and expand immediately
			if name == "include.path" {
				// if this is a relative, prepend containing dir context
				includeFile := value
				if !path.IsAbs(includeFile) {
					includeFile = path.Join(dir, includeFile)
				}
				includemap, err := ReadConfigFile(includeFile)
				if err == nil {
					for key, value := range includemap {
						ret[key] = value
					}
				}
			} else {
				ret[name] = value
			}
		}

	}
	if scanner.Err() != nil {
		// Problem (other than io.EOF)
		// return content we read up to here anyway
		return ret, scanner.Err()
	}

	return ret, nil

}
<commit_msg>Fix parsing of config options controlling 'recent' commits<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// Chunk size that we split stored data into so it's easier to resume uploads/downloads
// This used to be configurable, but it caused too many issues if different people had different
// settings in a shared repository
// This is only 'var' rather than 'const' to allow tests to modify
var ChunkSize = int64(32 * 1024 * 1024)

// Options (command line or config file)
// Only general options, command-specific ones dealt with in commands
type Options struct {
	// Help option was requested
	HelpRequested bool
	// Output verbosely (to console & log)
	Verbose bool
	// Output quietly (to console)
	Quiet bool
	// Don't actually perform any tasks
	DryRun bool
	// Never prompt for user input, rely on command line options only
	NonInteractive bool
	// The command to run
	Command string
	// Other value options not converted
	StringOpts map[string]string
	// Other boolean options not converted
	BoolOpts StringSet
	// Other arguments to the command
	Args []string
	// Whether to write output to a log
	LogEnabled bool
	// Log file (optional, defaults to ~/git-lob.log if not specified)
	LogFile string
	// Log verbosely even if main Verbose option is disabled for console
	VerboseLog bool
	// Shared folder in which to store binary files for all repos
	SharedStore string
	// Auto fetch (download) on checkout?
	AutoFetchEnabled bool
	// 'Recent' window in days for all refs (branches/tags) compared to current date
	RecentRefsPeriodDays int
	// 'Recent' window in days for commits on HEAD compared to latest commit date
	RecentCommitsPeriodHEAD int
	// 'Recent' window in days for commits on other branches/tags compared to latest commit date
	RecentCommitsPeriodOther int
	// List of paths to include when fetching
	FetchIncludePaths []string
	// List of paths to exclude when fetching
	FetchExcludePaths []string
	// Combination of root .gitconfig and repository config as map
	GitConfig map[string]string
}

func NewOptions() *Options {
	return &Options{
		StringOpts:               make(map[string]string),
		BoolOpts:                 NewStringSet(),
		Args:                     make([]string, 0, 5),
		GitConfig:                make(map[string]string),
		RecentRefsPeriodDays:     90,
		RecentCommitsPeriodHEAD:  30,
		RecentCommitsPeriodOther: 0,
		FetchIncludePaths:        []string{},
		FetchExcludePaths:        []string{},
	}
}

// Load config from gitconfig and populate opts
func LoadConfig(opts *Options) {
	configmap := ReadConfig()
	parseConfig(configmap, opts)
}

// Parse a loaded config map and populate opts
func parseConfig(configmap map[string]string, opts *Options) {
	opts.GitConfig = configmap

	// Translate our settings to config
	if strings.ToLower(configmap["git-lob.verbose"]) == "true" {
		opts.Verbose = true
	}
	if strings.ToLower(configmap["git-lob.quiet"]) == "true" {
		opts.Quiet = true
	}
	if strings.ToLower(configmap["git-lob.logenabled"]) == "true" {
		opts.LogEnabled = true
	}
	logfile := configmap["git-lob.logfile"]
	if logfile != "" {
		opts.LogFile = logfile
	}
	if strings.ToLower(configmap["git-lob.logverbose"]) == "true" {
		opts.VerboseLog = true
	}
	if sharedStore := configmap["git-lob.sharedstore"]; sharedStore != "" {
		sharedStore = filepath.Clean(sharedStore)
		exists, isDir := FileOrDirExists(sharedStore)
		if exists && !isDir {
			LogErrorf("Invalid path for git-lob.sharedstore: %v\n", sharedStore)
		} else {
			if !exists {
				err := os.MkdirAll(sharedStore, 0755)
				if err != nil {
					LogErrorf("Unable to create path for git-lob.sharedstore: %v\n", sharedStore)
				} else {
					exists = true
					isDir = true
				}
			}

			if exists && isDir {
				opts.SharedStore = sharedStore
			}
		}
	}
	if strings.ToLower(configmap["git-lob.autofetch"]) == "true" {
		opts.AutoFetchEnabled = true
	}

	//git-lob.recent-refs          default: 90 days
	//git-lob.recent-commits-head  default: 30 days
	//git-lob.recent-commits-other default: 0 days
	if recentrefs := configmap["git-lob.recent-refs"]; recentrefs != "" {
		n, err := strconv.ParseInt(recentrefs, 10, 0)
		if err == nil {
			opts.RecentRefsPeriodDays = int(n)
		}
	}
	if recent := configmap["git-lob.recent-commits-head"]; recent != "" {
		n, err := strconv.ParseInt(recent, 10, 0)
		if err == nil {
			opts.RecentCommitsPeriodHEAD = int(n)
		}
	}
	if recent := configmap["git-lob.recent-commits-other"]; recent != "" {
		n, err := strconv.ParseInt(recent, 10, 0)
		if err == nil {
			opts.RecentCommitsPeriodOther = int(n)
		}
	}
	if fetchincludes := configmap["git-lob.fetch-include"]; fetchincludes != "" {
		// Split on comma
		for _, inc := range strings.Split(fetchincludes, ",") {
			inc = strings.TrimSpace(inc)
			opts.FetchIncludePaths = append(opts.FetchIncludePaths, inc)
		}
	}
	if fetchexcludes := configmap["git-lob.fetch-exclude"]; fetchexcludes != "" {
		// Split on comma
		for _, ex := range strings.Split(fetchexcludes, ",") {
			ex = strings.TrimSpace(ex)
			opts.FetchExcludePaths = append(opts.FetchExcludePaths, ex)
		}
	}

}

// Read .gitconfig / .git/config for specific options to override
// Returns a map of setting=value, where group levels are indicated by dot-notation
// e.g. git-lob.logfile=blah
// all keys are converted to lower case for easier matching
func ReadConfig() map[string]string {
	// Don't call out to 'git config' to read file, that's slower and forces a dependency on git
	// which we may not want to have (e.g. support for libgit clients)
	// Read files directly, it's a simple format anyway

	// TODO system git config?

	var ret map[string]string = nil

	// User config
	usr, err := user.Current()
	if err != nil {
		LogError("Unable to access user home directory")
		// continue anyway
	} else {
		userConfigFile := path.Join(usr.HomeDir, ".gitconfig")
		userConfig, err := ReadConfigFile(userConfigFile)
		if err == nil {
			if ret == nil {
				ret = userConfig
			} else {
				for key, val := range userConfig {
					ret[key] = val
				}
			}
		}
	}

	// repo config
	gitDir := GetGitDir()
	repoConfigFile := path.Join(gitDir, "config")
	repoConfig, err := ReadConfigFile(repoConfigFile)
	if err == nil {
		if ret == nil {
			ret = repoConfig
		} else {
			for key, val := range repoConfig {
				ret[key] = val
			}
		}
	}

	if ret == nil {
		ret = make(map[string]string)
	}

	return ret

}

// Read a specific .gitconfig-formatted config file
// Returns a map of setting=value, where group levels are indicated by dot-notation
// e.g. git-lob.logfile=blah
// all keys are converted to lower case for easier matching
func ReadConfigFile(filepath string) (map[string]string, error) {
	f, err := os.OpenFile(filepath, os.O_RDONLY, 0644)
	if err != nil {
		return make(map[string]string), err
	}
	defer f.Close()

	// Need the directory for relative path includes
	dir := path.Dir(filepath)
	return ReadConfigStream(f, dir)

}
func ReadConfigStream(in io.Reader, dir string) (map[string]string, error) {
	ret := make(map[string]string, 10)
	sectionRegex := regexp.MustCompile(`^\[(.*)\]$`)                    // simple section regex ie [section]
	namedSectionRegex := regexp.MustCompile(`^\[(.*)\s+\"(.*)\"\s*\]$`) // named section regex ie [section "name"]

	scanner := bufio.NewScanner(in)
	var currentSection string
	var currentSectionName string
	for scanner.Scan() {
		// Reads lines by default, \n is already stripped
		line := strings.TrimSpace(scanner.Text())
		// Detect comments - discard any of the line after the comment but keep anything before
		commentPos := strings.IndexAny(line, "#;")
		if commentPos != -1 {
			// skip comments
			if commentPos == 0 {
				continue
			} else {
				// just strip rest of line after the comment
				line = strings.TrimSpace(line[0:commentPos])
				if len(line) == 0 {
					continue
				}
			}
		}

		// Check for sections
		if secmatch := sectionRegex.FindStringSubmatch(line); secmatch != nil {
			// named section? [section "name"]
			if namedsecmatch := namedSectionRegex.FindStringSubmatch(line); namedsecmatch != nil {
				// Named section
				currentSection = namedsecmatch[1]
				currentSectionName = namedsecmatch[2]

			} else {
				// Normal section
				currentSection = secmatch[1]
				currentSectionName = ""
			}
			continue
		}

		// Otherwise, probably a standard setting
		equalPos := strings.Index(line, "=")
		if equalPos != -1 {
			name := strings.TrimSpace(line[0:equalPos])
			value := strings.TrimSpace(line[equalPos+1:])
			if currentSection != "" {
				if currentSectionName != "" {
					name = fmt.Sprintf("%v.%v.%v", currentSection, currentSectionName, name)
				} else {
					name = fmt.Sprintf("%v.%v", currentSection, name)
				}
			}
			// convert key to lower case for easier matching
			name = strings.ToLower(name)

			// Check for includes and expand immediately
			if name == "include.path" {
				// if this is a relative, prepend containing dir context
				includeFile := value
				if !path.IsAbs(includeFile) {
					includeFile = path.Join(dir, includeFile)
				}
				includemap, err := ReadConfigFile(includeFile)
				if err == nil {
					for key, value := range includemap {
						ret[key] = value
					}
				}
			} else {
				ret[name] = value
			}
		}

	}
	if scanner.Err() != nil {
		// Problem (other than io.EOF)
		// return content we read up to here anyway
		return ret, scanner.Err()
	}

	return ret, nil

}
<|endoftext|>