anchor.diff:  
   -include_lib("chef_regex.hrl").
   
  -%% Run List Regular Expressions
  -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  -%% Currently Chef does not support Semantic Versioning
  -%% (http://www.semver.org) of cookbooks, in that versions can only
  -%% take the form MAJOR.MINOR or MAJOR.MINOR.PATCH.
  +%% Regular Expression Macros
  +%% (Just to help DRY things up and make the usage patterns a little more clear)
  +
  +%% We commonly need to fully anchor a regex
  +-define(ANCHOR_REGEX(Regex), "^" ++ Regex ++ "$").
  +
  +%% Versions can only take the form MAJOR.MINOR or MAJOR.MINOR.PATCH.
   %%
   %% See http://wiki.opscode.com/display/chef/Version+Constraints
  -%%
  -%% A version is always optional, though.
  --define(RECIPE_VERSION_REGEX, "(?:@[[:digit:]]+(\\.[[:digit:]]+){1,2})?").
  +-define(VERSION_REGEX, "[[:digit:]]+(\\.[[:digit:]]+){1,2}").
  +
  +%% A version is always optional for recipes
  +-define(RECIPE_VERSION_REGEX, "(?:@" ++ ?VERSION_REGEX ++ ")?").
   
   %% Cookbooks, Recipes, and Roles have common naming conventions.
   %% There is a concrete reference for role names at
  
positive.code:     {ok, Regex} = re:compile(Pattern),
      {Regex, <<"Malformed recipe">>};
  regex_for(unqualified_recipe) ->
      Pattern = "^" ++ ?VERSIONED_RECIPE_REGEX ++ "$",
      {ok, Regex} = re:compile(Pattern),
      {Regex, <<"Malformed recipe">>}.
  
negative.code: %% Recipes can be cookbook-qualified; if not, the name is taken to be
  %% the cookbook, and the recipe is implicitly assumed to be "default".
  -define(COOKBOOK_PREFIX_REGEX, "(?:" ++ ?NAME_REGEX ++ "::)?").
  -define(COOKBOOK_QUALIFIED_RECIPE_REGEX, ?COOKBOOK_PREFIX_REGEX ++ ?NAME_REGEX).
  
  %% Sometimes, recipe names can have version qualifiers as well.
  -define(VERSIONED_RECIPE_REGEX, ?COOKBOOK_QUALIFIED_RECIPE_REGEX ++ ?RECIPE_VERSION_REGEX).
  
  -spec regex_for(regex_name()) -> {re_regex(),  re_msg()}.
  %% @doc provide compiled regex for use externally
  regex_for(recipe_name) ->
      %% Note that this does NOT include a version suffix!
      Pattern = ?ANCHOR_REGEX(?COOKBOOK_QUALIFIED_RECIPE_REGEX),
      {ok, Regex} = re:compile(Pattern),
      {Regex, <<"Invalid recipe name. Must only contain A-Z, a-z, 0-9, _ or -">>};
  regex_for(cookbook_version) ->
      Pattern = ?ANCHOR_REGEX(?VERSION_REGEX),
      {ok, Regex} = re:compile(Pattern),
      {Regex, <<"Invalid cookbook version">>};
  regex_for(cookbook_name) ->
      Pattern = ?ANCHOR_REGEX(?NAME_REGEX),
      {ok, Regex} = re:compile(Pattern),
      {Regex, <<"Malformed cookbook name. Must only contain A-Z, a-z, 0-9, _ or -">>};
  regex_for(environment_name) ->
      Pattern = ?ANCHOR_REGEX(?NAME_REGEX),
      {ok, Regex} = re:compile(Pattern),
      {Regex, <<"Malformed environment name. Must only contain A-Z, a-z, 0-9, _ or -">>};
  regex_for(client_name) ->
      % This might be the same as nodename -- nodename seems to allow ':' as well
      Pattern = ?ANCHOR_REGEX(?NAME_REGEX),
      {ok, Regex} = re:compile(Pattern),
      {Regex, <<"Malformed client name.  Must be A-Z, a-z, 0-9, _, -, or .">>};
  
  %% used in environments
  regex_for(cookbook_version_constraint) ->
      Pattern = "^(~>|=|>=?|<=?) " ++ ?VERSION_REGEX ++ "$",
      {ok, Regex} = re:compile(Pattern),
      {Regex, <<"Invalid cookbook version constraint">>};
  
  