maglev-webtools 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/LICENSE.txt +25 -0
  2. data/README.rdoc +121 -0
  3. data/bin/webtools +15 -0
  4. data/lib/web_tools/#debugger.rb# +212 -0
  5. data/lib/web_tools/browser.rb +45 -0
  6. data/lib/web_tools/debugger.rb +219 -0
  7. data/lib/web_tools/info.rb +29 -0
  8. data/lib/web_tools/middleware/debugger.rb +118 -0
  9. data/lib/web_tools/support/app_model.rb +117 -0
  10. data/lib/web_tools/support/code_browser.rb +109 -0
  11. data/lib/web_tools/support/ruby.rb +132 -0
  12. data/lib/web_tools/support/service_helper.rb +22 -0
  13. data/lib/web_tools/support/smalltalk_extensions.rb +65 -0
  14. data/lib/web_tools/support/smalltalk_tools.rb +16 -0
  15. data/lib/web_tools/ui.rb +67 -0
  16. data/lib/web_tools.rb +10 -0
  17. data/public/images/favicon.ico +0 -0
  18. data/public/javascript/CodeMirror/LICENSE +23 -0
  19. data/public/javascript/CodeMirror/css/Smalltalk.css +34 -0
  20. data/public/javascript/CodeMirror/js/codemirror.js +582 -0
  21. data/public/javascript/CodeMirror/js/editor.js +1671 -0
  22. data/public/javascript/CodeMirror/js/highlight.js +68 -0
  23. data/public/javascript/CodeMirror/js/parseSmalltalk.js +126 -0
  24. data/public/javascript/CodeMirror/js/parsedummy.js +32 -0
  25. data/public/javascript/CodeMirror/js/select.js +699 -0
  26. data/public/javascript/CodeMirror/js/stringstream.js +159 -0
  27. data/public/javascript/CodeMirror/js/tokenize.js +57 -0
  28. data/public/javascript/CodeMirror/js/undo.js +413 -0
  29. data/public/javascript/CodeMirror/js/util.js +133 -0
  30. data/public/javascript/CodeMirror/testSmalltalkParser.html +116 -0
  31. data/public/javascript/ace/ace-uncompressed.js +17299 -0
  32. data/public/javascript/ace/ace.js +1 -0
  33. data/public/javascript/ace/keybinding-emacs.js +1 -0
  34. data/public/javascript/ace/keybinding-vim.js +1 -0
  35. data/public/javascript/ace/mode-c_cpp.js +1 -0
  36. data/public/javascript/ace/mode-clojure.js +1 -0
  37. data/public/javascript/ace/mode-coffee.js +1 -0
  38. data/public/javascript/ace/mode-csharp.js +1 -0
  39. data/public/javascript/ace/mode-css.js +1 -0
  40. data/public/javascript/ace/mode-groovy.js +1 -0
  41. data/public/javascript/ace/mode-html.js +1 -0
  42. data/public/javascript/ace/mode-java.js +1 -0
  43. data/public/javascript/ace/mode-javascript.js +1 -0
  44. data/public/javascript/ace/mode-json.js +1 -0
  45. data/public/javascript/ace/mode-lua.js +1 -0
  46. data/public/javascript/ace/mode-markdown.js +1 -0
  47. data/public/javascript/ace/mode-ocaml.js +1 -0
  48. data/public/javascript/ace/mode-perl.js +1 -0
  49. data/public/javascript/ace/mode-php.js +1 -0
  50. data/public/javascript/ace/mode-python.js +1 -0
  51. data/public/javascript/ace/mode-ruby.js +1 -0
  52. data/public/javascript/ace/mode-scad.js +1 -0
  53. data/public/javascript/ace/mode-scala.js +1 -0
  54. data/public/javascript/ace/mode-scss.js +1 -0
  55. data/public/javascript/ace/mode-svg.js +1 -0
  56. data/public/javascript/ace/mode-textile.js +1 -0
  57. data/public/javascript/ace/mode-xml.js +1 -0
  58. data/public/javascript/ace/theme-clouds.js +1 -0
  59. data/public/javascript/ace/theme-clouds_midnight.js +1 -0
  60. data/public/javascript/ace/theme-cobalt.js +1 -0
  61. data/public/javascript/ace/theme-crimson_editor.js +1 -0
  62. data/public/javascript/ace/theme-dawn.js +1 -0
  63. data/public/javascript/ace/theme-eclipse.js +1 -0
  64. data/public/javascript/ace/theme-idle_fingers.js +1 -0
  65. data/public/javascript/ace/theme-kr_theme.js +1 -0
  66. data/public/javascript/ace/theme-merbivore.js +1 -0
  67. data/public/javascript/ace/theme-merbivore_soft.js +1 -0
  68. data/public/javascript/ace/theme-mono_industrial.js +1 -0
  69. data/public/javascript/ace/theme-monokai.js +1 -0
  70. data/public/javascript/ace/theme-pastel_on_dark.js +1 -0
  71. data/public/javascript/ace/theme-solarized_dark.js +1 -0
  72. data/public/javascript/ace/theme-solarized_light.js +1 -0
  73. data/public/javascript/ace/theme-textmate.js +1 -0
  74. data/public/javascript/ace/theme-twilight.js +1 -0
  75. data/public/javascript/ace/theme-vibrant_ink.js +1 -0
  76. data/public/javascript/ace/worker-coffee.js +1 -0
  77. data/public/javascript/ace/worker-css.js +1 -0
  78. data/public/javascript/ace/worker-javascript.js +1 -0
  79. data/public/javascript/webtools/#debugger.coffee# +253 -0
  80. data/public/javascript/webtools/browser.js +260 -0
  81. data/public/javascript/webtools/debugger.coffee +286 -0
  82. data/public/javascript/webtools/debugger.js +366 -0
  83. data/public/javascript/webtools/sessions.coffee +17 -0
  84. data/public/javascript/webtools/sessions.js +27 -0
  85. data/public/javascript/webtools/version.coffee +14 -0
  86. data/public/javascript/webtools/version.js +20 -0
  87. data/public/stylesheets/base/images/ui-anim_basic_16x16.gif +0 -0
  88. data/public/stylesheets/base/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  89. data/public/stylesheets/base/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  90. data/public/stylesheets/base/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  91. data/public/stylesheets/base/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  92. data/public/stylesheets/base/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  93. data/public/stylesheets/base/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  94. data/public/stylesheets/base/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  95. data/public/stylesheets/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  96. data/public/stylesheets/base/images/ui-icons_222222_256x240.png +0 -0
  97. data/public/stylesheets/base/images/ui-icons_2e83ff_256x240.png +0 -0
  98. data/public/stylesheets/base/images/ui-icons_454545_256x240.png +0 -0
  99. data/public/stylesheets/base/images/ui-icons_888888_256x240.png +0 -0
  100. data/public/stylesheets/base/images/ui-icons_cd0a0a_256x240.png +0 -0
  101. data/public/stylesheets/base/jquery.ui.accordion.css +12 -0
  102. data/public/stylesheets/base/jquery.ui.all.css +2 -0
  103. data/public/stylesheets/base/jquery.ui.autocomplete.css +39 -0
  104. data/public/stylesheets/base/jquery.ui.base.css +11 -0
  105. data/public/stylesheets/base/jquery.ui.button.css +35 -0
  106. data/public/stylesheets/base/jquery.ui.core.css +37 -0
  107. data/public/stylesheets/base/jquery.ui.datepicker.css +61 -0
  108. data/public/stylesheets/base/jquery.ui.dialog.css +13 -0
  109. data/public/stylesheets/base/jquery.ui.progressbar.css +4 -0
  110. data/public/stylesheets/base/jquery.ui.resizable.css +13 -0
  111. data/public/stylesheets/base/jquery.ui.selectable.css +3 -0
  112. data/public/stylesheets/base/jquery.ui.slider.css +17 -0
  113. data/public/stylesheets/base/jquery.ui.tabs.css +11 -0
  114. data/public/stylesheets/base/jquery.ui.theme.css +247 -0
  115. data/public/stylesheets/jquery.contextMenu.css +62 -0
  116. data/public/stylesheets/reset.css +18 -0
  117. data/public/stylesheets/webtools.css +53 -0
  118. data/public/test.html +47 -0
  119. data/views/browser.rhtml +63 -0
  120. data/views/debugger.rhtml +59 -0
  121. data/views/index.rhtml +8 -0
  122. data/views/layout.rhtml +24 -0
  123. data/views/sessions.rhtml +12 -0
  124. data/views/version.rhtml +10 -0
  125. 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>&nbsp;&nbsp;&nbsp;&nbsp;
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
@@ -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