amp-front 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. data/.document +5 -0
  2. data/.gitignore +24 -0
  3. data/Ampfile +3 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +36 -0
  6. data/LICENSE +20 -0
  7. data/README.md +50 -0
  8. data/Rakefile +64 -0
  9. data/VERSION +1 -0
  10. data/design_docs/commands.md +91 -0
  11. data/design_docs/dependencies.md +35 -0
  12. data/design_docs/plugins.md +47 -0
  13. data/features/amp.feature +8 -0
  14. data/features/amp_help.feature +36 -0
  15. data/features/amp_plugin_list.feature +10 -0
  16. data/features/step_definitions/amp-front_steps.rb +23 -0
  17. data/features/support/env.rb +4 -0
  18. data/lib/amp-front.rb +30 -0
  19. data/lib/amp-front/dispatch/commands/base.rb +158 -0
  20. data/lib/amp-front/dispatch/commands/builtin/help.rb +23 -0
  21. data/lib/amp-front/dispatch/commands/builtin/plugin.rb +24 -0
  22. data/lib/amp-front/dispatch/commands/validations.rb +171 -0
  23. data/lib/amp-front/dispatch/runner.rb +86 -0
  24. data/lib/amp-front/help/entries/__default__.erb +31 -0
  25. data/lib/amp-front/help/entries/ampfiles.md +42 -0
  26. data/lib/amp-front/help/entries/commands.erb +6 -0
  27. data/lib/amp-front/help/entries/new-commands.md +81 -0
  28. data/lib/amp-front/help/help.rb +312 -0
  29. data/lib/amp-front/plugins/base.rb +87 -0
  30. data/lib/amp-front/support/module_extensions.rb +92 -0
  31. data/lib/amp-front/third_party/maruku.rb +136 -0
  32. data/lib/amp-front/third_party/maruku/attributes.rb +227 -0
  33. data/lib/amp-front/third_party/maruku/defaults.rb +71 -0
  34. data/lib/amp-front/third_party/maruku/errors_management.rb +92 -0
  35. data/lib/amp-front/third_party/maruku/helpers.rb +260 -0
  36. data/lib/amp-front/third_party/maruku/input/charsource.rb +326 -0
  37. data/lib/amp-front/third_party/maruku/input/extensions.rb +69 -0
  38. data/lib/amp-front/third_party/maruku/input/html_helper.rb +189 -0
  39. data/lib/amp-front/third_party/maruku/input/linesource.rb +111 -0
  40. data/lib/amp-front/third_party/maruku/input/parse_block.rb +615 -0
  41. data/lib/amp-front/third_party/maruku/input/parse_doc.rb +234 -0
  42. data/lib/amp-front/third_party/maruku/input/parse_span_better.rb +746 -0
  43. data/lib/amp-front/third_party/maruku/input/rubypants.rb +225 -0
  44. data/lib/amp-front/third_party/maruku/input/type_detection.rb +147 -0
  45. data/lib/amp-front/third_party/maruku/input_textile2/t2_parser.rb +163 -0
  46. data/lib/amp-front/third_party/maruku/maruku.rb +33 -0
  47. data/lib/amp-front/third_party/maruku/output/to_ansi.rb +223 -0
  48. data/lib/amp-front/third_party/maruku/output/to_html.rb +991 -0
  49. data/lib/amp-front/third_party/maruku/output/to_markdown.rb +164 -0
  50. data/lib/amp-front/third_party/maruku/output/to_s.rb +56 -0
  51. data/lib/amp-front/third_party/maruku/string_utils.rb +191 -0
  52. data/lib/amp-front/third_party/maruku/structures.rb +167 -0
  53. data/lib/amp-front/third_party/maruku/structures_inspect.rb +87 -0
  54. data/lib/amp-front/third_party/maruku/structures_iterators.rb +61 -0
  55. data/lib/amp-front/third_party/maruku/textile2.rb +1 -0
  56. data/lib/amp-front/third_party/maruku/toc.rb +199 -0
  57. data/lib/amp-front/third_party/maruku/usage/example1.rb +33 -0
  58. data/lib/amp-front/third_party/maruku/version.rb +40 -0
  59. data/lib/amp-front/third_party/trollop.rb +766 -0
  60. data/spec/amp-front_spec.rb +25 -0
  61. data/spec/command_specs/base_spec.rb +123 -0
  62. data/spec/command_specs/command_spec.rb +97 -0
  63. data/spec/command_specs/help_spec.rb +33 -0
  64. data/spec/command_specs/spec_helper.rb +37 -0
  65. data/spec/command_specs/validations_spec.rb +267 -0
  66. data/spec/dispatch_specs/runner_spec.rb +116 -0
  67. data/spec/dispatch_specs/spec_helper.rb +15 -0
  68. data/spec/help_specs/help_entry_spec.rb +78 -0
  69. data/spec/help_specs/help_registry_spec.rb +77 -0
  70. data/spec/help_specs/spec_helper.rb +15 -0
  71. data/spec/plugin_specs/base_spec.rb +36 -0
  72. data/spec/plugin_specs/spec_helper.rb +15 -0
  73. data/spec/spec.opts +1 -0
  74. data/spec/spec_helper.rb +33 -0
  75. data/spec/support_specs/module_extensions_spec.rb +104 -0
  76. data/spec/support_specs/spec_helper.rb +15 -0
  77. data/test/third_party_tests/test_trollop.rb +1181 -0
  78. metadata +192 -0
@@ -0,0 +1,10 @@
1
+ Feature: Plugin List Command
2
+ In order to check my plugins
3
+ A user can use the "plugin list" command
4
+ to inspect the Amp runtime environment
5
+
6
+ Scenario: Running plugin list with no parameters
7
+ Given the argument plugin
8
+ And the argument list
9
+ When I run dispatch
10
+ Then I should see "Amp::Plugins::Base"
@@ -0,0 +1,23 @@
1
+ Given /the argument (.*)/ do |arg|
2
+ @arguments ||= []
3
+ @arguments << arg
4
+ end
5
+
6
+ When /I run dispatch/ do
7
+ dispatch = Amp::Dispatch::Runner.new(@arguments || [])
8
+ @result = swizzling_stdout { dispatch.run! }
9
+ end
10
+
11
+ Then /I should see "(.*)"/ do |arg|
12
+ @result.should =~ /#{arg}/
13
+ end
14
+
15
+ def swizzling_stdout
16
+ new_stdout = StringIO.new
17
+ $stdout, old_stdout = new_stdout, $stdout
18
+ yield
19
+ new_stdout.string
20
+ ensure
21
+ $stdout = old_stdout
22
+ new_stdout.string
23
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'amp-front'
3
+
4
+ require 'spec/expectations'
@@ -0,0 +1,30 @@
1
+ module Amp
2
+ VERSION = '0.0.1'
3
+ VERSION_TITLE = 'Koyaanisqatsi'
4
+
5
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
6
+
7
+ module Dispatch
8
+ autoload :Runner, 'amp-front/dispatch/runner.rb'
9
+ end
10
+
11
+ module Help
12
+ autoload :HelpUI, 'amp-front/help/help.rb'
13
+ autoload :HelpEntry, 'amp-front/help/help.rb'
14
+ autoload :HelpRegistry, 'amp-front/help/help.rb'
15
+ autoload :CommandHelpEntry, 'amp-front/help/help.rb'
16
+ autoload :FileHelpEntry, 'amp-front/help/help.rb'
17
+ end
18
+
19
+ module Plugins
20
+ autoload :Base, 'amp-front/plugins/base.rb'
21
+ autoload :Registry, 'amp-front/plugins/registry.rb'
22
+ end
23
+
24
+ autoload :Command, 'amp-front/dispatch/commands/base.rb'
25
+ autoload :ModuleExtensions, 'amp-front/support/module_extensions.rb'
26
+ end
27
+
28
+ autoload :ERB, 'erb'
29
+ autoload :Maruku, 'amp-front/third_party/maruku.rb'
30
+ autoload :Trollop, 'amp-front/third_party/trollop.rb'
@@ -0,0 +1,158 @@
1
+ ##################################################################
2
+ # Licensing Information #
3
+ # #
4
+ # The following code is licensed, as standalone code, under #
5
+ # the Ruby License, unless otherwise directed within the code. #
6
+ # #
7
+ # For information on the license of this code when distributed #
8
+ # with and used in conjunction with the other modules in the #
9
+ # Amp project, please see the root-level LICENSE file. #
10
+ # #
11
+ # © Michael J. Edgar and Ari Brown, 2009-2010 #
12
+ # #
13
+ ##################################################################
14
+ require File.expand_path(File.join(File.dirname(__FILE__), 'validations.rb'))
15
+
16
+ module Amp
17
+ module Command
18
+ # Creates a new command class and sets its name appropriately. Yields
19
+ # it, so it can be customized by the caller, adding options, an on_call
20
+ # block, and so on.
21
+ def self.create(name)
22
+ @current_base_module ||= Amp::Command
23
+ name = name.capitalize
24
+ new_class = Class.new(Base)
25
+ new_class.name = name
26
+ yield new_class
27
+ @current_base_module.const_set(name, new_class)
28
+ Amp::Help::CommandHelpEntry.new(name.downcase, new_class)
29
+ new_class
30
+ end
31
+
32
+ # Runs the provided block with a base module of the given name. So
33
+ # instead of creating Amp::Command::NewCommand, this allows you to
34
+ # namespace created code as Amp::Command::MyModule::NewCommand, isolating
35
+ # it from other plugins.
36
+ def self.namespace(namespace)
37
+ old_namespace = @current_base_module ||= Amp::Command
38
+ namespace = namespace.capitalize
39
+
40
+ unless old_namespace.const_defined?(namespace)
41
+ new_namespace = Module.new
42
+ old_namespace.const_set(namespace, new_namespace)
43
+ end
44
+ @current_base_module = const_get(namespace)
45
+ yield
46
+ ensure
47
+ @current_base_module = old_namespace
48
+ end
49
+
50
+ # See if the specified module list matches a defined constant
51
+ def self.lookup_const(modules)
52
+ current = Amp::Command
53
+ modules.each do |module_name|
54
+ if module_name =~ /^[A-Za-z0-9_]+$/ && current.const_defined?(module_name)
55
+ current = current.const_get(module_name)
56
+ else
57
+ return current
58
+ end
59
+ end
60
+ return current
61
+ end
62
+
63
+ # return the argument if 'new' would succeed
64
+ def self.creatable(command)
65
+ command.is_a?(Class) ? command : nil
66
+ end
67
+
68
+ # Looks up the command with the given name.
69
+ def self.for_name(name)
70
+ modules = name.split.map {|name| name.capitalize}
71
+ creatable(lookup_const(modules))
72
+ end
73
+
74
+ # The base class frmo which all comamnds inherit.
75
+ class Base
76
+ include Validations
77
+
78
+ class << self
79
+ attr_accessor :name, :options, :desc
80
+ end
81
+ self.name = 'Base'
82
+
83
+ # These are the runtime options selected when the command is called.
84
+ attr_accessor :options, :arguments
85
+
86
+ # This tracks all subclasses (and subclasses of subclasses, etc). Plus, this
87
+ # method is inherited, so Wool::Plugins::Git.all_subclasses will have all
88
+ # subclasses of Wool::Plugins::Git!
89
+ def self.all_commands
90
+ @all_commands ||= [self]
91
+ end
92
+
93
+ # When a Plugin subclass is subclassed, store the subclass and inform the
94
+ # next superclass up the inheritance hierarchy.
95
+ def self.inherited(klass)
96
+ klass.name ||= 'anonymous ' + self.name
97
+ self.all_commands << klass
98
+ next_klass = self.superclass
99
+ while next_klass != Amp::Command::Base.superclass
100
+ next_klass.send(:inherited, klass)
101
+ next_klass = next_klass.superclass
102
+ end
103
+ end
104
+
105
+ # Specifies the block to run, or returns the block.
106
+ def self.on_call(&block)
107
+ @on_call = block if block_given?
108
+ @on_call
109
+ end
110
+
111
+ def self.desc(*args)
112
+ self.desc = args[0] if args.size == 1 && args[0].is_a?(String)
113
+ @desc ||= ""
114
+ end
115
+
116
+ def self.options
117
+ @options ||= []
118
+ end
119
+
120
+ def self.opt(*args)
121
+ self.options << args
122
+ end
123
+
124
+ # Runs the command with the provided options and arguments.
125
+ def call(options, arguments)
126
+ self.options = options
127
+ self.arguments = arguments
128
+ instance_eval(&self.class.on_call)
129
+ end
130
+
131
+ # Collects the options specific to this command and returns them.
132
+ def collect_options(arguments)
133
+ args = arguments.dup
134
+ base_options = self.class.options # Trollop::options uses instance_eval
135
+ @parser, hash = Trollop::options(args) do
136
+ base_options.each do |option|
137
+ opt *option
138
+ end
139
+ end
140
+ [hash, args]
141
+ end
142
+
143
+ def education
144
+ if @parser
145
+ output = StringIO.new
146
+ @parser.educate(output)
147
+ output.string
148
+ else
149
+ ''
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ Dir[File.expand_path(File.dirname(__FILE__)) + '/builtin/**/*.rb'].each do |file|
157
+ require file
158
+ end
@@ -0,0 +1,23 @@
1
+ ##################################################################
2
+ # Licensing Information #
3
+ # #
4
+ # The following code is licensed, as standalone code, under #
5
+ # the Ruby License, unless otherwise directed within the code. #
6
+ # #
7
+ # For information on the license of this code when distributed #
8
+ # with and used in conjunction with the other modules in the #
9
+ # Amp project, please see the root-level LICENSE file. #
10
+ # #
11
+ # © Michael J. Edgar and Ari Brown, 2009-2010 #
12
+ # #
13
+ ##################################################################
14
+ Amp::Command.create('help') do |c|
15
+ c.desc "Prints the help for the program."
16
+
17
+ c.on_call do
18
+ output = ""
19
+
20
+ cmd_name = arguments.empty? ? "__default__" : arguments.first
21
+ Amp::Help::HelpUI.print_entry(cmd_name, options)
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ ##################################################################
2
+ # Licensing Information #
3
+ # #
4
+ # The following code is licensed, as standalone code, under #
5
+ # the Ruby License, unless otherwise directed within the code. #
6
+ # #
7
+ # For information on the license of this code when distributed #
8
+ # with and used in conjunction with the other modules in the #
9
+ # Amp project, please see the root-level LICENSE file. #
10
+ # #
11
+ # © Michael J. Edgar and Ari Brown, 2009-2010 #
12
+ # #
13
+ ##################################################################
14
+ Amp::Command.namespace 'plugin' do
15
+ Amp::Command.create('list') do |c|
16
+ c.desc "Lists all the plugins known to Amp."
17
+
18
+ c.on_call do
19
+ Amp::Plugins::Base.all_plugins.each do |plugin|
20
+ puts plugin.name
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,171 @@
1
+ ##################################################################
2
+ # Licensing Information #
3
+ # #
4
+ # The following code is licensed, as standalone code, under #
5
+ # the Ruby License, unless otherwise directed within the code. #
6
+ # #
7
+ # For information on the license of this code when distributed #
8
+ # with and used in conjunction with the other modules in the #
9
+ # Amp project, please see the root-level LICENSE file. #
10
+ # #
11
+ # © Michael J. Edgar and Ari Brown, 2009-2010 #
12
+ # #
13
+ ##################################################################
14
+
15
+ module Amp
16
+ module Command
17
+ module Validations
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ end
21
+ module ClassMethods
22
+ def before_blocks
23
+ @before_blocks ||= []
24
+ end
25
+
26
+ def after_blocks
27
+ @after_blocks ||= []
28
+ end
29
+
30
+ # Specifies one or more "before" callbacks that are run before
31
+ # the command executes. If any of them return non-truthy, then
32
+ # execution halts.
33
+ #
34
+ # @param block [Proc] a block to run
35
+ def before(&block)
36
+ before_blocks << block
37
+ end
38
+
39
+ # Specifies one or more "after" callbacks that are run before
40
+ # the command executes. If any of them return non-truthy, then
41
+ # execution halts.
42
+ #
43
+ # @param block [Proc] a block to run
44
+ def after(&block)
45
+ after_blocks << block
46
+ end
47
+
48
+ # Validates that the given block runs and returns true.
49
+ # TODO(adgar): Document how :if, :unless work
50
+ def validates_block(options={}, &block)
51
+ before do |opts, args|
52
+ if options[:if] || options[:unless]
53
+ checker = options[:if] || options[:unless]
54
+ result = if checker.is_a?(Proc)
55
+ checker.call(opts, args)
56
+ else
57
+ new.send(checker, opts, args)
58
+ end
59
+ result = !result if options[:unless]
60
+ next true unless result
61
+ end
62
+ block.call(opts, args)
63
+ end
64
+ end
65
+
66
+ # Validates each the given options by running their values through the
67
+ # block.
68
+ def validates_each(*syms, &block)
69
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
70
+ validates_block(options) do |opts, args|
71
+ syms.all? {|sym| block.call(opts, sym, opts[sym])}
72
+ end
73
+ end
74
+
75
+ # Validates that the given options were provided by the user.
76
+ def validates_presence_of(*syms)
77
+ validates_each(*syms) {|opts, attr, value| opts["#{attr}_given".to_sym]}
78
+ end
79
+
80
+ # Validates that the value of the given symbols is in the provided object.
81
+ # Takes an options hash that must contain the :in key:
82
+ #
83
+ # validates_inclusion_of :age, :favorite_number, :in => 13..18
84
+ #
85
+ # Also supports :if/:unless like the rails counterparts.
86
+ def validates_inclusion_of(*syms)
87
+ unless syms.last.is_a?(Hash)
88
+ raise ArgumentError.new('validates_inclusion_of takes an options hash')
89
+ end
90
+ options = syms.last
91
+ validates_each(*syms) {|opts, attr, value| options[:in].include?(value)}
92
+ end
93
+
94
+ VALID_LENGTH_KEYS = [:in, :within, :maximum, :minimum, :is]
95
+ def extract_length_key(options)
96
+ key_used = VALID_LENGTH_KEYS.find {|key| options.include?(key)}
97
+ if key_used.nil?
98
+ raise ArgumentError.new("One of #{VALID_LENGTH_KEYS.inspect} must be " +
99
+ "provided to validates_length_of")
100
+ end
101
+ key_used
102
+ end
103
+
104
+ # Validates the length of the string provided as an argument matches the
105
+ # provided constraints on length.
106
+ #
107
+ # @param [Symbol] attr the attribute to validate
108
+ # @param [Hash] options A set of options that specifies the constraint.
109
+ # Must have one of the following: :in, :within, :maximum, :minimum, :is.
110
+ def validates_length_of(attr, options={})
111
+ key_used = extract_length_key options
112
+ validates_block(options) do |opts, args|
113
+ value = opts[attr].size
114
+ numeric_comparison(value, key_used, options[key_used])
115
+ end
116
+ end
117
+ alias_method :validates_size_of, :validates_length_of
118
+
119
+ # Validates the number of arguments provided with the :in, :within, :maximum,
120
+ # :minimum, and :is relationships. To validate that a user provided at least
121
+ # 2 arguments in a copy command:
122
+ #
123
+ # command "copy" do
124
+ # validates_argument_count :minimum => 2
125
+ # end
126
+ #
127
+ def validates_argument_count(options={})
128
+ key_used = extract_length_key options
129
+ validates_block(options) do |opts, args|
130
+ value = args.size
131
+ numeric_comparison(value, key_used, options[key_used])
132
+ end
133
+ end
134
+
135
+ # Compares a value to a given constraint and returns whether it matches it
136
+ # or not.
137
+ #
138
+ # @param [Integer] value a numeric value
139
+ # @param [Symbol] key_used the key used for comparison. Either :within,
140
+ # :in, :maximum, :minimum, and :is.
141
+ # @param [Integer, #include?] constraint a value to compare against
142
+ # @return [Boolean]
143
+ def numeric_comparison(value, key_used, constraint)
144
+ if key_used == :in || key_used == :within
145
+ constraint.include?(value)
146
+ elsif key_used == :is
147
+ constraint == value
148
+ elsif key_used == :minimum
149
+ constraint <= value
150
+ elsif key_used == :maximum
151
+ constraint >= value
152
+ end
153
+ end
154
+ end
155
+
156
+ # Is this a valid set of options and arguments for this command?
157
+ def valid?(opts, args)
158
+ self.class.before_blocks.all? {|block| block.call(opts, args)}
159
+ end
160
+ alias_method :run_before, :valid?
161
+
162
+ def invalid?(opts, args)
163
+ !valid?(opts, args)
164
+ end
165
+
166
+ def run_after(opts, args)
167
+ self.class.after_blocks.all? {|block| block.call(opts, args)}
168
+ end
169
+ end
170
+ end
171
+ end