Adding files

This commit is contained in:
Scott C. MacCallum
2026-04-21 15:13:54 -04:00
commit f9ae4d3763
12 changed files with 335 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

156
06-linuxplus-quiz-mode.el Normal file
View File

@@ -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

60
10-ssh-service-lesson.el Normal file
View File

@@ -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 <service>\n\n"
"B. Stop the service:\n"
" sudo systemctl stop <service>\n"
" Then check status again.\n\n"
"C. Start the service:\n"
" sudo systemctl start <service>\n"
" Then check status again.\n\n"
"D. Disable the service:\n"
" sudo systemctl disable <service>\n\n"
"E. Re-enable the service:\n"
" sudo systemctl enable <service>\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 <service>\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

15
11-ssh-service-quiz.el Normal file
View File

@@ -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

View File

@@ -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

15
21-top-cpu-fields-quiz.el Normal file
View File

@@ -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

16
linuxplus-load-all.el Normal file
View File

@@ -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