<commit_before>package main

import (
	"bytes"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/edsrzf/mmap-go"
	"github.com/mibk/syd/core"
	"github.com/mibk/syd/pkg/undo"
	"github.com/mibk/syd/ui"
	"github.com/mibk/syd/ui/term"
	"github.com/mibk/syd/vi"
	"github.com/mibk/syd/view"
)

var (
	win      = &term.UI{}
	filename = ""
)

func main() {
	log.SetPrefix("syd: ")
	log.SetFlags(0)
	if err := win.Init(); err != nil {
		log.Fatalln("initializing ui:", err)
	}
	defer win.Close()

	var b []byte
	if len(os.Args) > 1 {
		filename = os.Args[1]
		m, err := readFile(filename)
		if err != nil {
			panic(err)
		}
		defer m.Unmap()
		b = []byte(m)
	}
	buf := undo.NewBuffer(b)

	e := &Editor{
		events:     make(chan ui.Event),
		vi:         vi.NewParser(),
		buffer:     buf,
		activeView: view.New(win, core.NewBuffer(buf)),
	}
	setMappings(e)
	go e.RouteEvents()
	e.Main()
}

func readFile(filename string) (mmap.MMap, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}

	m, err := mmap.Map(f, 0, 0)
	if err != nil {
		return nil, err
	}
	return m, nil
}

const (
	ModeNormal = iota
	ModeInsert
)

type Editor struct {
	events     chan ui.Event
	vi         *vi.Parser
	shouldQuit bool

	buffer     *undo.Buffer // TODO: remove
	activeView *view.View
	mode       int
}

func (e *Editor) RouteEvents() {
	for ev := range ui.Events {
		if keyPress, ok := ev.(ui.KeyPress); ok && e.mode == ModeNormal {
			e.vi.Decode(keyPress)
			continue
		}
		e.events <- ev
	}
}

func parseKeys(cmd string) []ui.KeyPress {
	events := make([]ui.KeyPress, len(cmd))
	for i, r := range []rune(cmd) {
		events[i] = ui.KeyPress{Key: r}
	}
	return events
}

func (e *Editor) AddOperator(cmd []ui.KeyPress, fn func(*view.View, int)) {
	e.vi.AddOperator(cmd, func(n int) { fn(e.activeView, n) }, false)
}

func (e *Editor) AddStringOperator(cmd string, fn func(*view.View, int)) {
	e.AddOperator(parseKeys(cmd), fn)
}

func (e *Editor) AddMotion(cmd []ui.KeyPress, fn func(*view.View, int)) {
	e.vi.AddMotion(cmd, func(n int) { fn(e.activeView, n) })
}

func (e *Editor) AddStringMotion(cmd string, fn func(*view.View, int)) {
	e.AddMotion(parseKeys(cmd), fn)
}

func (e *Editor) Main() {
	var (
		lastQ     int64 = -1
		timestamp time.Time
	)
	for !e.shouldQuit {
		e.activeView.Render()
		select {
		case action := <-e.vi.Actions:
			action()
		case ev := <-e.events:
			switch ev := ev.(type) {
			case ui.KeyPress:
				if ev.Key == ui.KeyEscape {
					e.mode = ModeNormal
					continue
				}
				handleKeyPress(e.activeView, ev)
			case ui.MouseBtnPress:
				switch ev.Button {
				case ui.MouseButton1:
					p := e.activeView.Frame().CharsUntilXY(ev.X, ev.Y)
					q := e.activeView.Origin() + int64(p)
					if time.Since(timestamp) < 300*time.Millisecond {
						e.activeView.Select(dblclick(e.activeView, q))
						e.activeView.Frame().SetWantCol(ui.ColQ0)
						lastQ = -1
						continue
					}
					e.activeView.Select(q, q)
					e.activeView.Frame().SetWantCol(ui.ColQ0)
					lastQ = q
					timestamp = time.Now()
				case ui.MouseButton2:
					// This is just ugly proof of concept.
					p := e.activeView.Frame().CharsUntilXY(ev.X, ev.Y)
					q := e.activeView.Origin() + int64(p)
					q0, q1 := dblclick(e.activeView, q)
					var cmd []rune
					for i := q0; i < q1; i++ {
						cmd = append(cmd, e.activeView.ReadRuneAt(i))
					}
					e.Execute(string(cmd))
				case ui.MouseWheelUp:
					scrollUp(e.activeView, 3)
				case ui.MouseWheelDown:
					scrollDown(e.activeView, 3)
				}
			case ui.MouseBtnRelease:
				lastQ = -1
			case ui.MouseMove:
				if lastQ < 0 {
					continue
				}
				p := e.activeView.Frame().CharsUntilXY(ev.X, ev.Y)
				q0, q1 := lastQ, e.activeView.Origin()+int64(p)
				if q1 < q0 {
					q0, q1 = q1, q0
				}
				e.activeView.Select(q0, q1)
			}
		}
	}
}

func (e *Editor) Execute(command string) {
	switch command {
	case "Put":
		if filename != "" {
			if err := saveFile(filename, e.activeView); err != nil {
				panic(err)
			}
		}
	default:
		v := e.activeView
		var selected []rune
		q0, q1 := v.Selected()
		for p := q0; p < q1; p++ {
			r := v.ReadRuneAt(p)
			selected = append(selected, r)
		}
		var buf bytes.Buffer
		rd := strings.NewReader(string(selected))
		cmd := exec.Command(command)
		cmd.Stdin = rd
		cmd.Stdout = &buf
		// TODO: Redirect stderr somewhere.
		if err := cmd.Run(); err != nil {
			panic(err)
		}
		s := buf.String()
		v.Insert(s)
		v.Select(q0, q0+int64(utf8.RuneCountInString(s)))
	}
}

func saveFile(filename string, v *view.View) error {
	// TODO: Read bytes directly from the undo.Buffer.
	f, err := os.Create(filename + "~")
	if err != nil {
		return err
	}

	var buf [64]byte
	var i int

	for p := int64(0); ; p++ {
		r := v.ReadRuneAt(p)
		if r == view.EOF || len(buf[i:]) < utf8.UTFMax {
			if _, err := f.Write(buf[:i]); err != nil {
				return err
			}
			i = 0
		}
		if r == view.EOF {
			break
		}
		i += utf8.EncodeRune(buf[i:], r)
	}
	f.Close()

	return os.Rename(filename+"~", filename)
}
<commit_msg>Add Undo & Redo commands<commit_after>package main

import (
	"bytes"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/edsrzf/mmap-go"
	"github.com/mibk/syd/core"
	"github.com/mibk/syd/pkg/undo"
	"github.com/mibk/syd/ui"
	"github.com/mibk/syd/ui/term"
	"github.com/mibk/syd/vi"
	"github.com/mibk/syd/view"
)

var (
	win      = &term.UI{}
	filename = ""
)

func main() {
	log.SetPrefix("syd: ")
	log.SetFlags(0)
	if err := win.Init(); err != nil {
		log.Fatalln("initializing ui:", err)
	}
	defer win.Close()

	var b []byte
	if len(os.Args) > 1 {
		filename = os.Args[1]
		m, err := readFile(filename)
		if err != nil {
			panic(err)
		}
		defer m.Unmap()
		b = []byte(m)
	}
	buf := undo.NewBuffer(b)

	e := &Editor{
		events:     make(chan ui.Event),
		vi:         vi.NewParser(),
		buffer:     buf,
		activeView: view.New(win, core.NewBuffer(buf)),
	}
	setMappings(e)
	go e.RouteEvents()
	e.Main()
}

func readFile(filename string) (mmap.MMap, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}

	m, err := mmap.Map(f, 0, 0)
	if err != nil {
		return nil, err
	}
	return m, nil
}

const (
	ModeNormal = iota
	ModeInsert
)

type Editor struct {
	events     chan ui.Event
	vi         *vi.Parser
	shouldQuit bool

	buffer     *undo.Buffer // TODO: remove
	activeView *view.View
	mode       int
}

func (e *Editor) RouteEvents() {
	for ev := range ui.Events {
		if keyPress, ok := ev.(ui.KeyPress); ok && e.mode == ModeNormal {
			e.vi.Decode(keyPress)
			continue
		}
		e.events <- ev
	}
}

func parseKeys(cmd string) []ui.KeyPress {
	events := make([]ui.KeyPress, len(cmd))
	for i, r := range []rune(cmd) {
		events[i] = ui.KeyPress{Key: r}
	}
	return events
}

func (e *Editor) AddOperator(cmd []ui.KeyPress, fn func(*view.View, int)) {
	e.vi.AddOperator(cmd, func(n int) { fn(e.activeView, n) }, false)
}

func (e *Editor) AddStringOperator(cmd string, fn func(*view.View, int)) {
	e.AddOperator(parseKeys(cmd), fn)
}

func (e *Editor) AddMotion(cmd []ui.KeyPress, fn func(*view.View, int)) {
	e.vi.AddMotion(cmd, func(n int) { fn(e.activeView, n) })
}

func (e *Editor) AddStringMotion(cmd string, fn func(*view.View, int)) {
	e.AddMotion(parseKeys(cmd), fn)
}

func (e *Editor) Main() {
	var (
		lastQ     int64 = -1
		timestamp time.Time
	)
	for !e.shouldQuit {
		e.activeView.Render()
		select {
		case action := <-e.vi.Actions:
			action()
		case ev := <-e.events:
			switch ev := ev.(type) {
			case ui.KeyPress:
				if ev.Key == ui.KeyEscape {
					e.mode = ModeNormal
					continue
				}
				handleKeyPress(e.activeView, ev)
			case ui.MouseBtnPress:
				switch ev.Button {
				case ui.MouseButton1:
					p := e.activeView.Frame().CharsUntilXY(ev.X, ev.Y)
					q := e.activeView.Origin() + int64(p)
					if time.Since(timestamp) < 300*time.Millisecond {
						e.activeView.Select(dblclick(e.activeView, q))
						e.activeView.Frame().SetWantCol(ui.ColQ0)
						lastQ = -1
						continue
					}
					e.activeView.Select(q, q)
					e.activeView.Frame().SetWantCol(ui.ColQ0)
					lastQ = q
					timestamp = time.Now()
				case ui.MouseButton2:
					// This is just ugly proof of concept.
					p := e.activeView.Frame().CharsUntilXY(ev.X, ev.Y)
					q := e.activeView.Origin() + int64(p)
					q0, q1 := dblclick(e.activeView, q)
					var cmd []rune
					for i := q0; i < q1; i++ {
						cmd = append(cmd, e.activeView.ReadRuneAt(i))
					}
					e.Execute(string(cmd))
				case ui.MouseWheelUp:
					scrollUp(e.activeView, 3)
				case ui.MouseWheelDown:
					scrollDown(e.activeView, 3)
				}
			case ui.MouseBtnRelease:
				lastQ = -1
			case ui.MouseMove:
				if lastQ < 0 {
					continue
				}
				p := e.activeView.Frame().CharsUntilXY(ev.X, ev.Y)
				q0, q1 := lastQ, e.activeView.Origin()+int64(p)
				if q1 < q0 {
					q0, q1 = q1, q0
				}
				e.activeView.Select(q0, q1)
			}
		}
	}
}

func (e *Editor) Execute(command string) {
	switch command {
	case "Put":
		if filename != "" {
			if err := saveFile(filename, e.activeView); err != nil {
				panic(err)
			}
		}
	case "Undo":
		e.activeView.Undo()
	case "Redo":
		e.activeView.Redo()
	default:
		v := e.activeView
		var selected []rune
		q0, q1 := v.Selected()
		for p := q0; p < q1; p++ {
			r := v.ReadRuneAt(p)
			selected = append(selected, r)
		}
		var buf bytes.Buffer
		rd := strings.NewReader(string(selected))
		cmd := exec.Command(command)
		cmd.Stdin = rd
		cmd.Stdout = &buf
		// TODO: Redirect stderr somewhere.
		if err := cmd.Run(); err != nil {
			panic(err)
		}
		s := buf.String()
		v.Insert(s)
		v.Select(q0, q0+int64(utf8.RuneCountInString(s)))
	}
}

func saveFile(filename string, v *view.View) error {
	// TODO: Read bytes directly from the undo.Buffer.
	f, err := os.Create(filename + "~")
	if err != nil {
		return err
	}

	var buf [64]byte
	var i int

	for p := int64(0); ; p++ {
		r := v.ReadRuneAt(p)
		if r == view.EOF || len(buf[i:]) < utf8.UTFMax {
			if _, err := f.Write(buf[:i]); err != nil {
				return err
			}
			i = 0
		}
		if r == view.EOF {
			break
		}
		i += utf8.EncodeRune(buf[i:], r)
	}
	f.Close()

	return os.Rename(filename+"~", filename)
}
<|endoftext|>


<commit_before>package git

/*
#include <git2.h>

extern int _go_git_tag_foreach(git_repository *repo, void *payload);
*/
import "C"
import (
	"runtime"
	"unsafe"
)

// Tag
type Tag struct {
	Object
	cast_ptr *C.git_tag
}

func (t *Tag) AsObject() *Object {
	return &t.Object
}

func (t Tag) Message() string {
	ret := C.GoString(C.git_tag_message(t.cast_ptr))
	runtime.KeepAlive(t)
	return ret
}

func (t Tag) Name() string {
	ret := C.GoString(C.git_tag_name(t.cast_ptr))
	runtime.KeepAlive(t)
	return ret
}

func (t Tag) Tagger() *Signature {
	cast_ptr := C.git_tag_tagger(t.cast_ptr)
	ret := newSignatureFromC(cast_ptr)
	runtime.KeepAlive(t)
	return ret
}

func (t Tag) Target() *Object {
	var ptr *C.git_object
	ret := C.git_tag_target(&ptr, t.cast_ptr)
	runtime.KeepAlive(t)
	if ret != 0 {
		return nil
	}

	return allocObject(ptr, t.repo)
}

func (t Tag) TargetId() *Oid {
	ret := newOidFromC(C.git_tag_target_id(t.cast_ptr))
	runtime.KeepAlive(t)
	return ret
}

func (t Tag) TargetType() ObjectType {
	ret := ObjectType(C.git_tag_target_type(t.cast_ptr))
	runtime.KeepAlive(t)
	return ret
}

type TagsCollection struct {
	repo *Repository
}

func (c *TagsCollection) Create(name string, obj Objecter, tagger *Signature, message string) (*Oid, error) {

	oid := new(Oid)

	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	cmessage := C.CString(message)
	defer C.free(unsafe.Pointer(cmessage))

	taggerSig, err := tagger.toC()
	if err != nil {
		return nil, err
	}
	defer C.git_signature_free(taggerSig)

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	o := obj.AsObject()
	ret := C.git_tag_create(oid.toC(), c.repo.ptr, cname, o.ptr, taggerSig, cmessage, 0)
	runtime.KeepAlive(c)
	runtime.KeepAlive(obj)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}

	return oid, nil
}

func (c *TagsCollection) Remove(name string) error {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	ret := C.git_tag_delete(c.repo.ptr, cname)
	runtime.KeepAlive(c)
	if ret < 0 {
		return MakeGitError(ret)
	}

	return nil
}

// CreateLightweight creates a new lightweight tag pointing to an object
// and returns the id of the target object.
//
// The name of the tag is validated for consistency (see git_tag_create() for the rules
// https://libgit2.github.com/libgit2/#HEAD/group/tag/git_tag_create) and should
// not conflict with an already existing tag name.
//
// If force is true and a reference already exists with the given name, it'll be replaced.
//
// The created tag is a simple reference and can be queried using
// repo.References.Lookup("refs/tags/<name>"). The name of the tag (eg "v1.0.0")
// is queried with ref.Shorthand().
func (c *TagsCollection) CreateLightweight(name string, obj Objecter, force bool) (*Oid, error) {

	oid := new(Oid)

	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	o := obj.AsObject()
	err := C.git_tag_create_lightweight(oid.toC(), c.repo.ptr, cname, o.ptr, cbool(force))
	runtime.KeepAlive(c)
	runtime.KeepAlive(obj)
	if err < 0 {
		return nil, MakeGitError(err)
	}

	return oid, nil
}

// List returns the names of all the tags in the repository,
// eg: ["v1.0.1", "v2.0.0"].
func (c *TagsCollection) List() ([]string, error) {
	var strC C.git_strarray

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ecode := C.git_tag_list(&strC, c.repo.ptr)
	runtime.KeepAlive(c)
	if ecode < 0 {
		return nil, MakeGitError(ecode)
	}
	defer C.git_strarray_free(&strC)

	tags := makeStringsFromCStrings(strC.strings, int(strC.count))
	return tags, nil
}

// ListWithMatch returns the names of all the tags in the repository
// that match a given pattern.
//
// The pattern is a standard fnmatch(3) pattern http://man7.org/linux/man-pages/man3/fnmatch.3.html
func (c *TagsCollection) ListWithMatch(pattern string) ([]string, error) {
	var strC C.git_strarray

	patternC := C.CString(pattern)
	defer C.free(unsafe.Pointer(patternC))

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ecode := C.git_tag_list_match(&strC, patternC, c.repo.ptr)
	runtime.KeepAlive(c)
	if ecode < 0 {
		return nil, MakeGitError(ecode)
	}
	defer C.git_strarray_free(&strC)

	tags := makeStringsFromCStrings(strC.strings, int(strC.count))
	return tags, nil
}

// TagForeachCallback is called for each tag in the repository.
//
// The name is the full ref name eg: "refs/tags/v1.0.0".
//
// Note that the callback is called for lightweight tags as well,
// so repo.LookupTag() will return an error for these tags. Use
// repo.References.Lookup() instead.
type TagForeachCallback func(name string, id *Oid) error
type tagForeachData struct {
	callback TagForeachCallback
	err      error
}

//export gitTagForeachCb
func gitTagForeachCb(name *C.char, id *C.git_oid, handle unsafe.Pointer) int {
	payload := pointerHandles.Get(handle)
	data, ok := payload.(*tagForeachData)
	if !ok {
		panic("could not retrieve tag foreach CB handle")
	}

	err := data.callback(C.GoString(name), newOidFromC(id))
	if err != nil {
		data.err = err
		return C.GIT_EUSER
	}

	return 0
}

// Foreach calls the callback for each tag in the repository.
func (c *TagsCollection) Foreach(callback TagForeachCallback) error {
	data := tagForeachData{
		callback: callback,
		err:      nil,
	}

	handle := pointerHandles.Track(&data)
	defer pointerHandles.Untrack(handle)

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	err := C._go_git_tag_foreach(c.repo.ptr, handle)
	runtime.KeepAlive(c)
	if err == C.GIT_EUSER {
		return data.err
	}
	if err < 0 {
		return MakeGitError(err)
	}

	return nil
}
<commit_msg>Similar to #513 Fix potential segfault on Tag objects<commit_after>package git

/*
#include <git2.h>

extern int _go_git_tag_foreach(git_repository *repo, void *payload);
*/
import "C"
import (
	"runtime"
	"unsafe"
)

// Tag
type Tag struct {
	Object
	cast_ptr *C.git_tag
}

func (t *Tag) AsObject() *Object {
	return &t.Object
}

func (t *Tag) Message() string {
	ret := C.GoString(C.git_tag_message(t.cast_ptr))
	runtime.KeepAlive(t)
	return ret
}

func (t *Tag) Name() string {
	ret := C.GoString(C.git_tag_name(t.cast_ptr))
	runtime.KeepAlive(t)
	return ret
}

func (t *Tag) Tagger() *Signature {
	cast_ptr := C.git_tag_tagger(t.cast_ptr)
	ret := newSignatureFromC(cast_ptr)
	runtime.KeepAlive(t)
	return ret
}

func (t *Tag) Target() *Object {
	var ptr *C.git_object
	ret := C.git_tag_target(&ptr, t.cast_ptr)
	runtime.KeepAlive(t)
	if ret != 0 {
		return nil
	}

	return allocObject(ptr, t.repo)
}

func (t *Tag) TargetId() *Oid {
	ret := newOidFromC(C.git_tag_target_id(t.cast_ptr))
	runtime.KeepAlive(t)
	return ret
}

func (t *Tag) TargetType() ObjectType {
	ret := ObjectType(C.git_tag_target_type(t.cast_ptr))
	runtime.KeepAlive(t)
	return ret
}

type TagsCollection struct {
	repo *Repository
}

func (c *TagsCollection) Create(name string, obj Objecter, tagger *Signature, message string) (*Oid, error) {

	oid := new(Oid)

	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	cmessage := C.CString(message)
	defer C.free(unsafe.Pointer(cmessage))

	taggerSig, err := tagger.toC()
	if err != nil {
		return nil, err
	}
	defer C.git_signature_free(taggerSig)

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	o := obj.AsObject()
	ret := C.git_tag_create(oid.toC(), c.repo.ptr, cname, o.ptr, taggerSig, cmessage, 0)
	runtime.KeepAlive(c)
	runtime.KeepAlive(obj)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}

	return oid, nil
}

func (c *TagsCollection) Remove(name string) error {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	ret := C.git_tag_delete(c.repo.ptr, cname)
	runtime.KeepAlive(c)
	if ret < 0 {
		return MakeGitError(ret)
	}

	return nil
}

// CreateLightweight creates a new lightweight tag pointing to an object
// and returns the id of the target object.
//
// The name of the tag is validated for consistency (see git_tag_create() for the rules
// https://libgit2.github.com/libgit2/#HEAD/group/tag/git_tag_create) and should
// not conflict with an already existing tag name.
//
// If force is true and a reference already exists with the given name, it'll be replaced.
//
// The created tag is a simple reference and can be queried using
// repo.References.Lookup("refs/tags/<name>"). The name of the tag (eg "v1.0.0")
// is queried with ref.Shorthand().
func (c *TagsCollection) CreateLightweight(name string, obj Objecter, force bool) (*Oid, error) {

	oid := new(Oid)

	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	o := obj.AsObject()
	err := C.git_tag_create_lightweight(oid.toC(), c.repo.ptr, cname, o.ptr, cbool(force))
	runtime.KeepAlive(c)
	runtime.KeepAlive(obj)
	if err < 0 {
		return nil, MakeGitError(err)
	}

	return oid, nil
}

// List returns the names of all the tags in the repository,
// eg: ["v1.0.1", "v2.0.0"].
func (c *TagsCollection) List() ([]string, error) {
	var strC C.git_strarray

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ecode := C.git_tag_list(&strC, c.repo.ptr)
	runtime.KeepAlive(c)
	if ecode < 0 {
		return nil, MakeGitError(ecode)
	}
	defer C.git_strarray_free(&strC)

	tags := makeStringsFromCStrings(strC.strings, int(strC.count))
	return tags, nil
}

// ListWithMatch returns the names of all the tags in the repository
// that match a given pattern.
//
// The pattern is a standard fnmatch(3) pattern http://man7.org/linux/man-pages/man3/fnmatch.3.html
func (c *TagsCollection) ListWithMatch(pattern string) ([]string, error) {
	var strC C.git_strarray

	patternC := C.CString(pattern)
	defer C.free(unsafe.Pointer(patternC))

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ecode := C.git_tag_list_match(&strC, patternC, c.repo.ptr)
	runtime.KeepAlive(c)
	if ecode < 0 {
		return nil, MakeGitError(ecode)
	}
	defer C.git_strarray_free(&strC)

	tags := makeStringsFromCStrings(strC.strings, int(strC.count))
	return tags, nil
}

// TagForeachCallback is called for each tag in the repository.
//
// The name is the full ref name eg: "refs/tags/v1.0.0".
//
// Note that the callback is called for lightweight tags as well,
// so repo.LookupTag() will return an error for these tags. Use
// repo.References.Lookup() instead.
type TagForeachCallback func(name string, id *Oid) error
type tagForeachData struct {
	callback TagForeachCallback
	err      error
}

//export gitTagForeachCb
func gitTagForeachCb(name *C.char, id *C.git_oid, handle unsafe.Pointer) int {
	payload := pointerHandles.Get(handle)
	data, ok := payload.(*tagForeachData)
	if !ok {
		panic("could not retrieve tag foreach CB handle")
	}

	err := data.callback(C.GoString(name), newOidFromC(id))
	if err != nil {
		data.err = err
		return C.GIT_EUSER
	}

	return 0
}

// Foreach calls the callback for each tag in the repository.
func (c *TagsCollection) Foreach(callback TagForeachCallback) error {
	data := tagForeachData{
		callback: callback,
		err:      nil,
	}

	handle := pointerHandles.Track(&data)
	defer pointerHandles.Untrack(handle)

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	err := C._go_git_tag_foreach(c.repo.ptr, handle)
	runtime.KeepAlive(c)
	if err == C.GIT_EUSER {
		return data.err
	}
	if err < 0 {
		return MakeGitError(err)
	}

	return nil
}
<|endoftext|>


<commit_before>// Package storage is a Google Cloud Storage client.
package storage

import (
	"errors"
	"io"
	"net/http"

	raw "code.google.com/p/google-api-go-client/storage/v1"
)

// ObjectInfo represents a Google Cloud Storage (GCS) object.
type ObjectInfo struct {
	// Bucket is the name of the bucket containing this GCS object.
	Bucket string `json:"bucket,omitempty"`

	// Name is the name of the object.
	Name string `json:"name,omitempty"`

	// ContentType is the MIME type of the object's content.
	ContentType string `json:"contentType,omitempty"`

	// Size is the length of the object's content.
	// Read-only.
	Size uint64 `json:"size,omitempty"`

	// ContentEncoding is the encoding of the object's content.
	// Read-only.
	ContentEncoding string `json:"contentEncoding,omitempty"`

	// MD5 is the MD5 hash of the data.
	// Read-only.
	MD5 []byte `json:"md5Hash,omitempty"`

	// CRC32C is the CRC32C checksum of the object's content.
	// Read-only.
	CRC32C []byte `json:"crc32c,omitempty"`

	// MediaLink is an URL to the object's content.
	// Read-only.
	MediaLink string `json:"mediaLink,omitempty"`

	// Metadata represents user-provided metadata, in key/value pairs.
	// It can be nil if no metadata is provided.
	Metadata map[string]string `json:"metadata,omitempty"`

	// Generation is the generation version of the object's content.
	// Read-only.
	Generation int64 `json:"generation,omitempty"`

	// MetaGeneration is the version of the metadata for this
	// object at this generation. This field is used for preconditions
	// and for detecting changes in metadata. A metageneration number
	// is only meaningful in the context of a particular generation
	// of a particular object. Readonly.
	MetaGeneration int64 `json:"metageneration,omitempty"`

	// TODO(jbd): Add ACL and owner.
	// TODO(jbd): Add timeDelete and updated.
}

func (o *ObjectInfo) toRawObject() *raw.Object {
	// TODO(jbd): add ACL and owner
	return &raw.Object{
		Bucket:      o.Bucket,
		Name:        o.Name,
		ContentType: o.ContentType,
	}
}

func newObjectInfo(o *raw.Object) *ObjectInfo {
	if o == nil {
		return nil
	}
	return &ObjectInfo{
		Bucket:          o.Bucket,
		Name:            o.Name,
		ContentType:     o.ContentType,
		ContentEncoding: o.ContentEncoding,
		Size:            o.Size,
		MD5:             []byte(o.Md5Hash),
		CRC32C:          []byte(o.Crc32c),
		MediaLink:       o.MediaLink,
		Generation:      o.Generation,
		MetaGeneration:  o.Metageneration,
	}
}

type BucketInfo struct {
	// Name is the name of the bucket.
	Name string `json:"name,omitempty"`
}

type Bucket struct {
	name string
	s    *raw.Service
}

type Client struct {
	s *raw.Service
}

func New(tr http.RoundTripper) (*Client, error) {
	return NewWithClient(&http.Client{Transport: tr})
}

func NewWithClient(c *http.Client) (*Client, error) {
	s, err := raw.New(c)
	if err != nil {
		return nil, err
	}
	return &Client{s: s}, nil
}

// TODO(jbd): Add storage.buckets.list.
// TODO(jbd): Add storage.buckets.insert.
// TODO(jbd): Add storage.buckets.update.
// TODO(jbd): Add storage.buckets.delete.

// TODO(jbd): Add storage.objects.list.

// GetBucketInfo returns the specified bucket.
func (c *Client) GetBucketInfo(name string) (*BucketInfo, error) {
	panic("not yet implemented")
}

func (c *Client) NewBucket(name string) *Bucket {
	return &Bucket{name: name, s: c.s}
}

// Stat returns the meta information of an object.
func (b *Bucket) Stat(name string) (*ObjectInfo, error) {
	o, err := b.s.Objects.Get(b.name, name).Do()
	if err != nil {
		// TODO(jbd): If 404, return ErrNotExists
		return nil, err
	}
	return newObjectInfo(o), nil
}

// Put inserts/updates an object with the provided meta information.
func (b *Bucket) Put(name string, info *ObjectInfo) (*ObjectInfo, error) {
	o, err := b.s.Objects.Insert(b.name, info.toRawObject()).Do()
	if err != nil {
		return nil, err
	}
	return newObjectInfo(o), nil
}

// Delete deletes the specified object.
func (b *Bucket) Delete(name string) error {
	return b.s.Objects.Delete(b.name, name).Do()
}

// Copy copies the source object to the destination with the new
// meta information properties provided.
// The destination object is inserted into the source bucket
// if the destination object doesn't specify another bucket name.
func (b *Bucket) Copy(name string, dest *ObjectInfo) (*ObjectInfo, error) {
	if dest.Name == "" {
		return nil, errors.New("storage: missing dest name")
	}
	destBucket := dest.Bucket
	if destBucket == "" {
		destBucket = b.name
	}
	o, err := b.s.Objects.Copy(
		b.name, name, destBucket, dest.Name, dest.toRawObject()).Do()
	if err != nil {
		// TODO(jbd): Return ErrNotExists if 404.
		return nil, err
	}
	return newObjectInfo(o), nil
}

// NewReader creates a new io.ReadCloser to read the contents
// of the object.
func (b *Bucket) NewReader(name string) (io.ReadCloser, error) {
	panic("not yet impelemented")
}

// NewWriter creates a new io.WriteCloser to write to the GCS object
// identified by the specified bucket and name.
// If such object doesn't exist, it creates one. If info is not nil,
// write operation also modifies the meta information of the object.
func (b *Bucket) NewWriter(name string, info *ObjectInfo) (io.WriteCloser, error) {
	panic("not yet implemented")
}
<commit_msg>storage: Add GCS scope constants.<commit_after>// Package storage is a Google Cloud Storage client.
package storage

import (
	"errors"
	"io"
	"net/http"

	raw "code.google.com/p/google-api-go-client/storage/v1"
)

// OAuth 2.0 scopes used by this API.
const (
	// Manage your data and permissions in Google Cloud Storage
	ScopeFullControl = raw.DevstorageFull_controlScope

	// View your data in Google Cloud Storage
	ScopeReadOnly = raw.DevstorageRead_onlyScope

	// Manage your data in Google Cloud Storage
	ScopeReadWrite = raw.DevstorageRead_writeScope
)

// ObjectInfo represents a Google Cloud Storage (GCS) object.
type ObjectInfo struct {
	// Bucket is the name of the bucket containing this GCS object.
	Bucket string `json:"bucket,omitempty"`

	// Name is the name of the object.
	Name string `json:"name,omitempty"`

	// ContentType is the MIME type of the object's content.
	ContentType string `json:"contentType,omitempty"`

	// Size is the length of the object's content.
	// Read-only.
	Size uint64 `json:"size,omitempty"`

	// ContentEncoding is the encoding of the object's content.
	// Read-only.
	ContentEncoding string `json:"contentEncoding,omitempty"`

	// MD5 is the MD5 hash of the data.
	// Read-only.
	MD5 []byte `json:"md5Hash,omitempty"`

	// CRC32C is the CRC32C checksum of the object's content.
	// Read-only.
	CRC32C []byte `json:"crc32c,omitempty"`

	// MediaLink is an URL to the object's content.
	// Read-only.
	MediaLink string `json:"mediaLink,omitempty"`

	// Metadata represents user-provided metadata, in key/value pairs.
	// It can be nil if no metadata is provided.
	Metadata map[string]string `json:"metadata,omitempty"`

	// Generation is the generation version of the object's content.
	// Read-only.
	Generation int64 `json:"generation,omitempty"`

	// MetaGeneration is the version of the metadata for this
	// object at this generation. This field is used for preconditions
	// and for detecting changes in metadata. A metageneration number
	// is only meaningful in the context of a particular generation
	// of a particular object. Readonly.
	MetaGeneration int64 `json:"metageneration,omitempty"`

	// TODO(jbd): Add ACL and owner.
	// TODO(jbd): Add timeDelete and updated.
}

func (o *ObjectInfo) toRawObject() *raw.Object {
	// TODO(jbd): add ACL and owner
	return &raw.Object{
		Bucket:      o.Bucket,
		Name:        o.Name,
		ContentType: o.ContentType,
	}
}

func newObjectInfo(o *raw.Object) *ObjectInfo {
	if o == nil {
		return nil
	}
	return &ObjectInfo{
		Bucket:          o.Bucket,
		Name:            o.Name,
		ContentType:     o.ContentType,
		ContentEncoding: o.ContentEncoding,
		Size:            o.Size,
		MD5:             []byte(o.Md5Hash),
		CRC32C:          []byte(o.Crc32c),
		MediaLink:       o.MediaLink,
		Generation:      o.Generation,
		MetaGeneration:  o.Metageneration,
	}
}

type BucketInfo struct {
	// Name is the name of the bucket.
	Name string `json:"name,omitempty"`
}

type Bucket struct {
	name string
	s    *raw.Service
}

type Client struct {
	s *raw.Service
}

func New(tr http.RoundTripper) (*Client, error) {
	return NewWithClient(&http.Client{Transport: tr})
}

func NewWithClient(c *http.Client) (*Client, error) {
	s, err := raw.New(c)
	if err != nil {
		return nil, err
	}
	return &Client{s: s}, nil
}

// TODO(jbd): Add storage.buckets.list.
// TODO(jbd): Add storage.buckets.insert.
// TODO(jbd): Add storage.buckets.update.
// TODO(jbd): Add storage.buckets.delete.

// TODO(jbd): Add storage.objects.list.

// GetBucketInfo returns the specified bucket.
func (c *Client) GetBucketInfo(name string) (*BucketInfo, error) {
	panic("not yet implemented")
}

func (c *Client) NewBucket(name string) *Bucket {
	return &Bucket{name: name, s: c.s}
}

// Stat returns the meta information of an object.
func (b *Bucket) Stat(name string) (*ObjectInfo, error) {
	o, err := b.s.Objects.Get(b.name, name).Do()
	if err != nil {
		// TODO(jbd): If 404, return ErrNotExists
		return nil, err
	}
	return newObjectInfo(o), nil
}

// Put inserts/updates an object with the provided meta information.
func (b *Bucket) Put(name string, info *ObjectInfo) (*ObjectInfo, error) {
	o, err := b.s.Objects.Insert(b.name, info.toRawObject()).Do()
	if err != nil {
		return nil, err
	}
	return newObjectInfo(o), nil
}

// Delete deletes the specified object.
func (b *Bucket) Delete(name string) error {
	return b.s.Objects.Delete(b.name, name).Do()
}

// Copy copies the source object to the destination with the new
// meta information properties provided.
// The destination object is inserted into the source bucket
// if the destination object doesn't specify another bucket name.
func (b *Bucket) Copy(name string, dest *ObjectInfo) (*ObjectInfo, error) {
	if dest.Name == "" {
		return nil, errors.New("storage: missing dest name")
	}
	destBucket := dest.Bucket
	if destBucket == "" {
		destBucket = b.name
	}
	o, err := b.s.Objects.Copy(
		b.name, name, destBucket, dest.Name, dest.toRawObject()).Do()
	if err != nil {
		// TODO(jbd): Return ErrNotExists if 404.
		return nil, err
	}
	return newObjectInfo(o), nil
}

// NewReader creates a new io.ReadCloser to read the contents
// of the object.
func (b *Bucket) NewReader(name string) (io.ReadCloser, error) {
	panic("not yet impelemented")
}

// NewWriter creates a new io.WriteCloser to write to the GCS object
// identified by the specified bucket and name.
// If such object doesn't exist, it creates one. If info is not nil,
// write operation also modifies the meta information of the object.
func (b *Bucket) NewWriter(name string, info *ObjectInfo) (io.WriteCloser, error) {
	panic("not yet implemented")
}
<|endoftext|>


<commit_before>// Package etcd is an etcd v3 implementation of kv
package etcd

import (
	"context"
	"log"

	"github.com/coreos/etcd/mvcc/mvccpb"
	client "github.com/coreos/etcd/clientv3"
	"github.com/micro/go-micro/config/options"
	"github.com/micro/go-micro/store"
)

type ekv struct {
	options.Options
	kv client.KV
}

func (e *ekv) Read(keys ...string) ([]*store.Record, error) {
	var values []*mvccpb.KeyValue

	for _, key := range keys {
		keyval, err := e.kv.Get(context.Background(), key)
		if err != nil {
			return nil, err
		}

		if keyval == nil || len(keyval.Kvs) == 0 {
			return nil, store.ErrNotFound
		}

		values = append(values, keyval.Kvs...)
	}

	var records []*store.Record

	for _, kv := range values {
		records = append(records, &store.Record{
			Key:   string(kv.Key),
			Value: kv.Value,
			// TODO: implement expiry
		})
	}

	return records, nil
}

func (e *ekv) Delete(keys ...string) error {
	var gerr error
	for _, key := range keys {
		_, err := e.kv.Delete(context.Background(), key)
		if err != nil {
			gerr = err
		}
	}
	return gerr
}

func (e *ekv) Write(records ...*store.Record) error {
	var gerr error
	for _, record := range records {
		// TODO create lease to expire keys
		_, err := e.kv.Put(context.Background(), record.Key, string(record.Value))
		if err != nil {
			gerr = err
		}
	}
	return gerr
}

func (e *ekv) Sync() ([]*store.Record, error) {
	keyval, err := e.kv.Get(context.Background(), "/", client.WithPrefix())
	if err != nil {
		return nil, err
	}
	var vals []*store.Record
	if keyval == nil || len(keyval.Kvs) == 0 {
		return vals, nil
	}
	for _, keyv := range keyval.Kvs {
		vals = append(vals, &store.Record{
			Key:   string(keyv.Key),
			Value: keyv.Value,
		})
	}
	return vals, nil
}

func (e *ekv) String() string {
	return "etcd"
}

func NewStore(opts ...options.Option) store.Store {
	options := options.NewOptions(opts...)

	var endpoints []string

	if e, ok := options.Values().Get("store.nodes"); ok {
		endpoints = e.([]string)
	}

	if len(endpoints) == 0 {
		endpoints = []string{"http://127.0.0.1:2379"}
	}

	// TODO: parse addresses
	c, err := client.New(client.Config{
		Endpoints: endpoints,
	})
	if err != nil {
		log.Fatal(err)
	}

	return &ekv{
		Options: options,
		kv:      client.NewKV(c),
	}
}
<commit_msg>go fmt<commit_after>// Package etcd is an etcd v3 implementation of kv
package etcd

import (
	"context"
	"log"

	client "github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/mvcc/mvccpb"
	"github.com/micro/go-micro/config/options"
	"github.com/micro/go-micro/store"
)

type ekv struct {
	options.Options
	kv client.KV
}

func (e *ekv) Read(keys ...string) ([]*store.Record, error) {
	var values []*mvccpb.KeyValue

	for _, key := range keys {
		keyval, err := e.kv.Get(context.Background(), key)
		if err != nil {
			return nil, err
		}

		if keyval == nil || len(keyval.Kvs) == 0 {
			return nil, store.ErrNotFound
		}

		values = append(values, keyval.Kvs...)
	}

	var records []*store.Record

	for _, kv := range values {
		records = append(records, &store.Record{
			Key:   string(kv.Key),
			Value: kv.Value,
			// TODO: implement expiry
		})
	}

	return records, nil
}

func (e *ekv) Delete(keys ...string) error {
	var gerr error
	for _, key := range keys {
		_, err := e.kv.Delete(context.Background(), key)
		if err != nil {
			gerr = err
		}
	}
	return gerr
}

func (e *ekv) Write(records ...*store.Record) error {
	var gerr error
	for _, record := range records {
		// TODO create lease to expire keys
		_, err := e.kv.Put(context.Background(), record.Key, string(record.Value))
		if err != nil {
			gerr = err
		}
	}
	return gerr
}

func (e *ekv) Sync() ([]*store.Record, error) {
	keyval, err := e.kv.Get(context.Background(), "/", client.WithPrefix())
	if err != nil {
		return nil, err
	}
	var vals []*store.Record
	if keyval == nil || len(keyval.Kvs) == 0 {
		return vals, nil
	}
	for _, keyv := range keyval.Kvs {
		vals = append(vals, &store.Record{
			Key:   string(keyv.Key),
			Value: keyv.Value,
		})
	}
	return vals, nil
}

func (e *ekv) String() string {
	return "etcd"
}

func NewStore(opts ...options.Option) store.Store {
	options := options.NewOptions(opts...)

	var endpoints []string

	if e, ok := options.Values().Get("store.nodes"); ok {
		endpoints = e.([]string)
	}

	if len(endpoints) == 0 {
		endpoints = []string{"http://127.0.0.1:2379"}
	}

	// TODO: parse addresses
	c, err := client.New(client.Config{
		Endpoints: endpoints,
	})
	if err != nil {
		log.Fatal(err)
	}

	return &ekv{
		Options: options,
		kv:      client.NewKV(c),
	}
}
<|endoftext|>


<commit_before>/*
tek is an automatic tagging library for Go.
*/
package tek

import (
	"os"
	"math"
	"strings"
	"unicode"
	"io/ioutil"
	"encoding/json"
)

const (
	VERSION = "0.0.2"
)

// need to expand more and rearranged
var indonesianStopWords []string = []string{
	"di",
	"dari",
	"juga",
	"lalu",
	"dengan",
	"ke",
	"ini",
	"itu",
	"dia",
	"dan",
	"aku",
	"saya",
	"kamu",
	"anda",
	"kita",
	"mereka",
	"yang",
	"adalah",
	"walaupun",
	"jika",
	"jadi",
	"akan",
	"tetapi",
	"begitupun",
	"bilamana",
	"bagaimanapun",
	"apa",
	"untuk",
	"kepada",
	"menurut",
	"siapa",
	"dimana",
	"kapan",
	"bagaimana",
	"kenapa",
	"mengapa",
	"pada",
	"dalam",
	"ada",
	"adapun",
	"apapun",
	"ya",
	"tidak",
	"bukan",
}
var englishStopWords []string = []string{
	"a",
	"an",
	"are",
	"arent",
	"about",
	"alone",
	"also",
	"am",
	"and",
	"as",
	"at",
	"after",
	"all",
	"another",
	"any",
	"be",
	"because",
	"before",
	"beside",
	"besides",
	"between",
	"but",
	"by",
	"come",
	"does",
	"doesnt",
	"did",
	"didnt",
	"do",
	"dont",
	"we",
	"for",
	"his",
	"him",
	"himself",
	"himselves",
	"her",
	"herself",
	"herselves",
	"how",
	"our",
	"ours",
	"yours",
	"your",
	"with",
	"my",
	"you",
	"the",
	"in",
	"that",
	"thats",
	"out",
	"on",
	"off",
	"if",
	"will",
	"these",
	"there",
	"theres",
	"those",
	"he",
	"she",
	"it",
	"its",
	"us",
	"is",
	"would",
	"wouldnt",
	"was",
	"wasnt",
	"have",
	"havent",
	"were",
	"werent",
	"has",
	"hasnt",
	"wont",
	"not",
	"had",
	"hadnt",
	"isnt",
	"etc",
	"for",
	"i",
	"or",
	"of",
	"on",
	"other",
	"others",
	"so",
	"than",
	"that",
	"though",
	"to",
	"too",
	"they",
	"through",
	"until",
}

var lang string = "en"

var stopWords []string = englishStopWords

// Define your own stop words by providing a slice of string of stop words
func SetStopWords(s []string) {
	stopWords = s
}

// need to tweak these values later
var modifier map[string]float64 = map[string]float64{ "nama": 2.5, "nomina" : 1.75, "verba" : 1, "adjektiva" : 0.5, "adverbia" : 0.75, "numeralia" : 0.5 }

type Vocab struct {
	Id int `json:id"`
	Word string `json:"word"`
	Type string `json:"type"`
}

var pos []*Vocab

// Set language used, defaulted to english if not called. If argument is not "id" or "en", empty stop words will be used
// For now only support Indonesian and English stop words
func SetLang(l string) error {
	switch l {
	case "id":
		stopWords = indonesianStopWords
		fb, err := ioutil.ReadFile(os.Getenv("GOPATH") + "/src/github.com/JesusIslam/tek/pos_id.json")
		if err != nil {
			return err
		}
		err = json.Unmarshal(fb, &pos)
		if err != nil {
			return err
		}
	break
	case "en":
		stopWords = englishStopWords
	break
	default:
		// if undefined language, use empty stopwords
		stopWords = []string{}
	break
	}
	lang = l
	return nil
}

// The main method of this package, return a slice of *Info struct, sorted by their weight descending.
func GetTags(text string, num int) []*Info {
	// sequential ops, cannot go parallel
	dict := createDictionary(text)
	seq := createSeqDict(dict)
	// we could go concurrent here
	rmStopWordsChan := make(chan []string)
	createSentencesChan := make(chan [][]string)
	defer close(rmStopWordsChan)
	defer close(createSentencesChan)
	go removeStopWords(seq, stopWords, rmStopWordsChan)
	go createSentences(text, createSentencesChan)
	sens := <- createSentencesChan
	seq = <- rmStopWordsChan
	// end
	termsCount := float64(len(flatten(sens)))
	var termsInfo []*Info
	for _, term := range seq {
		// find idf of each term by counting word occurence first
		count := 0.0
		for _, sen := range sens {
			found := false
			for _, word := range sen {
				if term == word {
					found = true
				}
			}
			if found {
				count++
			}
		}
		idf := math.Log(termsCount / count)
		termsInfo = append(termsInfo, &Info{term, idf, 0.0, 0.0})
	}
	// find each word their tf-idf
	for i, term := range termsInfo {
		var count float64
		for _, sen := range sens {
			for _, word := range sen {
				word = sanitizeWord(word)
				if term.Term == word {
					count++
				}
			}
		}
		termsInfo[i].Tf = count / termsCount
		termsInfo[i].Tfidf = termsInfo[i].Tf * term.Idf
	}
	if lang == "id" {
		for i, term := range termsInfo {
			for _, vocab := range pos {
				if term.Term != vocab.Word {
					termsInfo[i].Tfidf += termsInfo[i].Tfidf * modifier["nama"]
					break
				}
				if vocab.Word == term.Term {
					if vocab.Type != "lain-lain" || vocab.Type != "pronomina" || vocab.Type != "interjeksi" || vocab.Type != "preposisi" {
						termsInfo[i].Tfidf += termsInfo[i].Tfidf * modifier[vocab.Type]
						break
					}
					break
				}
			}
		}
	}
	// sort descending by tfidf
	for i, v := range termsInfo {
		j := i - 1
		for j >= 0 && termsInfo[j].Tfidf < v.Tfidf {
			termsInfo[j+1] = termsInfo[j]
			j -= 1
		}
		termsInfo[j+1] = v
	}
	// return only N number of tags
	result := make([]*Info, num)
	copy(result, termsInfo[:num])
	// empty termsInfo
	termsInfo = []*Info{}
	return result
}

type Info struct {
	Term string
	Idf float64
	Tf float64
	Tfidf float64
}

func flatten(sens [][]string) []string {
	var flat []string
	for _, v := range sens {
		flat = append(flat, v...)
	}
	return flat
}

func createSentences(text string, createSentencesChan chan<- [][]string) {
	text = strings.TrimSpace(text)
	words := strings.Fields(text)
	var sentence []string
	var sentences [][]string
	for _, word := range words {
		// lowercase them FIX 1
		word = strings.ToLower(word)
		// if there isn't . ? or !, append to sentence. If found, also append (and remove the non alphanumerics) but reset the sentence
		if strings.ContainsRune(word, '.') || strings.ContainsRune(word, '!') || strings.ContainsRune(word, '?') {
			word = strings.Map(func (r rune) rune {
				if r == '.' || r == '!' || r == '?' {
					return -1
				}
				return r
			}, word)
			// sanitize them FIX 2
			word = sanitizeWord(word)
			sentence = append(sentence, word)
			sentences = append(sentences, sentence)
			sentence = []string{}
		} else {
			// sanitize them FIX 2
			word = sanitizeWord(word)
			sentence = append(sentence, word)
		}
	}
	if len(sentence) > 0 {
		sentences = append(sentences, sentence)
	}
	sentences = uniqSentences(sentences)
	createSentencesChan <- sentences
}

func uniqSentences(sentences [][]string) [][]string {
	var z []string
	for _, v := range sentences {
		j := strings.Join(v, " ")
		z = append(z, j)
	}
	m := make(map[string]bool)
	var uniq []string
	for _, v := range z {
		if m[v] {
			continue
		}
		uniq = append(uniq, v)
		m[v] = true
	}
	var unique [][]string
	for _, v := range uniq {
		unique = append(unique, strings.Fields(v))
	}
	return unique
}

func removeStopWords(seq []string, StopWords []string, rmStopWordsChan chan<- []string) {
	var res []string
	for _, v := range seq {
		stopWord := false
		for _, x := range StopWords {
			if v == x {
				stopWord = true
				break
			}
		}
		if !stopWord {
			res = append(res, v)
		}
	}
	rmStopWordsChan <- res
}

func sanitizeWord(word string) string {
	word = strings.ToLower(word)
	var prev rune
	word = strings.Map(func (r rune) rune {
		// don't remove '-' if it exists after alphanumerics
		if r == '-' && ((prev >= '0' && prev <= '9') || (prev >= 'a' && prev <= 'z') || prev == 'ä' || prev == 'ö' || prev == 'ü' || prev == 'ß' || prev == 'é') {
			return r
		}
		if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !unicode.IsSpace(r) {
			return -1
		}
		prev = r
		return r
	}, word)
	return word
}

func createSeqDict(dict map[string]int) []string {
	var seq []string
	for term, _ := range dict {
		seq = append(seq, term)
	}
	return seq
}

func createDictionary(text string) map[string]int {
	// trim all spaces
	text = strings.TrimSpace(text)
	// lowercase the text
	text = strings.ToLower(text)
	// remove all non alphanumerics but spaces
	var prev rune
	text = strings.Map(func (r rune) rune {
		// don't remove '-' if it exists after alphanumerics
		if r == '-' && ((prev >= '0' && prev <= '9') || (prev >= 'a' && prev <= 'z') || prev == 'ä' || prev == 'ö' || prev == 'ü' || prev == 'ß' || prev == 'é') {
			return r
		}
		if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !unicode.IsSpace(r) {
			return -1
		}
		prev = r
		return r
	}, text)
	// TRYING TO FIX BUG : remove all double spaces left
	text = strings.Replace(text, "  ", " ", -1)
	// turn it into bag of words
	words := strings.Fields(text)
	// turn it into dictionary
	dict := make(map[string]int)
	i := 1
	for _, word := range words {
		if dict[word] == 0 {
			dict[word] = i
			i++
		}
	}
	return dict
}<commit_msg>Adding error guard<commit_after>/*
tek is an automatic tagging library for Go.
*/
package tek

import (
	"os"
	"math"
	"strings"
	"unicode"
	"io/ioutil"
	"encoding/json"
)

const (
	VERSION = "0.0.2"
)

// need to expand more and rearranged
var indonesianStopWords []string = []string{
	"di",
	"dari",
	"juga",
	"lalu",
	"dengan",
	"ke",
	"ini",
	"itu",
	"dia",
	"dan",
	"aku",
	"saya",
	"kamu",
	"anda",
	"kita",
	"mereka",
	"yang",
	"adalah",
	"walaupun",
	"jika",
	"jadi",
	"akan",
	"tetapi",
	"begitupun",
	"bilamana",
	"bagaimanapun",
	"apa",
	"untuk",
	"kepada",
	"menurut",
	"siapa",
	"dimana",
	"kapan",
	"bagaimana",
	"kenapa",
	"mengapa",
	"pada",
	"dalam",
	"ada",
	"adapun",
	"apapun",
	"ya",
	"tidak",
	"bukan",
}
var englishStopWords []string = []string{
	"a",
	"an",
	"are",
	"arent",
	"about",
	"alone",
	"also",
	"am",
	"and",
	"as",
	"at",
	"after",
	"all",
	"another",
	"any",
	"be",
	"because",
	"before",
	"beside",
	"besides",
	"between",
	"but",
	"by",
	"come",
	"does",
	"doesnt",
	"did",
	"didnt",
	"do",
	"dont",
	"we",
	"for",
	"his",
	"him",
	"himself",
	"himselves",
	"her",
	"herself",
	"herselves",
	"how",
	"our",
	"ours",
	"yours",
	"your",
	"with",
	"my",
	"you",
	"the",
	"in",
	"that",
	"thats",
	"out",
	"on",
	"off",
	"if",
	"will",
	"these",
	"there",
	"theres",
	"those",
	"he",
	"she",
	"it",
	"its",
	"us",
	"is",
	"would",
	"wouldnt",
	"was",
	"wasnt",
	"have",
	"havent",
	"were",
	"werent",
	"has",
	"hasnt",
	"wont",
	"not",
	"had",
	"hadnt",
	"isnt",
	"etc",
	"for",
	"i",
	"or",
	"of",
	"on",
	"other",
	"others",
	"so",
	"than",
	"that",
	"though",
	"to",
	"too",
	"they",
	"through",
	"until",
}

var lang string = "en"

var stopWords []string = englishStopWords

// Define your own stop words by providing a slice of string of stop words
func SetStopWords(s []string) {
	stopWords = s
}

// need to tweak these values later
// var modifier map[string]float64 = map[string]float64{ "nama": 2.5, "nomina" : 1.75, "verba" : 1, "adjektiva" : 0.5, "adverbia" : 0.75, "numeralia" : 0.5 }
var modifier map[string]float64 = map[string]float64{ "nama": 3.5, "nomina" : 3.0, "verba" : 2.0, "adjektiva" : 1.0, "adverbia" : 0.25, "numeralia" : 0.5 }

type Vocab struct {
	Id int `json:id"`
	Word string `json:"word"`
	Type string `json:"type"`
}

var pos []*Vocab

// Set language used, defaulted to english if not called. If argument is not "id" or "en", empty stop words will be used
// For now only support Indonesian and English stop words
func SetLang(l string) error {
	switch l {
	case "id":
		stopWords = indonesianStopWords
		fb, err := ioutil.ReadFile(os.Getenv("GOPATH") + "/src/github.com/JesusIslam/tek/pos_id.json")
		if err != nil {
			return err
		}
		err = json.Unmarshal(fb, &pos)
		if err != nil {
			return err
		}
	break
	case "en":
		stopWords = englishStopWords
	break
	default:
		// if undefined language, use empty stopwords
		stopWords = []string{}
	break
	}
	lang = l
	return nil
}

// The main method of this package, return a slice of *Info struct, sorted by their weight descending.
func GetTags(text string, num int) []*Info {
	// sequential ops, cannot go parallel
	dict := createDictionary(text)
	seq := createSeqDict(dict)
	// we could go concurrent here
	rmStopWordsChan := make(chan []string)
	createSentencesChan := make(chan [][]string)
	defer close(rmStopWordsChan)
	defer close(createSentencesChan)
	go removeStopWords(seq, stopWords, rmStopWordsChan)
	go createSentences(text, createSentencesChan)
	sens := <- createSentencesChan
	seq = <- rmStopWordsChan
	// end
	termsCount := float64(len(flatten(sens)))
	var termsInfo []*Info
	for _, term := range seq {
		// find idf of each term by counting word occurence first
		count := 0.0
		for _, sen := range sens {
			found := false
			for _, word := range sen {
				if term == word {
					found = true
				}
			}
			if found {
				count++
			}
		}
		idf := math.Log(termsCount / count)
		termsInfo = append(termsInfo, &Info{term, idf, 0.0, 0.0})
	}
	// find each word their tf-idf
	for i, term := range termsInfo {
		var count float64
		for _, sen := range sens {
			for _, word := range sen {
				word = sanitizeWord(word)
				if term.Term == word {
					count++
				}
			}
		}
		termsInfo[i].Tf = count / termsCount
		termsInfo[i].Tfidf = termsInfo[i].Tf * term.Idf
	}
	if lang == "id" {
		for i, term := range termsInfo {
			for _, vocab := range pos {
				if term.Term != vocab.Word {
					termsInfo[i].Tfidf += termsInfo[i].Tfidf * modifier["nama"]
					break
				}
				if vocab.Word == term.Term {
					if vocab.Type != "lain-lain" || vocab.Type != "pronomina" || vocab.Type != "interjeksi" || vocab.Type != "preposisi" {
						termsInfo[i].Tfidf += termsInfo[i].Tfidf * modifier[vocab.Type]
						break
					}
					break
				}
			}
		}
	}
	// sort descending by tfidf
	for i, v := range termsInfo {
		j := i - 1
		for j >= 0 && termsInfo[j].Tfidf < v.Tfidf {
			termsInfo[j+1] = termsInfo[j]
			j -= 1
		}
		termsInfo[j+1] = v
	}
	// out of range error guard
	if num >= len(termsInfo) {
		num = len(termsInfo)
	}
	// return only N number of tags
	result := make([]*Info, num)
	copy(result, termsInfo[:num])
	// empty termsInfo
	termsInfo = []*Info{}
	return result
}

type Info struct {
	Term string
	Idf float64
	Tf float64
	Tfidf float64
}

func flatten(sens [][]string) []string {
	var flat []string
	for _, v := range sens {
		flat = append(flat, v...)
	}
	return flat
}

func createSentences(text string, createSentencesChan chan<- [][]string) {
	text = strings.TrimSpace(text)
	words := strings.Fields(text)
	var sentence []string
	var sentences [][]string
	for _, word := range words {
		// lowercase them FIX 1
		word = strings.ToLower(word)
		// if there isn't . ? or !, append to sentence. If found, also append (and remove the non alphanumerics) but reset the sentence
		if strings.ContainsRune(word, '.') || strings.ContainsRune(word, '!') || strings.ContainsRune(word, '?') {
			word = strings.Map(func (r rune) rune {
				if r == '.' || r == '!' || r == '?' {
					return -1
				}
				return r
			}, word)
			// sanitize them FIX 2
			word = sanitizeWord(word)
			sentence = append(sentence, word)
			sentences = append(sentences, sentence)
			sentence = []string{}
		} else {
			// sanitize them FIX 2
			word = sanitizeWord(word)
			sentence = append(sentence, word)
		}
	}
	if len(sentence) > 0 {
		sentences = append(sentences, sentence)
	}
	sentences = uniqSentences(sentences)
	createSentencesChan <- sentences
}

func uniqSentences(sentences [][]string) [][]string {
	var z []string
	for _, v := range sentences {
		j := strings.Join(v, " ")
		z = append(z, j)
	}
	m := make(map[string]bool)
	var uniq []string
	for _, v := range z {
		if m[v] {
			continue
		}
		uniq = append(uniq, v)
		m[v] = true
	}
	var unique [][]string
	for _, v := range uniq {
		unique = append(unique, strings.Fields(v))
	}
	return unique
}

func removeStopWords(seq []string, StopWords []string, rmStopWordsChan chan<- []string) {
	var res []string
	for _, v := range seq {
		stopWord := false
		for _, x := range StopWords {
			if v == x {
				stopWord = true
				break
			}
		}
		if !stopWord {
			res = append(res, v)
		}
	}
	rmStopWordsChan <- res
}

func sanitizeWord(word string) string {
	word = strings.ToLower(word)
	var prev rune
	word = strings.Map(func (r rune) rune {
		// don't remove '-' if it exists after alphanumerics
		if r == '-' && ((prev >= '0' && prev <= '9') || (prev >= 'a' && prev <= 'z') || prev == 'ä' || prev == 'ö' || prev == 'ü' || prev == 'ß' || prev == 'é') {
			return r
		}
		if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !unicode.IsSpace(r) {
			return -1
		}
		prev = r
		return r
	}, word)
	return word
}

func createSeqDict(dict map[string]int) []string {
	var seq []string
	for term, _ := range dict {
		seq = append(seq, term)
	}
	return seq
}

func createDictionary(text string) map[string]int {
	// trim all spaces
	text = strings.TrimSpace(text)
	// lowercase the text
	text = strings.ToLower(text)
	// remove all non alphanumerics but spaces
	var prev rune
	text = strings.Map(func (r rune) rune {
		// don't remove '-' if it exists after alphanumerics
		if r == '-' && ((prev >= '0' && prev <= '9') || (prev >= 'a' && prev <= 'z') || prev == 'ä' || prev == 'ö' || prev == 'ü' || prev == 'ß' || prev == 'é') {
			return r
		}
		if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !unicode.IsSpace(r) {
			return -1
		}
		prev = r
		return r
	}, text)
	// TRYING TO FIX BUG : remove all double spaces left
	text = strings.Replace(text, "  ", " ", -1)
	// turn it into bag of words
	words := strings.Fields(text)
	// turn it into dictionary
	dict := make(map[string]int)
	i := 1
	for _, word := range words {
		if dict[word] == 0 {
			dict[word] = i
			i++
		}
	}
	return dict
}<|endoftext|>


<commit_before>package s3api

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/golang/protobuf/jsonpb"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
)

type Action string

const (
	ACTION_READ  = "Read"
	ACTION_WRITE = "Write"
	ACTION_ADMIN = "Admin"
)

type Iam interface {
	Check(f http.HandlerFunc, actions ...Action) http.HandlerFunc
}

type IdentityAccessManagement struct {
	identities []*Identity
	domain     string
}

type Identity struct {
	Name        string
	Credentials []*Credential
	Actions     []Action
}

type Credential struct {
	AccessKey string
	SecretKey string
}

func NewIdentityAccessManagement(fileName string, domain string) *IdentityAccessManagement {
	iam := &IdentityAccessManagement{
		domain: domain,
	}
	if fileName == "" {
		return iam
	}
	if err := iam.loadS3ApiConfiguration(fileName); err != nil {
		glog.Fatalf("fail to load config file %s: %v", fileName, err)
	}
	return iam
}

func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) error {

	s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}

	rawData, readErr := ioutil.ReadFile(fileName)
	if readErr != nil {
		glog.Warningf("fail to read %s : %v", fileName, readErr)
		return fmt.Errorf("fail to read %s : %v", fileName, readErr)
	}

	glog.V(1).Infof("maybeLoadVolumeInfo Unmarshal volume info %v", fileName)
	if err := jsonpb.Unmarshal(bytes.NewReader(rawData), s3ApiConfiguration); err != nil {
		glog.Warningf("unmarshal error: %v", err)
		return fmt.Errorf("unmarshal %s error: %v", fileName, err)
	}

	for _, ident := range s3ApiConfiguration.Identities {
		t := &Identity{
			Name:        ident.Name,
			Credentials: nil,
			Actions:     nil,
		}
		for _, action := range ident.Actions {
			t.Actions = append(t.Actions, Action(action))
		}
		for _, cred := range ident.Credentials {
			t.Credentials = append(t.Credentials, &Credential{
				AccessKey: cred.AccessKey,
				SecretKey: cred.SecretKey,
			})
		}
		iam.identities = append(iam.identities, t)
	}

	return nil
}

func (iam *IdentityAccessManagement) isEnabled() bool {

	return len(iam.identities) > 0
}

func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {

	for _, ident := range iam.identities {
		for _, cred := range ident.Credentials {
			if cred.AccessKey == accessKey {
				return ident, cred, true
			}
		}
	}
	return nil, nil, false
}

func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) http.HandlerFunc {

	if !iam.isEnabled() {
		return f
	}

	return func(w http.ResponseWriter, r *http.Request) {
		errCode := iam.authRequest(r, action)
		if errCode == ErrNone {
			f(w, r)
			return
		}
		writeErrorResponse(w, errCode, r.URL)
	}
}

// check whether the request has valid access keys
func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) ErrorCode {
	var identity *Identity
	var s3Err ErrorCode
	switch getRequestAuthType(r) {
	case authTypeStreamingSigned:
		return ErrNone
	case authTypeUnknown:
		glog.V(3).Infof("unknown auth type")
		return ErrAccessDenied
	case authTypePresignedV2, authTypeSignedV2:
		glog.V(3).Infof("v2 auth type")
		identity, s3Err = iam.isReqAuthenticatedV2(r)
	case authTypeSigned, authTypePresigned:
		glog.V(3).Infof("v4 auth type")
		identity, s3Err = iam.reqSignatureV4Verify(r)
	case authTypePostPolicy:
		glog.V(3).Infof("post policy auth type")
		return ErrNotImplemented
	case authTypeJWT:
		glog.V(3).Infof("jwt auth type")
		return ErrNotImplemented
	case authTypeAnonymous:
		return ErrAccessDenied
	default:
		return ErrNotImplemented
	}

	glog.V(3).Infof("auth error: %v", s3Err)
	if s3Err != ErrNone {
		return s3Err
	}

	glog.V(3).Infof("user name: %v actions: %v", identity.Name, identity.Actions)

	bucket, _ := getBucketAndObject(r)

	if !identity.canDo(action, bucket) {
		return ErrAccessDenied
	}

	return ErrNone

}

func (identity *Identity) canDo(action Action, bucket string) bool {
	for _, a := range identity.Actions {
		if a == "Admin" {
			return true
		}
	}
	for _, a := range identity.Actions {
		if a == action {
			return true
		}
	}
	if bucket == "" {
		return false
	}
	limitedByBucket := string(action) + ":" + bucket
	for _, a := range identity.Actions {
		if string(a) == limitedByBucket {
			return true
		}
	}
	return false
}
<commit_msg>S3: configurable access for anonymous user<commit_after>package s3api

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/golang/protobuf/jsonpb"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
)

type Action string

const (
	ACTION_READ  = "Read"
	ACTION_WRITE = "Write"
	ACTION_ADMIN = "Admin"
)

type Iam interface {
	Check(f http.HandlerFunc, actions ...Action) http.HandlerFunc
}

type IdentityAccessManagement struct {
	identities []*Identity
	domain     string
}

type Identity struct {
	Name        string
	Credentials []*Credential
	Actions     []Action
}

type Credential struct {
	AccessKey string
	SecretKey string
}

func NewIdentityAccessManagement(fileName string, domain string) *IdentityAccessManagement {
	iam := &IdentityAccessManagement{
		domain: domain,
	}
	if fileName == "" {
		return iam
	}
	if err := iam.loadS3ApiConfiguration(fileName); err != nil {
		glog.Fatalf("fail to load config file %s: %v", fileName, err)
	}
	return iam
}

func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) error {

	s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}

	rawData, readErr := ioutil.ReadFile(fileName)
	if readErr != nil {
		glog.Warningf("fail to read %s : %v", fileName, readErr)
		return fmt.Errorf("fail to read %s : %v", fileName, readErr)
	}

	glog.V(1).Infof("maybeLoadVolumeInfo Unmarshal volume info %v", fileName)
	if err := jsonpb.Unmarshal(bytes.NewReader(rawData), s3ApiConfiguration); err != nil {
		glog.Warningf("unmarshal error: %v", err)
		return fmt.Errorf("unmarshal %s error: %v", fileName, err)
	}

	for _, ident := range s3ApiConfiguration.Identities {
		t := &Identity{
			Name:        ident.Name,
			Credentials: nil,
			Actions:     nil,
		}
		for _, action := range ident.Actions {
			t.Actions = append(t.Actions, Action(action))
		}
		for _, cred := range ident.Credentials {
			t.Credentials = append(t.Credentials, &Credential{
				AccessKey: cred.AccessKey,
				SecretKey: cred.SecretKey,
			})
		}
		iam.identities = append(iam.identities, t)
	}

	return nil
}

func (iam *IdentityAccessManagement) isEnabled() bool {

	return len(iam.identities) > 0
}

func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {

	for _, ident := range iam.identities {
		for _, cred := range ident.Credentials {
			if cred.AccessKey == accessKey {
				return ident, cred, true
			}
		}
	}
	return nil, nil, false
}

func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, found bool) {

	for _, ident := range iam.identities {
		if ident.Name == "anonymous" {
			return ident, true
		}
	}
	return nil, false
}

func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) http.HandlerFunc {

	if !iam.isEnabled() {
		return f
	}

	return func(w http.ResponseWriter, r *http.Request) {
		errCode := iam.authRequest(r, action)
		if errCode == ErrNone {
			f(w, r)
			return
		}
		writeErrorResponse(w, errCode, r.URL)
	}
}

// check whether the request has valid access keys
func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) ErrorCode {
	var identity *Identity
	var s3Err ErrorCode
	var found bool
	switch getRequestAuthType(r) {
	case authTypeStreamingSigned:
		return ErrNone
	case authTypeUnknown:
		glog.V(3).Infof("unknown auth type")
		return ErrAccessDenied
	case authTypePresignedV2, authTypeSignedV2:
		glog.V(3).Infof("v2 auth type")
		identity, s3Err = iam.isReqAuthenticatedV2(r)
	case authTypeSigned, authTypePresigned:
		glog.V(3).Infof("v4 auth type")
		identity, s3Err = iam.reqSignatureV4Verify(r)
	case authTypePostPolicy:
		glog.V(3).Infof("post policy auth type")
		return ErrNotImplemented
	case authTypeJWT:
		glog.V(3).Infof("jwt auth type")
		return ErrNotImplemented
	case authTypeAnonymous:
		identity, found = iam.lookupAnonymous()
		if !found {
			return ErrAccessDenied
		}
	default:
		return ErrNotImplemented
	}

	glog.V(3).Infof("auth error: %v", s3Err)
	if s3Err != ErrNone {
		return s3Err
	}

	glog.V(3).Infof("user name: %v actions: %v", identity.Name, identity.Actions)

	bucket, _ := getBucketAndObject(r)

	if !identity.canDo(action, bucket) {
		return ErrAccessDenied
	}

	return ErrNone

}

func (identity *Identity) canDo(action Action, bucket string) bool {
	for _, a := range identity.Actions {
		if a == "Admin" {
			return true
		}
	}
	for _, a := range identity.Actions {
		if a == action {
			return true
		}
	}
	if bucket == "" {
		return false
	}
	limitedByBucket := string(action) + ":" + bucket
	for _, a := range identity.Actions {
		if string(a) == limitedByBucket {
			return true
		}
	}
	return false
}
<|endoftext|>


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

// HTTP file system request handler

package http

import (
	"fmt"
	"io"
	"mime"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
	"utf8"
)

// Heuristic: b is text if it is valid UTF-8 and doesn't
// contain any unprintable ASCII or Unicode characters.
func isText(b []byte) bool {
	for len(b) > 0 && utf8.FullRune(b) {
		rune, size := utf8.DecodeRune(b)
		if size == 1 && rune == utf8.RuneError {
			// decoding error
			return false
		}
		if 0x7F <= rune && rune <= 0x9F {
			return false
		}
		if rune < ' ' {
			switch rune {
			case '\n', '\r', '\t':
				// okay
			default:
				// binary garbage
				return false
			}
		}
		b = b[size:]
	}
	return true
}

func dirList(w ResponseWriter, f *os.File) {
	fmt.Fprintf(w, "
\n")
	for {
		dirs, err := f.Readdir(100)
		if err != nil || len(dirs) == 0 {
			break
		}
		for _, d := range dirs {
			name := d.Name
			if d.IsDirectory() {
				name += "/"
			}
			// TODO htmlescape
			fmt.Fprintf(w, "%s\n", name, name)
		}
	}
	fmt.Fprintf(w, "
\n")
}

func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
	const indexPage = "/index.html"

	// redirect .../index.html to .../
	if strings.HasSuffix(r.URL.Path, indexPage) {
		Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len(indexPage)+1], StatusMovedPermanently)
		return
	}

	f, err := os.Open(name, os.O_RDONLY, 0)
	if err != nil {
		// TODO expose actual error?
		NotFound(w, r)
		return
	}
	defer f.Close()

	d, err1 := f.Stat()
	if err1 != nil {
		// TODO expose actual error?
		NotFound(w, r)
		return
	}

	if redirect {
		// redirect to canonical path: / at end of directory url
		// r.URL.Path always begins with /
		url := r.URL.Path
		if d.IsDirectory() {
			if url[len(url)-1] != '/' {
				Redirect(w, r, url+"/", StatusMovedPermanently)
				return
			}
		} else {
			if url[len(url)-1] == '/' {
				Redirect(w, r, url[0:len(url)-1], StatusMovedPermanently)
				return
			}
		}
	}

	if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
		w.WriteHeader(StatusNotModified)
		return
	}
	w.Header().Set("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat))

	// use contents of index.html for directory, if present
	if d.IsDirectory() {
		index := name + filepath.FromSlash(indexPage)
		ff, err := os.Open(index, os.O_RDONLY, 0)
		if err == nil {
			defer ff.Close()
			dd, err := ff.Stat()
			if err == nil {
				name = index
				d = dd
				f = ff
			}
		}
	}

	if d.IsDirectory() {
		dirList(w, f)
		return
	}

	// serve file
	size := d.Size
	code := StatusOK

	// use extension to find content type.
	ext := filepath.Ext(name)
	if ctype := mime.TypeByExtension(ext); ctype != "" {
		w.Header().Set("Content-Type", ctype)
	} else {
		// read first chunk to decide between utf-8 text and binary
		var buf [1024]byte
		n, _ := io.ReadFull(f, buf[:])
		b := buf[:n]
		if isText(b) {
			w.Header().Set("Content-Type", "text-plain; charset=utf-8")
		} else {
			w.Header().Set("Content-Type", "application/octet-stream") // generic binary
		}
		f.Seek(0, 0) // rewind to output whole file
	}

	// handle Content-Range header.
	// TODO(adg): handle multiple ranges
	ranges, err := parseRange(r.Header.Get("Range"), size)
	if err != nil || len(ranges) > 1 {
		Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
		return
	}
	if len(ranges) == 1 {
		ra := ranges[0]
		if _, err := f.Seek(ra.start, 0); err != nil {
			Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
			return
		}
		size = ra.length
		code = StatusPartialContent
		w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
	}

	w.Header().Set("Accept-Ranges", "bytes")
	w.Header().Set("Content-Length", strconv.Itoa64(size))

	w.WriteHeader(code)

	if r.Method != "HEAD" {
		io.Copyn(w, f, size)
	}
}

// ServeFile replies to the request with the contents of the named file or directory.
func ServeFile(w ResponseWriter, r *Request, name string) {
	serveFile(w, r, name, false)
}

type fileHandler struct {
	root   string
	prefix string
}

// FileServer returns a handler that serves HTTP requests
// with the contents of the file system rooted at root.
// It strips prefix from the incoming requests before
// looking up the file name in the file system.
func FileServer(root, prefix string) Handler { return &fileHandler{root, prefix} }

func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
	path := r.URL.Path
	if !strings.HasPrefix(path, f.prefix) {
		NotFound(w, r)
		return
	}
	path = path[len(f.prefix):]
	serveFile(w, r, filepath.Join(f.root, filepath.FromSlash(path)), true)
}

// httpRange specifies the byte range to be sent to the client.
type httpRange struct {
	start, length int64
}

// parseRange parses a Range header string as per RFC 2616.
func parseRange(s string, size int64) ([]httpRange, os.Error) {
	if s == "" {
		return nil, nil // header not present
	}
	const b = "bytes="
	if !strings.HasPrefix(s, b) {
		return nil, os.NewError("invalid range")
	}
	var ranges []httpRange
	for _, ra := range strings.Split(s[len(b):], ",", -1) {
		i := strings.Index(ra, "-")
		if i < 0 {
			return nil, os.NewError("invalid range")
		}
		start, end := ra[:i], ra[i+1:]
		var r httpRange
		if start == "" {
			// If no start is specified, end specifies the
			// range start relative to the end of the file.
			i, err := strconv.Atoi64(end)
			if err != nil {
				return nil, os.NewError("invalid range")
			}
			if i > size {
				i = size
			}
			r.start = size - i
			r.length = size - r.start
		} else {
			i, err := strconv.Atoi64(start)
			if err != nil || i > size || i < 0 {
				return nil, os.NewError("invalid range")
			}
			r.start = i
			if end == "" {
				// If no end is specified, range extends to end of the file.
				r.length = size - r.start
			} else {
				i, err := strconv.Atoi64(end)
				if err != nil || r.start > i {
					return nil, os.NewError("invalid range")
				}
				if i >= size {
					i = size - 1
				}
				r.length = i - r.start + 1
			}
		}
		ranges = append(ranges, r)
	}
	return ranges, nil
}
<commit_msg>http: avoid crash when asked for multiple file ranges<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// HTTP file system request handler

package http

import (
	"fmt"
	"io"
	"mime"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
	"utf8"
)

// Heuristic: b is text if it is valid UTF-8 and doesn't
// contain any unprintable ASCII or Unicode characters.
func isText(b []byte) bool {
	for len(b) > 0 && utf8.FullRune(b) {
		rune, size := utf8.DecodeRune(b)
		if size == 1 && rune == utf8.RuneError {
			// decoding error
			return false
		}
		if 0x7F <= rune && rune <= 0x9F {
			return false
		}
		if rune < ' ' {
			switch rune {
			case '\n', '\r', '\t':
				// okay
			default:
				// binary garbage
				return false
			}
		}
		b = b[size:]
	}
	return true
}

func dirList(w ResponseWriter, f *os.File) {
	fmt.Fprintf(w, "
\n")
	for {
		dirs, err := f.Readdir(100)
		if err != nil || len(dirs) == 0 {
			break
		}
		for _, d := range dirs {
			name := d.Name
			if d.IsDirectory() {
				name += "/"
			}
			// TODO htmlescape
			fmt.Fprintf(w, "%s\n", name, name)
		}
	}
	fmt.Fprintf(w, "
\n")
}

func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
	const indexPage = "/index.html"

	// redirect .../index.html to .../
	if strings.HasSuffix(r.URL.Path, indexPage) {
		Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len(indexPage)+1], StatusMovedPermanently)
		return
	}

	f, err := os.Open(name, os.O_RDONLY, 0)
	if err != nil {
		// TODO expose actual error?
		NotFound(w, r)
		return
	}
	defer f.Close()

	d, err1 := f.Stat()
	if err1 != nil {
		// TODO expose actual error?
		NotFound(w, r)
		return
	}

	if redirect {
		// redirect to canonical path: / at end of directory url
		// r.URL.Path always begins with /
		url := r.URL.Path
		if d.IsDirectory() {
			if url[len(url)-1] != '/' {
				Redirect(w, r, url+"/", StatusMovedPermanently)
				return
			}
		} else {
			if url[len(url)-1] == '/' {
				Redirect(w, r, url[0:len(url)-1], StatusMovedPermanently)
				return
			}
		}
	}

	if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
		w.WriteHeader(StatusNotModified)
		return
	}
	w.Header().Set("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat))

	// use contents of index.html for directory, if present
	if d.IsDirectory() {
		index := name + filepath.FromSlash(indexPage)
		ff, err := os.Open(index, os.O_RDONLY, 0)
		if err == nil {
			defer ff.Close()
			dd, err := ff.Stat()
			if err == nil {
				name = index
				d = dd
				f = ff
			}
		}
	}

	if d.IsDirectory() {
		dirList(w, f)
		return
	}

	// serve file
	size := d.Size
	code := StatusOK

	// use extension to find content type.
	ext := filepath.Ext(name)
	if ctype := mime.TypeByExtension(ext); ctype != "" {
		w.Header().Set("Content-Type", ctype)
	} else {
		// read first chunk to decide between utf-8 text and binary
		var buf [1024]byte
		n, _ := io.ReadFull(f, buf[:])
		b := buf[:n]
		if isText(b) {
			w.Header().Set("Content-Type", "text-plain; charset=utf-8")
		} else {
			w.Header().Set("Content-Type", "application/octet-stream") // generic binary
		}
		f.Seek(0, 0) // rewind to output whole file
	}

	// handle Content-Range header.
	// TODO(adg): handle multiple ranges
	ranges, err := parseRange(r.Header.Get("Range"), size)
	if err == nil && len(ranges) > 1 {
		err = os.ErrorString("multiple ranges not supported")
	}
	if err != nil {
		Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
		return
	}
	if len(ranges) == 1 {
		ra := ranges[0]
		if _, err := f.Seek(ra.start, 0); err != nil {
			Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
			return
		}
		size = ra.length
		code = StatusPartialContent
		w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
	}

	w.Header().Set("Accept-Ranges", "bytes")
	w.Header().Set("Content-Length", strconv.Itoa64(size))

	w.WriteHeader(code)

	if r.Method != "HEAD" {
		io.Copyn(w, f, size)
	}
}

// ServeFile replies to the request with the contents of the named file or directory.
func ServeFile(w ResponseWriter, r *Request, name string) {
	serveFile(w, r, name, false)
}

type fileHandler struct {
	root   string
	prefix string
}

// FileServer returns a handler that serves HTTP requests
// with the contents of the file system rooted at root.
// It strips prefix from the incoming requests before
// looking up the file name in the file system.
func FileServer(root, prefix string) Handler { return &fileHandler{root, prefix} }

func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
	path := r.URL.Path
	if !strings.HasPrefix(path, f.prefix) {
		NotFound(w, r)
		return
	}
	path = path[len(f.prefix):]
	serveFile(w, r, filepath.Join(f.root, filepath.FromSlash(path)), true)
}

// httpRange specifies the byte range to be sent to the client.
type httpRange struct {
	start, length int64
}

// parseRange parses a Range header string as per RFC 2616.
func parseRange(s string, size int64) ([]httpRange, os.Error) {
	if s == "" {
		return nil, nil // header not present
	}
	const b = "bytes="
	if !strings.HasPrefix(s, b) {
		return nil, os.NewError("invalid range")
	}
	var ranges []httpRange
	for _, ra := range strings.Split(s[len(b):], ",", -1) {
		i := strings.Index(ra, "-")
		if i < 0 {
			return nil, os.NewError("invalid range")
		}
		start, end := ra[:i], ra[i+1:]
		var r httpRange
		if start == "" {
			// If no start is specified, end specifies the
			// range start relative to the end of the file.
			i, err := strconv.Atoi64(end)
			if err != nil {
				return nil, os.NewError("invalid range")
			}
			if i > size {
				i = size
			}
			r.start = size - i
			r.length = size - r.start
		} else {
			i, err := strconv.Atoi64(start)
			if err != nil || i > size || i < 0 {
				return nil, os.NewError("invalid range")
			}
			r.start = i
			if end == "" {
				// If no end is specified, range extends to end of the file.
				r.length = size - r.start
			} else {
				i, err := strconv.Atoi64(end)
				if err != nil || r.start > i {
					return nil, os.NewError("invalid range")
				}
				if i >= size {
					i = size - 1
				}
				r.length = i - r.start + 1
			}
		}
		ranges = append(ranges, r)
	}
	return ranges, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Tamás Gulácsi. All rights reserved.
// Use of this source code is governed by The MIT License
// found in the accompanying LICENSE file.

package ora

/*
#include <stdlib.h>
#include <oci.h>
#include "version.h"
*/
import "C"
import (
	"time"
	"unsafe"
)

type nullp struct {
	p []C.sb2
}

func (np *nullp) Pointer() *C.sb2 {
	if np.p == nil {
		np.p = (*((*[1]C.sb2)(C.malloc(2))))[:1]
	}
	return &np.p[0]
}

func (np *nullp) IsNull() bool {
	return np.p[0] < 0
}

func (np *nullp) Free() {
	if np.p != nil {
		C.free(unsafe.Pointer(&np.p[0]))
	}
}

func (np *nullp) Set(isNull bool) {
	np.p[0] = 0
	if isNull {
		np.p[0] = -1
		np.p = nil
	}
}

type lobLocatorp struct {
	p []*C.OCILobLocator
}

func (ll *lobLocatorp) Pointer() **C.OCILobLocator {
	if ll.p == nil {
		ll.p = (*((*[1]*C.OCILobLocator)(C.malloc(C.size_t(ll.Size())))))[:1]
	}
	return &ll.p[0]
}
func (ll *lobLocatorp) Value() *C.OCILobLocator {
	if ll.p == nil {
		return nil
	}
	return ll.p[0]
}
func (ll *lobLocatorp) Size() int {
	return int(C.sof_LobLocatorp)
}
func (ll *lobLocatorp) Free() {
	if ll.p != nil {
		C.free(unsafe.Pointer(&ll.p[0]))
		ll.p = nil
	}
}

type dateTimep struct {
	p    []*C.OCIDateTime
	zone []byte
}

func (dt *dateTimep) Pointer() **C.OCIDateTime {
	if dt.p == nil {
		dt.p = (*((*[1]*C.OCIDateTime)(C.malloc(C.size_t(dt.Size())))))[:1]
	}
	return &dt.p[0]
}
func (dt *dateTimep) Value() *C.OCIDateTime {
	if dt.p == nil {
		return nil
	}
	return dt.p[0]
}
func (dt *dateTimep) Size() int { return int(C.sof_DateTimep) }
func (dt *dateTimep) Free() {
	if dt.p != nil {
		if dt.p[0] != nil {
			C.OCIDescriptorFree(
				unsafe.Pointer(dt.p[0]),  //void     *descp,
				C.OCI_DTYPE_TIMESTAMP_TZ) //ub4      type );
			dt.p[0] = nil
		}
		C.free(unsafe.Pointer(&dt.p[0]))
		dt.p = nil
	}
}
func (dt *dateTimep) Alloc(env *Env) error {
	r := C.OCIDescriptorAlloc(
		unsafe.Pointer(env.ocienv),                      //CONST dvoid   *parenth,
		(*unsafe.Pointer)(unsafe.Pointer(dt.Pointer())), //dvoid         **descpp,
		C.OCI_DTYPE_TIMESTAMP_TZ,                        //ub4           type,
		0,   //size_t        xtramem_sz,
		nil) //dvoid         **usrmempp);
	if r == C.OCI_ERROR {
		return env.ociError()
	} else if r == C.OCI_INVALID_HANDLE {
		return errNew("unable to allocate oci timestamp handle during bind")
	}
	return nil
}
func (dt *dateTimep) Set(env *Env, value time.Time) error {
	if dt.Value() == nil {
		if err := dt.Alloc(env); err != nil {
			return err
		}
	}
	dt.zone = zoneOffset(dt.zone[:0], value)
	r := C.OCIDateTimeConstruct(
		unsafe.Pointer(env.ocienv),                //dvoid         *hndl,
		env.ocierr,                                //OCIError      *err,
		dt.Value(),                                //OCIDateTime   *datetime,
		C.sb2(value.Year()),                       //sb2           year,
		C.ub1(int32(value.Month())),               //ub1           month,
		C.ub1(value.Day()),                        //ub1           day,
		C.ub1(value.Hour()),                       //ub1           hour,
		C.ub1(value.Minute()),                     //ub1           min,
		C.ub1(value.Second()),                     //ub1           sec,
		C.ub4(value.Nanosecond()),                 //ub4           fsec,
		(*C.OraText)(unsafe.Pointer(&dt.zone[0])), //OraText       *timezone,
		C.size_t(len(dt.zone)))                    //size_t        timezone_length );
	if r == C.OCI_ERROR {
		return env.ociError()
	}
	return nil
}

func zoneOffset(buf []byte, value time.Time) []byte {
	if cap(buf) < 6 {
		n := len(buf)
		buf = append(buf, make([]byte, 6)...)[:n]
	}
	_, zoneOffsetInSeconds := value.Zone()
	if zoneOffsetInSeconds < 0 {
		buf = append(buf, '-')
		zoneOffsetInSeconds *= -1
	} else {
		buf = append(buf, '+')
	}
	hourOffset := zoneOffsetInSeconds / 3600
	zoneOffsetInSeconds -= hourOffset * 3600
	minuteOffset := zoneOffsetInSeconds / 60
	buf = printTwoDigits(buf, hourOffset)
	buf = append(buf, ':')
	buf = printTwoDigits(buf, minuteOffset)
	return buf
}

func printTwoDigits(buf []byte, num int) []byte {
	if num == 0 {
		return append(buf, '0', '0')
	}
	if num < 0 {
		num *= -1
	}
	if num < 10 {
		return append(buf, '0', byte('0'+num))
	}
	return append(buf, byte('0'+num/10), byte('0'+(num%10)))
}

type datep struct {
	p *C.OCIDate
}

func (dt *datep) Pointer() *C.OCIDate {
	if dt.p == nil {
		dt.p = (*C.OCIDate)(C.malloc(C.size_t(dt.Size())))
	}
	return dt.p
}
func (dt *datep) Value() C.OCIDate {
	if dt.p == nil {
		return C.OCIDate{}
	}
	return *dt.p
}
func (dt *datep) Size() int {
	return C.sizeof_OCIDate
}
func (dt *datep) Free() {
	if dt.p != nil {
		C.OCIDescriptorFree(
			unsafe.Pointer(dt.p), //void     *descp,
			C.OCI_DTYPE_DATE)     //ub4      type );
		dt.p = nil
	}
}
func (dt *datep) Set(env *Env, value time.Time) error {
	ociSetDateTime(dt.Pointer(), value)
	return nil
}

func ociSetDateTime(ociDate *C.OCIDate, value time.Time) {
	value = value.Local()
	//OCIDateSetDate and OCIDateSetTime are just macros, don't play well with cgo
	ociDate.OCIDateYYYY = C.sb2(value.Year())
	ociDate.OCIDateMM = C.ub1(int32(value.Month()))
	ociDate.OCIDateDD = C.ub1(value.Day())
	ociDate.OCIDateTime.OCITimeHH = C.ub1(value.Hour())
	ociDate.OCIDateTime.OCITimeMI = C.ub1(value.Minute())
	ociDate.OCIDateTime.OCITimeSS = C.ub1(value.Second())
}

func (dt datep) Get() time.Time {
	return ociGetDateTime(dt.Value())
}

func ociGetDateTime(ociDate C.OCIDate) time.Time {
	//OCIDateGetDate and OCIDateGetTime are just macros, don't play well with cgo
	return time.Date(
		int(ociDate.OCIDateYYYY),
		time.Month(ociDate.OCIDateMM),
		int(ociDate.OCIDateDD),
		int(ociDate.OCIDateTime.OCITimeHH),
		int(ociDate.OCIDateTime.OCITimeMI),
		int(ociDate.OCIDateTime.OCITimeSS),
		0,
		time.Local)
}

type numberp struct {
	p *C.OCINumber
}

func (np numberp) Pointer() *C.OCINumber {
	if np.p == nil {
		np.p = (*C.OCINumber)(C.malloc(C.sizeof_OCINumber))
	}
	return np.p
}
func (np numberp) Value() C.OCINumber {
	return *np.p
}
func (np numberp) Size() int {
	return C.sizeof_OCINumber
}
func (np *numberp) Free() {
	if np.p != nil {
		C.free(unsafe.Pointer(np.p))
		np.p = nil
	}
}

type intervalp struct {
	p **C.OCIInterval
}

func (ip *intervalp) Pointer() **C.OCIInterval {
	if ip.p == nil {
		ip.p = (**C.OCIInterval)(C.malloc(C.size_t(ip.Size())))
	}
	return ip.p
}
func (ip *intervalp) Value() *C.OCIInterval {
	if ip.p == nil {
		return nil
	}
	return *ip.p
}
func (ip intervalp) Size() int { return int(C.sof_Intervalp) }
func (ip *intervalp) Free() {
	if ip.p != nil {
		C.free(unsafe.Pointer(ip.p))
		ip.p = nil
	}
}
<commit_msg>initialize slice in nullp before use<commit_after>// Copyright 2016 Tamás Gulácsi. All rights reserved.
// Use of this source code is governed by The MIT License
// found in the accompanying LICENSE file.

package ora

/*
#include <stdlib.h>
#include <oci.h>
#include "version.h"
*/
import "C"
import (
	"time"
	"unsafe"
)

type nullp struct {
	p []C.sb2
}

func (np *nullp) Pointer() *C.sb2 {
	if np.p == nil {
		np.p = (*((*[1]C.sb2)(C.malloc(2))))[:1]
	}
	return &np.p[0]
}

func (np *nullp) IsNull() bool {
	return np.p == nil || np.p[0] < 0
}

func (np *nullp) Free() {
	if np.p != nil {
		C.free(unsafe.Pointer(&np.p[0]))
		np.p = nil
	}
}

func (np *nullp) Set(isNull bool) {
	p := np.Pointer()
	*p = 0
	if isNull {
		*p = -1
	}
}

type lobLocatorp struct {
	p []*C.OCILobLocator
}

func (ll *lobLocatorp) Pointer() **C.OCILobLocator {
	if ll.p == nil {
		ll.p = (*((*[1]*C.OCILobLocator)(C.malloc(C.size_t(ll.Size())))))[:1]
	}
	return &ll.p[0]
}
func (ll *lobLocatorp) Value() *C.OCILobLocator {
	if ll.p == nil {
		return nil
	}
	return ll.p[0]
}
func (ll *lobLocatorp) Size() int {
	return int(C.sof_LobLocatorp)
}
func (ll *lobLocatorp) Free() {
	if ll.p != nil {
		C.free(unsafe.Pointer(&ll.p[0]))
		ll.p = nil
	}
}

type dateTimep struct {
	p    []*C.OCIDateTime
	zone []byte
}

func (dt *dateTimep) Pointer() **C.OCIDateTime {
	if dt.p == nil {
		dt.p = (*((*[1]*C.OCIDateTime)(C.malloc(C.size_t(dt.Size())))))[:1]
	}
	return &dt.p[0]
}
func (dt *dateTimep) Value() *C.OCIDateTime {
	if dt.p == nil {
		return nil
	}
	return dt.p[0]
}
func (dt *dateTimep) Size() int { return int(C.sof_DateTimep) }
func (dt *dateTimep) Free() {
	if dt.p != nil {
		if dt.p[0] != nil {
			C.OCIDescriptorFree(
				unsafe.Pointer(dt.p[0]),  //void     *descp,
				C.OCI_DTYPE_TIMESTAMP_TZ) //ub4      type );
			dt.p[0] = nil
		}
		C.free(unsafe.Pointer(&dt.p[0]))
		dt.p = nil
	}
}
func (dt *dateTimep) Alloc(env *Env) error {
	r := C.OCIDescriptorAlloc(
		unsafe.Pointer(env.ocienv),                      //CONST dvoid   *parenth,
		(*unsafe.Pointer)(unsafe.Pointer(dt.Pointer())), //dvoid         **descpp,
		C.OCI_DTYPE_TIMESTAMP_TZ,                        //ub4           type,
		0,   //size_t        xtramem_sz,
		nil) //dvoid         **usrmempp);
	if r == C.OCI_ERROR {
		return env.ociError()
	} else if r == C.OCI_INVALID_HANDLE {
		return errNew("unable to allocate oci timestamp handle during bind")
	}
	return nil
}
func (dt *dateTimep) Set(env *Env, value time.Time) error {
	if dt.Value() == nil {
		if err := dt.Alloc(env); err != nil {
			return err
		}
	}
	dt.zone = zoneOffset(dt.zone[:0], value)
	r := C.OCIDateTimeConstruct(
		unsafe.Pointer(env.ocienv),                //dvoid         *hndl,
		env.ocierr,                                //OCIError      *err,
		dt.Value(),                                //OCIDateTime   *datetime,
		C.sb2(value.Year()),                       //sb2           year,
		C.ub1(int32(value.Month())),               //ub1           month,
		C.ub1(value.Day()),                        //ub1           day,
		C.ub1(value.Hour()),                       //ub1           hour,
		C.ub1(value.Minute()),                     //ub1           min,
		C.ub1(value.Second()),                     //ub1           sec,
		C.ub4(value.Nanosecond()),                 //ub4           fsec,
		(*C.OraText)(unsafe.Pointer(&dt.zone[0])), //OraText       *timezone,
		C.size_t(len(dt.zone)))                    //size_t        timezone_length );
	if r == C.OCI_ERROR {
		return env.ociError()
	}
	return nil
}

func zoneOffset(buf []byte, value time.Time) []byte {
	if cap(buf) < 6 {
		n := len(buf)
		buf = append(buf, make([]byte, 6)...)[:n]
	}
	_, zoneOffsetInSeconds := value.Zone()
	if zoneOffsetInSeconds < 0 {
		buf = append(buf, '-')
		zoneOffsetInSeconds *= -1
	} else {
		buf = append(buf, '+')
	}
	hourOffset := zoneOffsetInSeconds / 3600
	zoneOffsetInSeconds -= hourOffset * 3600
	minuteOffset := zoneOffsetInSeconds / 60
	buf = printTwoDigits(buf, hourOffset)
	buf = append(buf, ':')
	buf = printTwoDigits(buf, minuteOffset)
	return buf
}

func printTwoDigits(buf []byte, num int) []byte {
	if num == 0 {
		return append(buf, '0', '0')
	}
	if num < 0 {
		num *= -1
	}
	if num < 10 {
		return append(buf, '0', byte('0'+num))
	}
	return append(buf, byte('0'+num/10), byte('0'+(num%10)))
}

type datep struct {
	p *C.OCIDate
}

func (dt *datep) Pointer() *C.OCIDate {
	if dt.p == nil {
		dt.p = (*C.OCIDate)(C.malloc(C.size_t(dt.Size())))
	}
	return dt.p
}
func (dt *datep) Value() C.OCIDate {
	if dt.p == nil {
		return C.OCIDate{}
	}
	return *dt.p
}
func (dt *datep) Size() int {
	return C.sizeof_OCIDate
}
func (dt *datep) Free() {
	if dt.p != nil {
		C.OCIDescriptorFree(
			unsafe.Pointer(dt.p), //void     *descp,
			C.OCI_DTYPE_DATE)     //ub4      type );
		dt.p = nil
	}
}
func (dt *datep) Set(env *Env, value time.Time) error {
	ociSetDateTime(dt.Pointer(), value)
	return nil
}

func ociSetDateTime(ociDate *C.OCIDate, value time.Time) {
	value = value.Local()
	//OCIDateSetDate and OCIDateSetTime are just macros, don't play well with cgo
	ociDate.OCIDateYYYY = C.sb2(value.Year())
	ociDate.OCIDateMM = C.ub1(int32(value.Month()))
	ociDate.OCIDateDD = C.ub1(value.Day())
	ociDate.OCIDateTime.OCITimeHH = C.ub1(value.Hour())
	ociDate.OCIDateTime.OCITimeMI = C.ub1(value.Minute())
	ociDate.OCIDateTime.OCITimeSS = C.ub1(value.Second())
}

func (dt datep) Get() time.Time {
	return ociGetDateTime(dt.Value())
}

func ociGetDateTime(ociDate C.OCIDate) time.Time {
	//OCIDateGetDate and OCIDateGetTime are just macros, don't play well with cgo
	return time.Date(
		int(ociDate.OCIDateYYYY),
		time.Month(ociDate.OCIDateMM),
		int(ociDate.OCIDateDD),
		int(ociDate.OCIDateTime.OCITimeHH),
		int(ociDate.OCIDateTime.OCITimeMI),
		int(ociDate.OCIDateTime.OCITimeSS),
		0,
		time.Local)
}

type numberp struct {
	p *C.OCINumber
}

func (np numberp) Pointer() *C.OCINumber {
	if np.p == nil {
		np.p = (*C.OCINumber)(C.malloc(C.sizeof_OCINumber))
	}
	return np.p
}
func (np numberp) Value() C.OCINumber {
	return *np.p
}
func (np numberp) Size() int {
	return C.sizeof_OCINumber
}
func (np *numberp) Free() {
	if np.p != nil {
		C.free(unsafe.Pointer(np.p))
		np.p = nil
	}
}

type intervalp struct {
	p **C.OCIInterval
}

func (ip *intervalp) Pointer() **C.OCIInterval {
	if ip.p == nil {
		ip.p = (**C.OCIInterval)(C.malloc(C.size_t(ip.Size())))
	}
	return ip.p
}
func (ip *intervalp) Value() *C.OCIInterval {
	if ip.p == nil {
		return nil
	}
	return *ip.p
}
func (ip intervalp) Size() int { return int(C.sof_Intervalp) }
func (ip *intervalp) Free() {
	if ip.p != nil {
		C.free(unsafe.Pointer(ip.p))
		ip.p = nil
	}
}
<|endoftext|>