Adding files
This commit is contained in:
@@ -24,18 +24,14 @@
|
||||
(plist-get question :choices))
|
||||
|
||||
(defun linuxplus-quiz--current-question ()
|
||||
(nth linuxplus-quiz-current-question
|
||||
linuxplus-quiz-questions))
|
||||
(nth linuxplus-quiz-current-question linuxplus-quiz-questions))
|
||||
|
||||
(defun linuxplus-quiz--current-user-answer ()
|
||||
(nth linuxplus-quiz-current-question
|
||||
linuxplus-quiz-user-answers))
|
||||
(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))
|
||||
(setf (nth linuxplus-quiz-current-question linuxplus-quiz-user-answers)
|
||||
answer))
|
||||
|
||||
(defun linuxplus-quiz--answer-marker (answer)
|
||||
(cond
|
||||
@@ -56,18 +52,14 @@
|
||||
((null answer) "unanswered")
|
||||
(t (format "%S" answer))))
|
||||
|
||||
(defun linuxplus-quiz--correct-answer-for-display
|
||||
(question)
|
||||
(let ((type
|
||||
(linuxplus-quiz--question-type question)))
|
||||
(defun linuxplus-quiz--correct-answer-for-display (question)
|
||||
(let ((type (linuxplus-quiz--question-type question)))
|
||||
(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)))
|
||||
(format "%S" (linuxplus-quiz--question-answers question)))
|
||||
(t "unknown"))))
|
||||
|
||||
(defun linuxplus-quiz--advance-question ()
|
||||
@@ -83,544 +75,235 @@
|
||||
(1- linuxplus-quiz-current-question))
|
||||
(setq linuxplus-quiz-current-choice 0)))
|
||||
|
||||
(defun linuxplus-quiz--multiple-selected-p
|
||||
(answer choice-index)
|
||||
(defun linuxplus-quiz--multiple-selected-p (answer choice-index)
|
||||
(and (listp answer)
|
||||
(memq choice-index answer)))
|
||||
|
||||
(defun linuxplus-quiz--toggle-current-choice ()
|
||||
(let* ((answer (copy-sequence
|
||||
(or (linuxplus-quiz--current-user-answer) nil)))
|
||||
(choice linuxplus-quiz-current-choice))
|
||||
(if (memq choice answer)
|
||||
(setq answer (delq choice answer))
|
||||
(push choice answer))
|
||||
(linuxplus-quiz--set-current-user-answer
|
||||
(sort answer #'<))))
|
||||
|
||||
(defun linuxplus-quiz--advance-choice ()
|
||||
"Advance to next multiple-choice bracket."
|
||||
(let* ((question
|
||||
(linuxplus-quiz--current-question))
|
||||
(choices
|
||||
(linuxplus-quiz--question-choices
|
||||
question)))
|
||||
"Advance to the next multiple-choice bracket if one exists."
|
||||
(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)))
|
||||
|
||||
(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 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))
|
||||
#'<)))
|
||||
|
||||
(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)))
|
||||
(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)))
|
||||
|
||||
"Update current question 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-question question-index)
|
||||
(unless (eq (linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))
|
||||
'multiple)
|
||||
(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))))
|
||||
|
||||
(let ((type (linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))))
|
||||
(cond
|
||||
|
||||
((eq type 'true-false)
|
||||
(message
|
||||
"Valid input: t for true, f for false."))
|
||||
|
||||
(message "Valid input: t for true, f for false."))
|
||||
((eq type 'yes-no)
|
||||
(message
|
||||
"Valid input: y for yes, n for no."))
|
||||
|
||||
(message "Valid input: y for yes, n for no."))
|
||||
((eq type 'multiple)
|
||||
(message
|
||||
"Use the space bar to add/remove the * character."))
|
||||
|
||||
(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."
|
||||
|
||||
(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)
|
||||
|
||||
(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 ()
|
||||
|
||||
(let ((inhibit-read-only t))
|
||||
|
||||
(erase-buffer)
|
||||
|
||||
(insert "Linux+ Quiz\n")
|
||||
(insert "===========\n\n")
|
||||
|
||||
(let ((current-type
|
||||
(linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))))
|
||||
|
||||
(cond
|
||||
|
||||
((eq current-type 'true-false)
|
||||
(insert
|
||||
"Type 't' for true or 'f' for false inside the brackets.\n"))
|
||||
|
||||
(insert "Type 't' for true or 'f' for false inside the brackets.\n"))
|
||||
((eq current-type 'yes-no)
|
||||
(insert
|
||||
"Type 'y' for yes or 'n' for no inside the brackets.\n"))
|
||||
|
||||
(insert "Type 'y' for yes or 'n' for no inside the brackets.\n"))
|
||||
((eq current-type 'multiple)
|
||||
(insert
|
||||
"Use the space bar to add/remove the * character for multiple choice questions.\n"))
|
||||
|
||||
(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")
|
||||
|
||||
(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))
|
||||
|
||||
(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))))
|
||||
|
||||
(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))))
|
||||
|
||||
(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))))
|
||||
|
||||
(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"))
|
||||
|
||||
(format "%s %s %s\n"
|
||||
(if choice-current-p ">" " ")
|
||||
(linuxplus-quiz--answer-marker selected)
|
||||
(nth choice-index choices))))))
|
||||
(insert "\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))))
|
||||
|
||||
(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)
|
||||
|
||||
(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)
|
||||
|
||||
(dotimes (_ linuxplus-quiz-current-choice)
|
||||
(re-search-forward "\\[[^]]*\\]" nil t))
|
||||
(when (re-search-forward "\\[[^]]*\\]" nil t)
|
||||
(backward-char 1))))))))
|
||||
|
||||
(defun linuxplus-quiz-answer-true ()
|
||||
|
||||
(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
|
||||
'true)
|
||||
|
||||
(if (eq (linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))
|
||||
'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)
|
||||
|
||||
(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)
|
||||
|
||||
(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)
|
||||
|
||||
(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)
|
||||
|
||||
(if (eq (linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))
|
||||
'multiple)
|
||||
(progn
|
||||
(linuxplus-quiz--toggle-current-choice)
|
||||
(linuxplus-quiz--advance-choice)
|
||||
(linuxplus-quiz-render))
|
||||
|
||||
(linuxplus-quiz--invalid-input-message)))
|
||||
|
||||
(defun linuxplus-quiz-next ()
|
||||
|
||||
(interactive)
|
||||
|
||||
(linuxplus-quiz--sync-current-position-from-point)
|
||||
|
||||
(let ((type
|
||||
(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))))))
|
||||
|
||||
(let ((type (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))
|
||||
|
||||
(1+ linuxplus-quiz-current-choice))
|
||||
(linuxplus-quiz--advance-question)))
|
||||
|
||||
(linuxplus-quiz-render))
|
||||
|
||||
(defun linuxplus-quiz-prev ()
|
||||
|
||||
(interactive)
|
||||
|
||||
(linuxplus-quiz--sync-current-position-from-point)
|
||||
|
||||
(let ((type
|
||||
(linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))))
|
||||
|
||||
(if
|
||||
(and
|
||||
(eq type 'multiple)
|
||||
(> linuxplus-quiz-current-choice 0))
|
||||
|
||||
(let ((type (linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))))
|
||||
(if (and (eq type 'multiple)
|
||||
(> linuxplus-quiz-current-choice 0))
|
||||
(setq linuxplus-quiz-current-choice
|
||||
(1-
|
||||
linuxplus-quiz-current-choice))
|
||||
|
||||
(1- linuxplus-quiz-current-choice))
|
||||
(linuxplus-quiz--previous-question)))
|
||||
|
||||
(linuxplus-quiz-render))
|
||||
|
||||
(defun linuxplus-quiz-invalid-key ()
|
||||
@@ -628,185 +311,71 @@ If replacing, stay."
|
||||
(linuxplus-quiz--invalid-input-message))
|
||||
|
||||
(defun linuxplus-quiz-return-to-lesson ()
|
||||
|
||||
"Return to lesson/window layout."
|
||||
|
||||
"Return to the lesson/window layout from before the quiz."
|
||||
(interactive)
|
||||
|
||||
(let ((config
|
||||
linuxplus-quiz-return-window-configuration)
|
||||
|
||||
(buffer
|
||||
linuxplus-quiz-return-buffer))
|
||||
|
||||
(when
|
||||
(window-configuration-p config)
|
||||
|
||||
(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)
|
||||
|
||||
(when (buffer-live-p buffer)
|
||||
(switch-to-buffer buffer))))
|
||||
|
||||
(defun linuxplus-quiz-finish ()
|
||||
|
||||
(interactive)
|
||||
|
||||
(let ((score 0)
|
||||
|
||||
(total
|
||||
(length linuxplus-quiz-questions)))
|
||||
|
||||
(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))
|
||||
|
||||
(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)
|
||||
|
||||
(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 (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"))
|
||||
|
||||
(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)))
|
||||
(message "Quiz complete. Score: %d/%d" score total)))
|
||||
|
||||
(setq linuxplus-quiz-mode-map
|
||||
|
||||
(let ((map
|
||||
(make-sparse-keymap)))
|
||||
|
||||
;; invalid printable input
|
||||
|
||||
(let ((map (make-sparse-keymap)))
|
||||
;; Invalid printable input by default.
|
||||
(dotimes (i 95)
|
||||
(define-key map
|
||||
(char-to-string (+ i 32))
|
||||
#'linuxplus-quiz-invalid-key))
|
||||
|
||||
(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)
|
||||
|
||||
;; 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)
|
||||
;; Navigation / control.
|
||||
(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))
|
||||
|
||||
@@ -815,50 +384,24 @@ If replacing, stay."
|
||||
special-mode
|
||||
"LinuxPlus-Quiz"
|
||||
"Major mode for Linux+ quizzes."
|
||||
|
||||
(setq buffer-read-only t))
|
||||
|
||||
(defun linuxplus-start-quiz
|
||||
(lesson questions buffer)
|
||||
|
||||
"Start Linux+ quiz in full window."
|
||||
|
||||
(let ((return-config
|
||||
(current-window-configuration))
|
||||
|
||||
(return-buffer
|
||||
(current-buffer))
|
||||
|
||||
(buf
|
||||
(get-buffer-create buffer)))
|
||||
|
||||
(defun linuxplus-start-quiz (lesson questions buffer)
|
||||
"Start a Linux+ quiz in a full window."
|
||||
(let ((return-config (current-window-configuration))
|
||||
(return-buffer (current-buffer))
|
||||
(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-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))
|
||||
|
||||
(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
|
||||
|
||||
70
100-systemd-service-lesson.el
Normal file
70
100-systemd-service-lesson.el
Normal file
@@ -0,0 +1,70 @@
|
||||
;;; 100-systemd-service-lesson.el --- systemd-service-lesson
|
||||
|
||||
(defun systemd-service-lesson ()
|
||||
"Linux+ lesson for systemd service management."
|
||||
(interactive)
|
||||
(delete-other-windows)
|
||||
(let ((buf (get-buffer-create "*systemd Service Lesson*"))
|
||||
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
|
||||
(switch-to-buffer buf)
|
||||
(read-only-mode -1)
|
||||
(erase-buffer)
|
||||
(insert
|
||||
"Linux+ Lesson: systemd Service Management\n"
|
||||
"=========================================\n\n"
|
||||
"Concept:\n"
|
||||
"Learn how to manage services with systemctl and understand\n"
|
||||
"the difference between starting, enabling, disabling, and masking.\n\n"
|
||||
"Use BOTH VMs:\n"
|
||||
"- Debian 13 usually uses service name: ssh\n"
|
||||
"- Rocky Linux usually uses service name: sshd\n\n"
|
||||
"Core meanings:\n"
|
||||
" systemctl start <service> = start now\n"
|
||||
" systemctl stop <service> = stop now\n"
|
||||
" systemctl restart <service> = stop then start\n"
|
||||
" systemctl reload <service> = reload config if supported\n"
|
||||
" systemctl enable <service> = start at boot\n"
|
||||
" systemctl disable <service> = do not start at boot\n"
|
||||
" systemctl enable --now <service> = start now and enable at boot\n"
|
||||
" systemctl mask <service> = prevent normal starting\n"
|
||||
" systemctl unmask <service> = remove mask\n\n"
|
||||
"Hands-on tasks in Debian 13:\n"
|
||||
" systemctl status ssh\n"
|
||||
" systemctl is-enabled ssh\n"
|
||||
" sudo systemctl restart ssh\n"
|
||||
" journalctl -u ssh --since today\n\n"
|
||||
"Hands-on tasks in Rocky Linux:\n"
|
||||
" systemctl status sshd\n"
|
||||
" systemctl is-enabled sshd\n"
|
||||
" sudo systemctl restart sshd\n"
|
||||
" journalctl -u sshd --since today\n\n"
|
||||
"Optional lab on either disposable VM:\n"
|
||||
" sudo systemctl stop <service>\n"
|
||||
" systemctl status <service>\n"
|
||||
" sudo systemctl start <service>\n"
|
||||
" sudo systemctl disable <service>\n"
|
||||
" systemctl is-enabled <service>\n"
|
||||
" sudo systemctl enable <service>\n\n"
|
||||
"Boot target review:\n"
|
||||
" systemctl get-default\n"
|
||||
" sudo systemctl set-default multi-user.target\n"
|
||||
" sudo systemctl set-default graphical.target\n\n"
|
||||
"Exam traps:\n"
|
||||
"- enable does not start a service immediately.\n"
|
||||
"- disable does not stop a currently running service.\n"
|
||||
"- mask is stronger than disable.\n"
|
||||
"- reload and restart are not the same.\n"
|
||||
"- Debian commonly uses ssh; Red Hat/Rocky commonly uses sshd.\n\n"
|
||||
"When finished, run:\n"
|
||||
" M-x systemd-service-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)))
|
||||
|
||||
;;; 100-systemd-service-lesson.el ends here
|
||||
@@ -1,49 +1,764 @@
|
||||
;;; 101-systemd-service-quiz.el --- systemd-service-quiz
|
||||
;;; 06-linuxplus-quiz-mode.el --- linuxplus-quiz-mode
|
||||
|
||||
(defun systemd-service-quiz ()
|
||||
"Quiz for systemd service management."
|
||||
(defvar-local linuxplus-quiz-lesson-name nil)
|
||||
(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)
|
||||
|
||||
(defun linuxplus-quiz--question-type (question)
|
||||
(plist-get question :type))
|
||||
|
||||
(defun linuxplus-quiz--question-prompt (question)
|
||||
(plist-get question :prompt))
|
||||
|
||||
(defun linuxplus-quiz--question-answer (question)
|
||||
(plist-get question :answer))
|
||||
|
||||
(defun linuxplus-quiz--question-answers (question)
|
||||
(plist-get question :answers))
|
||||
|
||||
(defun linuxplus-quiz--question-choices (question)
|
||||
(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
|
||||
((eq answer 'true) "[t]")
|
||||
((eq answer 'false) "[f]")
|
||||
((eq answer 'yes) "[y]")
|
||||
((eq answer 'no) "[n]")
|
||||
((eq answer 'selected) "[*]")
|
||||
(t "[]")))
|
||||
|
||||
(defun linuxplus-quiz--answer-string (answer)
|
||||
(cond
|
||||
((eq answer 'true) "true")
|
||||
((eq answer 'false) "false")
|
||||
((eq answer 'yes) "yes")
|
||||
((eq answer 'no) "no")
|
||||
((listp answer) (format "%S" answer))
|
||||
((null answer) "unanswered")
|
||||
(t (format "%S" answer))))
|
||||
|
||||
(defun linuxplus-quiz--correct-answer-for-display
|
||||
(question)
|
||||
(let ((type
|
||||
(linuxplus-quiz--question-type question)))
|
||||
(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.
|
||||
|
||||
If selecting:
|
||||
- add *
|
||||
- advance to next bracket
|
||||
|
||||
If removing:
|
||||
- clear *
|
||||
- stay on current bracket."
|
||||
|
||||
(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)
|
||||
|
||||
;; Return immediately.
|
||||
(setq answer nil))
|
||||
|
||||
;; Removal path only reaches here.
|
||||
(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--sync-current-position-from-point ()
|
||||
"Update current question from cursor position."
|
||||
(let ((question-index
|
||||
(linuxplus-quiz--question-index-at-point)))
|
||||
|
||||
(when question-index
|
||||
|
||||
(setq linuxplus-quiz-current-question
|
||||
question-index)
|
||||
|
||||
(unless
|
||||
(eq
|
||||
(linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))
|
||||
'multiple)
|
||||
|
||||
(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 ()
|
||||
(let ((inhibit-read-only t))
|
||||
|
||||
(erase-buffer)
|
||||
|
||||
(insert "Linux+ Quiz\n")
|
||||
(insert "===========\n\n")
|
||||
|
||||
(let ((current-type
|
||||
(linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))))
|
||||
|
||||
(cond
|
||||
|
||||
((eq current-type 'true-false)
|
||||
(insert
|
||||
"Type 't' for true or 'f' for false inside the brackets.\n"))
|
||||
|
||||
((eq current-type 'yes-no)
|
||||
(insert
|
||||
"Type 'y' for yes or 'n' for no inside the brackets.\n"))
|
||||
|
||||
((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 ()
|
||||
(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)
|
||||
(linuxplus-quiz--sync-current-position-from-point)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`systemctl disable ssh` prevents startup at boot but does not necessarily stop a currently running service."
|
||||
:answer true)
|
||||
(if
|
||||
(eq
|
||||
(linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))
|
||||
'true-false)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`systemctl mask ssh` is stronger than `systemctl disable ssh` because it prevents normal starting."
|
||||
:answer true)
|
||||
(linuxplus-quiz--set-answer-and-maybe-advance
|
||||
'true)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`systemctl reload ssh` and `systemctl restart ssh` always do exactly the same thing."
|
||||
:answer false)
|
||||
(linuxplus-quiz--invalid-input-message)))
|
||||
|
||||
(:type true-false
|
||||
:prompt "`systemctl status <service>` is useful when troubleshooting a failed service."
|
||||
:answer true)
|
||||
(defun linuxplus-quiz-answer-false ()
|
||||
(interactive)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`journalctl -u <service>` can show logs for a specific systemd service."
|
||||
:answer true)
|
||||
(linuxplus-quiz--sync-current-position-from-point)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`systemctl --failed` can help identify failed units."
|
||||
:answer true)
|
||||
(if
|
||||
(eq
|
||||
(linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))
|
||||
'true-false)
|
||||
|
||||
(:type yes-no
|
||||
:prompt "On many Red Hat-family systems, is the SSH service commonly named `sshd`?"
|
||||
:answer yes)
|
||||
(linuxplus-quiz--set-answer-and-maybe-advance
|
||||
'false)
|
||||
|
||||
(:type yes-no
|
||||
:prompt "On many Debian-family systems, is the SSH service commonly named `sshd` instead of `ssh`?"
|
||||
:answer no))
|
||||
"*systemd Service Quiz*"))
|
||||
(linuxplus-quiz--invalid-input-message)))
|
||||
|
||||
;;; 101-systemd-service-quiz.el ends here
|
||||
(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 ()
|
||||
(interactive)
|
||||
|
||||
(linuxplus-quiz--sync-current-position-from-point)
|
||||
|
||||
(let ((type
|
||||
(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 ()
|
||||
(interactive)
|
||||
|
||||
(linuxplus-quiz--sync-current-position-from-point)
|
||||
|
||||
(let ((type
|
||||
(linuxplus-quiz--question-type
|
||||
(linuxplus-quiz--current-question))))
|
||||
|
||||
(if
|
||||
(and
|
||||
(eq type 'multiple)
|
||||
(> 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."
|
||||
|
||||
(setq buffer-read-only t))
|
||||
|
||||
(defun linuxplus-start-quiz
|
||||
(lesson questions buffer)
|
||||
|
||||
"Start Linux+ quiz in full window."
|
||||
|
||||
(let ((return-config
|
||||
(current-window-configuration))
|
||||
|
||||
(return-buffer
|
||||
(current-buffer))
|
||||
|
||||
(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
|
||||
|
||||
144
102-user-management-lesson.el
Normal file
144
102-user-management-lesson.el
Normal file
@@ -0,0 +1,144 @@
|
||||
(defun linuxplus-user-management-lesson ()
|
||||
"CompTIA Linux+ lesson: user management with lesson window and ansi-term."
|
||||
(interactive)
|
||||
|
||||
(let ((lesson-buffer (get-buffer-create "*Linux+ User Management Lesson*"))
|
||||
(term-buffer-name "*Linux+ Terminal*")
|
||||
(shell-program (or shell-file-name "/bin/bash")))
|
||||
|
||||
;; Prepare lesson buffer
|
||||
(switch-to-buffer lesson-buffer)
|
||||
(read-only-mode -1)
|
||||
(erase-buffer)
|
||||
|
||||
(insert
|
||||
"Linux+ XK0-005 Lesson: User and Group Management
|
||||
|
||||
Study objectives:
|
||||
- Linux account files
|
||||
- User defaults
|
||||
- Group management
|
||||
- Account troubleshooting
|
||||
|
||||
|
||||
==================================================
|
||||
/etc/passwd
|
||||
==================================================
|
||||
|
||||
Stores:
|
||||
- username
|
||||
- UID
|
||||
- primary GID
|
||||
- home directory
|
||||
- shell
|
||||
|
||||
Does NOT store:
|
||||
- password hashes
|
||||
- password aging
|
||||
|
||||
|
||||
==================================================
|
||||
/etc/shadow
|
||||
==================================================
|
||||
|
||||
Stores:
|
||||
- password hashes
|
||||
- expiration
|
||||
- password aging
|
||||
|
||||
|
||||
Commands:
|
||||
|
||||
sudo chage -l $USER
|
||||
|
||||
|
||||
==================================================
|
||||
/etc/group
|
||||
==================================================
|
||||
|
||||
Stores group information.
|
||||
|
||||
Commands:
|
||||
|
||||
groups $USER
|
||||
|
||||
id $USER
|
||||
|
||||
|
||||
==================================================
|
||||
User defaults
|
||||
==================================================
|
||||
|
||||
Files:
|
||||
|
||||
/etc/default/useradd
|
||||
/etc/login.defs
|
||||
|
||||
|
||||
Try:
|
||||
|
||||
cat /etc/default/useradd
|
||||
|
||||
grep UID /etc/login.defs
|
||||
|
||||
|
||||
==================================================
|
||||
usermod exam trap
|
||||
==================================================
|
||||
|
||||
|
||||
SAFE:
|
||||
|
||||
sudo usermod -aG GROUP USER
|
||||
|
||||
|
||||
DANGEROUS:
|
||||
|
||||
sudo usermod -G GROUP USER
|
||||
|
||||
|
||||
Without -a:
|
||||
Existing supplementary groups are replaced.
|
||||
|
||||
|
||||
==================================================
|
||||
Rename groups
|
||||
==================================================
|
||||
|
||||
Syntax:
|
||||
|
||||
groupmod -n NEW OLD
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
sudo groupmod -n Development WIP
|
||||
|
||||
|
||||
==================================================
|
||||
|
||||
When finished:
|
||||
|
||||
M-x linuxplus-user-management-quiz
|
||||
|
||||
")
|
||||
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Create the study layout
|
||||
(delete-other-windows)
|
||||
|
||||
;; Top = lesson
|
||||
;; Bottom = terminal
|
||||
|
||||
(split-window-below)
|
||||
|
||||
(other-window 1)
|
||||
|
||||
;; Reuse terminal if already created
|
||||
(if (get-buffer term-buffer-name)
|
||||
(switch-to-buffer term-buffer-name)
|
||||
(ansi-term shell-program term-buffer-name))
|
||||
|
||||
;; Put cursor back in lesson
|
||||
(other-window -1)))
|
||||
52
103-user-management-quiz.el
Normal file
52
103-user-management-quiz.el
Normal file
@@ -0,0 +1,52 @@
|
||||
;;; 103-user-management-quiz.el --- user-management-quiz
|
||||
|
||||
(defun linuxplus-user-management-quiz ()
|
||||
"Quiz for Linux+ user and group management."
|
||||
(interactive)
|
||||
|
||||
(linuxplus-start-quiz
|
||||
'linuxplus-user-management-lesson
|
||||
|
||||
'((:type true-false
|
||||
:prompt "The /etc/passwd file stores password expiration and aging information."
|
||||
:answer false)
|
||||
|
||||
(:type true-false
|
||||
:prompt "The /etc/group and /etc/passwd files are useful when troubleshooting group membership issues."
|
||||
:answer true)
|
||||
|
||||
(:type true-false
|
||||
:prompt "Running usermod -G developers bob always preserves existing supplementary groups."
|
||||
:answer false)
|
||||
|
||||
(:type true-false
|
||||
:prompt "The command usermod -aG wheel bob appends bob to the wheel group."
|
||||
:answer true)
|
||||
|
||||
(:type true-false
|
||||
:prompt "The file /etc/login.defs can define UID ranges and password aging defaults."
|
||||
:answer true)
|
||||
|
||||
(:type true-false
|
||||
:prompt "The command groupmod -n NewName OldName renames a Linux group."
|
||||
:answer true)
|
||||
|
||||
(:type true-false
|
||||
:prompt "The x shown in /etc/passwd means the account has no password."
|
||||
:answer false)
|
||||
|
||||
(:type true-false
|
||||
:prompt "The id command can display UID, GID, and group memberships."
|
||||
:answer true)
|
||||
|
||||
(:type yes-no
|
||||
:prompt "Is a mobile authenticator generating one-time passwords an example of a software token?"
|
||||
:answer yes)
|
||||
|
||||
(:type yes-no
|
||||
:prompt "Should /etc/profile be checked first when verifying UID and group membership problems?"
|
||||
:answer no))
|
||||
|
||||
"*Linux+ User Management Quiz*"))
|
||||
|
||||
;;; 103-user-management-quiz.el ends here
|
||||
16
61-file-permissions-quiz.el
Normal file
16
61-file-permissions-quiz.el
Normal file
@@ -0,0 +1,16 @@
|
||||
;;; 61-file-permissions-quiz.el --- file-permissions-quiz
|
||||
|
||||
(defun file-permissions-quiz ()
|
||||
"Quiz for Linux file permissions and ownership."
|
||||
(interactive)
|
||||
(linuxplus-start-quiz
|
||||
'file-permissions-lesson
|
||||
'("Does chmod change file permissions?"
|
||||
"Does chown change ownership?"
|
||||
"Does 755 equal rwxr-xr-x?"
|
||||
"Does 644 equal rw-r--r--?"
|
||||
"Does execute permission on a directory allow traversal?")
|
||||
'(y y y y y)
|
||||
"*File Permissions Quiz*"))
|
||||
|
||||
;;; 61-file-permissions-quiz.el ends here
|
||||
63
70-process-management-lesson.el
Normal file
63
70-process-management-lesson.el
Normal file
@@ -0,0 +1,63 @@
|
||||
;;; 70-process-management-lesson.el --- process-management-lesson
|
||||
|
||||
(defun process-management-lesson ()
|
||||
"Linux+ lesson for process management and signals."
|
||||
(interactive)
|
||||
(delete-other-windows)
|
||||
(let ((buf (get-buffer-create "*Process Management Lesson*"))
|
||||
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
|
||||
(switch-to-buffer buf)
|
||||
(read-only-mode -1)
|
||||
(erase-buffer)
|
||||
(insert
|
||||
"Linux+ Lesson: Process Management and Signals\n"
|
||||
"============================================\n\n"
|
||||
"Concepts:\n"
|
||||
"- ps shows processes\n"
|
||||
"- top shows live processes\n"
|
||||
"- kill sends signals to processes\n"
|
||||
"- pkill kills by process name\n"
|
||||
"- jobs shows shell background jobs\n\n"
|
||||
"Important signals:\n"
|
||||
" SIGTERM = 15 = graceful termination\n"
|
||||
" SIGKILL = 9 = forced termination\n\n"
|
||||
"Hands-on tasks:\n\n"
|
||||
"1. View running processes:\n"
|
||||
" ps aux | less\n\n"
|
||||
"2. View live processes:\n"
|
||||
" top\n"
|
||||
" Press q to quit.\n\n"
|
||||
"3. Start a background process:\n"
|
||||
" sleep 300 &\n\n"
|
||||
"4. View background jobs:\n"
|
||||
" jobs\n\n"
|
||||
"5. Find the sleep process:\n"
|
||||
" ps aux | grep sleep\n\n"
|
||||
"6. Kill the process gracefully:\n"
|
||||
" kill PID\n\n"
|
||||
"7. Start another sleep process:\n"
|
||||
" sleep 300 &\n\n"
|
||||
"8. Kill it by name:\n"
|
||||
" pkill sleep\n\n"
|
||||
"9. Start another sleep process:\n"
|
||||
" sleep 300 &\n\n"
|
||||
"10. Kill with SIGKILL:\n"
|
||||
" kill -9 PID\n\n"
|
||||
"Linux+ exam traps:\n"
|
||||
"- kill sends signals, it does NOT always mean SIGKILL\n"
|
||||
"- kill default signal is SIGTERM (15)\n"
|
||||
"- pkill works by process name\n"
|
||||
"- ps aux is VERY important\n\n"
|
||||
"When finished, run:\n"
|
||||
" M-x process-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)))
|
||||
|
||||
;;; 70-process-management-lesson.el ends here
|
||||
16
71-process-management-quiz.el
Normal file
16
71-process-management-quiz.el
Normal file
@@ -0,0 +1,16 @@
|
||||
;;; 71-process-management-quiz.el --- process-management-quiz
|
||||
|
||||
(defun process-management-quiz ()
|
||||
"Quiz for Linux process management and signals."
|
||||
(interactive)
|
||||
(linuxplus-start-quiz
|
||||
'process-management-lesson
|
||||
'("Does ps display processes?"
|
||||
"Is SIGKILL signal 9?"
|
||||
"Does kill default to SIGTERM?"
|
||||
"Does pkill kill by process name?"
|
||||
"Does top show live process activity?")
|
||||
'(y y y y y)
|
||||
"*Process Management Quiz*"))
|
||||
|
||||
;;; 71-process-management-quiz.el ends here
|
||||
66
80-log-filtering-lesson.el
Normal file
66
80-log-filtering-lesson.el
Normal file
@@ -0,0 +1,66 @@
|
||||
;;; 80-log-filtering-lesson.el --- log-filtering-lesson
|
||||
|
||||
(defun log-filtering-lesson ()
|
||||
"Linux+ lesson for log filtering and grep chaining."
|
||||
(interactive)
|
||||
(delete-other-windows)
|
||||
(let ((buf (get-buffer-create "*Log Filtering Lesson*"))
|
||||
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
|
||||
(switch-to-buffer buf)
|
||||
(read-only-mode -1)
|
||||
(erase-buffer)
|
||||
(insert
|
||||
"Linux+ Lesson: Log Filtering and grep Chaining\n"
|
||||
"==============================================\n\n"
|
||||
"Concepts:\n"
|
||||
"- grep searches for patterns\n"
|
||||
"- pipes combine filters\n"
|
||||
"- chaining grep narrows results\n"
|
||||
"- logs contain noise and useful signals\n\n"
|
||||
"VERY IMPORTANT:\n"
|
||||
"This is WRONG:\n\n"
|
||||
" grep ssh fail logfile\n\n"
|
||||
"Why?\n"
|
||||
"- grep expects ONE pattern\n"
|
||||
"- 'fail' becomes a filename\n\n"
|
||||
"Correct approaches:\n\n"
|
||||
" grep ssh logfile | grep fail\n\n"
|
||||
"or:\n\n"
|
||||
" grep 'ssh.*fail\\|fail.*ssh' logfile\n\n"
|
||||
"Hands-on tasks:\n\n"
|
||||
"1. View recent journal entries:\n"
|
||||
" journalctl | head\n\n"
|
||||
"2. Search for ssh activity:\n"
|
||||
" journalctl | grep -i ssh\n\n"
|
||||
"3. Search for failures:\n"
|
||||
" journalctl | grep -i fail\n\n"
|
||||
"4. Chain filters together:\n"
|
||||
" journalctl | grep -i ssh | grep -i fail\n\n"
|
||||
"5. Search for invalid users:\n"
|
||||
" journalctl | grep -i invalid\n\n"
|
||||
"6. Search for authentication issues:\n"
|
||||
" journalctl | grep -i denied\n\n"
|
||||
"7. Compare broad vs narrow filtering:\n"
|
||||
" journalctl | grep -i ssh\n"
|
||||
" journalctl | grep -i ssh | grep -i fail\n\n"
|
||||
"What to notice:\n"
|
||||
"- first command = noise\n"
|
||||
"- second command = signal\n"
|
||||
"- chained grep reduces output size\n\n"
|
||||
"Linux+ exam traps:\n"
|
||||
"- grep takes ONE pattern unless combined explicitly\n"
|
||||
"- grep chaining is very common\n"
|
||||
"- logs are often filtered progressively\n\n"
|
||||
"When finished, run:\n"
|
||||
" M-x log-filtering-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)))
|
||||
|
||||
;;; 80-log-filtering-lesson.el ends here
|
||||
16
81-log-filtering-quiz.el
Normal file
16
81-log-filtering-quiz.el
Normal file
@@ -0,0 +1,16 @@
|
||||
;;; 81-log-filtering-quiz.el --- log-filtering-quiz
|
||||
|
||||
(defun log-filtering-quiz ()
|
||||
"Quiz for log filtering and grep chaining."
|
||||
(interactive)
|
||||
(linuxplus-start-quiz
|
||||
'log-filtering-lesson
|
||||
'("Does grep normally take one pattern?"
|
||||
"Does grep ssh fail logfile treat fail as a filename?"
|
||||
"Can grep chaining reduce noisy output?"
|
||||
"Does grep -i ignore case?"
|
||||
"Is grep ssh logfile | grep fail a valid filter chain?")
|
||||
'(y y y y y)
|
||||
"*Log Filtering Quiz*"))
|
||||
|
||||
;;; 81-log-filtering-quiz.el ends here
|
||||
83
90-file-search-lesson.el
Normal file
83
90-file-search-lesson.el
Normal file
@@ -0,0 +1,83 @@
|
||||
;;; 90-file-search-lesson.el --- file-search-lesson
|
||||
|
||||
(defun file-search-lesson ()
|
||||
"Linux+ lesson for finding files and commands."
|
||||
(interactive)
|
||||
(delete-other-windows)
|
||||
(let ((buf (get-buffer-create "*File Search Lesson*"))
|
||||
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
|
||||
(switch-to-buffer buf)
|
||||
(read-only-mode -1)
|
||||
(erase-buffer)
|
||||
|
||||
(insert
|
||||
"Linux+ Lesson: File Searching and Command Discovery\n"
|
||||
"==================================================\n\n"
|
||||
|
||||
"Concepts:\n"
|
||||
"- `find` searches the filesystem in real time\n"
|
||||
"- `locate` searches a database\n"
|
||||
"- `which` finds executable paths\n"
|
||||
"- `grep` filters output\n\n"
|
||||
|
||||
"VERY IMPORTANT:\n"
|
||||
"- `find` is slower but current\n"
|
||||
"- `locate` is faster but may be outdated\n\n"
|
||||
|
||||
"Hands-on tasks:\n\n"
|
||||
|
||||
"1. Create a practice directory:\n"
|
||||
" mkdir -p ~/linuxplus-search-test\n"
|
||||
" cd ~/linuxplus-search-test\n\n"
|
||||
|
||||
"2. Create practice files:\n"
|
||||
" touch alpha.txt beta.log gamma.conf\n\n"
|
||||
|
||||
"3. Find a specific file:\n"
|
||||
" find . -name alpha.txt\n\n"
|
||||
|
||||
"4. Find all .txt files:\n"
|
||||
" find . -name '*.txt'\n\n"
|
||||
|
||||
"5. Find directories only:\n"
|
||||
" find . -type d\n\n"
|
||||
|
||||
"6. Find files only:\n"
|
||||
" find . -type f\n\n"
|
||||
|
||||
"7. Search for a command location:\n"
|
||||
" which grep\n"
|
||||
" which systemctl\n\n"
|
||||
|
||||
"8. Use locate:\n"
|
||||
" locate passwd\n\n"
|
||||
|
||||
"9. If locate gives poor results:\n"
|
||||
" sudo updatedb\n\n"
|
||||
|
||||
"10. Combine find with grep:\n"
|
||||
" find /etc -name '*.conf' | grep ssh\n\n"
|
||||
|
||||
"Linux+ exam traps:\n"
|
||||
"- `find` searches live filesystem data\n"
|
||||
"- `locate` uses a database\n"
|
||||
"- `which` searches PATH for executables\n"
|
||||
"- `find` is commonly combined with pipes\n\n"
|
||||
|
||||
"When finished, run:\n"
|
||||
" M-x file-search-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)))
|
||||
|
||||
;;; 90-file-search-lesson.el ends here
|
||||
50
91-file-search-quiz.el
Normal file
50
91-file-search-quiz.el
Normal file
@@ -0,0 +1,50 @@
|
||||
;;; 91-file-search-quiz.el --- file-search-quiz
|
||||
|
||||
(defun file-search-quiz ()
|
||||
"Quiz for Linux file searching tools."
|
||||
(interactive)
|
||||
(linuxplus-start-quiz
|
||||
'file-search-lesson
|
||||
'((:type true-false
|
||||
:prompt "`find` searches the live filesystem while `locate` uses a database."
|
||||
:answer 'true)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`which grep` can display the path to the `grep` executable."
|
||||
:answer 'true)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`locate` always reflects the most current filesystem state."
|
||||
:answer 'false)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`find . -type f` searches only for files."
|
||||
:answer 'true)
|
||||
|
||||
(:type true-false
|
||||
:prompt "`find /etc -name '*.conf' | grep ssh` is a valid filter chain."
|
||||
:answer 'true)
|
||||
|
||||
(:type multiple
|
||||
:prompt "Which statements about Linux file-search tools are correct?"
|
||||
:choices ("`find` searches the live filesystem."
|
||||
"`locate` uses a database."
|
||||
"`which` searches PATH for executables."
|
||||
"All of the above.")
|
||||
:answers (0 1 2 3))
|
||||
|
||||
(:type multiple
|
||||
:prompt "Which statements are false?"
|
||||
:choices ("`locate` can show stale results if its database is outdated."
|
||||
"`which` searches the entire filesystem."
|
||||
"`find` requires an `updatedb` database before it works."
|
||||
"`find . -name '*.txt'` can search for text files below the current directory.")
|
||||
:answers (1 2))
|
||||
|
||||
(:type yes-no
|
||||
:prompt "Does `updatedb` refresh the database used by `locate`?"
|
||||
:answer 'yes))
|
||||
|
||||
"*File Search Quiz*"))
|
||||
|
||||
;;; 91-file-search-quiz.el ends here
|
||||
Reference in New Issue
Block a user