system_browser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: