Before You Begin

  • Go installed (version 1.21 or later). Download here.
  • A text editor (VS Code, GoLand, or even Notepad).
  • Basic comfort with your terminal (opening, typing commands).

No prior programming experience? You'll still follow along—I'll explain every piece.

---

The Problem: You Have a Gemstone Collection, and It's Chaos

Imagine you inherited a box of gemstones—rubies, sapphires, emeralds, amethysts. Each has a carat weight, a color, and a value. You need a quick way to add new stones, list everything you own, and maybe someday search, sort, or persist to a file.

Spreadsheets are overkill. Paper gets lost. What you really want is a tiny command-line program that lives in your terminal and does exactly what you need—no bloat.

That program is what we'll build in Go.

---

Step 1: Project Skeleton with Go Modules

Go uses modules to manage dependencies and organise code. Create a new directory and initialise a module:

mkdir gem-cli
cd gem-cli
go mod init gem-cli

This creates a go.mod file. It's just a fingerprint saying "this is the gem-cli module".

Now create a file called main.go. Every Go program starts in package main and runs the main() function.

package main

import "fmt"

func main() {
    fmt.Println("Gemstone Inventory v0.1")
}

Run it:

go run main.go

You'll see the message. Not impressive yet, but the engine is humming.

---

Step 2: Define a Gemstone – Structs in Go

A gemstone has fixed attributes: name, carat weight, color, and price. In Go we use a struct to bundle these together. The naming convention is Gemstone (exported) because we might use it from other packages later.

Add this above main():

type Gemstone struct {
    Name  string
    Carat float64
    Color string
    Price float64
}

Now you can create a stone like this inside main():

g := Gemstone{Name: "Ruby", Carat: 2.5, Color: "Red", Price: 4500.00}
fmt.Printf("%s: %.1f carat %s - $%.2f\n", g.Name, g.Carat, g.Color, g.Price)

Run again. You've just built a data model.

---

Step 3: Store Multiple Stones – Slices

One stone is lonely. We need a collection. Go slices are dynamic arrays. Declare a slice of Gemstone:

var collection []Gemstone

Add stones using append:

collection = append(collection, Gemstone{Name: "Sapphire", Carat: 3.0, Color: "Blue", Price: 6000})
collection = append(collection, Gemstone{Name: "Emerald", Carat: 1.8, Color: "Green", Price: 3200})

Now print them all with a simple for loop:

for i, gem := range collection {
    fmt.Printf("%d. %s\n", i+1, gem)
}

But fmt.Printf with a struct gives ugly output like {Ruby 2.5 Red 4500}. Let's fix that by adding a method.

---

Step 4: Make It Pretty – Methods on Structs

A method is a function attached to a type. Add this after the struct definition:

func (g Gemstone) String() string {
    return fmt.Sprintf("%s (%.1f ct, %s) - $%.2f", g.Name, g.Carat, g.Color, g.Price)
}

Now fmt.Printf("%s\n", gem) will call this String() method automatically. Re-run and see clean lines.

---

Step 5: Add Interactivity – Reading User Input

The CLI must accept commands. Let's implement a simple loop that asks the user what to do: add a gemstone or list them all.

Replace the main() body with this:

func main() {
    collection := []Gemstone{
        {Name: "Amethyst", Carat: 1.2, Color: "Purple", Price: 250},
        {Name: "Topaz", Carat: 0.9, Color: "Yellow", Price: 180},
    }

    fmt.Println("Gemstone Inventory Manager")
    fmt.Println("Commands: add, list, quit")

    for {
        fmt.Print("> ")
        var cmd string
        fmt.Scanln(&cmd)

        switch cmd {
        case "add":
            var name, color string
            var carat, price float64

            fmt.Print("Name: ")
            fmt.Scanln(&name)
            fmt.Print("Carat: ")
            fmt.Scanln(&carat)
            fmt.Print("Color: ")
            fmt.Scanln(&color)
            fmt.Print("Price: ")
            fmt.Scanln(&price)

            collection = append(collection, Gemstone{name, carat, color, price})
            fmt.Println("Added.")

        case "list":
            for i, gem := range collection {
                fmt.Printf("%d. %s\n", i+1, gem)
            }

        case "quit":
            fmt.Println("Goodbye.")
            return

        default:
            fmt.Println("Unknown command. Try add, list, or quit.")
        }
    }
}

This uses fmt.Scanln for input. Note: Scanln stops at whitespace, so a gem name with a space ("Star Ruby") will read only "Star". For now that's acceptable; later you can switch to bufio.Scanner.

---

Step 6: Build and Run Like a Real Tool

Compile a standalone binary:

go build -o gem-cli

Now run it:

./gem-cli

You have a working gemstone inventory manager. Add a few stones, list them, quit. It lives in your terminal.

---

Where to Go Next

This tool is in‑memory only – all data disappears when you quit. Next steps:

  • Save and load from a JSON file using encoding/json.
  • Add a search command that filters by color or minimum carat.
  • Use flag package to accept command‑line arguments instead of an interactive menu.
  • Write unit tests with testing package – essential for any real software.

---

Common Issues & Fixes

go: go.mod file not found in current directory or any parent directory Run go mod init gem-cli in the project folder before building.

undefined: Gemstone Make sure the type Gemstone struct is defined above main() and that there are no typos. Also verify all names are exported (capitalized).

fmt.Scanln skips input Scanln can behave oddly if there's leftover newline in the buffer. A quick workaround is to use fmt.Scanf with newline handling or switch to bufio.NewReader(os.Stdin).

Binary won't run on another machine Cross‑compile with GOOS=linux GOARCH=amd64 go build (or for Windows/macOS).

---

Sponsored Deal

---

You've written a real CLI tool in Go from zero. The concepts you learned—structs, slices, methods, user input, loops—are the foundation for everything else in the language. Run with it.