From d472c4c1553c96525655d6385b7b84d299f31ee5 Mon Sep 17 00:00:00 2001 From: Jesse Laprade Date: Fri, 19 Jun 2020 17:54:51 -0400 Subject: [PATCH] simplied, organized, and just better programmed everything haha --- src/args.rkt | 50 --------- src/config.rkt | 18 --- src/messages.rkt | 77 ------------- src/rodo.rkt | 278 ++++++++++++++++++++++++++++++++++++++++++++++- src/utils.rkt | 134 ----------------------- 5 files changed, 277 insertions(+), 280 deletions(-) delete mode 100644 src/args.rkt delete mode 100644 src/config.rkt delete mode 100644 src/messages.rkt delete mode 100644 src/utils.rkt diff --git a/src/args.rkt b/src/args.rkt deleted file mode 100644 index 44c5003..0000000 --- a/src/args.rkt +++ /dev/null @@ -1,50 +0,0 @@ -#lang racket/base - -(require racket/file - "config.rkt" - "utils.rkt") - -(provide (all-defined-out)) - -(define (process-args vectorof-args) - (let ([lengthof-vector (vector-length vectorof-args)]) - (cond - ;; if no args - [(= lengthof-vector 0) - (displayln-messages '(show-usage))] - - ;; 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 help command - [(and (= lengthof-vector 1) - (equal? (vector-ref vectorof-args 0) help-command)) - (displayln-messages '(show-help))] - - ;; if initialize command - [(and (= lengthof-vector 1) - (equal? (vector-ref vectorof-args 0) initialize-command)) - (initialize)] - - ;; if list command - [(and (= lengthof-vector 1) - (equal? (vector-ref vectorof-args 0) list-command)) - (ls)] - - ;; if add command - [(and (= lengthof-vector 2) - (equal? (vector-ref vectorof-args 0) add-command)) - (add (vector-ref vectorof-args 1))] - - ;; 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 deleted file mode 100644 index 05ff426..0000000 --- a/src/config.rkt +++ /dev/null @@ -1,18 +0,0 @@ -#lang racket/base - -(provide (all-defined-out)) - -(define program-name "rodo") - -(define program-directory (build-path (find-system-path 'home-dir) ".config" "rodo")) -(define program-file (build-path program-directory "list.txt")) - -(define help-command "help") - -(define initialize-command "initialize") - -(define add-command "add") - -(define list-command "ls") - -(define remove-command "rm") diff --git a/src/messages.rkt b/src/messages.rkt deleted file mode 100644 index ea5443c..0000000 --- a/src/messages.rkt +++ /dev/null @@ -1,77 +0,0 @@ -#lang racket/base - -(require "config.rkt") - -(provide (all-defined-out)) - -(define hyphen " - ") -(define indent " ") -(define newline "\n") -(define newline-double "\n\n") - -(define messages - (hash - 'show-help - (string-append - "NAME" newline - indent (format "~a" program-name) - newline-double - - "DESCRIPTION" newline - indent (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" newline - indent (format "~a [command] []" program-name) - newline-double - - "COMMANDS" newline - indent initialize-command hyphen (format "Creates a list file located at ~a" program-file) - newline-double - - indent list-command hyphen "Displays items from your todo list" - newline-double - - indent add-command " \"A quoted string\"" hyphen "Adds an item to your todo list" - newline-double - - indent remove-command " " hyphen "Removes an item from your todo list" - newline-double - - "USAGE EXAMPLES" newline - indent (format "~a ~a" program-name initialize-command) - newline - - indent (format "~a ~a" program-name list-command) - newline - - indent (format "~a ~a \"Go for a walk\"" program-name add-command) - newline - - indent (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)) - - '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" - - '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 245b962..f35fc9f 100644 --- a/src/rodo.rkt +++ b/src/rodo.rkt @@ -1,6 +1,282 @@ #lang racket/base -(require "args.rkt") +(require racket/file + racket/list + racket/match + racket/string) + +;; ------------------------------------------------ +;; values +;; ------------------------------------------------ +(define help-command "help") +(define ls-command "ls") +(define rm-command "rm") +(define add-command "add") +(define program-name "rodo") +(define program-name-dotted (string-append "." program-name)) +(define home-directory-path (find-system-path 'home-dir)) +(define program-path (build-path home-directory-path program-name-dotted)) +(define correct-permissions 384) +(define message-prefix "> ") + +;; ------------------------------------------------ +;; messages +;; ------------------------------------------------ +(define messages + (hash + 'error-add (list (format "Error: No arguments were found after the '~a' command." add-command) + (format "Suggestion: Try using a quoted argument after the '~a' command." ls-command) + (format "Example: ~a add \"Go for a walk\"" program-name)) + + 'item-not-found (list "Error: Item not found." + (format "Suggestion: Try using the '~a' command to see which number " ls-command) + " correlates to which message in your list." + (format "Example: ~a ~a" program-name ls-command)) + + 'error-rm (list (format "Error: No arguments were found after the '~a' command." rm-command) + (format "Suggestion: Try using a number after the '~a' command." rm-command) + (format "Example: ~a ~a 2" program-name rm-command) + "" + (format "Note: You may need to run the '~a' command to see which " ls-command) + " number correlates to which item in your list." + (format "Example: ~a ~a" program-name ls-command)) + + 'error-usage (list "Error: Incorrect usage." + (format "Suggestions: Try running the '~a' command." help-command) + (format "Example: ~a ~a" program-name help-command)) + + 'error-ls (list (format "Error: Found arguments after the '~a' command." ls-command) + (format "Suggestions: Try using '~a' without any arguments." ls-command) + (format "Example: ~a ~a" program-name ls-command)) + + 'error-fake-file (list (format "Error: A '~a' directory was found in your home directory." program-name-dotted) + (format "Suggestion 1: Move the '~a' directory out of your " program-name-dotted) + (format " home directory before using ~a." program-name) + (format "Suggestion 2: Rename the '~a' directory before using ~a." program-name-dotted program-name)) + + 'error-permissions-prompt (list (format "Error: '~a''s permissions are incorrect." program-path) + "Would you like to fix them? [y/n]") + + 'cancel-creation (list (format "Cancelled the creation of ~a." program-path)) + + 'permissions-fixed (list (format "'~a''s permissions were successfully fixed." program-path)) + + 'file-created (list (format "'~a' was successfully created." program-path)) + + 'not-found-prompt (list (format "'~a' wasn't found." program-path) + "Would you like to create it? [y/n]") + + 'error-option (list "Error: '~a' is not an option.") + + 'empty-list (list "There is nothing in your list.") + + 'added (list "'~a' was added to your list.") + + 'removed (list "'~a' was removed from your list."))) + +;; ------------------------------------------------ +;; helpers +;; ------------------------------------------------ +(define (messages-ref key) + (map (lambda (element) (string-append message-prefix element)) + (hash-ref messages key))) + +(define-syntax-rule (displayln-message-format key string ...) + (let ([string-to-format (string-join (messages-ref key) "\n")]) + (displayln (format string-to-format string ...)))) + +(define (displayln-message-list key) + (let ([listof-strings (messages-ref key)]) + (for ([string listof-strings]) + (displayln string)))) + +(define (displayln-for . strings) + (for ([string strings]) + (displayln string))) + +;; -rw------- permissions +(define (file-has-correct-permissions? file) + (equal? correct-permissions (file-or-directory-permissions file 'bits))) + +;; ------------------------------------------------ +;; repair +;; ------------------------------------------------ +(define (repair/not-an-option user-input) + (displayln-message-format 'error-option user-input) + (exit)) + +(define (repair/cancel) + (displayln-message-list 'cancel-creation) + (exit)) + +(define (repair/fake-file) + (displayln-message-list 'error-fake-file) + (exit)) + +(define (repair/create-exit-mode key) + (match key + [(or 'ls 'rm) (exit)] + ['add 'do-nothing])) + +(define (repair/fix-permissions key) + (file-or-directory-permissions program-path correct-permissions) + (displayln-message-list 'permissions-fixed) + (repair/create-exit-mode key)) + +(define (repair/wrong-permissions key) + (displayln-message-list 'error-permissions-prompt) + (display message-prefix) + (let ([user-input (read-line)]) + (case (string->symbol user-input) + ['y (repair/fix-permissions key)] + ['n (repair/cancel)] + [else (repair/not-an-option user-input)]))) + +(define (repair/create-file key) + (close-output-port (open-output-file program-path)) + (file-or-directory-permissions program-path correct-permissions) + (displayln-message-list 'file-created) + (repair/create-exit-mode key)) + +(define (repair/not-found key) + (displayln-message-list 'not-found-prompt) + (display message-prefix) + (let ([user-input (read-line)]) + (case (string->symbol user-input) + ['y (repair/create-file key)] + ['n (repair/cancel)] + [else (repair/not-an-option user-input)]))) + +(define (repair key) + (cond + [(directory-exists? program-path) + (repair/fake-file)] + + [(not (file-exists? program-path)) + (repair/not-found key)] + + [(not (file-has-correct-permissions? program-path)) + (repair/wrong-permissions key)] + + [else 'do-nothing])) + +;; ------------------------------------------------ +;; ls +;; ------------------------------------------------ +(define (ls/display-list listof-items) + ;; add1 starts the listof-numbers at 1 instead of 0 + (let* ([listof-numbers (map add1 (range (length listof-items)))] + [listof-number-strings (map number->string listof-numbers)] + [combine-lists (lambda (a b) (string-append a ". " b))] + [listof-numbered-items (map combine-lists + listof-number-strings + listof-items)]) + (for ([item listof-numbered-items]) + (displayln item)))) + +(define (ls) + (repair 'ls) + (let ([listof-items (file->lines program-path)]) + (if (null? listof-items) + (displayln-message-list 'empty-list) + (ls/display-list listof-items)))) + +;; ------------------------------------------------ +;; rm +;; ------------------------------------------------ +(define (rm/remove-item listof-items item-number) + (let* ([item-to-remove (list-ref listof-items item-number)] + [list-without-item (remove item-to-remove listof-items)]) + (display-lines-to-file list-without-item + program-path + #:exists 'truncate) + (displayln-message-format 'removed item-to-remove))) + +(define (rm string) + (repair 'rm) + (let* ([listof-items (file->lines program-path)] + ;; subtract 1 because the index starts at + ;; 0 under the hood, but the numbers presented from 'ls' + ;; start at 1. + [item-number (string->number string)] + [item-number-sub1 (sub1 item-number)] + [list-length (length listof-items)]) + (if (and (not (null? listof-items)) + (number? item-number) + (positive? item-number) + ;; 1 less than length, because we want to + ;; remove the index number, which is one less + ;; than the item the user chose + ;; Example: + ;; We have a list length of 3: + ;; '(1 2 3) + ;; `list-ref` in rm/remove-item above + ;; uses an index that starts at 0, so + ;; the index of the numbers above are: + ;; '(0 1 2) + ;; The 2 is the last index number in a + ;; list of length 3, which is what we + ;; want, because if you try to remove + ;; an index larger than 2, such as the + ;; list length 3, then that would be + ;; an error + (< item-number-sub1 list-length)) + (rm/remove-item listof-items item-number-sub1) + (displayln-message-list 'item-not-found)))) + +;; ------------------------------------------------ +;; add +;; ------------------------------------------------ +;; The string-cleaned and -remade is incase there +;; are multiple newline characters. This ensures +;; there is only one newline character. +(define (add string) + (repair 'add) + (let* ([string-no-newline (string-replace string "\n" "")] + [string-newline (string-append string-no-newline "\n")]) + (display-to-file string-newline + program-path + #:exists 'append) + (displayln-message-format 'added string-no-newline))) + +;; ------------------------------------------------ +;; help +;; ------------------------------------------------ +(define (help) + (displayln-for + "Usage:" + (format " ~a [] []" program-name) + "" + "Commands:" + (format " ~a - Adds an item to your list." add-command) + (format " ~a - Prints a numbered list of your items." ls-command) + (format " ~a - Removes an item from your list." rm-command) + "" + "Examples:" + (format " ~a ~a \"Go for a walk\"" program-name add-command) + (format " ~a ~a" program-name ls-command) + (format " ~a ~a 2" program-name rm-command))) + +(define (process-args vectorof-args) + (define (args-ref number) + (vector-ref vectorof-args number)) + (match vectorof-args + ;; Proper usage + [(or '#("-h") + '#("--help") + '#(help-command)) (help)] + [(vector add-command _) (add (args-ref 1))] + [(vector rm-command _) (rm (args-ref 1))] + [(vector ls-command) (ls)] + ;; Improper usage + ;; This is checked so we can give the user hints on how to + ;; use the software if they have part of the command + ;; correct + [(vector ls-command _) (displayln-message-list 'error-ls)] + [(vector add-command) (displayln-message-list 'error-add)] + [(vector rm-command) (displayln-message-list 'error-rm)] + [(or (vector) + (vector _ ...)) (displayln-message-list 'error-usage)])) (define (main vectorof-args) (process-args vectorof-args)) diff --git a/src/utils.rkt b/src/utils.rkt deleted file mode 100644 index 5c1b17d..0000000 --- a/src/utils.rkt +++ /dev/null @@ -1,134 +0,0 @@ -#lang racket/base - -(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) - (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 -(define (directory-create-700 a-directory) - (make-directory a-directory) - (file-or-directory-permissions a-directory #o700)) - -;; -------------------------------------------------------- -;; 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))) - -;; -------------------------------------------------------- -;; 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 (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))) - -;; -------------------------------------------------------- -;; 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)))) - -;; -------------------------------------------------------- -;; Initialize -;; -------------------------------------------------------- -(define (initialize/cancel) - (displayln-messages '(cancel)) - (exit)) - -(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 (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 (initialize) - (if (file-exists? program-file) - (displayln-messages '(file-already-exists)) - (initialize/prompt)))