Class: Hglib::Server
- Inherits:
-
Object
- Object
- Hglib::Server
- Extended by:
- Loggability
- Defined in:
- lib/hglib/server.rb
Overview
A mercurial server object. This uses the Mercurial Command Server protocol to execute Mercurial commands.
Refs:
Constant Summary collapse
- HEADER_TEMPLATE =
String#unpack template for message headers from the command server
'aI>'- COMMAND_TEMPLATE =
Array#pack template for commands sent to the command server
'A*I>A*'- MESSAGE_TEMPLATE =
Array#pack template for plain messages sent to the command server
'I>A*'- EXTENSION_DISABLED_DETAILS =
A Regexp to match the detail message when a command belongs to a disabled extension.
%r{ (?-x:is provided by the following extension:) \s+ (?<extension_name>\w+) }x
Instance Attribute Summary collapse
-
#args ⇒ Object
readonly
The additional arguments to send to the command server on startup.
-
#byte_input_callback ⇒ Object
readonly
The callable used to fetch byte-oriented input.
-
#line_input_callback ⇒ Object
readonly
The callable used to fetch line-oriented input.
-
#pid ⇒ Object
The PID of the running command server if there is one.
-
#reader ⇒ Object
The reader end of the pipe used to communicate with the command server.
-
#repo ⇒ Object
readonly
The Pathname to the repository the server should target.
-
#writer ⇒ Object
The writer end of the pipe used to communicate with the command server.
Class Method Summary collapse
-
.make_command_option(optname, value) ⇒ Object
Form one or more command line options given an
optnameandvalueand return them as an Array. -
.mangle_options(**options) ⇒ Object
Turn the specified
opthashinto an Array of command line options.
Instance Method Summary collapse
-
#handle_errors(command, errors, details) ⇒ Object
Form and raise an exception for the given
errorsresulting from runningcommand. -
#initialize(repo = nil, **args) ⇒ Server
constructor
Create a new Hglib::Server that will be invoked for the specified
repo. -
#is_started? ⇒ Boolean
(also: #started?)
Returns
trueif the underlying command server has been started. -
#on_byte_input(&callback) ⇒ Object
Register a
callbackthat will be called when the command server asks for byte-oriented input. -
#on_line_input(&callback) ⇒ Object
Register a
callbackthat will be called when the command server asks for line-oriented input. -
#register_input_callbacks(io = $stdin) ⇒ Object
Register the callbacks necessary to read both line and byte input from the specified
io, which is expected to respond to #gets and #read. -
#run(command, *args, **options) ⇒ Object
Run the specified
commandwith the givenargsvia the server and return the result. -
#run_with_json_template(command, *args, symbolize: true, **options) ⇒ Object
Run the specified
commandwith the givenargswith the JSON template and return the result. -
#start ⇒ Object
Open a pipe and start the command server.
-
#stop ⇒ Object
Stop the command server and clean up the pipes.
Constructor Details
#initialize(repo = nil, **args) ⇒ Server
Create a new Hglib::Server that will be invoked for the specified repo. Any additional args given will be passed to the ‘hg serve` command on startup.
78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/hglib/server.rb', line 78 def initialize( repo=nil, **args ) @repo = Pathname( repo ) if repo @reader = nil @writer = nil @pid = nil @byte_input_callback = nil @line_input_callback = nil end |
Instance Attribute Details
#args ⇒ Object (readonly)
The additional arguments to send to the command server on startup
101 102 103 |
# File 'lib/hglib/server.rb', line 101 def args @args end |
#byte_input_callback ⇒ Object
The callable used to fetch byte-oriented input
117 118 119 |
# File 'lib/hglib/server.rb', line 117 def byte_input_callback @byte_input_callback end |
#line_input_callback ⇒ Object
The callable used to fetch line-oriented input
122 123 124 |
# File 'lib/hglib/server.rb', line 122 def line_input_callback @line_input_callback end |
#pid ⇒ Object
The PID of the running command server if there is one
113 114 115 |
# File 'lib/hglib/server.rb', line 113 def pid @pid end |
#reader ⇒ Object
The reader end of the pipe used to communicate with the command server.
105 106 107 |
# File 'lib/hglib/server.rb', line 105 def reader @reader end |
#repo ⇒ Object (readonly)
The Pathname to the repository the server should target
97 98 99 |
# File 'lib/hglib/server.rb', line 97 def repo @repo end |
#writer ⇒ Object
The writer end of the pipe used to communicate with the command server.
109 110 111 |
# File 'lib/hglib/server.rb', line 109 def writer @writer end |
Class Method Details
.make_command_option(optname, value) ⇒ Object
Form one or more command line options given an optname and value and return them as an Array.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/hglib/server.rb', line 55 def self::make_command_option( optname, value ) case value when TrueClass return [ optname ] when FalseClass, NilClass return [ optname.sub(/\A--/, '--no-') ] if optname.start_with?( '--' ) when String, Numeric if optname.start_with?( '--' ) return [ "#{optname}=#{value}" ] else return [ optname, value ] end when Array return value.map {|v| self.make_command_option(optname, v) } else raise ArgumentError, "can't handle command option: %p" % [{ name => value }] end end |
.mangle_options(**options) ⇒ Object
Turn the specified opthash into an Array of command line options.
43 44 45 46 47 48 49 50 |
# File 'lib/hglib/server.rb', line 43 def self::( ** ) return .flat_map do |name, val| prefix = name.length > 1 ? '--' : '-' optname = "%s%s" % [ prefix, name.to_s.gsub(/_/, '-') ] self.make_command_option( optname, val ) end.compact end |
Instance Method Details
#handle_errors(command, errors, details) ⇒ Object
Form and raise an exception for the given errors resulting from running command.
204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/hglib/server.rb', line 204 def handle_errors( command, errors, details ) err = nil if details && (m = details.match(EXTENSION_DISABLED_DETAILS) ) err = Hglib::DisabledExtensionError.new( command, m[:extension_name] ) else err = Hglib::CommandError.new( command, errors, details: details ) end raise( err, nil, caller(2) ) end |
#is_started? ⇒ Boolean Also known as: started?
Returns true if the underlying command server has been started.
229 230 231 |
# File 'lib/hglib/server.rb', line 229 def is_started? return self.pid ? true : false end |
#on_byte_input(&callback) ⇒ Object
Register a callback that will be called when the command server asks for byte-oriented input. The callback will be called with the (maximum) number of bytes to return.
129 130 131 132 |
# File 'lib/hglib/server.rb', line 129 def on_byte_input( &callback ) raise LocalJumpError, "no block given" unless callback self.byte_input_callback = callback end |
#on_line_input(&callback) ⇒ Object
Register a callback that will be called when the command server asks for line-oriented input. The callback will be called with the (maximum) number of bytes to return.
138 139 140 141 |
# File 'lib/hglib/server.rb', line 138 def on_line_input( &callback ) raise LocalJumpError, "no block given" unless callback self.line_input_callback = callback end |
#register_input_callbacks(io = $stdin) ⇒ Object
Register the callbacks necessary to read both line and byte input from the specified io, which is expected to respond to #gets and #read.
146 147 148 149 |
# File 'lib/hglib/server.rb', line 146 def register_input_callbacks( io=$stdin ) self.on_byte_input( &io.method(:read) ) self.on_line_input( &io.method(:gets) ) end |
#run(command, *args, **options) ⇒ Object
Run the specified command with the given args via the server and return the result. If the command requires input, the callbacks registered with #on_byte_input and #on_line_input will be used to read it. If one of these callbacks is not registered, an IOError will be raised.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/hglib/server.rb', line 156 def run( command, *args, ** ) args = args.compact self.log.debug { "Running command: %p" % [ Shellwords.join([command.to_s] + args) ] } self.start unless self.started? done = false output = String.new errors = [] args += self.class.( ** ) self.write_command( 'runcommand', command, *args ) until done channel, data = self. case channel when 'o' # self.log.debug "Got command output: %p" % [ data ] output << data when 'r' done = true when 'e' self.log.debug "Got command error: %p" % [ data ] errors << data when 'L' self.log.debug "Server requested line input (%d bytes)" % [ data ] input = self.get_line_input( data.to_i ) self.( input.chomp + "\n" ) when 'I' self.log.debug "Server requested byte input (%d bytes)" % [ data ] input = self.get_byte_input( data.to_i ) self.( input ) else msg = "Unexpected channel %p" % [ channel ] self.log.error( msg ) raise( msg ) if channel =~ /\p{Upper}/ # Mandatory end end self.handle_errors( command, errors, output ) unless errors.empty? self.log.debug { "Got %s response: %p" % [ command.to_s.upcase, output ] } return output end |
#run_with_json_template(command, *args, symbolize: true, **options) ⇒ Object
Run the specified command with the given args with the JSON template and return the result.
219 220 221 222 223 224 225 |
# File 'lib/hglib/server.rb', line 219 def run_with_json_template( command, *args, symbolize: true, ** ) [:T] = 'json' json = self.run( command, *args, ** ) return JSON.parse( json, symbolize_names: symbolize ) end |
#start ⇒ Object
Open a pipe and start the command server.
236 237 238 239 240 |
# File 'lib/hglib/server.rb', line 236 def start self.log.debug "Starting." self.spawn_server self.read_hello end |
#stop ⇒ Object
Stop the command server and clean up the pipes.
244 245 246 247 248 249 250 251 252 253 |
# File 'lib/hglib/server.rb', line 244 def stop return unless self.started? self.log.debug "Stopping." self.writer.close if self.writer self.writer = nil self.reader.close if self.reader self.reader = nil self.stop_server end |