Writing a Go-Based CLI Tool for Time Tracking

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setup
  4. Creating the CLI Tool
  5. Adding Time Tracking Commands
  6. Saving and Loading Data
  7. Conclusion

Introduction

In this tutorial, we will learn how to create a command-line interface (CLI) tool in Go for time tracking. We will build a tool that allows users to track their time and manage tasks using simple commands. By the end of this tutorial, you will be able to create your own CLI tool using Go and perform time tracking operations.

Prerequisites

To follow this tutorial, you should have a basic understanding of the Go programming language and have Go installed on your machine. If you don’t have Go installed, you can download it from the official Go website (https://golang.org/) and follow the installation instructions for your specific operating system.

Setup

Before we start building our CLI tool, let’s set up a new Go project. Open your terminal and create a new directory for your project:

mkdir go-time-tracking
cd go-time-tracking

Next, initialize a new Go module using the following command:

go mod init github.com/your-username/go-time-tracking

This will create a go.mod file that tracks the dependencies for your project.

Creating the CLI Tool

To create the CLI tool, we will be using the Cobra library, which provides a simple and elegant way to build CLI applications in Go. Cobra takes care of parsing command-line arguments, generating help documentation, and managing subcommands.

First, let’s add Cobra as a dependency to our project by running the following command:

go get -u github.com/spf13/cobra/cobra

Once the dependency is installed, let’s create a new Go file named main.go in the root of our project directory. Open the file in your favorite text editor and add the following code:

package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "time-tracker",
	Short: "A CLI tool for time tracking",
	Long: `time-tracker is a command-line interface tool
designed to help you track and manage your time.`,
	Run: func(cmd *cobra.Command, args []string) {
		// Default command action
	},
}

func main() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

This sets up our basic CLI tool structure using Cobra. It initializes a new Cobra root command and defines its name, short description, long description, and default action. The main function executes the root command and handles any errors that may occur.

To test our CLI tool, run the following command:

go run main.go

You should see the following output:

A CLI tool for time tracking

Usage:
  time-tracker [command]

Available Commands:
  help        Help about any command

Flags:
      --config string   config file (default is $HOME/.time-tracker.yaml)
  -h, --help            help for time-tracker
  -t, --toggle          Help message for toggle

Use "time-tracker [command] --help" for more information about a command.

Congratulations! We have successfully set up the foundation of our CLI tool.

Adding Time Tracking Commands

Now that we have our basic CLI tool structure in place, let’s add some commands for time tracking. For this tutorial, we will focus on two main commands: start and stop.

Modify the main.go file with the following code to add the start and stop commands:

package main

import (
	"fmt"
	"os"
	"time"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "time-tracker",
	Short: "A CLI tool for time tracking",
	Long: `time-tracker is a command-line interface tool
designed to help you track and manage your time.`,
	Run: func(cmd *cobra.Command, args []string) {
		// Default command action
	},
}

var startCmd = &cobra.Command{
	Use:   "start",
	Short: "Start tracking time for a task",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Time tracking started...")
		startTime := time.Now()
		fmt.Println("Start Time:", startTime)
	},
}

var stopCmd = &cobra.Command{
	Use:   "stop",
	Short: "Stop tracking time for the current task",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Time tracking stopped...")
	},
}

func main() {
	rootCmd.AddCommand(startCmd)
	rootCmd.AddCommand(stopCmd)

	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

We added startCmd and stopCmd as subcommands to the root command using the AddCommand function. Each command has its own short description and action function. The start command prints the current time as the start time, and the stop command simply prints a message indicating the time tracking has stopped.

To test the new commands, run the following commands:

go run main.go start

Output:

Time tracking started...
Start Time: 2022-01-01 12:34:56.789
go run main.go stop

Output:

Time tracking stopped...

Great! We now have the basic functionality of starting and stopping time tracking.

Saving and Loading Data

To make our time tracking tool more useful, let’s implement saving and loading of tracked time data. We will use a simple text file to store the start and stop times of each task.

Create a new directory named data at the root of your project. This directory will be used to store our time tracking data. Inside the data directory, create a new file named tasks.txt.

Modify the main.go file with the following code to add file I/O functionality:

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "time-tracker",
	Short: "A CLI tool for time tracking",
	Long: `time-tracker is a command-line interface tool
designed to help you track and manage your time.`,
	Run: func(cmd *cobra.Command, args []string) {
		// Default command action
	},
}

var startCmd = &cobra.Command{
	Use:   "start",
	Short: "Start tracking time for a task",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Time tracking started...")
		startTime := time.Now()
		fmt.Println("Start Time:", startTime)

		// Append start time to tasks.txt
		task := "Start Time: " + startTime.Format(time.RFC3339) + "\n"
		writeToTasksFile(task)
	},
}

var stopCmd = &cobra.Command{
	Use:   "stop",
	Short: "Stop tracking time for the current task",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Time tracking stopped...")
		
		// Retrieve last start time from tasks.txt
		startTime, err := getLastStartTime()
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		fmt.Println("Start Time:", startTime)

		// Calculate and print elapsed time
		stopTime := time.Now()
		elapsedTime := stopTime.Sub(startTime)
		fmt.Println("Elapsed Time:", elapsedTime.String())

		// Append stop time to tasks.txt
		task := "Stop Time: " + stopTime.Format(time.RFC3339) + "\n"
		writeToTasksFile(task)
	},
}

func writeToTasksFile(task string) {
	dataDir := getDataDir()
	filePath := filepath.Join(dataDir, "tasks.txt")
	err := ioutil.WriteFile(filePath, []byte(task), os.FileMode(0644))
	if err != nil {
		fmt.Println("Error:", err)
	}
}

func getLastStartTime() (time.Time, error) {
	dataDir := getDataDir()
	filePath := filepath.Join(dataDir, "tasks.txt")
	fileData, err := ioutil.ReadFile(filePath)
	if err != nil {
		return time.Time{}, err
	}
	
	lines := strings.Split(string(fileData), "\n")
	for i := len(lines) - 1; i >= 0; i-- {
		if strings.HasPrefix(lines[i], "Start Time: ") {
			startTimeStr := strings.TrimPrefix(lines[i], "Start Time: ")
			startTime, err := time.Parse(time.RFC3339, startTimeStr)
			if err != nil {
				return time.Time{}, err
			}
			return startTime, nil
		}
	}
	return time.Time{}, fmt.Errorf("no start time found")
}

func getDataDir() string {
	homeDir, _ := os.UserHomeDir()
	dataDir := filepath.Join(homeDir, ".time-tracker")
	if _, err := os.Stat(dataDir); os.IsNotExist(err) {
		os.Mkdir(dataDir, os.FileMode(0755))
	}
	return dataDir
}

func main() {
	rootCmd.AddCommand(startCmd)
	rootCmd.AddCommand(stopCmd)

	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

We added functions to write to the tasks.txt file and retrieve the last start time from the file. The writeToTasksFile function appends the task data to the file, and the getLastStartTime function reads the file and retrieves the last recorded start time.

Let’s test the new functionality by running the following commands:

go run main.go start
go run main.go stop

You should see the start and stop times printed, and the tasks.txt file will contain the recorded start and stop times.

Conclusion

In this tutorial, we have learned how to create a CLI tool for time tracking using Go. We started by setting up the project structure and adding the Cobra library as a dependency. We then added commands for starting and stopping time tracking and implemented saving and loading of data using a text file.

By following this tutorial, you should now have a basic understanding of how to create a CLI tool in Go and perform time tracking operations. You can further enhance the tool by adding more commands, implementing additional features, or integrating with other services.

Take what you have learned and start building your own CLI tools using Go. Have fun exploring the possibilities and experimenting with new ideas!