Table of Contents
- Introduction
- Prerequisites
- Setup
-
Creating the Recipe Manager - Defining the Data Structures - Loading Recipes from a File - Displaying the Recipe List - Adding a New Recipe - Searching for Recipes - Deleting a Recipe - Saving Changes to a File
- Conclusion
Introduction
In this tutorial, we will develop a command-line recipe manager using Go. The recipe manager will allow users to load, display, add, search, and delete recipes. By the end of this tutorial, you will have a working recipe manager implemented in Go.
Prerequisites
To follow along with this tutorial, you should have basic knowledge of the Go programming language. You should also have Go installed on your machine. If you need help installing Go, refer to the official Go documentation.
Setup
Before we start building our recipe manager, let’s set up the project structure and import the necessary packages.
Create a new directory for your project and initialize a Go module:
mkdir recipe-manager
cd recipe-manager
go mod init github.com/your-username/recipe-manager
Next, create a new Go file named main.go
:
touch main.go
Open the main.go
file in your preferred text editor.
Import the required packages:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
We have imported the bufio
, fmt
, os
, and strings
packages, which will be used for reading user input, formatting output, accessing the file system, and manipulating strings, respectively.
Now, let’s start building our recipe manager.
Creating the Recipe Manager
Defining the Data Structures
First, we need to define the data structures to represent a recipe. Each recipe will have a name, ingredients, and instructions.
Add the following struct definition to the main.go
file:
type Recipe struct {
Name string
Ingredients []string
Instructions string
}
This Recipe
struct will serve as our recipe data type.
Loading Recipes from a File
To allow users to load recipes from a file, let’s implement a function that reads recipes from a given file path and returns a slice of Recipe
structs.
Add the following function to the main.go
file:
func loadRecipesFromFile(filepath string) ([]Recipe, error) {
file, err := os.Open(filepath)
if err != nil {
return nil, err
}
defer file.Close()
var recipes []Recipe
scanner := bufio.NewScanner(file)
var currentRecipe Recipe
for scanner.Scan() {
line := scanner.Text()
if line == "" {
// Blank line; add completed recipe to the list
if currentRecipe.Name != "" {
recipes = append(recipes, currentRecipe)
currentRecipe = Recipe{}
}
} else {
// Non-blank line; populate recipe fields
if currentRecipe.Name == "" {
currentRecipe.Name = line
} else if currentRecipe.Instructions == "" {
currentRecipe.Instructions = line
} else {
currentRecipe.Ingredients = append(currentRecipe.Ingredients, line)
}
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
if currentRecipe.Name != "" {
recipes = append(recipes, currentRecipe)
}
return recipes, nil
}
The loadRecipesFromFile
function takes a file path as an argument and uses the os.Open
function to open the file. It utilizes a bufio.Scanner
to read the file line by line. We parse the lines and populate the Recipe
struct accordingly. Whenever we encounter a blank line, we add the completed Recipe
to the list of recipes. Finally, we return the slice of recipes.
Displaying the Recipe List
Next, let’s implement a function that displays the list of loaded recipes.
Add the following function to the main.go
file:
func displayRecipeList(recipes []Recipe) {
fmt.Println("Recipe List:")
fmt.Println("-------------------------------------")
for i, recipe := range recipes {
fmt.Printf("%d. %s\n", i+1, recipe.Name)
}
fmt.Println("-------------------------------------")
}
The displayRecipeList
function takes a slice of recipes as an argument and prints each recipe’s name with a corresponding number.
Adding a New Recipe
To enable users to add new recipes, let’s implement a function that prompts the user for recipe details and adds the new recipe to the recipe list.
Add the following function to the main.go
file:
func addRecipe(recipes []Recipe) []Recipe {
var recipe Recipe
fmt.Println("Add a New Recipe")
fmt.Println("-------------------------------------")
fmt.Print("Recipe Name: ")
reader := bufio.NewReader(os.Stdin)
recipe.Name, _ = reader.ReadString('\n')
recipe.Name = strings.TrimSpace(recipe.Name)
fmt.Println("Ingredients (one ingredient per line, enter an empty line to finish):")
for {
line, _ := reader.ReadString('\n')
line = strings.TrimSpace(line)
if line == "" {
break
}
recipe.Ingredients = append(recipe.Ingredients, line)
}
fmt.Println("Instructions:")
for {
line, _ := reader.ReadString('\n')
line = strings.TrimSpace(line)
if line == "" {
break
}
recipe.Instructions += line + "\n"
}
recipes = append(recipes, recipe)
fmt.Println("Recipe added successfully!")
return recipes
}
The addRecipe
function prompts the user for the recipe name, ingredients (entered one per line), and instructions. It adds the new recipe to the slice of recipes and returns the updated list.
Searching for Recipes
Let’s implement a function that allows users to search for recipes by name.
Add the following function to the main.go
file:
func searchRecipes(recipes []Recipe, searchTerm string) []Recipe {
var matchingRecipes []Recipe
for _, recipe := range recipes {
if strings.Contains(strings.ToLower(recipe.Name), strings.ToLower(searchTerm)) {
matchingRecipes = append(matchingRecipes, recipe)
}
}
return matchingRecipes
}
The searchRecipes
function takes a slice of recipes and a search term as arguments. It searches for recipes whose name contains the search term (case-insensitive) and returns a new slice containing the matching recipes.
Deleting a Recipe
To allow users to delete a recipe from the recipe list, let’s implement a function that removes a recipe based on its index.
Add the following function to the main.go
file:
func deleteRecipe(recipes []Recipe, index int) []Recipe {
if index < 0 || index >= len(recipes) {
fmt.Println("Invalid recipe index!")
return recipes
}
fmt.Printf("Are you sure you want to delete '%s'? (y/n): ", recipes[index].Name)
reader := bufio.NewReader(os.Stdin)
confirmation, _ := reader.ReadString('\n')
confirmation = strings.TrimSpace(confirmation)
if confirmation != "y" {
fmt.Println("Deletion canceled.")
return recipes
}
copy(recipes[index:], recipes[index+1:])
recipes[len(recipes)-1] = Recipe{}
recipes = recipes[:len(recipes)-1]
fmt.Println("Recipe deleted successfully!")
return recipes
}
The deleteRecipe
function takes a slice of recipes and an index as arguments. It removes the recipe at the specified index from the slice.
Saving Changes to a File
To allow users to save their changes to a file, let’s implement a function that writes the updated recipe list to a given file path.
Add the following function to the main.go
file:
func saveRecipesToFile(filepath string, recipes []Recipe) error {
file, err := os.Create(filepath)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
for _, recipe := range recipes {
fmt.Fprintln(writer, recipe.Name)
fmt.Fprintln(writer, recipe.Instructions)
for _, ingredient := range recipe.Ingredients {
fmt.Fprintln(writer, ingredient)
}
fmt.Fprintln(writer)
}
return writer.Flush()
}
The saveRecipesToFile
function takes a file path and a slice of recipes as arguments. It opens the file for writing, writes each recipe’s details to the file, and flushes the writer to ensure all data is written.
Conclusion
In this tutorial, we have developed a command-line recipe manager using Go. We learned how to load recipes from a file, display the recipe list, add new recipes, search for recipes, delete recipes, and save changes to a file. Feel free to enhance this recipe manager further by adding additional features such as editing recipes or improving the user interface.
Happy cooking with your command-line recipe manager in Go!