mud 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +27 -0
- data/README.md +2 -0
- data/Rakefile +2 -0
- data/bin/mud +14 -0
- data/js/JSON.js +219 -0
- data/js/global.js.erb +10 -0
- data/js/inline_modules.js.erb +12 -0
- data/js/list.js.erb +3 -0
- data/js/require.js +42 -0
- data/lib/mud.rb +18 -0
- data/lib/mud/cli.rb +65 -0
- data/lib/mud/context.rb +167 -0
- data/lib/mud/dependency.rb +40 -0
- data/lib/mud/html_result.rb +39 -0
- data/lib/mud/installed_module.rb +19 -0
- data/lib/mud/js_result.rb +25 -0
- data/lib/mud/module.rb +39 -0
- data/lib/mud/server.rb +86 -0
- data/lib/mud/utils.rb +146 -0
- data/lib/mud/version.rb +3 -0
- data/mud.gemspec +25 -0
- metadata +131 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mud (0.0.4)
|
5
|
+
hpricot (>= 0.8.4)
|
6
|
+
sinatra (>= 1.1.2)
|
7
|
+
thor (>= 0.14.6)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
hpricot (0.8.4)
|
13
|
+
rack (1.2.2)
|
14
|
+
sinatra (1.1.2)
|
15
|
+
rack (~> 1.1)
|
16
|
+
tilt (~> 1.2)
|
17
|
+
thor (0.14.6)
|
18
|
+
tilt (1.2.2)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
hpricot (>= 0.8.4)
|
25
|
+
mud!
|
26
|
+
sinatra (>= 1.1.2)
|
27
|
+
thor (>= 0.14.6)
|
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/mud
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
|
5
|
+
require 'mud'
|
6
|
+
require 'mud/cli'
|
7
|
+
|
8
|
+
begin
|
9
|
+
Mud::CLI.start
|
10
|
+
rescue Mud::ResolveError => err
|
11
|
+
puts err.message, "Try running: mud install #{err.name}"
|
12
|
+
rescue Interrupt
|
13
|
+
puts "Quitting..."
|
14
|
+
end
|
data/js/JSON.js
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
if (typeof JSON === 'undefined') { // Fixed from the original JSON to support Google Closure compilation
|
2
|
+
JSON = {};
|
3
|
+
}
|
4
|
+
|
5
|
+
(function () {
|
6
|
+
"use strict";
|
7
|
+
|
8
|
+
function f(n) {
|
9
|
+
// Format integers to have at least two digits.
|
10
|
+
return n < 10 ? '0' + n : n;
|
11
|
+
}
|
12
|
+
|
13
|
+
if (typeof Date.prototype.toJSON !== 'function') {
|
14
|
+
|
15
|
+
Date.prototype.toJSON = function (key) {
|
16
|
+
|
17
|
+
return isFinite(this.valueOf()) ?
|
18
|
+
this.getUTCFullYear() + '-' +
|
19
|
+
f(this.getUTCMonth() + 1) + '-' +
|
20
|
+
f(this.getUTCDate()) + 'T' +
|
21
|
+
f(this.getUTCHours()) + ':' +
|
22
|
+
f(this.getUTCMinutes()) + ':' +
|
23
|
+
f(this.getUTCSeconds()) + 'Z' : null;
|
24
|
+
};
|
25
|
+
|
26
|
+
String.prototype.toJSON =
|
27
|
+
Number.prototype.toJSON =
|
28
|
+
Boolean.prototype.toJSON = function (key) {
|
29
|
+
return this.valueOf();
|
30
|
+
};
|
31
|
+
}
|
32
|
+
|
33
|
+
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
34
|
+
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
35
|
+
gap,
|
36
|
+
indent,
|
37
|
+
meta = { // table of character substitutions
|
38
|
+
'\b': '\\b',
|
39
|
+
'\t': '\\t',
|
40
|
+
'\n': '\\n',
|
41
|
+
'\f': '\\f',
|
42
|
+
'\r': '\\r',
|
43
|
+
'"' : '\\"',
|
44
|
+
'\\': '\\\\'
|
45
|
+
},
|
46
|
+
rep;
|
47
|
+
|
48
|
+
|
49
|
+
function quote(string) {
|
50
|
+
|
51
|
+
escapable.lastIndex = 0;
|
52
|
+
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
|
53
|
+
var c = meta[a];
|
54
|
+
return typeof c === 'string' ? c :
|
55
|
+
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
56
|
+
}) + '"' : '"' + string + '"';
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
function str(key, holder) {
|
61
|
+
|
62
|
+
var i, // The loop counter.
|
63
|
+
k, // The member key.
|
64
|
+
v, // The member value.
|
65
|
+
length,
|
66
|
+
mind = gap,
|
67
|
+
partial,
|
68
|
+
value = holder[key];
|
69
|
+
|
70
|
+
if (value && typeof value === 'object' &&
|
71
|
+
typeof value.toJSON === 'function') {
|
72
|
+
value = value.toJSON(key);
|
73
|
+
}
|
74
|
+
|
75
|
+
if (typeof rep === 'function') {
|
76
|
+
value = rep.call(holder, key, value);
|
77
|
+
}
|
78
|
+
|
79
|
+
switch (typeof value) {
|
80
|
+
case 'string':
|
81
|
+
return quote(value);
|
82
|
+
|
83
|
+
case 'number':
|
84
|
+
|
85
|
+
return isFinite(value) ? String(value) : 'null';
|
86
|
+
|
87
|
+
case 'boolean':
|
88
|
+
case 'null':
|
89
|
+
|
90
|
+
return String(value);
|
91
|
+
|
92
|
+
case 'object':
|
93
|
+
|
94
|
+
if (!value) {
|
95
|
+
return 'null';
|
96
|
+
}
|
97
|
+
|
98
|
+
gap += indent;
|
99
|
+
partial = [];
|
100
|
+
|
101
|
+
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
102
|
+
|
103
|
+
length = value.length;
|
104
|
+
for (i = 0; i < length; i += 1) {
|
105
|
+
partial[i] = str(i, value) || 'null';
|
106
|
+
}
|
107
|
+
|
108
|
+
v = partial.length === 0 ? '[]' : gap ?
|
109
|
+
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
|
110
|
+
'[' + partial.join(',') + ']';
|
111
|
+
gap = mind;
|
112
|
+
return v;
|
113
|
+
}
|
114
|
+
|
115
|
+
if (rep && typeof rep === 'object') {
|
116
|
+
length = rep.length;
|
117
|
+
for (i = 0; i < length; i += 1) {
|
118
|
+
if (typeof rep[i] === 'string') {
|
119
|
+
k = rep[i];
|
120
|
+
v = str(k, value);
|
121
|
+
if (v) {
|
122
|
+
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
} else {
|
127
|
+
|
128
|
+
for (k in value) {
|
129
|
+
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
130
|
+
v = str(k, value);
|
131
|
+
if (v) {
|
132
|
+
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
v = partial.length === 0 ? '{}' : gap ?
|
139
|
+
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
|
140
|
+
'{' + partial.join(',') + '}';
|
141
|
+
gap = mind;
|
142
|
+
return v;
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
if (typeof JSON.stringify !== 'function') {
|
147
|
+
JSON.stringify = function (value, replacer, space) {
|
148
|
+
|
149
|
+
var i;
|
150
|
+
gap = '';
|
151
|
+
indent = '';
|
152
|
+
|
153
|
+
if (typeof space === 'number') {
|
154
|
+
for (i = 0; i < space; i += 1) {
|
155
|
+
indent += ' ';
|
156
|
+
}
|
157
|
+
|
158
|
+
} else if (typeof space === 'string') {
|
159
|
+
indent = space;
|
160
|
+
}
|
161
|
+
|
162
|
+
rep = replacer;
|
163
|
+
if (replacer && typeof replacer !== 'function' &&
|
164
|
+
(typeof replacer !== 'object' ||
|
165
|
+
typeof replacer.length !== 'number')) {
|
166
|
+
throw new Error('JSON.stringify');
|
167
|
+
}
|
168
|
+
|
169
|
+
return str('', {'': value});
|
170
|
+
};
|
171
|
+
}
|
172
|
+
|
173
|
+
if (typeof JSON.parse !== 'function') {
|
174
|
+
JSON.parse = function (text, reviver) {
|
175
|
+
|
176
|
+
var j;
|
177
|
+
|
178
|
+
function walk(holder, key) {
|
179
|
+
|
180
|
+
var k, v, value = holder[key];
|
181
|
+
if (value && typeof value === 'object') {
|
182
|
+
for (k in value) {
|
183
|
+
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
184
|
+
v = walk(value, k);
|
185
|
+
if (v !== undefined) {
|
186
|
+
value[k] = v;
|
187
|
+
} else {
|
188
|
+
delete value[k];
|
189
|
+
}
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
return reviver.call(holder, key, value);
|
194
|
+
}
|
195
|
+
|
196
|
+
text = String(text);
|
197
|
+
cx.lastIndex = 0;
|
198
|
+
if (cx.test(text)) {
|
199
|
+
text = text.replace(cx, function (a) {
|
200
|
+
return '\\u' +
|
201
|
+
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
202
|
+
});
|
203
|
+
}
|
204
|
+
|
205
|
+
if (/^[\],:{}\s]*$/
|
206
|
+
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
|
207
|
+
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
|
208
|
+
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
209
|
+
|
210
|
+
j = eval('(' + text + ')');
|
211
|
+
|
212
|
+
return typeof reviver === 'function' ?
|
213
|
+
walk({'': j}, '') : j;
|
214
|
+
}
|
215
|
+
|
216
|
+
throw new SyntaxError('JSON.parse');
|
217
|
+
};
|
218
|
+
}
|
219
|
+
}());
|
data/js/global.js.erb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
|
2
|
+
(function(require) {
|
3
|
+
<%= render :erb => 'inline_modules.js.erb', :locals => { :modules => modules } %>
|
4
|
+
|
5
|
+
<% modules.each do |mod| %>
|
6
|
+
window[<%= mod.name.to_json %>] = require(<%= mod.name.to_json %>);
|
7
|
+
<% end %>
|
8
|
+
})();
|
9
|
+
|
10
|
+
<%= render :erb => 'list.js.erb', :locals => { :list => appends } %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
<%= render :file => 'require.js' %>
|
3
|
+
|
4
|
+
<%= render :file => 'JSON.js' %>
|
5
|
+
|
6
|
+
<% modules.each do |mod| %>
|
7
|
+
require.define(<%= mod.name.to_json %>, function(module, exports) {
|
8
|
+
<%= mod.content %>
|
9
|
+
});
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<%= render :erb => 'list.js.erb', :locals => { :list => appends } if defined?(appends) %>
|
data/js/list.js.erb
ADDED
data/js/require.js
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
(function() {
|
2
|
+
if (typeof require !== 'undefined') {
|
3
|
+
return; // define only once
|
4
|
+
}
|
5
|
+
|
6
|
+
var noop = function() {};
|
7
|
+
var modules = {};
|
8
|
+
var definitions = {};
|
9
|
+
|
10
|
+
require = function(name) {
|
11
|
+
if (arguments.length > 1) { // this syntax allows for and module and it's plugins to be loaded
|
12
|
+
var val = require(arguments[0]);
|
13
|
+
|
14
|
+
for (var i = 1; i < arguments.length; i++) {
|
15
|
+
require(arguments[i]);
|
16
|
+
}
|
17
|
+
return val;
|
18
|
+
}
|
19
|
+
name = name.split('@')[0]; // TODO: make this versioning a lot better
|
20
|
+
|
21
|
+
if (definitions[name] && !modules[name]) { // if not already loaded and an def exists
|
22
|
+
var def = definitions[name];
|
23
|
+
|
24
|
+
delete definitions[name];
|
25
|
+
|
26
|
+
var module = modules[name] = function() {
|
27
|
+
return module.exports;
|
28
|
+
};
|
29
|
+
|
30
|
+
module.browser = true; // allows for non-hacky browser js detection
|
31
|
+
module.exports = {};
|
32
|
+
|
33
|
+
def(module, module.exports);
|
34
|
+
}
|
35
|
+
|
36
|
+
return window[name] || (modules[name] || noop)();
|
37
|
+
};
|
38
|
+
|
39
|
+
require.define = function(name, def) {
|
40
|
+
definitions[name] = def;
|
41
|
+
};
|
42
|
+
}());
|
data/lib/mud.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'json'
|
6
|
+
require 'erb'
|
7
|
+
require 'rbconfig'
|
8
|
+
|
9
|
+
require 'sinatra/base'
|
10
|
+
require 'thor'
|
11
|
+
require 'hpricot'
|
12
|
+
|
13
|
+
module Mud
|
14
|
+
require 'mud/utils'
|
15
|
+
extend Mud::Utils
|
16
|
+
end
|
17
|
+
|
18
|
+
%w(context dependency js_result html_result module installed_module server).each { |f| require "mud/#{f}" }
|
data/lib/mud/cli.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
class CLI < Thor
|
4
|
+
include Thor::Actions
|
5
|
+
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
@context = Mud::Context.new
|
9
|
+
say("(in #{@context.dir})")
|
10
|
+
end
|
11
|
+
|
12
|
+
map "ls" => "list"
|
13
|
+
|
14
|
+
desc "resolve PATH", "resolve the given path"
|
15
|
+
method_option :compile, :default => nil, :desc => "compile loaded modules"
|
16
|
+
def resolve(path)
|
17
|
+
modules = @context.resolve_document(path)
|
18
|
+
result = Mud::JsResult.new(modules, :compile => options[:compile])
|
19
|
+
say(result.to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "inline PATH", "resolve and inline the given path"
|
23
|
+
method_option :compile, :default => nil, :desc => "compile loaded modules"
|
24
|
+
def inline(path)
|
25
|
+
result = @context.inline_document(path, :compile => options[:compile])
|
26
|
+
say(result.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "module A,B,...,C", "load in these modules"
|
30
|
+
method_option :compile, :default => nil, :desc => "compile loaded modules"
|
31
|
+
#method_option :output, :default => nil, :desc => "output file"
|
32
|
+
def modules(modules)
|
33
|
+
modules = modules.split(',').map { |mod_name| @context.module!(mod_name) }
|
34
|
+
result = @context.inline(modules, :compile => options[:compile])
|
35
|
+
|
36
|
+
if out = options[:output]
|
37
|
+
File.open(out, 'w') { |f| f.write(result.to_s) }
|
38
|
+
else
|
39
|
+
say(result.to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "server", "run a mud server"
|
44
|
+
#method_options :fork, :default => false, :desc => "run the server as a daemon (only supported on unix platforms)"
|
45
|
+
def server
|
46
|
+
Mud::Server.context = @context
|
47
|
+
Mud::Server.run!
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "list", "list all installed packages"
|
51
|
+
method_option :path, :default => false, :desc => "include module paths"
|
52
|
+
def list
|
53
|
+
modules = @context.available_modules.values
|
54
|
+
if modules.empty?
|
55
|
+
say("no modules found")
|
56
|
+
else
|
57
|
+
modules.each do |m|
|
58
|
+
msg = m.name + (options[:path] ? " => #{m.path}" : '')
|
59
|
+
say(msg)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/mud/context.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
class ResolveError < StandardError
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(name_or_dependency)
|
7
|
+
@name = name_or_dependency.is_a?(Mud::Dependency) ? name_or_dependency.name : name_or_dependency
|
8
|
+
super("No module named '#{@name}' in context")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Context
|
13
|
+
MODULE_DIRECTORIES = ['js_modules', 'shared_modules']
|
14
|
+
MODULE_GLOBAL = Mud.home_directory('.mud', 'js_modules')
|
15
|
+
|
16
|
+
attr_reader :available_modules, :dir
|
17
|
+
|
18
|
+
def initialize(dir = '.')
|
19
|
+
@dir = File.absolute_path(dir)
|
20
|
+
@available_modules = {}
|
21
|
+
reload
|
22
|
+
end
|
23
|
+
|
24
|
+
def reload
|
25
|
+
dirs = dirs(@dir)
|
26
|
+
removed = @available_modules.dup
|
27
|
+
|
28
|
+
dirs.each do |dir|
|
29
|
+
Dir.glob(File.join(dir, '*.js')) do |mod_path|
|
30
|
+
name = Mud::Module.parse_name(mod_path)
|
31
|
+
mod = @available_modules[name]
|
32
|
+
removed.delete(name)
|
33
|
+
|
34
|
+
unless mod and mod.modified == File.mtime(mod_path) and mod.path == mod_path
|
35
|
+
@available_modules[name] = Mud::InstalledModule.new(mod_path, self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
@available_modules.delete_if { |key, _| removed.key?(key) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def install(name, opts = {})
|
44
|
+
return if @available_modules[name]# or raise exception
|
45
|
+
|
46
|
+
src = nil # download module src from mudhub and write to disk in module global
|
47
|
+
path = nil
|
48
|
+
|
49
|
+
@available_modules[name] = Mud::InstalledModule.new(path, self) # check dependencies and download if not present
|
50
|
+
end
|
51
|
+
|
52
|
+
def uninstall(module_or_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def module(name)
|
56
|
+
@available_modules[name]
|
57
|
+
end
|
58
|
+
|
59
|
+
def module!(name)
|
60
|
+
@available_modules[name] || (raise Mud::ResolveError.new(name))
|
61
|
+
end
|
62
|
+
|
63
|
+
def resolve_document(path)
|
64
|
+
resolve analyze_document(path).first
|
65
|
+
end
|
66
|
+
|
67
|
+
def inline_document(path, opts = {})
|
68
|
+
modules, type = analyze_document(path)
|
69
|
+
|
70
|
+
result = inline(modules, opts)
|
71
|
+
|
72
|
+
if type == :js
|
73
|
+
main = modules.first
|
74
|
+
result << main.content
|
75
|
+
else
|
76
|
+
result = Mud::HtmlResult.new(path, result)
|
77
|
+
end
|
78
|
+
|
79
|
+
result
|
80
|
+
end
|
81
|
+
|
82
|
+
def resolve(module_or_list)
|
83
|
+
modules = module_or_list.is_a?(Mud::Module) ? [module_or_list] : module_or_list
|
84
|
+
|
85
|
+
resolved = []
|
86
|
+
|
87
|
+
resolver = proc do |modules|
|
88
|
+
modules.each do |mod|
|
89
|
+
next if resolved.include?(mod)
|
90
|
+
resolved.unshift(mod) if mod.is_a?(Mud::InstalledModule)
|
91
|
+
|
92
|
+
dep = mod.unresolvable_dependencies.first
|
93
|
+
raise Mud::ResolveError.new(dep) if dep
|
94
|
+
|
95
|
+
dependencies = mod.dependencies.map(&:resolve).delete_if { |m| resolved.include?(m) }
|
96
|
+
resolver.call(dependencies) unless dependencies.empty?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
resolver.call(modules)
|
100
|
+
|
101
|
+
resolved
|
102
|
+
end
|
103
|
+
|
104
|
+
def inline(module_or_list, opts = {})
|
105
|
+
resolved = resolve(module_or_list)
|
106
|
+
Mud::JsResult.new(resolved, opts)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def analyze_document(path)
|
112
|
+
content = Mud.render path
|
113
|
+
type = content.match(/^\s*</) ? :html : :js
|
114
|
+
|
115
|
+
modules = if type == :html
|
116
|
+
analyze_html(path, content)
|
117
|
+
else
|
118
|
+
[Mud::Module.new(path, content, self)]
|
119
|
+
end
|
120
|
+
|
121
|
+
return modules, type
|
122
|
+
end
|
123
|
+
|
124
|
+
def analyze_html(path, html)
|
125
|
+
inner_modules = []
|
126
|
+
doc = Hpricot(html)
|
127
|
+
|
128
|
+
doc.search('//script').each do |script_tag|
|
129
|
+
src = script_tag.attributes['src']
|
130
|
+
|
131
|
+
if src and not src.empty? and not src.match(/^\w+:\/\//)
|
132
|
+
begin
|
133
|
+
content = Mud.render src, :basepath => path
|
134
|
+
inner_modules << Mud::Module.new(src, content, self)
|
135
|
+
rescue Errno::ENOENT, Net::HTTPError
|
136
|
+
# Does not exist. Ignore.
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
content = script_tag.inner_html
|
141
|
+
if content and not content.empty?
|
142
|
+
inner_modules << Mud::Module.new("#{File.basename(path)}-embedded-script-#{inner_modules.length}", content, self)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
inner_modules
|
147
|
+
end
|
148
|
+
|
149
|
+
def dirs(start)
|
150
|
+
dirs = [MODULE_GLOBAL]
|
151
|
+
|
152
|
+
current = File.absolute_path(start)
|
153
|
+
while true
|
154
|
+
dirs += MODULE_DIRECTORIES.map { |dir| File.join(current, dir) }
|
155
|
+
break if current == Mud.root_directory
|
156
|
+
current = File.expand_path(current, '..')
|
157
|
+
end
|
158
|
+
|
159
|
+
dirs.keep_if { |dir| File.exists?(dir) }
|
160
|
+
end
|
161
|
+
|
162
|
+
def in(*paths)
|
163
|
+
File.join(@dir, *paths)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
class Dependency
|
4
|
+
def self.analyze(src, context)
|
5
|
+
dependencies = Set.new
|
6
|
+
|
7
|
+
# Find all required files on the form require('name') og require('name', 'sub_name', ...)
|
8
|
+
src.scan(/require\(((?:'[^']+'(?:,\s)?)+)\)/).each do |req|
|
9
|
+
req.first.scan(/'([^']+)'/).each { |name| dependencies << new(name.first, context) }
|
10
|
+
end
|
11
|
+
|
12
|
+
dependencies.to_a
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :name, :context
|
16
|
+
|
17
|
+
def initialize(name, context)
|
18
|
+
@name = name
|
19
|
+
@context = context
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"Mud::Dependency #{@name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
other.is_a?(self.class) and other.name == @name
|
28
|
+
end
|
29
|
+
|
30
|
+
def resolvable?
|
31
|
+
!!resolve
|
32
|
+
end
|
33
|
+
|
34
|
+
# Resolves to the corresponding module if present in this context.
|
35
|
+
def resolve
|
36
|
+
@context.module(@name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
class HtmlResult
|
4
|
+
def initialize(html, js)
|
5
|
+
@html = html
|
6
|
+
@js = js
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
doc = html_doc
|
11
|
+
script = doc.search('//script').find { |script_tag| /.*\/dev$/.match script_tag.attributes['src'] }
|
12
|
+
|
13
|
+
if script
|
14
|
+
script.remove_attribute(:src)
|
15
|
+
else
|
16
|
+
script = Hpricot::Elem.new('script', :type => 'text/javascript')
|
17
|
+
head = doc.at('/html/head')
|
18
|
+
|
19
|
+
unless head
|
20
|
+
head = Hpricot::Elem.new('head')
|
21
|
+
doc.root.children.unshift(head)
|
22
|
+
end
|
23
|
+
|
24
|
+
head.children = (head.children || []).unshift(script)
|
25
|
+
end
|
26
|
+
|
27
|
+
script.inner_html = @js.to_s
|
28
|
+
|
29
|
+
doc.to_html
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def html_doc
|
35
|
+
Hpricot(Mud.render @html)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
class InstalledModule < Mud::Module
|
4
|
+
attr_reader :path, :modified
|
5
|
+
|
6
|
+
def initialize(path, context)
|
7
|
+
super(path, File.open(path) { |f| f.read }, context)
|
8
|
+
@content = nil
|
9
|
+
|
10
|
+
@path = path
|
11
|
+
@modified = File.mtime(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def content
|
15
|
+
File.open(@path) { |f| f.read }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
class JsResult
|
4
|
+
def initialize(modules, opts = {})
|
5
|
+
opts = { :global => false, :compile => nil }.update(opts)
|
6
|
+
|
7
|
+
@modules = modules
|
8
|
+
@global = opts[:global]
|
9
|
+
@compile = opts[:compile]
|
10
|
+
|
11
|
+
@appends = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
result = Mud.render :erb => (@global ? 'global.js.erb' : 'inline_modules.js.erb'),
|
16
|
+
:locals => { :modules => @modules, :appends => @appends }, :basepath => Mud.js_directory
|
17
|
+
@compile ? Mud.compile(result, @compile) : result
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(src)
|
21
|
+
@appends << src
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/mud/module.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
class Module
|
4
|
+
attr_reader :name, :content, :context, :dependencies
|
5
|
+
|
6
|
+
def self.parse_name(path_or_name)
|
7
|
+
File.basename(path_or_name).split(/\.js$/i).first
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(name, content, context)
|
11
|
+
@name = self.class.parse_name(name)
|
12
|
+
@content = content
|
13
|
+
@context = context
|
14
|
+
|
15
|
+
@dependencies = Mud::Dependency.analyze(content, context)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"#{self.class} #{@name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
other.is_a?(self.class) and other.name == @name
|
24
|
+
end
|
25
|
+
|
26
|
+
def unresolvable_dependencies
|
27
|
+
@dependencies.select { |d| not d.resolvable? }
|
28
|
+
end
|
29
|
+
|
30
|
+
def resolvable?
|
31
|
+
@dependencies.all?(&:resolvable?)
|
32
|
+
end
|
33
|
+
|
34
|
+
def depends_on?(module_or_dependency)
|
35
|
+
!!@dependencies.find { |d| d.name == module_or_dependency.name }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/mud/server.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
class Server < Sinatra::Base
|
4
|
+
enable :logging, :dump_errors, :inline_templates
|
5
|
+
disable :static, :run
|
6
|
+
|
7
|
+
set :port, 10000
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def port=(port)
|
11
|
+
set :port, port
|
12
|
+
end
|
13
|
+
|
14
|
+
def context=(context)
|
15
|
+
@@context = context
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
helpers do
|
20
|
+
def context
|
21
|
+
unless defined?(@@context)
|
22
|
+
@@context = Mud::Context.new
|
23
|
+
else
|
24
|
+
@@context.reload
|
25
|
+
end
|
26
|
+
|
27
|
+
@@context
|
28
|
+
end
|
29
|
+
|
30
|
+
def js
|
31
|
+
content_type 'application/javascript'
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_modules(modules, opts)
|
35
|
+
halt 400 if modules.nil? or modules.empty?
|
36
|
+
modules = modules.split(',').map { |name| context.module!(name) }
|
37
|
+
context.inline(modules, opts).to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
get '/dev' do
|
42
|
+
ref = params[:ref] || request.referrer
|
43
|
+
|
44
|
+
js
|
45
|
+
if ref.nil? or ref == '/'
|
46
|
+
host = "#{request.host.split(':').first}:#{settings.port}"
|
47
|
+
erb :dev, :locals => { :host => host }
|
48
|
+
else
|
49
|
+
modules = context.resolve_document(ref)
|
50
|
+
Mud::JsResult.new(modules).to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
get '/m/:modules' do |modules|
|
55
|
+
js
|
56
|
+
process_modules(modules, :global => false)
|
57
|
+
end
|
58
|
+
|
59
|
+
get '/g/:modules' do |modules|
|
60
|
+
js
|
61
|
+
process_modules(modules, :global => true)
|
62
|
+
end
|
63
|
+
|
64
|
+
get '/p/:var' do |var|
|
65
|
+
content_type 'text/html'
|
66
|
+
erb :play, :locals => { :var => var }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
__END__
|
73
|
+
|
74
|
+
@@ dev
|
75
|
+
document.write('<script src="http://<%= host %>/dev?ref=' + window.location + '"></script>');
|
76
|
+
|
77
|
+
@@ play
|
78
|
+
<!DOCTYPE html>
|
79
|
+
<html>
|
80
|
+
<head>
|
81
|
+
<title>mud play</title>
|
82
|
+
<script src="<%= "/m/#{var}" %>"></script>
|
83
|
+
</head>
|
84
|
+
<body>
|
85
|
+
</body>
|
86
|
+
</html>
|
data/lib/mud/utils.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
module Mud
|
2
|
+
|
3
|
+
File.class_eval do
|
4
|
+
def self.hide(file_name, force_rename = false)
|
5
|
+
os = RbConfig::CONFIG['host_os']
|
6
|
+
|
7
|
+
if os =~ /mswin|windows|cygwin/i
|
8
|
+
raise IOError.new("Could not hide file '#{file_name}' using attrib +h") unless system("attrib +h '#{file_name}'")
|
9
|
+
file_name
|
10
|
+
else
|
11
|
+
# Assume unix-like
|
12
|
+
|
13
|
+
parent, base = dirname(file_name), basename(file_name)
|
14
|
+
|
15
|
+
if base.start_with?('.')
|
16
|
+
file_name
|
17
|
+
else
|
18
|
+
if os =~ /darwin/i and not force_rename
|
19
|
+
# OS X
|
20
|
+
raise IOError.new("Could not hide file '#{file_name}' using SetFile -a -V") unless system("SetFile -a V '#{file_name}'")
|
21
|
+
return file_name
|
22
|
+
end
|
23
|
+
|
24
|
+
hidden = join(parent, ".#{base}")
|
25
|
+
raise IOError.new("Can't hide '#{file_name}' by renaming to '#{hidden} (already exists)") if exists?(hidden)
|
26
|
+
rename(file_name, hidden)
|
27
|
+
hidden
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Utils
|
34
|
+
JS_DIRECTORY = File.expand_path(File.join File.dirname(__FILE__), '..', '..', 'js')
|
35
|
+
|
36
|
+
ROOT_DIRECTORY = File.absolute_path('/')
|
37
|
+
HOME_DIRECTORY = Dir.home
|
38
|
+
|
39
|
+
[:js, :root, :home].each do |name|
|
40
|
+
name = "#{name}_directory"
|
41
|
+
module_eval %{
|
42
|
+
def #{name}(*paths)
|
43
|
+
File.join(#{name.upcase}, *paths)
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def compile(src, type = 'simple', out = nil)
|
49
|
+
raise ArgumentError.new("Type must be either 'simple' or 'advanced', was '#{type}'") unless ['simple', 'advanced'].include?(type.to_s)
|
50
|
+
level = "#{type.upcase}_OPTIMIZATIONS"
|
51
|
+
|
52
|
+
response = Net::HTTP.post_form(URI.parse('http://closure-compiler.appspot.com/compile'),
|
53
|
+
:output_info => 'compiled_code',
|
54
|
+
:compilation_level => level,
|
55
|
+
:warning_level => 'default',
|
56
|
+
:js_code => src)
|
57
|
+
|
58
|
+
if out
|
59
|
+
File.open(out) { |f| f.write(response.body) }
|
60
|
+
end
|
61
|
+
|
62
|
+
response.body
|
63
|
+
end
|
64
|
+
|
65
|
+
def render(location, opts = {})
|
66
|
+
if location.is_a?(Hash)
|
67
|
+
opts = location
|
68
|
+
else
|
69
|
+
opts = guess(location).update(opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
type, path = opts.first
|
73
|
+
|
74
|
+
content = case type
|
75
|
+
when :erb, :file then
|
76
|
+
basepath = opts[:basepath] || path
|
77
|
+
basepath = basepath.gsub(/^file:\/\//, '')
|
78
|
+
|
79
|
+
path = opts[:basepath] ? File.join(basepath, path) : basepath
|
80
|
+
|
81
|
+
File.open(path) { |f| f.read }
|
82
|
+
when :http then
|
83
|
+
basepath = opts[:basepath] || path
|
84
|
+
basepath = "http://#{basepath}" unless basepath.start_with?('http://')
|
85
|
+
|
86
|
+
path = opts[:basepath] ? URI.join(basepath, path) : basepath
|
87
|
+
|
88
|
+
response = Net::HTTP.get_response(URI.parse(path))
|
89
|
+
response.error! unless (200..299).include?(response.code.to_i)
|
90
|
+
response.body
|
91
|
+
else
|
92
|
+
raise ArgumentError.new("Unknown type '#{type}'")
|
93
|
+
end
|
94
|
+
|
95
|
+
if type == :erb
|
96
|
+
locals = opts[:locals] || {}
|
97
|
+
content = ERB.new(content).result(LocalsBinding.new(locals).binding)
|
98
|
+
end
|
99
|
+
|
100
|
+
content
|
101
|
+
end
|
102
|
+
alias :cat :render
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
class LocalsBinding < BasicObject
|
107
|
+
def initialize(locals, &block)
|
108
|
+
@_locals = locals
|
109
|
+
|
110
|
+
locals.each_pair do |name, value|
|
111
|
+
instance_eval %{
|
112
|
+
def #{name}
|
113
|
+
_get(:#{name})
|
114
|
+
end
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
instance_eval(&block) if block
|
119
|
+
end
|
120
|
+
|
121
|
+
def js_directory(*paths)
|
122
|
+
::Mud.js_directory(*paths)
|
123
|
+
end
|
124
|
+
|
125
|
+
def render(opts)
|
126
|
+
::Mud.render(opts.update :basepath => js_directory)
|
127
|
+
end
|
128
|
+
|
129
|
+
def binding
|
130
|
+
::Proc.new {}.binding
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def _get(name)
|
136
|
+
@_locals[name.to_sym] || @_locals[name.to_s]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def guess(path)
|
141
|
+
protocol = (path.match(/^(\w+):\/\//) || [])[1] || 'file'
|
142
|
+
{ protocol.to_sym => path }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
data/lib/mud/version.rb
ADDED
data/mud.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "mud/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "mud"
|
7
|
+
s.version = Mud::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Mirza Kapetanovic", "Mathias Buus"]
|
10
|
+
#s.email = [""]
|
11
|
+
s.homepage = "http://mudhub.org"
|
12
|
+
s.summary = %q{Simple browser Javascript package manager}
|
13
|
+
s.description = %q{Mud is a simple package manager for client-side Javascript. Used for installing new packages and resolving dependencies.}
|
14
|
+
|
15
|
+
#s.rubyforge_project = "mud"
|
16
|
+
|
17
|
+
s.add_dependency("sinatra", ">= 1.1.2")
|
18
|
+
s.add_dependency("thor", ">= 0.14.6")
|
19
|
+
s.add_dependency("hpricot", ">= 0.8.4")
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
#s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = ["mud"]
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mud
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
version: 0.0.4
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Mirza Kapetanovic
|
13
|
+
- Mathias Buus
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-29 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: sinatra
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
version: 1.1.2
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: thor
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
- 14
|
47
|
+
- 6
|
48
|
+
version: 0.14.6
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: hpricot
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
- 8
|
62
|
+
- 4
|
63
|
+
version: 0.8.4
|
64
|
+
type: :runtime
|
65
|
+
version_requirements: *id003
|
66
|
+
description: Mud is a simple package manager for client-side Javascript. Used for installing new packages and resolving dependencies.
|
67
|
+
email:
|
68
|
+
executables:
|
69
|
+
- mud
|
70
|
+
extensions: []
|
71
|
+
|
72
|
+
extra_rdoc_files: []
|
73
|
+
|
74
|
+
files:
|
75
|
+
- .gitignore
|
76
|
+
- Gemfile
|
77
|
+
- Gemfile.lock
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- bin/mud
|
81
|
+
- js/JSON.js
|
82
|
+
- js/global.js.erb
|
83
|
+
- js/inline_modules.js.erb
|
84
|
+
- js/list.js.erb
|
85
|
+
- js/require.js
|
86
|
+
- lib/mud.rb
|
87
|
+
- lib/mud/cli.rb
|
88
|
+
- lib/mud/context.rb
|
89
|
+
- lib/mud/dependency.rb
|
90
|
+
- lib/mud/html_result.rb
|
91
|
+
- lib/mud/installed_module.rb
|
92
|
+
- lib/mud/js_result.rb
|
93
|
+
- lib/mud/module.rb
|
94
|
+
- lib/mud/server.rb
|
95
|
+
- lib/mud/utils.rb
|
96
|
+
- lib/mud/version.rb
|
97
|
+
- mud.gemspec
|
98
|
+
has_rdoc: true
|
99
|
+
homepage: http://mudhub.org
|
100
|
+
licenses: []
|
101
|
+
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
segments:
|
121
|
+
- 0
|
122
|
+
version: "0"
|
123
|
+
requirements: []
|
124
|
+
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 1.3.7
|
127
|
+
signing_key:
|
128
|
+
specification_version: 3
|
129
|
+
summary: Simple browser Javascript package manager
|
130
|
+
test_files: []
|
131
|
+
|