Class: Hglib::Server

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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

#argsObject (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_callbackObject

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_callbackObject

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

#pidObject

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

#readerObject

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

#repoObject (readonly)

The Pathname to the repository the server should target



97
98
99
# File 'lib/hglib/server.rb', line 97

def repo
  @repo
end

#writerObject

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::mangle_options( **options )
	return options.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.

Raises:

  • (err)


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.

Returns:

  • (Boolean)


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.

Raises:

  • (LocalJumpError)


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.

Raises:

  • (LocalJumpError)


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, **options )
	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.mangle_options( **options )
	self.write_command( 'runcommand', command, *args )

	until done
		channel, data = self.read_message

		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.write_message( input.chomp + "\n" )
		when 'I'
			self.log.debug "Server requested byte input (%d bytes)" % [ data ]
			input = self.get_byte_input( data.to_i )
			self.write_message( 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, **options )
	options[:T] = 'json'

	json = self.run( command, *args, **options )

	return JSON.parse( json, symbolize_names: symbolize )
end

#startObject

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

#stopObject

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