From f9ae4d37631c047dd253efff4ccca29b0d598598 Mon Sep 17 00:00:00 2001 From: "Scott C. MacCallum" Date: Tue, 21 Apr 2026 15:13:54 -0400 Subject: [PATCH] Adding files --- 00-linuxplus-lesson-history.el | 6 ++ 01-linuxplus-lesson-history-file.el | 7 ++ 02-linuxplus-save-lesson-history.el | 10 ++ 03-linuxplus-load-lesson-history.el | 8 ++ 04-linuxplus-record-score.el | 12 +++ 05-linuxplus-show-scores.el | 16 +++ 06-linuxplus-quiz-mode.el | 156 ++++++++++++++++++++++++++++ 10-ssh-service-lesson.el | 60 +++++++++++ 11-ssh-service-quiz.el | 15 +++ 20-top-cpu-fields-lesson.el | 14 +++ 21-top-cpu-fields-quiz.el | 15 +++ linuxplus-load-all.el | 16 +++ 12 files changed, 335 insertions(+) create mode 100644 00-linuxplus-lesson-history.el create mode 100644 01-linuxplus-lesson-history-file.el create mode 100644 02-linuxplus-save-lesson-history.el create mode 100644 03-linuxplus-load-lesson-history.el create mode 100644 04-linuxplus-record-score.el create mode 100644 05-linuxplus-show-scores.el create mode 100644 06-linuxplus-quiz-mode.el create mode 100644 10-ssh-service-lesson.el create mode 100644 11-ssh-service-quiz.el create mode 100644 20-top-cpu-fields-lesson.el create mode 100644 21-top-cpu-fields-quiz.el create mode 100644 linuxplus-load-all.el diff --git a/00-linuxplus-lesson-history.el b/00-linuxplus-lesson-history.el new file mode 100644 index 0000000..5a39acc --- /dev/null +++ b/00-linuxplus-lesson-history.el @@ -0,0 +1,6 @@ +;;; 00-linuxplus-lesson-history.el --- linuxplus-lesson-history + +(defvar linuxplus-lesson-history nil + "Alist of Linux+ lesson names and reported scores.") + +;;; 00-linuxplus-lesson-history.el ends here diff --git a/01-linuxplus-lesson-history-file.el b/01-linuxplus-lesson-history-file.el new file mode 100644 index 0000000..0bacf2c --- /dev/null +++ b/01-linuxplus-lesson-history-file.el @@ -0,0 +1,7 @@ +;;; 01-linuxplus-lesson-history-file.el --- linuxplus-lesson-history-file + +(defvar linuxplus-lesson-history-file + (expand-file-name "~/.emacs.d/linuxplus-lesson-history.el") + "File used to save `linuxplus-lesson-history` across Emacs sessions.") + +;;; 01-linuxplus-lesson-history-file.el ends here diff --git a/02-linuxplus-save-lesson-history.el b/02-linuxplus-save-lesson-history.el new file mode 100644 index 0000000..fdd0735 --- /dev/null +++ b/02-linuxplus-save-lesson-history.el @@ -0,0 +1,10 @@ +;;; 02-linuxplus-save-lesson-history.el --- linuxplus-save-lesson-history + +(defun linuxplus-save-lesson-history () + "Save lesson history to disk." + (with-temp-file linuxplus-lesson-history-file + (prin1 `(setq linuxplus-lesson-history ',linuxplus-lesson-history) + (current-buffer)) + (insert "\n"))) + +;;; 02-linuxplus-save-lesson-history.el ends here diff --git a/03-linuxplus-load-lesson-history.el b/03-linuxplus-load-lesson-history.el new file mode 100644 index 0000000..c4e88d7 --- /dev/null +++ b/03-linuxplus-load-lesson-history.el @@ -0,0 +1,8 @@ +;;; 03-linuxplus-load-lesson-history.el --- linuxplus-load-lesson-history + +(defun linuxplus-load-lesson-history () + "Load lesson history from disk." + (when (file-exists-p linuxplus-lesson-history-file) + (load-file linuxplus-lesson-history-file))) + +;;; 03-linuxplus-load-lesson-history.el ends here diff --git a/04-linuxplus-record-score.el b/04-linuxplus-record-score.el new file mode 100644 index 0000000..250d331 --- /dev/null +++ b/04-linuxplus-record-score.el @@ -0,0 +1,12 @@ +;;; 04-linuxplus-record-score.el --- linuxplus-record-score + +(defun linuxplus-record-score (lesson score) + "Record and persist lesson SCORE." + (let ((entry (assoc lesson linuxplus-lesson-history))) + (if entry + (setcdr entry score) + (push (cons lesson score) linuxplus-lesson-history))) + (linuxplus-save-lesson-history) + (message "Recorded %s score: %s" lesson score)) + +;;; 04-linuxplus-record-score.el ends here diff --git a/05-linuxplus-show-scores.el b/05-linuxplus-show-scores.el new file mode 100644 index 0000000..c70a66e --- /dev/null +++ b/05-linuxplus-show-scores.el @@ -0,0 +1,16 @@ +;;; 05-linuxplus-show-scores.el --- linuxplus-show-scores + +(defun linuxplus-show-scores () + "Display saved Linux+ lesson scores." + (interactive) + (with-current-buffer (get-buffer-create "*Linux+ Scores*") + (read-only-mode -1) + (erase-buffer) + (insert "Linux+ Lesson Scores\n====================\n\n") + (dolist (entry linuxplus-lesson-history) + (insert (format "%s: %s\n" (car entry) (cdr entry)))) + (goto-char (point-min)) + (view-mode 1) + (pop-to-buffer (current-buffer)))) + +;;; 05-linuxplus-show-scores.el ends here diff --git a/06-linuxplus-quiz-mode.el b/06-linuxplus-quiz-mode.el new file mode 100644 index 0000000..fb82d0b --- /dev/null +++ b/06-linuxplus-quiz-mode.el @@ -0,0 +1,156 @@ +;;; 06-linuxplus-quiz-mode.el --- linuxplus-quiz-mode + +(defvar-local linuxplus-quiz-questions nil + "Questions for the current Linux+ quiz buffer.") + +(defvar-local linuxplus-quiz-answers nil + "Correct answers for the current Linux+ quiz buffer.") + +(defvar-local linuxplus-quiz-user-answers nil + "User answers for the current Linux+ quiz buffer.") + +(defvar-local linuxplus-quiz-current-index 0 + "Current question index in the Linux+ quiz buffer.") + +(defvar-local linuxplus-quiz-lesson-name nil + "Lesson name associated with the current Linux+ quiz buffer.") + +(defun linuxplus-quiz-answer-marker (answer) + "Return a display marker string for ANSWER." + (cond + ((eq answer 'y) "[y]") + ((eq answer 'n) "[n]") + (t "[ ]"))) + +(defun linuxplus-quiz-answer-string (answer) + "Return a readable string for ANSWER." + (cond + ((eq answer 'y) "y") + ((eq answer 'n) "n") + (t "unanswered"))) + +(defun linuxplus-quiz-result-string (user-answer correct-answer) + "Return a readable result string comparing USER-ANSWER to CORRECT-ANSWER." + (if (eq user-answer correct-answer) + "correct" + "incorrect")) + +(defun linuxplus-quiz-render () + "Render the current Linux+ quiz buffer." + (let ((inhibit-read-only t)) + (erase-buffer) + (insert "Linux+ Quiz\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 () + "Finish the current quiz, record the score, and show detailed results." + (interactive) + (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 () + "Answer yes to the current Linux+ quiz question." + (interactive) + (setf (nth linuxplus-quiz-current-index linuxplus-quiz-user-answers) 'y) + (if (< linuxplus-quiz-current-index + (1- (length linuxplus-quiz-questions))) + (setq linuxplus-quiz-current-index + (1+ linuxplus-quiz-current-index)) + (linuxplus-quiz-render)) + (when (derived-mode-p 'linuxplus-quiz-mode) + (linuxplus-quiz-render))) + +(defun linuxplus-quiz-answer-n () + "Answer no to the current Linux+ quiz question." + (interactive) + (setf (nth linuxplus-quiz-current-index linuxplus-quiz-user-answers) 'n) + (if (< linuxplus-quiz-current-index + (1- (length linuxplus-quiz-questions))) + (setq linuxplus-quiz-current-index + (1+ linuxplus-quiz-current-index)) + (linuxplus-quiz-render)) + (when (derived-mode-p 'linuxplus-quiz-mode) + (linuxplus-quiz-render))) + +(defun linuxplus-quiz-next () + "Move to next question without changing answer." + (interactive) + (when (< linuxplus-quiz-current-index + (1- (length linuxplus-quiz-questions))) + (setq linuxplus-quiz-current-index + (1+ linuxplus-quiz-current-index)) + (linuxplus-quiz-render))) + +(defun linuxplus-quiz-prev () + "Move to previous question." + (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 + (let ((map (make-sparse-keymap))) + (define-key map (kbd "y") #'linuxplus-quiz-answer-y) + (define-key map (kbd "n") #'linuxplus-quiz-answer-n) + (define-key map (kbd "f") #'linuxplus-quiz-next) + (define-key map (kbd "b") #'linuxplus-quiz-prev) + (define-key map (kbd "x") #'linuxplus-quiz-finish) + (define-key map (kbd "q") #'quit-window) + map) + "Keymap for `linuxplus-quiz-mode`.") + +(define-derived-mode linuxplus-quiz-mode special-mode "LinuxPlus-Quiz" + "Major mode for Linux+ quiz buffers." + (setq buffer-read-only t)) + +(defun linuxplus-start-quiz (lesson questions answers buffer) + "Start a Linux+ quiz for LESSON using QUESTIONS and ANSWERS in BUFFER." + (let ((buf (get-buffer-create buffer))) + (with-current-buffer buf + (linuxplus-quiz-mode) + (setq linuxplus-quiz-lesson-name lesson) + (setq linuxplus-quiz-questions questions) + (setq linuxplus-quiz-answers answers) + (setq linuxplus-quiz-user-answers (make-list (length questions) nil)) + (setq linuxplus-quiz-current-index 0) + (linuxplus-quiz-render)) + (pop-to-buffer buf))) + +;;; 06-linuxplus-quiz-mode.el ends here diff --git a/10-ssh-service-lesson.el b/10-ssh-service-lesson.el new file mode 100644 index 0000000..4b85703 --- /dev/null +++ b/10-ssh-service-lesson.el @@ -0,0 +1,60 @@ +;;; 10-ssh-service-lesson.el --- ssh-service-lesson + +(defun ssh-service-lesson () + "Hands-on Linux+ lesson for managing the SSH service with systemctl." + (interactive) + (delete-other-windows) + (let ((lesson-buffer (get-buffer-create "*SSH Service Lesson*")) + (shell (or shell-file-name (getenv "SHELL") "/bin/sh"))) + (switch-to-buffer lesson-buffer) + (read-only-mode -1) + (erase-buffer) + (insert + "Linux+ Lesson: SSH Service Management\n" + "====================================\n\n" + "Concept:\n" + "Learn the difference between starting/stopping a service now and\n" + "enabling/disabling it for boot.\n\n" + "VM requirement:\n" + "- Use a systemd-based Linux VM.\n" + "- Debian/Ubuntu usually use service name: ssh\n" + "- Fedora/Rocky/Alma/RHEL usually use service name: sshd\n\n" + "In the terminal below, determine whether your VM uses ssh or sshd:\n" + " systemctl status ssh\n" + " systemctl status sshd\n\n" + "Use the correct service name for the rest of the lesson.\n\n" + "Hands-on tasks:\n" + "A. Check status:\n" + " sudo systemctl status \n\n" + "B. Stop the service:\n" + " sudo systemctl stop \n" + " Then check status again.\n\n" + "C. Start the service:\n" + " sudo systemctl start \n" + " Then check status again.\n\n" + "D. Disable the service:\n" + " sudo systemctl disable \n\n" + "E. Re-enable the service:\n" + " sudo systemctl enable \n\n" + "What to notice:\n" + "- start = runs now\n" + "- stop = stops now\n" + "- enable = starts at boot\n" + "- disable = does not start at boot\n\n" + "Important Linux+ trap:\n" + "A service can be enabled but not currently running.\n" + "If so, use:\n" + " systemctl start \n\n" + "When you finish the hands-on portion, run:\n" + " M-x ssh-service-quiz\n") + (goto-char (point-min)) + (view-mode 1) + (split-window-below) + (other-window 1) + (let ((term-buffer (get-buffer "*term*"))) + (if (buffer-live-p term-buffer) + (switch-to-buffer term-buffer) + (ansi-term shell "term"))) + (other-window -1))) + +;;; 10-ssh-service-lesson.el ends here diff --git a/11-ssh-service-quiz.el b/11-ssh-service-quiz.el new file mode 100644 index 0000000..a1f5b1c --- /dev/null +++ b/11-ssh-service-quiz.el @@ -0,0 +1,15 @@ +;;; 11-ssh-service-quiz.el --- ssh-service-quiz + +(defun ssh-service-quiz () + (interactive) + (linuxplus-start-quiz + 'ssh-service-lesson + '("Does start run now?" + "Does enable run at boot?" + "Does disable stop running service?" + "Use start if enabled but stopped?" + "Are enable and start the same?") + '(y y n y n) + "*SSH Quiz*")) + +;;; 11-ssh-service-quiz.el ends here diff --git a/20-top-cpu-fields-lesson.el b/20-top-cpu-fields-lesson.el new file mode 100644 index 0000000..c454946 --- /dev/null +++ b/20-top-cpu-fields-lesson.el @@ -0,0 +1,14 @@ +;;; 20-top-cpu-fields-lesson.el --- top-cpu-fields-lesson + +(defun top-cpu-fields-lesson () + (interactive) + (delete-other-windows) + (switch-to-buffer "*Top Lesson*") + (erase-buffer) + (insert "Use 'top' and observe CPU fields.\n") + (split-window-below) + (other-window 1) + (ansi-term (getenv "SHELL") "term") + (other-window -1)) + +;;; 20-top-cpu-fields-lesson.el ends here diff --git a/21-top-cpu-fields-quiz.el b/21-top-cpu-fields-quiz.el new file mode 100644 index 0000000..17799d5 --- /dev/null +++ b/21-top-cpu-fields-quiz.el @@ -0,0 +1,15 @@ +;;; 21-top-cpu-fields-quiz.el --- top-cpu-fields-quiz + +(defun top-cpu-fields-quiz () + (interactive) + (linuxplus-start-quiz + 'top-cpu-fields-lesson + '("Does wa indicate disk?" + "Does st indicate VM?" + "Does id mean idle?" + "Does us mean user?" + "Does sy mean system?") + '(y y y y y) + "*Top Quiz*")) + +;;; 21-top-cpu-fields-quiz.el ends here diff --git a/linuxplus-load-all.el b/linuxplus-load-all.el new file mode 100644 index 0000000..a338f09 --- /dev/null +++ b/linuxplus-load-all.el @@ -0,0 +1,16 @@ +;;; linuxplus-load-all.el --- linuxplus-load-all + +(require 'seq) + +(defvar linuxplus-base-directory + (file-name-directory (or load-file-name buffer-file-name))) + +(defun linuxplus-load-all () + (interactive) + (dolist (f (sort (directory-files linuxplus-base-directory t "^[0-9].*\\.el$") + #'string-lessp)) + (load-file f)) + (linuxplus-load-lesson-history) + (message "Linux+ loaded.")) + +;;; linuxplus-load-all.el ends here