commit 6b00d0d113824e7234498188d9acbb349c9afdfd Author: sML Date: Tue Dec 27 10:04:51 2022 +0100 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..558b261 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Intro +htask (hacker task) will be a cli task manager. Now under development. +It will use SQLite3 to storage the data. + +Check changelog.txt to see the current status. + +# Installation +```sh +git clone https://code.lacashita.com/sml/htask +go build htask.go +./htask.go +``` + +# Demo +![](htask.gif) + +# Usage + +htask [command] [options] + +## Add tasks + +| Description | Command | +| ----------- | ----------- | +| Add simple task | htask add -t "hack my school"| +| Add complete task | htask add -t "hack my school" -P 0 -p hacking| + +## Delete task +| Description | Command | +| ----------- | ----------- | +| Delete task 1 | htask delete 1| + +## Start task +| Description | Command | +| ----------- | ----------- | +| Start task 1 | htask start 1| + +## Finish task +| Description | Command | +| ----------- | ----------- | +| Finish task 1 | htask done 1| + +## List tasks +| Description | Command | +| ----------- | ----------- | +| Show all tasks | htask list| +| Show tasks filtered by project | htask list -p "hacking"| +| Shows all tasks P0 | htask list -P 0| +| Shows tasks with string | htask list string| + +## Edit tasks +| Description | Command | +| ----------- | ----------- | +| Edit task 1 | htask edit 1| + +## View tasks +| Description | Command | +| ----------- | ----------- | +| View task 1 | htask view 1| + + + + + + + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..18f3d22 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module htask + +go 1.19 + +require ( + github.com/fatih/color v1.13.0 + github.com/mattn/go-sqlite3 v1.14.15 +) + +require ( + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/rodaine/table v1.0.1 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..500a604 --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= +github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/htask b/htask new file mode 100755 index 0000000..385094e Binary files /dev/null and b/htask differ diff --git a/htask.gif b/htask.gif new file mode 100644 index 0000000..d0604aa Binary files /dev/null and b/htask.gif differ diff --git a/htask.go b/htask.go new file mode 100644 index 0000000..44ff30a --- /dev/null +++ b/htask.go @@ -0,0 +1,538 @@ +package main + +import ( + "flag" + "fmt" + "database/sql" + "log" + "os" + "os/exec" + "io/ioutil" + "github.com/fatih/color" + _ "github.com/mattn/go-sqlite3" + "github.com/rodaine/table" + "strconv" + "time" +) + +func check(e error) { + if e != nil { + panic(e) + } +} + +const help = ` + +(ยท)_(o) HaCkErTaSk + +Basic help +----------- +Create task: htask add -t "My new task". +List task: htask list +List task filetered: htask list -p blog +View task: htask view +Edit task: htask edit +Modify task: htask mod -P 1 +Delete task: htask delete +Mark task as Started: htask start +Mark task as Done: htask done + +Check more help in README.md file. +` + +func main() { + // Get HTASK_DB env, if doesnt exists create ~/hackertask.db as database. + var filedb string + if os.Getenv("HTASK_DB") == "" { + var homepath = os.Getenv("HOME") + filedb = homepath+"/hackertask.db" + } else { + filedb = os.Getenv("HTASK_DB") + } + + checkDB(filedb) + htaskmain(len(os.Args),filedb) +} + +type Task struct { +id *int +name *string +priority *string +project *string +comments *string +status *string +createdate *string +startdate *string +enddate *string +duedate *string +tags *string +} + + +// Main function. +func htaskmain(arguments int,filedb string){ + var t Task + var existid int + + sqlDB, _ := sql.Open("sqlite3",filedb) + defer sqlDB.Close() + + if len(os.Args) < 2 { + fmt.Println(help) + os.Exit(0) + } + + switch os.Args[1] { + + case "add": + addCommand := flag.NewFlagSet("add", flag.ExitOnError) + t.priority = addCommand.String("P", "5", "Priority") + t.project = addCommand.String("p", "default", "Project") + t.name = addCommand.String("t", "", "Task name") + t.status = addCommand.String("s", "Open", "Status") + t.duedate = addCommand.String("d", "NoDate", "Due Date") + + addCommand.Parse(os.Args[2:]) + if *t.name == "" { + fmt.Println("Parameter -t should not be empty") + os.Exit(0) + } else { + addTask(sqlDB,*t.name,*t.status,*t.priority,*t.project,*t.duedate) + } + + case "list": + listCommand := flag.NewFlagSet("list", flag.ExitOnError) + t.priority = listCommand.String("P", "%", "Priority") + t.project = listCommand.String("p", "%", "Project") + t.status = listCommand.String("s", "%", "Status") + t.duedate = listCommand.String("d", "%", "Due Date") + + listCommand.Parse(os.Args[2:]) + listTask(sqlDB,*t.priority,*t.project,*t.status,*t.duedate) + + case "mod": + if len(os.Args) > 2{ + modCommand := flag.NewFlagSet("mod", flag.ExitOnError) + t.name = modCommand.String("t", "", "Task name") + t.priority = modCommand.String("P", "", "Priority") + t.project = modCommand.String("p", "", "Project") + t.duedate = modCommand.String("d", "", "Due Date") + modCommand.Parse(os.Args[3:]) + + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + if existid != 0 { + modTask(sqlDB,*t.name,*t.priority,*t.project,*t.duedate,theintid) + } else { + fmt.Println("ID dont exist.") + } + } else { + fmt.Println("Usage: htask mod ") + } + + case "view": + if len(os.Args) > 2{ + // Convert param 2 into int. + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + if existid != 0 { + viewTask(sqlDB,theintid) + } else { + fmt.Println("ID dont exist.") + } + } else { + fmt.Println("Usage: htask view ") + } + + case "delete": + if len(os.Args) > 2{ + // Convert param 2 into int. + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + if existid != 0 { + deleteTask(sqlDB,theintid) + } else { + fmt.Println("ID dont exist.") + } + } else { + fmt.Println("Usage: htask delete ") + } + + case "done": + if len(os.Args) > 2{ + // Convert param 2 into int. + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + existDONE := checkStatus(sqlDB,theintid) + if existid != 0 { + if existDONE != "Done" { + doneTask(sqlDB,theintid) + } else { + fmt.Println("This task was marked as Done in the past.") + } + } else { + fmt.Println("ID dont exist") + } + } else { + fmt.Println("Usage: htask done ") + } + + case "start": + if len(os.Args) > 2{ + // Convert param 2 into int. + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + existDONE := checkStatus(sqlDB,theintid) + if existid != 0 { + if existDONE != "Working" && existDONE != "Done" { + startTask(sqlDB,theintid) + } else { + fmt.Println("This task was started/finished in the past.") + } + } else { + fmt.Println("ID dont exist") + } + } else { + fmt.Println("Usage: htask start ") + } + + case "edit": + if len(os.Args) > 2{ + // Convert param 2 into int. + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + if existid != 0 { + editTask(sqlDB,theintid) + } else { + fmt.Println("ID dont exist.") + } + } else { + fmt.Println("Usage: htask edit ") + } + + default: + fmt.Println(help) + os.Exit(0) + } +} + +// Func to add Task. +func addTask(db *sql.DB, name string, status string, priority string, project string, duedate string) { + date := time.Now().Local().String() + insertSQL := `INSERT INTO htask(name, status, priority, project,createdate,duedate) VALUES (?, ?, ?, ?, ?, ?)` + statement, err := db.Prepare(insertSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(name, status, priority, project,date,duedate) + if err != nil { + log.Fatalln(err.Error()) + } + color.Green("[+] Task added\n") +} + +// Func to mod ify Task. +func modTask(db *sql.DB, name string, priority string, project string, duedate string, id int) { +// Mod name +if name != "" { + modSQL := `UPDATE htask set name = ? where id = ?` + statement, err := db.Prepare(modSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(name,id) + if err != nil { + log.Fatalln(err.Error()) + } +} + +// Mod priority +if priority != "" { + modSQL := `UPDATE htask set priority = ? where id = ?` + statement, err := db.Prepare(modSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(priority,id) + if err != nil { + log.Fatalln(err.Error()) + } +} +// Mod project +if project != "" { + modSQL := `UPDATE htask set project = ? where id = ?` + statement, err := db.Prepare(modSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(project,id) + if err != nil { + log.Fatalln(err.Error()) + } +} +// Mod duedate +if duedate != "" { + modSQL := `UPDATE htask set duedate = ? where id = ?` + statement, err := db.Prepare(modSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(duedate,id) + if err != nil { + log.Fatalln(err.Error()) + } +} +//If all ok prints: +color.Green("[+] Task modified\n") +} + +// Func to show all Tasks. +func listTask(db *sql.DB, priority string, project string, status string, duedate string) { + headerFmt := color.New(color.FgMagenta, color.Underline).SprintfFunc() + columnFmt := color.New(color.FgGreen).SprintfFunc() + tbl := table.New("ID", "Priority", "Project", "Task","Status","DueDate") + tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) + row, err := db.Query("SELECT * FROM htask where id like '%' and priority like ? and project like ? and status like ? and duedate like ?",priority,project,status,duedate) + if err != nil { + log.Fatal(err) + } + defer row.Close() + fmt.Printf("\n") + for row.Next() { + var id int + var name string + var priority string + var project string + var status string + var duedate string + var comments string + var createdate string + var startdate string + var enddate string + var tags string + row.Scan(&id, &name, &priority, &project, &status, &duedate, &comments, &createdate, &startdate, &enddate, &tags) + tbl.AddRow(id,priority,project,name,status,duedate) + } + +tbl.Print() +fmt.Printf("\n") +} + +// Func to view comments. +func viewTask(db *sql.DB, id int) { + row, err := db.Query("SELECT comments FROM htask where id = ?",id) + if err != nil { + log.Fatal(err) + } + defer row.Close() + fmt.Printf("\n") + for row.Next() { + var comments string + row.Scan(&comments) + fmt.Println(comments) + } +fmt.Printf("\n") +} + +// Func to delete task. +func deleteTask(db *sql.DB, id int) { + deleteTaskSQL := `DELETE FROM htask WHERE id = ?` + statement, err := db.Prepare(deleteTaskSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(id) + if err != nil { + log.Fatalln(err.Error()) + } + color.Green("[+] Task %v deleted successfully.\n",id) +} + +// Func to done task. +func doneTask(db *sql.DB, id int) { + date := time.Now().Local().String() + doneTaskSQL := `UPDATE htask set status = "Done",enddate = ? WHERE id = ?` + statement, err := db.Prepare(doneTaskSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(date,id) + if err != nil { + log.Fatalln(err.Error()) + } + color.Green("[+] Task %v mark as Done.\n",id) +} + +// Func to start task. +func startTask(db *sql.DB, id int) { + date := time.Now().Local().String() + startTaskSQL := `UPDATE htask set status = "Working",startdate = ? WHERE id = ?` + statement, err := db.Prepare(startTaskSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(date,id) + if err != nil { + log.Fatalln(err.Error()) + } + color.Green("[+] Task %v mark as Working.\n",id) +} + +// Func to edit task. +func editTask(db *sql.DB, id int) { + // Obtain current comments. + var currentcomments string + row, err := db.Query("SELECT comments from htask where id = ?",id) + if err != nil { + log.Fatal(err) + } + defer row.Close() + for row.Next() { + var comments string + row.Scan( &comments) + currentcomments = comments +} + // Create temp file with current comments + var vi string + if os.Getenv("EDITOR") == "" { + vi = "nvim" + } else { + vi = os.Getenv("EDITOR") + } + tmpDir := os.TempDir() + tmpFile, tmpFileErr := ioutil.TempFile(tmpDir, "tempFilePrefix") + if tmpFileErr != nil { + fmt.Printf("Error %s while creating tempFile", tmpFileErr) + } + path, err := exec.LookPath(vi) + if err != nil { + fmt.Printf("Error %s while looking up for %s!!", path, vi) + } + d1 := []byte(currentcomments) + err = os.WriteFile(tmpFile.Name(), d1, 0644) + check(err) + cmd := exec.Command(path, tmpFile.Name()) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Start() + if err != nil { + fmt.Printf("Start failed: %s", err) + } + err = cmd.Wait() + + dat, err := os.ReadFile(tmpFile.Name()) + check(err) + color.Green("[+] Note saved successfully.\n") + currentdata := string(dat) + // Update current comments + updateCommentsSQL := `UPDATE htask set comments = ? where id = ?` + statement, err := db.Prepare(updateCommentsSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(currentdata,id) + if err != nil { + log.Fatalln(err.Error()) + } + // Delete temp file + os.Remove(tmpFile.Name()) +} + +// Func to check if ID exists +func checkID(db *sql.DB, id int ) int{ + var total int + row, err := db.Query("SELECT count(*) as total from htask where id = ?",id) + if err != nil { + log.Fatal(err) + } + defer row.Close() + for row.Next() { + var count int + row.Scan( &count) + total = count + } + return total +} + +// Func to check if task was marked as Done +func checkStatus(db *sql.DB, id int ) string{ + var resultado string + row, err := db.Query("SELECT status from htask where id = ?",id) + if err != nil { + log.Fatal(err) + } + defer row.Close() + for row.Next() { + var status string + row.Scan( &status) + resultado = status + } + return resultado +} + +// Func to check if DB file exists, if not, it creates the file. +func checkDB(filedb string) { + _, err := os.Stat(filedb) + //If file htask.db doesnt exist. + if err != nil { + color.Green("[+]Creating Database\n") + file, err := os.Create(filedb) + if err != nil { + log.Fatal(err.Error()) + } + file.Close() + sqlDB, _ := sql.Open("sqlite3",filedb) + defer sqlDB.Close() + createTable(sqlDB) + } + } + +// Func to create Database file/schema. +func createTable(db *sql.DB) { + createHtaskTableSQL := `CREATE TABLE htask ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT, + "priority" TEXT, + "project" TEXT, + "status" TEXT, + "duedate" TEXT, + "comments" TEXT, + "createdate" TEXT, + "startdate" TEXT, + "enddate" TEXT, + "tags" TEXT + );` + + color.Green("[+]Creating htask table...") + statement, err := db.Prepare(createHtaskTableSQL) + if err != nil { + log.Fatal(err.Error()) + } + statement.Exec() + color.Green("[+]HackerTask DB created") +}