plezi 0.12.22 → 0.14.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/LICENSE.txt +17 -18
  4. data/README.md +54 -698
  5. data/Rakefile +7 -4
  6. data/bin/config.ru +22 -0
  7. data/{test → bin}/console +4 -6
  8. data/bin/hello_world +52 -0
  9. data/bin/setup +8 -0
  10. data/exe/plezi +145 -0
  11. data/lib/plezi.rb +24 -137
  12. data/lib/plezi/activation.rb +28 -0
  13. data/lib/plezi/api.rb +62 -0
  14. data/lib/plezi/controller/controller.rb +259 -0
  15. data/lib/plezi/controller/controller_class.rb +176 -0
  16. data/lib/plezi/controller/cookies.rb +40 -0
  17. data/lib/plezi/helpers.rb +60 -0
  18. data/lib/plezi/rake.rb +2 -24
  19. data/lib/plezi/render/erb.rb +34 -0
  20. data/lib/plezi/render/has_cache.rb +36 -0
  21. data/lib/plezi/render/markdown.rb +63 -0
  22. data/lib/plezi/render/render.rb +49 -0
  23. data/lib/plezi/render/sass.rb +55 -0
  24. data/lib/plezi/render/slim.rb +33 -0
  25. data/lib/plezi/router/adclient.rb +23 -0
  26. data/lib/plezi/router/assets.rb +67 -0
  27. data/lib/plezi/router/errors.rb +29 -0
  28. data/lib/plezi/router/route.rb +112 -0
  29. data/lib/plezi/router/router.rb +120 -0
  30. data/lib/plezi/version.rb +1 -1
  31. data/lib/plezi/websockets/message_dispatch.rb +91 -0
  32. data/lib/plezi/websockets/redis.rb +55 -0
  33. data/plezi.gemspec +25 -16
  34. data/resources/404.erb +5 -4
  35. data/resources/500.erb +5 -4
  36. data/resources/{500.html → 503.html} +8 -9
  37. data/resources/client.js +253 -0
  38. data/resources/config.ru +5 -36
  39. data/resources/ctrlr.rb +34 -0
  40. data/resources/gemfile +4 -0
  41. data/resources/mini_app.rb +28 -82
  42. data/resources/mini_exec +7 -0
  43. data/resources/mini_welcome_page.html +0 -0
  44. data/resources/procfile +3 -0
  45. data/resources/rakefile +4 -8
  46. data/resources/routes.rb +9 -26
  47. data/resources/{websockets.js → simple-client.js} +3 -3
  48. metadata +60 -85
  49. data/bin/plezi +0 -104
  50. data/docs/async_helpers.md +0 -245
  51. data/docs/controllers.md +0 -27
  52. data/docs/logging.md +0 -49
  53. data/docs/routes.md +0 -209
  54. data/docs/websockets.md +0 -213
  55. data/lib/plezi/builders/ac_model.rb +0 -59
  56. data/lib/plezi/builders/app_builder.rb +0 -137
  57. data/lib/plezi/builders/builder.rb +0 -43
  58. data/lib/plezi/builders/form_builder.rb +0 -27
  59. data/lib/plezi/common/api.rb +0 -92
  60. data/lib/plezi/common/cache.rb +0 -122
  61. data/lib/plezi/common/defer.rb +0 -21
  62. data/lib/plezi/common/dsl.rb +0 -94
  63. data/lib/plezi/common/redis.rb +0 -65
  64. data/lib/plezi/common/renderer.rb +0 -141
  65. data/lib/plezi/common/settings.rb +0 -52
  66. data/lib/plezi/handlers/controller_core.rb +0 -106
  67. data/lib/plezi/handlers/controller_magic.rb +0 -284
  68. data/lib/plezi/handlers/http_router.rb +0 -205
  69. data/lib/plezi/handlers/placebo.rb +0 -112
  70. data/lib/plezi/handlers/route.rb +0 -216
  71. data/lib/plezi/handlers/session.rb +0 -109
  72. data/lib/plezi/handlers/stubs.rb +0 -156
  73. data/lib/plezi/handlers/ws_identity.rb +0 -253
  74. data/lib/plezi/handlers/ws_object.rb +0 -308
  75. data/lib/plezi/helpers/http_sender.rb +0 -84
  76. data/lib/plezi/helpers/magic_helpers.rb +0 -104
  77. data/lib/plezi/helpers/mime_types.rb +0 -1995
  78. data/lib/plezi/oauth.rb +0 -5
  79. data/lib/plezi/oauth/auth_controller.rb +0 -229
  80. data/logo/dark.png +0 -0
  81. data/logo/light.png +0 -0
  82. data/logo/sign.png +0 -0
  83. data/resources/404.haml +0 -121
  84. data/resources/404.html +0 -124
  85. data/resources/404.slim +0 -120
  86. data/resources/500.haml +0 -120
  87. data/resources/500.slim +0 -120
  88. data/resources/Gemfile +0 -86
  89. data/resources/code.rb +0 -8
  90. data/resources/controller.rb +0 -142
  91. data/resources/database.yml +0 -33
  92. data/resources/db_ac_config.rb +0 -59
  93. data/resources/db_dm_config.rb +0 -51
  94. data/resources/db_sequel_config.rb +0 -33
  95. data/resources/en.yml +0 -204
  96. data/resources/haml_config.rb +0 -6
  97. data/resources/i18n_config.rb +0 -14
  98. data/resources/initialize.rb +0 -49
  99. data/resources/mini_exec.rb +0 -7
  100. data/resources/oauth_config.rb +0 -24
  101. data/resources/plezi_client.js +0 -198
  102. data/resources/plezi_websockets.html +0 -47
  103. data/resources/redis_config.rb +0 -42
  104. data/resources/slim_config.rb +0 -11
  105. data/resources/welcome_page.html +0 -272
  106. data/test/dispatch +0 -58
  107. data/test/hello_world +0 -13
  108. data/test/plezi_tests.rb +0 -581
@@ -0,0 +1,29 @@
1
+ module Plezi
2
+ module Base
3
+ class Err404Ctrl
4
+ def index
5
+ response.status = 404
6
+ render('404') || 'Error 404, not found.'
7
+ end
8
+
9
+ def requested_method
10
+ :index
11
+ end
12
+
13
+ include Plezi::Controller
14
+ end
15
+ class Err500Ctrl
16
+ def index
17
+ response.status = 500
18
+ render('500') || 'Internal Error 500.'
19
+ rescue
20
+ 'Internal Error 500.'
21
+ end
22
+
23
+ def requested_method
24
+ :index
25
+ end
26
+ include Plezi::Controller
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,112 @@
1
+ require 'plezi/controller/controller'
2
+ require 'thread'
3
+ require 'rack'
4
+ require 'rack/query_parser.rb'
5
+
6
+ module Plezi
7
+ module Base
8
+ class Route
9
+ attr_reader :prefix, :controller, :param_names
10
+
11
+ def initialize(path, controller)
12
+ @route_id = "Route#{object_id.to_s(16)}".to_sym
13
+ m = path.match(/([^\:\(\*]*)(.*)/)
14
+ @prefix = m[1].chomp('/'.freeze)
15
+ if @prefix.nil? || @prefix == ''.freeze
16
+ @prefix = '/'.freeze
17
+ @prefix_length = 1
18
+ else
19
+ @prefix = "/#{@prefix}" if @prefix[0] != '/'.freeze
20
+ @prefix_length = @prefix.length + 1
21
+ end
22
+ @controller = controller
23
+ @param_names = []
24
+ @origial = path.dup.freeze
25
+ path2regex(m[2])
26
+ self.class.qp
27
+ case @controller
28
+ when Class
29
+ prep_controller
30
+ when Regexp
31
+ raise "Rewrite Routes can't contain more then one parameter to collect" if @param_names.length > 1
32
+ else
33
+ raise 'Controller should be a class object' unless controller.is_a?(Class)
34
+ end
35
+ end
36
+
37
+ def call(request, response)
38
+ return nil unless match(request.path_info, request)
39
+ case @controller
40
+ when Class
41
+ c = @controller.new
42
+ return c._pl_respond(request, response, Thread.current[@route_id])
43
+ when Regexp
44
+ params = Thread.current[@route_id]
45
+ return nil unless controller =~ params[@param_names[0]]
46
+ request.path_info = "/#{params.delete('*'.freeze).to_a.join '/'}"
47
+ request.params.update params
48
+ end
49
+ nil
50
+ end
51
+
52
+ def fits_params(path, request)
53
+ params = (Thread.current[@route_id] ||= {}).clear
54
+ params.update request.params.to_h if request && request.params
55
+ # puts "cutting: #{path[(@prefix_length)..-1] ? path[(@prefix_length + 1)..-1] : 'nil'}"
56
+ pa = (path[@prefix_length..-1] || ''.freeze).split('/'.freeze)
57
+ # puts "check param count: #{pa}"
58
+ return false unless @params_range.include?(pa.length)
59
+ @param_names.each do |key|
60
+ next if pa[0].nil?
61
+ self.class.qp.normalize_params(params, Plezi.try_utf8!(Rack::Utils.unescape(key)),
62
+ Plezi.try_utf8!(Rack::Utils.unescape(pa.shift)), 100)
63
+ end
64
+ params['*'.freeze] = pa unless pa.empty?
65
+ true
66
+ end
67
+
68
+ def match(req_path, request = nil)
69
+ # puts "#{req_path} starts with #{@prefix}? #{req_path.start_with?(@prefix)}"
70
+ req_path.start_with?(@prefix) && fits_params(req_path, request)
71
+ end
72
+
73
+ def path2regex(postfix)
74
+ pfa = postfix.split '/'.freeze
75
+ start = 0; stop = 0
76
+ optional = false
77
+ while pfa.any?
78
+ name = pfa.shift
79
+ raise "#{name} is not a valid path section in #{@origial}" if /^((\:[\w\.\[\]]+)|(\(\:[\w\.\[\]]+\))|(\*))$/.match(name).nil?
80
+ if name[0] == ':'
81
+ raise "Cannot have a required parameter after an optional parameter in #{@origial}" if optional
82
+ @param_names << name[1..-1].freeze
83
+ elsif name[0] == '('
84
+ optional = true
85
+ @param_names << name[2..-2].freeze
86
+ elsif name[0] == '*'
87
+ stop += 999_999
88
+ break
89
+ else
90
+ raise "invalid path section #{name} in #{@origial}"
91
+ end
92
+ optional ? (stop += 1) : (start += 1)
93
+ end
94
+ unless (@param_names.include? 'id'.freeze) || stop >= 999_999
95
+ @param_names << 'id'.freeze
96
+ stop += 1
97
+ end
98
+ @params_range = (start..(start + stop))
99
+ @param_names.freeze
100
+ @params_range.freeze
101
+ end
102
+
103
+ def prep_controller
104
+ @controller.include Plezi::Controller
105
+ end
106
+
107
+ def self.qp
108
+ @qp ||= ::Rack::QueryParser.new(Hash, 65_536, 100)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,120 @@
1
+ require 'plezi/router/route'
2
+ require 'plezi/router/errors'
3
+ require 'plezi/router/assets'
4
+ require 'plezi/router/adclient'
5
+ require 'rack'
6
+
7
+ module Plezi
8
+ module Base
9
+ module Router
10
+ @routes = []
11
+ @app = nil
12
+
13
+ module_function
14
+
15
+ def new(app)
16
+ puts 'Plezi as Middleware'
17
+ @app = ((app == Plezi.app) ? nil : app)
18
+ Plezi.app
19
+ end
20
+
21
+ def call(env)
22
+ request = Rack::Request.new(env)
23
+ response = Rack::Response.new
24
+ ret = nil
25
+ @routes.each { |route| ret = route.call(request, response); break if ret }
26
+ unless ret
27
+ return @app.call(env) if @app
28
+ ret = ::Plezi::Base::Err404Ctrl.new._pl_respond(request, response, request.params)
29
+ end
30
+ response.write(ret) if ret.is_a?(String)
31
+ return response.finish
32
+ rescue => e
33
+ puts e.message, e.backtrace
34
+ response = Rack::Response.new
35
+ response.write ::Plezi::Base::Err500Ctrl.new._pl_respond(request, response, request.params)
36
+ return response.finish
37
+ end
38
+
39
+ def route(path, controller)
40
+ path = path.chomp('/'.freeze) unless path == '/'.freeze
41
+ case controller
42
+ when :client
43
+ controller = ::Plezi::Base::Router::ADClient
44
+ when :assets
45
+ controller = ::Plezi::Base::Assets
46
+ path << '/*'.freeze unless path[-1] == '*'.freeze
47
+ when Regexp
48
+ path << '/*'.freeze unless path[-1] == '*'.freeze
49
+ end
50
+ @routes << Route.new(path, controller)
51
+ end
52
+
53
+ def list
54
+ @routes
55
+ end
56
+
57
+ def url_for(controller, method_sym, params = {})
58
+ # GET,PUT,POST,DELETE
59
+ r = nil
60
+ url = '/'.dup
61
+ @routes.each do |tmp|
62
+ case tmp.controller
63
+ when Class
64
+ next if tmp.controller != controller
65
+ r = tmp
66
+ break
67
+ when Regexp
68
+ nm = nil
69
+ nm = tmp.param_names[0] if params[tmp.param_names[0]]
70
+ nm ||= tmp.param_names[0].to_sym
71
+ url << "#{params.delete nm}/" if params[nm] && params[nm].to_s =~ tmp.controller
72
+ else
73
+ next
74
+ end
75
+ end
76
+ return nil if r.nil?
77
+ case method_sym.to_sym
78
+ when :new
79
+ params.delete :id
80
+ params.delete :_method
81
+ params.delete '_method'.freeze
82
+ params['id'.freeze] = :new
83
+ when :create
84
+ params['id'.freeze] = :new
85
+ params.delete :id
86
+ params['_method'.freeze] = :post
87
+ params.delete :_method
88
+ when :update
89
+ params.delete :_method
90
+ params['_method'.freeze] = :put
91
+ when :delete
92
+ params.delete :_method
93
+ params['_method'.freeze] = :delete
94
+ when :index
95
+ params.delete 'id'.freeze
96
+ params.delete '_method'.freeze
97
+ params.delete :id
98
+ params.delete :_method
99
+ when :show
100
+ raise "The URL for ':show' MUST contain a valid 'id' parameter for the object's index to display." unless params['id'.freeze].nil? && params[:id].nil?
101
+ params.delete '_method'.freeze
102
+ params.delete :_method
103
+ else
104
+ params.delete :id
105
+ params['id'.freeze] = method_sym
106
+ end
107
+ names = r.param_names
108
+ url.chomp! '/'.freeze
109
+ url << r.prefix
110
+ url.clear if url == '/'.freeze
111
+ while names.any? && params[name[0]]
112
+ url << "/#{Rack::Utils.escape params[names.shift]}"
113
+ end
114
+ url = '/'.dup if url.empty?
115
+ (url << '?') << Rack::Utils.build_nested_query(params) if params.any?
116
+ url
117
+ end
118
+ end
119
+ end
120
+ end
data/lib/plezi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Plezi
2
- VERSION = "0.12.22"
2
+ VERSION = '0.14.0'.freeze
3
3
  end
@@ -0,0 +1,91 @@
1
+ require 'set'
2
+ require 'securerandom'
3
+ require 'yaml'
4
+ module Plezi
5
+ module Base
6
+ module MessageDispatch
7
+ class << self
8
+ # Allows pub/sub drivers to attach to the message dispatch using `MessageDispatch.drivers << driver`
9
+ attr_reader :drivers
10
+ end
11
+ @drivers = [].to_set
12
+
13
+ module_function
14
+
15
+ def pid
16
+ @uuid ||= SecureRandom.urlsafe_base64.tap { |str| @prefix_len = str.length }
17
+ end
18
+
19
+ def _init
20
+ @drivers.each(&:connect)
21
+ end
22
+
23
+ def push(message)
24
+ # message[:type] = message[:type].name if message[:type].is_a?(Class)
25
+ message[:origin] = pid
26
+ hst = message.delete(:host) || Plezi.app_name
27
+ yml = message.to_yaml
28
+ @drivers.each { |d| d.push(hst, yml) }
29
+ end
30
+
31
+ def <<(msg)
32
+ @safe_types ||= [Symbol, Date, Time, Encoding, Struct, Regexp, Range, Set].freeze
33
+ msg = YAML.safe_load(msg, @safe_types)
34
+ return if msg[:origin] == pid
35
+ msg[:type] ||= msg['type'.freeze]
36
+ msg[:type] = Object.const_get msg[:type] if msg[:type] && msg[:type] != :all
37
+ if msg[:target] ||= msg['target'.freeze]
38
+ Iodine::Websocket.defer(target2uuid(msg[:target])) { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[msg[:event]], *(msg[:args] ||= msg['args'.freeze] || []))) if ws._pl_ws_map[msg[:event] ||= msg['event'.freeze]] }
39
+ elsif (msg[:type]) == :all
40
+ Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[msg[:event]], *(msg[:args] ||= msg['args'.freeze] || []))) if ws._pl_ws_map[msg[:event] ||= msg['event'.freeze]] }
41
+ else
42
+ Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[msg[:event]], *(msg[:args] ||= msg['args'.freeze] || []))) if ws.is_a?(msg[:type]) && msg[:type]._pl_ws_map[msg[:event] ||= msg['event'.freeze]] }
43
+ end
44
+
45
+ rescue => e
46
+ puts '*** The following could be a security breach attempt:', e.message, e.backtrace
47
+ nil
48
+ end
49
+
50
+ def unicast(_sender, target, meth, args)
51
+ return false if target.nil?
52
+ if (tuuid = target2uuid)
53
+ Iodine::Websocket.defer(tuuid) { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws._pl_ws_map[meth] }
54
+ return true
55
+ end
56
+ push target: target, args: args, host: target2pid(target)
57
+ end
58
+
59
+ def broadcast(sender, meth, args)
60
+ if sender.is_a?(Class)
61
+ Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws.is_a?(sender) && ws._pl_ws_map[meth] }
62
+ push type: sender.name, args: args, event: meth
63
+ else
64
+ sender.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws.is_a?(sender.class) && ws._pl_ws_map[meth] }
65
+ push type: sender.class.name, args: args, event: meth
66
+ end
67
+ end
68
+
69
+ def multicast(sender, meth, args)
70
+ if sender.is_a?(Class)
71
+ Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws._pl_ws_map[meth] }
72
+ push type: :all, args: args, event: meth
73
+ else
74
+ sender.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws._pl_ws_map[meth] }
75
+ push type: :all, args: args, event: meth
76
+ end
77
+ end
78
+
79
+ def target2uuid(target)
80
+ return nil unless target.start_with? pid
81
+ target[@prefix_len..-1].to_i
82
+ end
83
+
84
+ def target2pid(target)
85
+ target ? target[0..(@prefix_len - 1)] : Plezi.app_name
86
+ end
87
+ end
88
+ end
89
+ end
90
+ # connect default drivers
91
+ require 'plezi/websockets/redis'
@@ -0,0 +1,55 @@
1
+ require 'securerandom'
2
+ module Plezi
3
+ module Base
4
+ module MessageDispatch
5
+ module RedisDriver
6
+ @redis_locker ||= Mutex.new
7
+ @redis = @redis_sub_thread = nil
8
+
9
+ module_function
10
+
11
+ def connect
12
+ return false unless ENV['PL_REDIS_URL'] && defined?(::Redis)
13
+ return @redis if (@redis_sub_thread && @redis_sub_thread.alive?) && @redis
14
+ @redis_locker.synchronize do
15
+ return @redis if (@redis_sub_thread && @redis_sub_thread.alive?) && @redis # repeat the test inside syncing, things change.
16
+ @redis.quit if @redis
17
+ @redis = ::Redis.new(url: ENV['PL_REDIS_URL'])
18
+ raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless @redis
19
+ @redis_sub_thread = Thread.new do
20
+ begin
21
+ ::Redis.new(url: ENV['PL_REDIS_URL']).subscribe(::Plezi.app_name, ::Plezi::Base::MessageDispatch.pid) do |on|
22
+ on.message do |_channel, msg|
23
+ ::Plezi::Base::MessageDispatch << msg
24
+ end
25
+ end
26
+ rescue => e
27
+ puts e.message, e.backtrace
28
+ retry
29
+ end
30
+ end
31
+ @redis
32
+ end
33
+ end
34
+
35
+ # Get the current redis connection.
36
+ def redis
37
+ @redis || connect
38
+ end
39
+
40
+ def push(channel, message)
41
+ return unless connect
42
+ return if away?(channel)
43
+ redis.publish(channel, message)
44
+ end
45
+
46
+ def away?(server)
47
+ return true unless connect
48
+ @redis.pubsub('CHANNELS', server).empty?
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ::Plezi::Base::MessageDispatch.drivers << ::Plezi::Base::MessageDispatch::RedisDriver
data/plezi.gemspec CHANGED
@@ -4,25 +4,34 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'plezi/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "plezi"
7
+ spec.name = 'plezi'
8
8
  spec.version = Plezi::VERSION
9
- spec.authors = ["Boaz Segev"]
10
- spec.email = ['boaz@2be.co.il']
11
- spec.summary = %q{Plezi - the easy way to add Websockets, RESTful routing and HTTP streaming services to Ruby Web-Apps.}
12
- spec.description = %q{Plezi - the easy way to add Websockets, RESTful routing and HTTP streaming services to Ruby Web-Apps.}
13
- spec.homepage = "http://www.plezi.io/"
14
- spec.license = "MIT"
9
+ spec.authors = ['Boaz Segev']
10
+ spec.email = ['bo@plezi.io']
15
11
 
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
12
+ spec.summary = 'The Plezi.io Ruby Framework for real time web applications.'
13
+ spec.description = 'The Plezi.io Ruby Framework for real time web applications.'
14
+ spec.homepage = 'http://plezi.io'
15
+ spec.license = 'MIT'
20
16
 
21
- spec.add_dependency "iodine", "~> 0.1.20"
22
- spec.add_development_dependency "bundler", "~> 1.7"
23
- spec.add_development_dependency "rake", "~> 10.0"
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
+ else
22
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
23
+ end
24
24
 
25
- spec.post_install_message = "Thank you for installing Plezi, the native Ruby Framework for real time web-apps."
26
- # spec.post_install_message = "** Deprecation Warning:\n\nThank you for installing Plezi, the native Ruby Framework for real time web-apps."
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
27
29
 
30
+ spec.add_dependency 'iodine', '~> 0.2.0'
31
+ # spec.add_dependency 'redcarpet', '> 3.3.0'
32
+ # spec.add_dependency 'slim', '> 3.0.0'
33
+
34
+ spec.add_development_dependency 'bundler', '~> 1.12'
35
+ spec.add_development_dependency 'rake', '~> 10.0'
36
+ spec.add_development_dependency 'minitest', '~> 5.0'
28
37
  end