~manuel-uberti/flymake-proselint

47b7618b35a94a6452b82779c9d60950c7c7ea12 — Philip Kaludercic 2 years ago 09b5a7a
Allow for file local Proselint configurations

* flymake-proselint.el:
(flymake-proselint-max-errors): Add new option.
(flymake-proselint-enable): Add new option.
(flymake-proselint-disable): Add new option.
(flymake-proselint-config-directory): Add new option.
(flymake-proselint-generate-configuration): Add new function.
(flymake-proselint-backend): Use 'flymake-proselint-generate-configuration'.
1 files changed, 196 insertions(+), 1 deletions(-)

M flymake-proselint.el
M flymake-proselint.el => flymake-proselint.el +196 -1
@@ 34,6 34,7 @@
  (require 'subr-x)
  (require 'pcase))
(require 'flymake)
(require 'xdg)

(defgroup flymake-proselint ()
  "Flymake backend for proselint."


@@ 50,6 51,197 @@ The following %-sequences are replaced:
  %c - the error code"
  :type 'string)

(defconst flymake-proselint--options
  ;; Regenerate using:
  ;;
  ;; (with-temp-buffer
  ;;   (call-process "proselint" nil t nil "--dump-config")
  ;;   (goto-char (point-min))
  ;;   (let ((config (json-parse-buffer :object-type 'alist)))
  ;;     (mapcar #'car (alist-get 'checks config))))
  '(airlinese.misc
    annotations.misc
    archaism.misc
    cliches.hell
    cliches.misc
    consistency.spacing
    consistency.spelling
    corporate_speak.misc
    cursing.filth
    cursing.nfl
    cursing.nword
    dates_times.am_pm
    dates_times.dates
    hedging.misc
    hyperbole.misc
    jargon.misc
    lexical_illusions.misc
    lgbtq.offensive_terms
    lgbtq.terms
    links.broken
    malapropisms.misc
    misc.apologizing
    misc.back_formations
    misc.bureaucratese
    misc.but
    misc.capitalization
    misc.chatspeak
    misc.commercialese
    misc.composition
    misc.currency
    misc.debased
    misc.false_plurals
    misc.illogic
    misc.inferior_superior
    misc.institution_name
    misc.latin
    misc.many_a
    misc.metaconcepts
    misc.metadiscourse
    misc.narcissism
    misc.not_guilty
    misc.phrasal_adjectives
    misc.preferred_forms
    misc.pretension
    misc.professions
    misc.punctuation
    misc.scare_quotes
    misc.suddenly
    misc.tense_present
    misc.waxed
    misc.whence
    mixed_metaphors.misc
    mondegreens.misc
    needless_variants.misc
    nonwords.misc
    oxymorons.misc
    psychology.misc
    redundancy.misc
    redundancy.ras_syndrome
    security.credit_card
    security.password
    sexism.misc
    skunked_terms.misc
    spelling.able_atable
    spelling.able_ible
    spelling.athletes
    spelling.em_im_en_in
    spelling.er_or
    spelling.in_un
    spelling.misc
    terms.animal_adjectives
    terms.denizen_labels
    terms.eponymous_adjectives
    terms.venery
    typography.diacritical_marks
    typography.exclamation
    typography.symbols
    uncomparables.misc
    weasel_words.misc
    weasel_words.very)
  "List of Proselint options.")

(defconst flymake-proselint--custom-type
  `(set :greedy t
	,@(mapcar
	   (lambda (opt)
	     ;; TODO: Add a :tag
	     `(const ,opt))
	   flymake-proselint--options))
  "Custom option type for proselint configurations.")

(defun flymake-proselint-safe-option-p (val)
  "Check if VAL is a safe (and valid) local value."
  (and (listp val)
       (catch 'fail
	 (dolist (elem val)
	   (unless (memq elem flymake-proselint--options)
	     (throw 'fail nil)))
	 t)))

(defcustom flymake-proselint-max-errors 1000
  "After how many errors should Proselint give up?
For this option to work, the user option
`flymake-proselint-config-directory' must be non-nil."
  :safe #'natnump
  :type 'natnum)

(defcustom flymake-proselint-enable '()
  "List of Proselint options to enable.
See `flymake-proselint--options' for a list of possible options.
For this option to work, the user option
`flymake-proselint-config-directory' must be non-nil."
  :safe #'flymake-proselint-safe-option-p
  :type flymake-proselint--custom-type)

(defcustom flymake-proselint-disable '()
  "List of Proselint options to disable.
See `flymake-proselint--options' for a list of possible options.
For this option to work, the user option
`flymake-proselint-config-directory' must be non-nil."
  :safe #'flymake-proselint-safe-option-p
  :type flymake-proselint--custom-type)

(defcustom flymake-proselint-config-directory
  (when-let* ((parent (or (xdg-cache-home) (temporary-file-directory)))
              (dir (expand-file-name "flymake-proselint" parent)))
    (condition-case nil
        (progn
          (unless (file-exists-p dir)
            (make-directory dir))
          dir)
      ;; In case there was an issue while creating the directory, this
      ;; option will just be disabled.
      (permission-denied nil)))
  "Directory to use for temporary configuration files.
If nil, `flymake-proselint' will not generate temporary
configuration files.  This means that the user options
`flymake-proselint-max-errors', `flymake-proselint-enable' and
`flymake-proselint-disable' will have no effect.."
  :type 'boolean)

(defun flymake-proselint-generate-configuration ()
  "Generate a configuration and return path.
This function uses the buffer local values of
`flymake-proselint-max-errors', `flymake-proselint-enable' and
`flymake-proselint-disable'.  If no configuration was
generated (either because it wasn't necessary or because disabled
by `flymake-proselint-inhibit-configuration'), the return value
will be nil."
  (unless (and flymake-proselint-config-directory
               (= flymake-proselint-max-errors 1000) ;default
               (null flymake-proselint-enable)
               (null flymake-proselint-disable))
    (let ((output (expand-file-name
                   ;; As done by `make-backup-file-name-1'
                   (secure-hash
                    'sha256
                    (prin1-to-string
                     (list (file-truename (buffer-file-name))
                           flymake-proselint-max-errors
                           flymake-proselint-enable
                           flymake-proselint-disable)))
                   flymake-proselint-config-directory)))
      (unless (file-exists-p output)
        (let* ((config (with-temp-buffer
                         (call-process "proselint" nil t nil "--dump-config")
                         (goto-char (point-min))
                         (json-parse-buffer :object-type 'alist)))
               (checks (alist-get 'checks config)))
          (setf (alist-get 'checks config)
                (mapcar
                 (lambda (opt)
                   (cons opt
                         (cond
                          ((memq opt flymake-proselint-disable) :false)
                          ((memq opt flymake-proselint-enable)  t)
                          ((alist-get opt checks)))))
                 flymake-proselint--options)
                (alist-get 'max_errors config)
                flymake-proselint-max-errors)
          (with-temp-file output (json-insert config))))
      output)))

(defun flymake-proselint-sentinel-1 (source data)
  "Handle a successfully parsed DATA from SOURCE.
DATA is a list of error diagnostics that are converted into


@@ 133,7 325,10 @@ node (flymake) Backend functions for more details."
  (let ((proc (make-process
               :name "proselint-flymake" :noquery t :connection-type 'pipe
               :buffer (generate-new-buffer " *proselint-flymake*")
               :command '("proselint" "--json" "-")
               :command
               (if-let* ((conf (flymake-proselint-generate-configuration)))
                   (list "proselint" "--config" conf "--json" "-")
                 '("proselint" "--json" "-"))
               :sentinel #'flymake-proselint-sentinel)))
    (process-put proc 'source (current-buffer))
    (process-put proc 'report-fn report-fn)