<commit_before>// Package i3bar provides an Go library for i3bar protocol support.
package i3bar

import (
    "encoding/json"
    "fmt"
    "io"
)

// Header represents the header of an i3bar message.
type Header struct {
    Version int         `json:"version"`
    Stop_signal int     `json:"stop_signal,omitempty"`
    Cont_signal int     `json:"cont_signal,omitempty"`
    Click_events bool   `json:"click_events,omitempty"`
}

// String pretty prints Header objects.
func (d *Header) String() string {
    str, err := json.MarshalIndent(d, "", "    ")
    if err != nil {
        return err.Error()
    }
    return string(str)
}

// Block represents a single block of an i3bar message.
type Block struct {
    Full_text string            `json:"full_text"`
    Short_text string           `json:"short_text,omitempty"`
    Color string                `json:"color,omitempty"`
    Min_width int               `json:"min_width,omitempty"`
    Align string                `json:"align,omitempty"`
    Name string                 `json:"name,omitempty"`
    Instance string             `json:"instance,omitempty"`
    Urgent bool                 `json:"urgent,omitempty"`
    Separator bool              `json:"separator"`
    Separator_block_width int   `json:"separator_block_width,omitempty"`
    Markup string               `json:"markup,omitempty"`
}

// String pretty prints Block objects.
func (d *Block) String() string {
    str, err := json.MarshalIndent(d, "", "    ")
    if err != nil {
        return err.Error()
    }
    return string(str)
}

// StatusLine represents a full i3bar status line.
type StatusLine []Block

// String pretty prints StatusLine objects.
func (d *StatusLine) String() string {
    str, err := json.MarshalIndent(d, "", "    ")
    if err != nil {
        return err.Error()
    }
    return string(str)
}

// Click represents an i3bar click event.
type Click struct {
    Name string     `json:"name"`
    Instance string `json:"instance"`
    X int           `json:"x"`
    Y int           `json:"y"`
    Button int      `json:"button"`
}

// String pretty prints Click objects.
func (d *Click) String() string {
    str, err := json.MarshalIndent(d, "", "    ")
    if err != nil {
        return err.Error()
    }
    return string(str)
}

func Init(h *Header, w io.Writer, r io.Reader, q chan bool) (chan StatusLine, error) {
    if (w == nil) {
        return nil, fmt.Errorf("error: Writer required")
    }
    var jsonWriter = json.NewEncoder(w)
    // TODO: implement read loop
    //var jsonReader *json.Decoder
    if (r != nil) {
        //jsonReader = json.NewDecoder(r)
    }

    msg, err := json.Marshal(h)
    if err != nil {
        return nil, fmt.Errorf("error: couldn't parse Header")
    }
    _, err = fmt.Fprintln(w, string(msg))
    if err != nil {
        return nil, fmt.Errorf("error: couldn't write to Writer")
    }

    statusChan := make(chan StatusLine)
    go writeLoop(jsonWriter, statusChan, q)
    return statusChan, nil
}

func writeLoop(e *json.Encoder, c chan StatusLine, q chan bool) {
    for block := range c {
        // TODO: proper error handling
        err := e.Encode(block)
        if err != nil {
            fmt.Printf("%s\n", err)
        }
    }

    close(q)
}
<commit_msg>added ability to stream infinite json array (required by i3bar protocol)<commit_after>// Package i3bar provides an Go library for i3bar protocol support.
package i3bar

import (
    "encoding/json"
    "fmt"
    "io"
)

// Header represents the header of an i3bar message.
type Header struct {
    Version int         `json:"version"`
    Stop_signal int     `json:"stop_signal,omitempty"`
    Cont_signal int     `json:"cont_signal,omitempty"`
    Click_events bool   `json:"click_events,omitempty"`
}

// String pretty prints Header objects.
func (d *Header) String() string {
    str, err := json.MarshalIndent(d, "", "    ")
    if err != nil {
        return err.Error()
    }
    return string(str)
}

// Block represents a single block of an i3bar message.
type Block struct {
    Full_text string            `json:"full_text"`
    Short_text string           `json:"short_text,omitempty"`
    Color string                `json:"color,omitempty"`
    Min_width int               `json:"min_width,omitempty"`
    Align string                `json:"align,omitempty"`
    Name string                 `json:"name,omitempty"`
    Instance string             `json:"instance,omitempty"`
    Urgent bool                 `json:"urgent,omitempty"`
    Separator bool              `json:"separator"`
    Separator_block_width int   `json:"separator_block_width,omitempty"`
    Markup string               `json:"markup,omitempty"`
}

// String pretty prints Block objects.
func (d *Block) String() string {
    str, err := json.MarshalIndent(d, "", "    ")
    if err != nil {
        return err.Error()
    }
    return string(str)
}

// StatusLine represents a full i3bar status line.
type StatusLine []Block

// String pretty prints StatusLine objects.
func (d *StatusLine) String() string {
    str, err := json.MarshalIndent(d, "", "    ")
    if err != nil {
        return err.Error()
    }
    return string(str)
}

// Click represents an i3bar click event.
type Click struct {
    Name string     `json:"name"`
    Instance string `json:"instance"`
    X int           `json:"x"`
    Y int           `json:"y"`
    Button int      `json:"button"`
}

// jsonArrayEncoder is an object that streams an infinite JSON array.
type jsonArrayEncoder struct {
    count int
    w io.Writer
    e json.Encoder
}

// Encode streams an infinite JSON array.
// Each call adds another element to the array.
func (e *jsonArrayEncoder) Encode(v interface{}) error {
    linePrefix := ","
    if (e.count == 0) {
        linePrefix = "["
        e.count++
    }
    _, err := e.w.Write([]byte(linePrefix))
    if err != nil {
        return err
    }

    err = e.e.Encode(v)
    if err != nil {
        return err
    }

    return nil
}

// newJsonArrayEncoder returns a new jsonArrayEncoder that wraps w.
func newJsonArrayEncoder(w io.Writer) *jsonArrayEncoder {
    return &jsonArrayEncoder{0, w, *json.NewEncoder(w)}
}

// String pretty prints Click objects.
func (d *Click) String() string {
    str, err := json.MarshalIndent(d, "", "    ")
    if err != nil {
        return err.Error()
    }
    return string(str)
}

// Init initializes the i3bar io.
// w is the io.Writer to write to (usually os.Stdout).
// r is the io.Reader to from (usually os.Stdin). TODO: implement
// q is a channel that will be closed when the write loop is finished.
// The returned channel can be used to write status lines to w.
func Init(h *Header, w io.Writer, r io.Reader, q chan bool) (chan StatusLine, error) {
    if (w == nil) {
        return nil, fmt.Errorf("error: Writer required")
    }
    var jsonWriter = newJsonArrayEncoder(w)
    // TODO: implement read loop
    //var jsonReader *json.Decoder
    if (r != nil) {
        //jsonReader = json.NewDecoder(r)
    }

    msg, err := json.Marshal(h)
    if err != nil {
        return nil, fmt.Errorf("error: couldn't parse Header")
    }
    _, err = fmt.Fprintln(w, string(msg))
    if err != nil {
        return nil, fmt.Errorf("error: couldn't write to Writer")
    }

    statusChan := make(chan StatusLine)
    go writeLoop(jsonWriter, statusChan, q)
    return statusChan, nil
}

// writeLoop continuosly writes status lines sent over c to e.
// q is closed when there are no more values to read from c.
func writeLoop(e *jsonArrayEncoder, c chan StatusLine, q chan bool) {
    for block := range c {
        // TODO: proper error handling
        err := e.Encode(block)
        if err != nil {
            fmt.Printf("%s\n", err)
        }
    }

    close(q)
}
<|endoftext|>


<commit_before>//
// go-sonos
// ========
//
// Copyright (c) 2012, Ian T. Richards <ianr@panix.com>
// 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.
//
// 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
// HOLDER 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.
//

package upnp

import (
	"encoding/xml"
	_ "log"
)

var (
	AlarmClock_EventType = registerEventType("AlarmClock")
)

type AlarmClockState struct {
	TimeZone              string
	TimeServer            string
	TimeGeneration        uint32
	AlarmListVersion      string
	DailyIndexRefreshTime string
	TimeFormat            string
	DateFormat            string
}

type AlarmClockEvent struct {
	AlarmClockState
	Svc *Service
}

func (this AlarmClockEvent) Service() *Service {
	return this.Svc
}

func (this AlarmClockEvent) Type() int {
	return AlarmClock_EventType
}

type AlarmClock struct {
	AlarmClockState
	Svc *Service
}

func (this *AlarmClock) BeginSet(svc *Service, channel chan Event) {
}

func (this *AlarmClock) HandleProperty(svc *Service, value string, channel chan Event) error {
	xml.Unmarshal([]byte("<AlarmClockState>"+value+"</AlarmClockState>"), &this.AlarmClockState)
	return nil
}

func (this *AlarmClock) EndSet(svc *Service, channel chan Event) {
	evt := AlarmClockEvent{AlarmClockState: this.AlarmClockState, Svc: svc}
	channel <- evt
}

func (this *AlarmClock) SetFormat(desiredTimeFormat, desiredDateFormat string) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"DesiredTimeFormat", desiredTimeFormat},
		{"DesiredDateFormat", desiredDateFormat},
	}
	response := this.Svc.Call("SetFormat", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetFormat() (currentTimeFormat, currentDateFormat string, err error) {
	type Response struct {
		XMLName           xml.Name
		CurrentTimeFormat string
		CurrentDateFormat string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetFormat")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	currentTimeFormat = doc.CurrentTimeFormat
	currentDateFormat = doc.CurrentDateFormat
	err = doc.Error()
	return
}

func (this *AlarmClock) SetTimeZone(index int32, autoAdjustDst bool) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"Index", index},
		{"AutoAdjustDst", autoAdjustDst},
	}
	response := this.Svc.Call("SetTimeZone", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetTimeZone() (index int32, autoAdjustDst bool, err error) {
	type Response struct {
		XMLName       xml.Name
		Index         int32
		AutoAdjustDst bool
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetTimeZone")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	index = doc.Index
	autoAdjustDst = doc.AutoAdjustDst
	err = doc.Error()
	return
}

func (this *AlarmClock) GetTimeZoneAndRule() (index int32, autoAdjustDst bool, timeZone string, err error) {
	type Response struct {
		XMLName       xml.Name
		Index         int32
		AutoAdjustDst bool
		TimeZone      string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetTimeZoneAndRule")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	index = doc.Index
	autoAdjustDst = doc.AutoAdjustDst
	timeZone = doc.TimeZone
	err = doc.Error()
	return
}

func (this *AlarmClock) GetTimeZoneRule(index int32) (timeZone string, err error) {
	type Response struct {
		XMLName  xml.Name
		TimeZone string
		upnpErrorResponse
	}
	args := []upnpArg{
		{"Index", index},
	}
	response := this.Svc.Call("GetTimeZoneRule", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	timeZone = doc.TimeZone
	err = doc.Error()
	return
}

func (this *AlarmClock) SetTimeServer(desiredTimeServer string) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"DesiredTimeServer", desiredTimeServer},
	}
	response := this.Svc.Call("SetTimeServer", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetTimeServer() (currentTimeServer string, err error) {
	type Response struct {
		XMLName           xml.Name
		CurrentTimeServer string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetTimeServer")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	currentTimeServer = doc.CurrentTimeServer
	err = doc.Error()
	return
}

func (this *AlarmClock) SetTimeNow(desiredTime, timeZoneForDesiredTime string) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"DesiredTime", desiredTime},
		{"TimeZoneForDesiredTime", timeZoneForDesiredTime},
	}
	response := this.Svc.Call("SetTimeNow", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetHouseholdTimeAtStamp(timeStamp string) (householdUTCTime string, err error) {
	type Response struct {
		XMLName          xml.Name
		HouseholdUTCTime string
		upnpErrorResponse
	}
	args := []upnpArg{
		{"TimeStamp", timeStamp},
	}
	response := this.Svc.Call("GetHouseholdTimeAtStamp", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	householdUTCTime = doc.HouseholdUTCTime
	err = doc.Error()
	return
}

type GetTimeNowResponse struct {
	CurrentUTCTime        string
	CurrentLocalTime      string
	CurrentTimeZone       string
	CurrentTimeGeneration uint32
}

func (this *AlarmClock) GetTimeNow() (getTimeNowResponse *GetTimeNowResponse, err error) {
	type Response struct {
		XMLName xml.Name
		GetTimeNowResponse
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetTimeNow")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	getTimeNowResponse = &doc.GetTimeNowResponse
	err = doc.Error()
	return
}

const (
	Recurrence_ONCE     = "ONCE"
	Recurrence_WEEKDAYS = "WEEKDAYS"
	Recurrence_WEEKENDS = "WEEKENDS"
	Recurrence_DAILY    = "DAILY"
)

const (
	AlarmPlayMode_NORMAL           = "NORMAL"
	AlarmPlayMode_REPEAT_ALL       = "REPEAT_ALL"
	AlarmPlayMode_SHUFFLE_NOREPEAT = "SHUFFLE_NOREPEAT"
	AlarmPlayMode_SHUFFLE          = "SHUFFLE"
)

type CreateAlarmRequest struct {
	StartLocalTime     string
	Duration           string
	Recurrence         string
	Enabled            bool
	RoomUUID           string
	ProgramURI         string
	ProgramMetaData    string
	PlayMode           string
	Volume             uint16
	IncludeLinkedZones bool
}

func (this *AlarmClock) CreateAlarm(req *CreateAlarmRequest) (assignedId uint32, err error) {
	type Response struct {
		XMLName    xml.Name
		AssignedID uint32
		upnpErrorResponse
	}
	args := []upnpArg{
		{"StartLocalTime", req.StartLocalTime},
		{"Duration", req.Duration},
		{"Recurrence", req.Recurrence},
		{"Enabled", req.Enabled},
		{"RoomUUID", req.RoomUUID},
		{"ProgramURI", req.ProgramURI},
		{"ProgramMetaData", req.ProgramMetaData},
		{"PlayMode", req.PlayMode},
		{"Volume", req.Volume},
		{"IncludeLinkedZones", req.IncludeLinkedZones},
	}
	response := this.Svc.Call("CreateAlarm", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	assignedId = doc.AssignedID
	err = doc.Error()
	return
}

type UpdateAlarmRequest CreateAlarmRequest

func (this *AlarmClock) UpdateAlarm(id uint32, req *UpdateAlarmRequest) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"ID", id},
		{"StartLocalTime", req.StartLocalTime},
		{"Duration", req.Duration},
		{"Recurrence", req.Recurrence},
		{"Enabled", req.Enabled},
		{"RoomUUID", req.RoomUUID},
		{"ProgramURI", req.ProgramURI},
		{"ProgramMetaData", req.ProgramMetaData},
		{"PlayMode", req.PlayMode},
		{"Volume", req.Volume},
		{"IncludeLinkedZones", req.IncludeLinkedZones},
	}
	response := this.Svc.Call("UpdateAlarm", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) DestroyAlarm(id uint32) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"ID", id},
	}
	response := this.Svc.Call("DestroyAlarm", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) ListAlarms() (currentAlarmList, currentAlarmListVersion string, err error) {
	type Response struct {
		XMLName                 xml.Name
		CurrentAlarmList        string
		CurrentAlarmListVersion string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("ListAlarms")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	currentAlarmList = doc.CurrentAlarmList
	currentAlarmListVersion = doc.CurrentAlarmListVersion
	err = doc.Error()
	return
}

func (this *AlarmClock) SetDailyIndexRefreshTime(desiredDailyIndexRefreshTime string) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"DesiredDailyIndexRefreshTime", desiredDailyIndexRefreshTime},
	}
	response := this.Svc.Call("SetDailyIndexRefreshTime", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetDailyIndexRefreshTime() (currentDailyIndexRefreshTime string, err error) {
	type Response struct {
		XMLName                      xml.Name
		CurrentDailyIndexRefreshTime string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetDailyIndexRefreshTime")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	currentDailyIndexRefreshTime = doc.CurrentDailyIndexRefreshTime
	err = doc.Error()
	return
}
<commit_msg>Better method for unpacking AlarmClock events<commit_after>//
// go-sonos
// ========
//
// Copyright (c) 2012, Ian T. Richards <ianr@panix.com>
// 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.
//
// 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
// HOLDER 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.
//

package upnp

import (
	"encoding/xml"
	_ "log"
)

var (
	AlarmClock_EventType = registerEventType("AlarmClock")
)

type AlarmClockState struct {
	TimeZone              string
	TimeServer            string
	TimeGeneration        uint32
	AlarmListVersion      string
	DailyIndexRefreshTime string
	TimeFormat            string
	DateFormat            string
}

type AlarmClockEvent struct {
	AlarmClockState
	Svc *Service
}

func (this AlarmClockEvent) Service() *Service {
	return this.Svc
}

func (this AlarmClockEvent) Type() int {
	return AlarmClock_EventType
}

type AlarmClock struct {
	AlarmClockState
	Svc *Service
}

func (this *AlarmClock) BeginSet(svc *Service, channel chan Event) {
}

type alarmClockUpdate_XML struct {
	XMLName xml.Name `xml:"AlarmClockState"`
	Value   string   `xml:",innerxml"`
}

func (this *AlarmClock) HandleProperty(svc *Service, value string, channel chan Event) error {
	update := alarmClockUpdate_XML{
		Value: value,
	}
	if bytes, err := xml.Marshal(update); nil != err {
		return err
	} else {
		xml.Unmarshal(bytes, &this.AlarmClockState)
	}
	return nil
}

func (this *AlarmClock) EndSet(svc *Service, channel chan Event) {
	evt := AlarmClockEvent{AlarmClockState: this.AlarmClockState, Svc: svc}
	channel <- evt
}

func (this *AlarmClock) SetFormat(desiredTimeFormat, desiredDateFormat string) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"DesiredTimeFormat", desiredTimeFormat},
		{"DesiredDateFormat", desiredDateFormat},
	}
	response := this.Svc.Call("SetFormat", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetFormat() (currentTimeFormat, currentDateFormat string, err error) {
	type Response struct {
		XMLName           xml.Name
		CurrentTimeFormat string
		CurrentDateFormat string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetFormat")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	currentTimeFormat = doc.CurrentTimeFormat
	currentDateFormat = doc.CurrentDateFormat
	err = doc.Error()
	return
}

func (this *AlarmClock) SetTimeZone(index int32, autoAdjustDst bool) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"Index", index},
		{"AutoAdjustDst", autoAdjustDst},
	}
	response := this.Svc.Call("SetTimeZone", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetTimeZone() (index int32, autoAdjustDst bool, err error) {
	type Response struct {
		XMLName       xml.Name
		Index         int32
		AutoAdjustDst bool
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetTimeZone")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	index = doc.Index
	autoAdjustDst = doc.AutoAdjustDst
	err = doc.Error()
	return
}

func (this *AlarmClock) GetTimeZoneAndRule() (index int32, autoAdjustDst bool, timeZone string, err error) {
	type Response struct {
		XMLName       xml.Name
		Index         int32
		AutoAdjustDst bool
		TimeZone      string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetTimeZoneAndRule")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	index = doc.Index
	autoAdjustDst = doc.AutoAdjustDst
	timeZone = doc.TimeZone
	err = doc.Error()
	return
}

func (this *AlarmClock) GetTimeZoneRule(index int32) (timeZone string, err error) {
	type Response struct {
		XMLName  xml.Name
		TimeZone string
		upnpErrorResponse
	}
	args := []upnpArg{
		{"Index", index},
	}
	response := this.Svc.Call("GetTimeZoneRule", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	timeZone = doc.TimeZone
	err = doc.Error()
	return
}

func (this *AlarmClock) SetTimeServer(desiredTimeServer string) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"DesiredTimeServer", desiredTimeServer},
	}
	response := this.Svc.Call("SetTimeServer", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetTimeServer() (currentTimeServer string, err error) {
	type Response struct {
		XMLName           xml.Name
		CurrentTimeServer string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetTimeServer")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	currentTimeServer = doc.CurrentTimeServer
	err = doc.Error()
	return
}

func (this *AlarmClock) SetTimeNow(desiredTime, timeZoneForDesiredTime string) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"DesiredTime", desiredTime},
		{"TimeZoneForDesiredTime", timeZoneForDesiredTime},
	}
	response := this.Svc.Call("SetTimeNow", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetHouseholdTimeAtStamp(timeStamp string) (householdUTCTime string, err error) {
	type Response struct {
		XMLName          xml.Name
		HouseholdUTCTime string
		upnpErrorResponse
	}
	args := []upnpArg{
		{"TimeStamp", timeStamp},
	}
	response := this.Svc.Call("GetHouseholdTimeAtStamp", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	householdUTCTime = doc.HouseholdUTCTime
	err = doc.Error()
	return
}

type GetTimeNowResponse struct {
	CurrentUTCTime        string
	CurrentLocalTime      string
	CurrentTimeZone       string
	CurrentTimeGeneration uint32
}

func (this *AlarmClock) GetTimeNow() (getTimeNowResponse *GetTimeNowResponse, err error) {
	type Response struct {
		XMLName xml.Name
		GetTimeNowResponse
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetTimeNow")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	getTimeNowResponse = &doc.GetTimeNowResponse
	err = doc.Error()
	return
}

const (
	Recurrence_ONCE     = "ONCE"
	Recurrence_WEEKDAYS = "WEEKDAYS"
	Recurrence_WEEKENDS = "WEEKENDS"
	Recurrence_DAILY    = "DAILY"
)

const (
	AlarmPlayMode_NORMAL           = "NORMAL"
	AlarmPlayMode_REPEAT_ALL       = "REPEAT_ALL"
	AlarmPlayMode_SHUFFLE_NOREPEAT = "SHUFFLE_NOREPEAT"
	AlarmPlayMode_SHUFFLE          = "SHUFFLE"
)

type CreateAlarmRequest struct {
	StartLocalTime     string
	Duration           string
	Recurrence         string
	Enabled            bool
	RoomUUID           string
	ProgramURI         string
	ProgramMetaData    string
	PlayMode           string
	Volume             uint16
	IncludeLinkedZones bool
}

func (this *AlarmClock) CreateAlarm(req *CreateAlarmRequest) (assignedId uint32, err error) {
	type Response struct {
		XMLName    xml.Name
		AssignedID uint32
		upnpErrorResponse
	}
	args := []upnpArg{
		{"StartLocalTime", req.StartLocalTime},
		{"Duration", req.Duration},
		{"Recurrence", req.Recurrence},
		{"Enabled", req.Enabled},
		{"RoomUUID", req.RoomUUID},
		{"ProgramURI", req.ProgramURI},
		{"ProgramMetaData", req.ProgramMetaData},
		{"PlayMode", req.PlayMode},
		{"Volume", req.Volume},
		{"IncludeLinkedZones", req.IncludeLinkedZones},
	}
	response := this.Svc.Call("CreateAlarm", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	assignedId = doc.AssignedID
	err = doc.Error()
	return
}

type UpdateAlarmRequest CreateAlarmRequest

func (this *AlarmClock) UpdateAlarm(id uint32, req *UpdateAlarmRequest) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"ID", id},
		{"StartLocalTime", req.StartLocalTime},
		{"Duration", req.Duration},
		{"Recurrence", req.Recurrence},
		{"Enabled", req.Enabled},
		{"RoomUUID", req.RoomUUID},
		{"ProgramURI", req.ProgramURI},
		{"ProgramMetaData", req.ProgramMetaData},
		{"PlayMode", req.PlayMode},
		{"Volume", req.Volume},
		{"IncludeLinkedZones", req.IncludeLinkedZones},
	}
	response := this.Svc.Call("UpdateAlarm", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) DestroyAlarm(id uint32) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"ID", id},
	}
	response := this.Svc.Call("DestroyAlarm", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) ListAlarms() (currentAlarmList, currentAlarmListVersion string, err error) {
	type Response struct {
		XMLName                 xml.Name
		CurrentAlarmList        string
		CurrentAlarmListVersion string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("ListAlarms")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	currentAlarmList = doc.CurrentAlarmList
	currentAlarmListVersion = doc.CurrentAlarmListVersion
	err = doc.Error()
	return
}

func (this *AlarmClock) SetDailyIndexRefreshTime(desiredDailyIndexRefreshTime string) (err error) {
	type Response struct {
		XMLName xml.Name
		upnpErrorResponse
	}
	args := []upnpArg{
		{"DesiredDailyIndexRefreshTime", desiredDailyIndexRefreshTime},
	}
	response := this.Svc.Call("SetDailyIndexRefreshTime", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *AlarmClock) GetDailyIndexRefreshTime() (currentDailyIndexRefreshTime string, err error) {
	type Response struct {
		XMLName                      xml.Name
		CurrentDailyIndexRefreshTime string
		upnpErrorResponse
	}
	response := this.Svc.CallVa("GetDailyIndexRefreshTime")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	currentDailyIndexRefreshTime = doc.CurrentDailyIndexRefreshTime
	err = doc.Error()
	return
}
<|endoftext|>


<commit_before>package gofigure

import (
	"fmt"
	"github.com/droundy/goopt"
	"log"
	"os"
)

// This function is a convenience function. It displays the default values for us but handles them
// in the way we prefer it.
func GooptFigureString(names []string, def string, help string) *string {
	s := new(string)
	*s = ""
	f := func (ss string) error {
		*s = ss
		return nil
	}
	goopt.ReqArg(names, def, help, f)
	return s
}

// Config contains the configuration options that may be set by
// command line flags and environment variables.
type Config struct {
	Description			string
	DescribeEnvironment	bool	// if true, the environment variable is automatically added to the flag description
	DisableCommandLine	bool
	EnvPrefix			string
	EnvOverridesFile	bool
	FileParser			File
	RequireFile			bool    // application will panic if RequireFile == true, FileParser != nil and file doesn't exist
	Version				string
	options				map[string]*Option
	bools				map[string]*bool
	flags				map[string]*string
	values				map[string]*string
}

// Returns a new Config instance.
func New() *Config {
	return &Config{
		DescribeEnvironment:	false,
		DisableCommandLine:		false,
		EnvOverridesFile:		false,
		RequireFile:			true,
		options:				make(map[string]*Option),
		bools:					make(map[string]*bool)
		flags:					make(map[string]*string),
		values:					make(map[string]*string),
	}
}

// Adds a configuration option, returns an Option instance for
// easily setting the corresponding environment variable, default
// value, and description.
func (c *Config) Add(name string) *Option {
	c.options[name] = &Option{
		name:    name,
		envVar:  "",
		def:     "",
		desc:    "",
		longOpt: name,
	}
	return c.options[name]
}

func (c *Config) AddBoolean(name string) *Option {
	c.options[name] = &Option{
		name:    name,
		envVar:  "",
		def:     "",
		desc:    "",
		longOpt: name,
		boolean: true,
	}
	return c.options[name]
}

// Returns a configuration option by flag name.
func (c Config) Get(name string) *string {
	return c.values[name]
}

// Parses the configuration options into defined flags, sets the value
// accordingly. Options are read first from command line flags, then
// from environment variables, and falls back to the default value if
// neither are set.
//
// See https://github.com/rakyll/globalconf/blob/master/globalconf.go
func (c *Config) Parse() {
	goopt.Description = func() string {
		return c.Description
	}

	goopt.Version = c.Version

	// Sets the flags from the configuration options.
	for name, o := range c.options {
		cmdline := []string{}
		if o.shortOpt != "" {
			cmdline = append(cmdline, "-" + o.shortOpt)
		}
		cmdline = append(cmdline, "--"+o.longOpt)
		desc := o.desc
		if c.DescribeEnvironment && o.envVar != "" {
			desc += fmt.Sprintf(" Environment variable: %s%s.", c.EnvPrefix, o.envVar)
		}
		c.flags[name] = GooptFigureString(cmdline, o.def, desc)
		defcopy := o.def
		c.values[name] = &defcopy
	}

	passed := make(map[string]bool)

	if !c.DisableCommandLine {
		goopt.Parse(nil)

		// Gather the options passed through command line.
		for name, f := range c.flags {
			if *f != "" {
				passed[name] = true
				*c.values[name] = *f
			}
		}
	}

	if (c.EnvOverridesFile) {
		c.ParseEnv(passed)
		err := c.ParseFile(passed)
		if err != nil && c.RequireFile {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
	} else {
		err := c.ParseFile(passed)
		if err != nil && c.RequireFile {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
		c.ParseEnv(passed)
	}
}

func (c *Config) ParseEnv(passed map[string]bool) {
	for name, f := range c.options {

		// Skip flags passed through the command line as the option is
		// already set and takes precedence over environment variables.
		if passed[name] {
			continue
		}

		// Some options shouldn't be set via environment variables.
		if f.envVar == "" {
			continue
		}

		// If the configuration option was not passed via the command line,
		// check the corresponding environment variable.
		envVar := c.EnvPrefix + f.envVar
		if val := os.Getenv(envVar); val != "" {
			*c.values[name] = val
			passed[name] = true
		}
	}
}

func (c Config) parseFileToMap(handler File) (ValueMap, error) {
	root, err := handler.Parse()
	if err != nil {
		return nil, err
	}
	ret := ValueMap{}
	for _, opt := range c.options {
		if val, ok := root.FindOption(opt); ok {
			ret.Set(opt.GetName(), val)
		}
	}
	return ret, nil
}

func (c *Config) ParseFile(passed map[string]bool) error {
	if c.FileParser == nil {
		return nil
	}

	values, err := c.parseFileToMap(c.FileParser)
	if err != nil {
		return err
	}

	for k, v := range values {
		if passed[k] {
			continue
		}

		if p, ok := c.values[k]; ok {
			*p = v
			passed[k] = true
		}
	}
	return nil
}

// Returns all options.
func (c *Config) GetOptions() map[string]*Option {
	ret := make(map[string]*Option)
	for name, val := range c.options {
		ret[name] = val
	}
	return ret
}

// Option contains the details of a configuration options,
// e.g. corresponding environment variable, default value,
// description.
type Option struct {
	name					string
	envVar					string
	shortOpt				string
	def						string
	desc					string
	longOpt					string
	boolean					bool
	fileSpec				string              // The file spec is of the form "(CATEGORY.)*NAME", eg. for 'foo' under the category 'bar', it would be foo.bar
}

func (o Option) GetName() string {
	return o.name
}

func (o Option) GetFileSpec() string {
	return o.fileSpec
}

func (o Option) GetShortOpt() string {
	return o.shortOpt
}

func (o Option) GetLongOpt() string {
	return o.longOpt
}

func (o Option) GetDefault() string {
	return o.def
}

func (o Option) GetEnvVar() string {
	return o.envVar
}

func (o Option) GetDescription() string {
	return o.desc
}

func (o *Option) FileSpec(spec string) *Option {
	o.fileSpec = spec
	return o
}

func (o *Option) ShortOpt(opt string) *Option {
	o.shortOpt = opt
	return o
}

func (o *Option) LongOpt(opt string) *Option {
	o.longOpt = opt
	return o
}

// Sets the configuration option's default value.
func (o *Option) Default(def string) *Option {
	o.def = def
	return o
}

// Sets the configuration option's corresponding environment variable.
func (o *Option) EnvVar(envVar string) *Option {
	o.envVar = envVar
	return o
}

// Sets the configuration options long description.
func (o *Option) Description(desc string) *Option {
	o.desc = desc
	return o
}
<commit_msg>adding ability to insert boolean flags; no parameter required on command line, values other than '', '0', 'false' and 'no' are considered true in env and files<commit_after>package gofigure

import (
	"fmt"
	"github.com/droundy/goopt"
	"log"
	"os"
	"strings"
)

// This function is a convenience function. It displays the default values for us but handles them
// in the way we prefer it.
func GooptFigureString(names []string, def string, help string) *string {
	s := new(string)
	*s = ""
	f := func (ss string) error {
		*s = ss
		return nil
	}
	goopt.ReqArg(names, def, help, f)
	return s
}

func GooptFigureBool(names []string, help string) *bool {
	s := new(bool)
	*s = false
	f := func () error {
		*s = true
		return nil
	}
	goopt.NoArg(names, help, f)
	return s
}

// Config contains the configuration options that may be set by
// command line flags and environment variables.
type Config struct {
	Description			string
	DescribeEnvironment	bool	// if true, the environment variable is automatically added to the flag description
	DisableCommandLine	bool
	EnvPrefix			string
	EnvOverridesFile	bool
	FileParser			File
	RequireFile			bool    // application will panic if RequireFile == true, FileParser != nil and file doesn't exist
	Version				string
	options				map[string]*Option
	bools				map[string]*bool
	flags				map[string]*string
	values				map[string]*string
}

// Returns a new Config instance.
func New() *Config {
	return &Config{
		DescribeEnvironment:	false,
		DisableCommandLine:		false,
		EnvOverridesFile:		false,
		RequireFile:			true,
		options:				make(map[string]*Option),
		bools:					make(map[string]*bool),
		flags:					make(map[string]*string),
		values:					make(map[string]*string),
	}
}

// Adds a configuration option, returns an Option instance for
// easily setting the corresponding environment variable, default
// value, and description.
func (c *Config) Add(name string) *Option {
	c.options[name] = &Option{
		name:    name,
		envVar:  "",
		def:     "",
		desc:    "",
		longOpt: name,
	}
	return c.options[name]
}

func (c *Config) AddBoolean(name string) *Option {
	c.options[name] = &Option{
		name:    name,
		envVar:  "",
		def:     "",
		desc:    "",
		longOpt: name,
		boolean: true,
	}
	return c.options[name]
}

// Returns a configuration option by flag name.
func (c Config) Get(name string) *string {
	return c.values[name]
}

// Parses the configuration options into defined flags, sets the value
// accordingly. Options are read first from command line flags, then
// from environment variables, and falls back to the default value if
// neither are set.
//
// See https://github.com/rakyll/globalconf/blob/master/globalconf.go
func (c *Config) Parse() {
	goopt.Description = func() string {
		return c.Description
	}

	goopt.Version = c.Version

	// Sets the flags from the configuration options.
	for name, o := range c.options {
		cmdline := []string{}
		if o.shortOpt != "" {
			cmdline = append(cmdline, "-" + o.shortOpt)
		}
		cmdline = append(cmdline, "--"+o.longOpt)
		desc := o.desc
		if c.DescribeEnvironment && o.envVar != "" {
			desc += fmt.Sprintf(" Environment variable: %s%s.", c.EnvPrefix, o.envVar)
		}
		if o.boolean {
			c.bools[name] = GooptFigureBool(cmdline, desc)
		} else {
			c.flags[name] = GooptFigureString(cmdline, o.def, desc)
		}
		defcopy := o.def
		c.values[name] = &defcopy
	}

	passed := make(map[string]bool)

	if !c.DisableCommandLine {
		goopt.Parse(nil)

		// Gather the options passed through command line.
		for name, f := range c.flags {
			if *f != "" {
				passed[name] = true
				*c.values[name] = *f
			}
		}

		for name, f := range c.bools {
			if *f {
				passed[name] = true
				*c.values[name] = "true"
			} else {
				*c.values[name] = "false"
			}
		}
	}

	if (c.EnvOverridesFile) {
		c.ParseEnv(passed)
		err := c.ParseFile(passed)
		if err != nil && c.RequireFile {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
	} else {
		err := c.ParseFile(passed)
		if err != nil && c.RequireFile {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
		c.ParseEnv(passed)
	}
}

func (c *Config) ParseEnv(passed map[string]bool) {
	for name, f := range c.options {

		// Skip flags passed through the command line as the option is
		// already set and takes precedence over environment variables.
		if passed[name] {
			continue
		}

		// Some options shouldn't be set via environment variables.
		if f.envVar == "" {
			continue
		}

		// If the configuration option was not passed via the command line,
		// check the corresponding environment variable.
		envVar := c.EnvPrefix + f.envVar
		if val := os.Getenv(envVar); val != "" {
			if f.boolean {
				boolval := "true"
				if strings.ToLower(val) == "false" || strings.ToLower(val) == "no" || val == "0" || val == "" {
					boolval = "false"
				}
				*c.values[name] = boolval
			} else {
				*c.values[name] = val
			}
			passed[name] = true
		}

	}
}

func (c Config) parseFileToMap(handler File) (ValueMap, error) {
	root, err := handler.Parse()
	if err != nil {
		return nil, err
	}
	ret := ValueMap{}
	for _, opt := range c.options {
		if val, ok := root.FindOption(opt); ok {
			ret.Set(opt.GetName(), val)
		}
	}
	return ret, nil
}

func (c *Config) ParseFile(passed map[string]bool) error {
	if c.FileParser == nil {
		return nil
	}

	values, err := c.parseFileToMap(c.FileParser)
	if err != nil {
		return err
	}

	for k, v := range values {
		if passed[k] {
			continue
		}

		if f, ok := c.options[k]; ok {
			if f.boolean {
				boolval := "true"
				if strings.ToLower(v) == "false" || strings.ToLower(v) == "no" || v == "0" || v == "" {
					boolval = "false"
				}
				*c.values[k] = boolval
			} else {
				if p, ok := c.values[k]; ok {
					*p = v
				}
			}
			passed[k] = true
		}
	}
	return nil
}

// Returns all options.
func (c *Config) GetOptions() map[string]*Option {
	ret := make(map[string]*Option)
	for name, val := range c.options {
		ret[name] = val
	}
	return ret
}

// Option contains the details of a configuration options,
// e.g. corresponding environment variable, default value,
// description.
type Option struct {
	name					string
	envVar					string
	shortOpt				string
	def						string
	desc					string
	longOpt					string
	boolean					bool
	fileSpec				string              // The file spec is of the form "(CATEGORY.)*NAME", eg. for 'foo' under the category 'bar', it would be foo.bar
}

func (o Option) GetName() string {
	return o.name
}

func (o Option) GetFileSpec() string {
	return o.fileSpec
}

func (o Option) GetShortOpt() string {
	return o.shortOpt
}

func (o Option) GetLongOpt() string {
	return o.longOpt
}

func (o Option) GetDefault() string {
	return o.def
}

func (o Option) GetEnvVar() string {
	return o.envVar
}

func (o Option) GetDescription() string {
	return o.desc
}

func (o *Option) FileSpec(spec string) *Option {
	o.fileSpec = spec
	return o
}

func (o *Option) ShortOpt(opt string) *Option {
	o.shortOpt = opt
	return o
}

func (o *Option) LongOpt(opt string) *Option {
	o.longOpt = opt
	return o
}

// Sets the configuration option's default value.
func (o *Option) Default(def string) *Option {
	if o.boolean {
		panic("Cannot set default value for boolean flag.")
	}
	o.def = def
	return o
}

// Sets the configuration option's corresponding environment variable.
func (o *Option) EnvVar(envVar string) *Option {
	o.envVar = envVar
	return o
}

// Sets the configuration options long description.
func (o *Option) Description(desc string) *Option {
	o.desc = desc
	return o
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"

	"github.com/arachnist/gorepost/config"
	"github.com/sorcix/irc"
)

type Connection struct {
	Network  string
	Input    chan irc.Message
	Output   chan irc.Message
	IRCConn  *irc.Conn
	QuitSend chan struct{}
	QuitRecv chan struct{}
}

func (c *Connection) Sender() {
	for {
		select {
		case msg := <-c.Input:
			c.IRCConn.Encode(&msg)
		case <-c.QuitSend:
			log.Println(c.Network, "closing Sender")
			close(c.Input)
			close(c.QuitSend)
			return
		}
	}
}

func (c *Connection) Receiver() {
	for {
		msg, err := c.IRCConn.Decode()
		if err != nil {
			log.Println(c.Network, "error decoding message", err.Error())
		}
		select {
		case c.Output <- *msg:
		case <-c.QuitRecv:
			log.Println(c.Network, "closing receiver")
			close(c.Output)
			close(c.QuitRecv)
			return
		}
	}
}

func SetupConn(network string, config config.Config, connection *Connection) error {
	rand.Seed(time.Now().UnixNano())
	server := config.Servers[network][rand.Intn(len(config.Servers[network]))]

	conn, err := irc.Dial(server)
	if err != nil {
		log.Println("Cannot connect to", network, "server:", server, "error:", err.Error())
		return err
	}
	connection.IRCConn = conn

	go connection.Sender()
	go connection.Receiver()

	// Initial commands sent to IRC server
	connection.Input <- irc.Message{
		Command:  "NICK",
		Trailing: config.Nick,
	}
	connection.Input <- irc.Message{
		Command:  "USER",
		Params:   []string{config.Nick, "3", "*"},
		Trailing: config.Nick,
	}

	return nil
}

func ConnectionKeeper(connection *Connection) {
	for {

	}
}

func main() {
	config, err := config.ReadConfig(os.Args[1])
	if err != nil {
		fmt.Println("Error reading configuration from", os.Args[1], "error:", err.Error())
		os.Exit(1)
	}

	logfile, err := os.OpenFile(config.Logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("Error opening", config.Logpath, "for writing, error:", err.Error())
		os.Exit(1)
	}
	log.SetOutput(logfile)

}
<commit_msg>Speaking of our own little irc connection lib…<commit_after>package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"

	"github.com/arachnist/gorepost/config"
	"github.com/arachnist/gorepost/irc"
)

func main() {
	config, err := config.ReadConfig(os.Args[1])
	if err != nil {
		fmt.Println("Error reading configuration from", os.Args[1], "error:", err.Error())
		os.Exit(1)
	}

	logfile, err := os.OpenFile(config.Logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("Error opening", config.Logpath, "for writing, error:", err.Error())
		os.Exit(1)
	}
	log.SetOutput(logfile)

}
<|endoftext|>


<commit_before>// Implements "Version 6" UUIDs in Go.  See http://bradleypeabody.github.io/uuidv6/
// UUIDs sort correctly by time when naively sorted as raw bytes, have a Time()
// function that returns time the UUID was created and have a reasonable
// guarantee of being globally unique (based on the specifications from
// RFC 4122, with a few intentional exceptions.)
package gouuidv6

import (
	"crypto/rand"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
	"sync"
	"time"
)

var bigEnd = binary.BigEndian

// "Version 6" UUID.
type UUID [16]byte

// Slice of UUIDs, sorts using first 64bits (where the time is)
type UUIDSlice []UUID

func (s UUIDSlice) Len() int           { return len(s) }
func (s UUIDSlice) Less(i, j int) bool { return bigEnd.Uint64(s[i][:8]) < bigEnd.Uint64(s[j][:8]) }
func (s UUIDSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// Textual representation per RFC 4122, e.g. "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
func (u UUID) String() string {
	return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
}

// Parse text representation
func Parse(us string) (UUID, error) {
	var ret UUID
	var v1, v2, v3, v4, v5 uint64
	_, err := fmt.Sscanf(us, "%08x-%04x-%04x-%04x-%012x", &v1, &v2, &v3, &v4, &v5)
	if err != nil {
		return ret, err
	}
	bigEnd.PutUint64(ret[8:], v5)
	bigEnd.PutUint16(ret[8:10], uint16(v4))
	bigEnd.PutUint16(ret[6:8], uint16(v3))
	bigEnd.PutUint16(ret[4:6], uint16(v2))
	bigEnd.PutUint32(ret[:4], uint32(v1))
	return ret, nil
}

func (u UUID) MarshalText() ([]byte, error)           { return []byte(u.String()), nil }
func (u *UUID) UnmarshalText(text []byte) (err error) { *u, err = Parse(string(text)); return }

func (u UUID) MarshalBinary() ([]byte, error)     { return u[:], nil }
func (u *UUID) UnmarshalBinary(data []byte) error { copy(u[:], data); return nil }

func (u UUID) MarshalJSON() ([]byte, error) { return []byte(`"` + u.String() + `"`), nil }
func (u *UUID) UnmarshalJSON(data []byte) error {
	s := ""
	err := json.Unmarshal(data, &s)
	if err != nil {
		return err
	}
	*u, err = Parse(s)
	return err
}

// Return as byte slice.
func (u UUID) Bytes() []byte { return u[:] }

// Return true if all UUID bytes are zero.
func (u UUID) IsNil() bool { return (bigEnd.Uint64(u[0:8]) | bigEnd.Uint64(u[8:16])) == 0 }

// Extract and return the time from the UUID.
func (u UUID) Time() time.Time {

	// verify version and variant fields
	if !((u[6]&0xF0) == 0x60 && (u[8]&0xC0) == 0x80) {
		return time.Time{} // return zero time if not a version 6 UUID
	}

	hi := uint64(bigEnd.Uint64(u[:8]))

	// chop the version data out and form the number we want
	t := ((hi >> 4) & 0xFFFFFFFFFFFFF000) | (0x0FFF & hi)

	// convert to nanoseconds
	ut := int64(t-tsoff) * 100

	return time.Unix(ut/int64(time.Second), ut%int64(time.Second))
}

// Return a new UUID initialized to a proper value according to "Version 6" rules.
func New() UUID {

	// NOTE: We intentionally ignore RFC 4122 section 4.2.1.2. and in the case
	// that UUIDs are requested within the same 100-nanosecond time interval,
	// we just increment the clock sequence - the same thing the RFC advises
	// in the case of the clock moving backward (section 4.1.5).

	// get current timestamp
	tsval := ts()

	newlock.Lock()
	// if clock is the same as last time or moved backward, increment clockseq
	if lastts >= tsval {
		clockseq++
	}
	lastts = tsval
	cs := clockseq
	newlock.Unlock()

	var ret UUID

	// shift up 4 bits, mask back in the relevant lower part and set the version
	hi := uint64(((tsval << 4) & 0xFFFFFFFFFFFF0000) | (tsval & 0x0FFF) | 0x6000)

	// 2 bit variant, 14 bits clock sequence, 48 bits node
	lo := (uint64(0x8000) << 48) | (uint64(cs&0x3fff) << 48) | node

	bigEnd.PutUint64(ret[:8], hi)
	bigEnd.PutUint64(ret[8:], lo)

	return ret

}

// Returns a timestamp appropriate for UUID time
func ts() uint64 { return tsoff + uint64(time.Now().UnixNano()/100) }

// UUID static time offset (see https://play.golang.org/p/pPJd86iZMW)
const tsoff = uint64(122192928000000000)

// lock we use when creating new UUIDs
var newlock sync.Mutex

// last timestamp used
var lastts uint64

// clock sequence value (32-bit so we can use sync/atomic)
var clockseq uint32

// the node part - based on interface MAC address or random
var node uint64

func init() {

	b := make([]byte, 8)

	// start with random clock sequence
	rand.Read(b)
	clockseq = bigEnd.Uint32(b[:4])

	// try to get first interface MAC and use that for node
	ifs, _ := net.Interfaces()
	for _, i := range ifs {
		if len(i.HardwareAddr) >= 6 {
			node = uint64(bigEnd.Uint16(i.HardwareAddr[:2]))<<32 | uint64(bigEnd.Uint32(i.HardwareAddr[2:6]))
			break
		}
	}

	// no node yet, make it random
	if node == 0 {
		RandomizeNode()
	}

}

// Set the 'node' part of the UUID to a random value, instead of using one
// of the MAC addresses from the system.  Use this if you are concerned about
// the privacy aspect of using a MAC address.
func RandomizeNode() {
	b := make([]byte, 8)
	rand.Read(b)
	// mask out high 2 bytes and set the multicast bit
	node = (bigEnd.Uint64(b[:8]) & 0x0000FFFFFFFFFFFF) | 0x0000010000000000
}
<commit_msg>added function to make uuid based on a specific time<commit_after>// Implements "Version 6" UUIDs in Go.  See http://bradleypeabody.github.io/uuidv6/
// UUIDs sort correctly by time when naively sorted as raw bytes, have a Time()
// function that returns time the UUID was created and have a reasonable
// guarantee of being globally unique (based on the specifications from
// RFC 4122, with a few intentional exceptions.)
package gouuidv6

import (
	"crypto/rand"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
	"sync"
	"time"
)

var bigEnd = binary.BigEndian

// "Version 6" UUID.
type UUID [16]byte

// Slice of UUIDs, sorts using first 64bits (where the time is)
type UUIDSlice []UUID

func (s UUIDSlice) Len() int           { return len(s) }
func (s UUIDSlice) Less(i, j int) bool { return bigEnd.Uint64(s[i][:8]) < bigEnd.Uint64(s[j][:8]) }
func (s UUIDSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// Textual representation per RFC 4122, e.g. "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
func (u UUID) String() string {
	return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
}

// Parse text representation
func Parse(us string) (UUID, error) {
	var ret UUID
	var v1, v2, v3, v4, v5 uint64
	_, err := fmt.Sscanf(us, "%08x-%04x-%04x-%04x-%012x", &v1, &v2, &v3, &v4, &v5)
	if err != nil {
		return ret, err
	}
	bigEnd.PutUint64(ret[8:], v5)
	bigEnd.PutUint16(ret[8:10], uint16(v4))
	bigEnd.PutUint16(ret[6:8], uint16(v3))
	bigEnd.PutUint16(ret[4:6], uint16(v2))
	bigEnd.PutUint32(ret[:4], uint32(v1))
	return ret, nil
}

func (u UUID) MarshalText() ([]byte, error)           { return []byte(u.String()), nil }
func (u *UUID) UnmarshalText(text []byte) (err error) { *u, err = Parse(string(text)); return }

func (u UUID) MarshalBinary() ([]byte, error)     { return u[:], nil }
func (u *UUID) UnmarshalBinary(data []byte) error { copy(u[:], data); return nil }

func (u UUID) MarshalJSON() ([]byte, error) { return []byte(`"` + u.String() + `"`), nil }
func (u *UUID) UnmarshalJSON(data []byte) error {
	s := ""
	err := json.Unmarshal(data, &s)
	if err != nil {
		return err
	}
	*u, err = Parse(s)
	return err
}

// Return as byte slice.
func (u UUID) Bytes() []byte { return u[:] }

// Return true if all UUID bytes are zero.
func (u UUID) IsNil() bool { return (bigEnd.Uint64(u[0:8]) | bigEnd.Uint64(u[8:16])) == 0 }

// Extract and return the time from the UUID.
func (u UUID) Time() time.Time {

	// verify version and variant fields
	if !((u[6]&0xF0) == 0x60 && (u[8]&0xC0) == 0x80) {
		return time.Time{} // return zero time if not a version 6 UUID
	}

	hi := uint64(bigEnd.Uint64(u[:8]))

	// chop the version data out and form the number we want
	t := ((hi >> 4) & 0xFFFFFFFFFFFFF000) | (0x0FFF & hi)

	// convert to nanoseconds
	ut := int64(t-tsoff) * 100

	return time.Unix(ut/int64(time.Second), ut%int64(time.Second))
}

func NewFromTime(t time.Time) UUID {

	// NOTE: We intentionally ignore RFC 4122 section 4.2.1.2. and in the case
	// that UUIDs are requested within the same 100-nanosecond time interval,
	// we just increment the clock sequence - the same thing the RFC advises
	// in the case of the clock moving backward (section 4.1.5).

	// get current timestamp
	tsval := tstime(t)

	newlock.Lock()
	// if clock is the same as last time or moved backward, increment clockseq
	if lastts >= tsval {
		clockseq++
	}
	lastts = tsval
	cs := clockseq
	newlock.Unlock()

	var ret UUID

	// shift up 4 bits, mask back in the relevant lower part and set the version
	hi := uint64(((tsval << 4) & 0xFFFFFFFFFFFF0000) | (tsval & 0x0FFF) | 0x6000)

	// 2 bit variant, 14 bits clock sequence, 48 bits node
	lo := (uint64(0x8000) << 48) | (uint64(cs&0x3fff) << 48) | node

	bigEnd.PutUint64(ret[:8], hi)
	bigEnd.PutUint64(ret[8:], lo)

	return ret

}

// Return a new UUID initialized to a proper value according to "Version 6" rules.
func New() UUID { return NewFromTime(time.Now()) }

// Returns a timestamp appropriate for UUID time
func ts() uint64 { return tsoff + uint64(time.Now().UnixNano()/100) }

func tstime(t time.Time) uint64 { return tsoff + uint64(t.UnixNano()/100) }

// UUID static time offset (see https://play.golang.org/p/pPJd86iZMW)
const tsoff = uint64(122192928000000000)

// lock we use when creating new UUIDs
var newlock sync.Mutex

// last timestamp used
var lastts uint64

// clock sequence value (32-bit so we can use sync/atomic)
var clockseq uint32

// the node part - based on interface MAC address or random
var node uint64

func init() {

	b := make([]byte, 8)

	// start with random clock sequence
	rand.Read(b)
	clockseq = bigEnd.Uint32(b[:4])

	// try to get first interface MAC and use that for node
	ifs, _ := net.Interfaces()
	for _, i := range ifs {
		if len(i.HardwareAddr) >= 6 {
			node = uint64(bigEnd.Uint16(i.HardwareAddr[:2]))<<32 | uint64(bigEnd.Uint32(i.HardwareAddr[2:6]))
			break
		}
	}

	// no node yet, make it random
	if node == 0 {
		RandomizeNode()
	}

}

// Set the 'node' part of the UUID to a random value, instead of using one
// of the MAC addresses from the system.  Use this if you are concerned about
// the privacy aspect of using a MAC address.
func RandomizeNode() {
	b := make([]byte, 8)
	rand.Read(b)
	// mask out high 2 bytes and set the multicast bit
	node = (bigEnd.Uint64(b[:8]) & 0x0000FFFFFFFFFFFF) | 0x0000010000000000
}
<|endoftext|>


<commit_before>package crawler

import (
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/pkg/errors"
)

const (
	timeout = 5 // sec
)

type ChannelParser interface {
	Parse(r io.Reader) ([]*ChannelItem, error)
}

type ChannelImage struct {
	Url string
}

type ChannelVideo struct {
	Url string
}

type ChannelItem struct {
	Url         string
	Title       string
	PublishedAt *time.Time
	Images      []*ChannelImage
	Videos      []*ChannelVideo
}

func (c *ChannelItem) UniqId() string {
	id := c.Url
	if c.PublishedAt != nil {
		id = fmt.Sprintf("%s%s", id, c.PublishedAt.Format("20060102150405"))
	}
	return id
}

func (c *ChannelItem) ImageUrlsToString() string {
	s := []string{}
	for _, image := range c.Images {
		s = append(s, image.Url)
	}
	return strings.Join(s, ",")
}

func (c *ChannelItem) VideoUrlsToString() string {
	s := []string{}
	for _, video := range c.Videos {
		s = append(s, video.Url)
	}
	return strings.Join(s, ",")
}

type Channel struct {
	Url    string
	Title  string
	Items  []*ChannelItem
	Client *http.Client `json:"-"`
}

type ChannelClient struct {
	Channel *Channel
	parser  ChannelParser
}

func newChannel(url string, title string) *Channel {
	return &Channel{
		Url:    url,
		Title:  title,
		Client: http.DefaultClient,
	}
}

func newChannelClient(c *Channel, parser ChannelParser) *ChannelClient {
	return &ChannelClient{
		Channel: c,
		parser:  parser,
	}
}

func (c *ChannelClient) Fetch() (*Channel, error) {
	req, err := http.NewRequest("GET", c.Channel.Url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Cache-Control", "no-cache, max-age=0")

	resp, err := c.Channel.Client.Do(req)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to get resource. url:%s", c.Channel.Url)
	}
	defer resp.Body.Close()

	if c.parser == nil {
		return nil, errors.Errorf("You must implemented ChannelParser. url:%s", c.Channel.Url)
	}

	c.Channel.Items, err = c.parser.Parse(resp.Body)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to parse channel. url:%s", c.Channel.Url)
	}
	return c.Channel, nil
}
<commit_msg>cache-control parameter tuning<commit_after>package crawler

import (
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/pkg/errors"
)

const (
	timeout = 5 // sec
)

type ChannelParser interface {
	Parse(r io.Reader) ([]*ChannelItem, error)
}

type ChannelImage struct {
	Url string
}

type ChannelVideo struct {
	Url string
}

type ChannelItem struct {
	Url         string
	Title       string
	PublishedAt *time.Time
	Images      []*ChannelImage
	Videos      []*ChannelVideo
}

func (c *ChannelItem) UniqId() string {
	id := c.Url
	if c.PublishedAt != nil {
		id = fmt.Sprintf("%s%s", id, c.PublishedAt.Format("20060102150405"))
	}
	return id
}

func (c *ChannelItem) ImageUrlsToString() string {
	s := []string{}
	for _, image := range c.Images {
		s = append(s, image.Url)
	}
	return strings.Join(s, ",")
}

func (c *ChannelItem) VideoUrlsToString() string {
	s := []string{}
	for _, video := range c.Videos {
		s = append(s, video.Url)
	}
	return strings.Join(s, ",")
}

type Channel struct {
	Url    string
	Title  string
	Items  []*ChannelItem
	Client *http.Client `json:"-"`
}

type ChannelClient struct {
	Channel *Channel
	parser  ChannelParser
}

func newChannel(url string, title string) *Channel {
	return &Channel{
		Url:    url,
		Title:  title,
		Client: http.DefaultClient,
	}
}

func newChannelClient(c *Channel, parser ChannelParser) *ChannelClient {
	return &ChannelClient{
		Channel: c,
		parser:  parser,
	}
}

func (c *ChannelClient) Fetch() (*Channel, error) {
	req, err := http.NewRequest("GET", c.Channel.Url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Cache-Control", "max-age=0, must-revalidate")

	resp, err := c.Channel.Client.Do(req)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to get resource. url:%s", c.Channel.Url)
	}
	defer resp.Body.Close()

	if c.parser == nil {
		return nil, errors.Errorf("You must implemented ChannelParser. url:%s", c.Channel.Url)
	}

	c.Channel.Items, err = c.parser.Parse(resp.Body)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to parse channel. url:%s", c.Channel.Url)
	}
	return c.Channel, nil
}
<|endoftext|>


<commit_before>package crontainer

import (
	"log"
	"math/rand"
	"os/exec"
)

type Task struct {
	Command  string
	Schedule string
	Name     string
}

func NewTask(command string, schedule string, name string) *Task {
	if name == "" {
		name = randStringBytes(15)
	}
	return &Task{
		Command:  command,
		Schedule: schedule,
		Name:     name,
	}
}

// Run the task on the command line
func (t *Task) Run() {
	t.log("Started")
	cmd := exec.Command("/bin/sh", "-c", t.Command)

	out, err := cmd.Output()
	if err != nil {
		t.log(err)
	}

	// TODO Provide an io.Writer as Stdout and Stderr to capture the whole output
	// The following line only returns the last line of the output
	t.log(string(out))

	t.log("Done")
}

func (t *Task) log(text interface{}) {
	log.Printf("[%15s] %s", t.Name, text)
}

const letterBytes = "abcdefghijklmnopqrstuvwxyz1234567890"

func randStringBytes(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}
<commit_msg>use uuid for generated task ids<commit_after>package crontainer

import (
	"log"
	"os/exec"
	"github.com/satori/go.uuid"
)

type Task struct {
	Command  string
	Schedule string
	Name     string
}

func NewTask(command string, schedule string, name string) *Task {
	if name == "" {
		name = uuid.NewV4()
	}
	return &Task{
		Command:  command,
		Schedule: schedule,
		Name:     name,
	}
}

// Run the task on the command line
func (t *Task) Run() {
	t.log("Started")
	cmd := exec.Command("/bin/sh", "-c", t.Command)

	out, err := cmd.Output()
	if err != nil {
		t.log(err)
	}

	// TODO Provide an io.Writer as Stdout and Stderr to capture the whole output
	// The following line only returns the last line of the output
	t.log(string(out))

	t.log("Done")
}

func (t *Task) log(text interface{}) {
	log.Printf("[%15s] %s", t.Name, text)
}
<|endoftext|>


<commit_before>package graphite

import (
	"bytes"
	"fmt"
	"log"
	"net"
	"time"
)

// Graphite is a struct that defines the relevant properties of a graphite
// connection
type Graphite struct {
	Host    string
	Port    int
	Timeout time.Duration
	conn    net.Conn
	nop     bool
}

// defaultTimeout is the default number of seconds that we're willing to wait
// before forcing the connection establishment to fail
const defaultTimeout = 5

// IsNop is a getter for *graphite.Graphite.nop
func (graphite *Graphite) IsNop() bool {
	if graphite.nop {
		return true
	} else {
		return false
	}
}

// Given a Graphite struct, Connect populates the Graphite.conn field with an
// appropriate TCP connection
func (graphite *Graphite) Connect() error {
	if !graphite.IsNop() {
		address := fmt.Sprintf("%s:%d", graphite.Host, graphite.Port)

		if graphite.Timeout == 0 {
			graphite.Timeout = defaultTimeout * time.Second
		}

		conn, err := net.DialTimeout("tcp", address, graphite.Timeout)
		if err != nil {
			return err
		}

		graphite.conn = conn
	}

	return nil
}

// Given a Metric struct, the SendMetric method sends the supplied metric to the
// Graphite connection that the method is called upon
func (graphite *Graphite) SendMetric(metric Metric) error {
	if metric.Timestamp == 0 {
		metric.Timestamp = time.Now().Unix()
	}

	return graphite.sendMetric(metric)
}

// The SimpleSend method can be used to just pass a metric name and value and
// have it be sent to the Graphite host with the current timestamp
func (graphite *Graphite) SimpleSend(stat string, value string) error {
	metric := NewMetric(stat, value, time.Now().Unix())
	err := graphite.sendMetric(metric)
	if err != nil {
		return err
	}
	return nil
}

// sendMetric is an internal function that is used to write to the TCP
// connection in order to communicate a metric to the remote Graphite host
func (graphite *Graphite) sendMetric(metric Metric) error {
	if !graphite.IsNop() {
		buf := bytes.NewBufferString(fmt.Sprintf("%s %s %d\n", metric.Name, metric.Value, metric.Timestamp))
		_, err := graphite.conn.Write(buf.Bytes())
		if err != nil {
			return err
		}
	} else {
		log.Printf("Graphite: %s\n", metric)
	}

	return nil
}

// NewGraphiteHost is a factory method that's used to create a new Graphite
// connection given a hostname and a port number
func NewGraphite(host string, port int) (*Graphite, error) {
	Graphite := &Graphite{Host: host, Port: port}
	err := Graphite.Connect()
	if err != nil {
		return nil, err
	}

	return Graphite, nil
}

// NewGraphiteNop is a factory method that returns a Graphite struct but will
// not actually try to send any packets to a remote host and, instead, will just
// log. This is useful if you want to use Graphite in a project but don't want
// to make Graphite a requirement for the project.
func NewGraphiteNop(host string, port int) *Graphite {
	graphiteNop := &Graphite{Host: host, Port: port, nop: true}
	return graphiteNop
}
<commit_msg>Handle batches of metrics<commit_after>package graphite

import (
	"bytes"
	"fmt"
	"log"
	"net"
	"time"
)

// Graphite is a struct that defines the relevant properties of a graphite
// connection
type Graphite struct {
	Host    string
	Port    int
	Timeout time.Duration
	conn    net.Conn
	nop     bool
}

// defaultTimeout is the default number of seconds that we're willing to wait
// before forcing the connection establishment to fail
const defaultTimeout = 5

// IsNop is a getter for *graphite.Graphite.nop
func (graphite *Graphite) IsNop() bool {
	if graphite.nop {
		return true
	} else {
		return false
	}
}

// Given a Graphite struct, Connect populates the Graphite.conn field with an
// appropriate TCP connection
func (graphite *Graphite) Connect() error {
	if !graphite.IsNop() {
		address := fmt.Sprintf("%s:%d", graphite.Host, graphite.Port)

		if graphite.Timeout == 0 {
			graphite.Timeout = defaultTimeout * time.Second
		}

		conn, err := net.DialTimeout("tcp", address, graphite.Timeout)
		if err != nil {
			return err
		}

		graphite.conn = conn
	}

	return nil
}

// Given a Metric struct, the SendMetric method sends the supplied metric to the
// Graphite connection that the method is called upon
func (graphite *Graphite) SendMetric(metric Metric) error {
	if metric.Timestamp == 0 {
		metric.Timestamp = time.Now().Unix()
	}
	metrics := make([]Metric, 1)
	metrics[0] = metric

	return graphite.sendMetrics(metrics)
}

// Given a slice of Metrics, the SendMetrics method sends the metrics, as a
// batch, to the Graphite connection that the method is called upon
func (graphite *Graphite) SendMetrics(metrics []Metric) error {
	for _, metric := range metrics {
		if metric.Timestamp == 0 {
			metric.Timestamp = time.Now().Unix()
		}
	}

	return graphite.sendMetrics(metrics)
}

// sendMetrics is an internal function that is used to write to the TCP
// connection in order to communicate metrics to the remote Graphite host
func (graphite *Graphite) sendMetrics(metrics []Metric) error {
	zeroed_metric := Metric{} // ignore unintialized metrics
	if !graphite.IsNop() {
		buf := bytes.NewBufferString("")
		for _, metric := range metrics {
			if metric == zeroed_metric {
				continue // ignore unintialized metrics
			}
			buf.WriteString(fmt.Sprintf("%s %s %d\n", metric.Name, metric.Value, metric.Timestamp))
		}
		_, err := graphite.conn.Write(buf.Bytes())
		fmt.Print("Sent msg:", buf.String(), "'")
		if err != nil {
			return err
		}
	} else {
		for _, metric := range metrics {
			log.Printf("Graphite: %s\n", metric)
		}
	}
	return nil
}

// The SimpleSend method can be used to just pass a metric name and value and
// have it be sent to the Graphite host with the current timestamp
func (graphite *Graphite) SimpleSend(stat string, value string) error {
	metrics := make([]Metric, 1)
	metrics[0] = NewMetric(stat, value, time.Now().Unix())
	err := graphite.sendMetrics(metrics)
	if err != nil {
		return err
	}
	return nil
}

// NewGraphiteHost is a factory method that's used to create a new Graphite
// connection given a hostname and a port number
func NewGraphite(host string, port int) (*Graphite, error) {
	Graphite := &Graphite{Host: host, Port: port}
	err := Graphite.Connect()
	if err != nil {
		return nil, err
	}

	return Graphite, nil
}

// NewGraphiteNop is a factory method that returns a Graphite struct but will
// not actually try to send any packets to a remote host and, instead, will just
// log. This is useful if you want to use Graphite in a project but don't want
// to make Graphite a requirement for the project.
func NewGraphiteNop(host string, port int) *Graphite {
	graphiteNop := &Graphite{Host: host, Port: port, nop: true}
	return graphiteNop
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

type grtCmd struct {
	digest   string
	method   string
	protocol string
	domain   string
	endpoint string
	getVars  map[string]string
	body     string
}

func NewGrtCmd() grtCmd {
	var cmd grtCmd

	cmd.method = "GET"
	cmd.protocol = "http"
	cmd.domain = "gerrit.dev.returnpath.net"
	cmd.endpoint = "/a/changes/"
	cmd.getVars = map[string]string{
		"q": "status:open",
	}
	cmd.body = ""

	resp, err := client.Get(this.protocol + "://" + this.domain + this.endpoint + getQry)
	if err != nil {
		log.Fatal(err)
	}

	req, err := http.NewRequest(cmd.method, cmd.protocol+"://"+cmd.domain+cmd.endpoint, nil)
	if err != nil {
		log.Fatal(err)
	}

	user := GetConfigValue("gerrit.user")
	pass := GetConfigValue("gerrit.pass")
	auth := GetAuthorization(user, pass, resp)
	cmd.digest = GetAuthString(auth, req.URL, req.Method, 1)

	return cmd
}

func (this grtCmd) Call() string {
	var client http.Client
	getQry := ""

	for k, v := range this.getVars {
		getQry += k + "=" + v + "&"
	}

	if len(getQry) > 0 {
		getQry = "?" + getQry[:len(getQry)-1]
	}

	resp, err := client.Get(this.protocol + "://" + this.domain + this.endpoint + getQry)
	if err != nil {
		log.Fatal(err)
	}

	req, err := http.NewRequest(this.method, this.protocol+"://"+this.domain+this.endpoint+getQry, nil)
	if err != nil {
		log.Fatal(err)
	}

	resp, err = client.Do(req)
	if err != nil {
		log.Fatal(err)
	}

	req.Header.Add("Authorization", digest)

	resp, err = client.Do(req)

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

	return string(body)
}
<commit_msg>Compilation bug fixes<commit_after>package main

import (
	"io/ioutil"
	"log"
	"net/http"
)

type grtCmd struct {
	digest   string
	method   string
	protocol string
	domain   string
	endpoint string
	getVars  map[string]string
	body     string
}

func NewGrtCmd() grtCmd {
	var cmd grtCmd

	cmd.method = "GET"
	cmd.protocol = "http"
	cmd.domain = "gerrit.dev.returnpath.net"
	cmd.endpoint = "/a/changes/"
	cmd.getVars = map[string]string{
		"q": "status:open",
	}
	cmd.body = ""

	var client http.Client

	resp, err := client.Get(cmd.protocol + "://" + cmd.domain + cmd.endpoint)
	if err != nil {
		log.Fatal(err)
	}

	req, err := http.NewRequest(cmd.method, cmd.protocol+"://"+cmd.domain+cmd.endpoint, nil)
	if err != nil {
		log.Fatal(err)
	}

	user := GetConfigValue("gerrit.user")
	pass := GetConfigValue("gerrit.pass")
	auth := GetAuthorization(user, pass, resp)
	cmd.digest = GetAuthString(auth, req.URL, req.Method, 1)

	return cmd
}

func (this grtCmd) Call() string {
	var client http.Client
	getQry := ""

	for k, v := range this.getVars {
		getQry += k + "=" + v + "&"
	}

	if len(getQry) > 0 {
		getQry = "?" + getQry[:len(getQry)-1]
	}

	resp, err := client.Get(this.protocol + "://" + this.domain + this.endpoint + getQry)
	if err != nil {
		log.Fatal(err)
	}

	req, err := http.NewRequest(this.method, this.protocol+"://"+this.domain+this.endpoint+getQry, nil)
	if err != nil {
		log.Fatal(err)
	}

	resp, err = client.Do(req)
	if err != nil {
		log.Fatal(err)
	}

	req.Header.Add("Authorization", this.digest)

	resp, err = client.Do(req)

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

	return string(body)
}
<|endoftext|>


<commit_before>// Copyright © 2016 Wei Shen <shenwei356@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"encoding/csv"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"sync"

	"github.com/shenwei356/util/pathutil"
	"github.com/shenwei356/xopen"
	"github.com/spf13/cobra"
)

// splitCmd represents the split command
var splitCmd = &cobra.Command{
	Use:   "split",
	Short: "split CSV/TSV into multiple files according to column values",
	Long: `split CSV/TSV into multiple files according to column values

Note:

  1. flag -o/--out-file can specify out directory for splitted files

`,
	Run: func(cmd *cobra.Command, args []string) {
		config := getConfigs(cmd)
		files := getFileList(args)
		if len(files) > 1 {
			checkError(fmt.Errorf("no more than one file should be given"))
		}
		runtime.GOMAXPROCS(config.NumCPUs)

		fieldStr := getFlagString(cmd, "fields")
		if fieldStr == "" {
			checkError(fmt.Errorf("flag -f (--fields) needed"))
		}
		fields, colnames, negativeFields, needParseHeaderRow := parseFields(cmd, fieldStr, config.NoHeaderRow)
		var fieldsMap map[int]struct{}
		if len(fields) > 0 {
			fields2 := make([]int, len(fields))
			fieldsMap = make(map[int]struct{}, len(fields))
			for i, f := range fields {
				if negativeFields {
					fieldsMap[f*-1] = struct{}{}
					fields2[i] = f * -1
				} else {
					fieldsMap[f] = struct{}{}
					fields2[i] = f
				}
			}
			fields = fields2
		}

		fuzzyFields := getFlagBool(cmd, "fuzzy-fields")
		ignoreCase := getFlagBool(cmd, "ignore-case")
		bufRowsSize := getFlagNonNegativeInt(cmd, "buf-rows")
		bufGroupsSize := getFlagNonNegativeInt(cmd, "buf-groups")
		gzipped := getFlagBool(cmd, "out-gzip")

		file := files[0]
		csvReader, err := newCSVReaderByConfig(config, file)
		checkError(err)
		csvReader.Run()

		var outFilePrefix, outFileSuffix string
		if isStdin(file) {
			if config.OutTabs || config.Tabs {
				outFilePrefix, outFileSuffix = "stdin", ".tsv"
			} else {
				outFilePrefix, outFileSuffix = "stdin", ".csv"
			}
		} else {
			outFilePrefix, outFileSuffix = filepathTrimExtension(file)
		}
		if gzipped &&
			!strings.HasSuffix(strings.ToLower(outFileSuffix), ".gz") {
			outFileSuffix = outFileSuffix + ".gz"
		}

		if config.OutFile != "-" { // outdir
			outdir := config.OutFile
			var existed bool
			existed, err = pathutil.DirExists(outdir)
			checkError(err)
			if !existed {
				checkError(os.MkdirAll(outdir, 0775))
			}
			outFilePrefix = filepath.Join(outdir, filepath.Base(outFilePrefix))
		}

		parseHeaderRow := needParseHeaderRow // parsing header row
		printHeaderRow := needParseHeaderRow
		var colnames2fileds map[string]int // column name -> field
		var colnamesMap map[string]*regexp.Regexp

		checkFields := true
		var items []string
		var key string
		var headerRow []string
		// moreThanOneWrite := make(map[string]bool)
		rowsBuf := make(map[string][][]string, bufGroupsSize)
		var ok bool

		printMetaLine := true
		for chunk := range csvReader.Ch {
			checkError(chunk.Err)

			for _, record := range chunk.Data {
				if parseHeaderRow { // parsing header row
					colnames2fileds = make(map[string]int, len(record))
					for i, col := range record {
						colnames2fileds[col] = i + 1
					}
					colnamesMap = make(map[string]*regexp.Regexp, len(colnames))
					for _, col := range colnames {
						if !fuzzyFields {
							if negativeFields {
								if _, ok = colnames2fileds[col[1:]]; !ok {
									checkError(fmt.Errorf(`column "%s" not existed in file: %s`, col[1:], file))
								}
							} else {
								if _, ok = colnames2fileds[col]; !ok {
									checkError(fmt.Errorf(`column "%s" not existed in file: %s`, col, file))
								}
							}
						}
						if negativeFields {
							colnamesMap[col[1:]] = fuzzyField2Regexp(col[1:])
						} else {
							colnamesMap[col] = fuzzyField2Regexp(col)
						}
					}

					if len(fields) == 0 { // user gives the colnames
						fields = []int{}
						for _, col := range record {
							if fuzzyFields {
								for _, re := range colnamesMap {
									if re.MatchString(col) {
										ok = true
										break
									}
								}
							} else {
								_, ok = colnamesMap[col]
							}
							if ok {
								fields = append(fields, colnames2fileds[col])
							}
						}
					}

					fieldsMap = make(map[int]struct{}, len(fields))
					for _, f := range fields {
						fieldsMap[f] = struct{}{}
					}

					parseHeaderRow = false
				}
				if checkFields {
					for field := range fieldsMap {
						if field > len(record) {
							checkError(fmt.Errorf(`field (%d) out of range (%d) in file: %s`, field, len(record), file))
						}
					}
					fields2 := []int{}
					for f := range record {
						_, ok = fieldsMap[f+1]
						if negativeFields {
							if !ok {
								fields2 = append(fields2, f+1)
							}
						} else {
							if ok {
								fields2 = append(fields2, f+1)
							}
						}
					}
					fields = fields2
					if len(fields) == 0 {
						checkError(fmt.Errorf("no fields matched in file: %s", file))
					}
					items = make([]string, len(fields))

					checkFields = false
				}

				for i, f := range fields {
					items[i] = record[f-1]
				}

				if printHeaderRow {
					headerRow = record
					printHeaderRow = false
					continue
				}

				key = strings.Join(items, "-")
				if ignoreCase {
					key = strings.ToLower(key)
				}

				row := make([]string, len(record))
				copy(row, record)

				if _, ok = rowsBuf[key]; ok {
					rowsBuf[key] = append(rowsBuf[key], row)
					if len(rowsBuf[key]) == bufRowsSize {
						appendRows(config,
							printMetaLine,
							csvReader,
							headerRow,
							fmt.Sprintf("%s-%s%s", outFilePrefix, key, outFileSuffix),
							rowsBuf[key],
							key,
						)
						rowsBuf[key] = make([][]string, 0, 1)

					}
				} else {
					rowsBuf[key] = make([][]string, 0, 1)
					rowsBuf[key] = append(rowsBuf[key], row)
					if len(rowsBuf) == bufGroupsSize { // empty the buffer
						var wg sync.WaitGroup
						tokens := make(chan int, config.NumCPUs)
						for key, rows := range rowsBuf {
							if len(rows) == 0 {
								continue
							}
							wg.Add(1)
							tokens <- 1
							go func(key string, rows [][]string) {
								appendRows(config,
									printMetaLine,
									csvReader,
									headerRow,
									fmt.Sprintf("%s-%s%s", outFilePrefix, key, outFileSuffix),
									rows,
									key,
								)
								<-tokens
								wg.Done()
							}(key, rows)
						}

						wg.Wait()

						rowsBuf = make(map[string][][]string, bufGroupsSize)
					}
				}
			}
		}

		var wg sync.WaitGroup
		tokens := make(chan int, config.NumCPUs)
		for key, rows := range rowsBuf {
			if len(rows) == 0 {
				continue
			}
			wg.Add(1)
			tokens <- 1
			go func(key string, rows [][]string) {
				appendRows(config,
					printMetaLine,
					csvReader,
					headerRow,
					fmt.Sprintf("%s-%s%s", outFilePrefix, key, outFileSuffix),
					rows,
					key,
				)
				<-tokens
				wg.Done()
			}(key, rows)
		}

		wg.Wait()

		if config.IgnoreEmptyRow {
			log.Warningf("file '%s': %d empty rows ignored", file, csvReader.NumEmptyRows)
		}
		if config.IgnoreIllegalRow {
			log.Warningf("file '%s': %d illegal rows ignored", file, csvReader.NumIllegalRows)
		}
	},
}

func init() {
	RootCmd.AddCommand(splitCmd)
	splitCmd.Flags().StringP("fields", "f", "1", `comma separated key fields, column name or index. e.g. -f 1-3 or -f id,id2 or -F -f "group*"`)
	splitCmd.Flags().BoolP("fuzzy-fields", "F", false, `using fuzzy fields, e.g., -F -f "*name" or -F -f "id123*"`)
	splitCmd.Flags().BoolP("ignore-case", "i", false, `ignore case`)
	splitCmd.Flags().BoolP("out-gzip", "G", false, `force output gzipped file`)
	splitCmd.Flags().IntP("buf-rows", "b", 100000, `buffering N rows for every group before writing to file`)
	splitCmd.Flags().IntP("buf-groups", "g", 100, `buffering N groups before writing to file`)

}

var moreThanOneWrite sync.Map

func appendRows(config Config,
	printMetaLine bool,
	csvReader *CSVReader,
	headerRow []string,
	outFile string,
	rows [][]string,
	// moreThanOneWrite map[string]bool,
	key string,
) {

	var outfh *xopen.Writer
	var err error

	_, writeOnce := moreThanOneWrite.Load(key)
	if writeOnce {
		outfh, err = xopen.WopenFile(outFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	} else {
		outfh, err = xopen.Wopen(outFile)
		moreThanOneWrite.Store(key, true)
	}
	checkError(err)
	defer outfh.Close()

	writer := csv.NewWriter(outfh)
	if config.OutTabs || config.Tabs {
		writer.Comma = '\t'
	} else {
		writer.Comma = config.OutDelimiter
	}

	if printMetaLine && len(csvReader.MetaLine) > 0 {
		outfh.WriteString(fmt.Sprintf("sep=%s\n", string(writer.Comma)))
	}
	if !writeOnce && headerRow != nil {
		checkError(writer.Write(headerRow))
	}

	for _, row := range rows {
		checkError(writer.Write(row))
	}

	writer.Flush()
	checkError(writer.Error())
}
<commit_msg>split: rename variables<commit_after>// Copyright © 2016 Wei Shen <shenwei356@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"encoding/csv"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"sync"

	"github.com/shenwei356/util/pathutil"
	"github.com/shenwei356/xopen"
	"github.com/spf13/cobra"
)

// splitCmd represents the split command
var splitCmd = &cobra.Command{
	Use:   "split",
	Short: "split CSV/TSV into multiple files according to column values",
	Long: `split CSV/TSV into multiple files according to column values

Note:

  1. flag -o/--out-file can specify out directory for splitted files

`,
	Run: func(cmd *cobra.Command, args []string) {
		config := getConfigs(cmd)
		files := getFileList(args)
		if len(files) > 1 {
			checkError(fmt.Errorf("no more than one file should be given"))
		}
		runtime.GOMAXPROCS(config.NumCPUs)

		fieldStr := getFlagString(cmd, "fields")
		if fieldStr == "" {
			checkError(fmt.Errorf("flag -f (--fields) needed"))
		}
		fields, colnames, negativeFields, needParseHeaderRow := parseFields(cmd, fieldStr, config.NoHeaderRow)
		var fieldsMap map[int]struct{}
		if len(fields) > 0 {
			fields2 := make([]int, len(fields))
			fieldsMap = make(map[int]struct{}, len(fields))
			for i, f := range fields {
				if negativeFields {
					fieldsMap[f*-1] = struct{}{}
					fields2[i] = f * -1
				} else {
					fieldsMap[f] = struct{}{}
					fields2[i] = f
				}
			}
			fields = fields2
		}

		fuzzyFields := getFlagBool(cmd, "fuzzy-fields")
		ignoreCase := getFlagBool(cmd, "ignore-case")
		bufRowsSize := getFlagNonNegativeInt(cmd, "buf-rows")
		bufGroupsSize := getFlagNonNegativeInt(cmd, "buf-groups")
		gzipped := getFlagBool(cmd, "out-gzip")

		file := files[0]
		csvReader, err := newCSVReaderByConfig(config, file)
		checkError(err)
		csvReader.Run()

		var outFilePrefix, outFileSuffix string
		if isStdin(file) {
			if config.OutTabs || config.Tabs {
				outFilePrefix, outFileSuffix = "stdin", ".tsv"
			} else {
				outFilePrefix, outFileSuffix = "stdin", ".csv"
			}
		} else {
			outFilePrefix, outFileSuffix = filepathTrimExtension(file)
		}
		if gzipped &&
			!strings.HasSuffix(strings.ToLower(outFileSuffix), ".gz") {
			outFileSuffix = outFileSuffix + ".gz"
		}

		if config.OutFile != "-" { // outdir
			outdir := config.OutFile
			var existed bool
			existed, err = pathutil.DirExists(outdir)
			checkError(err)
			if !existed {
				checkError(os.MkdirAll(outdir, 0775))
			}
			outFilePrefix = filepath.Join(outdir, filepath.Base(outFilePrefix))
		}

		parseHeaderRow := needParseHeaderRow // parsing header row
		printHeaderRow := needParseHeaderRow
		var colnames2fileds map[string]int // column name -> field
		var colnamesMap map[string]*regexp.Regexp

		checkFields := true
		var items []string
		var key string
		var headerRow []string
		// moreThanOneWrite := make(map[string]bool)
		rowsBuf := make(map[string][][]string, bufGroupsSize)
		var ok bool

		printMetaLine := true
		for chunk := range csvReader.Ch {
			checkError(chunk.Err)

			for _, record := range chunk.Data {
				if parseHeaderRow { // parsing header row
					colnames2fileds = make(map[string]int, len(record))
					for i, col := range record {
						colnames2fileds[col] = i + 1
					}
					colnamesMap = make(map[string]*regexp.Regexp, len(colnames))
					for _, col := range colnames {
						if !fuzzyFields {
							if negativeFields {
								if _, ok = colnames2fileds[col[1:]]; !ok {
									checkError(fmt.Errorf(`column "%s" not existed in file: %s`, col[1:], file))
								}
							} else {
								if _, ok = colnames2fileds[col]; !ok {
									checkError(fmt.Errorf(`column "%s" not existed in file: %s`, col, file))
								}
							}
						}
						if negativeFields {
							colnamesMap[col[1:]] = fuzzyField2Regexp(col[1:])
						} else {
							colnamesMap[col] = fuzzyField2Regexp(col)
						}
					}

					if len(fields) == 0 { // user gives the colnames
						fields = []int{}
						for _, col := range record {
							if fuzzyFields {
								for _, re := range colnamesMap {
									if re.MatchString(col) {
										ok = true
										break
									}
								}
							} else {
								_, ok = colnamesMap[col]
							}
							if ok {
								fields = append(fields, colnames2fileds[col])
							}
						}
					}

					fieldsMap = make(map[int]struct{}, len(fields))
					for _, f := range fields {
						fieldsMap[f] = struct{}{}
					}

					parseHeaderRow = false
				}
				if checkFields {
					for field := range fieldsMap {
						if field > len(record) {
							checkError(fmt.Errorf(`field (%d) out of range (%d) in file: %s`, field, len(record), file))
						}
					}
					fields2 := []int{}
					for f := range record {
						_, ok = fieldsMap[f+1]
						if negativeFields {
							if !ok {
								fields2 = append(fields2, f+1)
							}
						} else {
							if ok {
								fields2 = append(fields2, f+1)
							}
						}
					}
					fields = fields2
					if len(fields) == 0 {
						checkError(fmt.Errorf("no fields matched in file: %s", file))
					}
					items = make([]string, len(fields))

					checkFields = false
				}

				for i, f := range fields {
					items[i] = record[f-1]
				}

				if printHeaderRow {
					headerRow = record
					printHeaderRow = false
					continue
				}

				key = strings.Join(items, "-")
				if ignoreCase {
					key = strings.ToLower(key)
				}

				row := make([]string, len(record))
				copy(row, record)

				if _, ok = rowsBuf[key]; ok {
					rowsBuf[key] = append(rowsBuf[key], row)
					if len(rowsBuf[key]) == bufRowsSize {
						appendRows(config,
							printMetaLine,
							csvReader,
							headerRow,
							fmt.Sprintf("%s-%s%s", outFilePrefix, key, outFileSuffix),
							rowsBuf[key],
							key,
						)
						rowsBuf[key] = make([][]string, 0, 1)

					}
				} else {
					rowsBuf[key] = make([][]string, 0, 1)
					rowsBuf[key] = append(rowsBuf[key], row)
					if len(rowsBuf) == bufGroupsSize { // empty the buffer
						var wg sync.WaitGroup
						tokens := make(chan int, config.NumCPUs)
						for key, rows := range rowsBuf {
							if len(rows) == 0 {
								continue
							}
							wg.Add(1)
							tokens <- 1
							go func(key string, rows [][]string) {
								appendRows(config,
									printMetaLine,
									csvReader,
									headerRow,
									fmt.Sprintf("%s-%s%s", outFilePrefix, key, outFileSuffix),
									rows,
									key,
								)
								<-tokens
								wg.Done()
							}(key, rows)
						}

						wg.Wait()

						rowsBuf = make(map[string][][]string, bufGroupsSize)
					}
				}
			}
		}

		var wg sync.WaitGroup
		tokens := make(chan int, config.NumCPUs)
		for key, rows := range rowsBuf {
			if len(rows) == 0 {
				continue
			}
			wg.Add(1)
			tokens <- 1
			go func(key string, rows [][]string) {
				appendRows(config,
					printMetaLine,
					csvReader,
					headerRow,
					fmt.Sprintf("%s-%s%s", outFilePrefix, key, outFileSuffix),
					rows,
					key,
				)
				<-tokens
				wg.Done()
			}(key, rows)
		}

		wg.Wait()

		if config.IgnoreEmptyRow {
			log.Warningf("file '%s': %d empty rows ignored", file, csvReader.NumEmptyRows)
		}
		if config.IgnoreIllegalRow {
			log.Warningf("file '%s': %d illegal rows ignored", file, csvReader.NumIllegalRows)
		}
	},
}

func init() {
	RootCmd.AddCommand(splitCmd)
	splitCmd.Flags().StringP("fields", "f", "1", `comma separated key fields, column name or index. e.g. -f 1-3 or -f id,id2 or -F -f "group*"`)
	splitCmd.Flags().BoolP("fuzzy-fields", "F", false, `using fuzzy fields, e.g., -F -f "*name" or -F -f "id123*"`)
	splitCmd.Flags().BoolP("ignore-case", "i", false, `ignore case`)
	splitCmd.Flags().BoolP("out-gzip", "G", false, `force output gzipped file`)
	splitCmd.Flags().IntP("buf-rows", "b", 100000, `buffering N rows for every group before writing to file`)
	splitCmd.Flags().IntP("buf-groups", "g", 100, `buffering N groups before writing to file`)

}

var writtenFiles sync.Map

func appendRows(config Config,
	printMetaLine bool,
	csvReader *CSVReader,
	headerRow []string,
	outFile string,
	rows [][]string,
	key string,
) {

	var outfh *xopen.Writer
	var err error

	_, written := writtenFiles.Load(key)
	if written {
		outfh, err = xopen.WopenFile(outFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	} else {
		outfh, err = xopen.Wopen(outFile)
		writtenFiles.Store(key, true)
	}
	checkError(err)
	defer outfh.Close()

	writer := csv.NewWriter(outfh)
	if config.OutTabs || config.Tabs {
		writer.Comma = '\t'
	} else {
		writer.Comma = config.OutDelimiter
	}

	if printMetaLine && len(csvReader.MetaLine) > 0 {
		outfh.WriteString(fmt.Sprintf("sep=%s\n", string(writer.Comma)))
	}
	if !written && headerRow != nil {
		checkError(writer.Write(headerRow))
	}

	for _, row := range rows {
		checkError(writer.Write(row))
	}

	writer.Flush()
	checkError(writer.Error())
}
<|endoftext|>