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 # 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 # Screenshot
@ -54,17 +54,17 @@ If you are using a single-file executable, create a wrapper as follows:
# Introduction # Introduction
This readme will guide you through downloading, installing, and using the rodo 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 list manager. It is intended for people who spend a lot of their time on the
command line and want a minimal todo list application. command line and want a minimal list manager.
# Conventions used in this readme # Conventions used in this readme
* Notes - Notes signify additional information * **Note** - Notes signify additional information
* Tips - Tips signify an alternate procedure for completing a step * **Tip**- Tips signify an alternate procedure for completing a step
* Cautions - Cautions signify that damage may occur * **Caution** - Cautions signify that damage may occur
* Examples - Examples provide a visual reference of how a procedure would be carried out in the real world * **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 * `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 # 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 **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. file can be changed by editing the `config.rkt` file.

View File

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

View File

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

View File

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

View File

@ -4,115 +4,125 @@
(provide (all-defined-out)) (provide (all-defined-out))
(define newline "\n")
(define messages (define messages
(hash (hash
'show-help (string-append 'show-help
"Usage\n" (string-append "\n"
"=====\n" "Conventions used in this help message\n"
(format "If you want to use the ~a or ~a commands, follow the structure below:\n" "====================================="
config:initialize-command "\n\n"
config:list-command) "[command] - [command]s should be replaced by a command from the Command section below without the surrounding \"[\" and \"]\"s."
(format "~a <command>\n\n" "\n\n"
config:program-name) "<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" ;; initialize-command
config:add-command (format "~a" (car config:initialize-command))
config:remove-command) "\n\n"
(format "~a <command> [argument]\n\n" config:program-name) "\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" ;; add-command
"========\n" (format "~a" (car config:add-command))
(format "This section describes the commands available for ~a.\n\n" config:program-name) "\n\n"
(format "Note: The commands mentioned in this section should replace the \"<command>\" filler text found in the Usage section above.\n\n") "\t"
"Adds an item to your list."
"\n\n"
;; init ;; remove-command
(format "~a\n" config:initialize-command) (format "~a" (car config:remove-command))
"====\n" "\n\n"
(format "Creates ~a in ~a. This is where your todo list will be stored.\n\n" "\t"
config:list-file "Removes an item from your list."
config:program-directory) "\n\n"
"Example:\n" "Usage examples\n"
(format "~a ~a\n\n" config:program-name config:initialize-command) "=============="
"\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 ;; list-command
(format "~a\n" config:list-command) (format "~a" (car config:list-command))
"====\n" "\n\n"
"Displays items in your todo list.\n\n" "\t"
(format "~a ~a" config:program-name (car config:list-command))
"\n\n"
"Example:\n" ;; add-command
(format "~a ~a\n\n" config:program-name config:list-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) ;; remove-command
"=================\n" (format "~a" (car config:remove-command))
"Adds an item to the list.\n\n" "\n\n"
"\t"
"Examples:\n" (format "~a ~a 2" config:program-name (car config:remove-command))
(format "~a ~a this is an unquoted item\n" config:program-name config:add-command) "\n\n"
(format "~a ~a \"this is a quoted item\"\n\n" config:program-name config:add-command) "\t"
"Note: The example above will remove the third item from your list, because the list starts at zero."
(format "~a [argument]\n" config:remove-command) "\n\n"
"=============\n"
"Removes an item from the list.\n\n"
"Example:\n"
(format "~a ~a 1\n\n" config:program-name config:remove-command)
(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"
"Can't see the whole help message?\n" "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." "\n\n"
config:program-name) "\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)
"When you want to quit, type `q` (without the grave accents).\n") "\n\n"
"\t"
"When you want to quit, type \"q\" (without the double quotation marks)."
"\n\n")
'empty-todo-list "> There is nothing in your list.\n" 'empty-list "> There is nothing in your list.\n"
'show-usage (format "> For usage type `~a -h` or `~a --help`.\n" 'show-usage (format "> For usage type \"~a -h\" (without the double quotation marks).\n" config:program-name)
config:program-name
config:program-name)
'creating (format "> Creating ~a in ~a.\n" 'creating (format "> Creating a list in ~a...\n" config:list-file)
config:list-file
config:program-directory)
'creation-error (string-append 'creation-error (format "> Error: Could not create a list file in ~a.\n" config:list-file)
(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")
'file-already-exists (format "> Error: ~a already exists in ~a~a.\n" 'file-already-exists (format "> Error: A list file already exists in ~a.\n" config:list-file)
config:program-name
config:program-directory 'successfully-created (format "> Your list file was successfully created in ~a.\n" config:list-file)
'file-not-found (format "> Error: Could not find ~a.\n" config:list-file)
'item-not-found "> Error: Could not find that item.\n"
'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) config:list-file)
'successfully-created (format "> ~a has been successfully created in ~a.\n" 'try-init (format "> Try typing \"~a ~a\" to set it up (without the double quotation marks).\n"
config:program-directory config:program-name (car config:initialize-command))
config:list-file)
'file-not-found (format "> Error: Could not find ~a~a\n" 'terminating (format "> Exited ~a.\n" config:program-name)
config:program-directory
config:list-file)
'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")
'try-init (format "> Try typing `~a ~a` to set it up (without the grave accents).\n"
config:program-name
config:initialize-command)
'terminating (format "> Exiting ~a\n" config:program-name)
'choose-y/n "> Error: Please choose y or n\n" 'choose-y/n "> Error: Please choose y or n\n"

View File

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

143
utils.rkt
View File

@ -8,113 +8,122 @@
(provide (all-defined-out)) (provide (all-defined-out))
(define (list-file-exists?) (define (create-file-600 a-file)
(file-exists? config:path-to-list-file)) (let ([opened-file (open-output-file a-file #:mode 'text #:exists 'truncate)])
(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)])
(close-output-port opened-file)) (close-output-port opened-file))
(file-or-directory-permissions a-file #o600)) (file-or-directory-permissions a-file #o600))
(define (create-program-directory-700 a-directory) (define (create-directory-700 a-directory)
(make-directory a-directory) (make-directory a-directory)
(file-or-directory-permissions a-directory #o700)) (file-or-directory-permissions a-directory #o700))
(define (display-hash-ref hash-list key) (define (display-hash-ref hash-list key)
(display (hash-ref hash-list key))) (display (hash-ref hash-list key)))
(define (file->string-list a-file) (define (list->ascending-numbers lst)
(let ([todo-list (file:file->lines a-file (list:range (length lst)))
#:mode 'text
#:line-mode 'any)])
todo-list))
(define (list-file-empty? a-file) (define (list->dotted-ascending-numbers lst)
(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)
(map (lambda (x) (string-append x ". ")) (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) (define (list->numbered-list lst)
(map (lambda (x y) (string-append x y)) (map (lambda (x y) (string-append x y))
(list->list-of-dotted-numbers lst) (list->dotted-ascending-numbers lst)
lst)) lst))
(define (file->numbered-list-as-lines a-file) (define (file->vertically-numbered-list a-file)
(string:string-join (list->numbered-list (file->string-list a-file)) (string:string-join
(list->numbered-list (file:file->lines a-file))
"\n" "\n"
#:after-last "\n")) #:after-last "\n"))
(define (append-item-to-list args lst) (define (surround-with-quotation-marks item)
(reverse (cons args (reverse (file->string-list lst))))) (string-append "\"" item "\""))
(define (display-item-added args) (define (display-item-added item)
(display-hash-ref messages:messages 'item-added-prefix) (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)) (display-hash-ref messages:messages 'item-added-suffix))
(define (display-item-removed args) (define (display-item-removed args)
(display-hash-ref messages:messages 'item-removed-prefix) (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)) (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) (define (show-list-from-file a-file)
(cond [(and (program-directory-exists?) (cond
(list-file-exists?)) ;; If exists and not empty
(if (list-file-empty? a-file) [(and (file-exists? config:list-file)
(display-hash-ref messages:messages 'empty-todo-list) (not (null? (file:file->lines a-file))))
(display (file->numbered-list-as-lines a-file)))] (display (file->vertically-numbered-list a-file))]
[else
;; If exists and empty
[(and (file-exists? config:list-file)
(null? (file:file->lines a-file)))
(display-hash-ref messages:messages 'empty-list)]
;; If not exist
[(and (not (file-exists? config:list-file)))
(begin
(display-hash-ref messages:messages 'file-not-found) (display-hash-ref messages:messages 'file-not-found)
(display-hash-ref messages:messages 'try-init)])) (display-hash-ref messages:messages 'try-init))]
(define (write-item-to-file a-file args) ;; Otherwise
(let ([new-list (append-item-to-list args a-file)]) [else (display-hash-ref messages:messages 'show-usage)]))
(file:display-to-file (string:string-join new-list "\n")
a-file
#:mode 'text
#:exists 'truncate))
(display-item-added args))
;; The cdr here prevents user commands, such as "add" being added to the file ;; 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) (define (add-item-to-list a-file args)
(if (and (program-directory-exists?) (if (and (file-exists? config:list-file))
(list-file-exists?)) (let* ([item (string:string-join (cdr args) " ")]
(write-item-to-file a-file (string:string-join (cdr (vector->list args)))) [new-list (append-item-to-list item config:list-file)])
;; Else (file:display-to-file (string:string-join new-list "\n")
config:list-file
#:mode 'text
#:exists 'truncate)
(display-item-added item))
(begin (begin
(display-hash-ref messages:messages 'file-not-found) (display-hash-ref messages:messages 'file-not-found)
(display-hash-ref messages:messages 'try-init)))) (display-hash-ref messages:messages 'try-init))))
(define (remove-item-from-list-file a-file args) ;; TODO: Turn into a check-remove-conditions and then break
(let* ([removed-item (get-removed-item a-file args)] ;; the rest down into separate functions
[new-list (remove removed-item (file->string-list a-file))]) (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)]
;; 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") (file:display-to-file (string:string-join new-list "\n")
a-file config:list-file
#:mode 'text #:mode 'text
#:exists 'truncate) #:exists 'truncate)
(display-item-removed removed-item))) (display-item-removed item-to-remove))
;; Else
(display-hash-ref messages:messages 'item-not-found)))]
(define (remove-item-from-list a-file args) ;; If directory and file don't exist
(cond [(list-file-empty? a-file) [(and (not (directory-exists? config:program-directory))
(display-hash-ref messages:messages 'empty-todo-list)] (not (file-exists? config:list-file)))
[(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 (begin
(display-hash-ref messages:messages 'file-not-found) (display-hash-ref messages:messages 'file-not-found)
(display-hash-ref messages:messages 'try-init))])) (display-hash-ref messages:messages 'try-init))]))