This is related to #628262: Study fabric and build similar on top of backend.inc. In addition to command-specific options (as implemented in #671612: drush should support command-specific options), there are more things that we can do with contexts. Drush already has a certain number of pre-defined contexts, used to prioritized options. This proposal allows for additional contexts to be defined (typically in a drush configuration file), and then applied either by name or by situation.

A context is defined in a drushrc.php file like so:

$context['contextname'] = array(
  'options' => array('key' => 'value', 'key2' => 'value2'),
  'hooks' => 'myhook',
  'includes' => 'name.context.inc',
);

'options' are command-line options that are copied to the 'process' context when the context they are defined in is selected. More on 'hooks' and 'includes' later.

The easiest way to invoke a context is by name, e.g. drush --context=contextname .... Contexts can also be invoked contextually via context selectors.

$options['context-selectors'] = array(
  array( 'selectors' => array('source-type' => 'dev', 'target-type' => 'live'), 'context' => 'publish'),
);

In this example, if you call drush with drush --source-type=dev --target-type=live ..., this is the same as specifying 'context' => 'publish'. This is interesting because you can use the existing site alias feature to define 'type' => 'dev' in your dev sites and 'type' => 'live' in your live sites, and then the 'publish' context will be applied in any operation where the source is a dev site and the target is a live site.

If the context does not define a 'hooks' entry explicitly, then the context hook will default to the context name. The context hooks are added to the hooks list in drush_invoke, right after the commandfile hooks. So, for example, you could define a hook function drush_publish_post_sql_sync, and it would be called after sql-sync completes whenever the 'publish' context has been applied. Similarly, the 'includes' line can be used to specify a file that will be included (via require-once) when in context, but this is not necessary; hooks can also be defined in other files that are always included, such as a drushrc.php file.

This feature would allow the behavior of drush to be customized in flexible ways just by defining contexts, context selectors and hook functions in a drushrc.php file that is then shared among the members of a development team. While the totality of the operation of named contexts may be somewhat complex, recipes in example.drushrc.php would probably be pretty easy to understand and customize.

The code for this is not too complicated, and nearly complete. I'll post a patch shortly, once it's ready.

Comments

greg.1.anderson’s picture

Status: Active » Needs review
StatusFileSize
new11.55 KB

Here we have drush dispatching on steroids.

To illustrate what can be done with this patch, please consider the following brief walkthrough.

Step 1: Add some hook functions to your drushrc.php file:

function drush_magical_pre_sql_conf() {
  drush_print("inside magical pre-sql-conf hook");
}

function drush_magical_post_sql_conf() {
  drush_print("inside magical post-sql-conf hook");
}

Now if you call drush sql-conf you won't see anything special, but if you call drush --context=magical sql-conf, you should see both of your hooks fire. Nifty.

Step 2: Add some context selectors to your drushrc.php file:

$options['context-selectors'] = array(
  array( 'selectors' => array('xyzzy' => 1), 'context' => 'magical' ),
  'drush_test_magical',
);

function drush_test_magical() {
  $result = array();
  
  if (drush_get_option('magical') != NULL) {
    $result[] = 'magical';
  }
  
  return $result;
}

This example shows the two kinds of context selectors. A selector array contains a list of options with required values and a context name, whereas a selector string is the name of a php function somewhere that can do any test it likes to apply contexts. The return code is an array of context names. Now, drush --xyzzy=1 sql-conf or drush --magical=anything sql-conf will fire your hooks.

Step 3: Add a context definition.

$context['magical'] = array(
  'options' => array( 'v' => TRUE, 'd' => TRUE ),
  'hooks' => 'magical',
);

This perhaps should have been the first step; however, an empty or missing context definition defaults to a simple context that defines no options, and defines a single hook with the same name as the context. If you add the above definition to your drushrc.php file and re-run any of the tests above, you will see that your hooks fire, and in addition, 'verbose' and 'debug' are defined, so you will also see a list of all of your hook opportunities for sql-conf. If you change 'hooks' from 'magical' to 'mystical' and run again, then you will notice that the 'magical' hooks are no longer firing, but there are a number of hooks shown in the output of the hook opportunities code containing 'mystical' in their function names.

To make full use of this with sql-sync and rsync will require a separate patch to make those functions more hook-able. I'd like to do that as a separate step after this basic functionality is reviewed and vetted.

adrian’s picture

Status: Needs review » Needs work

this reaaaallly feels like overkill to me.

i don't like the idea of requiring people to define functions in their config files. They aren't easily version controlled (different for every person).

There are so many different layers of drushrc configuration files, that you end up in situations where contexts only exist when you are working in specific places. And there's also the possibility of conflicting 'contexts' (it's entirely possible for multiple contexts to be set at the same time).

'Context' is also already used internally to mean something different.

To add a new 'namespace' at the moment, all you need to do is create a $namespace.drush.inc file, and you get the drush invoke stuff for free. But what this seems to be about, is something that has bothered me before too ... namely being able to conditionally include modules.

The kernel of your issue here for me is the ability to go "--enable-$context" , and then have that namespace be used, and to have the ability to alias sets of options to imply others.

You can already do the following in your custom module :

<?php
function drush_mymodule_init($args) {
drush_set_option("option", 1);
drush_set_option("option2", 3);
}

My idea is to add a meta-info file for the drush command files, that are loaded and considered before actually loading the module and adding it to the stack.

greg.1.anderson’s picture

Okay, I can agree to your technique. The meta-info file needs to be considered twice, though: once before the commandlist is built, so it can include a file that defines commands, and once after the user-specified command is selected, so that additional hook functions can be defined based on the command parameters.

I suppose, though, that since that would be complicated (the later half), it might be better to just settle for:

function drush_mynamespace_pre_somecommand() {
  if (myconditions()) {
    // hook!
  }
}

I can go for that. I'll roll together the first half. Before drush adds a namespace and includes a file, it will look for a similarly-named file in the same folder. If found, that file will be included first, and a well-known function name inside it will be executed. Iff the result of the function is TRUE, then the commandlist processing will happen as usual. Otherwise, that file will be skipped.

If anyone has an opinion on the naming convention, let me know; otherwise I'll pick something and post a patch shortly.

adrian’s picture

how about $namespace_drush_status() ? , and have it default to TRUE if not defined.

i was considering making it a .info file with a specific flag you specify (ie:

status: 0
flag: enable-module

but that is nowhere near flexible enough. so a hook will have to do.

greg.1.anderson’s picture

Yes, I think that code is better. How about $namespace_drush_load_status(), though, for better alignment with its function? It could be stored in $namespace.load.status.inc.

If you prefer brevity, then I'll stick with $namespace_drush_status() and $namespace.status.inc.

anarcat’s picture

I'm a bit confused by this issue: are we ditching the original "named contexts" idea? I agree it's very confusing to add another "context" paradigm, and we should avoid reusing that term. Therefore we should probably clarify the issue title.

The kernel of your issue here for me is the ability to go "--enable-$context" , and then have that namespace be used, and to have the ability to alias sets of options to imply others.

Yes. This is what was requested in #418208: do not load everything and provide a way to tell drush what to load and that issue is now a duplicate of this one.

You can already do the following in your custom module :

function drush_mymodule_init($args) {
drush_set_option("option", 1);
drush_set_option("option2", 3);
}

I'm not sure what this does for us...

My idea is to add a meta-info file for the drush command files, that are loaded and considered before actually loading the module and adding it to the stack.

Hum. Another meta-info file? How about just parsing the module's .info file, which should be free to have any field we want?

greg.1.anderson’s picture

@anarcat: You're right, the requirements on this issue are getting confused. There are a bunch of features represented here; let's take a step back and list them:

  1. Use options to turn off existing commandfiles
  2. Use options to add additional commandfiles or hooks (dynamic namespaces)
  3. Use php code instead of options to do 1. or 2.
  4. Use command-specific options or alias-defined options to do 1. or 2.
  5. Turn commandfiles off before they are ever included (avoid defining duplicate functions)
  6. Define options through metadata instead of php code
  7. Define hook functions in drushrc.php
  8. Use a meta-inf file to disable commandfiles

My patch in #1 provided a mechanism to do everything except 1. and 8.

Adrian did not think it was necessary to be able to dynamically add hooks; he preferred using the existing namespace = commandfile = hook association that drush currently provides, and wants a mechanism to just turn off existing commandfiles before they are loaded (1. and 5.) In #4, I think we agreed that meta-inf files are not flexible enough; therefore code is needed (3. is required, 8. is superfluous).

To answer the question in #6, in #2, Adrian was pointing out that options can be added via code during their init functions, so he felt that it was overkill to allow them to be defined using metadata in a drush configuration file. I kind of like this feature; it is analogous to the command-specific options.

In #3, I gave up on the idea of turning off hooks via command-specific or alias-specified options (feature 4.), since you can also get this behavior just by wrapping every hook in an 'if' statement. However, I think that turning hooks on and off is what this feature is all about, so in the end I think I'd be a little dissatisfied if hooks could be switched during commandfile collection, but not during command processing (when command-specific and alias-defined options are available).

At the moment, I'm thinking of removing 2. and adding 1. to my patch. I really want to keep 4. It is unclear to me whether we need 5.; if we do not, then we don't need an extra .inc file, we can just call a well-known function that is defined in the drush.inc file.

I'm still on the fence about 6.; I might try to keep this feature if I can do it in a simpler way, or at least do it in a way that is not called "contexts". The idea from fabrique is that you can have a simple name (a "context") that defines a set of options and hooks. Doing this easily in metadata is useful, I think.

Defining hooks in the drushrc.php was really just one example usage, one that I thought was easy to understand and describe. It may very well be undesirable, but in any event, nothing in my previous patch or my next patch will prevent or require code in drushrc.php.

I don't think we need 8.

adrian’s picture

StatusFileSize
new1.79 KB

ok. here's my look at it.
dead simple.

created 2 files in ~/.drush

~/.drush/mytest.drush.load.inc

function mytest_drush_load() {
  return drush_get_option('enable-mytest', false);
}

and the actual commandfile

function mytest_drush_command() {
  return array(
    'mytest' => array('description' => 'BLAH'),);
}

obviously this is just an example.

you can just as easily have your _load_func do something like :

function mytest_drush_load() {
 if ((drush_get_option('source-type')  == 'dev') && (drush_get_option('target-type') == 'live')) {
    drush_set_option('enable-mytest', true);
 }

 return drush_set_option('enable-mytest', false);
}

This way you aren't stuck with us generating your conditionals from a non expressive array. IE: what if your conditions for enabling the module contains conditions like if this conflicting module is enabled and these flags are set in a certain way, or a certain value is over a certain threshold .. enable the module.

greg.1.anderson’s picture

Status: Needs work » Needs review
StatusFileSize
new3.04 KB

Okay, I like that quite a bit. There's only one limitation with it. If there is an option that is defined in a drush configuration file that is stored in a Drupal site folder, then that option will not be defined until DRUSH_BOOTSTRAP_DRUPAL_SITE, but at this point you will have already missed all of the modules that had their load_check run during an earlier phase.

Here is a oh-so-slightly-more complicated version that retries the load test on every phase. If this satisfies the things you need, I'll open a new issue to add the code to allow this all to work in the case of drush sql-sync mydev mylive, which is requirement 4 above. n.b. the options defined in 'mydev' and 'mylive' are currently not applied until after the sql-sync command is invoked (e.g. 'type' => 'dev' and 'type' => 'live', defined in 'mydev' and 'mylive', respectively, will define 'source-type' and 'target-type' as 'dev' and 'live', respectively, after sql-sync evaluates its parameters).

moshe weitzman’s picture

Assigned: greg.1.anderson » adrian

adrian to review.

anarcat’s picture

Issue tags: +drush-3.0

Marking this for 3.0.

adrian’s picture

Status: Needs review » Fixed

I've committed this patch and confirmed your new logic.

awesome. finally.

moshe weitzman’s picture

Status: Fixed » Active

#9 talked about a todo after the patch. greg/adrian should close this if no follow-up needed.

greg.1.anderson’s picture

Assigned: adrian » greg.1.anderson

I'm glad that #9 works well for everyone and is committed. I guess I'll leave this open for follow-on patches, although I'm not quite ready to do them yet.

greg.1.anderson’s picture

Issue tags: -drush-3.0

Removing the "drush-3.0" tag, as follow-on changes are not necessary for 3.0-stable.

greg.1.anderson’s picture

Title: Use named contexts to apply sets of options and define new hook functions » Allow drush command files to define functions that control whether or not they will be loaded

I'm just going to mark this "fixed". The important part went in with #12. I don't want named contexts any more. An alias already functions in part like a named context. There are a couple of things that are, perhaps, missing: having --option1 and --option2 imply (add) an --option3 when used together, and the ability for an option to "turn on" a hook dynamically. If we can do the later, then the former can be accomplished in code per #2, and per #8, I'm not sure we need dynamic hooks. If I change my mind later, I'll open a new issue about dynamic hooks.

Also, renaming so that the title matches what was committed.

greg.1.anderson’s picture

Status: Active » Fixed

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.