maglev-webtools 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +25 -0
- data/README.rdoc +121 -0
- data/bin/webtools +15 -0
- data/lib/web_tools/#debugger.rb# +212 -0
- data/lib/web_tools/browser.rb +45 -0
- data/lib/web_tools/debugger.rb +219 -0
- data/lib/web_tools/info.rb +29 -0
- data/lib/web_tools/middleware/debugger.rb +118 -0
- data/lib/web_tools/support/app_model.rb +117 -0
- data/lib/web_tools/support/code_browser.rb +109 -0
- data/lib/web_tools/support/ruby.rb +132 -0
- data/lib/web_tools/support/service_helper.rb +22 -0
- data/lib/web_tools/support/smalltalk_extensions.rb +65 -0
- data/lib/web_tools/support/smalltalk_tools.rb +16 -0
- data/lib/web_tools/ui.rb +67 -0
- data/lib/web_tools.rb +10 -0
- data/public/images/favicon.ico +0 -0
- data/public/javascript/CodeMirror/LICENSE +23 -0
- data/public/javascript/CodeMirror/css/Smalltalk.css +34 -0
- data/public/javascript/CodeMirror/js/codemirror.js +582 -0
- data/public/javascript/CodeMirror/js/editor.js +1671 -0
- data/public/javascript/CodeMirror/js/highlight.js +68 -0
- data/public/javascript/CodeMirror/js/parseSmalltalk.js +126 -0
- data/public/javascript/CodeMirror/js/parsedummy.js +32 -0
- data/public/javascript/CodeMirror/js/select.js +699 -0
- data/public/javascript/CodeMirror/js/stringstream.js +159 -0
- data/public/javascript/CodeMirror/js/tokenize.js +57 -0
- data/public/javascript/CodeMirror/js/undo.js +413 -0
- data/public/javascript/CodeMirror/js/util.js +133 -0
- data/public/javascript/CodeMirror/testSmalltalkParser.html +116 -0
- data/public/javascript/ace/ace-uncompressed.js +17299 -0
- data/public/javascript/ace/ace.js +1 -0
- data/public/javascript/ace/keybinding-emacs.js +1 -0
- data/public/javascript/ace/keybinding-vim.js +1 -0
- data/public/javascript/ace/mode-c_cpp.js +1 -0
- data/public/javascript/ace/mode-clojure.js +1 -0
- data/public/javascript/ace/mode-coffee.js +1 -0
- data/public/javascript/ace/mode-csharp.js +1 -0
- data/public/javascript/ace/mode-css.js +1 -0
- data/public/javascript/ace/mode-groovy.js +1 -0
- data/public/javascript/ace/mode-html.js +1 -0
- data/public/javascript/ace/mode-java.js +1 -0
- data/public/javascript/ace/mode-javascript.js +1 -0
- data/public/javascript/ace/mode-json.js +1 -0
- data/public/javascript/ace/mode-lua.js +1 -0
- data/public/javascript/ace/mode-markdown.js +1 -0
- data/public/javascript/ace/mode-ocaml.js +1 -0
- data/public/javascript/ace/mode-perl.js +1 -0
- data/public/javascript/ace/mode-php.js +1 -0
- data/public/javascript/ace/mode-python.js +1 -0
- data/public/javascript/ace/mode-ruby.js +1 -0
- data/public/javascript/ace/mode-scad.js +1 -0
- data/public/javascript/ace/mode-scala.js +1 -0
- data/public/javascript/ace/mode-scss.js +1 -0
- data/public/javascript/ace/mode-svg.js +1 -0
- data/public/javascript/ace/mode-textile.js +1 -0
- data/public/javascript/ace/mode-xml.js +1 -0
- data/public/javascript/ace/theme-clouds.js +1 -0
- data/public/javascript/ace/theme-clouds_midnight.js +1 -0
- data/public/javascript/ace/theme-cobalt.js +1 -0
- data/public/javascript/ace/theme-crimson_editor.js +1 -0
- data/public/javascript/ace/theme-dawn.js +1 -0
- data/public/javascript/ace/theme-eclipse.js +1 -0
- data/public/javascript/ace/theme-idle_fingers.js +1 -0
- data/public/javascript/ace/theme-kr_theme.js +1 -0
- data/public/javascript/ace/theme-merbivore.js +1 -0
- data/public/javascript/ace/theme-merbivore_soft.js +1 -0
- data/public/javascript/ace/theme-mono_industrial.js +1 -0
- data/public/javascript/ace/theme-monokai.js +1 -0
- data/public/javascript/ace/theme-pastel_on_dark.js +1 -0
- data/public/javascript/ace/theme-solarized_dark.js +1 -0
- data/public/javascript/ace/theme-solarized_light.js +1 -0
- data/public/javascript/ace/theme-textmate.js +1 -0
- data/public/javascript/ace/theme-twilight.js +1 -0
- data/public/javascript/ace/theme-vibrant_ink.js +1 -0
- data/public/javascript/ace/worker-coffee.js +1 -0
- data/public/javascript/ace/worker-css.js +1 -0
- data/public/javascript/ace/worker-javascript.js +1 -0
- data/public/javascript/webtools/#debugger.coffee# +253 -0
- data/public/javascript/webtools/browser.js +260 -0
- data/public/javascript/webtools/debugger.coffee +286 -0
- data/public/javascript/webtools/debugger.js +366 -0
- data/public/javascript/webtools/sessions.coffee +17 -0
- data/public/javascript/webtools/sessions.js +27 -0
- data/public/javascript/webtools/version.coffee +14 -0
- data/public/javascript/webtools/version.js +20 -0
- data/public/stylesheets/base/images/ui-anim_basic_16x16.gif +0 -0
- data/public/stylesheets/base/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/public/stylesheets/base/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/public/stylesheets/base/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/public/stylesheets/base/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/public/stylesheets/base/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/public/stylesheets/base/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/public/stylesheets/base/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/public/stylesheets/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/public/stylesheets/base/images/ui-icons_222222_256x240.png +0 -0
- data/public/stylesheets/base/images/ui-icons_2e83ff_256x240.png +0 -0
- data/public/stylesheets/base/images/ui-icons_454545_256x240.png +0 -0
- data/public/stylesheets/base/images/ui-icons_888888_256x240.png +0 -0
- data/public/stylesheets/base/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/public/stylesheets/base/jquery.ui.accordion.css +12 -0
- data/public/stylesheets/base/jquery.ui.all.css +2 -0
- data/public/stylesheets/base/jquery.ui.autocomplete.css +39 -0
- data/public/stylesheets/base/jquery.ui.base.css +11 -0
- data/public/stylesheets/base/jquery.ui.button.css +35 -0
- data/public/stylesheets/base/jquery.ui.core.css +37 -0
- data/public/stylesheets/base/jquery.ui.datepicker.css +61 -0
- data/public/stylesheets/base/jquery.ui.dialog.css +13 -0
- data/public/stylesheets/base/jquery.ui.progressbar.css +4 -0
- data/public/stylesheets/base/jquery.ui.resizable.css +13 -0
- data/public/stylesheets/base/jquery.ui.selectable.css +3 -0
- data/public/stylesheets/base/jquery.ui.slider.css +17 -0
- data/public/stylesheets/base/jquery.ui.tabs.css +11 -0
- data/public/stylesheets/base/jquery.ui.theme.css +247 -0
- data/public/stylesheets/jquery.contextMenu.css +62 -0
- data/public/stylesheets/reset.css +18 -0
- data/public/stylesheets/webtools.css +53 -0
- data/public/test.html +47 -0
- data/views/browser.rhtml +63 -0
- data/views/debugger.rhtml +59 -0
- data/views/index.rhtml +8 -0
- data/views/layout.rhtml +24 -0
- data/views/sessions.rhtml +12 -0
- data/views/version.rhtml +10 -0
- metadata +316 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'web_tools'
|
2
|
+
require 'maglev/debugger'
|
3
|
+
|
4
|
+
module WebTools::Middleware
|
5
|
+
class Debugger
|
6
|
+
attr_reader :production_mode
|
7
|
+
|
8
|
+
def initialize(app, *args)
|
9
|
+
@app = app
|
10
|
+
self.production_mode = true if args.include?(:production)
|
11
|
+
self.production_mode ||= false if args.include?(:development)
|
12
|
+
self.production_mode ||= (ENV["RACK_ENV"] == :production)
|
13
|
+
end
|
14
|
+
|
15
|
+
def production_mode=(bool)
|
16
|
+
@production_mode = bool
|
17
|
+
if bool
|
18
|
+
@debugger = self
|
19
|
+
else
|
20
|
+
@debugger = InProcessDebugger.new(@app)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
dup._call(env)
|
26
|
+
end
|
27
|
+
|
28
|
+
def _call(env)
|
29
|
+
@debugger.wrap_call(env) do
|
30
|
+
Maglev::Debugger.debug(production_mode) { @app.call(env) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def wrap_call(env)
|
35
|
+
yield
|
36
|
+
end
|
37
|
+
|
38
|
+
class InProcessDebugger
|
39
|
+
def initialize(app)
|
40
|
+
@app = app
|
41
|
+
@debugger_app = WebTools::Debugger.new
|
42
|
+
load_template
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_template
|
46
|
+
file = File.expand_path(__FILE__)
|
47
|
+
_, @template = ::IO.read(file).gsub("\r\n", "\n").split(/^__END__$/, 2)
|
48
|
+
ERB.new(@template).def_method(self.class, 'render_template(title, path)')
|
49
|
+
end
|
50
|
+
|
51
|
+
def debugger_active?
|
52
|
+
defined?(@@debugger_active) && !!@@debugger_active &&
|
53
|
+
defined?(@@debugged_process) && @@debugged_process.alive?
|
54
|
+
end
|
55
|
+
|
56
|
+
def wrap_call(env)
|
57
|
+
if debugger_active?
|
58
|
+
p env
|
59
|
+
if env["PATH_INFO"] == "/"
|
60
|
+
[500, {"Content-Type" => "text/html"}, info_message(env)]
|
61
|
+
else
|
62
|
+
response = @debugger_app.call(env)
|
63
|
+
response[1]['Access-Control-Allow-Origin'] = "*"
|
64
|
+
response
|
65
|
+
end
|
66
|
+
else
|
67
|
+
application_call(env, yield)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def application_call(env, result)
|
72
|
+
if result.is_a? Maglev::Debugger::Process
|
73
|
+
raise result.exception if result[:skip_debugger]
|
74
|
+
result[:skip_debugger] = false
|
75
|
+
@@debugged_path = env["PATH_INFO"]
|
76
|
+
@@debugged_process = result.thread
|
77
|
+
@@debugger_active = true
|
78
|
+
@@debugged_exception = result.exception.message
|
79
|
+
[500, {"Content-Type" => "text/html"}, info_message(env)]
|
80
|
+
else
|
81
|
+
result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def info_message(env)
|
86
|
+
[render_template(@@debugged_exception,
|
87
|
+
env['rack.url_scheme'].to_s + "://" +
|
88
|
+
env['HTTP_HOST'].to_s +
|
89
|
+
env['SCRIPT_NAME'].to_s + "/process/" +
|
90
|
+
@@debugged_process.object_id.to_s)]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
__END__
|
97
|
+
<!doctype html system>
|
98
|
+
<html>
|
99
|
+
<head>
|
100
|
+
<title>Maglev Debugger</title>
|
101
|
+
</head>
|
102
|
+
<body>
|
103
|
+
<h4><%= title %></h4>
|
104
|
+
There was an error, and execution has been suspended.
|
105
|
+
You can interact with this debugging service to inspect the problem.
|
106
|
+
<br>
|
107
|
+
The entry point is at:
|
108
|
+
<br>
|
109
|
+
<a href="<%= path %>"><%= path %></a>
|
110
|
+
<br>
|
111
|
+
Once you're done debugging, you can click
|
112
|
+
<form style="display:inline" name="resumeForm" method="POST" action="<%= path %>">
|
113
|
+
<input type="hidden" name="running" VALUE="true">
|
114
|
+
<a href="#" onClick="document.resumeForm.submit(); return false">here</a>
|
115
|
+
</form>
|
116
|
+
to resume the original process.
|
117
|
+
</body>
|
118
|
+
</html>
|
@@ -0,0 +1,117 @@
|
|
1
|
+
|
2
|
+
require 'web_tools/support/code_browser'
|
3
|
+
require 'web_tools/support/smalltalk_extensions'
|
4
|
+
|
5
|
+
# This module emulates all of the API from the Smalltalk side of things
|
6
|
+
module WebTools
|
7
|
+
|
8
|
+
# This is a ViewModel for the WebTools Application.
|
9
|
+
#
|
10
|
+
# All of the methods that return "Objects" should return a Hash. Keys
|
11
|
+
# beginning with '_' are reserved for metadata applied by the GUI.
|
12
|
+
class AppModel
|
13
|
+
def initialize
|
14
|
+
end
|
15
|
+
|
16
|
+
VERSION_HEADERS = [['Attribute', 'The attribute.'],
|
17
|
+
['Stone', 'The value for the stone process.'],
|
18
|
+
['WebTools', 'The value for the WebTools vm process (if different than the Stone''s value)']]
|
19
|
+
|
20
|
+
|
21
|
+
# Returns a hash of configuration parameters for the stone and the gem.
|
22
|
+
# The has has three keys:
|
23
|
+
# + :timestamp => when the report was generated
|
24
|
+
# + :headers => array of [name, description] pairs for the fields
|
25
|
+
# + :report => An array of data. Each entry is an array of the field data.
|
26
|
+
#
|
27
|
+
def version_report
|
28
|
+
stone_rpt = stone_version_report
|
29
|
+
gem_rpt = gem_version_report
|
30
|
+
data = { }
|
31
|
+
(stone_rpt.keys + gem_rpt.keys).each do |k|
|
32
|
+
g = stone_rpt[k] == gem_rpt[k] ? '' : gem_rpt[k]
|
33
|
+
data[k] = [stone_rpt[k], g]
|
34
|
+
end
|
35
|
+
{ :timestamp => Time.now.asctime,
|
36
|
+
:headers => VERSION_HEADERS,
|
37
|
+
:report => data }
|
38
|
+
end
|
39
|
+
|
40
|
+
# An array of [name, description] entries for the fields in the session report.
|
41
|
+
SESSION_FIELDS =
|
42
|
+
[['User', 'The UserProfile of the session, or nil if the UserProfile is recently created and not visible from this session''s transactional view, or the session is no longer active.'],
|
43
|
+
['PID', 'The process ID of the Gem process of the session.'],
|
44
|
+
['Host', 'The hostname of the machine running the Gem process (a String, limited to 127 bytes).'],
|
45
|
+
['Prim #', 'Primitive number in which the Gem is executing (if it is executing in a long primitive such as MFC).'],
|
46
|
+
['View Age', 'Time since the session''s most recent beginTransaction, commitTransaction, or abortTransaction.'],
|
47
|
+
['State', 'The session state (an enum from SesTaskStatusEType in session.ht).'],
|
48
|
+
['Trans', 'One of the following: ''none'' if the session is in transactionless mode, ''out'' if it is not in a transaction, and ''in'' if it is in a transaction.'],
|
49
|
+
['Oldest CR?', 'A Boolean whose value is true if the session is currently referencing the oldest commit record, and false if it is not.'],
|
50
|
+
['Serial', 'The session''s serial number. A serial number will not be reused until the stone is restarted.'],
|
51
|
+
['Session', "The session's sessionId."],
|
52
|
+
['GCI IP', 'A String containing the ip address of host running the GCI process. If the GCI application is linked (using libgcilnk*.so or gcilnk*.dll) this ip address is the address of the machine running the gem process.'],
|
53
|
+
['Priority', 'The priority of the session where 0 is lowest, 2 is normal, and 4 is highest. Session priority is used by the stone to order requests for service by sessions.'],
|
54
|
+
['Host ID', 'Unique host ID of the host where the session is running.'],
|
55
|
+
['Quiet', 'Time since the session''s most recent request to stone.'],
|
56
|
+
['Age', 'Time since the session logged in.'],
|
57
|
+
['CRB', 'Commit Record Backlog: number of commits which have occurred since the session obtained its view.']]
|
58
|
+
|
59
|
+
# Returns a hash of configuration parameters for the stone and the gem.
|
60
|
+
# The has has three keys:
|
61
|
+
# + :timestamp => when the report was generated
|
62
|
+
# + :headers => array of [name, description] pairs for the fields
|
63
|
+
# + :report => An array of data. Each entry is an array of the field data.
|
64
|
+
#
|
65
|
+
def session_report
|
66
|
+
ts = Time.now
|
67
|
+
now = ts.to_i
|
68
|
+
session_info = Maglev::System.current_session_ids.map do |id|
|
69
|
+
sess_desc = Maglev::System.description_of_session id
|
70
|
+
sess_desc[0] = sess_desc[0].instance_variable_get(:@_st_userId) # UserProfile
|
71
|
+
sess_desc[3] = '' if sess_desc[3] == 0 # Primitive?
|
72
|
+
sess_desc[4] = format_secs(now - sess_desc[4]) # View Age
|
73
|
+
sess_desc[6] = ['none', 'out', 'in'][sess_desc[6] + 1] # Transaction
|
74
|
+
sess_desc[13] = format_secs(now - sess_desc[13]) # Quiet
|
75
|
+
sess_desc[14] = format_secs(now - sess_desc[14]) # Age
|
76
|
+
sess_desc
|
77
|
+
# sess_cache_slot = Maglev::System.cache_slot_for_sessionid id
|
78
|
+
end
|
79
|
+
{ :timestamp => ts.asctime,
|
80
|
+
:headers => SESSION_FIELDS,
|
81
|
+
:report => session_info }
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
SECS_PER_DAY = 86400
|
86
|
+
SECS_PER_HOUR = 3600
|
87
|
+
SECS_PER_MIN = 60
|
88
|
+
SECS_PER_SEC = 1
|
89
|
+
|
90
|
+
# Format number of seconds like "3 days 12:07:58"
|
91
|
+
def format_secs(seconds)
|
92
|
+
splits = []
|
93
|
+
[SECS_PER_DAY, SECS_PER_HOUR, SECS_PER_MIN, SECS_PER_SEC].each do |x|
|
94
|
+
splits << seconds / x
|
95
|
+
seconds = seconds % x
|
96
|
+
end
|
97
|
+
days = splits.shift
|
98
|
+
|
99
|
+
ts = "%02d:%02d:%02d" % splits
|
100
|
+
days > 0 ? "#{days} #{days == 1 ? 'day' : 'days'} #{ts}" : ts
|
101
|
+
end
|
102
|
+
|
103
|
+
def stone_version_report
|
104
|
+
results = { }
|
105
|
+
rpt = Maglev::System.stone_version_report
|
106
|
+
rpt.keys.each { |k| results[k] = rpt.at(k) }
|
107
|
+
results
|
108
|
+
end
|
109
|
+
|
110
|
+
def gem_version_report
|
111
|
+
results = { }
|
112
|
+
rpt = Maglev::System.gem_version_report
|
113
|
+
rpt.keys.each { |k| results[k] = rpt.at(k) }
|
114
|
+
results
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'web_tools/support/ruby'
|
2
|
+
|
3
|
+
module WebTools
|
4
|
+
# The CodeBrowser is a ViewModel for MagLev code browsing. It keeps the
|
5
|
+
# entire state of the UI and returns structured data for use by a UI.
|
6
|
+
# For the first pass, we do not cache anything.
|
7
|
+
class CodeBrowser
|
8
|
+
def self.class_and_module_list
|
9
|
+
{ 'modules' => WebTools::Ruby.class_and_module_names }
|
10
|
+
end
|
11
|
+
|
12
|
+
def select_module(module_name)
|
13
|
+
mod = Ruby.find_in_namespace(module_name)
|
14
|
+
methods = mod.instance_methods(false) +
|
15
|
+
mod.protected_instance_methods(false) +
|
16
|
+
mod.private_instance_methods(false)
|
17
|
+
{
|
18
|
+
:ancestors => mod.ancestors.reverse,
|
19
|
+
:constants => mod.constants.sort,
|
20
|
+
:instance_methods => methods.uniq.sort,
|
21
|
+
:module_methods => Ruby.module_fns_for(mod),
|
22
|
+
:selected_module => module_name,
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def select_constant(module_name, const_name)
|
27
|
+
parent = Ruby.find_in_namespace module_name
|
28
|
+
{
|
29
|
+
:const_value => ObjectInfo.for(parent.const_get(const_name)),
|
30
|
+
:selected_constant => const_name,
|
31
|
+
:selected_module => module_name,
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def select_method(module_name, method_name, is_instance_method)
|
36
|
+
mod = Ruby.find_in_namespace(module_name)
|
37
|
+
src, file, line = mod.method_source(method_name, is_instance_method)
|
38
|
+
{
|
39
|
+
:is_instance_method => is_instance_method,
|
40
|
+
:method_line_number => line,
|
41
|
+
:method_source => src,
|
42
|
+
:method_source_file => file,
|
43
|
+
:module_name => module_name,
|
44
|
+
:selected_method => method_name,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def object_info(object_id)
|
49
|
+
ObjectInfo.for(object_id)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ObjectInfo
|
54
|
+
# Return a new ObjectInfo instance for the object with the given object
|
55
|
+
# id.
|
56
|
+
def self.for_id(object_id)
|
57
|
+
new(ObjectSpace._id2ref(object_id))
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return a new ObjectInfo instance for the given object.
|
61
|
+
def self.for(object)
|
62
|
+
new(object)
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :info
|
66
|
+
|
67
|
+
def initialize(obj)
|
68
|
+
@info = { }
|
69
|
+
@info[:object_id] = obj.object_id
|
70
|
+
@info[:class] = obj.class.name
|
71
|
+
@info[:inspect] = obj.inspect
|
72
|
+
@info[:instance_variables] = []
|
73
|
+
@info[:enumerated] = []
|
74
|
+
@info[:enumerated_size] = obj.respond_to?(:size) ? obj.size : nil
|
75
|
+
|
76
|
+
obj.instance_variables.each do |iv|
|
77
|
+
val = obj.instance_variable_get(iv)
|
78
|
+
@info[:instance_variables] << [iv, val.inspect, val.object_id]
|
79
|
+
end
|
80
|
+
|
81
|
+
limit = 10
|
82
|
+
case obj
|
83
|
+
when Enumerable, Array
|
84
|
+
obj.each_with_index do |o,i|
|
85
|
+
if i > limit
|
86
|
+
@info[:enumerated] << '...'
|
87
|
+
break
|
88
|
+
else
|
89
|
+
@info[:enumerated] << o.inspect
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
when Hash
|
94
|
+
obj.each_with_index do |pair, idx|
|
95
|
+
if idx > limit
|
96
|
+
@info[:enumerated] << ['', '...']
|
97
|
+
break
|
98
|
+
end
|
99
|
+
@info[:enumerated] << [pair[0].to_s, pair[1].inspect]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_json(*args)
|
106
|
+
@info.to_json
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'maglev/method_source'
|
2
|
+
module WebTools
|
3
|
+
# Provide information about the Ruby environment
|
4
|
+
module Ruby
|
5
|
+
# Traverse the Ruby namespace hierarchy and execute block for all classes
|
6
|
+
# and modules. Returns an IdentitySet of all classes and modules found.
|
7
|
+
# Skips autoloads (i.e., does not trigger them and does not yield them to
|
8
|
+
# the block).
|
9
|
+
#
|
10
|
+
# @param [Module] klass The Class or Module object to start traversal.
|
11
|
+
# Default is Object.
|
12
|
+
#
|
13
|
+
# @param [IdentitySet] rg The recursion guard used to prevent infinite
|
14
|
+
# loops; also used as return value.
|
15
|
+
#
|
16
|
+
# @return [IdentitySet] An IdentitySet of all the Classes and Modules
|
17
|
+
# registered in the Ruby namespace
|
18
|
+
#
|
19
|
+
def each_module(klass=Object, rg=IdentitySet.new, &block)
|
20
|
+
raise 'Must be class or module' unless Module === klass
|
21
|
+
unless rg.include?(klass)
|
22
|
+
rg.add klass
|
23
|
+
yield klass
|
24
|
+
klass.constants.each do |c|
|
25
|
+
unless klass.autoload?(c)
|
26
|
+
obj = klass.const_get c
|
27
|
+
each_module(obj, rg, &block) if Module === obj
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
rg
|
32
|
+
end
|
33
|
+
module_function :each_module
|
34
|
+
|
35
|
+
# Returns a sorted list of class and module names in the Ruby Namespace
|
36
|
+
#
|
37
|
+
# @return [Array] sorted list of class and module names found in the Ruby
|
38
|
+
# namespace hierarchy.
|
39
|
+
def class_and_module_names
|
40
|
+
names = []
|
41
|
+
each_module { |klass| names << klass.name }
|
42
|
+
names.sort
|
43
|
+
end
|
44
|
+
module_function :class_and_module_names
|
45
|
+
|
46
|
+
# A simple message from Ruby Land.
|
47
|
+
def say_something
|
48
|
+
"Hello from MagLev, My name is '#{self.name}'"
|
49
|
+
end
|
50
|
+
module_function :say_something
|
51
|
+
|
52
|
+
# Return an object named in the Ruby namespace.
|
53
|
+
#
|
54
|
+
# @param [String] name The name of the object. E.g., "Object",
|
55
|
+
# "Errno::EACCES", "Foo::Bar::Baz".
|
56
|
+
#
|
57
|
+
# @return [Object] the named object.
|
58
|
+
#
|
59
|
+
# @raise [NameError] if the name can't be found
|
60
|
+
def find_in_namespace(name)
|
61
|
+
name.split('::').inject(Object) do |parent, name|
|
62
|
+
obj = parent.const_get name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
module_function :find_in_namespace
|
66
|
+
|
67
|
+
MODULE_MOD_FNS = Module.instance_methods(false)
|
68
|
+
|
69
|
+
def module_info_for(name)
|
70
|
+
mod = find_in_namespace name
|
71
|
+
{ :constants => mod.constants.sort,
|
72
|
+
:class_methods => module_fns_for(mod),
|
73
|
+
:instance_methods => mod.instance_methods(false).sort }
|
74
|
+
end
|
75
|
+
module_function :module_info_for
|
76
|
+
|
77
|
+
def module_fns_for(mod)
|
78
|
+
res = []
|
79
|
+
begin
|
80
|
+
sclass = mod.__singleton_class
|
81
|
+
res = sclass.instance_methods(false) - MODULE_MOD_FNS
|
82
|
+
rescue => e
|
83
|
+
# nothing
|
84
|
+
end
|
85
|
+
res
|
86
|
+
end
|
87
|
+
module_function :module_fns_for
|
88
|
+
|
89
|
+
# Get the source code for the named class and method
|
90
|
+
#
|
91
|
+
# The success of this method depends on being called from a Ruby stack,
|
92
|
+
# not a Smalltalk stack.
|
93
|
+
#
|
94
|
+
# @param [String] The name of the class
|
95
|
+
# @param [String] The method name
|
96
|
+
# @param [Boolean] if true, then look for a class method, otherwise look
|
97
|
+
# for an instance method.
|
98
|
+
def source_for(class_name, method_name, instance_method=true)
|
99
|
+
klass = find_in_namespace(class_name)
|
100
|
+
gsnmeth = klass.gs_method_for(method_name, instance_method)
|
101
|
+
src = gsnmeth.__source_string
|
102
|
+
file,line = gsnmeth.__source_location
|
103
|
+
file.nil? ? "#{src}\n# No file information available." : "#{src}\n##{file}:#{line}"
|
104
|
+
end
|
105
|
+
module_function :source_for
|
106
|
+
|
107
|
+
# Return a display string suitable for rendering a constant.
|
108
|
+
# Currently, it just returns the results of #inspect on the object.
|
109
|
+
#
|
110
|
+
# TODO
|
111
|
+
# 1. If the const value is a proc, then return "Proc: " + the source code.
|
112
|
+
#
|
113
|
+
# @param [String,Symbol] class_name The name of the class to query.
|
114
|
+
# @param [String,Symbol] const_name The name of the constant.
|
115
|
+
# @return [String] A description of the constant.
|
116
|
+
#
|
117
|
+
def const_info_for(class_name, const_name)
|
118
|
+
klass = find_in_namespace class_name
|
119
|
+
const = klass.const_get const_name
|
120
|
+
const.inspect
|
121
|
+
end
|
122
|
+
module_function :const_info_for
|
123
|
+
|
124
|
+
# def logit(msg)
|
125
|
+
# File.open("/tmp/log", "a+") do |f|
|
126
|
+
# f.puts msg
|
127
|
+
# f.flush
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
# module_function :logit
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'json/pure' unless defined? JSON
|
2
|
+
|
3
|
+
module WebTools::Support::ServiceHelper
|
4
|
+
def self.included(base)
|
5
|
+
base.set :show_exceptions, true
|
6
|
+
base.set :raise_errors, false
|
7
|
+
|
8
|
+
base.error do
|
9
|
+
excep = request.env['sinatra.error']
|
10
|
+
{ '_stack' => excep.backtrace.join("<br>") }.to_json
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns a JSON string that contains the data under the "data" key.
|
15
|
+
# Adds other keys (_time, _stack) if appropriate.
|
16
|
+
def prepare_data(data)
|
17
|
+
raise "Expecting Hash" unless Hash === data
|
18
|
+
data['_time'] = ((Time.now - @ts) * 1_000).to_i
|
19
|
+
data['_stack'] = @stack
|
20
|
+
data.to_json
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Extra Smalltalk features used by WebTools
|
2
|
+
class Maglev::System
|
3
|
+
class_primitive_nobridge 'stone_version_report', 'stoneVersionReport'
|
4
|
+
class_primitive_nobridge 'gem_version_report', 'gemVersionReport'
|
5
|
+
|
6
|
+
# Returns an Array of Fixnums corresponding to all of the sessions
|
7
|
+
# currently running on the GemStone system.
|
8
|
+
#
|
9
|
+
class_primitive_nobridge 'current_session_ids', 'currentSessions'
|
10
|
+
|
11
|
+
# Return the cache process slot number (a SmallInteger) for the given
|
12
|
+
# session ID. The session must be connected to the same shared page
|
13
|
+
# cache as the session invoking this method.
|
14
|
+
#
|
15
|
+
# A return of nil indicates the session could not be located.
|
16
|
+
class_primitive_nobridge 'cache_slot_for_sessionid', 'cacheSlotForSessionId:'
|
17
|
+
|
18
|
+
# Returns an Array describing the session as follows:
|
19
|
+
#
|
20
|
+
# 1. The UserProfile of the session, or nil if the UserProfile is recently
|
21
|
+
# created and not visible from this session's transactional view,
|
22
|
+
# or the session is no longer active.
|
23
|
+
# 2. The process ID of the Gem process of the session (an Integer).
|
24
|
+
# 3. The hostname of the machine running the Gem process
|
25
|
+
# (a String, limited to 127 bytes).
|
26
|
+
# 4. Primitive number in which the Gem is executing, or 0 if it is not executing
|
27
|
+
# in a long primitive.
|
28
|
+
# 5. Time of the session's most recent beginTransaction, commitTransaction, or
|
29
|
+
# abortTransaction (from System timeGmt).
|
30
|
+
# 6. The session state (a SmallInteger).
|
31
|
+
# 7. A SmallInteger whose value is -1 if the session is in transactionless mode,
|
32
|
+
# 0 if it is not in a transaction and 1 if it is in a transaction.
|
33
|
+
# 8. A Boolean whose value is true if the session is currently referencing the
|
34
|
+
# oldest commit record, and false if it is not.
|
35
|
+
# 9. The session's serial number (a SmallInteger).
|
36
|
+
# 10. The session's sessionId (a SmallInteger).
|
37
|
+
# 11. A String containing the ip address of host running the GCI process.
|
38
|
+
# If the GCI application is linked (using libgcilnk*.so or gcilnk*.dll)
|
39
|
+
# this ip address is the address of the machine running the gem process .
|
40
|
+
# 12. The priority of the session (a SmallInteger).
|
41
|
+
# 13. Unique host ID of the host where the session is running (an Integer)
|
42
|
+
# 14. Time of the session's most recent request to stone (from System timeGmt)
|
43
|
+
# 15. Time the session logged in (from System timeGmt)
|
44
|
+
# 16. Number of commits which have occurred since the session obtained its view.
|
45
|
+
#
|
46
|
+
# Because a session can update its commit record without committing a
|
47
|
+
# transaction, it is possible that no session actually references the oldest
|
48
|
+
# commit record. Therefore, the eighth element may be false for all current
|
49
|
+
# sessions.
|
50
|
+
#
|
51
|
+
# To execute this method for any session other than your current session, you
|
52
|
+
# must have the SessionAccess privilege.
|
53
|
+
#
|
54
|
+
class_primitive_nobridge 'description_of_session', 'descriptionOfSession:'
|
55
|
+
end
|
56
|
+
|
57
|
+
# The stoneVersionReport returns a StringKeyValueDictionary.
|
58
|
+
# Unfortunately, that is on a different branch of the Smalltalk class
|
59
|
+
# hierarchy than RubyHash, so we need to add a few methods to let us
|
60
|
+
# iterate over the dictionary.
|
61
|
+
StringKeyValueDictionary = __resolve_smalltalk_global(:StringKeyValueDictionary)
|
62
|
+
class StringKeyValueDictionary
|
63
|
+
primitive_nobridge 'keys', 'keys'
|
64
|
+
primitive_nobridge 'at', 'at:'
|
65
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# The Smalltalk product, upon which MagLev is based, comes with a simple
|
2
|
+
# code and statistics browser written in Smalltalk. This ruby script
|
3
|
+
# registers that code into the Ruby namespace, and then starts the
|
4
|
+
# application. Once it is running, it will print a URL to connect to.
|
5
|
+
|
6
|
+
# Register the Smalltalk WebTools Server class into the Ruby Namespace
|
7
|
+
SmalltalkWebTools = __resolve_smalltalk_global(:Server)
|
8
|
+
|
9
|
+
# Expose the class-side method needed to run the application
|
10
|
+
class SmalltalkWebTools
|
11
|
+
class_primitive_nobridge 'run', 'runInForeground'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Invoke. This will print out something like: http://cairo:60166/
|
15
|
+
# Point your web browser to the given url and play.
|
16
|
+
SmalltalkWebTools.run
|
data/lib/web_tools/ui.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'web_tools'
|
3
|
+
|
4
|
+
class WebTools::UI < Sinatra::Base
|
5
|
+
use WebTools::Browser
|
6
|
+
use WebTools::Debugger
|
7
|
+
use WebTools::Info
|
8
|
+
|
9
|
+
before do
|
10
|
+
@location = env["SCRIPT_NAME"]
|
11
|
+
@javascripts = %w[
|
12
|
+
https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js
|
13
|
+
https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js
|
14
|
+
]
|
15
|
+
@stylesheets = %w[reset.css webtools.css]
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/' do
|
19
|
+
erb :index
|
20
|
+
end
|
21
|
+
|
22
|
+
get '/browser' do
|
23
|
+
@javascripts += %w[webtools/browser.js CodeMirror/js/codemirror.js]
|
24
|
+
erb :browser
|
25
|
+
end
|
26
|
+
|
27
|
+
get '/debugger' do
|
28
|
+
@javascripts += %w[
|
29
|
+
ace/ace.js
|
30
|
+
ace/mode-ruby.js
|
31
|
+
webtools/debugger.js]
|
32
|
+
erb :debugger
|
33
|
+
end
|
34
|
+
|
35
|
+
get '/info/version' do
|
36
|
+
@javascripts += %w[webtools/version.js]
|
37
|
+
@stylesheets = []
|
38
|
+
erb :version
|
39
|
+
end
|
40
|
+
|
41
|
+
get '/info/sessions' do
|
42
|
+
@javascripts += %w[webtools/sessions.js]
|
43
|
+
@stylesheets = []
|
44
|
+
erb :sessions
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def erb(*args, &block)
|
50
|
+
@stylesheets = @stylesheets.map do |path|
|
51
|
+
unless path =~ /^((http)|\/)/
|
52
|
+
"#{@location}/stylesheets/#{path}"
|
53
|
+
else
|
54
|
+
path
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@javascripts = @javascripts.map do |path|
|
59
|
+
unless path =~ /^((http)|\/)/
|
60
|
+
"#{@location}/javascript/#{path}"
|
61
|
+
else
|
62
|
+
path
|
63
|
+
end
|
64
|
+
end
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
data/lib/web_tools.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module WebTools
|
2
|
+
module Support; end
|
3
|
+
module Middleware; end
|
4
|
+
|
5
|
+
path = File.expand_path("../web_tools", __FILE__)
|
6
|
+
autoload :Browser, File.join(path, "browser.rb")
|
7
|
+
autoload :Debugger, File.join(path, "debugger.rb")
|
8
|
+
autoload :Info, File.join(path, "info.rb")
|
9
|
+
autoload :UI, File.join(path, "ui.rb")
|
10
|
+
end
|
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2007-2010 Marijn Haverbeke
|
2
|
+
|
3
|
+
This software is provided 'as-is', without any express or implied
|
4
|
+
warranty. In no event will the authors be held liable for any
|
5
|
+
damages arising from the use of this software.
|
6
|
+
|
7
|
+
Permission is granted to anyone to use this software for any
|
8
|
+
purpose, including commercial applications, and to alter it and
|
9
|
+
redistribute it freely, subject to the following restrictions:
|
10
|
+
|
11
|
+
1. The origin of this software must not be misrepresented; you must
|
12
|
+
not claim that you wrote the original software. If you use this
|
13
|
+
software in a product, an acknowledgment in the product
|
14
|
+
documentation would be appreciated but is not required.
|
15
|
+
|
16
|
+
2. Altered source versions must be plainly marked as such, and must
|
17
|
+
not be misrepresented as being the original software.
|
18
|
+
|
19
|
+
3. This notice may not be removed or altered from any source
|
20
|
+
distribution.
|
21
|
+
|
22
|
+
Marijn Haverbeke
|
23
|
+
marijnh@gmail.com
|