(eval-when-compile
(require 'tempo))
(defgroup snmp nil
"Mode for editing SNMP MIB files."
:group 'data
:version "20.4")
(defcustom snmp-special-indent t
"*If non-nil, use a simple heuristic to try to guess the right indentation.
If nil, then no special indentation is attempted."
:type 'boolean
:group 'snmp)
(defcustom snmp-indent-level 4
"*Indentation level for SNMP MIBs."
:type 'integer
:group 'snmp)
(defcustom snmp-tab-always-indent nil
"*Non-nil means TAB should always reindent the current line.
A value of nil means reindent if point is within the initial line indentation;
otherwise insert a TAB."
:type 'boolean
:group 'snmp)
(defcustom snmp-completion-ignore-case t
"*Non-nil means that case differences are ignored during completion.
A value of nil means that case is significant.
This is used during Tempo template completion."
:type 'boolean
:group 'snmp)
(defcustom snmp-common-mode-hook nil
"*Hook(s) evaluated when a buffer enters either SNMP or SNMPv2 mode."
:type 'hook
:group 'snmp)
(defcustom snmp-mode-hook nil
"*Hook(s) evaluated when a buffer enters SNMP mode."
:type 'hook
:group 'snmp)
(defcustom snmpv2-mode-hook nil
"*Hook(s) evaluated when a buffer enters SNMPv2 mode."
:type 'hook
:group 'snmp)
(defvar snmp-tempo-tags nil
"*Tempo tags for SNMP mode.")
(defvar snmpv2-tempo-tags nil
"*Tempo tags for SNMPv2 mode.")
(defvar snmp-font-lock-keywords-1
(list
'("^[ \t]*\\([a-z][-a-zA-Z0-9]+\\)[ \t]+\\(\\(MODULE-\\(COMPLIANCE\\|IDENTITY\\)\\|OBJECT-\\(COMPLIANCE\\|GROUP\\|IDENTITY\\|TYPE\\)\\|TRAP-\\(GROUP\\|TYPE\\)\\)\\|\\(OBJECT\\)[ \t]+\\(IDENTIFIER\\)[ \t]*::=\\)"
(1 font-lock-variable-name-face) (3 font-lock-keyword-face nil t)
(7 font-lock-keyword-face nil t) (8 font-lock-keyword-face nil t))
'("^[ \t]*\\([A-Z][-a-zA-Z0-9]+\\)[ \t]+\\(DEFINITIONS\\)[ \t]*::="
(1 font-lock-function-name-face) (2 font-lock-keyword-face))
)
"Basic SNMP MIB mode expression highlighting.")
(defvar snmp-font-lock-keywords-2
(append
'(("ACCESS\\|BEGIN\\|DE\\(FVAL\\|SCRIPTION\\)\\|END\\|FROM\\|I\\(MPORTS\\|NDEX\\)\\|S\\(TATUS\\|YNTAX\\)"
(0 font-lock-keyword-face)))
snmp-font-lock-keywords-1)
"Medium SNMP MIB mode expression highlighting.")
(defvar snmp-font-lock-keywords-3
(append
'(("\\([^\n]+\\)[ \t]+::=[ \t]+\\(SEQUENCE\\)[ \t]+{"
(1 font-lock-reference-face) (2 font-lock-keyword-face))
("::=[ \t]*{[ \t]*\\([a-z0-9].*[ \t]+\\)?\\([0-9]+\\)[ \t]*}"
(1 font-lock-reference-face nil t) (2 font-lock-variable-name-face)))
snmp-font-lock-keywords-2)
"Gaudy SNMP MIB mode expression highlighting.")
(defvar snmp-font-lock-keywords snmp-font-lock-keywords-1
"Default SNMP MIB mode expression highlighting.")
(defvar snmp-mode-syntax-list nil
"Predefined types for SYNTAX clauses.")
(defvar snmp-rfc1155-types
'(("INTEGER") ("OCTET STRING") ("OBJECT IDENTIFIER") ("NULL") ("IpAddress")
("NetworkAddress") ("Counter") ("Gauge") ("TimeTicks") ("Opaque"))
"Types from RFC 1155 v1 SMI.")
(defvar snmp-rfc1213-types
'(("DisplayString"))
"Types from RFC 1213 MIB-II.")
(defvar snmp-rfc1902-types
'(("INTEGER") ("OCTET STRING") ("OBJECT IDENTIFIER") ("Integer32")
("IpAddress") ("Counter32") ("Gauge32") ("Unsigned32") ("TimeTicks")
("Opaque") ("Counter64"))
"Types from RFC 1902 v2 SMI.")
(defvar snmp-rfc1903-types
'(("DisplayString") ("PhysAddress") ("MacAddress") ("TruthValue")
("TestAndIncr") ("AutonomousType") ("InstancePointer")
("VariablePointer") ("RowPointer") ("RowStatus") ("TimeStamp")
("TimeInterval") ("DateAndTime") ("StorageType") ("TDomain")
("TAddress"))
"Types from RFC 1903 Textual Conventions.")
(defvar snmp-mode-access-list nil
"Predefined values for ACCESS clauses.")
(defvar snmp-rfc1155-access
'(("read-only") ("read-write") ("write-only") ("not-accessible"))
"ACCESS values from RFC 1155 v1 SMI.")
(defvar snmp-rfc1902-access
'(("read-only") ("read-write") ("read-create") ("not-accessible")
("accessible-for-notify"))
"ACCESS values from RFC 1155 v1 SMI.")
(defvar snmp-mode-status-list nil
"Predefined values for STATUS clauses.")
(defvar snmp-rfc1212-status
'(("mandatory") ("obsolete") ("deprecated"))
"STATUS values from RFC 1212 v1 SMI.")
(defvar snmp-rfc1902-status
'(("current") ("obsolete") ("deprecated"))
"STATUS values from RFC 1902 v2 SMI.")
(eval-when-compile
(require 'cl)
(require 'imenu))
(defvar snmp-mode-abbrev-table nil
"Abbrev table in use in SNMP mode.")
(define-abbrev-table 'snmp-mode-abbrev-table ())
(defvar snmpv2-mode-abbrev-table nil
"Abbrev table in use in SNMPv2 mode.")
(define-abbrev-table 'snmpv2-mode-abbrev-table ())
(defvar snmp-mode-map (make-sparse-keymap)
"Keymap used in SNMP mode.")
(define-key snmp-mode-map "\t" 'snmp-indent-command)
(define-key snmp-mode-map "\177" 'backward-delete-char-untabify)
(define-key snmp-mode-map "\C-c\C-i" 'tempo-complete-tag)
(define-key snmp-mode-map "\C-c\C-f" 'tempo-forward-mark)
(define-key snmp-mode-map "\C-c\C-b" 'tempo-backward-mark)
(defvar snmp-mode-syntax-table nil
"Syntax table used for buffers in SNMP mode.")
(if snmp-mode-syntax-table
()
(setq snmp-mode-syntax-table (make-syntax-table))
(modify-syntax-entry ?\\ "\\" snmp-mode-syntax-table)
(modify-syntax-entry ?- "_ 1234" snmp-mode-syntax-table)
(modify-syntax-entry ?\n ">" snmp-mode-syntax-table)
(modify-syntax-entry ?\^m ">" snmp-mode-syntax-table)
(modify-syntax-entry ?_ "." snmp-mode-syntax-table)
(modify-syntax-entry ?: "." snmp-mode-syntax-table)
(modify-syntax-entry ?= "." snmp-mode-syntax-table))
(defun snmp-common-mode (name mode abbrev font-keywords imenu-index tempo-tags)
(kill-all-local-variables)
(setq mode-name name)
(setq major-mode mode)
(use-local-map snmp-mode-map)
(set-syntax-table snmp-mode-syntax-table)
(setq local-abbrev-table abbrev)
(make-local-variable 'paragraph-start)
(setq paragraph-start (concat "$\\|" page-delimiter))
(make-local-variable 'paragraph-separate)
(setq paragraph-separate paragraph-start)
(make-local-variable 'paragraph-ignore-fill-prefix)
(setq paragraph-ignore-fill-prefix t)
(make-local-variable 'comment-start)
(setq comment-start "-- ")
(make-local-variable 'comment-start-skip)
(setq comment-start-skip "--+[ \t]*")
(make-local-variable 'comment-column)
(setq comment-column 40)
(make-local-variable 'parse-sexp-ignore-comments)
(setq parse-sexp-ignore-comments t)
(make-local-variable 'indent-line-function)
(setq indent-line-function (if snmp-special-indent
'snmp-indent-line
'indent-to-left-margin))
(make-local-variable 'font-lock-defaults)
(setq font-lock-defaults (cons font-keywords '(nil nil ((?- . "w 1234")))))
(make-local-variable 'imenu-create-index-function)
(setq imenu-create-index-function imenu-index)
(tempo-use-tag-list tempo-tags)
(make-local-variable 'tempo-match-finder)
(setq tempo-match-finder "\\b\\(.+\\)\\=")
(make-local-variable 'tempo-interactive)
(setq tempo-interactive t)
(make-local-variable 'require-final-newline)
(setq require-final-newline mode-require-final-newline))
(defun snmp-mode ()
"Major mode for editing SNMP MIBs.
Expression and list commands understand all C brackets.
Tab indents for C code.
Comments start with -- and end with newline or another --.
Delete converts tabs to spaces as it moves back.
\\{snmp-mode-map}
Turning on snmp-mode runs the hooks in `snmp-common-mode-hook', then
`snmp-mode-hook'."
(interactive)
(snmp-common-mode "SNMP" 'snmp-mode
snmp-mode-abbrev-table
'(snmp-font-lock-keywords
snmp-font-lock-keywords-1
snmp-font-lock-keywords-2
snmp-font-lock-keywords-3)
'snmp-mode-imenu-create-index
'snmp-tempo-tags)
(make-local-variable 'snmp-mode-syntax-list)
(setq snmp-mode-syntax-list (append snmp-rfc1155-types
snmp-rfc1213-types
snmp-mode-syntax-list))
(make-local-variable 'snmp-mode-access-list)
(setq snmp-mode-access-list snmp-rfc1155-access)
(make-local-variable 'snmp-mode-status-list)
(setq snmp-mode-status-list snmp-rfc1212-status)
(run-mode-hooks 'snmp-common-mode-hook 'snmp-mode-hook))
(defun snmpv2-mode ()
"Major mode for editing SNMPv2 MIBs.
Expression and list commands understand all C brackets.
Tab indents for C code.
Comments start with -- and end with newline or another --.
Delete converts tabs to spaces as it moves back.
\\{snmp-mode-map}
Turning on snmp-mode runs the hooks in `snmp-common-mode-hook',
then `snmpv2-mode-hook'."
(interactive)
(snmp-common-mode "SNMPv2" 'snmpv2-mode
snmpv2-mode-abbrev-table
'(snmp-font-lock-keywords
snmp-font-lock-keywords-1
snmp-font-lock-keywords-2
snmp-font-lock-keywords-3)
'snmp-mode-imenu-create-index
'snmpv2-tempo-tags)
(make-local-variable 'snmp-mode-syntax-list)
(setq snmp-mode-syntax-list (append snmp-rfc1902-types
snmp-rfc1903-types
snmp-mode-syntax-list))
(make-local-variable 'snmp-mode-access-list)
(setq snmp-mode-access-list snmp-rfc1902-access)
(make-local-variable 'snmp-mode-status-list)
(setq snmp-mode-status-list snmp-rfc1902-status)
(run-mode-hooks 'snmp-common-mode-hook 'snmpv2-mode-hook))
(defvar snmp-macro-open
"[a-zA-Z][-a-zA-Z0-9]*[ \t]*\\(OBJECT\\|TRAP\\)-\\(TYPE\\|GROUP\\)\
\\|DESCRIPTION\\|IMPORTS\\|MODULE\\(-IDENTITY\\|-COMPLIANCE\\)\
\\|.*::=[ \t]*\\(BEGIN\\|TEXTUAL-CONVENTION\\)[ \t]*$")
(defvar snmp-macro-close
"::=[ \t]*{\\|\\(END\\|.*[;\"]\\)[ \t]*$")
(defun snmp-calculate-indent ()
"Calculate the current line indentation in SNMP MIB code.
We use a very simple scheme: if the previous non-empty line was a \"macro
open\" string, add `snmp-indent-level' to it. If it was a \"macro close\"
string, subtract `snmp-indent-level'. Otherwise, use the same indentation
as the previous non-empty line. Note comments are considered empty
lines for the purposes of this function."
(let ((empty (concat "\\([ \t]*\\)\\(" comment-start-skip "\\|$\\)"))
(case-fold-search nil)) (save-excursion
(while (and (>= (forward-line -1) 0)
(looking-at empty)))
(skip-chars-forward " \t")
(+ (current-column)
(cond ((looking-at snmp-macro-open)
snmp-indent-level)
((looking-at snmp-macro-close)
(- snmp-indent-level))
(t 0))))))
(defun snmp-indent-line ()
"Indent current line as SNMP MIB code."
(let ((indent (snmp-calculate-indent))
(pos (- (point-max) (point)))
shift-amt beg end)
(beginning-of-line)
(setq beg (point))
(skip-chars-forward " \t")
(setq shift-amt (- indent (current-column)))
(if (zerop shift-amt)
nil
(delete-region beg (point))
(indent-to indent))
(if (> (- (point-max) pos) (point))
(goto-char (- (point-max) pos)))))
(defun snmp-indent-command ()
"Indent current line as SNMP MIB code, or sometimes insert a TAB.
If `snmp-tab-always-indent' is t, always reindent the current line when
this command is run.
If `snmp-tab-always-indent' is nil, reindent the current line if point is
in the initial indentation. Otherwise, insert a TAB."
(interactive)
(if (and (not snmp-tab-always-indent)
(save-excursion
(skip-chars-backward " \t")
(not (bolp))))
(insert-tab)
(snmp-indent-line)))
(defvar snmp-clause-regexp
"^[ \t]*\\([a-zA-Z][-a-zA-Z0-9]*\\)[ \t\n]*\
\\(TRAP-TYPE\\|::=\\|OBJECT\\(-TYPE[ \t\n]+SYNTAX\\|[ \t\n]+IDENTIFIER[ \t\n]*::=\\)\\)")
(defun snmp-mode-imenu-create-index ()
(let ((index-alist '())
(index-oid-alist '())
(index-tc-alist '())
(index-table-alist '())
(index-trap-alist '())
(case-fold-search nil) prev-pos token marker end)
(goto-char (point-min))
(imenu-progress-message prev-pos 0)
(save-match-data
(while (re-search-forward snmp-clause-regexp nil t)
(imenu-progress-message prev-pos)
(setq
end (match-end 0)
token (cons (buffer-substring (match-beginning 1) (match-end 1))
(set-marker (make-marker) (match-beginning 1))))
(goto-char (match-beginning 2))
(cond ((looking-at "OBJECT-TYPE[ \t\n]+SYNTAX")
(push token index-alist))
((looking-at "OBJECT[ \t\n]+IDENTIFIER[ \t\n]*::=")
(push token index-oid-alist))
((looking-at "::=[ \t\n]*SEQUENCE[ \t\n]*{")
(push token index-table-alist))
((looking-at "TRAP-TYPE")
(push token index-trap-alist))
((looking-at "::=")
(push token index-tc-alist)))
(goto-char end)))
(imenu-progress-message prev-pos 100)
(setq index-alist (nreverse index-alist))
(and index-tc-alist
(push (cons "Textual Conventions" (nreverse index-tc-alist))
index-alist))
(and index-trap-alist
(push (cons "Traps" (nreverse index-trap-alist))
index-alist))
(and index-table-alist
(push (cons "Tables" (nreverse index-table-alist))
index-alist))
(and index-oid-alist
(push (cons "Object IDs" (nreverse index-oid-alist))
index-alist))
index-alist))
(require 'tempo)
(defun snmp-completing-read (prompt table &optional pred require init hist)
"Read from the minibuffer, with completion.
Like `completing-read', but the variable `snmp-completion-ignore-case'
controls whether case is significant."
(let ((completion-ignore-case snmp-completion-ignore-case))
(completing-read prompt table pred require init hist)))
(tempo-define-template "snmp-object-type"
'(> (P "Object Label: ") " OBJECT-TYPE" n>
"SYNTAX "
(if tempo-interactive
(snmp-completing-read "Syntax: " snmp-mode-syntax-list nil nil)
p) n>
"ACCESS "
(if tempo-interactive
(snmp-completing-read "Access: " snmp-mode-access-list nil t)
p) n>
"STATUS "
(if tempo-interactive
(snmp-completing-read "Status: " snmp-mode-status-list nil t)
p) n>
"DESCRIPTION" n> "\"" p "\"" n>
(P "Default Value: " defval t)
(if (string= "" (tempo-lookup-named 'defval))
nil
'(l "DEFVAL { " (s defval) " }" n>))
"::= { " (p "OID: ") " }" n)
"objectType"
"Insert an OBJECT-TYPE macro."
'snmp-tempo-tags)
(tempo-define-template "snmp-table-type"
'(> (P "Table Name: " table)
(P "Entry Name: " entry t)
(let* ((entry (tempo-lookup-named 'entry))
(seq (copy-sequence entry)))
(aset entry 0 (downcase (aref entry 0)))
(aset seq 0 (upcase (aref seq 0)))
(tempo-save-named 'obj-entry entry)
(tempo-save-named 'seq-entry seq)
nil)
" OBJECT-TYPE" n>
"SYNTAX SEQUENCE OF "
(s seq-entry) n>
"ACCESS not-accessible" n>
"STATUS mandatory" n>
"DESCRIPTION" n> "\"" p "\"" n>
"::= { " (p "OID: ") " }" n n>
(s obj-entry) " OBJECT-TYPE" n>
"SYNTAX " (s seq-entry) n>
"ACCESS not-accessible" n>
"STATUS mandatory" n>
"DESCRIPTION" n> "\"" p "\"" n>
"INDEX { " (p "Index List: ") " }" n>
"::= {" (s table) " 1 }" n n>
(s seq-entry) " ::= SEQUENCE {" n> p n> "}" n)
"tableType"
"Insert an SNMP table."
'snmp-tempo-tags)
(tempo-define-template "snmpv2-object-type"
'(> (P "Object Label: ") " OBJECT-TYPE" n>
"SYNTAX "
(if tempo-interactive
(snmp-completing-read "Syntax: " snmp-mode-syntax-list nil nil)
p) n>
"MAX-ACCESS "
(if tempo-interactive
(snmp-completing-read "Max Access: " snmp-mode-access-list nil t)
p) n>
"STATUS "
(if tempo-interactive
(snmp-completing-read "Status: " snmp-mode-status-list nil t)
p) n>
"DESCRIPTION" n> "\"" p "\"" n>
(P "Default Value: " defval t)
(if (string= "" (tempo-lookup-named 'defval))
nil
'(l "DEFVAL { " (s defval) " }" n>))
"::= { " (p "OID: ") " }" n)
"objectType"
"Insert an v2 SMI OBJECT-TYPE macro."
'snmpv2-tempo-tags)
(tempo-define-template "snmpv2-table-type"
'(> (P "Table Name: " table)
(P "Entry Name: " entry t)
(let* ((entry (tempo-lookup-named 'entry))
(seq (copy-sequence entry)))
(aset entry 0 (downcase (aref entry 0)))
(aset seq 0 (upcase (aref seq 0)))
(tempo-save-named 'obj-entry entry)
(tempo-save-named 'seq-entry seq)
nil)
" OBJECT-TYPE" n>
"SYNTAX SEQUENCE OF "
(s seq-entry) n>
"MAX-ACCESS not-accessible" n>
"STATUS current" n>
"DESCRIPTION" n> "\"" p "\"" n>
"::= { " (p "OID: ") " }" n n>
(s obj-entry) " OBJECT-TYPE" n>
"SYNTAX " (s seq-entry) n>
"MAX-ACCESS not-accessible" n>
"STATUS current" n>
"DESCRIPTION" n> "\"" p "\"" n>
"INDEX { " (p "Index List: ") " }" n>
"::= { " (s table) " 1 }" n n>
(s seq-entry) " ::= SEQUENCE {" n> p n> "}" n)
"tableType"
"Insert an v2 SMI SNMP table."
'snmpv2-tempo-tags)
(tempo-define-template "snmpv2-textual-convention"
'(> (P "Texual Convention Type: ") " ::= TEXTUAL-CONVENTION" n>
"STATUS "
(if tempo-interactive
(snmp-completing-read "Status: " snmp-mode-status-list nil t)
p) n>
"DESCRIPTION" n> "\"" p "\"" n>
"SYNTAX "
(if tempo-interactive
(snmp-completing-read "Syntax: " snmp-mode-syntax-list nil nil)
p) n> )
"textualConvention"
"Insert an v2 SMI TEXTUAL-CONVENTION macro."
'snmpv2-tempo-tags)
(provide 'snmp-mode)