modulation 0.6
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.
- checksums.yaml +7 -0
- data/README.md +277 -0
- data/lib/modulation.rb +234 -0
- data/lib/modulation/gem.rb +16 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 920b7e8e5d77f1c94944970aec2cda441f7dfadb
|
4
|
+
data.tar.gz: e7422d6abea4eb6b88c4435f6fd4a1dab1e86018
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 70a95296ebf8feecbbe2361cbd8e021145d8cbad9fb6e7651acf72001bcf50f26dc33ee27fe309c04a711f050e2cd96ade41f5d0416c10b8c845b91eee7c5615
|
7
|
+
data.tar.gz: 85774eec2d3b3b34f48fffacad087ef2cd15dca5e346e3402732f9bfb7866237ba47991119138119eefe8250833331abbe7f6def0d9799f6dd29aec8d133f263
|
data/README.md
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
# Modulation - explicit dependencies for Ruby
|
2
|
+
|
3
|
+
Modulation provides an alternative way to organize Ruby code. Instead of
|
4
|
+
littering the global namespace with classes and modules, Mrodulation lets you
|
5
|
+
explicitly import and export declarations in order to better control
|
6
|
+
dependencies in your codebase.
|
7
|
+
|
8
|
+
With Modulation, you always know where a module comes from, and you have full
|
9
|
+
control over which parts of a module's code you wish to expose to the outside
|
10
|
+
world. With Modulation, you can more easily write in a functional style with a
|
11
|
+
minimum of boilerplate code.
|
12
|
+
|
13
|
+
> **Important notice**: Modulation is currently at an experimental stage. Use
|
14
|
+
> it at your own risk!
|
15
|
+
|
16
|
+
## Rationale
|
17
|
+
|
18
|
+
Splitting your Ruby code into multiple files loaded using `require` poses a
|
19
|
+
number of problems:
|
20
|
+
|
21
|
+
- Once a file is `require`d, any class, module or constant in it is available
|
22
|
+
to any other file in your codebase. All "globals" (classes, modules,
|
23
|
+
constants) are loaded, well, globally, in a single namespace. Namespace
|
24
|
+
collisions are easy in Ruby.
|
25
|
+
- Since a `require` can appear in any file in your code, it's easy to lose
|
26
|
+
track of where a certain file was required and where it is used.
|
27
|
+
- To avoid class name ocnflicts, classes need to be nested under a single
|
28
|
+
hierarchical tree, sometime reaching 4 levels or more, i.e.
|
29
|
+
`ActiveSupport::Messages::Rotator::Encryptor`.
|
30
|
+
- There's no easy way to control the visibility of specific so-called globals.
|
31
|
+
Everything is wide-open.
|
32
|
+
- Writing reusable functional code requires wrapping it in modules using
|
33
|
+
`class << self`, `def self.foo ...` or `include Singleton`.
|
34
|
+
|
35
|
+
Personally, I have found that managing dependencies with `require` over in
|
36
|
+
large codebases is... not as elegant or painfree as I would expect from a
|
37
|
+
first-class development environment.
|
38
|
+
|
39
|
+
So I came up with Modulation, a small gem that takes a different approach to
|
40
|
+
organizing Ruby code: any so-called global declarations are hidden unless
|
41
|
+
explicitly exported, and the global namespace remains clutter-free. All
|
42
|
+
dependencies between source files are explicit, and are easily grokked.
|
43
|
+
|
44
|
+
Here's a simple example:
|
45
|
+
|
46
|
+
*math.rb*
|
47
|
+
```ruby
|
48
|
+
export :fib
|
49
|
+
|
50
|
+
def fib(n)
|
51
|
+
(0..1).include?(n) ? n : (fib(n - 1) + fib(n - 2))
|
52
|
+
end
|
53
|
+
```
|
54
|
+
*app.rb*
|
55
|
+
```ruby
|
56
|
+
require 'modulation'
|
57
|
+
Math = import('./math')
|
58
|
+
puts Math.fib(10)
|
59
|
+
```
|
60
|
+
|
61
|
+
## Organizing Ruby code base with Modul
|
62
|
+
|
63
|
+
Any Ruby source file can be a module. Modules can export declarations (usually
|
64
|
+
an API for a specific functionality) to be shared with other modules. Modules
|
65
|
+
can also import declarations from other modules.
|
66
|
+
|
67
|
+
Each module is loaded and evaluated in the context of a newly-created `Module`,
|
68
|
+
then transformed into a class and handed off to the importing module.
|
69
|
+
|
70
|
+
### Exporting declarations
|
71
|
+
|
72
|
+
Any class, module or constant be exported using `export`:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
export :User, :Session
|
76
|
+
|
77
|
+
class User
|
78
|
+
...
|
79
|
+
end
|
80
|
+
|
81
|
+
class Session
|
82
|
+
...
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
A module may also expose a set of methods without using `class << self`, for
|
87
|
+
example when writing in a functional style:
|
88
|
+
|
89
|
+
*seq.rb*
|
90
|
+
```ruby
|
91
|
+
export :fib, :luc
|
92
|
+
|
93
|
+
def fib(n)
|
94
|
+
(0..1).include?(n) ? n : (fib(n - 1) + fib(n - 2))
|
95
|
+
end
|
96
|
+
|
97
|
+
def luc(n)
|
98
|
+
(0..1).include?(n) ? (2 - n) : (luc(n - 1) + luc(n - 2))
|
99
|
+
end
|
100
|
+
```
|
101
|
+
*app.rb*
|
102
|
+
```ruby
|
103
|
+
require 'modulation'
|
104
|
+
Seq = import('./seq')
|
105
|
+
puts Seq.fib(10)
|
106
|
+
```
|
107
|
+
|
108
|
+
### Importing declarations
|
109
|
+
|
110
|
+
Declarations from another module can be imported using `import`:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
require 'modulation'
|
114
|
+
Models = import('./models')
|
115
|
+
...
|
116
|
+
|
117
|
+
user = Models::User.new(...)
|
118
|
+
|
119
|
+
...
|
120
|
+
```
|
121
|
+
|
122
|
+
Alternatively, a module interested in a single declaration from another module
|
123
|
+
can use the following technique:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
require 'modulation'
|
127
|
+
User = import('./models')::User
|
128
|
+
...
|
129
|
+
|
130
|
+
user = User.new(...)
|
131
|
+
```
|
132
|
+
|
133
|
+
> **Note about paths**: module paths are always relative to the file
|
134
|
+
> calling the `import` method.
|
135
|
+
|
136
|
+
### Default exports
|
137
|
+
|
138
|
+
A module may wish to expose just a single class or constant, in which case it
|
139
|
+
can use `export_default`:
|
140
|
+
|
141
|
+
*user.rb*
|
142
|
+
```ruby
|
143
|
+
export_default :User
|
144
|
+
|
145
|
+
class User
|
146
|
+
...
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
*app.rb*
|
151
|
+
```ruby
|
152
|
+
require 'modulation'
|
153
|
+
User = import('./user')
|
154
|
+
User.new(...)
|
155
|
+
```
|
156
|
+
|
157
|
+
The default exported value can also be defined directly thus:
|
158
|
+
|
159
|
+
*config.rb*
|
160
|
+
```ruby
|
161
|
+
export_default(
|
162
|
+
host: 'localhost',
|
163
|
+
port: 1234,
|
164
|
+
...
|
165
|
+
)
|
166
|
+
```
|
167
|
+
|
168
|
+
*app.rb*
|
169
|
+
```ruby
|
170
|
+
config = import('./config')
|
171
|
+
db.connect(config[:host], config[:port])
|
172
|
+
```
|
173
|
+
|
174
|
+
### Importing methods into classes and modules
|
175
|
+
|
176
|
+
Modulation provides the `extend_from` and `include_from` methods to include
|
177
|
+
imported methods in classes and modules:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
module Sequences
|
181
|
+
extend_from('./seq.rb')
|
182
|
+
end
|
183
|
+
|
184
|
+
Sequences.fib(5)
|
185
|
+
|
186
|
+
# extend integers
|
187
|
+
class Integer
|
188
|
+
include_from('./seq.rb')
|
189
|
+
|
190
|
+
def seq(kind)
|
191
|
+
send(kind, self)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
5.seq(:fib)
|
196
|
+
```
|
197
|
+
|
198
|
+
### Accessing the global namespace
|
199
|
+
|
200
|
+
If you need to access the global namespace inside a module just prefix the
|
201
|
+
class name with double colons:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
class ::GlobalClass
|
205
|
+
...
|
206
|
+
end
|
207
|
+
|
208
|
+
::ENV = { ... }
|
209
|
+
|
210
|
+
what = ::MEANING_OF_LIFE
|
211
|
+
```
|
212
|
+
|
213
|
+
## Writing gems using Modulation
|
214
|
+
|
215
|
+
Modulation can be used to write gems, providing fine-grained control over your
|
216
|
+
gem's public APIs and letting you hide any implementation details. In order to
|
217
|
+
allow loading a gem using either `require` or `import`, code your gem's main
|
218
|
+
file normally, but add `require 'modulation/gem'` at the top, and export your
|
219
|
+
gem's main namespace as a default export, e.g.:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
require 'modulation/gem'
|
223
|
+
|
224
|
+
export_default :MyGem
|
225
|
+
|
226
|
+
module MyGem
|
227
|
+
...
|
228
|
+
MyClass = import('my_gem/my_class')
|
229
|
+
...
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
233
|
+
## Importing gems using Modulation
|
234
|
+
|
235
|
+
Gems written using modulation can also be loaded using `import`. If modulation
|
236
|
+
does not find the module specified by the given relative path, it will attempt
|
237
|
+
to load a gem by the same name.
|
238
|
+
|
239
|
+
> **Note**: using `import` to load a gem is very much *alpha*, and might
|
240
|
+
> introduce problems not encountered when loading with `require` such as
|
241
|
+
> shadowing of global namespaces, or any other bizarre and unexpected
|
242
|
+
> behaviors. Actually, there's not much point in using it to load a gem which
|
243
|
+
> does not use Modulation. When loading gems using import, Modulation will
|
244
|
+
> raise an exception if no symbols were exported by the gem.
|
245
|
+
|
246
|
+
## Coding style recommendations
|
247
|
+
|
248
|
+
* Import modules into constants, not into variables:
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
Settings = import('./settings')
|
252
|
+
```
|
253
|
+
|
254
|
+
* Place your exports at the top of your module:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
export :foo, :bar, :baz
|
258
|
+
|
259
|
+
...
|
260
|
+
```
|
261
|
+
|
262
|
+
* Place your imports at the top of your module:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
Foo = import('./foo')
|
266
|
+
Bar = import('./bar')
|
267
|
+
Baz = import('./baz')
|
268
|
+
|
269
|
+
...
|
270
|
+
```
|
271
|
+
|
272
|
+
## Known limitations and problems
|
273
|
+
|
274
|
+
- Modulation is (probably) not production-ready.
|
275
|
+
- Modulation probably doesn't play well with `Marshal`.
|
276
|
+
- Modulation probably doesn't play well with code-analysis tools.
|
277
|
+
- Modulation doesn't play well with rdoc/yard.
|
data/lib/modulation.rb
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Kernel extensions - modul's API
|
5
|
+
module Kernel
|
6
|
+
# Returns an encapsulated imported module.
|
7
|
+
# @param fn [String] module file name
|
8
|
+
# @param caller_location [String] caller location
|
9
|
+
# @return [Class] module facade
|
10
|
+
def import(fn, caller_location = caller.first)
|
11
|
+
Modulation.import_module(fn, caller_location)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Object extensions
|
16
|
+
class Object
|
17
|
+
# Returns the objects metaclass (shamelessly stolen from the metaid gem).
|
18
|
+
# @return [Class] object's metaclass
|
19
|
+
def metaclass; class << self; self; end; end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Module
|
23
|
+
# Extends the receiver with exported methods from the given file name
|
24
|
+
# @param fn [String] module filename
|
25
|
+
# @return [void]
|
26
|
+
def extend_from(fn)
|
27
|
+
mod = import(fn, caller.first)
|
28
|
+
mod.methods(false).each do |sym|
|
29
|
+
metaclass.send(:define_method, sym, mod.method(sym).to_proc)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Includes exported methods from the given file name in the receiver
|
34
|
+
# The module's methods will be available as instance methods
|
35
|
+
# @param fn [String] module filename
|
36
|
+
# @return [void]
|
37
|
+
def include_from(fn)
|
38
|
+
mod = import(fn, caller.first)
|
39
|
+
mod.methods(false).each do |sym|
|
40
|
+
send(:define_method, sym, mod.method(sym).to_proc)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Modulation
|
46
|
+
@@loaded_modules = {}
|
47
|
+
@@full_backtrace = false
|
48
|
+
|
49
|
+
def self.full_backtrace!
|
50
|
+
@@full_backtrace = true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Imports a module from a file
|
54
|
+
# If the module is already loaded, returns the loaded module.
|
55
|
+
# @param fn [String] source file name (with or without extension)
|
56
|
+
# @param caller_location [String]
|
57
|
+
# @return [Module] loaded module object
|
58
|
+
def self.import_module(fn, caller_location = caller.first)
|
59
|
+
fn = module_absolute_path(fn, caller_location)
|
60
|
+
@@loaded_modules[fn] ||= create_module_from_file(fn)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.module_absolute_path(fn, caller_location)
|
64
|
+
orig_fn = fn
|
65
|
+
caller_file = (caller_location =~ /^([^\:]+)\:/) ?
|
66
|
+
$1 : (raise "Could not expand path")
|
67
|
+
fn = File.expand_path(fn, File.dirname(caller_file))
|
68
|
+
if File.file?("#{fn}.rb")
|
69
|
+
fn + '.rb'
|
70
|
+
else
|
71
|
+
if File.file?(fn)
|
72
|
+
return fn
|
73
|
+
else
|
74
|
+
lookup_gem(orig_fn) || (raise "Module not found: #{fn}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.lookup_gem(name)
|
80
|
+
spec = Gem::Specification.find_by_name(name)
|
81
|
+
fn = File.join(spec.full_require_paths, "#{name}.rb")
|
82
|
+
File.file?(fn) ? fn : nil
|
83
|
+
rescue Gem::MissingSpecError
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates a new module from a source file
|
88
|
+
# @param fn [String] source file name
|
89
|
+
# @return [Module] module
|
90
|
+
def self.create_module_from_file(fn)
|
91
|
+
make_module(location: fn)
|
92
|
+
rescue => e
|
93
|
+
if @@full_backtrace
|
94
|
+
raise
|
95
|
+
else
|
96
|
+
# remove *modul* methods from backtrace and reraise
|
97
|
+
backtrace = e.backtrace.reject {|l| l.include?(__FILE__)}
|
98
|
+
raise(e, e.message, backtrace)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Loads a module from file or block, wrapping it in a module facade
|
103
|
+
# @param info [Hash] module info
|
104
|
+
# @param block [Proc] module block
|
105
|
+
# @return [Class] module facade
|
106
|
+
def self.make_module(info, &block)
|
107
|
+
export_default = nil
|
108
|
+
m = initialize_module {|v| export_default = v}
|
109
|
+
m.__module_info = info
|
110
|
+
load_module_code(m, info, &block)
|
111
|
+
|
112
|
+
if export_default
|
113
|
+
transform_export_default_value(export_default, m)
|
114
|
+
else
|
115
|
+
m.tap {m.__set_exported_symbols(m, m.__exported_symbols)}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns exported value for a default export
|
120
|
+
# If the given value is a symbol, returns the value of the corresponding
|
121
|
+
# constant.
|
122
|
+
# @param value [any] export_default value
|
123
|
+
# @param mod [Module] module
|
124
|
+
# @return [any] exported value
|
125
|
+
def self.transform_export_default_value(value, mod)
|
126
|
+
if value.is_a?(Symbol) && mod.metaclass.const_defined?(value)
|
127
|
+
mod.metaclass.const_get(value)
|
128
|
+
else
|
129
|
+
value
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Initializes a new module ready to evaluate a file module
|
134
|
+
# @note The given block is used to pass the value given to `export_default`
|
135
|
+
# @return [Module] new module
|
136
|
+
def self.initialize_module(&export_default_block)
|
137
|
+
Module.new.tap do |m|
|
138
|
+
m.extend(ModuleMethods)
|
139
|
+
m.metaclass.include(ModuleMetaclassMethods)
|
140
|
+
m.__export_default_block = export_default_block
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Loads a source file or a block into the given module
|
145
|
+
# @param m [Module] module
|
146
|
+
# @param fn [String] source file path
|
147
|
+
# @return [void]
|
148
|
+
def self.load_module_code(m, info)
|
149
|
+
fn = info[:location]
|
150
|
+
m.instance_eval(IO.read(fn), fn)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Module façade methods
|
154
|
+
module ModuleMethods
|
155
|
+
# Responds to missing constants by checking metaclass
|
156
|
+
# If the given constant is defined on the metaclass, the same constant is
|
157
|
+
# defined on self and its value is returned. This is essential to
|
158
|
+
# supporting constants in modules.
|
159
|
+
# @param name [Symbol] constant name
|
160
|
+
# @return [any] constant value
|
161
|
+
def const_missing(name)
|
162
|
+
if metaclass.const_defined?(name)
|
163
|
+
unless !@__exported_symbols || @__exported_symbols.include?(name)
|
164
|
+
raise NameError, "private constant `#{name}' accessed in #{inspect}", caller
|
165
|
+
end
|
166
|
+
metaclass.const_get(name).tap {|value| const_set(name, value)}
|
167
|
+
else
|
168
|
+
raise NameError, "uninitialized constant #{inspect}::#{name}", caller
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# read and write module information
|
173
|
+
attr_accessor :__module_info
|
174
|
+
|
175
|
+
# Sets exported_symbols ivar and marks all non-exported methods as private
|
176
|
+
# @param m [Module] imported module
|
177
|
+
# @param symbols [Array] array of exported symbols
|
178
|
+
# @return [void]
|
179
|
+
def __set_exported_symbols(m, symbols)
|
180
|
+
@__exported_symbols = symbols
|
181
|
+
metaclass.instance_methods(false).each do |m|
|
182
|
+
metaclass.send(:private, m) unless symbols.include?(m)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns a text representation of the module for inspection
|
187
|
+
# @return [String] module string representation
|
188
|
+
def inspect
|
189
|
+
module_name = name || 'Module'
|
190
|
+
if __module_info[:location]
|
191
|
+
"#{module_name}:#{__module_info[:location]}"
|
192
|
+
else
|
193
|
+
"#{module_name}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Module façade metaclass methods
|
199
|
+
module ModuleMetaclassMethods
|
200
|
+
# Adds given symbols to the exported_symbols array
|
201
|
+
# @param symbols [Array] array of symbols
|
202
|
+
# @return [void]
|
203
|
+
def export(*symbols)
|
204
|
+
symbols = symbols.first if Array === symbols.first
|
205
|
+
__exported_symbols.concat(symbols)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Sets a module's value, so when imported it will represent the given value,
|
209
|
+
# instead of a module facade
|
210
|
+
# @param v [Symbol, any] symbol or value
|
211
|
+
# @return [void]
|
212
|
+
def export_default(v)
|
213
|
+
@__export_default_block.call(v) if @__export_default_block
|
214
|
+
end
|
215
|
+
|
216
|
+
# read and write module info
|
217
|
+
attr_accessor :__module_info
|
218
|
+
|
219
|
+
# Returns exported_symbols array
|
220
|
+
# @return [Array] array of exported symbols
|
221
|
+
def __exported_symbols
|
222
|
+
@exported_symbols ||= []
|
223
|
+
end
|
224
|
+
|
225
|
+
# Sets export_default block, used for setting the returned module object to
|
226
|
+
# a class or constant
|
227
|
+
# @param block [Proc] default export block
|
228
|
+
# @return [void]
|
229
|
+
def __export_default_block=(block)
|
230
|
+
@__export_default_block = block
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative('../modulation')
|
2
|
+
|
3
|
+
# Kernel extensions - mock up the Modulation API with nop methods, so
|
4
|
+
# requiring a gem would work. Sample usage:
|
5
|
+
#
|
6
|
+
# require 'modulation/gem'
|
7
|
+
# export_default :MyGem
|
8
|
+
#
|
9
|
+
# module MyGem
|
10
|
+
# MyClass = import('my_class')
|
11
|
+
# MyOtherClass = import('my_other_class')
|
12
|
+
# end
|
13
|
+
module Kernel
|
14
|
+
def export(*args); end
|
15
|
+
def export_default(v); end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: modulation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.6'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sharon Rosner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "Modulation provides an alternative way to organize Ruby code. Instead
|
14
|
+
of \nlittering the global namespace with classes and modules, Modulation lets\nyou
|
15
|
+
explicitly import and export declarations in order to better control \ndependencies
|
16
|
+
in your codebase.\n\nWith Modulation, you always know where a module comes from,
|
17
|
+
and you have\nfull control over which parts of a module's code you wish to expose
|
18
|
+
to the \noutside world. With Modulation, you can more easily write in a functional\nstyle
|
19
|
+
with a minimum of boilerplate code.\n"
|
20
|
+
email: ciconia@gmail.com
|
21
|
+
executables: []
|
22
|
+
extensions: []
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.md
|
25
|
+
files:
|
26
|
+
- README.md
|
27
|
+
- lib/modulation.rb
|
28
|
+
- lib/modulation/gem.rb
|
29
|
+
homepage: http://github.com/ciconia/modulation
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata:
|
33
|
+
source_code_uri: https://github.com/ciconia/modulation
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- "--title"
|
37
|
+
- Modulation
|
38
|
+
- "--main"
|
39
|
+
- README.md
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 2.6.13
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: 'Modulation: explicit dependencies for Ruby'
|
58
|
+
test_files: []
|