Lots of small stuff

This commit is contained in:
Olivier Tremblay 2016-05-11 09:44:12 -04:00
commit b5c1189de8
No known key found for this signature in database
GPG key ID: 1A9FE7C1DFF65CB0
14 changed files with 911 additions and 0 deletions

52
cmd/jkl/comment.go Normal file
View file

@ -0,0 +1,52 @@
package main
import (
"bytes"
"errors"
"flag"
"io"
"otremblay.com/jkl"
)
type CommentCmd struct {
args []string
file string
issueKey string
}
func NewCommentCmd(args []string) (*CommentCmd, error) {
ccmd := &CommentCmd{}
f := flag.NewFlagSet("comments", flag.ExitOnError)
f.StringVar(&ccmd.file, "f", "", "File to get issue comment from")
f.Parse(args)
if len(f.Args()) < 1 {
return nil, ErrNotEnoughArgs
}
ccmd.issueKey = f.Arg(0)
return ccmd, nil
}
var ErrNotEnoughArgs = errors.New("Not enough arguments")
func (ccmd *CommentCmd) Comment() error {
var b = bytes.NewBufferString("")
var comment io.Reader
var err error
if ccmd.file != "" {
comment, err = GetTextFromSpecifiedFile(ccmd.file, b)
if err != nil {
return err
}
} else {
comment, err = GetTextFromTmpFile(b)
if err != nil {
return err
}
}
io.Copy(b, comment)
return jkl.AddComment(ccmd.issueKey, b.String())
}

54
cmd/jkl/create.go Normal file
View file

@ -0,0 +1,54 @@
package main
import (
"bytes"
"errors"
"flag"
"os"
"otremblay.com/jkl"
)
type CreateCmd struct {
args []string
project string
file string
}
func NewCreateCmd(args []string) (*CreateCmd, error) {
ccmd := &CreateCmd{project: os.Getenv("JIRA_PROJECT")}
f := flag.NewFlagSet("x", flag.ExitOnError)
f.StringVar(&ccmd.project, "p", "", "Jira project key")
f.StringVar(&ccmd.file, "f", "", "File to get issue description from")
f.Parse(args)
return ccmd, nil
}
var ErrCcmdJiraProjectRequired = errors.New("Jira project needs to be set")
func (ccmd *CreateCmd) Create() error {
var b = bytes.NewBufferString(CREATE_TEMPLATE)
var iss *jkl.Issue
var err error
if ccmd.file != "" {
iss, err = GetIssueFromFile(ccmd.file, b)
if err != nil {
return err
}
} else {
iss, err = GetIssueFromTmpFile(b)
if err != nil {
return err
}
}
if iss.Fields != nil &&
(iss.Fields.Project == nil || iss.Fields.Project.Key == "") {
iss.Fields.Project = &jkl.Project{Key: ccmd.project}
}
return jkl.Create(iss)
}
const CREATE_TEMPLATE = `Issue Type:
Summary:
Description:`

59
cmd/jkl/edit.go Normal file
View file

@ -0,0 +1,59 @@
package main
import (
"bytes"
"flag"
"os"
"text/template"
"otremblay.com/jkl"
)
type EditCmd struct {
args []string
project string
file string
}
func NewEditCmd(args []string) (*CreateCmd, error) {
ccmd := &CreateCmd{project: os.Getenv("JIRA_PROJECT")}
f := flag.NewFlagSet("x", flag.ExitOnError)
f.StringVar(&ccmd.project, "p", "", "Jira project key")
f.StringVar(&ccmd.file, "f", "filename", "File to get issue description from")
f.Parse(args)
return ccmd, nil
}
func (ecmd *EditCmd) Edit(taskKey string) error {
b := bytes.NewBuffer(nil)
iss, err := jkl.GetIssue(taskKey)
if err != nil {
return err
}
err = editTmpl.Execute(b, iss)
if err != nil {
return err
}
if ecmd.file != "" {
iss, err = GetIssueFromFile(ecmd.file, b)
if err != nil {
return err
}
} else {
iss, err = GetIssueFromTmpFile(b)
if err != nil {
return err
}
}
iss.Key = taskKey
return jkl.Edit(iss)
}
const EDIT_TEMPLATE = `Summary: {{.Fields.Summary}}
Description: {{.Fields.Description}}`
var editTmpl = template.Must(template.New("editTmpl").Parse(EDIT_TEMPLATE))

148
cmd/jkl/editor.go Normal file
View file

@ -0,0 +1,148 @@
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"regexp"
"strings"
"reflect"
"bufio"
"otremblay.com/jkl"
)
// def get_editor do
// [System.get_env("EDITOR"), "nano", "vim", "vi"]
// |> Enum.find(nil, fn (ed) -> System.find_executable(ed) != nil end)
// end
var editors = []string{os.Getenv("EDITOR"), "nano", "vim", "vi"}
// GetEditor returns the path to an editor, taking $EDITOR in account
func GetEditor() string {
for _, ed := range editors {
if p, err := exec.LookPath(ed); err == nil {
return p
}
}
log.Fatal("No editor available; use flags.")
return ""
}
func copyInitial(dst io.WriteSeeker, initial io.Reader) {
io.Copy(dst, initial)
dst.Seek(0, 0)
}
func GetIssueFromTmpFile(initial io.Reader) (*jkl.Issue, error) {
f, err := ioutil.TempFile(os.TempDir(), "jkl")
if err != nil {
return nil, err
}
copyInitial(f, initial)
f2, err := GetTextFromFile(f)
if err != nil {
return nil, err
}
return IssueFromFile(f2), nil
}
func GetTextFromTmpFile(initial io.Reader) (io.Reader, error) {
f, err := ioutil.TempFile(os.TempDir(), "jkl")
if err != nil {
return nil, err
}
copyInitial(f, initial)
return GetTextFromFile(f)
}
func GetTextFromSpecifiedFile(filename string, initial io.Reader) (io.Reader, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
if fi, err := f.Stat(); err == nil && fi.Size() == 0 {
copyInitial(f, initial)
}
return GetTextFromFile(f)
}
func GetTextFromFile(file *os.File) (io.Reader, error) {
cmd := exec.Command(GetEditor(), file.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Println(err)
return nil, err
}
_, err = file.Seek(0, 0)
return file, err
}
func GetIssueFromFile(filename string, initial io.Reader) (*jkl.Issue, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
if fi, err := f.Stat(); err == nil && fi.Size() == 0 {
copyInitial(f, initial)
}
f2, err := GetTextFromFile(f)
if err != nil {
return nil, err
}
return IssueFromFile(f2), nil
}
var spacex = regexp.MustCompile(`\s`)
func IssueFromFile(f io.Reader) *jkl.Issue {
iss := &jkl.Issue{Fields: &jkl.Fields{}}
riss := reflect.ValueOf(iss).Elem()
fieldsField := riss.FieldByName("Fields").Elem()
currentField := reflect.Value{}
brd := bufio.NewReader(f)
for {
b, _, err := brd.ReadLine()
if err != nil {
break
}
parts := strings.Split(string(b), ":")
potentialField := spacex.ReplaceAllString(parts[0], "")
if newfield := fieldsField.FieldByName(potentialField); newfield.IsValid() {
parts = parts[1:len(parts)]
if potentialField == "IssueType" {
iss.Fields.IssueType = &jkl.IssueType{}
currentField = reflect.Value{}
f2 := newfield.Elem()
f3 := f2.FieldByName("Name")
f3.SetString(strings.TrimSpace(strings.Join(parts, ":")))
} else if potentialField == "Project" {
iss.Fields.Project = &jkl.Project{}
currentField = reflect.Value{}
f2 := newfield.Elem()
f3 := f2.FieldByName("Key")
f3.SetString(strings.TrimSpace(strings.Join(parts, ":")))
} else {
currentField = newfield
}
}
if currentField.IsValid() {
currentField.SetString(strings.TrimSpace(currentField.String() + "\n" + strings.Join(parts, ":")))
}
}
return iss
}
func IssueFromList(list []string) *jkl.Issue {
return IssueFromFile(bytes.NewBufferString(strings.Join(list, "\n")))
}

36
cmd/jkl/editor_test.go Normal file
View file

@ -0,0 +1,36 @@
package main
import (
"strings"
"testing"
)
func TestIssueFromList(t *testing.T) {
iss := IssueFromList(strings.Split(`Description: Cowboys
from
hell
Issue Type: Sometype
this is ignored
Summary: Dookienator
also ignored`, "\n"))
AssertEqual(t, `Cowboys
from
hell`, iss.Fields.Description)
AssertEqual(t, "Sometype", iss.Fields.IssueType.Name)
}
func TestSpacex(t *testing.T) {
AssertEqual(t, "Something", spacex.ReplaceAllString("Some thing", ""))
}
func AssertEqual(t *testing.T, expected interface{}, actual interface{}) {
if expected != actual {
t.Errorf(`Assertation failed!
Asserted: %v
Actual: %v`, expected, actual)
}
}
func Assert(t *testing.T, fn func() bool) {
}

63
cmd/jkl/jkl.go Normal file
View file

@ -0,0 +1,63 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"github.com/joho/godotenv"
)
func main() {
err := godotenv.Load(".jklrc", fmt.Sprintf("%s/.jklrc", os.Getenv("HOME")))
if err != nil {
log.Fatalln(err)
}
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Print(usage)
return
}
if err := runcmd(flag.Args()); err != nil {
log.Println(err)
}
}
func runcmd(args []string) error {
switch args[0] {
case "list":
return List(flag.Args()[1:])
case "create":
ccmd, err := NewCreateCmd(flag.Args()[1:])
if err != nil {
return err
}
return ccmd.Create()
case "task":
tcmd := &TaskCmd{}
return tcmd.Handle(flag.Args()[1:])
case "edit":
ecmd := &EditCmd{}
return ecmd.Edit(flag.Arg(1))
case "comment":
ccmd, err := NewCommentCmd(flag.Args()[1:])
if err != nil {
return err
}
return ccmd.Comment()
}
fmt.Println(usage)
return nil
}
const usage = `Usage:
jkl [options] <command> [args]
Available commands:
list
create
edit
`

30
cmd/jkl/list.go Normal file
View file

@ -0,0 +1,30 @@
package main
import (
"flag"
"os"
"strings"
"text/template"
"otremblay.com/jkl"
)
var listTemplateStr string
var listTemplate *template.Template
func init() {
flag.StringVar(&listTemplateStr, "listTemplate", "{{.Key}}\t({{.Fields.IssueType.Name}}{{if .Fields.Parent}} of {{.Fields.Parent.Key}}{{end}})\t{{.Fields.Summary}}\n", "Go template used in list command")
listTemplate = template.Must(template.New("listTemplate").Parse(listTemplateStr))
}
func List(args []string) error {
if issues, err := jkl.List(strings.Join(args, " ")); err != nil {
return err
} else {
for _, issue := range issues {
listTemplate.Execute(os.Stdout, issue)
}
}
return nil
}

28
cmd/jkl/task.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"errors"
"fmt"
"otremblay.com/jkl"
)
type TaskCmd struct{}
func (t *TaskCmd) Handle(args []string) error {
if len(args) == 1 {
return t.Get(args[0])
}
return ErrTaskSubCommandNotFound
}
var ErrTaskSubCommandNotFound = errors.New("Subcommand not found.")
func (t *TaskCmd) Get(taskKey string) error {
issue, err := jkl.GetIssue(taskKey)
if err != nil {
return err
}
fmt.Println(issue)
return nil
}

78
cmd/jklfs/jklfile.go Normal file
View file

@ -0,0 +1,78 @@
package main
import (
"fmt"
"os"
"time"
)
import "github.com/hanwen/go-fuse/fuse"
import "github.com/hanwen/go-fuse/fuse/nodefs"
import "io/ioutil"
func NewJklfsFile() (nodefs.File, error) {
f, err := ioutil.TempFile("", "jklfile")
if err != nil {
return nil, err
}
return &jklfile{f}, nil
}
type jklfile struct {
*os.File
}
func (f *jklfile) InnerFile() nodefs.File {
return nil
}
func (f *jklfile) String() string {
return fmt.Sprintf("jklfile(%s)", f.Name())
}
func (f *jklfile) Write(data []byte, off int64) (uint32, fuse.Status) {
n, err := f.File.WriteAt(data, off)
if err != nil {
return fuse.EACCES
}
return uint32(n), fuse.OK
}
func (f *jklfile) Fsync(flag int) (code fuse.Status) {
return fuse.OK
}
func (f *jklfile) Truncate(size uint64) fuse.Status {
return fuse.EPERM
}
func (f *jklfile) Chmod(mode uint32) fuse.Status {
return fuse.EPERM
}
func (f *jklfile) Chown(uid uint32, gid uint32) fuse.Status {
return fuse.EPERM
}
func (f *jklfile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
return fuse.EPERM
}
func (f *jklfile) Flush() fuse.Status {
return fuse.OK
}
func (f *jklfile) GetAttr(out *fuse.Attr) fuse.Status {
return fuse.OK
}
func (f *jklfile) Read(dest []byte, off int64) (fuse.ReadResult, fuse.Status) {
return nil, fuse.OK
}
func (f *jklfile) Release() {
}
func (f *jklfile) SetInode(i *nodefs.Inode) {}
func (f *jklfile) Utimens(atime *time.Time, mtime *time.Time) fuse.Status {
return fuse.EPERM
}

BIN
cmd/jklfs/jklfs Executable file

Binary file not shown.

99
cmd/jklfs/jklfs.go Normal file
View file

@ -0,0 +1,99 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"strings"
"otremblay.com/jkl"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
type jklfs struct {
pathfs.FileSystem
issuePerDirs map[string]*jkl.Issue
}
func (j *jklfs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
switch name {
case "current_sprint":
return &fuse.Attr{
Mode: fuse.S_IFDIR | 0755,
}, fuse.OK
case "":
return &fuse.Attr{
Mode: fuse.S_IFDIR | 0755,
}, fuse.OK
}
if _, ok := j.issuePerDirs[name]; ok {
return &fuse.Attr{
Mode: fuse.S_IFDIR | 0755,
}, fuse.OK
}
pathPieces := strings.Split(name, "/")
path := strings.Join(pathPieces[0:2], "/")
if i, ok := j.issuePerDirs[path]; ok {
if path+"/description" == name {
return &fuse.Attr{
Mode: fuse.S_IFREG | 0644, Size: uint64(len(i.Fields.Description)),
}, fuse.OK
}
}
return nil, fuse.ENOENT
}
func (j *jklfs) OpenDir(name string, context *fuse.Context) (c []fuse.DirEntry, code fuse.Status) {
if name == "" {
c = []fuse.DirEntry{{Name: "current_sprint", Mode: fuse.S_IFDIR}}
return c, fuse.OK
}
if name == "current_sprint" {
issues, err := jkl.List("sprint in openSprints() and project = 'DO'")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, fuse.ENOENT
}
c = make([]fuse.DirEntry, len(issues))
for i, issue := range issues {
c[i] = fuse.DirEntry{Name: issue.Key, Mode: fuse.S_IFDIR}
j.issuePerDirs["current_sprint/"+issue.Key] = issue
}
return c, fuse.OK
}
if _, ok := j.issuePerDirs[name]; ok {
c = []fuse.DirEntry{fuse.DirEntry{Name: "description", Mode: fuse.S_IFREG}}
return c, fuse.OK
}
return nil, fuse.ENOENT
}
func (j *jklfs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
pathPieces := strings.Split(name, "/")
path := strings.Join(pathPieces[0:2], "/")
if i, ok := j.issuePerDirs[path]; ok {
if path+"/description" == name {
return nodefs.NewDataFile([]byte(i.Fields.Description)), fuse.OK
}
}
return nil, fuse.ENOENT
}
func main() {
flag.Parse()
if len(flag.Args()) < 1 {
log.Fatal("Usage:\n jklfs MOUNTPOINT")
}
nfs := pathfs.NewPathNodeFs(&jklfs{pathfs.NewDefaultFileSystem(), map[string]*jkl.Issue{}}, nil)
server, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), nil)
if err != nil {
log.Fatalf("Mount fail: %v\n", err)
}
server.Serve()
}