maglev-webtools 0.2.1

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.
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