system_browser 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a327e613373ddd9ce65d00f6709939646690211e
4
+ data.tar.gz: e2fa5ca9c87a732d6ec25579d2f90ef185f0d11b
5
+ SHA512:
6
+ metadata.gz: 24af54e9b78af57ee3ec68a3b4f49d43cdfe2dbb956950476492578c025e57cc9f77208324e035d436d64a634a14a5b148553e8041581aff672882b02070a6d0
7
+ data.tar.gz: 332ec858e408c9dd782d72f97ed3dcd43fbc4093ad3411c6cfe7419d27c856034f5e13f8f8439cd34f3077e6082cfb365dedcbc3e5c597adc45bb20318c39b3d
@@ -0,0 +1,6 @@
1
+ System Browser
2
+ ==============
3
+
4
+ ### v0.1.0 (July 20, 2015)
5
+
6
+ * Initial release
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2015 Kyrylo Silin
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 damages
5
+ arising from the use of this software.
6
+
7
+ Permission is granted to anyone to use this software for any purpose,
8
+ including commercial applications, and to alter it and redistribute it
9
+ freely, subject to the following restrictions:
10
+
11
+ 1. The origin of this software must not be misrepresented; you must not
12
+ claim that you wrote the original software. If you use this software
13
+ in a product, an acknowledgment in the product documentation would be
14
+ appreciated but is not required.
15
+
16
+ 2. Altered source versions must be plainly marked as such, and must not be
17
+ misrepresented as being the original software.
18
+
19
+ 3. This notice may not be removed or altered from any source distribution.
@@ -0,0 +1,72 @@
1
+ System Browser
2
+ ==
3
+
4
+ * [Repository](https://github.com/kyrylo/system_browser_server/)
5
+ * [Client][client]
6
+
7
+ Description
8
+ -----------
9
+
10
+ System Browser is a Ruby gem that serves as a bridge between a Ruby process
11
+ and the [System Browser Client][client]. It allows you to browse Ruby
12
+ behaviours (classes and modules), its methods and the methods' source code.
13
+ _Make sure that you have the client installed_.
14
+
15
+ Examples
16
+ --------
17
+
18
+ ### Basic example
19
+
20
+ ```ruby
21
+ require 'system_browser'
22
+
23
+ SystemBrowser.start
24
+ ```
25
+
26
+ ### Nonblocking start
27
+
28
+ By default `SystemBrowser.start` blocks the current thread. This is useful if you
29
+ launch the browser from a small script. If you start the browser inside a
30
+ complex framework such as Rails, blocking may be unwanted. In this case start
31
+ the browser like this:
32
+
33
+ ```ruby
34
+ SystemBrowser.start(block: false)
35
+ ```
36
+
37
+ The `block` flag will run the browser in a separate thread and return it (it's
38
+ up to you if you want to join it).
39
+
40
+ ### Debugging
41
+
42
+ If you wish to contribute, you may find the `debug` flag useful. For debugging
43
+ purposes invoke the browser like this:
44
+
45
+ ```ruby
46
+ SystemBrowser.start(debug: true)
47
+ ```
48
+
49
+ For additional information see the `examples/` directory.
50
+
51
+ Installation
52
+ ------------
53
+
54
+ All you need is to install the gem.
55
+
56
+ gem install system_browser
57
+
58
+ Limitations
59
+ -----------
60
+
61
+ Supports *only* CRuby.
62
+
63
+ * CRuby 2.2.2 and higher
64
+
65
+ Other Ruby versions were not tested, but in theory Ruby 2.2.x should work fine.
66
+
67
+ License
68
+ -------
69
+
70
+ The project uses the Zlib License. See LICENCE.txt file for more information.
71
+
72
+ [client]: https://github.com/kyrylo/system_browser_client/
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,52 @@
1
+ require 'socket'
2
+ require 'json'
3
+ require 'logger'
4
+ require 'shellwords'
5
+
6
+ require 'system_navigation'
7
+ require 'core_classes'
8
+ require 'coderay'
9
+
10
+ require_relative 'system_browser/server'
11
+ require_relative 'system_browser/client'
12
+ require_relative 'system_browser/session'
13
+ require_relative 'system_browser/request'
14
+ require_relative 'system_browser/request_processor'
15
+ require_relative 'system_browser/response'
16
+ require_relative 'system_browser/slogger'
17
+ require_relative 'system_browser/behaviour'
18
+ require_relative 'system_browser/gem2markdown'
19
+ require_relative 'system_browser/helpers/gem_service_helper'
20
+ require_relative 'system_browser/helpers/behaviour_service_helper'
21
+ require_relative 'system_browser/services/abstract_service'
22
+ require_relative 'system_browser/services/gem_service'
23
+ require_relative 'system_browser/services/behaviour_service'
24
+ require_relative 'system_browser/services/method_service'
25
+ require_relative 'system_browser/services/source_service'
26
+
27
+ module SystemBrowser
28
+ ##
29
+ # Starts the system browser.
30
+ #
31
+ # @param debug [Boolean] If true, prints debugging information
32
+ # @param nonblock [Boolean] If true, then creates a new thread. Otherwise
33
+ # runs in the current thread
34
+ # @return [Session.init]
35
+ def self.start(debug: false, block: true)
36
+ $DEBUG_SB = debug
37
+
38
+ if $DEBUG_SB
39
+ Thread.abort_on_exception = true
40
+ end
41
+
42
+ if block
43
+ SLogger.debug('[browser] Initialising a session, blocking')
44
+
45
+ Session.init
46
+ else
47
+ SLogger.debug('[browser] Initialising a session, NOT blocking')
48
+
49
+ Thread.new { Session.init }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,30 @@
1
+ module SystemBrowser
2
+ class Behaviour
3
+ DEFAULT_INSPECT = /#<(?:Module|Class):(0x[0-9a-f]+)>/
4
+
5
+ def self.from_str(behaviour_str)
6
+ self.new(behaviour_str).extract
7
+ end
8
+
9
+ def initialize(behaviour_str)
10
+ @behaviour_str = behaviour_str
11
+ @sn = SystemNavigation.default
12
+ end
13
+
14
+ def extract
15
+ behaviour = eval(@behaviour_str)
16
+
17
+ if behaviour.nil? && @behaviour_str.match(DEFAULT_INSPECT)
18
+ self.find_behaviour_by_object_id(Integer($1))
19
+ else
20
+ behaviour
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def find_behaviour_by_object_id(behaviour_id)
27
+ @sn.all_objects.find { |obj| (obj.__id__ << 1) == behaviour_id }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ module SystemBrowser
2
+ class Client
3
+ ##
4
+ # The command that the client sends to the server at the session
5
+ # initialisation.
6
+ PID_COMMAND = 'set-window-pid'
7
+
8
+ ##
9
+ # The name of the executable of the client application.
10
+ CLIENT_EXECUTABLE = 'system_browser'
11
+
12
+ ##
13
+ # @return [SystemBrowser::Session]
14
+ attr_writer :session
15
+
16
+ def initialize
17
+ @init_pid = nil
18
+ end
19
+
20
+ ##
21
+ # Spawns a new process in a new process group. I really wanted to find the
22
+ # way to spawn the process in the same group. However, when I do that, I
23
+ # cannot send any signals to the window anymore (the app crashes, because
24
+ # the ruby process exits).
25
+ #
26
+ # @note +@init_pid+ and +@window_pid+ are two different processes. The
27
+ # client uses +@init_id+ to bootstrap itself and +@window_pid+ is the
28
+ # process that can be used to send signals to the client.
29
+ #
30
+ # @return [Integer] the process ID of the client application
31
+ def start
32
+ @init_pid = spawn(CLIENT_EXECUTABLE, pgroup: true)
33
+ Process.wait(@init_pid)
34
+
35
+ @init_pid
36
+ end
37
+
38
+ ##
39
+ # @return [Integer] the process ID of the client process (window).
40
+ def window_pid=(window_pid)
41
+ SLogger.debug("[client] setting window pid (#{window_pid})")
42
+ @window_pid = window_pid
43
+ end
44
+
45
+ ##
46
+ # Destroys the window by sending the SIGINT signal (the window has its own
47
+ # handlers to destroy itself, so it's not our job). Does not wait for
48
+ # anything.
49
+ # @return [void]
50
+ def close
51
+ SLogger.debug("[client] interrupting window (#{@window_pid})...")
52
+
53
+ Process.kill(:INT, @window_pid)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,117 @@
1
+ module SystemBrowser
2
+ class Gem2Markdown
3
+ def self.convert(gem)
4
+ self.new(gem).convert
5
+ end
6
+
7
+ def initialize(gem)
8
+ @gem = gem
9
+ end
10
+
11
+ def convert
12
+ description = ''
13
+
14
+ [header,
15
+ summary,
16
+ homepage,
17
+ license,
18
+ author,
19
+ email,
20
+ newline,
21
+ description,
22
+ newline(2)
23
+ ].each do |desc|
24
+ description += (desc || '')
25
+ end
26
+
27
+ {
28
+ description: description,
29
+ development_deps: development_deps,
30
+ runtime_deps: runtime_deps
31
+ }
32
+ end
33
+
34
+ private
35
+
36
+ def header
37
+ "#{@gem.full_name}\n==" + newline
38
+ end
39
+
40
+ def summary
41
+ if @gem.summary
42
+ @gem.summary + newline
43
+ end
44
+ end
45
+
46
+ def homepage
47
+ if @gem.homepage
48
+ li("homepage: #{@gem.homepage}") + newline
49
+ end
50
+ end
51
+
52
+ def license
53
+ if @gem.licenses.any?
54
+ licenses = @gem.licenses.join(', ')
55
+ li("license: #{licenses}") + newline
56
+ end
57
+ end
58
+
59
+ def author
60
+ if @gem.authors.any?
61
+ authors = @gem.authors.join(', ')
62
+ li("by #{authors}") + newline
63
+ end
64
+ end
65
+
66
+ def description
67
+ if @gem.description
68
+ @gem.description + newline
69
+ end
70
+ end
71
+
72
+ def email
73
+ if @gem.email
74
+ li_h = 'email: '
75
+ email = @gem.email
76
+ item = nil
77
+
78
+ item = case email
79
+ when String
80
+ li(li_h + email)
81
+ when Array
82
+ li(li_h + email.join(', '))
83
+ else
84
+ fail RuntimeError, 'wrong email format'
85
+ end
86
+
87
+ item + newline
88
+ end
89
+
90
+ end
91
+
92
+ def runtime_deps
93
+ if @gem.runtime_dependencies.any?
94
+ @gem.runtime_dependencies.map do |(name, _ver, _type)|
95
+ name.to_s
96
+ end
97
+ end
98
+ end
99
+
100
+ def development_deps
101
+ if @gem.development_dependencies.any?
102
+ @gem.development_dependencies.map do |(name, _ver, _type)|
103
+ name.to_s
104
+ end
105
+ end
106
+ end
107
+
108
+
109
+ def li(item)
110
+ '* ' + item
111
+ end
112
+
113
+ def newline(n = 1)
114
+ "\n" * n
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,14 @@
1
+ module SystemBrowser
2
+ module Helpers
3
+ module BehaviourServiceHelper
4
+ def stdlib_behaviours
5
+ CoreClasses::Stdlib.as_set.map do |behaviour|
6
+ begin
7
+ eval(behaviour)
8
+ rescue
9
+ end
10
+ end.compact
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module SystemBrowser
2
+ module Helpers
3
+ module GemServiceHelper
4
+ def all_gems
5
+ Gem.loaded_specs
6
+ end
7
+
8
+ def find_gem(gem_name)
9
+ self.all_gems[gem_name]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ module SystemBrowser
2
+ class Request
3
+ ##
4
+ # Represents a request that ends connection.
5
+ FIN = 'FIN'
6
+
7
+ attr_reader :action, :resource, :scope, :other, :callback_id
8
+
9
+ def initialize(json)
10
+ @req = self.get_data(json)
11
+ @data = @req['system_browser_server']
12
+
13
+ @action = nil
14
+ @resource = nil
15
+ @scope = nil
16
+ @other = nil
17
+ end
18
+
19
+ def parse
20
+ @callback_id = @req['callbackId']
21
+
22
+ @action = @data['action']
23
+ @resource = @data['resource']
24
+ @scope = @data['scope']
25
+
26
+ @other = @data['other']
27
+ end
28
+
29
+ def sets_client_pid?
30
+ @action == Client::PID_COMMAND
31
+ end
32
+
33
+ def client_pid
34
+ @resource
35
+ end
36
+
37
+ protected
38
+
39
+ def get_data(json)
40
+ JSON.parse(json)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,74 @@
1
+ module SystemBrowser
2
+ class RequestProcessor
3
+ ACTIONS = {
4
+ 'get' => 'add',
5
+ 'autoget' => 'autoadd'
6
+ }.tap { |h| h.default = 'add' }
7
+
8
+ def initialize(request:, session:)
9
+ @request = request
10
+ @session = session
11
+ @services = [
12
+ Services::GemService,
13
+ Services::BehaviourService,
14
+ Services::MethodService,
15
+ Services::SourceService
16
+ ]
17
+ end
18
+
19
+ def process
20
+ @request.parse
21
+
22
+ if @request.sets_client_pid?
23
+ @session.set_client_pid(@request.client_pid)
24
+ else
25
+ self.process_services
26
+ end
27
+ end
28
+
29
+ protected
30
+
31
+ def process_services
32
+ service = self.find_service_for(@request.resource).new(
33
+ data: @request.scope,
34
+ other: @request.other)
35
+
36
+ data = service.__send__(@request.action)
37
+ data = self.replace_weird_characters(data) if data.instance_of?(String)
38
+
39
+ action = self.process_action
40
+ scope = self.process_scope(action)
41
+
42
+ data[:behaviour] = @request.scope if scope.empty?
43
+
44
+ action_str = "#{action}:#{@request.resource}:#{scope}"
45
+ response = Response.new(action: action_str, data: data)
46
+ response.set_callback_id(@request.callback_id)
47
+
48
+ @session.send(response)
49
+ end
50
+
51
+ def process_action
52
+ ACTIONS[@request.action]
53
+ end
54
+
55
+ def process_scope(action)
56
+ case action
57
+ when 'add' then @request.scope
58
+ when 'autoadd' then ''
59
+ else @request.scope
60
+ end
61
+ end
62
+
63
+ def find_service_for(req_service)
64
+ @services.find { |service| service.service_name == req_service }
65
+ end
66
+
67
+ ##
68
+ # Temporary hack before we support weird characters for real.
69
+ def replace_weird_characters(str)
70
+ ascii_str = str.force_encoding('ASCII-8BIT')
71
+ ascii_str.encode('UTF-8', undef: :replace, replace: '')
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,22 @@
1
+ module SystemBrowser
2
+ class Response
3
+ def initialize(data: nil, action: nil, resource: nil)
4
+ @response = {
5
+ callback_id: nil,
6
+ system_browser_client: {
7
+ action: action,
8
+ data: data,
9
+ resource: resource
10
+ }
11
+ }
12
+ end
13
+
14
+ def set_callback_id(callback_id)
15
+ @response[:callback_id] = callback_id
16
+ end
17
+
18
+ def to_json
19
+ JSON.generate(@response) + "\n"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,82 @@
1
+ module SystemBrowser
2
+ class Server
3
+ ##
4
+ # @return [SystemBrowser::Session]
5
+ attr_accessor :session
6
+
7
+ def initialize(port = 9696)
8
+ self.create_tcpserver(port)
9
+ end
10
+
11
+ ##
12
+ # Starts the TCP server.
13
+ #
14
+ # @note This method blocks the thread.
15
+ def start
16
+ Socket.accept_loop(@tcpserver) do |connection|
17
+ SLogger.debug("[server] accepted a new connection (#{connection})")
18
+
19
+ self.session.connection = connection
20
+ self.handle_connection(connection)
21
+ end
22
+ rescue IOError
23
+ Thread.exit
24
+ end
25
+
26
+ def shutdown
27
+ SLogger.debug("[server] shutting down the TCP server...")
28
+
29
+ @tcpserver.close
30
+ end
31
+
32
+ protected
33
+
34
+ ##
35
+ # Creates a new TCP server and tries to find a free port.
36
+ # @return [void]
37
+ def create_tcpserver(port)
38
+ @tcpserver = TCPServer.new(port)
39
+ rescue Errno::EADDRINUSE
40
+ SLogger.debug("[server] port #{port} is occupied. Trying port #{port + 1}")
41
+
42
+ port += 1
43
+ retry
44
+ end
45
+
46
+ ##
47
+ # Handles incoming connections.
48
+ #
49
+ # @param connection [TCPSocket]
50
+ # @return [void]
51
+ def handle_connection(connection)
52
+ loop do
53
+ unless readval = connection.gets
54
+ SLogger.debug("[server] connection #{connection} interrupted")
55
+
56
+ shutdown
57
+ break
58
+ end
59
+
60
+ if readval == Request::FIN
61
+ SLogger.debug("[server] received the FIN request")
62
+
63
+ self.session.destroy
64
+ break
65
+ else
66
+ SLogger.debug("[server] received a request")
67
+
68
+ self.process_request(Request.new(readval))
69
+ end
70
+ end
71
+ end
72
+
73
+ ##
74
+ # @param request [SystemBrowser::Request]
75
+ # @return [void]
76
+ def process_request(request)
77
+ RequestProcessor.new(request: request, session: self.session).process
78
+ rescue => e
79
+ SLogger.log_error(e)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,15 @@
1
+ module SystemBrowser
2
+ module Services
3
+ class AbstractService
4
+ def self.service_name
5
+ self.name.split('::').last.split('Service').first.downcase
6
+ end
7
+
8
+ def initialize(data:, other: nil)
9
+ @sn = SystemNavigation.default
10
+ @data = data
11
+ @other = other
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,68 @@
1
+ module SystemBrowser
2
+ module Services
3
+ class BehaviourService < AbstractService
4
+ CACHED_BEHAVIOURS = {}
5
+
6
+ def self.all_from(gem)
7
+ if CACHED_BEHAVIOURS.has_key?(gem)
8
+ CACHED_BEHAVIOURS[gem]
9
+ else
10
+ CACHED_BEHAVIOURS[gem] = SystemNavigation.new.all_classes_and_modules_in_gem_named(gem)
11
+ end
12
+ end
13
+
14
+ def get
15
+ self.behaviours.map do |behaviour|
16
+ is_module = behaviour.instance_of?(Module)
17
+
18
+ superclass = unless is_module
19
+ behaviour.superclass
20
+ end
21
+
22
+ {name: (behaviour.name ? behaviour.name : behaviour.inspect),
23
+ isModule: is_module,
24
+ isException: behaviour.ancestors.include?(Exception),
25
+ superclass: superclass}
26
+ end
27
+ end
28
+
29
+ def autoget
30
+ behaviour = SystemBrowser::Behaviour.from_str(@data)
31
+
32
+ if CoreClasses.as_set.find { |c| c == behaviour }
33
+ {gem: GemService::CORE_LABEL}
34
+ elsif self.stdlib_behaviours.find { |c| c == behaviour }
35
+ {gem: GemService::STDLIB_LABEL}
36
+ else
37
+ behaviours = @sn.all_classes_and_modules_in_gem_named(@other)
38
+ gem = if behaviours.include?(behaviour)
39
+ @other
40
+ else
41
+ self.all_gems.keys.find do |g|
42
+ behaviours = @sn.all_classes_and_modules_in_gem_named(g)
43
+ behaviours.include?(behaviour)
44
+ end
45
+ end
46
+
47
+ {gem: gem}
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ include SystemBrowser::Helpers::GemServiceHelper
54
+ include SystemBrowser::Helpers::BehaviourServiceHelper
55
+
56
+ def behaviours
57
+ case @data
58
+ when GemService::CORE_LABEL
59
+ CoreClasses.as_set
60
+ when GemService::STDLIB_LABEL
61
+ self.stdlib_behaviours
62
+ else
63
+ self.class.all_from(@data)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,82 @@
1
+ module SystemBrowser
2
+ module Services
3
+ class GemService < AbstractService
4
+ CORE_LABEL = 'Ruby Core'
5
+ STDLIB_LABEL = 'Ruby Stdlib'
6
+ DEFAULT_GEMS = [{name: CORE_LABEL}, {name: STDLIB_LABEL}]
7
+
8
+ def get
9
+ gems = self.all_gems.map { |gem| {name: gem.first} }
10
+ [*DEFAULT_GEMS, *gems]
11
+ end
12
+
13
+ def description(*args)
14
+ gem = self.find_gem(@data)
15
+
16
+ case @data
17
+ when CORE_LABEL
18
+ desc = <<DESC
19
+ Ruby Core-#{RUBY_VERSION}
20
+ ===
21
+ The Ruby Core defines common Ruby behaviours available to every program.
22
+ DESC
23
+
24
+ {
25
+ description: desc,
26
+ behaviours: self.count_behaviours(CoreClasses.as_set),
27
+ development_deps: [],
28
+ runtime_deps: [],
29
+ }
30
+ when STDLIB_LABEL
31
+ desc = <<DESC
32
+ Ruby Standard Library-#{RUBY_VERSION}
33
+ ===
34
+ The Ruby Standard Library is a vast collection of classes and modules that you can require in your code for additional features. System Browser shows only those behaviours that were required by this Ruby process.
35
+ DESC
36
+ {
37
+ description: desc,
38
+ behaviours: self.count_behaviours(BehaviourService.stdlib_behaviours),
39
+ development_deps: [],
40
+ runtime_deps: []
41
+ }
42
+ else
43
+ gemdata = Gem2Markdown.convert(gem)
44
+ behs = BehaviourService.all_from(gem.name)
45
+ gemdata[:behaviours] = count_behaviours(behs)
46
+ gemdata
47
+ end
48
+ end
49
+
50
+ def open
51
+ editor = [ENV['VISUAL'], ENV['EDITOR']].find{|e| !e.nil? && !e.empty? }
52
+ path = self.find_gem(@data).full_gem_path
53
+
54
+ command = [*Shellwords.split(editor), path]
55
+
56
+ system(*command)
57
+
58
+ :ok
59
+ end
60
+
61
+ protected
62
+
63
+ include SystemBrowser::Helpers::GemServiceHelper
64
+
65
+ def count_behaviours(collection)
66
+ behaviours = {}
67
+
68
+ grouped = collection.group_by(&:class)
69
+
70
+ exceptions = (grouped[Class] || []).group_by do |beh|
71
+ beh.ancestors.include?(Exception)
72
+ end
73
+
74
+ behaviours['modules'] = (grouped[Module] || []).count
75
+ behaviours['classes'] = (exceptions[false] || []).count
76
+ behaviours['exceptions'] = (exceptions[true] || []).count
77
+
78
+ behaviours
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,30 @@
1
+ module SystemBrowser
2
+ module Services
3
+ class MethodService < AbstractService
4
+ def get
5
+ behaviour = SystemBrowser::Behaviour.from_str(@data)
6
+ method_hash = @sn.all_methods_in_behavior(behaviour)
7
+ method_names_hash(method_hash)
8
+ end
9
+
10
+ protected
11
+
12
+ def method_names_hash(method_hash)
13
+ new_h = {}
14
+
15
+ method_hash.keys.each do |key|
16
+ new_val = method_hash[key].map do |k, values|
17
+ {
18
+ k => values.map do |m|
19
+ {name: m.name.to_s, c_method: m.source_location.nil? }
20
+ end
21
+ }
22
+ end
23
+ new_h[key] = new_val.first.merge(new_val.last)
24
+ end
25
+
26
+ new_h
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,60 @@
1
+ module SystemBrowser
2
+ module Services
3
+ class SourceService < AbstractService
4
+ def get
5
+ method = @other['method']['displayName']
6
+ owner = SystemBrowser::Behaviour.from_str(@other['owner'])
7
+
8
+ owner = (method.start_with?('#') ? owner : owner.singleton_class)
9
+ unbound_method = owner.instance_method(method[1..-1].to_sym)
10
+ source = FastMethodSource.comment_and_source_for(unbound_method)
11
+
12
+ CodeRay.scan(self.unindent(source), :ruby).div
13
+ end
14
+
15
+ protected
16
+
17
+ # Remove any common leading whitespace from every line in `text`.
18
+ #
19
+ # This can be used to make a HEREDOC line up with the left margin, without
20
+ # sacrificing the indentation level of the source code.
21
+ #
22
+ # e.g.
23
+ # opt.banner unindent <<-USAGE
24
+ # Lorem ipsum dolor sit amet, consectetur adipisicing elit,
25
+ # sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
26
+ # "Ut enim ad minim veniam."
27
+ # USAGE
28
+ #
29
+ # Heavily based on textwrap.dedent from Python, which is:
30
+ # Copyright (C) 1999-2001 Gregory P. Ward.
31
+ # Copyright (C) 2002, 2003 Python Software Foundation.
32
+ # Written by Greg Ward <gward@python.net>
33
+ #
34
+ # Licensed under <http://docs.python.org/license.html>
35
+ # From <http://hg.python.org/cpython/file/6b9f0a6efaeb/Lib/textwrap.py>
36
+ #
37
+ # @param [String] text The text from which to remove indentation
38
+ # @return [String] The text with indentation stripped.
39
+ def unindent(text, left_padding = 0)
40
+ # Empty blank lines
41
+ text = text.sub(/^[ \t]+$/, '')
42
+
43
+ # Find the longest common whitespace to all indented lines
44
+ # Ignore lines containing just -- or ++ as these seem to be used by
45
+ # comment authors as delimeters.
46
+ margin = text.scan(/^[ \t]*(?!--\n|\+\+\n)(?=[^ \t\n])/).inject do |current_margin, next_indent|
47
+ if next_indent.start_with?(current_margin)
48
+ current_margin
49
+ elsif current_margin.start_with?(next_indent)
50
+ next_indent
51
+ else
52
+ ""
53
+ end
54
+ end
55
+
56
+ text.gsub(/^#{margin}/, ' ' * left_padding)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,108 @@
1
+ module SystemBrowser
2
+ ##
3
+ # This class glues {SystemBrowser::Server} and {SystemBrowser::Client}
4
+ # providing the support for interaction between them.
5
+ class Session
6
+ @@running_session = false
7
+
8
+ ##
9
+ # Initialises a new session.
10
+ def self.init
11
+ if @@running_session
12
+ SLogger.debug("can't init a new session! Kill the old one first")
13
+ return
14
+ else
15
+ @@running_session = true
16
+ end
17
+
18
+ self.new(Server.new, Client.new).init
19
+ end
20
+
21
+ # @return [TCPSocket] the connection between the server and the client
22
+ attr_reader :connection
23
+
24
+ def initialize(server, client)
25
+ @server = server
26
+ @client = client
27
+ [@server, @client].each { |o| o.session = self }
28
+
29
+ @previous_sigint_callback = nil
30
+
31
+ self.register_sigint_hook
32
+ end
33
+
34
+ ##
35
+ # Runs {SystemBrowser::Server} in background. Invokes {SystemBrowser::Client}
36
+ # and suspends the calling thread for the duration of the client.
37
+ # @return [void]
38
+ def init
39
+ Thread.new { @server.start }
40
+ Thread.new { @client.start }.join
41
+
42
+ true
43
+ end
44
+
45
+ def destroy
46
+ self.restore_previous_sigint
47
+
48
+ @client.close
49
+ @server.shutdown
50
+
51
+ @@running_session = false
52
+
53
+ SLogger.debug('[SESSION] the session was destroyed')
54
+ end
55
+
56
+ ##
57
+ # Sets the client's window pid (real pid).
58
+ def set_client_pid(pid)
59
+ @client.window_pid = pid
60
+ end
61
+
62
+ ##
63
+ # Sends a response to the client.
64
+ #
65
+ # @param response [SystemBrowser::Response] the data to be sent to the client
66
+ # @return [void]
67
+ def send(response)
68
+ self.connection.puts(response.to_json)
69
+ end
70
+
71
+ ##
72
+ # @param connection [TCPSocket] the connection between the server and the
73
+ # client
74
+ # @return [void]
75
+ def connection=(connection)
76
+ @connection = connection
77
+ self.initialize_connection
78
+ end
79
+
80
+ protected
81
+
82
+ ##
83
+ # This method bootstraps the connection between the server and the client.
84
+ # @return [void]
85
+ def initialize_connection
86
+ self.send(Response.new(action: 'init'))
87
+ end
88
+
89
+ def restore_previous_sigint
90
+ Signal.trap(:INT, @previous_sigint_callback)
91
+ end
92
+
93
+ def register_sigint_hook
94
+ @previous_sigint_callback = Signal.trap(:INT, '')
95
+
96
+ Signal.trap(:INT) do
97
+ SLogger.debug('[SESSION] received Ctrl-C, killing myself softly')
98
+
99
+ self.destroy
100
+
101
+ if @previous_sigint_callback.instance_of?(Proc)
102
+ self.restore_previous_sigint
103
+ @previous_sigint_callback.call
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,22 @@
1
+ module SystemBrowser
2
+ module SLogger
3
+ class << self
4
+ @@logger = ::Logger.new(STDOUT)
5
+
6
+ [:debug, :error, :info].each do |m|
7
+ define_method(m) do |*args, &block|
8
+ if $DEBUG_SB
9
+ # Avoid the 'log writing failed due to trap' message in trap contexts.
10
+ Thread.new { @@logger.__send__(m, *args, &block) }.join
11
+ end
12
+ end
13
+ end
14
+
15
+ def log_error(error)
16
+ SLogger.error(error.class) do
17
+ [error.to_s, error.backtrace].join("\n")
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: system_browser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kyrylo Silin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: system_navigation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: core_classes
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coderay
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.10'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.10'
97
+ description: ''
98
+ email: silin@kyrylo.org
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - LICENCE.txt
105
+ - README.md
106
+ - VERSION
107
+ - lib/system_browser.rb
108
+ - lib/system_browser/behaviour.rb
109
+ - lib/system_browser/client.rb
110
+ - lib/system_browser/gem2markdown.rb
111
+ - lib/system_browser/helpers/behaviour_service_helper.rb
112
+ - lib/system_browser/helpers/gem_service_helper.rb
113
+ - lib/system_browser/request.rb
114
+ - lib/system_browser/request_processor.rb
115
+ - lib/system_browser/response.rb
116
+ - lib/system_browser/server.rb
117
+ - lib/system_browser/services/abstract_service.rb
118
+ - lib/system_browser/services/behaviour_service.rb
119
+ - lib/system_browser/services/gem_service.rb
120
+ - lib/system_browser/services/method_service.rb
121
+ - lib/system_browser/services/source_service.rb
122
+ - lib/system_browser/session.rb
123
+ - lib/system_browser/slogger.rb
124
+ homepage: https://github.com/kyrylo/system_browser
125
+ licenses:
126
+ - Zlib
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.4.5
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: ''
148
+ test_files: []
149
+ has_rdoc: