jsus 0.2.7 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +6 -0
- data/CHANGELOG +4 -1
- data/Gemfile +4 -1
- data/VERSION +1 -1
- data/bin/jsus +30 -9
- data/features/command-line/compression.feature +24 -0
- data/features/command-line/mooforge_compatibility_layer.feature +75 -0
- data/features/data/Compression/Source/Library/Color.js +17 -0
- data/features/data/Compression/Source/Widget/Input/Input.Color.js +21 -0
- data/features/data/Compression/package.yml +9 -0
- data/features/data/MooforgePlugin/Core/Source/Core.js +16 -0
- data/features/data/MooforgePlugin/Core/package.yml +8 -0
- data/features/data/MooforgePlugin/Plugin/Source/plugin-support.js +20 -0
- data/features/data/MooforgePlugin/Plugin/Source/plugin.js +21 -0
- data/features/data/MooforgePlugin/Plugin/package.yml +9 -0
- data/features/data/compression.min.js +1 -0
- data/jsus.gemspec +45 -22
- data/lib/jsus/middleware.rb +178 -0
- data/lib/jsus/package.rb +10 -10
- data/lib/jsus/source_file.rb +58 -30
- data/lib/jsus/tag.rb +18 -10
- data/lib/jsus/util/file_cache.rb +57 -0
- data/lib/jsus/util/inflection.rb +39 -0
- data/lib/jsus/util.rb +2 -0
- data/lib/jsus.rb +2 -0
- data/spec/data/ComplexDependencies/Mootools/Source/Core.js +16 -0
- data/spec/data/ComplexDependencies/Mootools/package.yml +8 -0
- data/spec/data/ComplexDependencies/Output/package.js +70 -0
- data/spec/data/ComplexDependencies/Output/scripts.json +14 -0
- data/spec/data/ComplexDependencies/Output/tree.json +33 -0
- data/spec/data/ComplexDependencies/Source/Library/Color.js +19 -0
- data/spec/data/ComplexDependencies/Source/Widget/Input/Input.Color.js +20 -0
- data/spec/data/ComplexDependencies/Source/Widget/Input.js +16 -0
- data/spec/data/ComplexDependencies/package.yml +10 -0
- data/spec/data/mooforge_quirky_source.js +20 -0
- data/spec/data/unicode_source.js +23 -0
- data/spec/data/unicode_source_with_bom.js +23 -0
- data/spec/jsus/middleware_spec.rb +216 -0
- data/spec/jsus/package_spec.rb +12 -6
- data/spec/jsus/source_file_spec.rb +42 -14
- data/spec/jsus/tag_spec.rb +8 -2
- data/spec/jsus/util/file_cache_spec.rb +67 -0
- data/spec/jsus/util/inflection_spec.rb +42 -0
- metadata +124 -62
- data/Gemfile.lock +0 -79
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
module Jsus
|
3
|
+
#
|
4
|
+
# Rack middleware
|
5
|
+
#
|
6
|
+
# Just "use Jsus::Middleware" in your rack application and all the requests
|
7
|
+
# to /javascripts/jsus/* are redirected to the middleware.
|
8
|
+
#
|
9
|
+
# Configuration:
|
10
|
+
#
|
11
|
+
# Use Jsus::Middleware.settings= method to change some settings, such as:
|
12
|
+
# :packages_dir - path to where you store your packages
|
13
|
+
# :cache - enable simple file caching
|
14
|
+
# :cache_path - directory for file caching
|
15
|
+
# :prefix - change /jsus/ to something else or remove it altogether (set to nil)
|
16
|
+
# :cache_pool - cache js pool between requests. Can save you some time
|
17
|
+
# between requests but annoys a lot during development.
|
18
|
+
#
|
19
|
+
#
|
20
|
+
# Examples:
|
21
|
+
#
|
22
|
+
# GET /javascripts/jsus/Mootools.Core+Mootools.More
|
23
|
+
# merges packages named Mootools.Core and Mootools.More with all the
|
24
|
+
# dependencies and outputs the result.
|
25
|
+
#
|
26
|
+
# GET /javascripts/jsus/Mootools.More~Mootools.Core
|
27
|
+
# returns package Mootools.More with all the dependencies MINUS any of
|
28
|
+
# Mootools.Core dependencies.
|
29
|
+
#
|
30
|
+
# GET /javascripts/jsus/Mootools.Core:Class+Mootools.More:Fx
|
31
|
+
# same thing but for source files providing Mootools.Core/Class and
|
32
|
+
# Mootools.More/Fx
|
33
|
+
#
|
34
|
+
#
|
35
|
+
# Also see sinatra example https://github.com/jsus/jsus-sinatra-app
|
36
|
+
#
|
37
|
+
class Middleware
|
38
|
+
include Rack
|
39
|
+
class <<self
|
40
|
+
DEFAULT_SETTINGS = {
|
41
|
+
:packages_dir => ".",
|
42
|
+
:cache => false,
|
43
|
+
:cache_path => nil,
|
44
|
+
:prefix => "jsus",
|
45
|
+
:cache_pool => true
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
def settings
|
49
|
+
@settings ||= DEFAULT_SETTINGS.dup
|
50
|
+
end # settings
|
51
|
+
|
52
|
+
def settings=(new_settings)
|
53
|
+
settings.merge!(new_settings)
|
54
|
+
end # settings=
|
55
|
+
|
56
|
+
def pool
|
57
|
+
@pool ||= Jsus::Pool.new(settings[:packages_dir])
|
58
|
+
end # pool
|
59
|
+
|
60
|
+
def cache?
|
61
|
+
settings[:cache]
|
62
|
+
end # cache?
|
63
|
+
|
64
|
+
def cache
|
65
|
+
@cache ||= cache? ? Util::FileCache.new(settings[:cache_path]) : nil
|
66
|
+
end # cache
|
67
|
+
end # class <<self
|
68
|
+
|
69
|
+
def initialize(app)
|
70
|
+
@app = app
|
71
|
+
end # initialize
|
72
|
+
|
73
|
+
def _call(env)
|
74
|
+
path = Utils.unescape(env["PATH_INFO"])
|
75
|
+
return @app.call(env) unless handled_by_jsus?(path)
|
76
|
+
path.sub!(path_prefix_regex, "")
|
77
|
+
components = path.split("/")
|
78
|
+
return @app.call(env) unless components.size >= 2
|
79
|
+
if components[0] == "require"
|
80
|
+
generate(components[1])
|
81
|
+
else
|
82
|
+
not_found!
|
83
|
+
end
|
84
|
+
end # _call
|
85
|
+
|
86
|
+
def call(env)
|
87
|
+
dup._call(env)
|
88
|
+
end # call
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def generate(path_string)
|
93
|
+
path_args = parse_path_string(path_string.sub(/.js$/, ""))
|
94
|
+
files = []
|
95
|
+
path_args[:include].each {|tag| files += get_associated_files(tag).to_a }
|
96
|
+
path_args[:exclude].each {|tag| files -= get_associated_files(tag).to_a }
|
97
|
+
if !files.empty?
|
98
|
+
response = Container.new(*files).map {|f| f.content }.join("\n")
|
99
|
+
cache.write(path_string, response) if cache?
|
100
|
+
respond_with(response)
|
101
|
+
else
|
102
|
+
not_found!
|
103
|
+
end
|
104
|
+
end # generate
|
105
|
+
|
106
|
+
# Notice: + is a space after url decoding
|
107
|
+
# input:
|
108
|
+
# "Package:A~Package:C Package:B~Other:D"
|
109
|
+
# output:
|
110
|
+
# {:include => ["Package/A", "Package/B"], :exclude => ["Package/C", "Other/D"]}
|
111
|
+
def parse_path_string(path_string)
|
112
|
+
path_string = " " + path_string unless path_string[0,1] =~ /\+\-/
|
113
|
+
included = []
|
114
|
+
excluded = []
|
115
|
+
path_string.scan(/([ ~])([^ ~]*)/) do |op, arg|
|
116
|
+
arg = arg.gsub(":", "/")
|
117
|
+
if op == " "
|
118
|
+
included << arg
|
119
|
+
else
|
120
|
+
excluded << arg
|
121
|
+
end
|
122
|
+
end
|
123
|
+
{:include => included, :exclude => excluded}
|
124
|
+
end # parse_path_string
|
125
|
+
|
126
|
+
def get_associated_files(source_file_or_package)
|
127
|
+
if package = pool.packages.detect {|pkg| pkg.name == source_file_or_package}
|
128
|
+
package.include_dependencies!
|
129
|
+
package.linked_external_dependencies.to_a + package.source_files.to_a
|
130
|
+
elsif source_file = pool.lookup(source_file_or_package)
|
131
|
+
pool.lookup_dependencies(source_file) << source_file
|
132
|
+
else
|
133
|
+
[]
|
134
|
+
end
|
135
|
+
end # get_associated_files
|
136
|
+
|
137
|
+
def not_found!
|
138
|
+
[404, {"Content-Type" => "text/plain"}, ["Jsus doesn't know anything about this entity"]]
|
139
|
+
end # not_found!
|
140
|
+
|
141
|
+
def respond_with(text)
|
142
|
+
[200, {"Content-Type" => "text/javascript"}, [text]]
|
143
|
+
end # respond_with
|
144
|
+
|
145
|
+
|
146
|
+
def handled_by_jsus?(path)
|
147
|
+
path =~ path_prefix_regex
|
148
|
+
end # handled_by_jsus?
|
149
|
+
|
150
|
+
def path_prefix
|
151
|
+
@path_prefix ||= self.class.settings[:prefix] ? "/javascripts/#{self.class.settings[:prefix]}/" : "/javascripts/"
|
152
|
+
end # path_prefix
|
153
|
+
|
154
|
+
def path_prefix_regex
|
155
|
+
@path_prefix_regex ||= %r{^#{path_prefix}}
|
156
|
+
end # path_prefix_regex
|
157
|
+
|
158
|
+
def pool
|
159
|
+
if cache_pool?
|
160
|
+
self.class.pool
|
161
|
+
else
|
162
|
+
@pool ||= Jsus::Pool.new(self.class.settings[:packages_dir])
|
163
|
+
end
|
164
|
+
end # pool
|
165
|
+
|
166
|
+
def cache?
|
167
|
+
self.class.cache?
|
168
|
+
end # cache?
|
169
|
+
|
170
|
+
def cache
|
171
|
+
self.class.cache
|
172
|
+
end # cache
|
173
|
+
|
174
|
+
def cache_pool?
|
175
|
+
self.class.settings[:cache_pool]
|
176
|
+
end # cache_pool?
|
177
|
+
end # class Middleware
|
178
|
+
end # module Jsus
|
data/lib/jsus/package.rb
CHANGED
@@ -7,7 +7,7 @@ module Jsus
|
|
7
7
|
attr_accessor :directory # directory which this package resides in (full path)
|
8
8
|
attr_accessor :pool # an instance of Pool
|
9
9
|
# Constructors
|
10
|
-
|
10
|
+
|
11
11
|
#
|
12
12
|
# Creates a package from given directory.
|
13
13
|
#
|
@@ -48,7 +48,7 @@ module Jsus
|
|
48
48
|
|
49
49
|
|
50
50
|
# Public API
|
51
|
-
|
51
|
+
|
52
52
|
# Returns a package.yml header.
|
53
53
|
def header
|
54
54
|
@header ||= {}
|
@@ -58,15 +58,15 @@ module Jsus
|
|
58
58
|
def name
|
59
59
|
header["name"] ||= ""
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
# Returns a package description.
|
63
63
|
def description
|
64
64
|
header["description"] ||= ""
|
65
|
-
end
|
65
|
+
end
|
66
66
|
|
67
67
|
# Returns a filename for compiled package.
|
68
68
|
def filename
|
69
|
-
header["filename"] ||= name + ".js"
|
69
|
+
header["filename"] ||= Jsus::Util::Inflection.snake_case(name) + ".js"
|
70
70
|
end
|
71
71
|
|
72
72
|
# Returns a list of sources filenames.
|
@@ -81,7 +81,7 @@ module Jsus
|
|
81
81
|
end
|
82
82
|
|
83
83
|
# Returns an array of provided tags names including those provided by linked external dependencies.
|
84
|
-
def provides_names
|
84
|
+
def provides_names
|
85
85
|
source_files.map {|s| s.provides_names(:short => true) }.flatten |
|
86
86
|
linked_external_dependencies.map {|d| d.provides_names }.flatten
|
87
87
|
end
|
@@ -103,7 +103,7 @@ module Jsus
|
|
103
103
|
def external_dependencies
|
104
104
|
source_files.map {|s| s.external_dependencies }.flatten
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
# Returns an array of external dependencies' names (including resolved ones).
|
108
108
|
def external_dependencies_names
|
109
109
|
external_dependencies.map {|d| d.name }
|
@@ -113,7 +113,7 @@ module Jsus
|
|
113
113
|
def linked_external_dependencies
|
114
114
|
@linked_external_dependencies ||= Container.new
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
# Compiles source files and linked external source files into a given category.
|
118
118
|
def compile(directory = ".")
|
119
119
|
fn = directory ? File.join(directory, filename) : nil
|
@@ -164,7 +164,7 @@ module Jsus
|
|
164
164
|
end
|
165
165
|
|
166
166
|
# Lists the required files for the package.
|
167
|
-
def required_files
|
167
|
+
def required_files
|
168
168
|
source_files.map {|s| s.required_files }.flatten
|
169
169
|
end
|
170
170
|
|
@@ -189,7 +189,7 @@ module Jsus
|
|
189
189
|
end
|
190
190
|
|
191
191
|
# Private API
|
192
|
-
|
192
|
+
|
193
193
|
def header=(new_header) # :nodoc:
|
194
194
|
@header = new_header
|
195
195
|
end
|
data/lib/jsus/source_file.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Jsus
|
2
2
|
# Generic exception for 'bad' source files (no yaml header, for example)
|
3
3
|
class BadSourceFileException < Exception; end
|
4
|
-
|
4
|
+
|
5
5
|
#
|
6
6
|
# SourceFile is a base for any Jsus operation.
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# It contains basic info about source as well as file content.
|
9
9
|
#
|
10
10
|
class SourceFile
|
@@ -29,7 +29,7 @@ module Jsus
|
|
29
29
|
|
30
30
|
#
|
31
31
|
# Initializes a SourceFile given the filename and options
|
32
|
-
#
|
32
|
+
#
|
33
33
|
# options:
|
34
34
|
# * <tt>:pool:</tt> -- an instance of Pool
|
35
35
|
# * <tt>:package:</tt> -- an instance of Package
|
@@ -38,9 +38,11 @@ module Jsus
|
|
38
38
|
#
|
39
39
|
def self.from_file(filename, options = {})
|
40
40
|
if File.exists?(filename)
|
41
|
-
source = File.
|
41
|
+
source = File.open(filename, 'r:utf-8') {|f| f.read }
|
42
|
+
bom = RUBY_VERSION =~ /1.9/ ? "\uFEFF" : "\xEF\xBB\xBF"
|
43
|
+
source.gsub!(bom, "")
|
42
44
|
yaml_data = source.match(%r(^/\*\s*(---.*?)\*/)m)
|
43
|
-
if (yaml_data && yaml_data[1] && header = YAML.load(yaml_data[1]))
|
45
|
+
if (yaml_data && yaml_data[1] && header = YAML.load(yaml_data[1]))
|
44
46
|
options[:header] = header
|
45
47
|
options[:relative_filename] = filename
|
46
48
|
options[:filename] = File.expand_path(filename)
|
@@ -78,7 +80,7 @@ module Jsus
|
|
78
80
|
header["description"]
|
79
81
|
end
|
80
82
|
|
81
|
-
#
|
83
|
+
#
|
82
84
|
# Returns an array of dependencies tags. Unordered.
|
83
85
|
#
|
84
86
|
def dependencies
|
@@ -95,7 +97,7 @@ module Jsus
|
|
95
97
|
#
|
96
98
|
def dependencies_names(options = {})
|
97
99
|
dependencies.map {|d| d.name(options) }
|
98
|
-
end
|
100
|
+
end
|
99
101
|
alias_method :requires_names, :dependencies_names
|
100
102
|
|
101
103
|
#
|
@@ -112,15 +114,15 @@ module Jsus
|
|
112
114
|
external_dependencies.map {|d| d.name }
|
113
115
|
end
|
114
116
|
|
115
|
-
#
|
117
|
+
#
|
116
118
|
# Returns an array with provides tags.
|
117
119
|
#
|
118
120
|
def provides
|
119
121
|
@provides
|
120
122
|
end
|
121
|
-
|
122
|
-
#
|
123
|
-
# Returns an array with provides names.
|
123
|
+
|
124
|
+
#
|
125
|
+
# Returns an array with provides names.
|
124
126
|
# Accepts options:
|
125
127
|
# * <tt>:short:</tt> -- whether provides should not prepend package name
|
126
128
|
# e.g. 'Class' instead of 'Core/Class' when in package 'Core')
|
@@ -134,13 +136,13 @@ module Jsus
|
|
134
136
|
def replaces
|
135
137
|
@replaces
|
136
138
|
end
|
137
|
-
|
139
|
+
|
138
140
|
|
139
141
|
#
|
140
142
|
# Returns a tag for source file, which this one is an extension for.
|
141
143
|
#
|
142
144
|
# E.g.: file Foo.js in package Core provides ['Class', 'Hash']. File Bar.js in package Bar
|
143
|
-
# extends 'Core/Class'. That means its contents would be appended to the Foo.js when compiling
|
145
|
+
# extends 'Core/Class'. That means its contents would be appended to the Foo.js when compiling
|
144
146
|
# the result.
|
145
147
|
#
|
146
148
|
def extends
|
@@ -162,7 +164,7 @@ module Jsus
|
|
162
164
|
@extensions = @extensions.flatten.compact.uniq
|
163
165
|
@extensions
|
164
166
|
end
|
165
|
-
|
167
|
+
|
166
168
|
def extensions=(new_value) # :nodoc:
|
167
169
|
@extensions = new_value
|
168
170
|
end
|
@@ -177,14 +179,14 @@ module Jsus
|
|
177
179
|
end
|
178
180
|
|
179
181
|
def include_extensions! # :nodoc:
|
180
|
-
if pool
|
182
|
+
if pool
|
181
183
|
provides.each do |p|
|
182
184
|
extensions << pool.lookup_extensions(p)
|
183
185
|
end
|
184
186
|
end
|
185
187
|
end
|
186
188
|
|
187
|
-
#
|
189
|
+
#
|
188
190
|
# Returns an array of files required by this files including all the filenames for extensions.
|
189
191
|
# SourceFile filename always goes first, all the extensions are unordered.
|
190
192
|
#
|
@@ -193,7 +195,7 @@ module Jsus
|
|
193
195
|
[filename, extensions.map {|e| e.filename}].flatten
|
194
196
|
end
|
195
197
|
|
196
|
-
#
|
198
|
+
#
|
197
199
|
# Returns a hash containing basic info with dependencies/provides tags' names
|
198
200
|
# and description for source file.
|
199
201
|
#
|
@@ -209,18 +211,26 @@ module Jsus
|
|
209
211
|
self.to_hash.inspect
|
210
212
|
end
|
211
213
|
# Private API
|
212
|
-
|
214
|
+
|
213
215
|
def header=(new_header) # :nodoc:
|
214
216
|
@header = new_header
|
215
217
|
# prepare defaults
|
216
218
|
@header["description"] ||= ""
|
217
219
|
# handle tags
|
218
|
-
@dependencies =
|
219
|
-
@
|
220
|
-
|
221
|
-
@
|
222
|
-
|
223
|
-
|
220
|
+
@dependencies = parse_tag_list(Array(@header["requires"]))
|
221
|
+
@provides = parse_tag_list(Array(@header["provides"]))
|
222
|
+
|
223
|
+
@extends = case @header["extends"]
|
224
|
+
when Array then Tag.new(@header["extends"][0])
|
225
|
+
when String then Tag.new(@header["extends"])
|
226
|
+
else nil
|
227
|
+
end
|
228
|
+
|
229
|
+
@replaces = case @header["replaces"]
|
230
|
+
when Array then Tag.new(@header["replaces"][0])
|
231
|
+
when String then Tag.new(@header["replaces"])
|
232
|
+
else nil
|
233
|
+
end
|
224
234
|
end
|
225
235
|
|
226
236
|
def content=(new_value) # :nodoc:
|
@@ -230,12 +240,30 @@ module Jsus
|
|
230
240
|
def content # :nodoc:
|
231
241
|
include_extensions
|
232
242
|
[@content, extensions.map {|e| e.content}].flatten.compact.join("\n")
|
233
|
-
end
|
234
|
-
|
243
|
+
end
|
244
|
+
|
235
245
|
def original_content # :nodoc:
|
236
246
|
@content
|
237
247
|
end
|
238
248
|
|
249
|
+
def parse_tag_list(tag_list)
|
250
|
+
tag_list.map do |tag_name|
|
251
|
+
case tag_name
|
252
|
+
when String
|
253
|
+
Tag.new(tag_name, :package => package)
|
254
|
+
when Hash
|
255
|
+
tags = []
|
256
|
+
tag_name.each do |pkg_name, sources|
|
257
|
+
normalized_package_name = pkg_name.sub(/(.+)\/.*$/, "\\1")
|
258
|
+
Array(sources).each do |source|
|
259
|
+
tags << Tag.new([normalized_package_name, source].join("/"))
|
260
|
+
end
|
261
|
+
end
|
262
|
+
tags
|
263
|
+
end
|
264
|
+
end.flatten
|
265
|
+
end # parse_tag_list
|
266
|
+
|
239
267
|
# Assigns an instance of Jsus::Pool to the source file.
|
240
268
|
# Also performs push to that pool.
|
241
269
|
def pool=(new_value)
|
@@ -247,17 +275,17 @@ module Jsus
|
|
247
275
|
def pool
|
248
276
|
@pool
|
249
277
|
end
|
250
|
-
|
278
|
+
|
251
279
|
def ==(other) # :nodoc:
|
252
280
|
eql?(other)
|
253
281
|
end
|
254
|
-
|
282
|
+
|
255
283
|
def eql?(other) # :nodoc:
|
256
284
|
other.kind_of?(SourceFile) && filename == other.filename
|
257
285
|
end
|
258
|
-
|
286
|
+
|
259
287
|
def hash
|
260
288
|
[self.class, filename].hash
|
261
289
|
end
|
262
290
|
end
|
263
|
-
end
|
291
|
+
end
|
data/lib/jsus/tag.rb
CHANGED
@@ -2,15 +2,15 @@ module Jsus
|
|
2
2
|
#
|
3
3
|
# Tag is basically just a string that contains a package name and a name for class
|
4
4
|
# (or not necessarily a class) which the given SourceFile provides/requires/extends/replaces.
|
5
|
-
#
|
5
|
+
#
|
6
6
|
class Tag
|
7
7
|
attr_accessor :package, :external # :nodoc:
|
8
8
|
|
9
9
|
# Constructors
|
10
|
-
|
10
|
+
|
11
11
|
#
|
12
12
|
# Creates a tag from given name/options.
|
13
|
-
#
|
13
|
+
#
|
14
14
|
# The way it works may seem a bit tricky but actually it parses name/options
|
15
15
|
# combinations in different ways and may be best described by examples:
|
16
16
|
#
|
@@ -21,8 +21,8 @@ module Jsus
|
|
21
21
|
# d = Tag.new("Core/Class", :package => core) # :package_name => "Core", :name => "Class", :external => false
|
22
22
|
# mash = Package.new(...) # let's consider its name is 'Mash'
|
23
23
|
# e = Tag.new("Core/Class", :package => mash) # :package_name => "Core", :name => "Class", :external => true
|
24
|
-
#
|
25
|
-
# Between all those, tags b,c,d and e are equal, meaning they all use
|
24
|
+
#
|
25
|
+
# Between all those, tags b,c,d and e are equal, meaning they all use
|
26
26
|
# the same spot in Hash or whatever else.
|
27
27
|
#
|
28
28
|
def initialize(name, options = {})
|
@@ -60,7 +60,7 @@ module Jsus
|
|
60
60
|
# * +:short:+ -- whether the tag should try using short form
|
61
61
|
#
|
62
62
|
# Important note: only non-external tags support short forms.
|
63
|
-
#
|
63
|
+
#
|
64
64
|
# Tag.new('Core/Class').name(:short => true) # => 'Core/Class'
|
65
65
|
# core = Package.new(...) # let's consider its name is 'Core'
|
66
66
|
# Tag.new('Core/Class', :package => core).name(:short => true) # => 'Class'
|
@@ -91,9 +91,9 @@ module Jsus
|
|
91
91
|
super
|
92
92
|
end
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
95
|
# Private API
|
96
|
-
|
96
|
+
|
97
97
|
def self.normalize_name_and_options(name, options = {}) # :nodoc:
|
98
98
|
result = {}
|
99
99
|
name.gsub!(%r(^(\.)?/), "")
|
@@ -108,6 +108,7 @@ module Jsus
|
|
108
108
|
end
|
109
109
|
result[:name] = name
|
110
110
|
end
|
111
|
+
result[:package_name] = normalize_package_name(result[:package_name]) if result[:package_name]
|
111
112
|
result
|
112
113
|
end
|
113
114
|
|
@@ -118,7 +119,14 @@ module Jsus
|
|
118
119
|
def self.name_and_options_to_full_name(name, options = {}) # :nodoc:
|
119
120
|
normalized_options_to_full_name(normalize_name_and_options(name, options))
|
120
121
|
end
|
121
|
-
|
122
|
+
|
123
|
+
def self.normalize_package_name(name) # :nodoc:
|
124
|
+
package_chunks = name.split("/")
|
125
|
+
package_chunks.map do |pc|
|
126
|
+
Jsus::Util::Inflection.random_case_to_mixed_case(pc)
|
127
|
+
end.join("/")
|
128
|
+
end # normalize_name
|
129
|
+
|
122
130
|
def package_name=(new_value) # :nodoc:
|
123
131
|
@package_name = new_value
|
124
132
|
end
|
@@ -137,6 +145,6 @@ module Jsus
|
|
137
145
|
|
138
146
|
def inspect # :nodoc
|
139
147
|
"<Jsus::Tag: #{name}>"
|
140
|
-
end
|
148
|
+
end
|
141
149
|
end
|
142
150
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
module Jsus
|
3
|
+
module Util
|
4
|
+
#
|
5
|
+
# Simple file cache manager.
|
6
|
+
#
|
7
|
+
class FileCache
|
8
|
+
# Initializes filecache to given directory
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
end # initialize
|
12
|
+
|
13
|
+
# Creates a file with given value for given key in cache directory
|
14
|
+
#
|
15
|
+
# Returns actual path for stored file.
|
16
|
+
def write(key, value)
|
17
|
+
item_path = generate_path(key)
|
18
|
+
FileUtils.mkdir_p(File.dirname(item_path))
|
19
|
+
File.open(item_path, 'w+') {|f| f.write(value) }
|
20
|
+
item_path
|
21
|
+
end # write
|
22
|
+
|
23
|
+
# If file exists for given cache key, returns path to that file.
|
24
|
+
# If file doesn't exist, returns nil.
|
25
|
+
def read(key)
|
26
|
+
item_path = generate_path(key)
|
27
|
+
File.exists?(item_path) ? item_path : nil
|
28
|
+
end # read
|
29
|
+
alias_method :exists?, :read
|
30
|
+
|
31
|
+
# If file with given key exists, returns path to it.
|
32
|
+
# Otherwise, writes value of yielded block.
|
33
|
+
def fetch(key, &block)
|
34
|
+
read(key) || write(key, yield)
|
35
|
+
end # fetch
|
36
|
+
|
37
|
+
# Deletes cache entry for given key.
|
38
|
+
def delete(key)
|
39
|
+
item_path = generate_path(key)
|
40
|
+
if File.exists?(item_path)
|
41
|
+
FileUtils.rm_f(item_path)
|
42
|
+
end
|
43
|
+
end # delete
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Generates path by cache key.
|
48
|
+
#
|
49
|
+
# Default strategy: append key to cache directory
|
50
|
+
# (slashes are replaced with dots)
|
51
|
+
def generate_path(key)
|
52
|
+
key = key.gsub(File::SEPARATOR, ".")
|
53
|
+
File.join(@path, key)
|
54
|
+
end # generate_path
|
55
|
+
end # class FileCache
|
56
|
+
end # module Util
|
57
|
+
end # module Jsus
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Jsus
|
2
|
+
module Util
|
3
|
+
# Various inflection helpers
|
4
|
+
module Inflection
|
5
|
+
class <<self
|
6
|
+
# Converts strings with various punctuation to pascal case
|
7
|
+
# hello_world => HelloWorld
|
8
|
+
# Oh.My.God => OhMyGod
|
9
|
+
# iAmCamelCase => IAmCamelCase
|
10
|
+
# some_Weird_._punctuation => SomeWeirdPunctuation
|
11
|
+
def random_case_to_mixed_case(string)
|
12
|
+
string.split(/[^a-zA-Z]+/).map {|chunk| capitalize(chunk) }.join
|
13
|
+
end # random_case_to_mixed_case
|
14
|
+
|
15
|
+
# Same as #random_case_to_mixed_case, but preserves dots
|
16
|
+
# color.fx => Color.Fx
|
17
|
+
def random_case_to_mixed_case_preserve_dots(string)
|
18
|
+
string.split(".").map {|c| random_case_to_mixed_case(c) }.join(".")
|
19
|
+
end # random_case_to_mixed_case
|
20
|
+
|
21
|
+
# Capitalizes first letter (doesn't do anything else to other letters, unlike String#capitalize)
|
22
|
+
def capitalize(string)
|
23
|
+
string[0,1].capitalize + string[1..-1].to_s
|
24
|
+
end # capitalize
|
25
|
+
|
26
|
+
# Downcases first letter
|
27
|
+
def decapitalize(string)
|
28
|
+
string[0,1].downcase + string[1..-1].to_s
|
29
|
+
end # decapitalize
|
30
|
+
|
31
|
+
# Translates MixedCase string to camel-case
|
32
|
+
def snake_case(string)
|
33
|
+
decapitalize(string.gsub(/(.)([A-Z])([a-z]+)/) {|_| "#{$1}_#{$2.downcase}#{$3}"}.
|
34
|
+
gsub(/[^A-Za-z_]+/, "_"))
|
35
|
+
end # snake_case
|
36
|
+
end # class <<self
|
37
|
+
end # module Inflection
|
38
|
+
end # module Util
|
39
|
+
end # module Jsus
|
data/lib/jsus/util.rb
CHANGED
data/lib/jsus.rb
CHANGED