emacsorphanage / req-package

dependency management system on top of use-package
GNU General Public License v3.0
153 stars 14 forks source link

Substitute `:require' with `:config' to separate dependency-related configurations #18

Closed Alexander-Shukaev closed 8 years ago

Alexander-Shukaev commented 9 years ago

This issue is a follow-up to the req-package discussion. Currently, the :require keyword is too strict as it will force req-package to fail if at least one package listed after the :require keyword is not available. As a result, the whole configuration, even those parts which have nothing to do with the absent package, will inevitably fail, what makes req-package quite rigid. For instance:

(req-package evil
  :require recentf
  :config
  (progn
    ;; configurations not related to `recentf'
  )
  (progn
    ;; configurations related to `recentf'
    (evil-make-overriding-map recentf-dialog-mode-map 'motion)
    (evil-set-initial-state 'recentf-dialog-mode 'motion)
    (evil-ex-define-cmd "rfm[enu]" #'recentf-open-files))
  (progn
    ;; configurations not related to `recentf'
  ))

Now, if recentf is not available, then all of the code under the :config keyword fails.

The idea is to improve granularity and flexibility of control over dependency-related configurations by slightly extending the existing interface of the :config keyword in a natural way. Here is a fairly complex commented example to demonstrate the idea:

(req-package evil
  :config
  ;; Here we configure only `evil' itself, e.g.:
  (let ((keymap evil-emacs-state-map))
    (setcdr keymap nil)
    (define-key keymap (read-kbd-macro evil-toggle-key) #'evil-exit-emacs-state))
  (setq-default evil-ex-commands nil)
  (evil-ex-define-cmd "w[rite]" #'evil-write)
  :config recentf
  ;; Here we configure what is related to `recentf' (plus `evil' of course).
  ;; It's kind of `:require recentf' (in a sense that it should also
  ;; manage dependencies as `:require recentf' does), but it also
  ;; includes corresponding configuration as well.  The benefit is
  ;; that if `recentf' is not available, then this code is simply
  ;; disabled/ignored (similar to how `with-eval-after-load' functions).
  ;; This is more general and flexible approach then `:require'.
  (evil-make-overriding-map recentf-dialog-mode-map 'motion)
  (evil-set-initial-state 'recentf-dialog-mode 'motion)
  (evil-ex-define-cmd "rfm[enu]" #'recentf-open-files)
  :config help
  ;; Again the same concept as with `recentf'.
  (let ((keymap evil-emacs-state-map))
    (define-key keymap (kbd "C-?") #'help))
  (evil-ex-define-cmd "h[elp]" #'help)
  :config (my minibuffer)
  ;; Again the same concept as with `recentf', but now two packages
  ;; are prerequisites.
  (my-add-hook 'minibuffer-setup-hook #'evil-insert-state))

By the way, those :config ... forms should execute exactly in the order they appear in the req-package macro. Why is it important? Consider (setq-default evil-ex-commands nil). It erases all of the default Evil ex-commands (set in the evil-maps subpackage). After that, we add our own ex-command: (evil-ex-define-cmd "w[rite]" #'evil-write). However, later on, if recentf is available, then (evil-ex-define-cmd "rfm[enu]" #'recentf-open-files) should run and obviously we want it to definitely run after (setq-default evil-ex-commands nil). Note that the same concept applies to how evil-emacs-state-map is managed (first reset and then gradually repopulated). Hence, the situation can occur quite often, and the deterministic control of dependency-related configurations execution order is vital.

Please, note that the benefits (dependency tracking, automatic downloading, etc.) of the original :require keyword should still apply to the new :config ... extension. However, it should definitely NOT do (require '...) behind the scenes. Instead, :config ... should behave similarly to the default :config, which simply translates to eval-after-load without require-ing anything, and at the same time, it should still build the dependency graph in the similar way as :require does in order to force evaluation of (req-package recentf ...) BEFORE the current one ((req-package evil ...) in this example), so that the default :config from (req-package recentf ...) is definitely executed BEFORE :config recentf from (req-package evil ...). Note that the code under either default :config from (req-package recentf ...) or from :config recentf from (req-package evil ...) will really execute only when recentf is truly loaded (by autoload, manually, or whatever) thanks to eval-after-load behind :config.

Finally, (req-package recentf ...) on its own without :demand (and that's the encouraged practice to avoid :demand, which is the same as (require 'recentf)) does not load recentf; and therefore, does not have to be touched at all if one does not want to use recentf anymore (i.e. one does not have to comment or erase it, but should rather only ensure that recentf is not loaded anywhere).

jwiegley commented 9 years ago

What if we introduced :dep-config PKG FORMS... to indicate per-dependency configuration? This way it would keep the implementation of :config separate (although, you can always have req-package override use-packages definition for this keyword too).

Alexander-Shukaev commented 9 years ago

That's also a possibility. I had a thought on the back of my mind that :config ... could be difficult to distinguish from :config during parsing. Thus, I guess :dep-config is fine, but then I think :config-dep looks even better.

Also don't forget that PKG can also be a list of packages as well (see the example above), which would probably translate into a series of nested eval-after-load with each corresponding package from this list.

aspiers commented 8 years ago

Any more thoughts on this? I'm struggling to understand how I can reliably set up my init files to load combinations of packages and configure them in the right way, e.g.

edvorg commented 8 years ago

Currently I just load some framework-like packages without deferring and then load other packages. It looks like this:

(req-package cider
  :require (key-chord guide-key)
  :config 
  (key-chord-define-global "09" 'cider-jack-in-clojurescript)
  (guide-key-related-config))

What we plan to do here is per dependency config sections like:

(req-package key-chord
  :defer t)

(req-package guide-key
  :defer t)

(req-package cider
  :config key-chord
  (key-chord-define-global "09" 'cider-jack-in-clojurescript)
  :config guide-key
  (guide-key-related-config))

So all branches will behave independently

aspiers commented 8 years ago

Makes sense - looking forward to it!

edvorg commented 8 years ago
(req-package banana)

(req-package cucumber)

(req-package mango
  :require banana
  :config (printf "%s" (+ mango banana)))

(req-package mango
  :require cucumber
  :config (printf "%s" (+ mango cucumber)))
Alexander-Shukaev commented 7 years ago

Do I understand correctly that

(req-package banana
  :defer t)

(req-package cucumber
  :defer t)

(req-package mango
  :require banana
  :config (printf "%s" (+ mango banana)))

(req-package mango
  :require cucumber
  :config (printf "%s" (+ mango cucumber)))

and

(req-package banana
  :defer t)

(req-package cucumber
  :defer t)

(req-package mango
  :dep-config banana
  (printf "%s" (+ mango banana)))

(req-package mango
  :dep-config cucumber
  (printf "%s" (+ mango cucumber)))

are equivalent?

edvorg commented 7 years ago

Correct. I just decided to go with first option, because it was easier in implementation and arguments parsing.

Alexander-Shukaev commented 7 years ago

I see. Thank you for the effort, good job!

edvorg commented 7 years ago

You're welcome!

2016年12月9日(金) 5:17 Alexander Shukaev notifications@github.com:

I see. Thank you for the effort, good job!

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/edvorg/req-package/issues/18#issuecomment-265872508, or mute the thread https://github.com/notifications/unsubscribe-auth/ABbhyx4aL8TgmE2VIguy1D6NXJS0tHMKks5rGIH7gaJpZM4FrxiE .