mud 0.0.4
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.
- 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
|
+
|