2025-02-24
Updated: 2025-02-28
If you don't have a fixed schedule, time slips away unnoticed, like socks in a dryer1. The absence of structure often leads to distractions and procrastination, making it easy to lose track of your priorities and deadlines. So, tasks that could be completed in minutes may take hours (of course always taking into consideration the planning fallacy bias), as the day progresses without a clear plan2. This lack of organization not only diminishes productivity but can also create a sense of overwhelm, as the weight of unfinished tasks accumulates. Ultimately, without a schedule, the precious hours of the day vanish, leaving you with a lingering sense of unfulfillment and regret.
When you start freelancing you have to manage your time in a way that it is productive and easy to organize.
There are many cli applications that can be used to manage my tasks. just to name a few:
However, I though it would be a good learning opportunity to make a small app in Golang, as it would help me to understand the language and prepare me for future projects, because if I can’t manage my tasks, at least I can manage to make a cool app about it! And the -real- project to add extra functionality is sxctl cli app of Stagex
So first thing I did was to check the main documentation, the guide about building-an-awesome-cli-app-in-go-oscon, and the cobra-cli.
Of course I had installed GO (on Debian) and populated my .bashrc
with:
#.bashrc
# Set up go
if [ -d /usr/local/go/bin ] ; then
export PATH="$PATH:/usr/local/go/bin"
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
fi
then making the directory for the new cli app
mkdir todoer && cd todoer
go install github.com/spf13/cobra-cli@latest
git init && go mod init codeberg.org/conyel/todoer
cobra-cli init . -a ConYel --viper
go run main go
so this is the directory
➜ tree
.
├── cmd
│ └── root.go
├── go.mod
├── go.sum
├── LICENSE
├── main.go
└── todoer
First let's add the add
command following the tutorial
cobra add add
and then in cmd/add.go
we change a bit the command to print the specific tasks
/*
Copyright © 2025 itsyou
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// addCmd represents the add command
var addCmd = &cobra.Command{
Use: "add",
Short: "Add a new task",
Long: `Add a new task to the list of your tasks.`,
Run: func(cmd *cobra.Command, args []string) { // <- from here
for _, x := range args {
fmt.Println(x)
} // <- to here
},
}
Then we move on to create the first struct that will include the task. After some thinking I chose the Tom's Obvious, Minimal Language (and friends). I searched for any go lib that parses TOML and found two that are still maintained, so I just picked go-toml. Why go-toml instead of the BurntSushi/toml?? Eh well, randomly for now.
Let's see if I'll have to change it in the future if it's not simple enough (or many bugs).
Anyway, let's make a dir for the models, install it and make our struct.
> mkdir todot
> touch cmds/todot.go
> go get github.com/pelletier/go-toml
and then in cmds/todot.go
package todot //ToDoT Tasks fail name for a struct?
import "time"
type Task struct {
//Version int
Name string
Created time.Time
DueBy time.Time
}
Ok so now we have our first structure which will be the task added/modified/removed. Let's add a print in add.go
to see how it works.
var addCmd = &cobra.Command{
Use: "add",
Short: "Add a new task",
Long: `Add a new task to the list of your tasks.`,
Run: func(cmd *cobra.Command, args []string) {
tasks := []todot.Task{}
for _, x := range args {
tasks = append(tasks,
todot.Task{Name: x, Created: time.Now()})
}
fmt.Println(tasks)
},
}
and build again (from now on I will show only output )
go build
> ./todoer add "one two" three
[{0 one two 0001-01-01 00:00:00 +0000 UTC} {0 three 0001-01-01 00:00:00 +0000 UTC}]
hmm I don't like it.
Let's change the default print of it and add a Due To so we have a deadline3. In add.go
add
Run: func(cmd *cobra.Command, args []string) {
var tasks = []todot.Task{}
for _, x := range args {
tasks = append(tasks,
todot.Task{
Name: x,
Created: time.Now(),
DueBy: time.Now().Local().Add(time.Minute * time.Duration(100))})
}
todot.SaveTasks("x", tasks)
todot.ToString(tasks[0])
and in todot.go
import "fmt"
import "github.com/pelletier/go-toml/v2"
func SaveTasks(filename string, tasks []Task) error {
b, err := toml.Marshal(tasks)
if err != nil {
return err
}
fmt.Println(string(b))
return nil
}
func ToString(t Task) {
fmt.Printf("Name: %s\nCreated: %s\nDue to: %s",
t.Name,
t.Created.Format("2006/01 02 Mon 15:00"),
t.DueBy.Format("2006/01 02 Mon 15:00"))
}
In that way we make a function on how to print (and later put on TOML file) that will show a useful info on each task. (In a bit we will see how wrong is what I made :P)
Let's run it and see what we get!
> ./todoer add "one two" three
[[]]
Name = 'one two'
Created = '2025/02 22 Fri 14:11'
DueBy = '2025/02 22 Fri 14:16'
[[]]
Name = 'three'
Created = '2025/02 22 Fri 14:11'
DueBy = '2025/02 22 Fri 14:16'
Name: one two
Created: 2025/02 22 Fri 14:11
Due to: 2025/02 22 Fri 14:16
ok it seems fine, no? Time to save to a file! changing todot.go
func SaveTasks(filename string, tasks []Task) error {
b, err := toml.Marshal(tasks)
err = os.WriteFile(filename, b, 0644)
if err != nil {
panic(err)
}
fmt.Println(string(b))
return nil
}
and then adding in add.go
:
err := todot.SaveTasks("./x.toml", tasks)
if err != nil {
panic(err)
}
and running it, we get a file x.toml
And here is the time to read a bit more about how to import a TOML file and see why it doesn't work importing that file.
Ok, fix the struct first. on todot.go
// import "time" // comment time as we will change it to string (for now at least :P )
type Task struct {
//Version int
Name string `toml:"Name"`
Created string `toml:"Created"`
DueBy string `toml:"DueBy"`
}
type Config struct {
Tasks []Task `toml:"task"` // add a parent struct to keep all tasks (maybe later we will change it if there is a better way)
}
// extend time // this to add more ways to print time as string
const (
// YYYY-MM-DD: 2022-03-23
YYYYMMDD = "2006-01-02"
// 24h hh:mm:ss: 14:23:20
HHMMSS24h = "15:04:05"
// 12h hh:mm:ss: 2:23:20 PM
HHMMSS12h = "3:04:05 PM"
// text date: March 23, 2022
TextDate = "January 2, 2006"
// text date with weekday: Wednesday, March 23, 2022
TextDateWithWeekday = "Monday, January 2, 2006"
// abbreviated text date: Mar 23 Wed
AbbrTextDate = "Jan 2 Mon"
//microwave date: Mar 23 Wed
Microwave = "2006/01 2 Mon 15:04"
)
change the other two functions and add one that will read ReadTasks
from the file
func SaveTasks(filename string, tasks *Config) error { // !!!here is a pointer, read more about them.
b, err := toml.Marshal(tasks)
err = os.WriteFile(filename, b, 0644)
if err != nil {
panic(err)
}
fmt.Println(string(b))
return nil
}
func ReadTasks(filename string) (*Config, error) { // !!!here is a pointer, read more about them.
var config Config
// var tasks []Task
data, err := os.ReadFile(filename)
if err != nil {
fmt.Println("Error in reading")
return nil, err
}
if err := toml.Unmarshal(data, &config); err != nil { // !!!here is a pointer, read more about them.
fmt.Println("Error in unmarshal")
return nil, err
}
fmt.Println("the config is:", config.Tasks)
return &config, nil
}
func (t Task) ToString() string {
return fmt.Sprintf("Name: %s\nCreated: %s\nDue to: %s",
t.Name,
t.Created,
t.DueBy)
}
and add command becomes like this:
Run: func(cmd *cobra.Command, args []string) {
newtasks := todot.Config{ //
Tasks: []todot.Task{}, // Initialize Tasks as an empty slice
}
timenow := time.Now().Local()
for _, task := range args {
newtasks.Tasks = append(newtasks.Tasks, // append to it
todot.Task{
Name: task,
Created: timenow.Format(todot.Microwave),
DueBy: timenow.Add(time.Minute * time.Duration(5)).Format(todot.Microwave),
})
}
err := todot.SaveTasks("./x.toml", &newtasks)
if err != nil {
panic(err)
}
fmt.Println(newtasks.Tasks[0].ToString())
// fmt.Printf("%#v\n", tasks)
},
}
and then run them to see if it actually make a proper TOML file:
> ./todoer add "one two" three
[[task]]
Name = 'one two'
Created = '2025/02 22 Fri 16:01'
DueBy = '2025/02 22 Fri 16:06'
[[task]]
Name = 'three'
Created = '2025/02 22 Fri 16:01'
DueBy = '2025/02 22 Fri 16:06'
Name: one two
Created: 2025/02 22 Fri 16:01
Due to: 2025/02 22 Fri 16:06
> cat x.toml
[[task]]
Name = 'one two'
Created = '2025/02 28 Fri 16:01'
DueBy = '2025/02 28 Fri 16:06'
[[task]]
Name = 'three'
Created = '2025/02 28 Fri 16:01'
DueBy = '2025/02 28 Fri 16:06'
Ok I think for this post is fine. Let's say it is the Part1 and move on to the next!
I once spent an entire afternoon organizing my bookmarks, from two different firefox profiles, my work and my personal, only to realize I had forgotten to eat lunch (╯°□°)╯︵ ┻━┻. Who knew that “bookmarks management” could be a full-time distracting job?
Deadlines are like zombies: they just keep coming back, no matter how many times you think you've "killed" them.