anishathalye / dotbot

A tool that bootstraps your dotfiles ⚡️

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Named conditions (and block directive)

gnfzdz opened this issue · comments

There's actually two requests here, but given the overlap I thought it suitable to just raise a single issue:

  1. Introduce a new type of plugin, condition plugins, that provide named, configurable conditions written in Python
  2. Introduce a new directive accepting: condition(s) to evaluate and a list of tasks/actions to execute when true

Motivation

There are currently 3 plugins linked from the official documentation that provide a condition and a container of tasks to be executed based on the outcome (if, ifarch, ifplatform). Dotbot users have already adopted this type of plugin so the interest is clearly there.

The most flexible (and similar to this request) is the if plugin which takes a single shell command. Personally, there are at least a couple other conditions that I'd like to share with the community that aren't well suited to a shell one-liner.

Notably, all three of the plugins also share the same issue. They don't propagate defaults from the parent context (and scope any new defaults set to the child context).

I think it would be great to provide improved support for this pattern in the core dotbot project itself.

Examples

First, you can see below a couple options for a hypothetical new directive. I don't actually have a strong preference here. Whatever we think is least likely to conflict with a custom plugin name.

- cond:
    if:
      tty: true
    then:
      - shell:
        - '<command_that_may_prompt_for_input>'
    else:
      - shell:
        - 'fallback_command'

- block:
    when:
      tty: true
    tasks:
      - shell:
        - '<command_that_may_prompt_for_input>'

Next you can see a few variants of the condition. I actually think it makes sense to support all of these.

# For a string, it is interpreted as a shell command (providing consistency with the link directive's current if option) 
- cond:
    if: "command -v ansible"
    then:
      - shell:
        - 'echo "I found ansible in $PATH"'

# For a dict, use each key to find the corresponding Condition plugin and pass the value as arguments
- cond:
    if:
      tty: true
      platform: 'arch'
    then:
      - shell:
        - 'echo "I am running in a TTY on ArchLinux!"'

# An array is also supported for the same reasons as with dotbot tasks/actions.
# It allows repeating a condition multiple times.
- cond:
    if:
      - decrypted: "./config/file/a"
      - decrypted: "./config/file/b"
      - 'command -v "program_requiring_sensitive_config"'
    then:
      - shell:
        - 'echo "I have everything i need to configure <program_requiring_sensitive_config>."'

I'm envisioning the condition plugins following the same pattern as those providing directives. A new test runner class would take the full condition and delegate to the appropriate condition plugins for evaluation.

import dotbot
import sys

class TtyCondition(dotbot.Condition):

    def can_handle(self, directive):
        return "tty" == self._directive

    def handle(self, directive, data=True):
        expected = data if data is not None else True
        return expected == (sys.stdin.isatty() and sys.stdout.isatty() and sys.stderr.isatty())

Other thoughts

  1. Unlike #225 the only potential for collision would be the single new directive. Behavior of tasks/actions remains the same as it is today.
  2. The if attribute in the current link plugin could also make use of the above conditions. As described above it would be backwards compatible.
  3. Why not just implement this as a new plugin? Configuring dotbot/dotbot-plugins as submodules seems to complicate import resolution. It's much easier if the new Condition abstract base class is in the dotbot repository itself. I'm fairly new to python, so maybe there's a pattern I'm missing that would actually make this easier.

I currently have this implemented as a separate plugin here, but I'm absolutely still interested in having this merged into dotbot core.

And one more complex example is available here (checking the encryption status of git-crypt managed files)

@gnfzdz, the condition/if/then/else constructs are extremely readable, and having Python-based condition providers is the correct -- and portable -- way to address this. I run dotbot across multiple operating systems and shell environments; shelling out to an unknown shell to run a string blindly simply doesn't work.

I'm 100% on board for this.