amp-front 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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