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)
    }
}