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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENCE.txt +19 -0
- data/README.md +72 -0
- data/VERSION +1 -0
- data/lib/system_browser.rb +52 -0
- data/lib/system_browser/behaviour.rb +30 -0
- data/lib/system_browser/client.rb +56 -0
- data/lib/system_browser/gem2markdown.rb +117 -0
- data/lib/system_browser/helpers/behaviour_service_helper.rb +14 -0
- data/lib/system_browser/helpers/gem_service_helper.rb +13 -0
- data/lib/system_browser/request.rb +43 -0
- data/lib/system_browser/request_processor.rb +74 -0
- data/lib/system_browser/response.rb +22 -0
- data/lib/system_browser/server.rb +82 -0
- data/lib/system_browser/services/abstract_service.rb +15 -0
- data/lib/system_browser/services/behaviour_service.rb +68 -0
- data/lib/system_browser/services/gem_service.rb +82 -0
- data/lib/system_browser/services/method_service.rb +30 -0
- data/lib/system_browser/services/source_service.rb +60 -0
- data/lib/system_browser/session.rb +108 -0
- data/lib/system_browser/slogger.rb +22 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -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
|
data/CHANGELOG.md
ADDED
data/LICENCE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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,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:
|