Merge pull request #1 from m455/rewrite-progress

Rewrite progress
main
Jesse Laprade 2019-11-19 21:55:32 -05:00 committed by GitHub
commit 4172db3259
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 234 additions and 211 deletions

View File

@ -1,6 +1,6 @@
# rodo
A minimal todo list program for people who live on the command line.
A minimal list manager for people who live on the command line.
# Screenshot
@ -54,17 +54,17 @@ If you are using a single-file executable, create a wrapper as follows:
# Introduction
This readme will guide you through downloading, installing, and using the rodo
todo list program. It is intended for people who spend a lot of their time on the
command line and want a minimal todo list application.
list manager. It is intended for people who spend a lot of their time on the
command line and want a minimal list manager.
# Conventions used in this readme
* Notes - Notes signify additional information
* Tips - Tips signify an alternate procedure for completing a step
* Cautions - Cautions signify that damage may occur
* Examples - Examples provide a visual reference of how a procedure would be carried out in the real world
* **Note** - Notes signify additional information
* **Tip**- Tips signify an alternate procedure for completing a step
* **Caution** - Cautions signify that damage may occur
* **Example** - Examples provide a visual reference of how a procedure would be carried out in the real world
* `Inline code` - Inline code signifies package names, filenames, or commands
* ```Code blocks``` - Code blocks signify file contents
* ```Code block``` - Code blocks signify file contents
# Platforms
@ -191,5 +191,5 @@ The examples below assume that you have [added rodo to your $PATH](#adding-rodo-
**Caution**: Changing the `config.rkt` file should be done at your own risk as it may break rodo's functionality
Settings such as the program name, directory, and the filename of the todo list
Settings such as the program name, directory, and the filename of the list
file can be changed by editing the `config.rkt` file.

View File

@ -2,45 +2,44 @@
(require (prefix-in config: "config.rkt")
(prefix-in init: "init.rkt")
(prefix-in file: racket/file)
(prefix-in messages: "messages.rkt")
(prefix-in utils: "utils.rkt"))
(provide (all-defined-out))
(define (check-args args)
(let ([args-length (vector-length args)])
(let ([args-length (length args)])
(cond
[(equal? args-length 0)
(utils:display-hash-ref messages:messages 'show-usage)]
;; help
;; help-command
[(and (equal? args-length 1)
(member (vector-ref args 0) config:help-command))
(member (list-ref args 0) config:help-command))
(utils:display-hash-ref messages:messages 'show-help)]
;; init
;; initialize-command
[(and (equal? args-length 1)
(member (vector-ref args 0) config:initialize-command))
(member (list-ref args 0) config:initialize-command))
(init:initialize)]
;; add
;; add-command
[(and (or (equal? args-length 2) (>= args-length 2))
(member (vector-ref args 0) config:add-command))
(utils:add-item-to-list config:path-to-list-file args)]
(member (list-ref args 0) config:add-command))
(utils:add-item-to-list config:list-file args)]
;; ls
;; list-command
[(and (equal? args-length 1)
(member (vector-ref args 0) config:list-command))
(utils:show-list-from-file config:path-to-list-file)]
(member (list-ref args 0) config:list-command))
(utils:show-list-from-file config:list-file)]
;; rm
;; remove-command
[(and (equal? args-length 2)
(member (vector-ref args 0) config:remove-command)
(real? (string->number (vector-ref args 1)))
(or (positive? (string->number (vector-ref args 1)))
(zero? (string->number (vector-ref args 1))))
;; Length subtract one because the numbering starts at zero
(not (> (string->number (vector-ref args 1)) (sub1 (length (utils:file->string-list config:path-to-list-file))))))
(utils:remove-item-from-list config:path-to-list-file args)]
(member (list-ref args 0) config:remove-command)
(real? (string->number (list-ref args 1)))
(or (positive? (string->number (list-ref args 1)))
(zero? (string->number (list-ref args 1)))))
(utils:remove-item-from-list args)]
[else (utils:display-hash-ref messages:messages 'show-usage)])))

View File

@ -1,7 +1,7 @@
#lang racket/base
(provide (all-defined-out))
(define list-file "todo.txt")
(define file-name "list.txt")
(define program-name "rodo")
(define program-directory
@ -9,25 +9,30 @@
(expand-user-path
(string-append "~/." program-name "/"))))
(define path-to-list-file
(string-append program-directory list-file))
(define list-file
(string-append program-directory file-name))
;; TODO: pluralize this value's name
(define help-command '("-h"
"--help"
"h"
"help"))
;; TODO: pluralize this value's name
(define initialize-command '("init"
"create"
"start"
"begin"))
;; TODO: pluralize this value's name
(define add-command '("add"
"a"))
;; TODO: pluralize this value's name
(define list-command '("ls"
"list"))
;; TODO: pluralize this value's name
(define remove-command '("rm"
"remove"
"del"

View File

@ -13,10 +13,10 @@
(cond [(member user-input (hash-ref messages:y/n 'yes))
(begin
(utils:display-hash-ref messages:messages 'creating)
(utils:create-program-directory-700 config:program-directory)
(utils:create-list-file-600 config:path-to-list-file)
(if (and (utils:program-directory-exists?)
(utils:list-file-exists?))
(utils:create-directory-700 config:program-directory)
(utils:create-file-600 config:list-file)
(if (and (directory-exists? config:program-directory)
(file-exists? config:list-file))
(utils:display-hash-ref messages:messages 'successfully-created)
(utils:display-hash-ref messages:messages 'creation-error)))]
[(member user-input (hash-ref messages:y/n 'no))
@ -25,7 +25,7 @@
(init-prompt messages:messages 'choose-y/n)])))
(define (initialize)
(if (utils:list-file-exists?)
(if (file-exists? config:list-file)
(utils:display-hash-ref messages:messages 'file-already-exists)
(begin
(init-prompt messages:messages 'init-y/n))))

View File

@ -4,127 +4,137 @@
(provide (all-defined-out))
(define newline "\n")
(define messages
(hash
'show-help (string-append
"Usage\n"
"=====\n"
(format "If you want to use the ~a or ~a commands, follow the structure below:\n"
config:initialize-command
config:list-command)
(format "~a <command>\n\n"
config:program-name)
'show-help
(string-append "\n"
"Conventions used in this help message\n"
"====================================="
"\n\n"
"[command] - [command]s should be replaced by a command from the Command section below without the surrounding \"[\" and \"]\"s."
"\n\n"
"<argument> - <argument>s should be replaced by user input without the surrounding \"<\" and \">\"s."
"\n\n"
"Command descriptions\n"
"===================="
"\n\n"
(format "If you want to use the ~a or ~a commands, follow the structure below:\n"
config:add-command
config:remove-command)
(format "~a <command> [argument]\n\n" config:program-name)
;; initialize-command
(format "~a" (car config:initialize-command))
"\n\n"
"\t"
(format "Creates a list in ~a." config:list-file)
"\n\n"
"Note: Replace <command> with one of the Commands below without the surrounding \"<\" and \">\".\n\n"
;; list-command
(format "~a" (car config:list-command))
"\n\n"
"\t"
"Displays items in your list."
"\n\n"
"Commands\n"
"========\n"
(format "This section describes the commands available for ~a.\n\n" config:program-name)
(format "Note: The commands mentioned in this section should replace the \"<command>\" filler text found in the Usage section above.\n\n")
;; add-command
(format "~a" (car config:add-command))
"\n\n"
"\t"
"Adds an item to your list."
"\n\n"
;; init
(format "~a\n" config:initialize-command)
"====\n"
(format "Creates ~a in ~a. This is where your todo list will be stored.\n\n"
config:list-file
config:program-directory)
;; remove-command
(format "~a" (car config:remove-command))
"\n\n"
"\t"
"Removes an item from your list."
"\n\n"
"Example:\n"
(format "~a ~a\n\n" config:program-name config:initialize-command)
"Usage examples\n"
"=============="
"\n\n"
;; initialize-command
(format "~a" (car config:initialize-command))
"\n\n"
"\t"
(format "~a ~a" config:program-name (car config:initialize-command))
"\n\n"
;; ls
(format "~a\n" config:list-command)
"====\n"
"Displays items in your todo list.\n\n"
;; list-command
(format "~a" (car config:list-command))
"\n\n"
"\t"
(format "~a ~a" config:program-name (car config:list-command))
"\n\n"
"Example:\n"
(format "~a ~a\n\n" config:program-name config:list-command)
;; add-command
(format "~a" (car config:add-command))
"\n\n"
"\t"
(format "~a ~a this is an item without double quotation marks" config:program-name (car config:add-command))
"\n\n"
"\t"
(format "~a ~a \"this is an item surrounded by double quotation marks\"" config:program-name (car config:add-command))
"\n\n"
"\t"
"Note: Grave accents (`) and single quotation marks (\') cannot be used with this command."
"\n\n"
(format "~a [argument(s)]\n" config:add-command)
"=================\n"
"Adds an item to the list.\n\n"
;; remove-command
(format "~a" (car config:remove-command))
"\n\n"
"\t"
(format "~a ~a 2" config:program-name (car config:remove-command))
"\n\n"
"\t"
"Note: The example above will remove the third item from your list, because the list starts at zero."
"\n\n"
"Examples:\n"
(format "~a ~a this is an unquoted item\n" config:program-name config:add-command)
(format "~a ~a \"this is a quoted item\"\n\n" config:program-name config:add-command)
"Can't see the whole help message?\n"
"================================="
"\n\n"
"\t"
(format "Try running \"~a -h | less\" (without the double quotation marks), so you can use the arrow keys to scroll up and down." config:program-name)
"\n\n"
"\t"
"When you want to quit, type \"q\" (without the double quotation marks)."
"\n\n")
(format "~a [argument]\n" config:remove-command)
"=============\n"
"Removes an item from the list.\n\n"
'empty-list "> There is nothing in your list.\n"
"Example:\n"
(format "~a ~a 1\n\n" config:program-name config:remove-command)
'show-usage (format "> For usage type \"~a -h\" (without the double quotation marks).\n" config:program-name)
(format "Note: You may have to run `~a ~a` to see which number corresponds to which item in your todo list."
config:program-name
config:list-command)
" "
"In the example above, the first item was removed from the todo list.\n\n"
'creating (format "> Creating a list in ~a...\n" config:list-file)
"Can't see the whole help message?\n"
"=================================\n"
(format "Try running `~a -h | less` so you can use the arrow keys to scroll up and down."
config:program-name)
" "
"When you want to quit, type `q` (without the grave accents).\n")
'creation-error (format "> Error: Could not create a list file in ~a.\n" config:list-file)
'empty-todo-list "> There is nothing in your list.\n"
'file-already-exists (format "> Error: A list file already exists in ~a.\n" config:list-file)
'show-usage (format "> For usage type `~a -h` or `~a --help`.\n"
config:program-name
config:program-name)
'successfully-created (format "> Your list file was successfully created in ~a.\n" config:list-file)
'creating (format "> Creating ~a in ~a.\n"
config:list-file
config:program-directory)
'file-not-found (format "> Error: Could not find ~a.\n" config:list-file)
'creation-error (string-append
(format "> Error: Could not create a(n) ~a in ~a.\n"
config:list-file
config:program-directory)
"> This might be due to directory permissions.\n")
'item-not-found "> Error: Could not find that item.\n"
'file-already-exists (format "> Error: ~a already exists in ~a~a.\n"
config:program-name
config:program-directory
config:list-file)
'init-y/n (format (string-append "> A list file will be created in ~a.\n"
"> Are you sure you want to continue? [y/n].\n")
config:list-file)
'successfully-created (format "> ~a has been successfully created in ~a.\n"
config:program-directory
config:list-file)
'try-init (format "> Try typing \"~a ~a\" to set it up (without the double quotation marks).\n"
config:program-name (car config:initialize-command))
'file-not-found (format "> Error: Could not find ~a~a\n"
config:program-directory
config:list-file)
'terminating (format "> Exited ~a.\n" config:program-name)
'init-y/n (string-append
(format "> ~a will be created in ~a.\n"
config:list-file
config:program-directory)
"> Are you sure you want to continue? [y/n]\n")
'choose-y/n "> Error: Please choose y or n\n"
'try-init (format "> Try typing `~a ~a` to set it up (without the grave accents).\n"
config:program-name
config:initialize-command)
'not-in-list "> Error: Item does not exist\n"
'terminating (format "> Exiting ~a\n" config:program-name)
'item-added-prefix "> Added "
'choose-y/n "> Error: Please choose y or n\n"
'item-added-suffix " to list\n"
'not-in-list "> Error: Item does not exist\n"
'item-removed-prefix "> Removed "
'item-added-prefix "> Added "
'item-added-suffix " to list\n"
'item-removed-prefix "> Removed "
'item-removed-suffix " from list\n"))
'item-removed-suffix " from list\n"))
(define y/n (hash 'yes '("yes" "Yes" "y" "Y")
'no '("no" "No" "n" "N")))
'no '("no" "No" "n" "N")))

View File

@ -5,4 +5,4 @@
(define (main args)
(args:check-args args))
(main (current-command-line-arguments))
(main (vector->list (current-command-line-arguments)))

169
utils.rkt
View File

@ -1,120 +1,129 @@
#lang racket/base
(require (prefix-in file: racket/file)
(prefix-in list: racket/list)
(prefix-in string: racket/string)
(prefix-in config: "config.rkt")
(require (prefix-in file: racket/file)
(prefix-in list: racket/list)
(prefix-in string: racket/string)
(prefix-in config: "config.rkt")
(prefix-in messages: "messages.rkt"))
(provide (all-defined-out))
(define (list-file-exists?)
(file-exists? config:path-to-list-file))
(define (program-directory-exists?)
(directory-exists? config:program-directory))
(define (create-list-file-600 a-file)
(let ([opened-file (open-output-file a-file
#:mode 'text
#:exists 'truncate)])
(define (create-file-600 a-file)
(let ([opened-file (open-output-file a-file #:mode 'text #:exists 'truncate)])
(close-output-port opened-file))
(file-or-directory-permissions a-file #o600))
(define (create-program-directory-700 a-directory)
(define (create-directory-700 a-directory)
(make-directory a-directory)
(file-or-directory-permissions a-directory #o700))
(define (display-hash-ref hash-list key)
(display (hash-ref hash-list key)))
(define (file->string-list a-file)
(let ([todo-list (file:file->lines a-file
#:mode 'text
#:line-mode 'any)])
todo-list))
(define (list->ascending-numbers lst)
(list:range (length lst)))
(define (list-file-empty? a-file)
(list:empty? (file->string-list a-file)))
(define (get-removed-item lst args)
(list-ref (file->string-list lst) (string->number args)))
(define (surround-with-quotation-marks args)
(display (string-append "\"" args "\"")))
(define (list->list-of-dotted-numbers lst)
(define (list->dotted-ascending-numbers lst)
(map (lambda (x) (string-append x ". "))
(map number->string (list:range (length lst)))))
(map number->string (list->ascending-numbers lst))))
(define (list->numbered-list lst)
(map (lambda (x y) (string-append x y))
(list->list-of-dotted-numbers lst)
(list->dotted-ascending-numbers lst)
lst))
(define (file->numbered-list-as-lines a-file)
(string:string-join (list->numbered-list (file->string-list a-file))
"\n"
#:after-last "\n"))
(define (file->vertically-numbered-list a-file)
(string:string-join
(list->numbered-list (file:file->lines a-file))
"\n"
#:after-last "\n"))
(define (append-item-to-list args lst)
(reverse (cons args (reverse (file->string-list lst)))))
(define (surround-with-quotation-marks item)
(string-append "\"" item "\""))
(define (display-item-added args)
(define (display-item-added item)
(display-hash-ref messages:messages 'item-added-prefix)
(surround-with-quotation-marks args)
(display (surround-with-quotation-marks item))
(display-hash-ref messages:messages 'item-added-suffix))
(define (display-item-removed args)
(display-hash-ref messages:messages 'item-removed-prefix)
(surround-with-quotation-marks args)
(display (surround-with-quotation-marks args))
(display-hash-ref messages:messages 'item-removed-suffix))
;; TODO: Turn into a check-show-list-conditions and then break
;; the rest down into separate functions
(define (show-list-from-file a-file)
(cond [(and (program-directory-exists?)
(list-file-exists?))
(if (list-file-empty? a-file)
(display-hash-ref messages:messages 'empty-todo-list)
(display (file->numbered-list-as-lines a-file)))]
[else
(display-hash-ref messages:messages 'file-not-found)
(display-hash-ref messages:messages 'try-init)]))
(cond
;; If exists and not empty
[(and (file-exists? config:list-file)
(not (null? (file:file->lines a-file))))
(display (file->vertically-numbered-list a-file))]
(define (write-item-to-file a-file args)
(let ([new-list (append-item-to-list args a-file)])
(file:display-to-file (string:string-join new-list "\n")
a-file
#:mode 'text
#:exists 'truncate))
(display-item-added args))
;; If exists and empty
[(and (file-exists? config:list-file)
(null? (file:file->lines a-file)))
(display-hash-ref messages:messages 'empty-list)]
;; The cdr here prevents user commands, such as "add" being added to the file
;; If not exist
[(and (not (file-exists? config:list-file)))
(begin
(display-hash-ref messages:messages 'file-not-found)
(display-hash-ref messages:messages 'try-init))]
;; Otherwise
[else (display-hash-ref messages:messages 'show-usage)]))
;; TODO: Change this to just use append with lst and (list args)
(define (append-item-to-list args lst)
(reverse (cons args (reverse (file:file->lines lst)))))
;; TODO: Turn into a check-add-conditions and then break
;; the rest down into separate functions
(define (add-item-to-list a-file args)
(if (and (program-directory-exists?)
(list-file-exists?))
(write-item-to-file a-file (string:string-join (cdr (vector->list args))))
;; Else
(if (and (file-exists? config:list-file))
(let* ([item (string:string-join (cdr args) " ")]
[new-list (append-item-to-list item config:list-file)])
(file:display-to-file (string:string-join new-list "\n")
config:list-file
#:mode 'text
#:exists 'truncate)
(display-item-added item))
(begin
(display-hash-ref messages:messages 'file-not-found)
(display-hash-ref messages:messages 'try-init))))
(define (remove-item-from-list-file a-file args)
(let* ([removed-item (get-removed-item a-file args)]
[new-list (remove removed-item (file->string-list a-file))])
(file:display-to-file (string:string-join new-list "\n")
a-file
#:mode 'text
#:exists 'truncate)
(display-item-removed removed-item)))
;; TODO: Turn into a check-remove-conditions and then break
;; the rest down into separate functions
(define (remove-item-from-list args)
(cond
;; If directory and file exist, but file is empty
[(and (directory-exists? config:program-directory)
(file-exists? config:list-file)
(null? config:list-file))
(display-hash-ref messages:messages 'empty-list)]
(define (remove-item-from-list a-file args)
(cond [(list-file-empty? a-file)
(display-hash-ref messages:messages 'empty-todo-list)]
[(and (program-directory-exists?)
(list-file-exists?))
(remove-item-from-list-file a-file (vector-ref args 1))]
[(and (not (program-directory-exists?))
(not (list-file-exists?)))
(begin
(display-hash-ref messages:messages 'file-not-found)
(display-hash-ref messages:messages 'try-init))]))
;; If directory and file exist, and file is not empty
[(and (directory-exists? config:program-directory)
(file-exists? config:list-file)
(not (null? config:list-file)))
(let ([user-args (string->number (list-ref args 1))]
;; Length subtract one because the numbering starts at zero
[list-length (sub1 (length (file:file->lines config:list-file)))])
(if (not (> user-args list-length))
(let* ([item-to-remove (list-ref (file:file->lines config:list-file) user-args)]
[new-list (remove item-to-remove (file:file->lines config:list-file))])
(file:display-to-file (string:string-join new-list "\n")
config:list-file
#:mode 'text
#:exists 'truncate)
(display-item-removed item-to-remove))
;; Else
(display-hash-ref messages:messages 'item-not-found)))]
;; If directory and file don't exist
[(and (not (directory-exists? config:program-directory))
(not (file-exists? config:list-file)))
(begin
(display-hash-ref messages:messages 'file-not-found)
(display-hash-ref messages:messages 'try-init))]))