Class: Lcsort

Inherits:
Object
  • Object
show all
Defined in:
lib/lcsort.rb,
lib/lcsort/version.rb,
lib/lcsort/volume_abbreviations.rb

Overview

The sorting code is organized as a class for code organization and possible future parameterization.

Lcsort.new.normalize(call)

But for convenience and efficiency, you can call as a class method too:

Lcsort.normalize(call)

Constant Summary collapse

HIGH_CHAR =
'~'
LC =
/^
\s*
([A-Z]{1,3})  # alpha
\s*
(?:         # optional numbers with optional decimal point
  (\d+)     # num
  (?:\s*?\.\s*?(\d+))?  # dec
)?
\s*
(?:         # optional doon1 -- date or other number eg 1991 , 103rd, 103d
  \.?
  (\d{1,4})
  (?:ST|ND|RD|TH|D)?
)?
\s*
(?:               # optional cutter
  \.? \s*
  ([A-Z])      # cutter letter  c1alpha
  # cutter numeric portion is optional entirely IF at end of string, to
  # support bottomout on partial cutters
  # optional cutter letter suffixes are also supported
  # ie .A12ab -- which requires lookahead to make sure not absorbing subsequent
  # cutter, doh.
  \s*
  (\d+                              # cutter numbers c1num
    (?: [a-zA-Z]{0,2}(?=[ \.]|\Z))? # ...with optional 1-2 letter suffix
  | \Z)
)?
\s*
(?:         # optional doon2 -- date or other number eg 1991 , 103rd, 103d
  \.?
  (\d{1,4})
  (?:ST|ND|RD|TH|D|Q)?
)?
\s*
(?:               # optional cutter
  \.? \s*
  ([A-Z])      # cutter letter  c2alpha
  \s*
  (\d+                              # cutter numbers c2num
    (?: [a-zA-Z]{0,2}(?=[ \.]|\Z))? # ...with optional 1-2 letter suffix
  | \Z)
)?
\s*
(?:               # optional cutter
  \.? \s*
  ([A-Z])      # cutter letter  c3alpha
  \s*
  (\d+                              # cutter numbers c3num
    (?: [a-zA-Z]{0,2}(?=[ \.]|\Z))? # ...with optional 1-2 letter suffix
  | \Z)
)?
(\s+.+?)?        # everthing else extra
\s*$/x
VERSION =
"0.9.1"
VolumeAbbreviations =
abbrevs

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeLcsort

Returns a new instance of Lcsort.



74
75
76
77
78
79
80
81
82
83
84
85
86
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
113
# File 'lib/lcsort.rb', line 74

def initialize()
  self.alpha_width       = 3
  self.class_whole_width = 4
  self.doon_width        = 4
  self.extra_vol_num_width = 4

  # cutter prefix separator must be lower ascii value than digit 0,
  # but higher than cutter_extralow_separator. `.` gives us
  # something that makes debugging easy and doesn't need to be
  # URI-escaped, which is nice.
  self.low_prefix_separator       = '.'
  # cutter extralow separator separates cutter letter suffixes
  # ei as in the 'ab' A234ab. It must be LOWER ascii value than
  # low_prefix_separator to make sort work.
  # Could use space ` `, but `-` is
  # less confusing debugging and nice that it doesn't need to be URI-escaped.
  self.cutter_extralow_separator = '-'

  # Using anything less than ascii 0 should work, but `.` is nice for
  # debugging.
  self.class_letter_padding      = '.'

  # Extra separator needs to be lower than our other separators,
  # especially cutter_extralow_separator.
  # Doubling the cutter_extralow_separator works.
  self.extra_separator           = (self.cutter_extralow_separator * 2)

  # Needs to sort LOWER than extra separator, at least in cases
  # where there's no extra. We tried comma, but MySQL did weird
  # things under utf8 collation. Let's see if it works better with
  # three dashes.
  self.append_suffix_separator   = (self.cutter_extralow_separator * 3)

  # Only state should be configuration, not about individual call numbers.
  # We re-use this for multiple call numbers, and don't want callnum-specific
  # state; we also want to ensure it's thread-safe for using between multiple
  # threads. So freeze it! Doesn't absolutely prevent state changes, but
  # helps and sends the message.
  self.freeze
end

Instance Attribute Details

#alpha_widthObject

Returns the value of attribute alpha_width.



70
71
72
# File 'lib/lcsort.rb', line 70

def alpha_width
  @alpha_width
end

#append_suffix_separatorObject

Returns the value of attribute append_suffix_separator.



71
72
73
# File 'lib/lcsort.rb', line 71

def append_suffix_separator
  @append_suffix_separator
end

#class_letter_paddingObject

Returns the value of attribute class_letter_padding.



71
72
73
# File 'lib/lcsort.rb', line 71

def class_letter_padding
  @class_letter_padding
end

#class_whole_widthObject

Returns the value of attribute class_whole_width.



70
71
72
# File 'lib/lcsort.rb', line 70

def class_whole_width
  @class_whole_width
end

#cutter_extralow_separatorObject

Returns the value of attribute cutter_extralow_separator.



71
72
73
# File 'lib/lcsort.rb', line 71

def cutter_extralow_separator
  @cutter_extralow_separator
end

#doon_widthObject

Returns the value of attribute doon_width.



70
71
72
# File 'lib/lcsort.rb', line 70

def doon_width
  @doon_width
end

#extra_num_regexpObject

Returns the value of attribute extra_num_regexp.



72
73
74
# File 'lib/lcsort.rb', line 72

def extra_num_regexp
  @extra_num_regexp
end

#extra_separatorObject

Returns the value of attribute extra_separator.



71
72
73
# File 'lib/lcsort.rb', line 71

def extra_separator
  @extra_separator
end

#extra_vol_num_widthObject

Returns the value of attribute extra_vol_num_width.



70
71
72
# File 'lib/lcsort.rb', line 70

def extra_vol_num_width
  @extra_vol_num_width
end

#low_prefix_separatorObject

Returns the value of attribute low_prefix_separator.



71
72
73
# File 'lib/lcsort.rb', line 71

def low_prefix_separator
  @low_prefix_separator
end

Class Method Details

.normalize(*args) ⇒ Object



126
127
128
# File 'lib/lcsort.rb', line 126

def self.normalize(*args)
  @global.normalize(*args)
end

.truncated_range_end(*args) ⇒ Object



130
131
132
# File 'lib/lcsort.rb', line 130

def self.truncated_range_end(*args)
  @global.truncated_range_end(*args)
end

Instance Method Details

#left_fill_number(content, width) ⇒ Object

Left-pad a whole number with zeroes to specified width



212
213
214
215
216
217
218
# File 'lib/lcsort.rb', line 212

def left_fill_number(content, width)
  content = content.to_s
  fill_spots = width - content.length
  fill_spots = 0 if fill_spots < 0

  return ('0' * fill_spots) + content
end

#normalize(cn, options = {}) ⇒ Object



134
135
136
137
138
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
# File 'lib/lcsort.rb', line 134

def normalize(cn, options = {})
  callnum = cn.upcase

  match = LC.match(callnum)
  unless match
    return nil
  end

  alpha, num, dec, doon1, c1alpha, c1num, doon2, c2alpha, c2num, c3alpha, c3num, extra = match.captures


  # We can't handle a class number wider than the space we have
  if num && num.length > self.class_whole_width
    return nil
  end

  normal_str = ""

  # Right fill alpha class with separators, to ensure sort, we
  # always have alpha.
  normal_str << right_fill( alpha, alpha_width,        self.class_letter_padding)

  # Left-fill whole number with preceding 0's to ensure sort,
  # Only needed if present, sort will work right regardless.
  if num
    normal_str << left_fill_number(num, class_whole_width)
  end

  # decimal class number needs no fill, add it if we have it.
  # relies on fixed width whole number to sort properly.
  normal_str << dec  if dec

  # Add cutters and doons in order, if present
  normal_str << normalize_doon(doon1) if doon1

  normal_str << normalize_cutter(c1alpha, c1num) if c1alpha

  normal_str << normalize_doon(doon2) if doon2

  normal_str << normalize_cutter(c2alpha, c2num) if c2alpha
  normal_str << normalize_cutter(c3alpha, c3num) if c3alpha

  normal_str << normalize_extra(extra)           if extra

  normal_str << normalize_append_suffix(options[:append_suffix]) if options[:append_suffix]

  # normally we REQUIRE an alpha and number for a good call number,
  # but for creating truncated_end_ranges, we relax that.
  unless options[:range_end_construction]
    unless alpha && num
      return nil
    end
  end

  return normal_str
end

#normalize_append_suffix(suffix) ⇒ Object



253
254
255
# File 'lib/lcsort.rb', line 253

def normalize_append_suffix(suffix)
  self.append_suffix_separator + suffix
end

#normalize_cutter(c_alpha_prefix, c_rest) ⇒ Object



220
221
222
223
224
225
226
227
228
# File 'lib/lcsort.rb', line 220

def normalize_cutter(c_alpha_prefix, c_rest)
  return nil if c_alpha_prefix.nil? || c_rest.nil?

  # Put a low separator before alpha suffix if present, to
  # ensure sort.
  c_rest = c_rest.sub(/(.*\d)([a-zA-Z]{1,2})\Z/, "\\1#{self.cutter_extralow_separator}\\2")

  self.low_prefix_separator + c_alpha_prefix + c_rest
end

#normalize_doon(doon) ⇒ Object



230
231
232
233
234
# File 'lib/lcsort.rb', line 230

def normalize_doon(doon)
  return nil if doon.nil?

  self.low_prefix_separator + left_fill_number(doon, self.doon_width)
end

#normalize_extra(extra) ⇒ Object

The ‘extra’ component is normalized by making it all alphanumeric, and adding an ultra low prefix separator.



238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/lcsort.rb', line 238

def normalize_extra(extra)
  # Left-pad any volume/number type designations with zeros, so
  # they sort appropriately.  We just find ALL numbers and
  # normalize them accordingly, it's good enough!
  extra_normalized = extra.gsub(/(\d+)/) do |match|
    left_fill_number($1, self.extra_vol_num_width)
  end

  # remove all non-alphanumeric
  extra_normalized = extra_normalized.gsub(/[^A-Z0-9]/, '')

  # Add very low prefix separator
  return (self.extra_separator + extra_normalized)
end

#right_fill(content, width, padding) ⇒ Object



203
204
205
206
207
208
209
# File 'lib/lcsort.rb', line 203

def right_fill(content, width, padding)
  content = content.to_s
  fill_spots = width - content.length
  fill_spots = 0 if fill_spots < 0

  content.to_s + (padding * fill_spots)
end

#truncated_range_end(callnum) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
# File 'lib/lcsort.rb', line 191

def truncated_range_end(callnum)
  # Tell normalize to relax it's restrictions for range_end
  # construction.
  normalized = normalize(callnum, :range_end_construction => true)

  return nil unless normalized

  # We just add a HIGH_CHAR on the end to make sure this sorts
  # after the original normalized with ANYTHING else on the end.
  return normalized + HIGH_CHAR
end