A To-Do app in Vlang
V language, also known as Vlang promises C’s speed, Rust’s safety, Go’s syntax. However, there is only one Vlang book avaiable from Packt and even that is simply listing language features–a mere simpler documentation.
In this tutorial, I provide a real-world practical example: a command-line to-do app.
Audience:
Students and enthusiasts who have learned some programming but don’t know what to do next. They taught you to draw shapes, now draw the owl!–that type of frustrated learners who know language syntax (of any language) but don’t know what to do with it or where to begin the next step.
Why Vlang
Because it’s learner-friendly but also very modern. It is a new language, so there won’t be any jobs etc. for it–yet–but the syntax is very clear and similar to Go–the skills can transfer. Also, it’s fast!
Prereq
Familiarity with Vlang syntax. General programming 101 including structs, loops, functions, enums and IO.
Help
This tutorial is in beta now. Two versions are being written: one, for total beginners and will also teach them Vlang syntax and the programming 101 concepts, tools and techniques. Another version does not teach Vlang and programming but goes into great detail explaining each part of this program. The resulting booklets will be announced on my Quora and also here.
Let’s go!
First, let’s Import the needed modules:
import os
import flag
import json
import fmt
Next, we define a custom type for each task:
struct Task {
id int
name string
done bool
}
Define a global variable for the file name:
const file_name = 'tasks.json'
Define a function that loads the tasks from the file:
fn load_tasks() []Task {
// Check if the file exists
if !os.exists(file_name) {
// Return empty array if not
return []
}
// Open the file for reading
f := os.open(file_name) or {
// Handle any error
eprintln('Failed to open $file_name: $err')
exit(1)
}
// Read all the content from the file
data := f.read_all() or {
// Handle any error
eprintln('Failed to read $file_name: $err')
exit(1)
}
// Close the file
f.close() or {
// Handle any error
eprintln('Failed to close $file_name: $err')
exit(1)
}
// Decode the JSON data into an array of tasks
tasks := json.decode([]Task, data) or {
// Handle any error
eprintln('Failed to decode JSON data: $err')
exit(1)
}
// Return the array of tasks
return tasks
}
Now let’s define a function that saves the tasks to the file:
fn save_tasks(tasks []Task) {
// Encode the array of tasks into JSON data
data := json.encode(tasks) or {
// Handle any error
eprintln('Failed to encode JSON data: $err')
exit(1)
}
// Open the file for writing
f := os.create(file_name) or {
// Handle any error
eprintln('Failed to create $file_name: $err')
exit(1)
}
// Write the JSON data to the file
f.write(data) or {
// Handle any error
eprintln('Failed to write $file_name: $err')
exit(1)
}
// Close the file
f.close() or {
// Handle any error
eprintln('Failed to close $file_name: $err')
exit(1)
}
}
Next, a function that prints the usage information:
fn print_usage() {
println('Usage: todo [flags] [commands]')
println('A simple command line to-do list app.')
println('Flags:')
println(' -h, --help Show this help message and exit')
println('Commands:')
println(' add [name] Add a new task with the given name')
println(' list List all the tasks')
println(' done [id] Mark the task with the given id as done')
println(' delete [id] Delete the task with the given id')
}
Followed by a function that prints the tasks in a table format:
fn print_tasks(tasks []Task) {
// Define some constants for the table format
const (
col1_width = 5
col2_width = 30
col3_width = 10
sep_char = '-'
sep_line = sep_char.repeat(col1_width + col2_width + col3_width + 6)
)
// Print the table header
println(sep_line)
println('| ID | Name | Status |')
println(sep_line)
// Print each task in a row
for task in tasks {
// Format the task fields
id := fmt.pad_left(task.id.str(), col1_width, ' ')
name := fmt.pad_right(task.name, col2_width, ' ')
status := if task.done { 'Done' } else { 'Pending' }
status = fmt.pad_right(status, col3_width, ' ')
// Print the row
println('| $id | $name | $status |')
}
// Print the table footer
println(sep_line)
}
Define a function that adds a new task with the given name:
fn add_task(tasks mut []Task, name string) {
// Generate a unique id for the new task
mut id := 1
for task in tasks {
if task.id >= id {
id = task.id + 1
}
}
// Create a new task with the id, name, and done status
new_task := Task{
id: id
name: name
done: false
}
// Append the new task to the array of tasks
tasks << new_task
// Save the tasks to the file
save_tasks(tasks)
// Print a success message
println('Added a new task: $new_task')
}
Define a function that marks the task with the given id as done:
fn done_task(tasks mut []Task, id int) {
// Find the index of the task with the id in the array of tasks
mut index := -1
for i, task in tasks {
if task.id == id {
index = i
break
}
}
// Check if the index is valid
if index == -1 {
// Print an error message if not
eprintln('Invalid task id: $id')
exit(1)
}
// Mark the task as done by changing its done status to true
tasks[index].done = true
// Save the tasks to the file
save_tasks(tasks)
// Print a success message
println('Marked the task as done: $tasks[index]')
}
Define a function that deletes the task with the given id:
fn delete_task(tasks mut []Task, id int) {
// Find the index of the task with the id in the array of tasks
mut index := -1
for i, task in tasks {
if task.id == id {
index = i
break
}
}
// Check if the index is valid
if index == -1 {
// Print an error message if not
eprintln('Invalid task id: $id')
exit(1)
}
// Delete the task by removing it from the array of tasks
deleted_task := tasks[index]
tasks.delete(index)
// Save the tasks to the file
save_tasks(tasks)
// Print a success message
println('Deleted the task: $deleted_task')
}
Load the tasks from the file into an array of tasks
mut tasks := load_tasks()
Create a flag parser to parse command line arguments and options
mut fp := flag.new_flag_parser(os.args)
```v
Add some flags to show help information and version number
```v
fp.add_bool('help', `h`, false, 'Show this help message and exit')
fp.add_string('version', `v`, '', 'Show program version and exit')
Parse command line arguments and options into a map of flags and values
flags := fp.parse()
Check if help flag is set or no command is given
if flags['help'].bool() || fp.args.len == 0 {
// Print usage information and exit
print_usage
exit(0)
}
Check if version flag is set
if flags['version'].str() != '' {
// Print program version and exit
println('todo v0.1.0')
exit(0)
}
Get the command from the first argument
command := fp.args[0]
Check what command is given and call the corresponding function
match command {
'add' {
// Check if a name is given as the second argument
if fp.args.len < 2 {
// Print an error message and exit if not
eprintln('Missing task name')
exit(1)
}
// Get the name from the second argument
name := fp.args[1]
// Call the add_task function with the tasks array and the name
add_task(mut tasks, name)
}
'list' {
// Call the print_tasks function with the tasks array
print_tasks(tasks)
}
'done' {
// Check if an id is given as the second argument
if fp.args.len < 2 {
// Print an error message and exit if not
eprintln('Missing task id')
exit(1)
}
// Get the id from the second argument and convert it to an int
id := fp.args[1].int() or {
// Print an error message and exit if conversion fails
eprintln('Invalid task id: $fp.args[1]')
exit(1)
}
// Call the done_task function with the tasks array and the id
done_task(mut tasks, id)
}
'delete' {
// Check if an id is given as the second argument
if fp.args.len < 2 {
// Print an error message and exit if not
eprintln('Missing task id')
exit(1)
}
// Get the id from the second argument and convert it to an int
id := fp.args[1].int() or {
// Print an error message and exit if conversion fails
eprintln('Invalid task id: $fp.args[1]')
exit(1)
}
// Call the delete_task function with the tasks array and the id
delete_task(mut tasks, id)
}
else {
// Print an error message and exit if unknown command is given
eprintln('Unknown command: $command')
exit(1)
}
}