Adding files
This commit is contained in:
+831
-123
@@ -1,156 +1,864 @@
|
|||||||
;;; 06-linuxplus-quiz-mode.el --- linuxplus-quiz-mode
|
;;; 06-linuxplus-quiz-mode.el --- linuxplus-quiz-mode
|
||||||
|
|
||||||
(defvar-local linuxplus-quiz-questions nil
|
(defvar-local linuxplus-quiz-lesson-name nil)
|
||||||
"Questions for the current Linux+ quiz buffer.")
|
(defvar-local linuxplus-quiz-questions nil)
|
||||||
|
(defvar-local linuxplus-quiz-user-answers nil)
|
||||||
|
(defvar-local linuxplus-quiz-current-question 0)
|
||||||
|
(defvar-local linuxplus-quiz-current-choice 0)
|
||||||
|
(defvar-local linuxplus-quiz-return-window-configuration nil)
|
||||||
|
(defvar-local linuxplus-quiz-return-buffer nil)
|
||||||
|
|
||||||
(defvar-local linuxplus-quiz-answers nil
|
(defun linuxplus-quiz--question-type (question)
|
||||||
"Correct answers for the current Linux+ quiz buffer.")
|
(plist-get question :type))
|
||||||
|
|
||||||
(defvar-local linuxplus-quiz-user-answers nil
|
(defun linuxplus-quiz--question-prompt (question)
|
||||||
"User answers for the current Linux+ quiz buffer.")
|
(plist-get question :prompt))
|
||||||
|
|
||||||
(defvar-local linuxplus-quiz-current-index 0
|
(defun linuxplus-quiz--question-answer (question)
|
||||||
"Current question index in the Linux+ quiz buffer.")
|
(plist-get question :answer))
|
||||||
|
|
||||||
(defvar-local linuxplus-quiz-lesson-name nil
|
(defun linuxplus-quiz--question-answers (question)
|
||||||
"Lesson name associated with the current Linux+ quiz buffer.")
|
(plist-get question :answers))
|
||||||
|
|
||||||
(defun linuxplus-quiz-answer-marker (answer)
|
(defun linuxplus-quiz--question-choices (question)
|
||||||
"Return a display marker string for ANSWER."
|
(plist-get question :choices))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--current-question ()
|
||||||
|
(nth linuxplus-quiz-current-question
|
||||||
|
linuxplus-quiz-questions))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--current-user-answer ()
|
||||||
|
(nth linuxplus-quiz-current-question
|
||||||
|
linuxplus-quiz-user-answers))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--set-current-user-answer (answer)
|
||||||
|
(setf
|
||||||
|
(nth linuxplus-quiz-current-question
|
||||||
|
linuxplus-quiz-user-answers)
|
||||||
|
answer))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--answer-marker (answer)
|
||||||
(cond
|
(cond
|
||||||
((eq answer 'y) "[y]")
|
((eq answer 'true) "[t]")
|
||||||
((eq answer 'n) "[n]")
|
((eq answer 'false) "[f]")
|
||||||
(t "[ ]")))
|
((eq answer 'yes) "[y]")
|
||||||
|
((eq answer 'no) "[n]")
|
||||||
|
((eq answer 'selected) "[*]")
|
||||||
|
(t "[]")))
|
||||||
|
|
||||||
(defun linuxplus-quiz-answer-string (answer)
|
(defun linuxplus-quiz--answer-string (answer)
|
||||||
"Return a readable string for ANSWER."
|
|
||||||
(cond
|
(cond
|
||||||
((eq answer 'y) "y")
|
((eq answer 'true) "true")
|
||||||
((eq answer 'n) "n")
|
((eq answer 'false) "false")
|
||||||
(t "unanswered")))
|
((eq answer 'yes) "yes")
|
||||||
|
((eq answer 'no) "no")
|
||||||
|
((listp answer) (format "%S" answer))
|
||||||
|
((null answer) "unanswered")
|
||||||
|
(t (format "%S" answer))))
|
||||||
|
|
||||||
(defun linuxplus-quiz-result-string (user-answer correct-answer)
|
(defun linuxplus-quiz--correct-answer-for-display
|
||||||
"Return a readable result string comparing USER-ANSWER to CORRECT-ANSWER."
|
(question)
|
||||||
(if (eq user-answer correct-answer)
|
(let ((type
|
||||||
"correct"
|
(linuxplus-quiz--question-type question)))
|
||||||
"incorrect"))
|
(cond
|
||||||
|
((memq type '(true-false yes-no))
|
||||||
|
(linuxplus-quiz--answer-string
|
||||||
|
(linuxplus-quiz--question-answer question)))
|
||||||
|
((eq type 'multiple)
|
||||||
|
(format "%S"
|
||||||
|
(linuxplus-quiz--question-answers
|
||||||
|
question)))
|
||||||
|
(t "unknown"))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--advance-question ()
|
||||||
|
(when (< linuxplus-quiz-current-question
|
||||||
|
(1- (length linuxplus-quiz-questions)))
|
||||||
|
(setq linuxplus-quiz-current-question
|
||||||
|
(1+ linuxplus-quiz-current-question))
|
||||||
|
(setq linuxplus-quiz-current-choice 0)))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--previous-question ()
|
||||||
|
(when (> linuxplus-quiz-current-question 0)
|
||||||
|
(setq linuxplus-quiz-current-question
|
||||||
|
(1- linuxplus-quiz-current-question))
|
||||||
|
(setq linuxplus-quiz-current-choice 0)))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--multiple-selected-p
|
||||||
|
(answer choice-index)
|
||||||
|
(and (listp answer)
|
||||||
|
(memq choice-index answer)))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--advance-choice ()
|
||||||
|
"Advance to next multiple-choice bracket."
|
||||||
|
(let* ((question
|
||||||
|
(linuxplus-quiz--current-question))
|
||||||
|
(choices
|
||||||
|
(linuxplus-quiz--question-choices
|
||||||
|
question)))
|
||||||
|
(when (and choices
|
||||||
|
(< linuxplus-quiz-current-choice
|
||||||
|
(1- (length choices))))
|
||||||
|
(setq linuxplus-quiz-current-choice
|
||||||
|
(1+ linuxplus-quiz-current-choice)))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--toggle-current-choice ()
|
||||||
|
"Toggle current multiple-choice selection."
|
||||||
|
(let* ((answer
|
||||||
|
(copy-sequence
|
||||||
|
(or
|
||||||
|
(linuxplus-quiz--current-user-answer)
|
||||||
|
nil)))
|
||||||
|
(choice linuxplus-quiz-current-choice)
|
||||||
|
(was-selected
|
||||||
|
(memq choice answer)))
|
||||||
|
|
||||||
|
(if was-selected
|
||||||
|
|
||||||
|
;; Remove selection and stay.
|
||||||
|
(setq answer
|
||||||
|
(delq choice answer))
|
||||||
|
|
||||||
|
;; Add selection and advance.
|
||||||
|
(push choice answer)
|
||||||
|
|
||||||
|
(setq answer
|
||||||
|
(sort answer #'<))
|
||||||
|
|
||||||
|
(linuxplus-quiz--set-current-user-answer
|
||||||
|
answer)
|
||||||
|
|
||||||
|
(linuxplus-quiz--advance-choice)
|
||||||
|
|
||||||
|
;; Stop removal logic.
|
||||||
|
(setq answer nil))
|
||||||
|
|
||||||
|
;; Removal path only.
|
||||||
|
(when answer
|
||||||
|
(setq answer
|
||||||
|
(sort answer #'<))
|
||||||
|
|
||||||
|
(linuxplus-quiz--set-current-user-answer
|
||||||
|
answer))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--answer-correct-p
|
||||||
|
(question user-answer)
|
||||||
|
|
||||||
|
(let ((type
|
||||||
|
(linuxplus-quiz--question-type question)))
|
||||||
|
|
||||||
|
(cond
|
||||||
|
|
||||||
|
((memq type '(true-false yes-no))
|
||||||
|
(eq user-answer
|
||||||
|
(linuxplus-quiz--question-answer
|
||||||
|
question)))
|
||||||
|
|
||||||
|
((eq type 'multiple)
|
||||||
|
(equal
|
||||||
|
(sort
|
||||||
|
(copy-sequence
|
||||||
|
(or user-answer nil))
|
||||||
|
#'<)
|
||||||
|
|
||||||
|
(sort
|
||||||
|
(copy-sequence
|
||||||
|
(linuxplus-quiz--question-answers
|
||||||
|
question))
|
||||||
|
#'<)))
|
||||||
|
|
||||||
|
(t nil))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--question-index-at-point ()
|
||||||
|
"Return zero-based question index at point."
|
||||||
|
(save-excursion
|
||||||
|
(let ((found nil))
|
||||||
|
(when
|
||||||
|
(re-search-backward
|
||||||
|
"^\\([0-9]+\\)\\.$"
|
||||||
|
nil t)
|
||||||
|
|
||||||
|
(setq found
|
||||||
|
(1-
|
||||||
|
(string-to-number
|
||||||
|
(match-string 1)))))
|
||||||
|
|
||||||
|
(when
|
||||||
|
(and found
|
||||||
|
(>= found 0)
|
||||||
|
(< found
|
||||||
|
(length
|
||||||
|
linuxplus-quiz-questions)))
|
||||||
|
found))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--choice-index-at-point ()
|
||||||
|
"Return zero-based multiple-choice bracket index at point."
|
||||||
|
|
||||||
|
(save-excursion
|
||||||
|
|
||||||
|
(let ((current-line
|
||||||
|
(line-beginning-position))
|
||||||
|
|
||||||
|
(question-start nil)
|
||||||
|
|
||||||
|
(choice-index 0)
|
||||||
|
|
||||||
|
result)
|
||||||
|
|
||||||
|
;; Find question start.
|
||||||
|
(when
|
||||||
|
(re-search-backward
|
||||||
|
"^\\([0-9]+\\)\\.$"
|
||||||
|
nil t)
|
||||||
|
|
||||||
|
(setq question-start (point)))
|
||||||
|
|
||||||
|
(when question-start
|
||||||
|
|
||||||
|
;; Start searching after question header.
|
||||||
|
(forward-line 1)
|
||||||
|
|
||||||
|
;; Walk brackets.
|
||||||
|
(while
|
||||||
|
(and
|
||||||
|
(null result)
|
||||||
|
(re-search-forward
|
||||||
|
"\\[[^]]*\\]"
|
||||||
|
nil t))
|
||||||
|
|
||||||
|
;; Did we hit current line?
|
||||||
|
(when
|
||||||
|
(= (line-beginning-position)
|
||||||
|
current-line)
|
||||||
|
|
||||||
|
(setq result choice-index))
|
||||||
|
|
||||||
|
;; Next bracket index.
|
||||||
|
(setq choice-index
|
||||||
|
(1+ choice-index))))
|
||||||
|
|
||||||
|
result)))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--sync-current-position-from-point ()
|
||||||
|
"Update current question and multiple-choice bracket from cursor position."
|
||||||
|
|
||||||
|
(let ((question-index
|
||||||
|
(linuxplus-quiz--question-index-at-point)))
|
||||||
|
|
||||||
|
(when question-index
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-current-question
|
||||||
|
question-index)
|
||||||
|
|
||||||
|
(if
|
||||||
|
(eq
|
||||||
|
(linuxplus-quiz--question-type
|
||||||
|
(linuxplus-quiz--current-question))
|
||||||
|
'multiple)
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-current-choice
|
||||||
|
(or
|
||||||
|
(linuxplus-quiz--choice-index-at-point)
|
||||||
|
0))
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-current-choice 0)))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--invalid-input-message ()
|
||||||
|
|
||||||
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
|
|
||||||
|
(let ((type
|
||||||
|
(linuxplus-quiz--question-type
|
||||||
|
(linuxplus-quiz--current-question))))
|
||||||
|
|
||||||
|
(cond
|
||||||
|
|
||||||
|
((eq type 'true-false)
|
||||||
|
(message
|
||||||
|
"Valid input: t for true, f for false."))
|
||||||
|
|
||||||
|
((eq type 'yes-no)
|
||||||
|
(message
|
||||||
|
"Valid input: y for yes, n for no."))
|
||||||
|
|
||||||
|
((eq type 'multiple)
|
||||||
|
(message
|
||||||
|
"Use the space bar to add/remove the * character."))
|
||||||
|
|
||||||
|
(t
|
||||||
|
(message "Invalid input.")))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz--set-answer-and-maybe-advance
|
||||||
|
(answer)
|
||||||
|
|
||||||
|
"Set ANSWER.
|
||||||
|
If unanswered, advance.
|
||||||
|
If replacing, stay."
|
||||||
|
|
||||||
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
|
|
||||||
|
(let ((old-answer
|
||||||
|
(linuxplus-quiz--current-user-answer)))
|
||||||
|
|
||||||
|
(linuxplus-quiz--set-current-user-answer
|
||||||
|
answer)
|
||||||
|
|
||||||
|
(unless old-answer
|
||||||
|
(linuxplus-quiz--advance-question))
|
||||||
|
|
||||||
|
(linuxplus-quiz-render)))
|
||||||
|
|
||||||
(defun linuxplus-quiz-render ()
|
(defun linuxplus-quiz-render ()
|
||||||
"Render the current Linux+ quiz buffer."
|
|
||||||
(let ((inhibit-read-only t))
|
(let ((inhibit-read-only t))
|
||||||
|
|
||||||
(erase-buffer)
|
(erase-buffer)
|
||||||
|
|
||||||
(insert "Linux+ Quiz\n")
|
(insert "Linux+ Quiz\n")
|
||||||
(insert "===========\n\n")
|
(insert "===========\n\n")
|
||||||
(insert "y = yes, n = no, b = back, f = forward, x = finish, q = quit\n\n")
|
|
||||||
(dotimes (i (length linuxplus-quiz-questions))
|
|
||||||
(let ((prefix (if (= i linuxplus-quiz-current-index) ">" " "))
|
|
||||||
(marker (linuxplus-quiz-answer-marker
|
|
||||||
(nth i linuxplus-quiz-user-answers)))
|
|
||||||
(question (nth i linuxplus-quiz-questions)))
|
|
||||||
(insert (format "%s %s %d. %s\n"
|
|
||||||
prefix marker (1+ i) question)))))
|
|
||||||
(goto-char (point-min)))
|
|
||||||
|
|
||||||
(defun linuxplus-quiz-finish ()
|
(let ((current-type
|
||||||
"Finish the current quiz, record the score, and show detailed results."
|
(linuxplus-quiz--question-type
|
||||||
(interactive)
|
(linuxplus-quiz--current-question))))
|
||||||
(let ((score 0)
|
|
||||||
(total (length linuxplus-quiz-questions)))
|
|
||||||
(dotimes (i total)
|
|
||||||
(when (eq (nth i linuxplus-quiz-user-answers)
|
|
||||||
(nth i linuxplus-quiz-answers))
|
|
||||||
(setq score (1+ score))))
|
|
||||||
(linuxplus-record-score linuxplus-quiz-lesson-name score)
|
|
||||||
(let ((inhibit-read-only t))
|
|
||||||
(erase-buffer)
|
|
||||||
(insert "Linux+ Quiz Results\n")
|
|
||||||
(insert "===================\n\n")
|
|
||||||
(insert (format "Lesson: %s\n" linuxplus-quiz-lesson-name))
|
|
||||||
(insert (format "Score: %d/%d\n\n" score total))
|
|
||||||
(insert "Detailed review:\n\n")
|
|
||||||
(dotimes (i total)
|
|
||||||
(let ((question (nth i linuxplus-quiz-questions))
|
|
||||||
(user-answer (nth i linuxplus-quiz-user-answers))
|
|
||||||
(correct-answer (nth i linuxplus-quiz-answers)))
|
|
||||||
(insert (format "%d. %s\n" (1+ i) question))
|
|
||||||
(insert (format " Your answer: %s\n"
|
|
||||||
(linuxplus-quiz-answer-string user-answer)))
|
|
||||||
(insert (format " Correct answer: %s\n"
|
|
||||||
(linuxplus-quiz-answer-string correct-answer)))
|
|
||||||
(insert (format " Result: %s\n\n"
|
|
||||||
(linuxplus-quiz-result-string
|
|
||||||
user-answer correct-answer))))))
|
|
||||||
(goto-char (point-min))
|
|
||||||
(view-mode 1)
|
|
||||||
(message "Quiz complete. Score: %d/%d" score total)))
|
|
||||||
|
|
||||||
(defun linuxplus-quiz-answer-y ()
|
(cond
|
||||||
"Answer yes to the current Linux+ quiz question."
|
|
||||||
(interactive)
|
((eq current-type 'true-false)
|
||||||
(setf (nth linuxplus-quiz-current-index linuxplus-quiz-user-answers) 'y)
|
(insert
|
||||||
(if (< linuxplus-quiz-current-index
|
"Type 't' for true or 'f' for false inside the brackets.\n"))
|
||||||
(1- (length linuxplus-quiz-questions)))
|
|
||||||
(setq linuxplus-quiz-current-index
|
((eq current-type 'yes-no)
|
||||||
(1+ linuxplus-quiz-current-index))
|
(insert
|
||||||
(linuxplus-quiz-render))
|
"Type 'y' for yes or 'n' for no inside the brackets.\n"))
|
||||||
(when (derived-mode-p 'linuxplus-quiz-mode)
|
|
||||||
(linuxplus-quiz-render)))
|
((eq current-type 'multiple)
|
||||||
|
(insert
|
||||||
|
"Use the space bar to add/remove the * character for multiple choice questions.\n"))
|
||||||
|
|
||||||
|
(t
|
||||||
|
(insert "Answer the question.\n"))))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
"b = back, TAB = forward, x = finish, q = quit\n\n")
|
||||||
|
|
||||||
|
(dotimes
|
||||||
|
(i (length linuxplus-quiz-questions))
|
||||||
|
|
||||||
|
(let* ((question
|
||||||
|
(nth i linuxplus-quiz-questions))
|
||||||
|
|
||||||
|
(type
|
||||||
|
(linuxplus-quiz--question-type
|
||||||
|
question))
|
||||||
|
|
||||||
|
(prompt
|
||||||
|
(linuxplus-quiz--question-prompt
|
||||||
|
question))
|
||||||
|
|
||||||
|
(user-answer
|
||||||
|
(nth i linuxplus-quiz-user-answers))
|
||||||
|
|
||||||
|
(current-question-p
|
||||||
|
(= i
|
||||||
|
linuxplus-quiz-current-question)))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format "%d.\n" (1+ i)))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format " %s\n" prompt))
|
||||||
|
|
||||||
|
(cond
|
||||||
|
|
||||||
|
((eq type 'true-false)
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
"%s %s True or False\n\n"
|
||||||
|
|
||||||
|
(if current-question-p
|
||||||
|
">"
|
||||||
|
" ")
|
||||||
|
|
||||||
|
(linuxplus-quiz--answer-marker
|
||||||
|
user-answer))))
|
||||||
|
|
||||||
|
((eq type 'yes-no)
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
"%s %s Yes or No\n\n"
|
||||||
|
|
||||||
|
(if current-question-p
|
||||||
|
">"
|
||||||
|
" ")
|
||||||
|
|
||||||
|
(linuxplus-quiz--answer-marker
|
||||||
|
user-answer))))
|
||||||
|
|
||||||
|
((eq type 'multiple)
|
||||||
|
|
||||||
|
(let ((choices
|
||||||
|
(linuxplus-quiz--question-choices
|
||||||
|
question)))
|
||||||
|
|
||||||
|
(dotimes
|
||||||
|
(choice-index
|
||||||
|
(length choices))
|
||||||
|
|
||||||
|
(let*
|
||||||
|
((selected
|
||||||
|
(if
|
||||||
|
(linuxplus-quiz--multiple-selected-p
|
||||||
|
user-answer
|
||||||
|
choice-index)
|
||||||
|
'selected
|
||||||
|
nil))
|
||||||
|
|
||||||
|
(choice-current-p
|
||||||
|
(and
|
||||||
|
current-question-p
|
||||||
|
(= choice-index
|
||||||
|
linuxplus-quiz-current-choice))))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
"%s %s %s\n"
|
||||||
|
|
||||||
|
(if choice-current-p
|
||||||
|
">"
|
||||||
|
" ")
|
||||||
|
|
||||||
|
(linuxplus-quiz--answer-marker
|
||||||
|
selected)
|
||||||
|
|
||||||
|
(nth choice-index
|
||||||
|
choices))))))
|
||||||
|
|
||||||
|
(insert "\n")))))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
"\nReminder: x = finish quiz, q = quit/return to lesson.\n"))
|
||||||
|
|
||||||
|
(linuxplus-quiz-goto-current-answer))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-goto-current-answer ()
|
||||||
|
|
||||||
|
(goto-char (point-min))
|
||||||
|
|
||||||
|
(let ((target-question
|
||||||
|
(format "^%d\\.$"
|
||||||
|
(1+
|
||||||
|
linuxplus-quiz-current-question))))
|
||||||
|
|
||||||
|
(when
|
||||||
|
(re-search-forward
|
||||||
|
target-question
|
||||||
|
nil t)
|
||||||
|
|
||||||
|
(let ((type
|
||||||
|
(linuxplus-quiz--question-type
|
||||||
|
(linuxplus-quiz--current-question))))
|
||||||
|
|
||||||
|
(cond
|
||||||
|
|
||||||
|
((memq type '(true-false yes-no))
|
||||||
|
|
||||||
|
(when
|
||||||
|
(re-search-forward
|
||||||
|
"\\[[^]]*\\]"
|
||||||
|
nil t)
|
||||||
|
|
||||||
|
(backward-char 1)))
|
||||||
|
|
||||||
|
((eq type 'multiple)
|
||||||
|
|
||||||
|
(dotimes
|
||||||
|
(_ linuxplus-quiz-current-choice)
|
||||||
|
|
||||||
|
(re-search-forward
|
||||||
|
"\\[[^]]*\\]"
|
||||||
|
nil t))
|
||||||
|
|
||||||
|
(when
|
||||||
|
(re-search-forward
|
||||||
|
"\\[[^]]*\\]"
|
||||||
|
nil t)
|
||||||
|
|
||||||
|
(backward-char 1))))))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-answer-true ()
|
||||||
|
|
||||||
(defun linuxplus-quiz-answer-n ()
|
|
||||||
"Answer no to the current Linux+ quiz question."
|
|
||||||
(interactive)
|
(interactive)
|
||||||
(setf (nth linuxplus-quiz-current-index linuxplus-quiz-user-answers) 'n)
|
|
||||||
(if (< linuxplus-quiz-current-index
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
(1- (length linuxplus-quiz-questions)))
|
|
||||||
(setq linuxplus-quiz-current-index
|
(if
|
||||||
(1+ linuxplus-quiz-current-index))
|
(eq
|
||||||
(linuxplus-quiz-render))
|
(linuxplus-quiz--question-type
|
||||||
(when (derived-mode-p 'linuxplus-quiz-mode)
|
(linuxplus-quiz--current-question))
|
||||||
(linuxplus-quiz-render)))
|
'true-false)
|
||||||
|
|
||||||
|
(linuxplus-quiz--set-answer-and-maybe-advance
|
||||||
|
'true)
|
||||||
|
|
||||||
|
(linuxplus-quiz--invalid-input-message)))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-answer-false ()
|
||||||
|
|
||||||
|
(interactive)
|
||||||
|
|
||||||
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
|
|
||||||
|
(if
|
||||||
|
(eq
|
||||||
|
(linuxplus-quiz--question-type
|
||||||
|
(linuxplus-quiz--current-question))
|
||||||
|
'true-false)
|
||||||
|
|
||||||
|
(linuxplus-quiz--set-answer-and-maybe-advance
|
||||||
|
'false)
|
||||||
|
|
||||||
|
(linuxplus-quiz--invalid-input-message)))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-answer-yes ()
|
||||||
|
|
||||||
|
(interactive)
|
||||||
|
|
||||||
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
|
|
||||||
|
(if
|
||||||
|
(eq
|
||||||
|
(linuxplus-quiz--question-type
|
||||||
|
(linuxplus-quiz--current-question))
|
||||||
|
'yes-no)
|
||||||
|
|
||||||
|
(linuxplus-quiz--set-answer-and-maybe-advance
|
||||||
|
'yes)
|
||||||
|
|
||||||
|
(linuxplus-quiz--invalid-input-message)))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-answer-no ()
|
||||||
|
|
||||||
|
(interactive)
|
||||||
|
|
||||||
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
|
|
||||||
|
(if
|
||||||
|
(eq
|
||||||
|
(linuxplus-quiz--question-type
|
||||||
|
(linuxplus-quiz--current-question))
|
||||||
|
'yes-no)
|
||||||
|
|
||||||
|
(linuxplus-quiz--set-answer-and-maybe-advance
|
||||||
|
'no)
|
||||||
|
|
||||||
|
(linuxplus-quiz--invalid-input-message)))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-toggle-multiple ()
|
||||||
|
|
||||||
|
(interactive)
|
||||||
|
|
||||||
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
|
|
||||||
|
(if
|
||||||
|
(eq
|
||||||
|
(linuxplus-quiz--question-type
|
||||||
|
(linuxplus-quiz--current-question))
|
||||||
|
'multiple)
|
||||||
|
|
||||||
|
(progn
|
||||||
|
(linuxplus-quiz--toggle-current-choice)
|
||||||
|
(linuxplus-quiz-render))
|
||||||
|
|
||||||
|
(linuxplus-quiz--invalid-input-message)))
|
||||||
|
|
||||||
(defun linuxplus-quiz-next ()
|
(defun linuxplus-quiz-next ()
|
||||||
"Move to next question without changing answer."
|
|
||||||
(interactive)
|
(interactive)
|
||||||
(when (< linuxplus-quiz-current-index
|
|
||||||
(1- (length linuxplus-quiz-questions)))
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
(setq linuxplus-quiz-current-index
|
|
||||||
(1+ linuxplus-quiz-current-index))
|
(let ((type
|
||||||
(linuxplus-quiz-render)))
|
(linuxplus-quiz--question-type
|
||||||
|
(linuxplus-quiz--current-question))))
|
||||||
|
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(eq type 'multiple)
|
||||||
|
|
||||||
|
(< linuxplus-quiz-current-choice
|
||||||
|
(1-
|
||||||
|
(length
|
||||||
|
(linuxplus-quiz--question-choices
|
||||||
|
(linuxplus-quiz--current-question))))))
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-current-choice
|
||||||
|
(1+
|
||||||
|
linuxplus-quiz-current-choice))
|
||||||
|
|
||||||
|
(linuxplus-quiz--advance-question)))
|
||||||
|
|
||||||
|
(linuxplus-quiz-render))
|
||||||
|
|
||||||
(defun linuxplus-quiz-prev ()
|
(defun linuxplus-quiz-prev ()
|
||||||
"Move to previous question."
|
|
||||||
(interactive)
|
(interactive)
|
||||||
(when (> linuxplus-quiz-current-index 0)
|
|
||||||
(setq linuxplus-quiz-current-index
|
|
||||||
(1- linuxplus-quiz-current-index))
|
|
||||||
(linuxplus-quiz-render)))
|
|
||||||
|
|
||||||
(defvar linuxplus-quiz-mode-map
|
(linuxplus-quiz--sync-current-position-from-point)
|
||||||
(let ((map (make-sparse-keymap)))
|
|
||||||
(define-key map (kbd "y") #'linuxplus-quiz-answer-y)
|
(let ((type
|
||||||
(define-key map (kbd "n") #'linuxplus-quiz-answer-n)
|
(linuxplus-quiz--question-type
|
||||||
(define-key map (kbd "f") #'linuxplus-quiz-next)
|
(linuxplus-quiz--current-question))))
|
||||||
(define-key map (kbd "b") #'linuxplus-quiz-prev)
|
|
||||||
(define-key map (kbd "x") #'linuxplus-quiz-finish)
|
(if
|
||||||
(define-key map (kbd "q") #'quit-window)
|
(and
|
||||||
map)
|
(eq type 'multiple)
|
||||||
"Keymap for `linuxplus-quiz-mode`.")
|
(> linuxplus-quiz-current-choice 0))
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-current-choice
|
||||||
|
(1-
|
||||||
|
linuxplus-quiz-current-choice))
|
||||||
|
|
||||||
|
(linuxplus-quiz--previous-question)))
|
||||||
|
|
||||||
|
(linuxplus-quiz-render))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-invalid-key ()
|
||||||
|
(interactive)
|
||||||
|
(linuxplus-quiz--invalid-input-message))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-return-to-lesson ()
|
||||||
|
|
||||||
|
"Return to lesson/window layout."
|
||||||
|
|
||||||
|
(interactive)
|
||||||
|
|
||||||
|
(let ((config
|
||||||
|
linuxplus-quiz-return-window-configuration)
|
||||||
|
|
||||||
|
(buffer
|
||||||
|
linuxplus-quiz-return-buffer))
|
||||||
|
|
||||||
|
(when
|
||||||
|
(window-configuration-p config)
|
||||||
|
|
||||||
|
(set-window-configuration config))
|
||||||
|
|
||||||
|
(when
|
||||||
|
(buffer-live-p buffer)
|
||||||
|
|
||||||
|
(switch-to-buffer buffer))))
|
||||||
|
|
||||||
|
(defun linuxplus-quiz-finish ()
|
||||||
|
|
||||||
|
(interactive)
|
||||||
|
|
||||||
|
(let ((score 0)
|
||||||
|
|
||||||
|
(total
|
||||||
|
(length linuxplus-quiz-questions)))
|
||||||
|
|
||||||
|
(dotimes (i total)
|
||||||
|
|
||||||
|
(when
|
||||||
|
(linuxplus-quiz--answer-correct-p
|
||||||
|
(nth i linuxplus-quiz-questions)
|
||||||
|
(nth i linuxplus-quiz-user-answers))
|
||||||
|
|
||||||
|
(setq score (1+ score))))
|
||||||
|
|
||||||
|
(linuxplus-record-score
|
||||||
|
linuxplus-quiz-lesson-name
|
||||||
|
score)
|
||||||
|
|
||||||
|
(let ((inhibit-read-only t))
|
||||||
|
|
||||||
|
(erase-buffer)
|
||||||
|
|
||||||
|
(insert "Linux+ Quiz Results\n")
|
||||||
|
(insert "===================\n\n")
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
"Lesson: %s\n"
|
||||||
|
linuxplus-quiz-lesson-name))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
"Score: %d/%d\n\n"
|
||||||
|
score total))
|
||||||
|
|
||||||
|
(insert "Detailed review:\n\n")
|
||||||
|
|
||||||
|
(dotimes (i total)
|
||||||
|
|
||||||
|
(let*
|
||||||
|
((question
|
||||||
|
(nth i linuxplus-quiz-questions))
|
||||||
|
|
||||||
|
(prompt
|
||||||
|
(linuxplus-quiz--question-prompt
|
||||||
|
question))
|
||||||
|
|
||||||
|
(user-answer
|
||||||
|
(nth i linuxplus-quiz-user-answers)))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
"%d. %s\n"
|
||||||
|
(1+ i)
|
||||||
|
prompt))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
" Your answer: %s\n"
|
||||||
|
(linuxplus-quiz--answer-string
|
||||||
|
user-answer)))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
" Correct answer: %s\n"
|
||||||
|
(linuxplus-quiz--correct-answer-for-display
|
||||||
|
question)))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
(format
|
||||||
|
" Result: %s\n\n"
|
||||||
|
|
||||||
|
(if
|
||||||
|
(linuxplus-quiz--answer-correct-p
|
||||||
|
question
|
||||||
|
user-answer)
|
||||||
|
|
||||||
|
"correct"
|
||||||
|
|
||||||
|
"incorrect")))))
|
||||||
|
|
||||||
|
(insert
|
||||||
|
"Press q to return to the lesson.\n"))
|
||||||
|
|
||||||
|
(goto-char (point-min))
|
||||||
|
|
||||||
|
(linuxplus-quiz-mode)
|
||||||
|
|
||||||
|
(message
|
||||||
|
"Quiz complete. Score: %d/%d"
|
||||||
|
score total)))
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-mode-map
|
||||||
|
|
||||||
|
(let ((map
|
||||||
|
(make-sparse-keymap)))
|
||||||
|
|
||||||
|
;; invalid printable input
|
||||||
|
|
||||||
|
(dotimes (i 95)
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(char-to-string (+ i 32))
|
||||||
|
#'linuxplus-quiz-invalid-key))
|
||||||
|
|
||||||
|
;; valid answers
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "t")
|
||||||
|
#'linuxplus-quiz-answer-true)
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "f")
|
||||||
|
#'linuxplus-quiz-answer-false)
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "y")
|
||||||
|
#'linuxplus-quiz-answer-yes)
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "n")
|
||||||
|
#'linuxplus-quiz-answer-no)
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "SPC")
|
||||||
|
#'linuxplus-quiz-toggle-multiple)
|
||||||
|
|
||||||
|
;; navigation
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "b")
|
||||||
|
#'linuxplus-quiz-prev)
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "TAB")
|
||||||
|
#'linuxplus-quiz-next)
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "x")
|
||||||
|
#'linuxplus-quiz-finish)
|
||||||
|
|
||||||
|
(define-key
|
||||||
|
map
|
||||||
|
(kbd "q")
|
||||||
|
#'linuxplus-quiz-return-to-lesson)
|
||||||
|
|
||||||
|
map))
|
||||||
|
|
||||||
|
(define-derived-mode
|
||||||
|
linuxplus-quiz-mode
|
||||||
|
special-mode
|
||||||
|
"LinuxPlus-Quiz"
|
||||||
|
"Major mode for Linux+ quizzes."
|
||||||
|
|
||||||
(define-derived-mode linuxplus-quiz-mode special-mode "LinuxPlus-Quiz"
|
|
||||||
"Major mode for Linux+ quiz buffers."
|
|
||||||
(setq buffer-read-only t))
|
(setq buffer-read-only t))
|
||||||
|
|
||||||
(defun linuxplus-start-quiz (lesson questions answers buffer)
|
(defun linuxplus-start-quiz
|
||||||
"Start a Linux+ quiz for LESSON using QUESTIONS and ANSWERS in BUFFER."
|
(lesson questions buffer)
|
||||||
(let ((buf (get-buffer-create buffer)))
|
|
||||||
(with-current-buffer buf
|
"Start Linux+ quiz in full window."
|
||||||
(linuxplus-quiz-mode)
|
|
||||||
(setq linuxplus-quiz-lesson-name lesson)
|
(let ((return-config
|
||||||
(setq linuxplus-quiz-questions questions)
|
(current-window-configuration))
|
||||||
(setq linuxplus-quiz-answers answers)
|
|
||||||
(setq linuxplus-quiz-user-answers (make-list (length questions) nil))
|
(return-buffer
|
||||||
(setq linuxplus-quiz-current-index 0)
|
(current-buffer))
|
||||||
(linuxplus-quiz-render))
|
|
||||||
(pop-to-buffer buf)))
|
(buf
|
||||||
|
(get-buffer-create buffer)))
|
||||||
|
|
||||||
|
(delete-other-windows)
|
||||||
|
|
||||||
|
(switch-to-buffer buf)
|
||||||
|
|
||||||
|
(linuxplus-quiz-mode)
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-return-window-configuration
|
||||||
|
return-config)
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-return-buffer
|
||||||
|
return-buffer)
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-lesson-name
|
||||||
|
lesson)
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-questions
|
||||||
|
questions)
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-user-answers
|
||||||
|
(make-list
|
||||||
|
(length questions)
|
||||||
|
nil))
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-current-question 0)
|
||||||
|
|
||||||
|
(setq linuxplus-quiz-current-choice 0)
|
||||||
|
|
||||||
|
(linuxplus-quiz-render)))
|
||||||
|
|
||||||
;;; 06-linuxplus-quiz-mode.el ends here
|
;;; 06-linuxplus-quiz-mode.el ends here
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
;;; 101-systemd-service-quiz.el --- systemd-service-quiz
|
||||||
|
|
||||||
|
(defun systemd-service-quiz ()
|
||||||
|
"Quiz for systemd service management."
|
||||||
|
(interactive)
|
||||||
|
(linuxplus-start-quiz
|
||||||
|
'systemd-service-lesson
|
||||||
|
'((:type true-false
|
||||||
|
:prompt "`systemctl start ssh` starts the service immediately but does not enable it at boot."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`systemctl enable ssh` starts the service immediately and enables it at boot."
|
||||||
|
:answer false)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`systemctl disable ssh` prevents startup at boot but does not necessarily stop a currently running service."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`systemctl mask ssh` is stronger than `systemctl disable ssh` because it prevents normal starting."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`systemctl reload ssh` and `systemctl restart ssh` always do exactly the same thing."
|
||||||
|
:answer false)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`systemctl status <service>` is useful when troubleshooting a failed service."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`journalctl -u <service>` can show logs for a specific systemd service."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`systemctl --failed` can help identify failed units."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type yes-no
|
||||||
|
:prompt "On many Red Hat-family systems, is the SSH service commonly named `sshd`?"
|
||||||
|
:answer yes)
|
||||||
|
|
||||||
|
(:type yes-no
|
||||||
|
:prompt "On many Debian-family systems, is the SSH service commonly named `sshd` instead of `ssh`?"
|
||||||
|
:answer no))
|
||||||
|
"*systemd Service Quiz*"))
|
||||||
|
|
||||||
|
;;; 101-systemd-service-quiz.el ends here
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
;;; 102-network-tools-lesson.el --- network-tools-lesson
|
||||||
|
|
||||||
|
(defun network-tools-lesson ()
|
||||||
|
"Linux+ lesson for network troubleshooting tools."
|
||||||
|
(interactive)
|
||||||
|
|
||||||
|
(delete-other-windows)
|
||||||
|
|
||||||
|
(let ((buf (get-buffer-create "*Network Tools Lesson*"))
|
||||||
|
(shell (or shell-file-name
|
||||||
|
(getenv "SHELL")
|
||||||
|
"/bin/sh")))
|
||||||
|
|
||||||
|
(switch-to-buffer buf)
|
||||||
|
|
||||||
|
(read-only-mode -1)
|
||||||
|
(erase-buffer)
|
||||||
|
|
||||||
|
(insert
|
||||||
|
"Linux+ Lesson: Network Troubleshooting Tools\n"
|
||||||
|
"============================================\n\n"
|
||||||
|
|
||||||
|
"Concept:\n"
|
||||||
|
"Linux administrators must diagnose network problems quickly.\n"
|
||||||
|
"CompTIA Linux+ expects familiarity with modern networking tools,\n"
|
||||||
|
"basic connectivity testing, interface inspection, DNS testing,\n"
|
||||||
|
"and socket/process inspection.\n\n"
|
||||||
|
|
||||||
|
"Core commands:\n\n"
|
||||||
|
|
||||||
|
"ip addr\n"
|
||||||
|
" Display IP address information.\n\n"
|
||||||
|
|
||||||
|
"ip route\n"
|
||||||
|
" Display routing table.\n\n"
|
||||||
|
|
||||||
|
"ping\n"
|
||||||
|
" Test ICMP connectivity.\n\n"
|
||||||
|
|
||||||
|
"ss -tulpn\n"
|
||||||
|
" Show listening TCP/UDP sockets and associated processes.\n\n"
|
||||||
|
|
||||||
|
"hostnamectl\n"
|
||||||
|
" Show or change persistent hostname.\n\n"
|
||||||
|
|
||||||
|
"dig\n"
|
||||||
|
" Query DNS servers.\n\n"
|
||||||
|
|
||||||
|
"traceroute\n"
|
||||||
|
" Show network path hops.\n\n"
|
||||||
|
|
||||||
|
"curl\n"
|
||||||
|
" Retrieve data from URLs and test HTTP services.\n\n"
|
||||||
|
|
||||||
|
"wget\n"
|
||||||
|
" Download files from remote systems.\n\n"
|
||||||
|
|
||||||
|
"nmap\n"
|
||||||
|
" Network discovery and port scanning.\n\n"
|
||||||
|
|
||||||
|
"Hands-on tasks:\n\n"
|
||||||
|
|
||||||
|
"1. Display IP addresses:\n"
|
||||||
|
" ip addr\n\n"
|
||||||
|
|
||||||
|
"2. Display routes:\n"
|
||||||
|
" ip route\n\n"
|
||||||
|
|
||||||
|
"3. Test connectivity:\n"
|
||||||
|
" ping -c 4 google.com\n\n"
|
||||||
|
|
||||||
|
"4. Inspect listening services:\n"
|
||||||
|
" ss -tulpn\n\n"
|
||||||
|
|
||||||
|
"5. Query DNS:\n"
|
||||||
|
" dig google.com\n\n"
|
||||||
|
|
||||||
|
"6. Download a webpage:\n"
|
||||||
|
" curl https://example.com\n\n"
|
||||||
|
|
||||||
|
"7. Download a file:\n"
|
||||||
|
" wget https://example.com/index.html\n\n"
|
||||||
|
|
||||||
|
"8. Display hostname:\n"
|
||||||
|
" hostnamectl\n\n"
|
||||||
|
|
||||||
|
"9. Run a localhost scan:\n"
|
||||||
|
" sudo nmap localhost\n\n"
|
||||||
|
|
||||||
|
"Important Linux+ exam traps:\n\n"
|
||||||
|
|
||||||
|
"- `ip` replaces many older networking tools.\n"
|
||||||
|
"- `ss` is preferred over `netstat` on modern systems.\n"
|
||||||
|
"- `dig` is for DNS lookups.\n"
|
||||||
|
"- `ping` uses ICMP.\n"
|
||||||
|
"- `curl` is commonly used for API and HTTP testing.\n"
|
||||||
|
"- `wget` is commonly used for recursive or file downloads.\n"
|
||||||
|
"- `nmap` is heavily associated with network discovery and auditing.\n"
|
||||||
|
"- `hostnamectl` changes persistent hostnames.\n\n"
|
||||||
|
|
||||||
|
"Debian/Rocky notes:\n\n"
|
||||||
|
|
||||||
|
"- `dig` may require package installation.\n"
|
||||||
|
"- Debian package: dnsutils\n"
|
||||||
|
"- Rocky package: bind-utils\n\n"
|
||||||
|
|
||||||
|
"When finished, run:\n\n"
|
||||||
|
"M-x network-tools-quiz\n")
|
||||||
|
|
||||||
|
(goto-char (point-min))
|
||||||
|
(view-mode 1)
|
||||||
|
|
||||||
|
(split-window-below)
|
||||||
|
(other-window 1)
|
||||||
|
|
||||||
|
(let ((term (get-buffer "*term*")))
|
||||||
|
(if (buffer-live-p term)
|
||||||
|
(switch-to-buffer term)
|
||||||
|
(ansi-term shell "term")))
|
||||||
|
|
||||||
|
(other-window -1)))
|
||||||
|
|
||||||
|
;;; 102-network-tools-lesson.el ends here
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
;;; 103-network-tools-quiz.el --- network-tools-quiz
|
||||||
|
|
||||||
|
(defun network-tools-quiz ()
|
||||||
|
"Quiz for Linux network troubleshooting tools."
|
||||||
|
(interactive)
|
||||||
|
|
||||||
|
(linuxplus-start-quiz
|
||||||
|
'network-tools-lesson
|
||||||
|
|
||||||
|
'((:type true-false
|
||||||
|
:prompt "`ss -tulpn` can display listening sockets and processes."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`dig` is commonly used for DNS lookups."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`ping` primarily tests DNS zone transfers."
|
||||||
|
:answer false)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`ip addr` displays interface addressing information."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`hostnamectl` can manage persistent hostnames."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`wget` is primarily associated with downloading remote files."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`curl` is commonly used for HTTP/API testing."
|
||||||
|
:answer true)
|
||||||
|
|
||||||
|
(:type true-false
|
||||||
|
:prompt "`netstat` is generally preferred over `ss` on modern Linux systems."
|
||||||
|
:answer false)
|
||||||
|
|
||||||
|
(:type yes-no
|
||||||
|
:prompt "Does `ping` use ICMP packets?"
|
||||||
|
:answer yes)
|
||||||
|
|
||||||
|
(:type yes-no
|
||||||
|
:prompt "On Debian-family systems, is the `dig` command commonly provided by the `dnsutils` package?"
|
||||||
|
:answer yes))
|
||||||
|
|
||||||
|
"*Network Tools Quiz*"))
|
||||||
|
|
||||||
|
;;; 103-network-tools-quiz.el ends here
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
;;; 30-auth-log-lesson.el --- auth-log-lesson
|
||||||
|
|
||||||
|
(defun auth-log-lesson ()
|
||||||
|
"Linux+ lesson for authentication logs."
|
||||||
|
(interactive)
|
||||||
|
(delete-other-windows)
|
||||||
|
(let ((buf (get-buffer-create "*Auth Log Lesson*"))
|
||||||
|
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
|
||||||
|
(switch-to-buffer buf)
|
||||||
|
(read-only-mode -1)
|
||||||
|
(erase-buffer)
|
||||||
|
(insert
|
||||||
|
"Linux+ Lesson: Authentication Logs\n"
|
||||||
|
"=================================\n\n"
|
||||||
|
"Concept:\n"
|
||||||
|
"Different Linux distributions store authentication logs in different locations.\n\n"
|
||||||
|
"You MUST know this for Linux+:\n\n"
|
||||||
|
" Red Hat / RHEL / Rocky / Alma:\n"
|
||||||
|
" /var/log/secure\n\n"
|
||||||
|
" Debian / Ubuntu:\n"
|
||||||
|
" /var/log/auth.log\n\n"
|
||||||
|
"Hands-on tasks (run in the terminal below):\n\n"
|
||||||
|
"1. Try viewing both files:\n"
|
||||||
|
" sudo less /var/log/auth.log\n"
|
||||||
|
" sudo less /var/log/secure\n\n"
|
||||||
|
" One of these will exist, depending on your distro.\n\n"
|
||||||
|
"2. Search for failed logins:\n"
|
||||||
|
" sudo grep -i fail /var/log/auth.log\n"
|
||||||
|
" sudo grep -i fail /var/log/secure\n\n"
|
||||||
|
"3. Search for ssh login activity:\n"
|
||||||
|
" sudo grep ssh /var/log/auth.log\n"
|
||||||
|
" sudo grep ssh /var/log/secure\n\n"
|
||||||
|
"4. Count failed login attempts:\n"
|
||||||
|
" sudo grep -i fail <file> | wc -l\n\n"
|
||||||
|
"Replace <file> with the correct log file for your system.\n\n"
|
||||||
|
"What to notice:\n"
|
||||||
|
"- Which file exists\n"
|
||||||
|
"- What failed login entries look like\n"
|
||||||
|
"- How grep helps filter logs\n\n"
|
||||||
|
"Linux+ exam trap:\n"
|
||||||
|
"If the question mentions failed login attempts on a Red Hat system,\n"
|
||||||
|
"the answer is:\n\n"
|
||||||
|
" /var/log/secure\n\n"
|
||||||
|
"When finished, run:\n"
|
||||||
|
" M-x auth-log-quiz\n")
|
||||||
|
(goto-char (point-min))
|
||||||
|
(view-mode 1)
|
||||||
|
(split-window-below)
|
||||||
|
(other-window 1)
|
||||||
|
(let ((term (get-buffer "*term*")))
|
||||||
|
(if (buffer-live-p term)
|
||||||
|
(switch-to-buffer term)
|
||||||
|
(ansi-term shell "term")))
|
||||||
|
(other-window -1)))
|
||||||
|
|
||||||
|
;;; 30-auth-log-lesson.el ends here
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
;;; 31-auth-log-quiz.el --- auth-log-quiz
|
||||||
|
|
||||||
|
(defun auth-log-quiz ()
|
||||||
|
"Quiz for authentication log knowledge."
|
||||||
|
(interactive)
|
||||||
|
(linuxplus-start-quiz
|
||||||
|
'auth-log-lesson
|
||||||
|
'("Is /var/log/secure used on Red Hat systems?"
|
||||||
|
"Is /var/log/auth.log used on Debian-based systems?"
|
||||||
|
"Does grep help filter log entries?"
|
||||||
|
"Is /var/log/messages used for authentication logs?"
|
||||||
|
"Can failed logins be found using grep fail?")
|
||||||
|
'(y y y n y)
|
||||||
|
"*Auth Log Quiz*"))
|
||||||
|
|
||||||
|
;;; 31-auth-log-quiz.el ends here
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
;;; 40-package-management-lesson.el --- package-management-lesson
|
||||||
|
|
||||||
|
(defun package-management-lesson ()
|
||||||
|
"Linux+ lesson for apt vs dnf package management."
|
||||||
|
(interactive)
|
||||||
|
(delete-other-windows)
|
||||||
|
(let ((buf (get-buffer-create "*Package Management Lesson*"))
|
||||||
|
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
|
||||||
|
(switch-to-buffer buf)
|
||||||
|
(read-only-mode -1)
|
||||||
|
(erase-buffer)
|
||||||
|
(insert
|
||||||
|
"Linux+ Lesson: Package Management (Debian vs Red Hat)\n"
|
||||||
|
"=====================================================\n\n"
|
||||||
|
"You MUST know both ecosystems.\n\n"
|
||||||
|
"Debian-based systems (Debian, Ubuntu):\n"
|
||||||
|
" apt update → refresh package list\n"
|
||||||
|
" apt upgrade → upgrade installed packages\n"
|
||||||
|
" apt install pkg → install package\n"
|
||||||
|
" apt remove pkg → remove package\n\n"
|
||||||
|
"Red Hat-based systems (RHEL, Rocky, Alma):\n"
|
||||||
|
" dnf check-update → check for updates\n"
|
||||||
|
" dnf upgrade → upgrade system\n"
|
||||||
|
" dnf install pkg → install package\n"
|
||||||
|
" dnf remove pkg → remove package\n\n"
|
||||||
|
"Hands-on tasks:\n\n"
|
||||||
|
"DEBIAN VM:\n"
|
||||||
|
" sudo apt update\n"
|
||||||
|
" sudo apt install tree\n"
|
||||||
|
" tree\n"
|
||||||
|
" sudo apt remove tree\n\n"
|
||||||
|
"ROCKY VM:\n"
|
||||||
|
" sudo dnf check-update\n"
|
||||||
|
" sudo dnf install tree\n"
|
||||||
|
" tree\n"
|
||||||
|
" sudo dnf remove tree\n\n"
|
||||||
|
"What to notice:\n"
|
||||||
|
"- Command differences (apt vs dnf)\n"
|
||||||
|
"- Same concepts, different syntax\n\n"
|
||||||
|
"Linux+ exam traps:\n"
|
||||||
|
"- apt is NOT used on Red Hat\n"
|
||||||
|
"- dnf/yum are NOT used on Debian\n\n"
|
||||||
|
"When finished, run:\n"
|
||||||
|
" M-x package-management-quiz\n")
|
||||||
|
(goto-char (point-min))
|
||||||
|
(view-mode 1)
|
||||||
|
(split-window-below)
|
||||||
|
(other-window 1)
|
||||||
|
(let ((term (get-buffer "*term*")))
|
||||||
|
(if (buffer-live-p term)
|
||||||
|
(switch-to-buffer term)
|
||||||
|
(ansi-term shell "term")))
|
||||||
|
(other-window -1)))
|
||||||
|
|
||||||
|
;;; 40-package-management-lesson.el ends here
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
;;; 41-package-management-quiz.el --- package-management-quiz
|
||||||
|
|
||||||
|
(defun package-management-quiz ()
|
||||||
|
"Quiz for apt vs dnf knowledge."
|
||||||
|
(interactive)
|
||||||
|
(linuxplus-start-quiz
|
||||||
|
'package-management-lesson
|
||||||
|
'("Is apt used on Debian-based systems?"
|
||||||
|
"Is dnf used on Red Hat-based systems?"
|
||||||
|
"Does apt update refresh package lists?"
|
||||||
|
"Does dnf install install packages?"
|
||||||
|
"Can apt be used on Rocky Linux?")
|
||||||
|
'(y y y y n)
|
||||||
|
"*Package Management Quiz*"))
|
||||||
|
|
||||||
|
;;; 41-package-management-quiz.el ends here
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
;;; 60-file-permissions-lesson.el --- file-permissions-lesson
|
||||||
|
|
||||||
|
(defun file-permissions-lesson ()
|
||||||
|
"Linux+ lesson for file permissions and ownership."
|
||||||
|
(interactive)
|
||||||
|
(delete-other-windows)
|
||||||
|
(let ((buf (get-buffer-create "*File Permissions Lesson*"))
|
||||||
|
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
|
||||||
|
(switch-to-buffer buf)
|
||||||
|
(read-only-mode -1)
|
||||||
|
(erase-buffer)
|
||||||
|
(insert
|
||||||
|
"Linux+ Lesson: File Permissions and Ownership\n"
|
||||||
|
"============================================\n\n"
|
||||||
|
"Concepts:\n"
|
||||||
|
"- chmod changes permissions\n"
|
||||||
|
"- chown changes ownership\n"
|
||||||
|
"- rwx permissions apply to:\n"
|
||||||
|
" user / group / others\n\n"
|
||||||
|
"Permission values:\n"
|
||||||
|
" r = 4\n"
|
||||||
|
" w = 2\n"
|
||||||
|
" x = 1\n\n"
|
||||||
|
"Common octal permissions:\n"
|
||||||
|
" 644 = rw-r--r--\n"
|
||||||
|
" 755 = rwxr-xr-x\n"
|
||||||
|
" 600 = rw-------\n"
|
||||||
|
" 700 = rwx------\n\n"
|
||||||
|
"Hands-on tasks:\n\n"
|
||||||
|
"1. Create a practice directory:\n"
|
||||||
|
" mkdir ~/linuxplus-permissions\n\n"
|
||||||
|
"2. Enter it:\n"
|
||||||
|
" cd ~/linuxplus-permissions\n\n"
|
||||||
|
"3. Create a file:\n"
|
||||||
|
" touch testfile\n\n"
|
||||||
|
"4. View permissions:\n"
|
||||||
|
" ls -l\n\n"
|
||||||
|
"5. Change permissions to 644:\n"
|
||||||
|
" chmod 644 testfile\n"
|
||||||
|
" ls -l\n\n"
|
||||||
|
"6. Change permissions to 755:\n"
|
||||||
|
" chmod 755 testfile\n"
|
||||||
|
" ls -l\n\n"
|
||||||
|
"7. Use symbolic mode:\n"
|
||||||
|
" chmod u+x testfile\n"
|
||||||
|
" chmod g-w testfile\n"
|
||||||
|
" ls -l\n\n"
|
||||||
|
"8. View ownership:\n"
|
||||||
|
" ls -l\n\n"
|
||||||
|
"9. Change ownership (requires sudo):\n"
|
||||||
|
" sudo chown root:root testfile\n"
|
||||||
|
" ls -l\n\n"
|
||||||
|
"10. Return ownership to yourself:\n"
|
||||||
|
" sudo chown $USER:$USER testfile\n"
|
||||||
|
" ls -l\n\n"
|
||||||
|
"Linux+ exam traps:\n"
|
||||||
|
"- chmod changes permissions, NOT ownership\n"
|
||||||
|
"- chown changes ownership\n"
|
||||||
|
"- x on directories means traversal\n"
|
||||||
|
"- 755 is common for executable files/directories\n"
|
||||||
|
"- 644 is common for regular files\n\n"
|
||||||
|
"When finished, run:\n"
|
||||||
|
" M-x file-permissions-quiz\n")
|
||||||
|
(goto-char (point-min))
|
||||||
|
(view-mode 1)
|
||||||
|
(split-window-below)
|
||||||
|
(other-window 1)
|
||||||
|
(let ((term (get-buffer "*term*")))
|
||||||
|
(if (buffer-live-p term)
|
||||||
|
(switch-to-buffer term)
|
||||||
|
(ansi-term shell "term")))
|
||||||
|
(other-window -1)))
|
||||||
|
|
||||||
|
;;; 60-file-permissions-lesson.el ends here
|
||||||
Reference in New Issue
Block a user