<commit_before>package client

import (
	"syscall"
	"unsafe"
)

type Termios struct {
	Iflag  uintptr
	Oflag  uintptr
	Cflag  uintptr
	Lflag  uintptr
	Cc     [20]byte
	Ispeed uintptr
	Ospeed uintptr
}

const (
	// Input flags
	inpck  = 0x010
	istrip = 0x020
	icrnl  = 0x100
	ixon   = 0x200

	// Output flags
	opost = 0x1

	// Control flags
	cs8 = 0x300

	// Local flags
	icanon = 0x100
	iexten = 0x400
)

const (
        HUPCL                             = 0x4000
        ICANON                            = 0x100
        ICRNL                             = 0x100
        IEXTEN                            = 0x400
        BRKINT                            = 0x2
        CFLUSH                            = 0xf
        CLOCAL                            = 0x8000
        CREAD                             = 0x800
        CS5                               = 0x0
        CS6                               = 0x100
        CS7                               = 0x200
        CS8                               = 0x300
        CSIZE                             = 0x300
        CSTART                            = 0x11
        CSTATUS                           = 0x14
        CSTOP                             = 0x13
        CSTOPB                            = 0x400
        CSUSP                             = 0x1a
        IGNBRK                            = 0x1
        IGNCR                             = 0x80
        IGNPAR                            = 0x4
        IMAXBEL                           = 0x2000
        INLCR                             = 0x40
        INPCK                             = 0x10
        ISIG                              = 0x80
        ISTRIP                            = 0x20
        IUTF8                             = 0x4000
        IXANY                             = 0x800
        IXOFF                             = 0x400
        IXON                              = 0x200
        NOFLSH                            = 0x80000000
        OCRNL                             = 0x10
        OFDEL                             = 0x20000
        OFILL                             = 0x80
        ONLCR                             = 0x2
        ONLRET                            = 0x40
        ONOCR                             = 0x20
        ONOEOT                            = 0x8
        OPOST                             = 0x1
RENB                            = 0x1000
        PARMRK                            = 0x8
        PARODD                            = 0x2000

        TOSTOP                            = 0x400000
        VDISCARD                          = 0xf
        VDSUSP                            = 0xb
        VEOF                              = 0x0
        VEOL                              = 0x1
        VEOL2                             = 0x2
        VERASE                            = 0x3
        VINTR                             = 0x8
        VKILL                             = 0x5
        VLNEXT                            = 0xe
        VMIN                              = 0x10
        VQUIT                             = 0x9
        VREPRINT                          = 0x6
        VSTART                            = 0xc
        VSTATUS                           = 0x12
        VSTOP                             = 0xd
        VSUSP                             = 0xa
        VT0                               = 0x0
        VT1                               = 0x10000
        VTDLY                             = 0x10000
        VTIME                             = 0x11
	ECHO				  = 0x00000008

        PENDIN                            = 0x20000000
)

type State struct {
       termios Termios
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
        var termios Termios
        _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
        return err == 0
}

// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
        var oldState State
        if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
                return nil, err
        }

        newState := oldState.termios
        newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
	newState.Iflag |= ICRNL
	newState.Oflag |= ONLCR
        newState.Lflag &^= ECHO | ICANON | ISIG
        if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
                return nil, err
        }

        return &oldState, nil
}

// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, state *State) error {
        _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
        return err
}

<commit_msg>white space<commit_after>package client

import (
	"syscall"
	"unsafe"
)

type Termios struct {
	Iflag  uintptr
	Oflag  uintptr
	Cflag  uintptr
	Lflag  uintptr
	Cc     [20]byte
	Ispeed uintptr
	Ospeed uintptr
}

const (
	// Input flags
	inpck  = 0x010
	istrip = 0x020
	icrnl  = 0x100
	ixon   = 0x200

	// Output flags
	opost = 0x1

	// Control flags
	cs8 = 0x300

	// Local flags
	icanon = 0x100
	iexten = 0x400
)

const (
        HUPCL                             = 0x4000
        ICANON                            = 0x100
        ICRNL                             = 0x100
        IEXTEN                            = 0x400
        BRKINT                            = 0x2
        CFLUSH                            = 0xf
        CLOCAL                            = 0x8000
        CREAD                             = 0x800
        CS5                               = 0x0
        CS6                               = 0x100
        CS7                               = 0x200
        CS8                               = 0x300
        CSIZE                             = 0x300
        CSTART                            = 0x11
        CSTATUS                           = 0x14
        CSTOP                             = 0x13
        CSTOPB                            = 0x400
        CSUSP                             = 0x1a
        IGNBRK                            = 0x1
        IGNCR                             = 0x80
        IGNPAR                            = 0x4
        IMAXBEL                           = 0x2000
        INLCR                             = 0x40
        INPCK                             = 0x10
        ISIG                              = 0x80
        ISTRIP                            = 0x20
        IUTF8                             = 0x4000
        IXANY                             = 0x800
        IXOFF                             = 0x400
        IXON                              = 0x200
        NOFLSH                            = 0x80000000
        OCRNL                             = 0x10
        OFDEL                             = 0x20000
        OFILL                             = 0x80
        ONLCR                             = 0x2
        ONLRET                            = 0x40
        ONOCR                             = 0x20
        ONOEOT                            = 0x8
        OPOST                             = 0x1
RENB                            = 0x1000
        PARMRK                            = 0x8
        PARODD                            = 0x2000

        TOSTOP                            = 0x400000
        VDISCARD                          = 0xf
        VDSUSP                            = 0xb
        VEOF                              = 0x0
        VEOL                              = 0x1
        VEOL2                             = 0x2
        VERASE                            = 0x3
        VINTR                             = 0x8
        VKILL                             = 0x5
        VLNEXT                            = 0xe
        VMIN                              = 0x10
        VQUIT                             = 0x9
        VREPRINT                          = 0x6
        VSTART                            = 0xc
        VSTATUS                           = 0x12
        VSTOP                             = 0xd
        VSUSP                             = 0xa
        VT0                               = 0x0
        VT1                               = 0x10000
        VTDLY                             = 0x10000
        VTIME                             = 0x11
	ECHO				  = 0x00000008

        PENDIN                            = 0x20000000
)

type State struct {
       termios Termios
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
        var termios Termios
        _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
        return err == 0
}

// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
        var oldState State
        if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
                return nil, err
        }

        newState := oldState.termios
        newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
        newState.Iflag |= ICRNL
        newState.Oflag |= ONLCR
        newState.Lflag &^= ECHO | ICANON | ISIG
        if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
                return nil, err
        }

        return &oldState, nil
}

// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, state *State) error {
        _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
        return err
}

<|endoftext|>


<commit_before>/* text.go contains functions for text annotation
TODO:
getters and setters for
- Stretch (redefine C type)
- Weight (uint)
- Style (redefine C type)
- Resolution (two doubles)
- Decoration (redefine C type)
- Encoding (string)
*/

package canvas

/*
#cgo CFLAGS: -fopenmp -I./_include
#cgo LDFLAGS: -lMagickWand -lMagickCore

#include <wand/magick_wand.h>
*/
import "C"

import (
	"unsafe"
)

type Alignment uint

const (
 	UndefinedAlign Alignment	= Alignment(C.UndefinedAlign)
	LeftAlign					= Alignment(C.LeftAlign)
	CenterAlign					= Alignment(C.CenterAlign)
	RightAlign					= Alignment(C.RightAlign)
)

// structure containing all text properties for an annotation
// except the colors that are defined by FillColor and StrokeColor
type TextProperties struct {
	Font		string
	Family		string
	Size		float64
	// Stretch		C.StretchType
	// Weight		uint
	// Style		C.StyleType
	// Resolution  [2]C.double
	Alignment	Alignment
	Antialias	bool
	// Decoration	C.DecorationType
	// Encoding	string
	UnderColor	*C.PixelWand
}

// Returns a TextProperties structure.
// Parameters:
//   read_default: if false, returns an empty structure.
//				   if true, returns a structure set with current canvas settings
func (self *Canvas) NewTextProperties(read_default bool) *TextProperties {
	if read_default == true {
		cfont := C.DrawGetFont(self.drawing)
		defer C.free(unsafe.Pointer(cfont))
		cfamily := C.DrawGetFontFamily(self.drawing)
		defer C.free(unsafe.Pointer(cfamily))
		csize := C.DrawGetFontSize(self.drawing)
		calignment := C.DrawGetTextAlignment(self.drawing)
		cantialias := C.DrawGetTextAntialias(self.drawing)
		antialias := false
		if cantialias == C.MagickTrue {
			antialias = true
		}

		underColor :=C.NewPixelWand()
		C.DrawGetTextUnderColor(self.drawing, underColor)
		return &TextProperties{
			Font: C.GoString(cfont),
			Family: C.GoString(cfamily),
			Size: float64(csize),
			Alignment: Alignment(calignment),
			Antialias: antialias,
			UnderColor: underColor,
		}
	}
	return &TextProperties{
		UnderColor: C.NewPixelWand(),
	}
}

// Sets canvas' default TextProperties
func (self *Canvas) SetTextProperties(def *TextProperties) {
	if def != nil {
		self.text = def
		self.SetFont(def.Font, def.Size)
		self.SetFontFamily(def.Family)
		self.SetTextAlignment(def.Alignment)
		self.SetTextAntialias(def.Antialias)
	}
}

// Gets a copy of canvas' current TextProperties
func (self *Canvas) TextProperties() *TextProperties {
	if self.text == nil {
		return nil
	}
	cpy := *self.text
	return &cpy
}

// Sets canvas' default font name
func (self *Canvas) SetFontName(font string) {
	self.text.Font = font
	cfont := C.CString(font)
	defer C.free(unsafe.Pointer(cfont))
	C.DrawSetFont(self.drawing, cfont)
}

// Returns canvas' current font name
func (self *Canvas) FontName() string {
	return self.text.Font
}

// Sets canvas' default font family
func (self *Canvas) SetFontFamily(family string) {
	self.text.Family = family
	cfamily := C.CString(family)
	defer C.free(unsafe.Pointer(cfamily))
	C.DrawSetFontFamily(self.drawing, cfamily)
}

// Returns canvas' current font family
func (self *Canvas) FontFamily() string {
	return self.text.Family
}

// Sets canvas' default font size
func (self *Canvas) SetFontSize(size float64) {
	self.text.Size = size
	C.DrawSetFontSize(self.drawing, C.double(size))
}

// Returns canvas' current font size
func (self *Canvas) FontSize() float64 {
	return self.text.Size
}

// Sets canvas' font name and size.
// If font is 0-length, the current font family is not changed
// If size is <= 0, the current font size is not changed
func (self *Canvas) SetFont(font string, size float64) {
	if len(font) > 0 {
		self.SetFontName(font)
	}
	if size > 0 {
		self.SetFontSize(size)
	}
}

// Returns canvas' current font name and size
func (self *Canvas) Font() (string, float64) {
	return self.text.Font, self.text.Size
}

// Sets canvas' default text alignment. Available values are:
// UndefinedAlign (?), LeftAlign, CenterAlign, RightAlign
func (self *Canvas) SetTextAlignment(a Alignment) {
	self.text.Alignment = a
	C.DrawSetTextAlignment(self.drawing, C.AlignType(a))
}

// Returns the canvas' current text aligment
func (self *Canvas) TextAlignment() Alignment {
	return self.text.Alignment
}

// Sets canvas' default text antialiasing option.
func (self *Canvas) SetTextAntialias(b bool) {
	self.text.Antialias = b
	C.DrawSetTextAntialias(self.drawing, magickBoolean(b))
}

// Returns the canvas' current text aligment
func (self *Canvas) TextAntialias() bool {
	return self.text.Antialias
}

// Draws a string at the specified coordinates and using the current canvas
// Alignment.
func (self *Canvas) Annotate(text string, x, y float64) {
	c_text := C.CString(text)
	defer C.free(unsafe.Pointer(c_text))
	C.DrawAnnotation(self.drawing, C.double(x), C.double(y), (*C.uchar)(unsafe.Pointer(c_text)))
}

// Draws a string at the specified coordinates and using the specified Text Properties
// Does not modify the canvas' default TextProperties
func (self *Canvas) AnnotateWithProperties(text string, x, y float64, prop *TextProperties) {
	if prop != nil {
		tmp := self.TextProperties()
		self.SetTextProperties(prop)
		self.Annotate(text, x, y)
		self.SetTextProperties(tmp)
	} else {
		self.Annotate(text, x, y)
	}
}
<commit_msg>Add Weight and Kerning -> Kerning works fine -> Weight does not seem to have any effect on default fonts<commit_after>/* text.go contains functions for text annotation
TODO:
getters and setters for
- Stretch (redefine C type)
- Style (redefine C type)
- Resolution (two doubles)
- Decoration (redefine C type)
- Encoding (string)
-  DrawSetTextInterlineSpacing(DrawingWand *,const double),
-  DrawSetTextInterwordSpacing(DrawingWand *,const double),
-  DrawSetGravity(DrawingWand *wand,const GravityType gravity)
*/

package canvas

/*
#cgo CFLAGS: -fopenmp -I./_include
#cgo LDFLAGS: -lMagickWand -lMagickCore

#include <wand/magick_wand.h>
*/
import "C"

import (
	"unsafe"
)

type Alignment uint

const (
 	UndefinedAlign Alignment	= Alignment(C.UndefinedAlign)
	LeftAlign					= Alignment(C.LeftAlign)
	CenterAlign					= Alignment(C.CenterAlign)
	RightAlign					= Alignment(C.RightAlign)
)

// structure containing all text properties for an annotation
// except the colors that are defined by FillColor and StrokeColor
type TextProperties struct {
	Font		string
	Family		string
	Size		float64
	// Stretch		C.StretchType
	Weight		uint
	// Style		C.StyleType
	// Resolution  [2]C.double
	Alignment	Alignment
	Antialias	bool
	// Decoration	C.DecorationType
	// Encoding	string
	Kerning		float64
	// Interline	float64
	// Interword	float64
	UnderColor	*C.PixelWand
}

// Returns a TextProperties structure.
// Parameters:
//   read_default: if false, returns an empty structure.
//				   if true, returns a structure set with current canvas settings
func (self *Canvas) NewTextProperties(read_default bool) *TextProperties {
	if read_default == true {
		cfont := C.DrawGetFont(self.drawing)
		defer C.free(unsafe.Pointer(cfont))
		cfamily := C.DrawGetFontFamily(self.drawing)
		defer C.free(unsafe.Pointer(cfamily))
		csize := C.DrawGetFontSize(self.drawing)
		cweight := C.DrawGetFontWeight(self.drawing)
		calignment := C.DrawGetTextAlignment(self.drawing)
		cantialias := C.DrawGetTextAntialias(self.drawing)
		ckerning := C.DrawGetTextKerning(self.drawing)
		antialias := false
		if cantialias == C.MagickTrue {
			antialias = true
		}

		underColor :=C.NewPixelWand()
		C.DrawGetTextUnderColor(self.drawing, underColor)
		return &TextProperties{
			Font: C.GoString(cfont),
			Family: C.GoString(cfamily),
			Size: float64(csize),
			Weight: uint(cweight),
			Alignment: Alignment(calignment),
			Antialias: antialias,
			Kerning: float64(ckerning),
			UnderColor: underColor,
		}
	}
	return &TextProperties{
		UnderColor: C.NewPixelWand(),
	}
}

// Sets canvas' default TextProperties
func (self *Canvas) SetTextProperties(def *TextProperties) {
	if def != nil {
		self.text = def
		self.SetFontFamily(def.Family)
		self.SetFont(def.Font, def.Size)
		self.SetFontWeight(def.Weight)
		self.SetTextAlignment(def.Alignment)
		self.SetTextAntialias(def.Antialias)
		self.SetTextKerning(def.Kerning)
	}
}

// Gets a copy of canvas' current TextProperties
func (self *Canvas) TextProperties() *TextProperties {
	if self.text == nil {
		return nil
	}
	cpy := *self.text
	return &cpy
}

// Sets canvas' default font name
func (self *Canvas) SetFontName(font string) {
	self.text.Font = font
	cfont := C.CString(font)
	defer C.free(unsafe.Pointer(cfont))
	C.DrawSetFont(self.drawing, cfont)
}

// Returns canvas' current font name
func (self *Canvas) FontName() string {
	return self.text.Font
}

// Sets canvas' default font family
func (self *Canvas) SetFontFamily(family string) {
	self.text.Family = family
	cfamily := C.CString(family)
	defer C.free(unsafe.Pointer(cfamily))
	C.DrawSetFontFamily(self.drawing, cfamily)
}

// Returns canvas' current font family
func (self *Canvas) FontFamily() string {
	return self.text.Family
}

// Sets canvas' default font size
func (self *Canvas) SetFontSize(size float64) {
	self.text.Size = size
	C.DrawSetFontSize(self.drawing, C.double(size))
}

// Returns canvas' current font size
func (self *Canvas) FontSize() float64 {
	return self.text.Size
}

// Sets canvas' default font weight
func (self *Canvas) SetFontWeight(weight uint) {
	self.text.Weight = weight
	C.DrawSetFontWeight(self.drawing, C.size_t(weight))
}

// Returns canvas' current font weight
func (self *Canvas) FontWeight() uint {
	return self.text.Weight
}

// Sets canvas' font name and size.
// If font is 0-length, the current font family is not changed
// If size is <= 0, the current font size is not changed
func (self *Canvas) SetFont(font string, size float64) {
	if len(font) > 0 {
		self.SetFontName(font)
	}
	if size > 0 {
		self.SetFontSize(size)
	}
}

// Returns canvas' current font name and size
func (self *Canvas) Font() (string, float64) {
	return self.text.Font, self.text.Size
}

// Sets canvas' default text alignment. Available values are:
// UndefinedAlign (?), LeftAlign, CenterAlign, RightAlign
func (self *Canvas) SetTextAlignment(a Alignment) {
	self.text.Alignment = a
	C.DrawSetTextAlignment(self.drawing, C.AlignType(a))
}

// Returns the canvas' current text aligment
func (self *Canvas) TextAlignment() Alignment {
	return self.text.Alignment
}

// Sets canvas' default text antialiasing option.
func (self *Canvas) SetTextAntialias(b bool) {
	self.text.Antialias = b
	C.DrawSetTextAntialias(self.drawing, magickBoolean(b))
}

// Returns the canvas' current text aligment
func (self *Canvas) TextAntialias() bool {
	return self.text.Antialias
}

// Sets canvas' default text antialiasing option.
func (self *Canvas) SetTextKerning(k float64) {
	self.text.Kerning = k
	C.DrawSetTextKerning(self.drawing, C.double(k))
}

// Returns the canvas' current text aligment
func (self *Canvas) TextKerning() float64 {
	return self.text.Kerning
}

// Draws a string at the specified coordinates and using the current canvas
// Alignment.
func (self *Canvas) Annotate(text string, x, y float64) {
	c_text := C.CString(text)
	defer C.free(unsafe.Pointer(c_text))
	C.DrawAnnotation(self.drawing, C.double(x), C.double(y), (*C.uchar)(unsafe.Pointer(c_text)))
}

// Draws a string at the specified coordinates and using the specified Text Properties
// Does not modify the canvas' default TextProperties
func (self *Canvas) AnnotateWithProperties(text string, x, y float64, prop *TextProperties) {
	if prop != nil {
		tmp := self.TextProperties()
		self.SetTextProperties(prop)
		self.Annotate(text, x, y)
		self.SetTextProperties(tmp)
	} else {
		self.Annotate(text, x, y)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"regexp"
	"strings"

	"golang.org/x/text/width"

	log "github.com/Sirupsen/logrus"
	"github.com/davecgh/go-spew/spew"
)

func getOnlyText(text string) string {
	items, err := parseText(text)
	if err != nil {
		log.Errorf("%s\ntext: %q", err, text)
		log.Error(spew.Sdump(items))

		panic(err)
	}

	var out string

	for _, item := range items {
		switch item.typ {
		case itemText, itemRawString:
			out += item.val
		}
	}

	return out
}

func shouldBreakLines(contexts []string) bool {
	for _, c := range contexts {
		if engine == engineRPGMVX {
			if strings.Contains(c, "GameINI/Title") || strings.Contains(c, "System/game_title/") {
				return false
			}
		} else if engine == engineWolf {
			if strings.HasSuffix(c, "Title") {
				return false
			}
		}
	}

	return true
}

func breakLines(text string) string {
	var out string

	lines := strings.Split(text, "\n")

	for n, l := range lines {
		out += breakLine(l)

		// Add newline if it's not the last line
		if n+1 < len(lines) {
			out += "\n"

		}
	}

	return out
}

func breakLine(text string) string {
	// Remove any extra trailing new lines
	text = strings.TrimRight(text, "\n")
	if len(text) < 1 {
		return text
	}

	items, err := parseText(text)
	if err != nil {
		log.Errorf("%s\ntext: %q", err, text)
		log.Error(spew.Sdump(items))

		panic(err)
	}

	log.Debug(spew.Sdump(items))

	var out string
	var justText string
	var line string

	var lineLength int

	if engine == engineRPGMVX {
		lineLength = 42
	} else if engine == engineWolf {
		lineLength = 62
	}

	for _, item := range items {
		switch item.typ {
		case itemText, itemRawString:
			if len([]rune(justText+item.val)) <= lineLength {
				out += line
				out += item.val

				line = ""
				justText += item.val

				break
			}

			log.Debugf("Trying to split %q from %q", item.val, text)

			words := strings.Split(item.val, " ")
			for i := range words {
				log.Debug("word: ", i+1, " / ", len(words), " len:", len([]rune(justText+words[i])))

				if len([]rune(justText+words[i])) <= lineLength {
					log.Debugf("adding %q", words[i])

					line += words[i]
					justText += words[i]

					if i+1 < len(words) {
						line += " "
						justText += " "
					}

					continue
				}

				log.Debugf("Word %q was too long to fit! Added a new line before it", words[i])

				line = strings.TrimRight(line, " ")
				line += "\n"

				out += line

				line = words[i]
				justText = words[i]

				if i+1 < len(words) {
					line += " "
					justText += " "
				}
			}
		default:
			out += item.val
		}
	}

	if len(line) > 0 {
		log.Debugf("Split! Trailing %q from %q", line, text)

		out += line
	}

	return out
}

func shouldTranslateText(text string) bool {
	text = strings.TrimSpace(text)

	if len(text) < 1 {
		return false
	}

	text = width.Narrow.String(text)

	return isJapanese(text)
}

func isJapanese(text string) bool {
	regex := regexp.MustCompile(`(\p{Hiragana}|\p{Katakana}|\p{Han})`)
	matches := regex.FindAllString(text, 1)

	return len(matches) >= 1
}

func replaceRegex(text string, expr string, repl string) string {
	regex := regexp.MustCompile(expr)

	return regex.ReplaceAllString(text, repl)
}

func unescapeText(text string) string {
	text = strings.Replace(text, `\\`, `\`, -1)

	text = strings.Replace(text, `\>`, `>`, -1)
	text = strings.Replace(text, `\#`, `#`, -1)

	return text
}

func escapeText(text string) string {
	text = strings.Replace(text, `\`, `\\`, -1)

	text = strings.Replace(text, `>`, `\>`, -1)
	text = strings.Replace(text, `#`, `\#`, -1)

	return text
}
<commit_msg>Use 42 character line length by default<commit_after>package main

import (
	"regexp"
	"strings"

	"golang.org/x/text/width"

	log "github.com/Sirupsen/logrus"
	"github.com/davecgh/go-spew/spew"
)

func getOnlyText(text string) string {
	items, err := parseText(text)
	if err != nil {
		log.Errorf("%s\ntext: %q", err, text)
		log.Error(spew.Sdump(items))

		panic(err)
	}

	var out string

	for _, item := range items {
		switch item.typ {
		case itemText, itemRawString:
			out += item.val
		}
	}

	return out
}

func shouldBreakLines(contexts []string) bool {
	for _, c := range contexts {
		if engine == engineRPGMVX {
			if strings.Contains(c, "GameINI/Title") || strings.Contains(c, "System/game_title/") {
				return false
			}
		} else if engine == engineWolf {
			if strings.HasSuffix(c, "Title") {
				return false
			}
		}
	}

	return true
}

func breakLines(text string) string {
	var out string

	lines := strings.Split(text, "\n")

	for n, l := range lines {
		out += breakLine(l)

		// Add newline if it's not the last line
		if n+1 < len(lines) {
			out += "\n"

		}
	}

	return out
}

func breakLine(text string) string {
	// Remove any extra trailing new lines
	text = strings.TrimRight(text, "\n")
	if len(text) < 1 {
		return text
	}

	items, err := parseText(text)
	if err != nil {
		log.Errorf("%s\ntext: %q", err, text)
		log.Error(spew.Sdump(items))

		panic(err)
	}

	log.Debug(spew.Sdump(items))

	var out string
	var justText string
	var line string

	var lineLength int

	switch engine {
	case engineWolf:
		lineLength = 64
	default:
		lineLength = 42
	}

	for _, item := range items {
		switch item.typ {
		case itemText, itemRawString:
			if len([]rune(justText+item.val)) <= lineLength {
				out += line
				out += item.val

				line = ""
				justText += item.val

				break
			}

			log.Debugf("Trying to split %q from %q", item.val, text)

			words := strings.Split(item.val, " ")
			for i := range words {
				log.Debug("word: ", i+1, " / ", len(words), " len:", len([]rune(justText+words[i])))

				if len([]rune(justText+words[i])) <= lineLength {
					log.Debugf("adding %q", words[i])

					line += words[i]
					justText += words[i]

					if i+1 < len(words) {
						line += " "
						justText += " "
					}

					continue
				}

				log.Debugf("Word %q was too long to fit! Added a new line before it", words[i])

				line = strings.TrimRight(line, " ")
				line += "\n"

				out += line

				line = words[i]
				justText = words[i]

				if i+1 < len(words) {
					line += " "
					justText += " "
				}
			}
		default:
			out += item.val
		}
	}

	if len(line) > 0 {
		log.Debugf("Split! Trailing %q from %q", line, text)

		out += line
	}

	return out
}

func shouldTranslateText(text string) bool {
	text = strings.TrimSpace(text)

	if len(text) < 1 {
		return false
	}

	text = width.Narrow.String(text)

	return isJapanese(text)
}

func isJapanese(text string) bool {
	regex := regexp.MustCompile(`(\p{Hiragana}|\p{Katakana}|\p{Han})`)
	matches := regex.FindAllString(text, 1)

	return len(matches) >= 1
}

func replaceRegex(text string, expr string, repl string) string {
	regex := regexp.MustCompile(expr)

	return regex.ReplaceAllString(text, repl)
}

func unescapeText(text string) string {
	text = strings.Replace(text, `\\`, `\`, -1)

	text = strings.Replace(text, `\>`, `>`, -1)
	text = strings.Replace(text, `\#`, `#`, -1)

	return text
}

func escapeText(text string) string {
	text = strings.Replace(text, `\`, `\\`, -1)

	text = strings.Replace(text, `>`, `\>`, -1)
	text = strings.Replace(text, `#`, `\#`, -1)

	return text
}
<|endoftext|>


<commit_before>package toot

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"strings"
)

type Account struct {
	Id             string `json:"id"`
	Username       string `json:"username"`
	Acct           string `json:"acct"`
	DisplayName    string `json:"display_name"`
	Note           string `json:"note"`
	Url            string `json:"url"`
	Avatar         string `json:"avatar"`
	Header         string `json:"header"`
	Locked         bool   `json:"locked"`
	CreatedAt      string `json:"created_at"`
	FollowersCount int    `json:"followers_count"`
	FollowingCount int    `json:"following_count"`
	StatusesCount  int    `json:"statuses_count"`
}

type Scope struct {
	Read   bool
	Write  bool
	Follow bool
}

func (s Scope) String() string {
	scopes := []string{}

	if s.Read {
		scopes = append(scopes, "read")
	}

	if s.Write {
		scopes = append(scopes, "write")
	}

	if s.Follow {
		scopes = append(scopes, "follow")
	}

	return strings.Join(scopes, " ")
}

type AppRequest struct {
	ClientName   string `json:"client_name"`
	Scopes       string `json:"scopes"`
	RedirectUris string `json:"redirect_uris"`
}

type App struct {
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

func NewApp(name string, scope Scope) App {
	url := "https://cmpwn.com/api/v1/apps"

	r := AppRequest{
		ClientName:   name,
		Scopes:       scope.String(),
		RedirectUris: "urn:ietf:wg:oauth:2.0:oob",
	}

	jsonStr, _ := json.Marshal(r)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	app := App{}
	json.Unmarshal(body, &app)

	return app
}
<commit_msg>Make appRequest private<commit_after>package toot

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"strings"
)

type Account struct {
	Id             string `json:"id"`
	Username       string `json:"username"`
	Acct           string `json:"acct"`
	DisplayName    string `json:"display_name"`
	Note           string `json:"note"`
	Url            string `json:"url"`
	Avatar         string `json:"avatar"`
	Header         string `json:"header"`
	Locked         bool   `json:"locked"`
	CreatedAt      string `json:"created_at"`
	FollowersCount int    `json:"followers_count"`
	FollowingCount int    `json:"following_count"`
	StatusesCount  int    `json:"statuses_count"`
}

type Scope struct {
	Read   bool
	Write  bool
	Follow bool
}

func (s Scope) String() string {
	scopes := []string{}

	if s.Read {
		scopes = append(scopes, "read")
	}

	if s.Write {
		scopes = append(scopes, "write")
	}

	if s.Follow {
		scopes = append(scopes, "follow")
	}

	return strings.Join(scopes, " ")
}

type appRequest struct {
	ClientName   string `json:"client_name"`
	Scopes       string `json:"scopes"`
	RedirectUris string `json:"redirect_uris"`
}

type App struct {
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

func NewApp(name string, scope Scope) App {
	url := "https://cmpwn.com/api/v1/apps"

	r := appRequest{
		ClientName:   name,
		Scopes:       scope.String(),
		RedirectUris: "urn:ietf:wg:oauth:2.0:oob",
	}

	jsonStr, _ := json.Marshal(r)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	app := App{}
	json.Unmarshal(body, &app)

	return app
}
<|endoftext|>


<commit_before>package hush

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"sort"
	"strings"

	"github.com/pkg/errors"

	yaml "gopkg.in/yaml.v2"
)

type T map[Path]Value

func LoadTree() (T, error) {
	hushPath, err := hushPath()
	if err != nil {
		return nil, err
	}

	_, err = os.Stat(hushPath)
	if os.IsNotExist(err) {
		warn("hush file does not exist. assuming an empty one")
		return T{}, nil
	}
	if err != nil {
		return nil, errors.Wrap(err, "can't stat hush file")
	}

	file, err := os.Open(hushPath)
	if err != nil {
		return nil, errors.Wrap(err, "opening hush file")
	}
	hushData, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, errors.Wrap(err, "can't read hush file")
	}

	keys := make(yaml.MapSlice, 0)
	err = yaml.Unmarshal(hushData, &keys)
	if err != nil {
		return nil, errors.Wrap(err, "can't parse hush file")
	}
	tree := newT(keys)
	return tree, nil
}

func newT(items yaml.MapSlice) T {
	t := make(T, 3*len(items))
	newT_(items, []string{}, t)
	return t
}

func newT_(items yaml.MapSlice, crumbs []string, t T) {
	n := len(crumbs)
	for _, item := range items {
		key := item.Key.(string)
		crumbs = append(crumbs, key)

		switch val := item.Value.(type) {
		case string:
			p := NewPath(strings.Join(crumbs, "/"))
			t[p] = NewValue(val)
		case yaml.MapSlice:
			newT_(val, crumbs, t)
		default:
			panic(fmt.Sprintf("unexpected type: %#v", val))
		}
		crumbs = crumbs[:n] // remove final crumb
	}
}

func (t T) mapSlice() yaml.MapSlice {
	// sort by key
	kvs := make([][]string, 0, len(t))
	for p, val := range t {
		kvs = append(kvs, []string{string(p), string(val)})
	}
	sort.SliceStable(kvs, func(i, j int) bool {
		return kvs[i][0] < kvs[j][0]
	})

	var slice yaml.MapSlice
	for _, kv := range kvs {
		path := strings.Split(kv[0], "\t")
		slice = mapSlice_(slice, path, kv[1])
	}
	return slice
}

func mapSlice_(slice yaml.MapSlice, path []string, value string) yaml.MapSlice {
	if len(path) == 0 {
		panic("path should never have 0 length")
	}
	if len(path) == 1 {
		return append(slice, yaml.MapItem{
			Key:   path[0],
			Value: value,
		})
	}

	var inner yaml.MapSlice
	if len(slice) == 0 {
		slice = append(slice, yaml.MapItem{Key: path[0]})
	} else {
		final := slice[len(slice)-1]
		if final.Key.(string) == path[0] {
			inner = final.Value.(yaml.MapSlice)
		} else {
			slice = append(slice, yaml.MapItem{Key: path[0]})
		}
	}
	slice[len(slice)-1].Value = mapSlice_(inner, path[1:], value)
	return slice
}

func (t T) filter(pattern string) T {
	keep := make(T)
	for p, val := range t {
		if matches(p, pattern) {
			keep[p] = val
		}
	}
	return keep
}

func isLowercase(s string) bool {
	return s == strings.ToLower(s)
}

func matches(p Path, pattern string) bool {
	ps := strings.Split(string(p), "\t")
	patterns := strings.Split(pattern, "/")
	if len(patterns) > len(ps) {
		return false
	}

	ignoreCase := isLowercase(pattern)
	for i, pattern := range patterns {
		haystack := ps[i]
		if ignoreCase {
			haystack = strings.ToLower(haystack)
		}
		if !strings.Contains(haystack, pattern) {
			return false
		}
	}
	return true
}

func (t T) get(p Path) (Value, bool) {
	val, ok := t[p]
	return val, ok
}

func (t T) set(p Path, val Value) {
	t[p] = val.Ciphertext(encryptionKey)
}

func (tree T) encrypt() {
	for p, v := range tree {
		tree[p] = v.Ciphertext(encryptionKey)
	}
}

var encryptionKey = []byte(`0123456789abcdef`)

// Decrypt returns a copy of this tree with all leaves decrypted.
func (tree T) Decrypt() T {
	t := make(T, len(tree))
	for p, v := range tree {
		t[p] = v.Plaintext(encryptionKey)
	}
	return t
}

// Print displays a tree for human consumption.
func (tree T) Print(w io.Writer) error {
	tree = tree.Decrypt()
	slice := tree.mapSlice()
	data, err := yaml.Marshal(slice)
	if err != nil {
		return errors.Wrap(err, "printing tree")
	}

	_, err = w.Write(data)
	return err
}

// Save stores a tree to disk for permanent, private archival.
func (tree T) Save() error {
	slice := tree.mapSlice()

	data, err := yaml.Marshal(slice)
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}

	// save to temporary file
	file, err := ioutil.TempFile("", "hush-")
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	_, err = file.Write(data)
	file.Close()
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}

	// move temporary file over top of permanent file
	hushPath, err := hushPath()
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	err = os.Rename(file.Name(), hushPath)
	return errors.Wrap(err, "saving tree")
}
<commit_msg>Safe permissions for hush file<commit_after>package hush

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"sort"
	"strings"

	"github.com/pkg/errors"

	yaml "gopkg.in/yaml.v2"
)

type T map[Path]Value

const safePerm = 0600 // rw- --- ---

func LoadTree() (T, error) {
	hushPath, err := hushPath()
	if err != nil {
		return nil, err
	}

	stat, err := os.Stat(hushPath)
	if os.IsNotExist(err) {
		warn("hush file does not exist. assuming an empty one")
		return T{}, nil
	}
	if err != nil {
		return nil, errors.Wrap(err, "can't stat hush file")
	}
	if (stat.Mode() & os.ModePerm) != safePerm {
		warn("hush file has loose permissions. fixing.")
		err := os.Chmod(hushPath, safePerm)
		if err != nil {
			die("couldn't fix permissions on hush file")
		}
	}

	file, err := os.Open(hushPath)
	if err != nil {
		return nil, errors.Wrap(err, "opening hush file")
	}
	hushData, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, errors.Wrap(err, "can't read hush file")
	}

	keys := make(yaml.MapSlice, 0)
	err = yaml.Unmarshal(hushData, &keys)
	if err != nil {
		return nil, errors.Wrap(err, "can't parse hush file")
	}
	tree := newT(keys)
	return tree, nil
}

func newT(items yaml.MapSlice) T {
	t := make(T, 3*len(items))
	newT_(items, []string{}, t)
	return t
}

func newT_(items yaml.MapSlice, crumbs []string, t T) {
	n := len(crumbs)
	for _, item := range items {
		key := item.Key.(string)
		crumbs = append(crumbs, key)

		switch val := item.Value.(type) {
		case string:
			p := NewPath(strings.Join(crumbs, "/"))
			t[p] = NewValue(val)
		case yaml.MapSlice:
			newT_(val, crumbs, t)
		default:
			panic(fmt.Sprintf("unexpected type: %#v", val))
		}
		crumbs = crumbs[:n] // remove final crumb
	}
}

func (t T) mapSlice() yaml.MapSlice {
	// sort by key
	kvs := make([][]string, 0, len(t))
	for p, val := range t {
		kvs = append(kvs, []string{string(p), string(val)})
	}
	sort.SliceStable(kvs, func(i, j int) bool {
		return kvs[i][0] < kvs[j][0]
	})

	var slice yaml.MapSlice
	for _, kv := range kvs {
		path := strings.Split(kv[0], "\t")
		slice = mapSlice_(slice, path, kv[1])
	}
	return slice
}

func mapSlice_(slice yaml.MapSlice, path []string, value string) yaml.MapSlice {
	if len(path) == 0 {
		panic("path should never have 0 length")
	}
	if len(path) == 1 {
		return append(slice, yaml.MapItem{
			Key:   path[0],
			Value: value,
		})
	}

	var inner yaml.MapSlice
	if len(slice) == 0 {
		slice = append(slice, yaml.MapItem{Key: path[0]})
	} else {
		final := slice[len(slice)-1]
		if final.Key.(string) == path[0] {
			inner = final.Value.(yaml.MapSlice)
		} else {
			slice = append(slice, yaml.MapItem{Key: path[0]})
		}
	}
	slice[len(slice)-1].Value = mapSlice_(inner, path[1:], value)
	return slice
}

func (t T) filter(pattern string) T {
	keep := make(T)
	for p, val := range t {
		if matches(p, pattern) {
			keep[p] = val
		}
	}
	return keep
}

func isLowercase(s string) bool {
	return s == strings.ToLower(s)
}

func matches(p Path, pattern string) bool {
	ps := strings.Split(string(p), "\t")
	patterns := strings.Split(pattern, "/")
	if len(patterns) > len(ps) {
		return false
	}

	ignoreCase := isLowercase(pattern)
	for i, pattern := range patterns {
		haystack := ps[i]
		if ignoreCase {
			haystack = strings.ToLower(haystack)
		}
		if !strings.Contains(haystack, pattern) {
			return false
		}
	}
	return true
}

func (t T) get(p Path) (Value, bool) {
	val, ok := t[p]
	return val, ok
}

func (t T) set(p Path, val Value) {
	t[p] = val.Ciphertext(encryptionKey)
}

func (tree T) encrypt() {
	for p, v := range tree {
		tree[p] = v.Ciphertext(encryptionKey)
	}
}

var encryptionKey = []byte(`0123456789abcdef`)

// Decrypt returns a copy of this tree with all leaves decrypted.
func (tree T) Decrypt() T {
	t := make(T, len(tree))
	for p, v := range tree {
		t[p] = v.Plaintext(encryptionKey)
	}
	return t
}

// Print displays a tree for human consumption.
func (tree T) Print(w io.Writer) error {
	tree = tree.Decrypt()
	slice := tree.mapSlice()
	data, err := yaml.Marshal(slice)
	if err != nil {
		return errors.Wrap(err, "printing tree")
	}

	_, err = w.Write(data)
	return err
}

// Save stores a tree to disk for permanent, private archival.
func (tree T) Save() error {
	slice := tree.mapSlice()

	data, err := yaml.Marshal(slice)
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}

	// save to temporary file
	file, err := ioutil.TempFile("", "hush-")
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	err = os.Chmod(file.Name(), safePerm)
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	_, err = file.Write(data)
	file.Close()
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}

	// move temporary file over top of permanent file
	hushPath, err := hushPath()
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	err = os.Rename(file.Name(), hushPath)
	return errors.Wrap(err, "saving tree")
}
<|endoftext|>


<commit_before>// -*- tab-width: 4; -*-

package main

import (
	//	"strings"
	//	"log"
	"bufio"
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
	"time"

	"net/http"

	"github.com/fatih/color"
)

// TODO when add/removing following in config, what happens?
// TODO require url (and nick...)  unique in following?
// TODO panics etc

var conf Config

type HttpResponse struct {
	url      string
	response *http.Response
	err      error
}

/////////////
/////////////
/////////////
func asyncHttpGets(urls []string) []*HttpResponse {
	ch := make(chan *HttpResponse)
	responses := []*HttpResponse{}
	client := http.Client{}
	for _, url := range urls {
		go func(url string) {
			fmt.Printf("Fetching %s \n", url)
			resp, err := client.Get(url)
			ch <- &HttpResponse{url, resp, err}
			if err != nil && resp != nil && resp.StatusCode == http.StatusOK {
				resp.Body.Close()
			}
		}(url)
	}

	for {
		select {
		case r := <-ch:
			fmt.Printf("%s was fetched\n", r.url)
			if r.err != nil {
				fmt.Println("with an error", r.err)
			}
			responses = append(responses, r)
			if len(responses) == len(urls) {
				return responses
			}
		case <-time.After(50 * time.Millisecond):
			fmt.Printf(".")
		}
	}
	///////return responses
}

/////////////
/////////////
/////////////

// for _, httpresp := range asyncHttpGets(urls) {
// 	if httpresp != nil && httpresp.response != nil {
// 		fmt.Printf("%s status: %s\n", httpresp.url,
// 			httpresp.response.Status)
// 		fmt.Printf("\n'%s'\n", httpresp.response.Body)
// 	}
// }

/////////////
/////////////
/////////////

type Tweeter struct {
	Nick string
	URL  string
}

type Tweet struct {
	Tweeter Tweeter
	Created time.Time
	Text    string
}

type Tweets []Tweet

func (tweets Tweets) Len() int {
	return len(tweets)
}
func (tweets Tweets) Less(i, j int) bool {
	return tweets[i].Created.Before(tweets[j].Created)
}
func (tweets Tweets) Swap(i, j int) {
	tweets[i], tweets[j] = tweets[j], tweets[i]
}

func pretty_duration(duration time.Duration) string {
	if duration.Hours() > 24 {
		return fmt.Sprintf("%d days ago", int(duration.Hours())/24)
	}
	if duration.Minutes() > 60 {
		return fmt.Sprintf("%d hours ago", int(duration.Hours()))
	}
	if duration.Seconds() > 60 {
		return fmt.Sprintf("%d minutes ago", int(duration.Minutes()))
	}
	return fmt.Sprintf("%d seconds ago", int(duration.Seconds()))
}

func formatmention(mentioned Tweeter, followednick string) string {
	str := "@" + mentioned.Nick
	if followednick != mentioned.Nick {
		str = str + fmt.Sprintf("(%s)", followednick)
	}
	coloring := color.New(color.Bold).SprintFunc()
	if mentioned.URL == conf.Twturl {
		coloring = color.New(color.FgBlue).SprintFunc()
	}
	return coloring(str)
}

func printtweet(tweet Tweet, now time.Time) {
	// twtxt regexen:
	// mention_re = re.compile(r'@<(?:(?P<name>\S+?)\s+)?(?P<url>\S+?://.*?)>')
	// short_mention_re = re.compile(r'@(?P<name>\w+)')
	// TODO: optional <name> in re?
	re := regexp.MustCompile(`@<([^ ]+) ([^>]+)>`)
	text := re.ReplaceAllStringFunc(tweet.Text,
		func(match string) string {
			parts := re.FindStringSubmatch(match)
			mentioned := Tweeter{Nick: parts[1], URL: parts[2]}

			for followednick, followedurl := range conf.Following {
				if mentioned.URL == followedurl {
					return formatmention(mentioned, followednick)
				}
			}
			if mentioned.URL == conf.Twturl {
				return formatmention(mentioned, conf.Nick)
			}
			return fmt.Sprintf("@<%s %s>", mentioned.Nick, mentioned.URL)
		})

	// color even on non-tty
	color.NoColor = false

	underline := color.New(color.Underline).SprintFunc()
	fmt.Printf("> %s (%s)\n  %s\n",
		underline(tweet.Tweeter.Nick),
		pretty_duration(now.Sub(tweet.Created)),
		text)
}

func parsetime(timestr string) time.Time {
	var tm time.Time
	var err error
	for _, layout := range []string{
		"2006-01-02T15:04:05.999999999Z07:00",
		"2006-01-02T15:04:05.999999999Z0700",
		"2006-01-02T15:04:05.999999999",
	} {
		tm, err = time.Parse(layout, timestr)
		if err != nil {
			continue
		} else {
			break
		}
	}
	if err != nil {
		return time.Unix(0, 0)
	}
	return tm.Round(time.Second)
}

func gettweets(scanner *bufio.Scanner, tweeter Tweeter) Tweets {
	var tweets Tweets
	i := 0
	for scanner.Scan() {
		i += 1
		a := strings.SplitN(scanner.Text(), "\t", 2)
		if len(a) != 2 {
			fmt.Fprintf(os.Stderr, color.RedString("could not parse: ", scanner.Text()))
		} else {
			tweets = append(tweets, Tweet{tweeter, parsetime(a[0]), a[1]})
		}
	}
	return tweets
}

// TODO... FIXME
var cacheonly bool = true

func main() {
	configpath := conf.Read()

	// TODO let cache have func too? or have conf not have...eh
	cache := Load(configpath)

	var alltweets Tweets

	client := http.Client{}

	for nick, url := range conf.Following {
		fmt.Fprintf(os.Stderr, "* fetching %s %s", nick, url)

		if cacheonly == false {
			req, err := http.NewRequest("GET", url, nil)
			if err != nil {
				panic(err)
			}

			if cached, ok := cache[url]; ok {
				// makes sense yeah? some gave us 200 before, but no last-modifed...
				if cached.Lastmodified != "" {
					req.Header.Set("If-Modified-Since", cached.Lastmodified)
					fmt.Fprintf(os.Stderr, " [I-M-S:'%s']", cached.Lastmodified)
				}
			}

			/////////////
			// dump, _ := httputil.DumpRequestOut(req, true)
			// fmt.Fprintf(os.Stderr, "\n%q\n", dump)

			resp, err := client.Do(req)
			if err != nil {
				panic(err)
			}

			/////////////
			// dump, _ = httputil.DumpResponse(resp, true)
			// fmt.Fprintf(os.Stderr, "%q\n", dump)

			// TODO handle redirect?
			actualurl := resp.Request.URL.String()
			if actualurl != url {
				fmt.Fprintf(os.Stderr, "\n * %s->%s\n", url, actualurl)
				url = actualurl
			}

			lastmodified := resp.Header.Get("Last-Modified")
			fmt.Fprintf(os.Stderr, " _%d_ | L-M:'%s'", resp.StatusCode, lastmodified)

			fmt.Fprintf(os.Stderr, "\n")

			var tweets Tweets

			// TODO, stuff and 401, 301? 404? ...
			switch resp.StatusCode {
			case 200:
				scanner := bufio.NewScanner(resp.Body)
				tweets = gettweets(scanner, Tweeter{Nick: nick, URL: url})
				cache[url] = Cached{Tweets: tweets, Lastmodified: lastmodified}
			case 304:
				tweets = cache[url].Tweets
			}

			//TODO defer?
			resp.Body.Close()

			alltweets = append(alltweets, tweets...)
		} else {
			var tweets Tweets
			if cached, ok := cache[url]; ok {
				tweets = cached.Tweets
			}
			alltweets = append(alltweets, tweets...)
		}
	}

	sort.Sort(alltweets)

	// TODO: limit from commandline
	for _, tweet := range alltweets {
		printtweet(tweet, time.Now().Round(time.Second))
	}

	cache.Store(configpath)

	os.Exit(0)

	return
}
<commit_msg>refactoring<commit_after>// -*- tab-width: 4; -*-

package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
	"time"

	"net/http"

	"github.com/fatih/color"
)

// TODO when add/removing following in config, what happens?
// TODO require url (and nick...)  unique in following?
// TODO panics etc

var conf Config

type Tweeter struct {
	Nick string
	URL  string
}
type Tweet struct {
	Tweeter Tweeter
	Created time.Time
	Text    string
}

// typedef to be able to attach sort methods
type Tweets []Tweet

func (tweets Tweets) Len() int {
	return len(tweets)
}
func (tweets Tweets) Less(i, j int) bool {
	return tweets[i].Created.Before(tweets[j].Created)
}
func (tweets Tweets) Swap(i, j int) {
	tweets[i], tweets[j] = tweets[j], tweets[i]
}

func get_tweets(scanner *bufio.Scanner, tweeter Tweeter) Tweets {
	var tweets Tweets
	i := 0
	for scanner.Scan() {
		i += 1
		a := strings.SplitN(scanner.Text(), "\t", 2)
		if len(a) != 2 {
			fmt.Fprintf(os.Stderr,
				color.RedString("could not parse: ", scanner.Text()))
		} else {
			tweet := Tweet{
				Tweeter: tweeter,
				Created: parsetime(a[0]),
				Text:    a[1],
			}
			tweets = append(tweets, tweet)
		}
	}
	return tweets
}

func parsetime(timestr string) time.Time {
	var tm time.Time
	var err error
	// Twtxt clients generally uses basically time.RFC3339Nano, but sometimes
	// there's a colon in the timezone, or no timezone at all.
	for _, layout := range []string{
		"2006-01-02T15:04:05.999999999Z07:00",
		"2006-01-02T15:04:05.999999999Z0700",
		"2006-01-02T15:04:05.999999999",
	} {
		tm, err = time.Parse(layout, timestr)
		if err != nil {
			continue
		} else {
			break
		}
	}
	if err != nil {
		return time.Unix(0, 0)
	}
	return tm.Round(time.Second)
}

func print_tweet(tweet Tweet, now time.Time) {
	text := shorten_mentions(tweet.Text)

	underline := color.New(color.Underline).SprintFunc()
	fmt.Printf("> %s (%s)\n  %s\n",
		underline(tweet.Tweeter.Nick),
		pretty_duration(now.Sub(tweet.Created)),
		text)
}

func shorten_mentions(text string) string {
	// a mention: @<somenick url>
	re := regexp.MustCompile(`@<([^ ]+) ([^>]+)>`)
	return re.ReplaceAllStringFunc(text, func(match string) string {
		parts := re.FindStringSubmatch(match)
		mentioned := Tweeter{
			Nick: parts[1],
			URL:  parts[2],
		}

		for followednick, followedurl := range conf.Following {
			if mentioned.URL == followedurl {
				return format_mention(mentioned, followednick)
			}
		}
		// Maybe we got mentioned ourselves?
		if mentioned.URL == conf.Twturl {
			return format_mention(mentioned, conf.Nick)
		}
		// Couldn't
		return match
	})
}

// Takes followednick to be able to indicated when somebody (URL) was mentioned
// using a nick other than the one we follow the person as.
func format_mention(mentioned Tweeter, followednick string) string {
	str := "@" + mentioned.Nick
	if followednick != mentioned.Nick {
		str = str + fmt.Sprintf("(%s)", followednick)
	}
	coloring := color.New(color.Bold).SprintFunc()
	if mentioned.URL == conf.Twturl {
		coloring = color.New(color.FgBlue).SprintFunc()
	}
	return coloring(str)
}

func pretty_duration(duration time.Duration) string {
	if duration.Hours() > 24 {
		return fmt.Sprintf("%d days ago", int(duration.Hours())/24)
	}
	if duration.Minutes() > 60 {
		return fmt.Sprintf("%d hours ago", int(duration.Hours()))
	}
	if duration.Seconds() > 60 {
		return fmt.Sprintf("%d minutes ago", int(duration.Minutes()))
	}
	return fmt.Sprintf("%d seconds ago", int(duration.Seconds()))
}

// TODO... FIXME
var cacheonly bool = true

func main() {
	// color even on non-tty (less!)
	color.NoColor = false

	configpath := conf.Read()

	// TODO let cache have func too? or have conf not have...eh
	cache := Load(configpath)

	var alltweets Tweets

	client := http.Client{}

	// what the httpgetter needs: url, lastmod
	for nick, url := range conf.Following {
		if cacheonly == false {
			req, err := http.NewRequest("GET", url, nil)
			if err != nil {
				panic(err)
			}

			if cached, ok := cache[url]; ok {
				// makes sense yeah? some gave us 200 before, but no last-modifed...
				if cached.Lastmodified != "" {
					req.Header.Set("If-Modified-Since", cached.Lastmodified)
				}
			}

			resp, err := client.Do(req)
			if err != nil {
				panic(err)
			}

			// TODO handle redirect?
			actualurl := resp.Request.URL.String()
			if actualurl != url {
				url = actualurl
			}

			lastmodified := resp.Header.Get("Last-Modified")

			var tweets Tweets

			// TODO, stuff and 401, 301? 404? ...
			switch resp.StatusCode {
			case 200:
				scanner := bufio.NewScanner(resp.Body)
				tweets = get_tweets(scanner, Tweeter{
					Nick: nick,
					URL:  url,
				})
				cache[url] = Cached{
					Tweets:       tweets,
					Lastmodified: lastmodified,
				}
			case 304:
				tweets = cache[url].Tweets
			}

			//TODO defer?
			resp.Body.Close()

			alltweets = append(alltweets, tweets...)
		} else {
			var tweets Tweets
			if cached, ok := cache[url]; ok {
				tweets = cached.Tweets
			}
			alltweets = append(alltweets, tweets...)
		}
	}

	sort.Sort(alltweets)

	// TODO: limit from commandline
	for _, tweet := range alltweets {
		print_tweet(tweet, time.Now().Round(time.Second))
	}

	cache.Store(configpath)

	os.Exit(0)

	return
}
<|endoftext|>


<commit_before>// Package uman is a minimal user and session manager in Golang.
// Copyright (C) 2016 niemal
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
package uman

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"golang.org/x/crypto/bcrypt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"
)

// Session is a structure representing what its name implies.
//
type Session struct {
	User       string
	Timestamp  int64
	Lifespan   int64
	Cookie     string
	CookiePath string
}

const lifespan int64 = 3600

// Constructor of Session.
func createSession() *Session {
	return &Session{
		User:       "",
		CookiePath: "/",
		Lifespan:   lifespan,
		Timestamp:  time.Now().Unix(),
	}
}

// SetHTTPCookie sets the HTTP cookie given the http.ResponseWriter.
//
func (sess *Session) SetHTTPCookie(w http.ResponseWriter) {
	http.SetCookie(w, &http.Cookie{
		Name:     "session",
		Path:     sess.CookiePath,
		Value:    sess.Cookie,
		HttpOnly: true,
		Expires:  time.Unix(sess.Timestamp+sess.Lifespan, 0),
		MaxAge:   int(sess.Lifespan),
	})
}

// Logout logs the session out!
//
func (sess *Session) Logout() {
	sess.User = ""
}

// SetLifespan changes the lifespan of a Session.
// Mainly exists to handle the typecasting between int and int64.
//
func (sess *Session) SetLifespan(seconds int) {
	sess.Lifespan = int64(seconds)
}

// IsLogged checks if the Session holds a user.
//
func (sess *Session) IsLogged() bool {
	if sess.User != "" {
		return true
	}

	return false
}

// UserManager is the backbone, holding all the goods.
//
type UserManager struct {
	CheckDelay    int
	Debug         bool
	users         map[string][]byte
	sessions      map[string]*Session
	databasePath  string
	sessionsMutex bool
	usersMutex    bool
}

// New is the constructor of UserManager.
//
func New(dbPath string) *UserManager {
	um := &UserManager{
		Debug:         true,
		CheckDelay:    60,
		databasePath:  dbPath,
		sessions:      make(map[string]*Session),
		sessionsMutex: false,
		usersMutex:    false,
	}

	um.reload()
	go um.checkSessions()

	return um
}

// Handles checking for debug mode and if so prints text.
func (um *UserManager) debug(message string) {
	if um.Debug {
		fmt.Println("[UserManager]: " + message)
	}
}

// Locks the running thread to prevent race conditions.
// A boolean is provided as the responsible mutex. Usually it's
// um.sessionsMutex or um.usersMutex.
func (um *UserManager) lock(mutex *bool) {
	um.debug("Thread is locked.")

	for *mutex {
		time.Sleep(time.Duration(300) * time.Millisecond)
	}

	um.debug("Thread is unlocked.")
}

// Reloads the database into memory, filling the appropriate variables.
func (um *UserManager) reload() {
	um.lock(&um.usersMutex)
	um.usersMutex = true

	um.users = make(map[string][]byte) // reset

	data, err := ioutil.ReadFile(um.databasePath)
	um.check(err)

	accounts := strings.Split(string(data), "\n")
	for _, acc := range accounts {
		creds := strings.Split(acc, ":")

		if len(creds) != 2 {
			break
		}

		um.users[creds[0]] = []byte(creds[1])
	}

	um.usersMutex = false
}

// Checks for expired Sessions.
func (um *UserManager) checkSessions() {
	for {
		for hash, sess := range um.sessions {
			if sess.Timestamp+sess.Lifespan < time.Now().Unix() {
				um.lock(&um.sessionsMutex)
				um.sessionsMutex = true

				um.debug("Session[" + hash + "] has expired.")
				delete(um.sessions, hash)

				um.sessionsMutex = false
			}
		}

		time.Sleep(time.Duration(um.CheckDelay) * time.Second)
	}
}

// Hashes any given input using bcrypt.
func (um *UserManager) hash(this []byte) []byte {
	// cost: minimum is 4, max is 31, default is 10
	// (https://godoc.org/golang.org/x/crypto/bcrypt)
	cost := 10

	hash, err := bcrypt.GenerateFromPassword(this, cost)
	um.check(err)

	return hash
}

// CheckHash checks a hash against its possible plaintext. This exists because of
// bcrypt's mechanism, we shouldn't just um.hash() and check it ourselves.
func (um *UserManager) checkHash(hash []byte, original []byte) bool {
	if bcrypt.CompareHashAndPassword(hash, original) != nil {
		return false
	}

	return true
}

// Check is for internal UserManager error handling. Specifically, if a path error occurs
// that means we just need to create the database. Furthermore in debug mode
// a stdout message pops before panic()'ing, due to a panic possibility occurring
// out of the blue.
func (um *UserManager) check(err error) {
	if err != nil {
		if _, ok := err.(*os.PathError); ok {
			um.debug("Path error occured, creating database now.")
			os.Create(um.databasePath)
		} else {
			um.debug(err.Error() + "\nPanicking.")
			panic(err)
		}
	}
}

// Uses SHA256 to hash a Session token (the sum of identifiers).
func (um *UserManager) hashHTTPSessionToken(ua string, ip string) string {
	hash := sha256.New()
	hash.Write([]byte(ua + ip))

	return hex.EncodeToString(hash.Sum(nil))
}

// Generates a unique cookie hash (it keeps trying if not).
func (um *UserManager) generateCookieHash() string {
	randBytes := make([]byte, 32)
	_, err := rand.Read(randBytes)
	um.check(err)

	hash := sha256.New()
	hash.Write(randBytes)

	result := hex.EncodeToString(hash.Sum(nil))
	for _, sess := range um.sessions {
		if sess.Cookie == result {
			return um.generateCookieHash()
		}
	}

	return result
}

// GetHTTPSession is made for HTTP oriented sessions.
// It attempts to find an existing Session. If it exists, it validates
// the given cookie (from the Request). If vaildation fails it's like the
// Session never existed in the first place, having a fresh one taking its
// place. Returns nil if either http.Request or http.ResponseWriter are nil.
//
func (um *UserManager) GetHTTPSession(w http.ResponseWriter, r *http.Request) *Session {
	if r == nil || w == nil {
		return nil
	}

	sep := ":"
	if strings.Contains(r.RemoteAddr, "%") {
		// it's ipv6
		sep = "%"
	}

	chopped := strings.SplitN(r.RemoteAddr, sep, 2)
	ip := ""

	if len(chopped) > 1 {
		ip = chopped[0]
	}

	hash := um.hashHTTPSessionToken(r.UserAgent(), ip)

	if sess, exists := um.sessions[hash]; exists {
		userCookie, err := r.Cookie("session")

		if err == nil && userCookie.Value == sess.Cookie {
			return sess
		}
	}

	um.lock(&um.sessionsMutex)
	um.sessionsMutex = true
	um.sessions[hash] = createSession()
	um.sessionsMutex = false

	sess := um.sessions[hash]

	sess.Cookie = um.generateCookieHash()
	sess.SetHTTPCookie(w)

	return sess
}

// GetSessionFromID is made for abstraction. One could produce IDs given
// unique user oriented identifying elements. If a Session is not found
// a new one takes its place and gets returned.
//
func (um *UserManager) GetSessionFromID(id string) *Session {
	if sess, exists := um.sessions[id]; exists {
		return sess
	}

	um.lock(&um.sessionsMutex)
	um.sessionsMutex = true
	um.sessions[id] = createSession()
	um.sessionsMutex = false

	return um.sessions[id]
}

// Register registers a new user, writing both into the database file and the memory.
// Returns false if the user exists already.
//
func (um *UserManager) Register(user string, pass string) bool {
	if _, exists := um.users[user]; exists || user == "" || pass == "" {
		return false
	}

	um.lock(&um.usersMutex)
	um.usersMutex = true
	um.users[user] = um.hash([]byte(pass))
	um.usersMutex = false

	pass = string(um.users[user])

	f, err := os.OpenFile(um.databasePath, os.O_APPEND|os.O_WRONLY, 0666)
	defer f.Close()
	um.check(err)

	_, err = f.WriteString(user + ":" + pass + "\n")
	um.check(err)

	um.debug("Registered user[" + user + "] with password[" + pass + "].")
	return true
}

// ChangePass changes the password of a user, given his old password matches the oldpass
// string variable. Writes both into the database file and the memory.
//
// Returns false if the old password given doesn't match the actual old password, or User
// doesn't exist.
//
func (um *UserManager) ChangePass(user string, oldpass string, newpass string) bool {
	if newpass != "" && um.checkHash(um.users[user], []byte(oldpass)) {
		oldpass := string(um.users[user])

		um.lock(&um.usersMutex)
		um.usersMutex = true
		um.users[user] = um.hash([]byte(newpass))
		um.usersMutex = false

		newpass = string(um.users[user])

		data, err := ioutil.ReadFile(um.databasePath)
		um.check(err)

		lines := strings.Split(string(data), "\n")
		for i, line := range lines {
			if strings.Contains(line, user) && strings.Contains(line, oldpass) {
				lines[i] = user + ":" + newpass
				break
			}
		}

		output := strings.Join(lines, "\n")
		err = ioutil.WriteFile(um.databasePath, []byte(output), 0666)
		um.check(err)

		um.debug("Changed the password of user[" + user + "],\n\t" +
			"from[" + oldpass + "] to[" + newpass + "].")
		return true
	}

	um.debug("Failed to change the password of User[" + user + "].")
	return false
}

// Login logs a user in given his credentials.
//
func (um *UserManager) Login(user string, pass string, sess *Session) bool {
	if user != "" && pass != "" && um.checkHash(um.users[user], []byte(pass)) {
		sess.User = user

		um.debug("User[" + user + "] has logged in.")
		return true
	}

	um.debug("User[" + user + "] failed to log in.")
	return false
}
<commit_msg>Implemented delete function<commit_after>// Package uman is a minimal user and session manager in Golang.
// Copyright (C) 2016 niemal
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
package uman

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"golang.org/x/crypto/bcrypt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"
)

// Session is a structure representing what its name implies.
//
type Session struct {
	User       string
	Timestamp  int64
	Lifespan   int64
	Cookie     string
	CookiePath string
}

const lifespan int64 = 3600

// Constructor of Session.
func createSession() *Session {
	return &Session{
		User:       "",
		CookiePath: "/",
		Lifespan:   lifespan,
		Timestamp:  time.Now().Unix(),
	}
}

// SetHTTPCookie sets the HTTP cookie given the http.ResponseWriter.
//
func (sess *Session) SetHTTPCookie(w http.ResponseWriter) {
	http.SetCookie(w, &http.Cookie{
		Name:     "session",
		Path:     sess.CookiePath,
		Value:    sess.Cookie,
		HttpOnly: true,
		Expires:  time.Unix(sess.Timestamp+sess.Lifespan, 0),
		MaxAge:   int(sess.Lifespan),
	})
}

// Logout logs the session out!
//
func (sess *Session) Logout() {
	sess.User = ""
}

// SetLifespan changes the lifespan of a Session.
// Mainly exists to handle the typecasting between int and int64.
//
func (sess *Session) SetLifespan(seconds int) {
	sess.Lifespan = int64(seconds)
}

// IsLogged checks if the Session holds a user.
//
func (sess *Session) IsLogged() bool {
	if sess.User != "" {
		return true
	}

	return false
}

// UserManager is the backbone, holding all the goods.
//
type UserManager struct {
	CheckDelay    int
	Debug         bool
	users         map[string][]byte
	sessions      map[string]*Session
	databasePath  string
	sessionsMutex bool
	usersMutex    bool
}

// New is the constructor of UserManager.
//
func New(dbPath string) *UserManager {
	um := &UserManager{
		Debug:         true,
		CheckDelay:    60,
		databasePath:  dbPath,
		sessions:      make(map[string]*Session),
		sessionsMutex: false,
		usersMutex:    false,
	}

	um.reload()
	go um.checkSessions()

	return um
}

// Handles checking for debug mode and if so prints text.
func (um *UserManager) debug(message string) {
	if um.Debug {
		fmt.Println("[UserManager]: " + message)
	}
}

// Locks the running thread to prevent race conditions.
// A boolean is provided as the responsible mutex. Usually it's
// um.sessionsMutex or um.usersMutex.
func (um *UserManager) lock(mutex *bool) {
	um.debug("Thread is locked.")

	for *mutex {
		time.Sleep(time.Duration(300) * time.Millisecond)
	}

	um.debug("Thread is unlocked.")
}

// Reloads the database into memory, filling the appropriate variables.
func (um *UserManager) reload() {
	um.lock(&um.usersMutex)
	um.usersMutex = true

	um.users = make(map[string][]byte) // reset

	data, err := ioutil.ReadFile(um.databasePath)
	um.check(err)

	accounts := strings.Split(string(data), "\n")
	for _, acc := range accounts {
		creds := strings.Split(acc, ":")

		if len(creds) != 2 {
			break
		}

		um.users[creds[0]] = []byte(creds[1])
	}

	um.usersMutex = false
}

// Checks for expired Sessions.
func (um *UserManager) checkSessions() {
	for {
		for hash, sess := range um.sessions {
			if sess.Timestamp+sess.Lifespan < time.Now().Unix() {
				um.lock(&um.sessionsMutex)
				um.sessionsMutex = true

				um.debug("Session[" + hash + "] has expired.")
				delete(um.sessions, hash)

				um.sessionsMutex = false
			}
		}

		time.Sleep(time.Duration(um.CheckDelay) * time.Second)
	}
}

// Hashes any given input using bcrypt.
func (um *UserManager) hash(this []byte) []byte {
	// cost: minimum is 4, max is 31, default is 10
	// (https://godoc.org/golang.org/x/crypto/bcrypt)
	cost := 10

	hash, err := bcrypt.GenerateFromPassword(this, cost)
	um.check(err)

	return hash
}

// CheckHash checks a hash against its possible plaintext. This exists because of
// bcrypt's mechanism, we shouldn't just um.hash() and check it ourselves.
func (um *UserManager) checkHash(hash []byte, original []byte) bool {
	if bcrypt.CompareHashAndPassword(hash, original) != nil {
		return false
	}

	return true
}

// Check is for internal UserManager error handling. Specifically, if a path error occurs
// that means we just need to create the database. Furthermore in debug mode
// a stdout message pops before panic()'ing, due to a panic possibility occurring
// out of the blue.
func (um *UserManager) check(err error) {
	if err != nil {
		if _, ok := err.(*os.PathError); ok {
			um.debug("Path error occured, creating database now.")
			os.Create(um.databasePath)
		} else {
			um.debug(err.Error() + "\nPanicking.")
			panic(err)
		}
	}
}

// Uses SHA256 to hash a Session token (the sum of identifiers).
func (um *UserManager) hashHTTPSessionToken(ua string, ip string) string {
	hash := sha256.New()
	hash.Write([]byte(ua + ip))

	return hex.EncodeToString(hash.Sum(nil))
}

// Generates a unique cookie hash (it keeps trying if not).
func (um *UserManager) generateCookieHash() string {
	randBytes := make([]byte, 32)
	_, err := rand.Read(randBytes)
	um.check(err)

	hash := sha256.New()
	hash.Write(randBytes)

	result := hex.EncodeToString(hash.Sum(nil))
	for _, sess := range um.sessions {
		if sess.Cookie == result {
			return um.generateCookieHash()
		}
	}

	return result
}

// GetHTTPSession is made for HTTP oriented sessions.
// It attempts to find an existing Session. If it exists, it validates
// the given cookie (from the Request). If vaildation fails it's like the
// Session never existed in the first place, having a fresh one taking its
// place. Returns nil if either http.Request or http.ResponseWriter are nil.
//
func (um *UserManager) GetHTTPSession(w http.ResponseWriter, r *http.Request) *Session {
	if r == nil || w == nil {
		return nil
	}

	sep := ":"
	if strings.Contains(r.RemoteAddr, "%") {
		// it's ipv6
		sep = "%"
	}

	chopped := strings.SplitN(r.RemoteAddr, sep, 2)
	ip := ""

	if len(chopped) > 1 {
		ip = chopped[0]
	}

	hash := um.hashHTTPSessionToken(r.UserAgent(), ip)

	if sess, exists := um.sessions[hash]; exists {
		userCookie, err := r.Cookie("session")

		if err == nil && userCookie.Value == sess.Cookie {
			return sess
		}
	}

	um.lock(&um.sessionsMutex)
	um.sessionsMutex = true
	um.sessions[hash] = createSession()
	um.sessionsMutex = false

	sess := um.sessions[hash]

	sess.Cookie = um.generateCookieHash()
	sess.SetHTTPCookie(w)

	return sess
}

// GetSessionFromID is made for abstraction. One could produce IDs given
// unique user oriented identifying elements. If a Session is not found
// a new one takes its place and gets returned.
//
func (um *UserManager) GetSessionFromID(id string) *Session {
	if sess, exists := um.sessions[id]; exists {
		return sess
	}

	um.lock(&um.sessionsMutex)
	um.sessionsMutex = true
	um.sessions[id] = createSession()
	um.sessionsMutex = false

	return um.sessions[id]
}

// Register registers a new user, writing both into the database file and the memory.
// Returns false if the user exists already.
//
func (um *UserManager) Register(user string, pass string) bool {
	if _, exists := um.users[user]; exists || user == "" || pass == "" {
		return false
	}

	um.lock(&um.usersMutex)
	um.usersMutex = true
	um.users[user] = um.hash([]byte(pass))
	um.usersMutex = false

	pass = string(um.users[user])

	f, err := os.OpenFile(um.databasePath, os.O_APPEND|os.O_WRONLY, 0666)
	defer f.Close()
	um.check(err)

	_, err = f.WriteString(user + ":" + pass + "\n")
	um.check(err)

	um.debug("Registered user[" + user + "] with password[" + pass + "].")
	return true
}

// ChangePass changes the password of a user, given his old password matches the oldpass
// string variable. Writes both into the database file and the memory.
//
// Returns false if the old password given doesn't match the actual old password, or User
// doesn't exist.
//
func (um *UserManager) ChangePass(user string, oldpass string, newpass string) bool {
	if newpass != "" && um.checkHash(um.users[user], []byte(oldpass)) {
		oldpass := string(um.users[user])

		um.lock(&um.usersMutex)
		um.usersMutex = true
		um.users[user] = um.hash([]byte(newpass))
		um.usersMutex = false

		newpass = string(um.users[user])

		data, err := ioutil.ReadFile(um.databasePath)
		um.check(err)

		lines := strings.Split(string(data), "\n")
		for i, line := range lines {
			if strings.Contains(line, user) && strings.Contains(line, oldpass) {
				lines[i] = user + ":" + newpass
				break
			}
		}

		output := strings.Join(lines, "\n")
		err = ioutil.WriteFile(um.databasePath, []byte(output), 0666)
		um.check(err)

		um.debug("Changed the password of user[" + user + "],\n\t" +
			"from[" + oldpass + "] to[" + newpass + "].")
		return true
	}

	um.debug("Failed to change the password of User[" + user + "].")
	return false
}

// Delete deletes a user given his name.
//
func (um *UserManager) Delete(user string) bool {
	exists := -1

	data, err := ioutil.ReadFile(um.databasePath)
	um.check(err)

	lines := strings.Split(string(data), "\n")

	var creds []string
	for i, line := range lines {
		creds = strings.Split(line, ":")

		if len(creds) != 2 {
			continue
		}

		if creds[0] == user {
			exists = i
			break
		}
	}

	if exists > -1 {
		um.lock(&um.usersMutex)
		um.usersMutex = true
		delete(um.users, user)
		um.usersMutex = false

		for _, sess := range um.sessions {
			if sess.User == user {
				sess.Logout()
			}
		}

		lines = append(lines[:exists], lines[exists+1:]...)
		output := strings.Join(lines, "\n")
		err = ioutil.WriteFile(um.databasePath, []byte(output), 0666)
		um.check(err)

		um.debug("Deleted user[" + user + "].")
		return true
	}

	um.debug("Attempted to delete unknown user[" + user + "].")
	return false
}

// Login logs a user in given his credentials.
//
func (um *UserManager) Login(user string, pass string, sess *Session) bool {
	if user != "" && pass != "" && um.checkHash(um.users[user], []byte(pass)) {
		sess.User = user

		um.debug("User[" + user + "] has logged in.")
		return true
	}

	um.debug("User[" + user + "] failed to log in.")
	return false
}
<|endoftext|>