Please disable your ad-blocker to access the content

Share

Golang Tutorial : Properties Viewer CLI Tool (windows)

Home » Programming Language » Golang » Golang Tutorial : Properties Viewer CLI Tool (windows)
golang-tutorial-properties-cli-tool

Go offers a simple way to build command-line tools using only standard libraries.

To write a Go program, you’ll need to setup Go setup up on your computer. If you’re not familiar with Go and want to spend a little extra time learning, you can take the Go tour to get started!

In this article we are going to create a CLI based tool for viewing the properties of folder and files as we use it in windows, linux and macos for viewing properties of a folder or file.

I’m going to build this CLI tool for windows you can also use it in other OS as well but you might need to update few code in it.

So the process of viewing properties in windows is mentioned below:

  • Right Click on the folder or file
  • Menu appears in which you will see lots of options like Open, Copy as path Properties and etc.
  • Click on the Properties
  • Pop up window appears in which you can see all the information of that file or folder e.g. full path, name, Size, created etc.
golang-tutorial-properties-cli-tool-image-1
golang-tutorial-properties-cli-tool-image-1
golang-tutorial-properties-cli-tool-image-2
golang-tutorial-properties-cli-tool-image-2

As you can see in properties window. It have all the information about that selected file or folder, So we are going to implement same functionality in CLI tool using Golang.

The functionality we’re going to implement are mentioned below:

  • Type: Will going to have type of selected file (if it’s folder or file).
  • Name: It will going to have the name of the opened folder or file.
  • Size: We will be calculating the size of the file or folder in bytes for calculating size we’ll be using filepath.WalkDir function so we can get the exact size of the given input.
  • Contains: The output of this is conditional based if input is a file then we’re not going to visible this otherwise if it’s a folder then we’re going to print it.
  • Created: We’re also going to print creation time of a file or folder.
  • Modified: Modified is not mentioned in the above picture but we’re going to print the last update time of provided input(file or folder).
  • Recently Accessed: We’re also checking the last access time of the file or folder(it works like you opened the folder and did no changes in it then the that time becomes Recently Accessed instead of modified because you just opened it and did no changes.
  • Permissions: We’re also checking the what permissions provided input have.

Preview

Viewing Properties of a File

golang-tutorial-properties-cli-tool-output-of-a-file
golang tutorial properties viewer cli tool output of a file

Viewing Properties of a Folder

golang-tutorial-properties-cli-tool-output-of-a-folder
golang tutorial properties viewer cli tool output of a folder

I’ll walk you through the each code block one by one so you can have more context on each block of codes.

  • Adding flag so user can view the properties.
  • Calling os.Stat(path) function for all information of provided path
  • Calculating total size of given input which can be file or folder
  • Function to get the count of files and folder if given input is a directory.
  • Storing values into variables
  • Printing all the info in table form

Adding Flag

loc := flag.String("f", "", "provide full path of file/folder")
flag.Parse()
if strings.TrimSpace(*loc) == "" {
    fmt.Println("flag is empty")
    flag.Usage()
    os.Exit(1)
}

In the first block of our code we have added a flag which will accept the full path of the file or folder for which you want to preview the information, after that we’re using flag.Parse() function so the input values can be passed through entire code.

As you can see we also have if condition for checking the provided flag values is blank or not if it’s blank then we don’t want our program to process it instead it’ll print a message and will terminate the entire process.

Creating stat function

// stat is to return the info of file and directory
func stat(path string) (fs.FileInfo, error) {
    fileInfo, err := os.Stat(path)
    return fileInfo, err
}

This block of code is first calling the os.Stat function which will return the fs.FileInfo function and fs.FileInfo holds the all information of the given file or folder, after calling os.Stat then we’re returning back all the information so information can be used by other functions as well.

Calculating total size of given input(folder or file)

func DirSize(path string) (int64, error) {
    var size int64
    err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        if !info.IsDir() {
            size += info.Size()
        }
        return err
    })
    return size, err
}

DirSize function is accepting a path and then using filepath.Walk function which reads all the file and folder information’s on root level so as you can see we’re only accessing the size of file on each iteration (working internally).

It’s increasing size of file based on condition as you can see in codebase.

Function to calculate total files and folders

func fileandfolderCount(path string) (int, int, error) {
    fi := 0
    fo := 0
    err := filepath.WalkDir(path, func(path string, info fs.DirEntry, err error) error {
        if err != nil {
            return err
        }

        if info.IsDir() {
            fo++
        } else {
            fi++
        }
        return nil
    })
    return fi, fo, err
}

For implementing the functionality of total files and folders as we have seen in properties window image, we have created fileandfolderCount function and what it does internally is:

  • Function accepts the string function parameter which is a path of file or folder.
  • then we’ve two variables which will hold the all counts fi variable will store the total count of files in given path, and fo will store the total count of folders in given path.
  • In next we’re calling filepath.WalkDir which accepts two parameters first is path and second is a function which will recursively read each directory exists in given path.
  • In filepath.WalkDir we have condition where we’re increasing the fi and fo accordingly.
  • In last we’re returning from the function if no error found in process.

Storing other values into variables

name := info.Name()
updatedAt := info.ModTime().Format(time.UnixDate)

sys := info.Sys().(*syscall.Win32FileAttributeData)
cTime := time.Unix(0, sys.CreationTime.Nanoseconds())
createdAt := cTime.Format(time.UnixDate)

aTime := time.Unix(0, sys.LastAccessTime.Nanoseconds())
lastAccess := aTime.Format(time.UnixDate)

In this block we’re storing all file info into their separate variables so it can be easily accessable, in first we’re storing file or folder name into separate variable called name, In next line we’re storing folder or file last updated time into separate variable called updatedAt and you can see we’re also formatting the time because info.ModTime returns the time which isn’t easy to understand that’s why we’re changing the format of it.

format of unix date “Mon Jan _2 15:04:05 MST 2006”

In last we’re accessing other values as well but as you can see we’re accessing the value for windows system using *syscall.Win32FileAttributeData

type Win32FileAttributeData struct {
    FileAttributes uint32
    CreationTime   Filetime
    LastAccessTime Filetime
    LastWriteTime  Filetime
    FileSizeHigh   uint32
    FileSizeLow    uint32
}

We’re using this because without this we can’t access the time of file creation and the last access time of file or folder.

We then assigning values to their respective variables then so they also can be easy to access.

Printing

fmt.Printf("+%s+\n", strings.Repeat("-", 80))
fmt.Printf("| %-20s | %-55s |\n", "Types of file", messageForDirAndFile(info.IsDir()))
fmt.Printf("|%s|\n", strings.Repeat("-", 80))
fmt.Printf("| %-20s | %-55s |\n", "Name", name)
fmt.Printf("+%s+\n", strings.Repeat("-", 80))
fmt.Printf("| %-20s | %s %-46s |\n", "Size", fmt.Sprintf("%d", size), "bytes")
fmt.Printf("+%s+\n", strings.Repeat("-", 80))
if folder != 0 && file != 1 {
    fmt.Printf("| %-20s | %s Files, %s %-39s |\n", "Contains", fmt.Sprintf("%d", file), fmt.Sprintf("%d", folder-1), "Folders")
    fmt.Printf("+%s+\n", strings.Repeat("-", 80))
}
fmt.Printf("| %-20s | %-55s |\n", "Permissions", info.Mode())
fmt.Printf("+%s+\n", strings.Repeat("-", 80))
fmt.Printf("| %-20s | %-55s |\n", "Created", createdAt)
fmt.Printf("+%s+\n", strings.Repeat("-", 80))
fmt.Printf("| %-20s | %-55s |\n", "Modified", updatedAt)
fmt.Printf("+%s+\n", strings.Repeat("-", 80))
fmt.Printf("| %-20s | %-55s |\n", "Recently Accessed", lastAccess)
fmt.Printf("+%s+\n", strings.Repeat("-", 80))

Now we have all the information we needed, we can print it so you can see we have multiple print statements for printing information accordingly

Below is the output and full code of the Properties Viewer CLI Tool

#Folder

$ go run main.go -f "C:\Users\KDSINGH\Desktop\singpass"
+--------------------------------------------------------------------------------+
| Types of file        | File folder                                             |
|--------------------------------------------------------------------------------|
| Name                 | singpass                                                |
+--------------------------------------------------------------------------------+
| Size                 | 19953229 bytes                                          |
+--------------------------------------------------------------------------------+
| Contains             | 2914 Files, 521 Folders                                 |
+--------------------------------------------------------------------------------+
| Permissions          | drwxrwxrwx                                              |
+--------------------------------------------------------------------------------+
| Created              | Wed Sep  7 16:06:51 IST 2022                            |
+--------------------------------------------------------------------------------+
| Modified             | Fri Sep  9 19:42:29 IST 2022                            |
+--------------------------------------------------------------------------------+
| Recently Accessed    | Wed Oct 26 14:07:11 IST 2022                            |
+--------------------------------------------------------------------------------+
#File

$ go run main.go -f "C:\Users\KDSINGH\Desktop\New Text Document.txt"
+--------------------------------------------------------------------------------+
| Types of file        | Simple file                                             |
|--------------------------------------------------------------------------------|
| Name                 | New Text Document.txt                                   |
+--------------------------------------------------------------------------------+
| Size                 | 26 bytes                                          |
+--------------------------------------------------------------------------------+
| Permissions          | -rw-rw-rw-                                              |
+--------------------------------------------------------------------------------+
| Created              | Mon Jul  4 16:58:42 IST 2022                            |
+--------------------------------------------------------------------------------+
| Modified             | Mon Jul  4 16:58:48 IST 2022                            |
+--------------------------------------------------------------------------------+
| Recently Accessed    | Wed Oct 26 12:17:42 IST 2022                            |
+--------------------------------------------------------------------------------+

Full Code

package main

import (
    "flag"
    "fmt"
    "io/fs"
    "os"
    "path/filepath"
    "strings"
    "syscall"
    "time"
)

func main() {

    loc := flag.String("f", "", "provide full path of file/folder")
    flag.Parse()
    if strings.TrimSpace(*loc) == "" {
        fmt.Println("flag is empty")
        flag.Usage()
        os.Exit(1)
    }

    info, err := stat(*loc)
    if err != nil {
        fmt.Println("failed to get stat of loc", err)
        os.Exit(2)
    }

    var size int64
    var file, folder int
    if info.IsDir() {
        size, err = DirSize(*loc)
        if err != nil {
            fmt.Println("failed to get stat of loc", err)
            os.Exit(2)
        }

        file, folder, err = fileandfolderCount(*loc)
        if err != nil {
            fmt.Println("failed to count folder and files into directory", err)
            os.Exit(2)
        }
    } else {
        size = info.Size()
        file = 1
        folder = 0
    }

    name := info.Name()
    updatedAt := info.ModTime().Format(time.UnixDate)

    sys := info.Sys().(*syscall.Win32FileAttributeData)
    cTime := time.Unix(0, sys.CreationTime.Nanoseconds())
    createdAt := cTime.Format(time.UnixDate)

    aTime := time.Unix(0, sys.LastAccessTime.Nanoseconds())
    lastAccess := aTime.Format(time.UnixDate)

    fmt.Printf("+%s+\n", strings.Repeat("-", 80))
    fmt.Printf("| %-20s | %-55s |\n", "Types of file", messageForDirAndFile(info.IsDir()))
    fmt.Printf("|%s|\n", strings.Repeat("-", 80))
    fmt.Printf("| %-20s | %-55s |\n", "Name", name)
    fmt.Printf("+%s+\n", strings.Repeat("-", 80))
    fmt.Printf("| %-20s | %s %-46s |\n", "Size", fmt.Sprintf("%d", size), "bytes")
    fmt.Printf("+%s+\n", strings.Repeat("-", 80))
    if folder != 0 && file != 1 {
        fmt.Printf("| %-20s | %s Files, %s %-39s |\n", "Contains", fmt.Sprintf("%d", file), fmt.Sprintf("%d", folder-1), "Folders")
        fmt.Printf("+%s+\n", strings.Repeat("-", 80))
    }
    fmt.Printf("| %-20s | %-55s |\n", "Permissions", info.Mode())
    fmt.Printf("+%s+\n", strings.Repeat("-", 80))
    fmt.Printf("| %-20s | %-55s |\n", "Created", createdAt)
    fmt.Printf("+%s+\n", strings.Repeat("-", 80))
    fmt.Printf("| %-20s | %-55s |\n", "Modified", updatedAt)
    fmt.Printf("+%s+\n", strings.Repeat("-", 80))
    fmt.Printf("| %-20s | %-55s |\n", "Recently Accessed", lastAccess)
    fmt.Printf("+%s+\n", strings.Repeat("-", 80))

}

func messageForDirAndFile(isDir bool) string {
    if isDir {
        return "File folder"
    }
    return "Simple file"
}

func fileandfolderCount(path string) (int, int, error) {
    fi := 0
    fo := 0
    err := filepath.WalkDir(path, func(path string, info fs.DirEntry, err error) error {
        if err != nil {
            return err
        }

        if info.IsDir() {
            fo++
        } else {
            fi++
        }
        return nil
    })
    return fi, fo, err
}

func DirSize(path string) (int64, error) {
    var size int64
    err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        if !info.IsDir() {
            size += info.Size()
        }
        return err
    })
    return size, err
}

// stat is to return the info of file and directory
func stat(path string) (fs.FileInfo, error) {
    fileInfo, err := os.Stat(path)
    return fileInfo, err
}

your comments are appreciated and if you wants to see your articles on this platform then please shoot a mail at this address kusingh@programmingeeksclub.com

Thanks for reading 🙂