ng 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/lib/nailgun/client/chunk.rb +25 -0
- data/lib/nailgun/client/chunk_header.rb +18 -0
- data/lib/nailgun/client.rb +194 -0
- data/lib/nailgun/version.rb +3 -0
- data/lib/nailgun.rb +49 -0
- data/ng.gemspec +17 -0
- metadata +57 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2012 Ryan Taylor Long
|
2
|
+
|
3
|
+
|
4
|
+
MIT License
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# ng - Pure-Ruby Nailgun client port
|
2
|
+
|
3
|
+
Eliminates the need to shell-out when using Nailgun
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ng'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ng
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Nailgun
|
2
|
+
class Client
|
3
|
+
class Chunk
|
4
|
+
attr_reader :content, :type
|
5
|
+
|
6
|
+
def initialize(type, content)
|
7
|
+
@content = content.to_s
|
8
|
+
@type = type
|
9
|
+
end
|
10
|
+
|
11
|
+
def header
|
12
|
+
ChunkHeader.new(type, length)
|
13
|
+
end
|
14
|
+
|
15
|
+
def length
|
16
|
+
content.length
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
header.to_s + content
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Nailgun
|
2
|
+
class Client
|
3
|
+
class ChunkHeader
|
4
|
+
def initialize(type, content_length)
|
5
|
+
@type = Nailgun::CHUNK_TYPES[type]
|
6
|
+
@content_length = content_length
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_a
|
10
|
+
[@content_length, @type]
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
to_a.pack('NA')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require_relative 'client/chunk'
|
2
|
+
require_relative 'client/chunk_header'
|
3
|
+
require 'socket'
|
4
|
+
require 'io/wait'
|
5
|
+
require 'logger'
|
6
|
+
require 'timeout'
|
7
|
+
|
8
|
+
module Nailgun
|
9
|
+
class Client
|
10
|
+
LOGGER = Logger.new('log')
|
11
|
+
|
12
|
+
attr_reader :opts, :socket
|
13
|
+
|
14
|
+
# Public: Convinience method to instantiate and run the command
|
15
|
+
#
|
16
|
+
# command - see #run
|
17
|
+
# args - see #run
|
18
|
+
# opts = {} - see #initialize
|
19
|
+
#
|
20
|
+
# Returns the duplicated String.
|
21
|
+
def self.run(command, args, opts = {})
|
22
|
+
self.new(opts).run(command, args)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Initialize a Client.
|
26
|
+
#
|
27
|
+
# opts = {} - a Hash of options to override the defaults in Nailgun::DEFAULTS
|
28
|
+
def initialize(opts = {})
|
29
|
+
@opts = Nailgun::DEFAULTS.merge(opts)
|
30
|
+
@socket = TCPSocket.new(*@opts.values_at(:hostname, :port))
|
31
|
+
debug "Opened new #{@socket.inspect}"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Run a command on the Client instance
|
35
|
+
#
|
36
|
+
# command - the command string
|
37
|
+
# *args - any arguments to send
|
38
|
+
def run(command, *args)
|
39
|
+
receive_loop # start the loop
|
40
|
+
|
41
|
+
send_args args.flatten
|
42
|
+
send_env opts[:env]
|
43
|
+
send_dir opts[:dir]
|
44
|
+
send_command command
|
45
|
+
send_stdin opts[:stdin]
|
46
|
+
|
47
|
+
receive_loop.join
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public: Start the receiver loop Thread, memoize it, and return the Thread
|
52
|
+
#
|
53
|
+
# Returns the Thread object, whose value will eventually be the exit status from the Nailgun
|
54
|
+
# server
|
55
|
+
def receive_loop
|
56
|
+
@loop ||= Thread.new {
|
57
|
+
catch(:exit) do
|
58
|
+
loop { receive_chunk }
|
59
|
+
end
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Private: Send the argument chunks
|
66
|
+
#
|
67
|
+
# *args - an Array of the arguments to send
|
68
|
+
def send_args(*args)
|
69
|
+
args.flatten.each do |arg|
|
70
|
+
send_chunk :arg, arg
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Private: Send the environment vars.
|
75
|
+
#
|
76
|
+
# env - a Hash in the format of Ruby's ENV constant
|
77
|
+
def send_env(env)
|
78
|
+
env.each do |var|
|
79
|
+
send_chunk :env, var.join(?=)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Private: Send the working directory
|
84
|
+
#
|
85
|
+
# dir - the working directory
|
86
|
+
def send_dir(dir)
|
87
|
+
send_chunk :dir, dir.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
# Private: Send the command to be run
|
91
|
+
#
|
92
|
+
# command - the Nail command (usually a Java class name)
|
93
|
+
def send_command(command)
|
94
|
+
send_chunk :cmd, command
|
95
|
+
end
|
96
|
+
|
97
|
+
# Private: Send the STDIN stream for the Nail to read from.
|
98
|
+
#
|
99
|
+
# io = nil - an IO of the stdin stream to send.
|
100
|
+
def send_stdin(io = nil)
|
101
|
+
unless io.nil?
|
102
|
+
begin
|
103
|
+
send_chunk :stdin, io.read(2048)
|
104
|
+
end until io.eof?
|
105
|
+
io.close
|
106
|
+
end
|
107
|
+
send_chunk :stdin_eof
|
108
|
+
end
|
109
|
+
|
110
|
+
# Private: Send a chunk. Used by the higher-level methods
|
111
|
+
#
|
112
|
+
# type - the chunk type
|
113
|
+
# content = nil - the actual content
|
114
|
+
def send_chunk(type, content = nil)
|
115
|
+
chunk = Chunk.new(type, content).to_s
|
116
|
+
debug "Sending #{type} chunk: #{content.inspect}"
|
117
|
+
socket.write chunk
|
118
|
+
end
|
119
|
+
|
120
|
+
# Private: get the next chunk from the socket, and then determine what to do with it.
|
121
|
+
def receive_chunk
|
122
|
+
Timeout.timeout(Nailgun::TIMEOUT, Nailgun::TimeoutError) do
|
123
|
+
length, type = receive_header
|
124
|
+
if length == 0
|
125
|
+
debug "Received #{type} chunk with no content"
|
126
|
+
else
|
127
|
+
debug "About to read #{type} chunk (#{length} B). Content follows (until <<<< END CHUNK >>>>) "
|
128
|
+
|
129
|
+
content = socket.read(length)
|
130
|
+
|
131
|
+
LOGGER << (content + "<<<< END CHUNK >>>>\n")
|
132
|
+
end
|
133
|
+
|
134
|
+
handle_chunk(type, content)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Private: Block while waiting for a header for the next chunk
|
139
|
+
#
|
140
|
+
# Returns [length, type]
|
141
|
+
def receive_header
|
142
|
+
socket.read(Nailgun::CHUNK_HEADER_LEN).unpack('NA')
|
143
|
+
end
|
144
|
+
|
145
|
+
# Private: Determine what to do with the received chunk
|
146
|
+
#
|
147
|
+
# type - chunk type
|
148
|
+
# content - chunk content
|
149
|
+
def handle_chunk(type, content)
|
150
|
+
case t = Nailgun::CHUNK_TYPES.key(type)
|
151
|
+
when :stdout, :stderr
|
152
|
+
opts[t].write content
|
153
|
+
when :exit
|
154
|
+
socket.close
|
155
|
+
handle_exit(content.to_i)
|
156
|
+
else
|
157
|
+
raise Nailgun::UnexpectedChunktypeError.new([type, content].join(?;))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def handle_exit(code)
|
162
|
+
if code == 0
|
163
|
+
throw :exit
|
164
|
+
elsif ex = Nailgun::EXIT_CODE_EXCEPTIONS[code]
|
165
|
+
raise ex.new
|
166
|
+
else
|
167
|
+
raise Nailgun::OtherError.new(code.to_s)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Private: Debug log message
|
172
|
+
def debug(message)
|
173
|
+
LOGGER.debug(message)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Extend the return value with a success? method akin to Process::Status, which I can't figure
|
177
|
+
# out how to instantiate manually
|
178
|
+
class ExitStatus
|
179
|
+
def initialize(value)
|
180
|
+
@val = value
|
181
|
+
end
|
182
|
+
def success?
|
183
|
+
@val == 0
|
184
|
+
end
|
185
|
+
def method_missing(*args)
|
186
|
+
@val.send(*args)
|
187
|
+
end
|
188
|
+
def inspect
|
189
|
+
@val.inspect
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
data/lib/nailgun.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative 'nailgun/version'
|
2
|
+
require_relative 'nailgun/client'
|
3
|
+
|
4
|
+
module Nailgun
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
hostname: 'localhost',
|
8
|
+
port: 2113,
|
9
|
+
stdin: nil,
|
10
|
+
stdout: STDOUT,
|
11
|
+
stderr: STDERR,
|
12
|
+
env: ENV,
|
13
|
+
dir: Dir.pwd
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
CHUNK_HEADER_LEN = 5
|
17
|
+
|
18
|
+
TIMEOUT = 5
|
19
|
+
|
20
|
+
TimeoutError = Class.new(StandardError)
|
21
|
+
SocketFailedError = Class.new(StandardError)
|
22
|
+
ConnectFailedError = Class.new(StandardError)
|
23
|
+
UnexpectedChunktypeError = Class.new(StandardError)
|
24
|
+
ServerExceptionError = Class.new(StandardError)
|
25
|
+
ConnectionBrokenError = Class.new(StandardError)
|
26
|
+
BadArgumentsError = Class.new(StandardError)
|
27
|
+
OtherError = Class.new(StandardError)
|
28
|
+
|
29
|
+
EXIT_CODE_EXCEPTIONS = {
|
30
|
+
999 => SocketFailedError,
|
31
|
+
998 => ConnectFailedError,
|
32
|
+
997 => UnexpectedChunktypeError,
|
33
|
+
996 => ServerExceptionError,
|
34
|
+
995 => ConnectionBrokenError,
|
35
|
+
994 => BadArgumentsError
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
CHUNK_TYPES = {
|
39
|
+
stdin: '0',
|
40
|
+
stdout: '1',
|
41
|
+
stderr: '2',
|
42
|
+
stdin_eof: '.',
|
43
|
+
arg: 'A',
|
44
|
+
env: 'E',
|
45
|
+
dir: 'D',
|
46
|
+
cmd: 'C',
|
47
|
+
exit: 'X'
|
48
|
+
}.freeze
|
49
|
+
end
|
data/ng.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/nailgun/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ['Ryan Taylor Long']
|
6
|
+
gem.email = ['ryan@rtlong.com']
|
7
|
+
gem.description = %q{Pure-Ruby Nailgun client port}
|
8
|
+
gem.summary = %q{Eliminates the need to shell-out when using Nailgun}
|
9
|
+
gem.homepage = ''
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = 'ng'
|
15
|
+
gem.require_paths = ['lib']
|
16
|
+
gem.version = Nailgun::VERSION
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ng
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan Taylor Long
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-18 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Pure-Ruby Nailgun client port
|
15
|
+
email:
|
16
|
+
- ryan@rtlong.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- lib/nailgun.rb
|
27
|
+
- lib/nailgun/client.rb
|
28
|
+
- lib/nailgun/client/chunk.rb
|
29
|
+
- lib/nailgun/client/chunk_header.rb
|
30
|
+
- lib/nailgun/version.rb
|
31
|
+
- ng.gemspec
|
32
|
+
homepage: ''
|
33
|
+
licenses: []
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.8.23
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: Eliminates the need to shell-out when using Nailgun
|
56
|
+
test_files: []
|
57
|
+
has_rdoc:
|