diff --git a/README.md b/README.md index f23bcb8..e57acbb 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ scripting. **Note**: To uninstall, run `sudo make uninstall-global` * `rodo help` - Displays the help message -* `rodo init` - Generates the directory and file required in your home folder, so rodo can operate +* `rodo initialize` - Generates the directory and file required in your home folder, so rodo can operate * `rodo add "your task here"` - Adds the message inside of quotation marks to your todo list * `rodo ls` - Displays your todo list * `rodo rm 2` - Removes the third item from your list. (The list starts at 0) @@ -205,7 +205,7 @@ the name of a command or how to use a command. ### To show the help message -1. Run `rodo -h` +1. Run `rodo help` ## Initializing rodo @@ -214,7 +214,7 @@ your list to a text file for later access. ### To initialize rodo -1. Run `rodo init` +1. Run `rodo initialize` ## Displaying your list @@ -270,8 +270,8 @@ command names. This section lists and describes rodo's commands. -* `-h` or `--help` displays the help message -* `init` creates a list file (See the `config.rkt` file for the default location of this file) +* `help` displays the help message +* `initialize` creates a list file in `~/.config/rodo/list.txt` by default * `ls` displays your list * `add` adds an item to your list * `rm` removes an item from your list @@ -280,18 +280,14 @@ This section lists and describes rodo's commands. The examples below assume that you have [added rodo to your $PATH](#adding-rodo-to-your-path). -`rodo -h` +`rodo help` -`rodo --help` - -`rodo init` +`rodo initialize` `rodo ls` `rodo add "this is an item"` -`rodo add this is an item without quotation marks` - `rodo rm 1` **Note**: You may have to run `rodo ls` to see which number corresponds to which item in your list. diff --git a/src/args.rkt b/src/args.rkt index 07d6c7c..44c5003 100644 --- a/src/args.rkt +++ b/src/args.rkt @@ -1,48 +1,50 @@ #lang racket/base -(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")) +(require racket/file + "config.rkt" + "utils.rkt") (provide (all-defined-out)) -(define (check-args args) - (let ([args-length (length args)] - [is-member? (lambda (command) (member (list-ref args 0) command))]) +(define (process-args vectorof-args) + (let ([lengthof-vector (vector-length vectorof-args)]) (cond ;; if no args - [(equal? args-length 0) - (utils:display-messages '(show-usage))] + [(= lengthof-vector 0) + (displayln-messages '(show-usage))] - ;; if one arg, and arg is the help command - [(and (equal? args-length 1) - (is-member? config:help-commands)) - (utils:display-messages '(show-help))] + ;; if more than 2 args + ;; Note: The add command requires items to be surrounded + ;; in double quotes, so single quotation marks can + ;; be used by the user in their add command text. + [(> lengthof-vector 2) + (displayln-messages '(too-many-args show-usage))] - ;; if one arg, and arg is the initialize command - [(and (equal? args-length 1) - (is-member? config:initialize-commands)) - (init:check-initialize-conditions)] + ;; if help command + [(and (= lengthof-vector 1) + (equal? (vector-ref vectorof-args 0) help-command)) + (displayln-messages '(show-help))] - ;; if two args, and the add command exists in one of those args - [(and (>= args-length 2) - (is-member? config:add-commands)) - (utils:check-add-conditions args)] + ;; if initialize command + [(and (= lengthof-vector 1) + (equal? (vector-ref vectorof-args 0) initialize-command)) + (initialize)] - ;; if one arg, and arg is the list command - [(and (equal? args-length 1) - (is-member? config:list-commands)) - (utils:check-list-conditions)] + ;; if list command + [(and (= lengthof-vector 1) + (equal? (vector-ref vectorof-args 0) list-command)) + (ls)] - ;; if two args, and the remove command exists in one of those args - [(and (>= args-length 2) - (equal? args-length 2) - (is-member? config:remove-commands) - (real? (string->number (list-ref args 1))) - (or (positive? (string->number (list-ref args 1))) - (zero? (string->number (list-ref args 1))))) - (utils:check-remove-conditions args)] + ;; if add command + [(and (= lengthof-vector 2) + (equal? (vector-ref vectorof-args 0) add-command)) + (add (vector-ref vectorof-args 1))] - [else (utils:display-messages '(show-usage))]))) + ;; if remove command + [(and (= lengthof-vector 2) + (equal? (vector-ref vectorof-args 0) remove-command) + (real? (string->number (vector-ref vectorof-args 1))) + (positive? (string->number (vector-ref vectorof-args 1)))) + (rm (string->number (vector-ref vectorof-args 1)))] + + [else (displayln-messages '(show-usage))]))) diff --git a/src/config.rkt b/src/config.rkt index 9782c62..05ff426 100644 --- a/src/config.rkt +++ b/src/config.rkt @@ -1,34 +1,18 @@ #lang racket/base + (provide (all-defined-out)) -(define file-name "list.txt") (define program-name "rodo") -(define program-directory - (path->string - (expand-user-path - (string-append "~/." program-name "/")))) +(define program-directory (build-path (find-system-path 'home-dir) ".config" "rodo")) +(define program-file (build-path program-directory "list.txt")) -(define list-file - (string-append program-directory file-name)) +(define help-command "help") -(define help-commands '("-h" - "--help" - "h" - "help")) +(define initialize-command "initialize") -(define initialize-commands '("init" - "create" - "start" - "begin")) +(define add-command "add") -(define add-commands '("add" - "a")) +(define list-command "ls") -(define list-commands '("ls" - "list")) - -(define remove-commands '("rm" - "remove" - "del" - "delete")) +(define remove-command "rm") diff --git a/src/init.rkt b/src/init.rkt deleted file mode 100644 index 960c176..0000000 --- a/src/init.rkt +++ /dev/null @@ -1,32 +0,0 @@ -#lang racket/base - -(require (prefix-in config: "config.rkt") - (prefix-in messages: "messages.rkt") - (prefix-in utils: "utils.rkt")) - -(provide (all-defined-out)) - -(define (create-initialization-contents) - (utils:display-messages '(creating)) - (utils:directory-create-700 config:program-directory) - (utils:file-create-600 config:list-file) - (if (and (directory-exists? config:program-directory) - (file-exists? config:list-file)) - (utils:display-messages '(successfully-created)) - ;; Otherwise - (utils:display-messages '(creation-error)))) - -(define (initialize) - (utils:display-messages '(init-y/n)) - (display "> ") - (let ([user-input (read-line)]) - (cond [(member user-input (hash-ref messages:y/n 'yes)) - (create-initialization-contents)] - [(member user-input (hash-ref messages:y/n 'no)) - (utils:display-messages '(terminating))] - [else (utils:display-messages '(choose-y/n))]))) - -(define (check-initialize-conditions) - (if (file-exists? config:list-file) - (utils:display-messages '(file-already-exists)) - (initialize))) diff --git a/src/messages.rkt b/src/messages.rkt index 34406d1..454db54 100644 --- a/src/messages.rkt +++ b/src/messages.rkt @@ -9,12 +9,6 @@ (define newline "\n") (define newline-double "\n\n") -(define help-command (car help-commands)) -(define initialize-command (car initialize-commands)) -(define list-command (car list-commands)) -(define add-command (car add-commands)) -(define remove-command (car remove-commands)) - (define messages (hash 'show-help @@ -23,72 +17,67 @@ tab-half (format "~a" program-name) newline-double "DESCRIPTION" newline - tab-half (format "~a is a todo-list program for the command line. ~a does not use any data formats, and cannot remove multiple items at once." program-name program-name) newline-double + tab-half (format (string-append "~a is a todo-list program for the command line. " + "~a does not use any data formats, and cannot " + "remove multiple items at once.") program-name program-name) + newline-double "USAGE SYNTAX" newline - tab-half (format "~a [command] " program-name) newline-double + tab-half (format "~a [command] " program-name) + newline-double "COMMANDS AVAILABLE" newline tab-half initialize-command newline - tab-full (format "Creates a list file located at ~a" list-file) newline-double + tab-full (format "Creates a list file located at ~a" program-file) + newline-double tab-half list-command newline - tab-full "Displays items from your list" newline-double + tab-full "Displays items from your todo list" + newline-double - tab-half add-command " " newline - tab-full "Adds an item to your list" newline-double + tab-half add-command " <\"A quoted string\">" newline + tab-full "Adds an item to your todo list" + newline-double - tab-half remove-command " " newline - tab-full "Removes an item from your list" newline-double + tab-half remove-command " " newline + tab-full "Removes an item from your todo list" + newline-double "USAGE EXAMPLES" newline tab-half initialize-command newline - tab-full (format "~a ~a" program-name initialize-command) newline-double + tab-full (format "~a ~a" program-name initialize-command) + newline-double tab-half list-command newline - tab-full (format "~a ~a" program-name list-command) newline-double + tab-full (format "~a ~a" program-name list-command) + newline-double tab-half add-command newline - tab-full (format "~a ~a this is an item without double quotation marks" program-name add-command) newline - tab-full (format "~a ~a \"this is an item surrounded by double quotation marks\"" program-name add-command) newline-double + tab-full (format "~a ~a \"Go for a walk\"" program-name add-command) + newline-double tab-half remove-command newline - tab-full (format "~a ~a 2" program-name remove-command) newline-double + tab-full (format "~a ~a 2" program-name remove-command) + newline-double "If you can't see the whole help message, then try running the following command: " newline (format "~a ~a | less" program-name help-command) newline) - 'empty-list "> There is nothing in your list\n" + 'empty-list "> There is nothing in your todo list" + 'show-usage (format "> For usage type the following command:\n~a ~a" program-name help-command) + 'initialize-prompt (format "> A todo list file will be created at ~a\n> Are you sure you want to continue? [y/n]" program-file) + 'cancel (format "> Cancelled initialization of ~a" program-name) + 'creating (format "> Creating ~a ..." program-file) + 'successfully-created (format "> Your todo list file was successfully created at ~a" program-file) + 'try-init (format "> Try typing the following command to initialize ~a:\n~a ~a" program-name program-name initialize-command) + 'try-ls (format "> Try typing the following command to see which item corresponds to which number in your list:\n~a ~a" program-name list-command) + 'item-added "> Added \"~a\" to your todo list" + 'item-removed "> Removed \"~a\" from your todo list" - 'show-usage (format "> For usage type ~a -h\n" program-name) - - 'creating (format "> Creating a list at ~a...\n" list-file) - - 'creation-error (format "> Error: Could not create a list file at ~a\n" list-file) - - 'file-already-exists (format "> Error: A list file already exists at ~a\n" list-file) - - 'successfully-created (format "> Your list file was successfully created at ~a\n" list-file) - - 'file-not-found (format "> Error: Could not find ~a\n" list-file) - - 'item-not-found "> Error: Could not find that item\n" - - 'init-y/n (format "> A list file will be created at ~a\n> Are you sure you want to continue? [y/n]\n" list-file) - - 'try-init (format "> Try typing the following to setup ~a:\n~a ~a\n" program-name program-name initialize-command) - - 'terminating (format "> Exited ~a\n" program-name) - - 'choose-y/n "> Error: Please choose y or n\n" - - 'not-in-list "> Error: Item does not exist\n" - - 'item-added "> Added \"~a\" to your list\n" - - 'item-removed "> Removed \"~a\" from your list\n")) - - -(define y/n - (hash 'yes '("yes" "Yes" "y" "Y") - 'no '("no" "No" "n" "N"))) + 'too-many-args "> Error: Too many arguments" + 'creation-error (format "> Error: Could not create ~a" program-file) + 'file-already-exists (format "> Error: A todo list file already exists at ~a" program-file) + 'file-not-found (format "> Error: Could not find ~a" program-file) + 'item-not-found "> Error: Could not find that item" + 'choose-y/n "> Error: Please choose y or n" + 'not-in-list "> Error: Item does not exist")) diff --git a/src/rodo.rkt b/src/rodo.rkt index 9d5475e..245b962 100644 --- a/src/rodo.rkt +++ b/src/rodo.rkt @@ -1,7 +1,8 @@ #lang racket/base -(require (prefix-in args: "args.rkt")) -(define (main args) - (args:check-args (vector->list args))) +(require "args.rkt") + +(define (main vectorof-args) + (process-args vectorof-args)) (main (current-command-line-arguments)) diff --git a/src/utils.rkt b/src/utils.rkt index 7b845b4..5c1b17d 100644 --- a/src/utils.rkt +++ b/src/utils.rkt @@ -1,17 +1,48 @@ #lang racket/base -(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")) +(require racket/file + racket/list + racket/string + "config.rkt" + "messages.rkt") (provide (all-defined-out)) +(define (append-period string) + (string-append string ". ")) + +(define (combine-lists lst1 lst2) + (map (lambda (lst1 lst2) (string-append lst1 lst2)) + lst1 + lst2)) + +(define (messages-ref key) + (hash-ref messages key)) + +(define (displayln-messages key-list) + (for ([key key-list]) + (displayln (messages-ref key)))) + +(define (displayln-format-message key item) + (displayln (format (messages-ref key) item))) + +(define (check-paths-exist) + (when (not (or (directory-exists? program-directory) + (file-exists? program-file))) + (displayln-messages '(file-not-found try-init)) + (exit))) + +(define (check-file-empty) + (when (null? (file->lines program-file)) + (displayln-messages '(empty-list)) + (exit))) + ;; This may be affected by the user's umask (define (file-create-600 a-file) - (let ([opened-file (open-output-file a-file #:mode 'text #:exists 'truncate)]) - (close-output-port opened-file)) + (close-output-port + (open-output-file a-file + #:mode 'text + #:exists 'truncate)) (file-or-directory-permissions a-file #o600)) ;; This may be affected by the user's umask @@ -19,97 +50,85 @@ (make-directory a-directory) (file-or-directory-permissions a-directory #o700)) -(define (display-messages key-list) - (for ([key key-list]) - (display (hash-ref messages:messages key)))) +;; -------------------------------------------------------- +;; ls +;; -------------------------------------------------------- +;; The (map add1 ...) here starts the numbering at 1 instead of 0 +(define (ls) + (check-paths-exist) + (check-file-empty) + (let* ([listof-file-lines (file->lines program-file)] + [listof-numbers (map add1 (range (length listof-file-lines)))] + [listof-number-strings (map number->string listof-numbers)] + [listof-dotted-number-strings (map append-period listof-number-strings)] + [combined-lists (combine-lists listof-dotted-number-strings listof-file-lines)] + [numbered-list (string-join combined-lists "\n" #:after-last "\n")]) + (display numbered-list))) -(define (list->ascending-numbers-list lst) - (list:range (length lst))) +;; -------------------------------------------------------- +;; add +;; -------------------------------------------------------- +(define (add/append-item-to-list lst subcommand) + (let* ([listof-lines (file->lines lst)] + [listof-lines-reversed (reverse listof-lines)] + [listof-lines-reversed-with-item (cons subcommand listof-lines-reversed)]) + (reverse listof-lines-reversed-with-item))) -(define (list->dotted-ascending-numbers-list lst) - (map (lambda (x) (string-append x ". ")) - (map number->string (list->ascending-numbers-list lst)))) +(define (add subcommand) + (check-paths-exist) + (let ([list-with-item (add/append-item-to-list program-file subcommand)]) + (display-lines-to-file list-with-item + program-file + #:mode 'text + #:exists 'truncate) + (displayln-format-message 'item-added subcommand))) -(define (list->numbered-list lst) - (map (lambda (x y) (string-append x y)) - (list->dotted-ascending-numbers-list lst) - lst)) +;; -------------------------------------------------------- +;; rm +;; -------------------------------------------------------- +;; The (sub1 subcommand-number) is because list-ref starts +;; its index at 0, so we want to lower the user input by 1 +(define (rm subcommand-number) + (check-paths-exist) + (check-file-empty) + (if (<= subcommand-number (length (file->lines program-file))) + (let* ([item-number (sub1 subcommand-number)] + [listof-lines (file->lines program-file)] + [item-to-remove (list-ref listof-lines item-number)] + [list-without-item (remove item-to-remove listof-lines)]) + (display-lines-to-file list-without-item + program-file + #:mode 'text + #:exists 'truncate) + (displayln-format-message 'item-removed item-to-remove)) + (displayln-messages '(item-not-found try-ls)))) -(define (file->vertically-numbered-list a-file) - (string:string-join - (list->numbered-list (file:file->lines a-file)) - "\n" - #:after-last "\n")) +;; -------------------------------------------------------- +;; Initialize +;; -------------------------------------------------------- +(define (initialize/cancel) + (displayln-messages '(cancel)) + (exit)) -(define (surround-with-quotation-marks item) - (string-append "\"" item "\"")) +(define (initialize/start) + (displayln-messages '(creating)) + (directory-create-700 program-directory) + (file-create-600 program-file) + (if (and (directory-exists? program-directory) + (file-exists? program-file)) + (displayln-messages '(successfully-created)) + (displayln-messages '(creation-error)))) -(define (display-item-added item-to-add) - (display (format (hash-ref messages:messages 'item-added) item-to-add))) +(define (initialize/prompt) + (displayln-messages '(initialize-prompt)) + (display "> ") + (let ([user-input (read-line)]) + (case (string->symbol user-input) + ['y (initialize/start)] + ['n (initialize/cancel)] + [else (displayln-messages '(choose-y/n))]))) -(define (display-item-removed item-to-remove) - (display (format (hash-ref messages:messages 'item-removed) item-to-remove))) - -(define (check-list-conditions) - (let ([file-exists? (lambda () (file-exists? config:list-file))] - [file-null? (lambda () (null? (file:file->lines config:list-file)))]) - (cond - [(and (file-exists?) - (file-null?)) - (display-messages '(empty-list))] - - [(and (file-exists?) - (not (file-null?))) - (display (file->vertically-numbered-list config:list-file))] - - [(not (file-exists?)) - (display-messages '(file-not-found try-init))] - - [else (display-messages '(show-usage))]))) - -(define (append-element-to-end-of-list lst item-to-add) - (reverse (cons item-to-add (reverse (file:file->lines lst))))) - -(define (item-add args) - (let* ([item-to-add (string:string-join (cdr args) " ")] - [new-list (append-element-to-end-of-list config:list-file item-to-add)]) - (file:display-lines-to-file new-list - config:list-file - #:mode 'text - #:exists 'truncate) - (display-item-added item-to-add))) - -(define (check-add-conditions args) - (if (and (file-exists? config:list-file)) - (item-add args) - (display-messages '(file-not-found try-init)))) - -(define (item-remove args) - (let* ([item-to-remove (list-ref (file:file->lines config:list-file) args)] - [new-list (remove item-to-remove (file:file->lines config:list-file))]) - (file:display-lines-to-file new-list config:list-file #:mode 'text #:exists 'truncate) - (display-item-removed item-to-remove))) - -(define (check-remove-conditions 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-messages '(empty-list))] - - ;; If directory and file exist, but file is not empty - [(and (directory-exists? config:program-directory) - (file-exists? config:list-file) - (not (null? config:list-file))) - (let ([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 (> args list-length)) - (item-remove args) - (display-messages '(item-not-found))))] - - ;; If directory and file does not exist - [(and (not (directory-exists? config:program-directory)) - (not (file-exists? config:list-file))) - (display-messages '(file-not-found try-init))])) +(define (initialize) + (if (file-exists? program-file) + (displayln-messages '(file-already-exists)) + (initialize/prompt)))