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