Class: Versionomy::Format::Delimiter

Inherits:
Base
  • Object
show all
Defined in:
lib/versionomy/format/delimiter.rb

Overview

The Delimiter format class provides a DSL for building formats that can handle most cases where the fields of a version number appear consecutively in order in the string formatting. We expect most version number schemes should fall into this category.

In general, the strategy is to provide, for each field, a set of regular expressions that recognize different formats for that field. Every field must be of the form "(pre)(value)(post)" where (pre) and (post) are delimiters preceding and following the value. Either or both delimiters may be the empty string.

To parse a string, the string is scanned from left to right and matched against the format for the fields in order. If the string matches, that part of the string is consumed and the field value is interpreted from it. If the string does not match, and the field is not marked as "required", then the field is set to its default value and the next field is tried.

During parsing, the actual delimiters, along with other information such as whether or not fields are required, are saved into a default set of parameters for unparsing. These are saved in the unparse_params of the version value, so that the version number can be unparsed in generally the same form. If the version number value is modified, this allows the unparsing of the new value to generally follow the format of the original string.

Formats that use the Delimiter mechanism also provide support for certain parsing and unparsing parameters. See the documentation for the parse and unparse methods for details.

For a usage example, see the definition of the standard format in Versionomy::Format::Standard#create.

Defined Under Namespace

Classes: AlphabeticIntegerRecognizer, BasicIntegerRecognizer, Builder, FieldHandler, IntegerFieldBuilder, MappingSymbolBuilder, MappingSymbolRecognizer, RecognizerBase, RegexpStringRecognizer, RegexpSymbolRecognizer, StringFieldBuilder, SymbolFieldBuilder

Instance Method Summary (collapse)

Methods inherited from Base

#===, #inspect, #to_s, #unparse_for_serialization

Constructor Details

- (Delimiter) initialize(schema_, default_opts_ = {}, &block_)

Create a format using delimiter tools. You should provide the version number schema, a set of default options, and a block.

Within the block, you can call methods of Versionomy::Format::Delimiter::Builder to provide parsers for the fields of the schema. Any fields you do not explicitly configure will get parsed in a default manner.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/versionomy/format/delimiter.rb', line 87

def initialize(schema_, default_opts_={}, &block_)
  # Special case used by modified_copy
  if schema_.kind_of?(Delimiter)
    orig_ = schema_
    @schema = orig_.schema
    @default_parse_params = orig_.default_parse_params
    @default_unparse_params = orig_.default_unparse_params
    @field_handlers = orig_.instance_variable_get(:@field_handlers).dup
    builder_ = Delimiter::Builder.new(@schema, @field_handlers,
      @default_parse_params, @default_unparse_params)
    ::Blockenspiel.invoke(block_, builder_)
    return
  end
  
  @schema = schema_
  @field_handlers = {}
  @default_parse_params = {}
  @default_unparse_params = {}
  builder_ = Delimiter::Builder.new(@schema, @field_handlers,
    @default_parse_params, @default_unparse_params)
  ::Blockenspiel.invoke(block_, builder_)
  _interpret_field_lists(@default_unparse_params)
  @schema.names.each do |name_|
    @field_handlers[name_] ||= Delimiter::FieldHandler.new(@schema.field_named(name_), default_opts_)
  end
end

Instance Method Details

- (Object) default_parse_params

Return a copy of the default parsing parameters used by this format. This hash cannot be edited in place. To modify the default parsing parameters, use modified_copy and call Versionomy::Format::Delimiter::Builder#default_parse_params in the block.



290
291
292
# File 'lib/versionomy/format/delimiter.rb', line 290

def default_parse_params
  @default_parse_params.dup
end

- (Object) default_unparse_params

Return a copy of the default unparsing parameters used by this format. This hash cannot be edited in place. To modify the default unparsing parameters, use modified_copy and call Versionomy::Format::Delimiter::Builder#default_unparse_params in the block.



300
301
302
# File 'lib/versionomy/format/delimiter.rb', line 300

def default_unparse_params
  @default_unparse_params.dup
end

- (Object) modified_copy(&block_)

Create a copy of this format, with the modifications given in the provided block. You can call methods of Versionomy::Format::Delimiter::Builder in the block. Field handlers that you specify in that block will override and change the field handlers from the original. Any fields not specified in this block will use the handlers from the original.



311
312
313
# File 'lib/versionomy/format/delimiter.rb', line 311

def modified_copy(&block_)
  Delimiter.new(self, &block_)
end

- (Object) parse(string_, params_ = nil)

Parse the given string and return a value. This method is required by the Format contract.

This method provides, out of the box, support for the following parse parameters:

:extra_characters

Determines what to do if the entire string cannot be consumed by the parsing process. If set to :ignore, any extra characters are ignored. If set to :suffix, the extra characters are set as the :suffix unparse parameter and are thus appended to the end of the string when unparsing takes place. If set to :error (the default), causes a Versionomy::Errors::ParseError to be raised if there are uninterpreted characters.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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
200
201
202
203
# File 'lib/versionomy/format/delimiter.rb', line 139

def parse(string_, params_=nil)
  parse_params_ = default_parse_params
  parse_params_.merge!(params_) if params_
  parse_state_ = {
    :backtrack => nil,
    :string => string_,
    :values => {},
    :unparse_params => {},
    :field => @schema.root_field,
    :recognizer_index => 0,
    :previous_field_missing => false
  }
  while (field_ = parse_state_[:field])
    handler_ = @field_handlers[field_.name]
    recognizer_ = handler_.get_recognizer(parse_state_[:recognizer_index])
    parse_data_ = nil
    if recognizer_
      parse_state_[:recognizer_index] += 1
      parse_data_ = recognizer_.parse(parse_state_, parse_params_)
      if parse_data_
        parse_state_[:previous_field_missing] = false
        if recognizer_.requires_next_field
          parse_state_ = {
            :backtrack => parse_state_,
            :string => parse_state_[:string],
            :values => parse_state_[:values].dup,
            :unparse_params => parse_state_[:unparse_params].dup,
            :field => parse_state_[:field],
            :recognizer_index => 0,
            :previous_field_missing => false,
            :next_field_required => true,
          }
        else
          parse_state_[:next_field_required] = false
        end
      end
    elsif parse_state_[:next_field_required]
      parse_state_ = parse_state_[:backtrack]
    else
      parse_data_ = [handler_.default_value, nil, nil, nil]
      parse_state_[:previous_field_missing] = true
      parse_state_[:next_field_required] = false
    end
    if parse_data_
      parse_state_[:values][field_.name] = parse_data_[0]
      parse_state_[:string] = parse_data_[2] if parse_data_[2]
      parse_state_[:unparse_params].merge!(parse_data_[3]) if parse_data_[3]
      parse_state_[:field] = field_.child(parse_data_[0])
      parse_state_[:recognizer_index] = 0
      handler_.set_style_unparse_param(parse_data_[1], parse_state_[:unparse_params])
    end
  end
  unparse_params_ = parse_state_[:unparse_params]
  if parse_state_[:string].length > 0
    case parse_params_[:extra_characters]
    when :ignore
      # do nothing
    when :suffix
      unparse_params_[:suffix] = parse_state_[:string]
    else
      raise Errors::ParseError, "Extra characters: #{parse_state_[:string].inspect}"
    end
  end
  Value.new(parse_state_[:values], self, unparse_params_)
end

- (Object) schema

Returns the schema understood by this format. This method is required by the Format contract.



118
119
120
# File 'lib/versionomy/format/delimiter.rb', line 118

def schema
  @schema
end

- (Object) unparse(value_, params_ = nil)

Unparse the given value and return a string. This method is required by the Format contract.

This method provides, out of the box, support for the following unparse parameters:

:suffix

A string to append to the unparsed string. Default is nothing.

:required_fields

An array of field names that must be present in the unparsed string. These are generally fields with default_value_optional set, but that we want present in the string anyway. For example, in the version number "2.0.0", often the third field will be default_value_optional, but we can include it in the required fields passed to unparse to force it to appear in the string.

:optional_fields

An array of field names that should have their presence in required_fields undone.

:fieldname_required

This is an alternate way of specifying whether a potentially optional field should be required. Accepted values are true and false.

:fieldname_style

Specify the style for unparsing the given field. See Versionomy::Format::Delimiter::Builder#field for more discussion of styles.

:fieldname_delim

Set the pre-delimiter for the given field, if supported. Note that the string specified must be legal-- it must match the regexp for the field. If not, it will revert to the default.

:fieldname_postdelim

Set the post-delimiter for the given field, if supported. Note that the string specified must be legal-- it must match the regexp for the field. If not, it will revert to the default.

:fieldname_case

This is used by letter-formatted integer fields only, and sets the case to use while unparsing. Recognized values are :lower (the default), and :upper.



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/versionomy/format/delimiter.rb', line 245

def unparse(value_, params_=nil)
  unparse_params_ = value_.unparse_params || default_unparse_params
  _interpret_field_lists(unparse_params_)
  if params_
    unparse_params_.merge!(params_)
    _interpret_field_lists(unparse_params_)
  end
  skipped_handler_list_ = nil
  requires_next_field_ = false
  string_ = ''
  value_.each_field_object do |field_, val_|
    handler_ = @field_handlers[field_.name]
    unparse_data_ = handler_.unparse(val_, unparse_params_, requires_next_field_)
    if unparse_data_
      if skipped_handler_list_ && handler_.requires_previous_field
        skipped_handler_list_.each do |pair_|
          frag_ = pair_[0].unparse(pair_[1], unparse_params_, true)
          unless frag_
            raise Errors::UnparseError, "Field #{field_.name} empty although a prerequisite for a later field"
          end
          string_ << frag_[0]
        end
      end
      skipped_handler_list_ = nil
      string_ << unparse_data_[0]
      requires_next_field_ = unparse_data_[1]
    else
      if handler_.requires_previous_field
        (skipped_handler_list_ ||= []) << [handler_, val_]
      else
        skipped_handler_list_ = [[handler_, val_]]
      end
      requires_next_field_ = false
    end
  end
  string_ << (unparse_params_[:suffix] || '')
  string_
end