Files
tdm/tdm.go
T

1458 lines
58 KiB
Go
Raw Normal View History

2021-10-10 20:49:02 -04:00
// =============================================================================
// Auth: Alex Celani
// File: tdm.go
2022-03-11 21:31:13 -05:00
// Revn: 03-11-2022 1.0
2021-10-10 20:49:02 -04:00
//
// Func: display and manage progress of a litany of items to be done,
// with an organization scheme similar to Trello. It's CLI
// Trello
//
2022-03-11 21:31:13 -05:00
// TODO: add debug statements
//
// literally how do I handle parsing multi-word input to the
// program???
//
// add second object to hold command information
// make subsubtasks for greater depth
// M or rooms to contain boards
// handle command line args
// M remember last args
// research git branches
// handle ^C gracefully
// move most (read: all) helper functions to external files
// M add priority to tasks
// attach text files???
2021-10-10 20:49:02 -04:00
// fix data storage issue
// contemplate adding XOR of data in file, altho who cares
// contemplate adding battery of tests
// contemplate moving functions to external file
// side-by-side printing
// colored printing?
// keep track of finished items
// periodic tasks? something to do with the time package
2022-03-11 21:31:13 -05:00
// convert file delimiters to (customizable?) variables
2021-10-10 20:49:02 -04:00
// =============================================================================
// CHANGE LOG
// -----------------------------------------------------------------------------
// 10-04-2021: init
// wrote bones of string parsing into data structure
// 10-05-2021: commented
// 10-06-2021: entirely rewrote text format and text processor
// 10-09-2021: added reading from file
// wrote bones to while loop
// created empty functions for commands
// wrote, but did not test, getting user input
2022-03-11 21:31:13 -05:00
// 10-10-2021: fixed user input
2021-10-10 20:49:02 -04:00
// wrote help()
// wrote second draft of show(), fix immediately
2022-03-11 21:31:13 -05:00
// 02-21-2022: gutted main loop
// standardized method to pass commands to subfunctions
// commented main()
// 02-22-2022: made help() change log and comment block
// commented help()
// added XXX tags to "if flag {" statements
// added searchable structure to ease defining searches
// with multiple different types of function calls
// began restructuring show() to focus on sanitizing
// inputs before show() gets called
// 02-23-2022: changed searchable to query
// 02-24-2022: reduced scope of sanitize() to work with a more rigid
// set of inputs, just to get a stable release
// finished sanitize()
// tested sanitize()
// commented sanitize()
// wrote the /all/ portion of show()
// 02-25-2022: wrote rest of show()
// rewrote /show/ portion of help()
// connected sanitize() to show(), added show() to main()
// 02-27-2022: commented /all/ portion of show()
// 03-01-2022: planning for find() and delete()
// wrote and tested find(), checks out
// changed delete() to del() to avoid overloading builtin
// wrote and tested first draft of del()
// 03-02-2022: commented /board/, /task/, and /subtask/ portions of
// show()
// wrote mark()
// made show() accept commands of len greater than 3
// commented first draft of mark()
// 03-03-2022: commented first draft of find()
// commented del()
// 03-04-2022: improved error condition in mark()
// 03-06-2022: wrote first draft of parse() to replace sanitize()
// 03-07-2022: wrote first draft of сделай()
// 03-08-2022: tested следай()
// wrote and tested fileOut()
// watched Taylor Tomlinson's Look At You
// updated fileIn() to work with files that don't exist
// updated work functions to take queries instead of
// []strings
// 03-09-2022: updated function comment blocks
// 03-10-2022: commented fileOut()
// began commenting parse()
// began updating help()
//*03-11-2022: updated help()
// commented /move/ and /mark/ of parse()
2022-03-11 22:53:11 -05:00
// super fixed subtask propogation bug in fileIn()
2021-10-10 20:49:02 -04:00
//
// =============================================================================
package main
import (
"fmt" // used for Println
"strings" // used for Split
"strconv" // used for Atoi
"os" // used for Exit, Stdin
2022-03-11 21:31:13 -05:00
"io/ioutil" // used for ReadFile, WriteFile
2021-10-10 20:49:02 -04:00
"bufio" // used for NewScanner
)
// aliases for common functions
var p = fmt.Print
var print = fmt.Println
var printf = fmt.Printf
var split = strings.Split
var lower = strings.ToLower
var atoi = strconv.Atoi
2022-03-11 21:31:13 -05:00
var itoa = strconv.Itoa
2021-10-10 20:49:02 -04:00
var exit = os.Exit
var read = ioutil.ReadFile
2022-03-11 21:31:13 -05:00
var write = ioutil.WriteFile
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
// wrapper function to print input prompt, take user input
2021-10-10 20:49:02 -04:00
func input( ps1 string ) string {
2022-03-11 21:31:13 -05:00
p( ps1 ) // print prompt
// create scanner on stdin
2021-10-10 20:49:02 -04:00
scanner := bufio.NewScanner( os.Stdin )
2022-03-11 21:31:13 -05:00
scanner.Scan() // read input
return scanner.Text() // return text
2021-10-10 20:49:02 -04:00
}
2022-03-11 21:31:13 -05:00
// wrapper function to contain all the file writing
func fileOut() {
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
// initialize data to be written as empty string
var data string = ""
// keep track of how many boards have been written, so the
// delimiter isn't written after the last board
var boardCount int = 0
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
// iterate over all boards
for bname, bcontents := range boards {
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
// increment number of boards written
// actually caused a medium bug, because it's incremented
// before the check against len(), so if != len() - 1 placed
// delimiters in the wrong places
boardCount++
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
data = data + bname // add board name to writestring
data = data + "|" // XXX task delimiter
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
// iterate over tasks
for tind, tcontents := range bcontents {
data = data + tcontents.name // add task name
data = data + "," // XXX task delimiter
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
// convert fill back to ascii
// no error, because I guess all ints can be converted to
// ascii, but not all ascii can be an int?
// TODO think about keeping fill as ascii
// maybe check the conversion to see if it worked (i.e.
// user entered a number), but the ints are never
// _actually_ used
fill := itoa( tcontents.fill )
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
data = data + fill // add task fill
// does the task contain subtasks?
if len( tcontents.subt ) != 0 {
// XXX task's subtask delimiter
data = data + ":"
// iterate over a task's subtasks
for sind, scontents := range tcontents.subt {
// add subtask name
2022-03-11 22:53:11 -05:00
// XXX
if flag { // if debug set, print incoming
// subtask and data before...
print( "subt.name: ", scontents.name )
print( "pre data: ", data )
}
2022-03-11 21:31:13 -05:00
data = data + scontents.name
2022-03-11 22:53:11 -05:00
// XXX
if flag { // and after
print( "post data: ", data )
}
2022-03-11 21:31:13 -05:00
data = data + ">" // XXX subtask fill delim
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
// convert fill back to ascii
// TODO as above
fill = itoa( scontents.fill )
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
data = data + fill // add subtask fill
// check if NOT last subtask
if sind != len( tcontents.subt ) - 1 {
data = data + "." // XXX subtask delimiter
2021-10-10 20:49:02 -04:00
}
}
}
2022-03-11 21:31:13 -05:00
// check if NOT last task
if tind != len( bcontents ) - 1 {
data = data + ";" // XXX task delimiter
2021-10-10 20:49:02 -04:00
}
2022-03-11 21:31:13 -05:00
}
// check if NOT last board
// slightly different than before, explained above
if boardCount != len( boards ) {
data = data + "$" // XXX board delimiter
}
2021-10-10 20:49:02 -04:00
}
2022-03-11 21:31:13 -05:00
// cast writestring to byte array, write to file named "data"
// if a file doesn't exist, make with 0x644 permissions
// take error as err, if fails
err := write( "data", []byte( data ), 0644 )
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
if flag { // if debug set, print data written
print( data )
}
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
if err != nil { // if write() failed, tell user
print( "Error in fileOut(): file not properly written" )
print( err )
}
2021-10-10 20:49:02 -04:00
}
2022-03-11 21:31:13 -05:00
// warpper function to contain all the file reading
// literally yanked from main() and pasted here
func fileIn() {
2021-10-10 20:49:02 -04:00
// test string to use in lieu of a file
// file :=
// "b1|tA,:sI>30.sII>30;tB,25;tC,40$b2|tA,5;tB,"
// b1|tA,:sI>30.sII>30;tB,25;tC,40 ~ first board
// $ ~ board delimiter
// b2|tA,5;tB, ~ second board
// b1 b2 ~ board names
// |tA,:sI>30.sII>30;tB,25;tC,40 |tA,5;tB, ~ board contents
// | | ~ task marker
// ta tB tC tA tB ~ task names
// ; ; ; ~ task delimiter
// ,:sI>30.sII>30 ,25 ,40 ,5 , ~ task contents
// , ,25 ,40 ,5 , ~ task fill
// , , , , , ~ task fill delim
// :sI>30.sII>30 ~ task subtasks
// : ~ subtask marker
// sI sII ~ subtask names
// . ~ subtask delim
// >30 >30 ~ subtask fill
// > > ~ subtask fill delim
2022-03-11 21:31:13 -05:00
file, err := read( "data" ) // open file, failure set err
// declare board, because if statement gets mad if I don't
var board []string
// XXX
if flag { // if debug set, print raw file
print( "file: ", string( file ) )
}
// no file -> stop trying to build object, let boards be empty
2021-10-10 20:49:02 -04:00
if err != nil {
2022-03-11 21:31:13 -05:00
// notify user and leave
print( "No file found" )
return
} else { // if there is a file...
// $ -> separates different boards
board = split( string( file ), "$" )
2021-10-10 20:49:02 -04:00
}
2022-03-11 21:31:13 -05:00
// iterate over all boards
2021-10-10 20:49:02 -04:00
for a := 0; a < len( board ); a++ {
2022-03-11 21:31:13 -05:00
// split board by name and tasks
2021-10-10 20:49:02 -04:00
bContents := split( board[a], "|" )
2022-03-11 21:31:13 -05:00
bName := bContents[0] // assign name of board to var
bTasks := bContents[1] // assign tasks of board to var
// XXX
if flag { // if debug set, print board name
2021-10-10 20:49:02 -04:00
print( "board name -> ", bName )
}
2022-03-11 21:31:13 -05:00
// separate tasks from each other
2021-10-10 20:49:02 -04:00
tTasks := split( bTasks, ";" )
2022-03-11 21:31:13 -05:00
var tk task // make empty task
// iterate over all tasks
2021-10-10 20:49:02 -04:00
for b := 0; b < len( tTasks ); b++ {
2022-03-11 21:31:13 -05:00
// XXX
if flag { // if debug set, print raw tasks
2021-10-10 20:49:02 -04:00
print( "tTasks -> ", tTasks[b] )
}
2022-03-11 21:31:13 -05:00
// split subtasks out of tasks
2021-10-10 20:49:02 -04:00
subs := split( tTasks[b], ":" )
2022-03-11 21:31:13 -05:00
// len is not one, there are subtasks
// i.e. no reason to process subtasks
2021-10-10 20:49:02 -04:00
if len( subs ) != 1 {
2022-03-11 21:31:13 -05:00
// split list of subtasks
2021-10-10 20:49:02 -04:00
sTasks := split( subs[1], "." )
2022-03-11 21:31:13 -05:00
// iterate over subtasks
2021-10-10 20:49:02 -04:00
for c := 0; c < len( sTasks ); c++ {
2022-03-11 21:31:13 -05:00
// split subtasks name from fill
2021-10-10 20:49:02 -04:00
sContents := split( sTasks[c], ">" )
2022-03-11 21:31:13 -05:00
// convert fill from string to int, discard err
2021-10-10 20:49:02 -04:00
fil, _ := atoi( sContents[1] )
2022-03-11 21:31:13 -05:00
// create subtask object with this extracted info
2021-10-10 20:49:02 -04:00
stk := subtask { name: sContents[0], fill: fil }
2022-03-11 21:31:13 -05:00
// add subtask object to array of such
2021-10-10 20:49:02 -04:00
subtasks = append( subtasks, stk )
}
2022-03-11 21:31:13 -05:00
// add full subtask array to task object
2021-10-10 20:49:02 -04:00
tk.subt = subtasks
}
2022-03-11 21:31:13 -05:00
// separate task from fill
2021-10-10 20:49:02 -04:00
tContents := split( tTasks[b], "," )
2022-03-11 21:31:13 -05:00
// if there is no fill, interpret this as 0 fill
2021-10-10 20:49:02 -04:00
if len( tContents[1] ) == 0 {
2022-03-11 21:31:13 -05:00
// inferred fill is a string, so it can be processed
// like all the other fills
2021-10-10 20:49:02 -04:00
tContents[1] = "0"
}
2022-03-11 21:31:13 -05:00
tk.name = tContents[0] // set name of task
fil, _ := atoi( tContents[1] ) // convert fill to int
tk.fill = fil // set fill of task
tasks = append( tasks, tk ) // add task to task array
// XXX
if flag { // if debug set, print array of tasks
2021-10-10 20:49:02 -04:00
print( "[]task -> ", tasks )
}
2022-03-11 21:31:13 -05:00
2022-03-11 22:53:11 -05:00
// these lines are so stupid
2021-10-10 20:49:02 -04:00
tk.subt = nil
2022-03-11 22:53:11 -05:00
subtasks = nil
2022-03-11 21:31:13 -05:00
// if tk.subt is not set to nil, then the same array would
// go to every other task subtask array that follows
2021-10-10 20:49:02 -04:00
}
2022-03-11 21:31:13 -05:00
// make a key/value pair out of the board name and task array
// add it to hashmap
2021-10-10 20:49:02 -04:00
boards[bName] = tasks
2022-03-11 21:31:13 -05:00
tasks = nil // nil out task array, same reason with subt
2021-10-10 20:49:02 -04:00
}
2022-03-11 21:31:13 -05:00
// XXX
if flag { // if debug set, print entire structure
2021-10-10 20:49:02 -04:00
print( "\n\nboards -> ", boards, "\n\n" )
}
2022-03-11 21:31:13 -05:00
}
// -----------------------------------------------------------------------------
// help( commands []string )
//
// Revn: 03-10-2022
// Args: commands - array of strings
// list of commands, should begin with "help" here
// any subcommands would specify with which
// commands that help is needed
// Func: Prints help messages to the user. If the user requests help about
// a specific topic, function will print only that topic, otherwise
// print everything
// TODO: FIXME rewrite
// add /about/ subcommand
//
// add fail response? change structure to a switch/case
// check that commands[0] is actualy "help"
// make more targeted help prompts for when help has args
// -----------------------------------------------------------------------------
// CHANGE LOG
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ??-??-????: wrote bones to
// 02-22-2022: made change log
// commented
// 02-25-2022: rewrote /show/ to reflect new scope
// 03-10-2022: rewrote /delete/ to reflect new scope
// removed /move/ and /choose/ portions, because those
// commands were removed
//
// -----------------------------------------------------------------------------
func help( commands []string ) {
var helpFlag bool = true // init flag to view all
var c string // init var for command[1]
if len( commands ) > 1 { // check for plural commands
c = commands[1] // capture first command
// recall that commands[0] is the word "help"
// set flag to see every entry on user request
helpFlag = c == "all" || c == ""
}
// XXX
if flag { // if debug set, print commands[0] and helpFlag
print( "\n\nc ->", c )
print( "helpFlag ->", helpFlag )
print( "\n" )
}
print() // print extra line to avoid cluttered look
// help text really should speak for itself
// important things to note: helpFlag is set true by default
// helpFlag is set false if user asks for specific entry
// c is set to the specific entry
// if the specific entry is not present, nothing is printed
// this really should be changed to a switch/case tbh
if helpFlag || c == "delete" {
print( " delete [board] <b>" )
print( " \\-- delete board <b>" )
print( " \\ [board|task|subtask] <b|t|s>" )
print( " \\-- delete board <b>, task <t>, or subtask <s>, provided argument exists " )
}
if helpFlag || c == "make" {
print( " make [board] <b>" )
print( " \\-- create new board, <b>" )
print( " \\ [board] <b> task <t>" )
print( " \\-- create new task <t> under board <b>" )
print( " \\ task <t> subtask <s>" )
print( " \\-- create new subtask <s> under task <t>" )
}
if helpFlag || c == "mark" {
print( " mark task <t> <fill>" )
print( " \\ \\-- fill -> 0 - 100" )
print( " \\-- set progress of <t> to <fill>" )
print( " mark subtask <s> <fill>" )
print( " \\ \\-- fill -> 0 - 100" )
print( " \\-- set progress of <s> to <fill>" )
}
if helpFlag || c == "show" {
print( " show [all]" )
print( " \\-- display everything" )
print( " show [board] <b>" )
print( " \\-- display contents of board <b>" )
print( " show task <t>" )
print( " \\-- display contents of task <t>" )
print( " show subtask <s>" )
print( " \\-- display contents of subtask <s>" )
}
if helpFlag || c == "exit" || c == "quit" {
print( " exit" )
print( " quit" )
print( " \\-- exit program" )
}
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
print() // print extra line to avoid cluttered look
}
2021-10-10 20:49:02 -04:00
2022-03-11 21:31:13 -05:00
// -----------------------------------------------------------------------------
// delete( search query )
//
// Revn: 03-08-2022
// Args: search - query
// object that summarizes what needs deleting
// Func: delete entries
// Meth: call find() to get position of wanted entry, replace with
// last item in list, replace list with [:len()-1] slice
// TODO:
// -----------------------------------------------------------------------------
// CHANGE LOG
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 03-01-2022: major planning for deleting functionality
// plans for moving search functionality to new find()
// function
// wrote bones, not working
// 03-03-2022: commented
// 03-08-2022: changed arguments from commands []string to search query
//
// -----------------------------------------------------------------------------
func del( search query ) {
// determine what needs searching
// check to see if query returned empty
// not entirely sure if that's possible, but check for it
if search == ( query{} ) {
return
}
// determine "coordinates" of object that needs finding
// x -> ( string ) name of board, shouldn't ever be blank
// y -> ( int ) index of task array for a given board
// z -> ( int ) index of subtask array for a given task
x, y, z := find( search )
if x != "" { // should be a given
changed = true
if y == -1 { // not task, should be board
// maps have easy delete function
// delete, from map 'boards', key 'x'
delete( boards, x )
} else { // yes task
if z == -1 { // not subtask, should be task
// XXX it really needs to be mentioned that everything
// here has to be referenced absolutely, because if you
// make some other variable about it, like
// b := boards[x][y]
// when you go to reassign the array, it won't get rid of
// the last task, of which there are now two
// boards -> map[string][]task
// boards[x] -> []task
// boards[x][y] -> task
// int len( boards[x] ) - 1
// task boards[x][len( boards[x] ) - 1]
// take task at the end of the array, place it over
// unwanted task
boards[x][y] = boards[x][len( boards[x] ) - 1]
// take slice of everything but the last task (
// because there's two of them in the array ), assign
// it to array to replace array with two copies
boards[x] = boards[x][:len( boards[x] ) - 1]
} else { // yes subtask
// oh boy
// boards -> map[string][]task
// boards[x] -> []task
// boards[x][y] -> task
// boards[x][y].subt -> []subtask
// boards[x][y].subt[z] -> subtask
// int len( boards[x][y].subt ) - 1
// subtask boards[x][y].subt[len( boards[x][y].subt ) - 1]
// take subtask at the end of the array, place it over
// unwanted subtask
boards[x][y].subt[z] = boards[x][y].subt[len(
boards[x][y].subt ) - 1]
// take slice of everything but the last subtask (
// because there's two of them in the array ), assign
// it to array to replace array with two copies
boards[x][y].subt = boards[x][y].subt[:len( boards[x][y].subt ) - 1]
}
}
} else { // should be unreachable
print( "Error in delete(): board does not exist or not specified" )
}
}
// -----------------------------------------------------------------------------
// find( search query ) ( string, int, int )
//
// Revn: 03-03-2022
// Args: search - query
// object that outlines what needs finding
// Retn: x, y, z - string, int, int
// "coordinates" that outline where sought-after
// objects are
// Func: iterate over all everything until exact positions of
// sought-after object is found
// TODO: incorporate with sanitize?
// error messages
// -----------------------------------------------------------------------------
// CHANGE LOG
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 03-01-2022: essentially stole from show()
// 03-03-2022: commented
//
// -----------------------------------------------------------------------------
func find( search query ) ( string, int, int ) {
var rboard string // init string as empty
var rtask int = -1 // init ints as -1, the forbidden index
var rsubt int = -1
//subcom := search.subcom
brd := search.board
tsk := search.task
sbt := search.subtask
// if user is looking for a board
if brd != "" {
_, prs := boards[brd] // quick check to see if board exists
if prs == true { // if exists...
rboard = brd // set returnable board name
//} else { // requested board doesn't exist
// print error about it
// print( "Board /" + brd + "/ does not exist" )
}
} else if tsk != "" { // if user is looking for task
// iterate over all boards
for bd, contents := range boards {
// iterate over tasks in given board
for tindex, ts := range contents {
// if found task matches sought task
if ts.name == tsk {
// keep track of board and task index
rboard = bd
rtask = tindex
}
}
}
} else if sbt != "" { // if user is looking for subtask
// iterate over all boards
for bd, contents := range boards {
// iterate over tasks in given board
for tindex, ts := range contents {
// iterate over subtask in given board
for sindex, st := range ts.subt {
// if found subtask matches sought subtask
if st.name == sbt {
// keep track of board, task index, and
// subtask index
rboard = bd
rtask = tindex
rsubt = sindex
}
}
}
}
} else { // should be unreachable
// implies that user wasnt looking for board, task, or
// subtask
//print( "what is happen" )
}
// return returnable 3-tuple
return rboard, rtask, rsubt
}
// -----------------------------------------------------------------------------
// сделай( search query )
//
// Revn: 03-09-2022
// Args: search - query
// object that summarizes what needs making
// Func: create new boards, tasks, and subtasks
// Meth: find parent object, create new child object, attach
// TODO: write second draft
// add debug
// -----------------------------------------------------------------------------
// CHANGE LOG
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 03-07-2022: wrote first draft
// 03-10-2022: commented
//
// -----------------------------------------------------------------------------
func сделай( search query ) {
if flag { // if debug set, print contents of argument
print( search )
}
// init variable to mark if parent object exists
var exists bool = false
// declare variable to determine what caused error
var err string
if search.subtask == "" { // if subtask not specified
if search.task == "" { // if task not specified
if search.board == "" { // if board also not specified
// failure, make called without argument
// print error, call help about it
print( "Error in make()" )
print( "No object specified" )
makeFailure := []string{ "help", "make" }
help( makeFailure )
return
} else { // yes board
exists = true // signal that parent did exist
// declare empty task array
var t []task
// map board name to empty task array
boards[search.board] = t
changed = true // signal that file needs rewriting
}
} else { // yes task
if search.board == "" { // if no parent board
err = "board"
// failure, called to make a task belonging to no
// board
// print error, call help about it
print( "Error in make()" )
print( "Cannot make task without specifying board" )
makeFailure := []string{ "help", "make" }
help( makeFailure )
return
} else { // board is specified
exists = true // signal parent did exist
// create task with user-given name
tk := task{ name: search.task }
// grab []task that given board maps to
// i literally don't want to type search.board out two
// more times, v clunky and causes line overrun
preTask := boards[search.board]
if len( preTask ) == 0 { // if empty
// declare empty task array
var t []task
t = append( t, tk ) // place inside array
// map task array to new board
boards[search.board] = t
} else { // array not empty
// simply append to pre-existing task array
boards[search.board] = append( preTask, tk )
}
changed = true // signal file needs rewriting
}
}
} else { // subtask specified
if search.task == "" { // no task tho
err = "task"
// failure, called to make subtask with a parent task
// print error, call help about it
print( "Error in make()" )
print( "Cannot make subtask with specifying task" )
makeFailure := []string{ "help", "make" }
help( makeFailure )
return
} else { // yes task
// iterate over boards to look for requested task
for bname, bcontents := range boards {
// iterate over tasks in boards
for ind, t := range bcontents {
// if task name is given parent task name
if t.name == search.task {
exists = true // signal parent exists
// create subtask with user-given name
stk := subtask{ name: search.subtask }
// if task's subtask array is empty
if len( t.subt ) == 0 {
// declare empty subtask array
var s []subtask
// place created subtask inside array
s = append( s, stk )
// map subtask array to task
boards[bname][ind].subt = s
} else { // array not empty
// simply append to pre-existing subtask
// array
boards[bname][ind].subt = append( t.subt, stk )
}
// signal file needs rewriting
changed = true
}
}
}
}
}
if !exists { // nothing was changed, because parent object
// doesn't exist
// print error, call help about it
print( "Error in make()" )
p( "Parent ", err )
if err == "board" {
p( " /", search.board, "/ " )
} else if err == "task" {
p( " /", search.task, "/ " )
} else {
p( " object " )
}
print( "does not exist" )
makeFailure := []string{ "help", "make" }
help( makeFailure )
}
}
// -----------------------------------------------------------------------------
// mark( search query )
//
// Revn: 03-11-2022
// Args: search - query
// object that summarizes what needs showing
// Func: allow user to complete tasks
// Meth: call to find() to get "coords", set object at those coords
// to fill
// TODO:
// -----------------------------------------------------------------------------
// CHANGE LOG
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 03-02-2022: planned and wrote first draft, needs much error
// checking
// commented first draft
// 03-04-2022: made error condition show more information
// 03-09-2022: fixed error condition bug where program didn't know
// what kind of object user and printed hanging slash
// 03-11-2022: added reference to variable 'change', so fileOut()
// will run
//
// -----------------------------------------------------------------------------
func mark( search query ) {
// check to see if query returned empty
// not entirely sure if that's possible, but check for it
if search == ( query{} ) {
return
}
// determine "coordinates" of object that needs finding
// x -> ( string ) name of board, shouldn't ever be blank
// y -> ( int ) index of task array for a given board
// z -> ( int ) index of subtask array for a given task
x, y, z := find( search )
// index of last item len( commands ) - 1
// last item in array commands[len( commands ) - 1]
// convert string to int
// atoi( commands[len( commands ) - 1] )
// markFill -> converted value
// err -> contains value of atoi() error
// would happen if passed value isn't ascii
markFill, err := atoi( search.subcom )
// on error, print failure, call help about it, return
if err != nil {
print( "Fill value /" + search.subcom + "/ is not a number" )
markFailure := []string{ "help", "mark" }
help( markFailure )
return
}
// round fill down to 100 max, specifically allow negative fill
if markFill > 100 {
markFill = 100
}
if x != "" { // should be true, board is given
if y == -1 { // not task, should be board
// shouldn't be possible, boards don't have a fill
print( "Error in mark(): task does not exist or not specified" )
} else { // yes task
if z == -1 { // not subtask, should be task
// boards -> map[string][]task
// boards[x] -> []task
// boards[x][y] -> task
// boards[x][y].fill -> task.fill, fill field in task
boards[x][y].fill = markFill
changed = true // signal that file needs rewriting
} else { // yes subtask
// boards -> map[string][]task
// boards[x] -> []task
// boards[x][y] -> task
// boards[x][y].subt -> []subtask
// boards[x][y].subt[z] -> subtask
// boards[x][y].subt[z].fill -> subtask.fill
boards[x][y].subt[z].fill = markFill
changed = true // signal that file needs rewriting
}
}
} else { // reachable if the user gives input that DNE
// notify user requested object did not exist
p( "Error in mark(): " )
if search.board != "" {
p( "board /", search.board )
} else if search.task != "" {
p( "task /", search.task )
} else if search.subtask != "" {
p( "subtask /", search.subtask )
} else {
p( "object /", search.board )
}
print( "/ does not exist" )
}
}
// -----------------------------------------------------------------------------
// parse( commands []string )
//
// Revn: 03-10-2022
// Args: commands - []string
// list of commands of what board, task, or subtask
// to make
// Retn: search - query
// object that contains pertinent information
// Func: parse user input, determine what command to call and about
// what object is being called about
// Meth: lots and lots of switch statements
// switch over first command
// switch over length of subcommand
// switch over contents of subcommand
// TODO: comments for
// /make/
// /mark/
//
// contemplate help case
// move to external file?
// -----------------------------------------------------------------------------
// CHANGE LOG
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 03-06-2022: planned
// wrote everything but help case
// tested pass cases
// 03-10-2022: commented /delete/ portion
// commented /show/ portion
//
// -----------------------------------------------------------------------------
func parse( commands []string ) query {
// declare return object
var search query
// separate command from subcommands, take note of amount of
// subcommands
comm := commands[0]
subcommands := commands[1:]
subcomlen := len( subcommands )
// set pre-emptively, clear if it's bad
// unused as of right now
//search.command = comm
// big switch statement over command
switch comm {
// these don't really need processing, break out immediately
case "set", "quit", "exit":
return search
case "delete":
// there are only so many types of commands that work here
// -> delete <board>
// -> delete board <board>
// -> delete task <task>
// -> delete subtask <subtask>
// i.e. subcommand can have either length 1 or 2
switch subcomlen {
case 1:
// -> delete <board>
// requested item is subcommands[0], so set
search.board = subcommands[0]
case 2:
// -> delete board|task|subtask <object>
// can be any of three, so switch over subcom[0]
// kind of wish I could do like a reverse set
// like subcommands[1] = board or task or subtask
switch subcommands[0] {
case "board":
search.board = subcommands[1]
case "task":
search.task = subcommands[1]
case "subtask":
search.subtask = subcommands[1]
default: // user didn't input valid object
// print error, call help about it
print( "Incorrect object specified" )
p( "Object /", subcommands[0] )
print( "/ is not valid" )
delFailure := []string{ "help", "delete" }
help( delFailure )
}
default: // user had the wrong number of args
// print error, call help about it
print( "Incorrect number of arguments" )
delFailure := []string{ "help", "delete" }
help( delFailure )
}
case "help":
// help( subcommands )
case "make":
// -> make <board>
// -> make board <b>
// -> make <board> task <t>
// -> make board <b> task <t>
// -> make task <t> subtask <s>
// if subcommand length is 1 or 2, make a board
// if subcommand length is 3 or 4, make a task/subtask
switch subcomlen {
case 1:
// -> make <board>
// only subcommand is argument, so set
search.board = subcommands[0]
case 2:
// -> make board <b>
// double triple check that user asked for board,
// then set second subcommand
if subcommands[0] == "board" {
search.board = subcommands[1]
} else { // user didn't seem to want to make a
// board
// print error, call help about it
print( "Incorrect argument type:" )
print( "Object /", subcommands[0], "/ does not match the following types:" )
print( "/board/" )
makeFailure := []string{ "help", "make" }
help( makeFailure )
}
case 3:
// -> make <board> task <t>
// if task is actually task, set <board> to board
// and <t> to task
if subcommands[1] == "task" {
search.board = subcommands[0]
search.task = subcommands[2]
} else { // user didn't want to make a task
// print error, call help about it
print( "Incorrect argument type:" )
print( "Object /", subcommands[1], "/ does not match the following types:" )
makeFailure := []string{ "help", "make" }
help( makeFailure )
}
case 4:
// -> make board <b> task <t>
// -> make task <t> subtask <s>
// look for a signature that matches the two above
// set appropriate fields
if subcommands[0] == "board" && subcommands[2] == "task" {
search.board = subcommands[1]
search.task = subcommands[3]
} else if subcommands[0] == "task" && subcommands[2] == "subtask" {
search.task = subcommands[1]
search.subtask = subcommands[3]
} else { // user input didn't conform to scope
// print error, call help about it
print( "Unknown call signature" )
makeFailure := []string{ "help", "make" }
help( makeFailure )
}
default: // subcommand length > 4
// print error, call help about it
print( "Incorrect number of arguments" )
makeFailure := []string{ "help", "make" }
help( makeFailure )
}
case "mark":
// NYI -> mark <board> [100]
// NYI -> mark board <b> [100]
// -> mark task <t> fill
// -> mark subtask <s> fill
switch subcomlen {
case 1, 2:
// -> mark <board>
// -> mark board <b>
// -> mark <board> 100
// marking boards isn't support yet
// if subcomlen == 2 {
// if subcommands[0] == "board" {
// search.board = subcommands[1]
// } else {
// // fail
// // unrecognized argument object
// // /[subcommands[0]]/
// // help mark
// print( "fail h" )
// }
// } else {
// search.board = subcommands[0]
// }
// search.subcom = "100"
print( "Incorrect number of arguments" )
markFailure := []string{ "help", "mark" }
help( markFailure )
case 3:
// -> mark task <t> fill
// -> mark subtask <s> fill
// -> mark board <b> 100 NYI
// pretty simple, double check first subcommand to
// see what object is being marked, set name as
// needed, set fill to subcommand
switch subcommands[0] {
case "task":
search.task = subcommands[1]
case "subtask":
search.subtask = subcommands[1]
default: // user didnt want task or subtask
// print error, call help about it
p( "Object /", subcommands[0] )
print( "/ not recognized" )
markFailure := []string{ "help", "mark" }
help( markFailure )
}
search.subcom = subcommands[2]
default: // subcommand length != 3
// print error, call help about it
print( "Incorrect number of arguments" )
markFailure := []string{ "help", "mark" }
help( markFailure )
}
case "show":
// similar call signature to delete()
// -> show
// -> show all
// -> show <board>
// -> show board <b>
// -> show task <t>
// -> show subtask <s>
// subcom lengths of 1, 2, and 3 are all possible
switch subcomlen {
case 0:
// show
// equivalent to show all, so set to all
search.subcom = "all"
case 1:
// show all
// show <board>
// if user asks for all, give all
if subcommands[0] == "all" {
search.subcom = "all"
} else { // otherwise, user is asking for board
search.board = subcommands[0]
}
case 2:
// show [board|task|subtask] <b|t|s>
// switch to see what was specified, set the
// appropriate field in search, easy peasy
switch subcommands[0] {
case "board":
search.board = subcommands[1]
case "task":
search.task = subcommands[1]
case "subtask":
search.subtask = subcommands[1]
default: // user didn't input valid object
// print error, call help about it
print( "Incorrect object specified" )
p( "Object /", subcommands[0] )
print( "/ is not valid" )
showFailure := []string{ "help", "show" }
help( showFailure )
}
default: // user had the wrong number of args
// print error, call help about it
print( "Incorrect number of arguments" )
showFailure := []string{ "help", "show" }
help( showFailure )
}
default: // command not recognized
// print error, call help about it
print( "Command /", comm, "/ not recognized" )
commFailure := []string{ "help" }
help( commFailure )
}
return search
}
// -----------------------------------------------------------------------------
// show( search query )
//
// Revn: 03-09-2022
// Args: search - query
// object that summarizes what needs showing
// Func: display stored information to the user
// Meth: iterate through everything, look for mathcing info, print
// TODO: stylize print in some way
// add debug flag
// add call to find()
// -----------------------------------------------------------------------------
// CHANGE LOG
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 02-24-2022: wrote bones
// wrote /all/ portion
// 02-25-2022: implemented /board/, /task/, and /subtask/ portions
// added calls to help() on error
// changed input argument from type query to type
// []string, so it can call sanitize()
// 02-27-2022: commented /all/ portion
// 03-02-2022: commented /board/, /task/, and /subtask/ portions
//
// -----------------------------------------------------------------------------
func show( search query ) {
// check to see if query returned empty
// not entirely sure if that's possible, but check for it
if search == ( query{} ) {
return
}
// ease of use variables from search struct
subcom := search.subcom
sbd := search.board
stk := search.task
sstk := search.subtask
if subcom != "" { // show all
// might not actually contain "all", double check
if subcom == "all" {
// iterate over boards' contents
for showBoard, contents := range boards {
// show all -> print board name
print( showBoard )
// iterate over board's tasks
for _, showTask := range contents {
// print indent with no trailing newline
// indent has len 4
p( " " )
// print task name, right justified (with respect
// to the indent)
printf( "%-16s", showTask.name )
// print task fill, left justified
// indent len 4 + 16 = 20
printf( "%16d\n", showTask.fill )
// iterate over the task's subtasks
for _, showSubtask := range showTask.subt {
// double indent
// double indent has len 8
p( " " )
// print subtask name, right justified (with
// respect to indent again)
printf( "%-16s", showSubtask.name )
// print subtask fill, left justified
// indent len 8 + 12 = 20
printf( "%12d\n", showSubtask.fill )
}
}
}
} else { // if command isn't "all"
// print error, call help about it
print( "Error in show()" )
print( "Subcommand /" + subcom + "/ does not exist" )
showFailure := []string{ "help", "show" }
help( showFailure )
return
}
} else if sbd != "" { // show board <>
// grab user requested board from boards
// prs is set true if board exists
contents, prs := boards[sbd]
if prs { // if user specified board exists
print( sbd ) // print board name
// iterate over tasks in board
for _, showTask := range contents {
// print indent with no trailing newline
// indent has len 4
p( " " )
// print task name, right justified (with respect
// to the indent)
printf( "%-16s", showTask.name )
// print task fill, left justified
// indent len 4 + 16 = 20
printf( "%16d\n", showTask.fill )
for _, showSubtask := range showTask.subt {
// double indent
// double indent has len 8
p( " " )
// print subtask name, right justified (with
// respect to indent again)
printf( "%-12s", showSubtask.name )
// print subtask fill, left justified
// indent len 8 + 12 = 20
printf( "%16d\n", showSubtask.fill )
}
}
} else { // if board DNE
// print error, call help about it
print( "Board /" + sbd + "/ does not exist" )
showFailure := []string{ "help", "show" }
help( showFailure )
return
}
} else if stk != "" { // show task <>
// can check if a board is present with _, prs :=
// but this is a flag to be set if a task exists
var exists bool
// iterate over all boards looking for named task
for showBoard, contents := range boards {
// iterate through all tasks in boards
for _, showTask := range contents {
// look for matching task name
if showTask.name == stk {
exists = true // set exists true
// print containing board name
print( showBoard )
// print task name with same justification
printf( "%-16s", showTask.name )
// print task fill with right justification
printf( "%20d\n", showTask.fill )
// iterate over contained subtasks
for _, showSubtask := range showTask.subt {
// print indent
p( " " )
// print subtask name left justified
printf( "%-16s", showSubtask.name )
// print subtask fill right justified
printf( "%16d\n", showSubtask.fill )
}
}
}
}
if !exists { // if task not found
// print error
// call help about it
print( "Task /" + stk + "/ does not exist" )
showFailure := []string{ "help", "show" }
help( showFailure )
return
}
} else if sstk != "" { // show subtask <>
// can check if a board is present with _, prs :=
// but this is a flag to be set if a task exists
var exists bool
// iterate over all boards looking for named task
for showBoard, contents := range boards {
// iterate through all tasks in boards
for _, showTask := range contents {
// iterate over contained subtasks
for _, showSubtask := range showTask.subt {
// look for matching subtask name
if showSubtask.name == sstk {
exists = true // set exists true
// print containing board name
print( showBoard )
// print containing task name
print( showTask.name )
// print subtask name left justified
printf( "%-16s", showSubtask.name )
// print subtask fill right justified
printf( "%20d\n", showSubtask.fill )
}
}
}
}
if !exists { // if subtask not found
// print error
// call help about it
print( "Subtask /" + sstk + "/ does not exist" )
showFailure := []string{ "help", "show" }
help( showFailure )
return
}
} else { // should be undefined behavior
// print contents of the query (human-readable)
print( "Error with input query!" )
print( " query.board -> " + sbd )
print( " query.task -> " + stk )
print( " query.subtask -> " + sstk )
print( " query.subcommand -> " + subcom )
// call help about it
showFailure := []string{ "help", "show" }
help( showFailure )
return
}
}
var flag bool = false // debug flag
var changed bool = false
// struct used for defining search parameters
type query struct { // relatively clean way to specify...
board string // a board
task string // a task
subtask string // a subtask
subcom string // and\or a subcommand
command string // and finally the command
}
/*
type subsubtask struct {
name string // subsubtasks are named
fill int // subsubtasks contain progress
//note string // subsubtasks can contain files
}
*/
// struct used for defining what constitutes a 'subtask'
type subtask struct {
name string // subtasks are named
fill int // subtasks contain progress
//ssbt subsubtask // subtasks can contain subsubtasks
//note string // subtasks can contain files
}
// struct used for defining what constitutes a 'task'
type task struct {
name string // tasks are named
fill int // tasks contain progress
subt []subtask // tasks can contain subtasks
//note string // tasks can contain files
}
// boards is a hashmap map[ ]
// that relates strings string
// to an array of tasks []task
var boards = make( map[string][]task )
2022-03-11 22:53:11 -05:00
var tasks []task // array of tasks referenced above
2022-03-11 21:31:13 -05:00
var subtasks []subtask // array of subtasks belonging to tasks
func main() {
fileIn()
for { // infinite loop, where everything happens
userIn := input( "-> " ) // take user input
ui := split( userIn, " " ) // split over spaces
command := ui[0] // first entry is command
// convention: send entire array to subfunction, do not strip
// any subcommands off
// switch case over command
// recognize user input, call appropriate command
var search query = parse( ui )
2021-10-10 20:49:02 -04:00
switch command {
2022-03-11 21:31:13 -05:00
case "delete":
del( search )
2021-10-10 20:49:02 -04:00
case "help":
2022-03-11 21:31:13 -05:00
help( ui )
case "make":
// make translated into Russian, because make is a
// GoLang keyword, and Norwegian word was less cool
// pronounced like 'zdye-lie'
сделай( search )
2021-10-10 20:49:02 -04:00
case "mark":
2022-03-11 21:31:13 -05:00
mark( search )
2021-10-10 20:49:02 -04:00
case "show":
2022-03-11 21:31:13 -05:00
show( search )
case "exit", "quit":
if flag { // if debug set, print if file was changed
print( changed )
}
// if file was changed, rewrite to file
if changed {
fileOut()
}
os.Exit( 0 ) // exeunt
case "set":
// XXX
// turn debug flag on and off
// conveniently only recognizes first and last word of
// command; "set true" will work, as will "set flag
// false" and "set sprite coke pepsi"
flag = ui[len( ui ) - 1] == "true"
default: // if the input is unrecognized
continue // try again
2021-10-10 20:49:02 -04:00
}
2022-03-11 21:31:13 -05:00
2021-10-10 20:49:02 -04:00
}
}
2022-03-11 21:31:13 -05:00