Class: FormatText::FTO

Inherits:
String
  • Object
show all
Defined in:
lib/fto.rb

Overview

Description

The FTO class is the user interface; all others are for developers modifying or extending the fto library.

FTO is a subclass of String, so all String methods work on an FTO object. FTO provides the additional format() method.

In addition to string text, the constructor (FTO.new) can take more than a single argument. Additional arguments will be stored as part of the object and will be available to the FTO#format() method at runtime.

An FTO object can be created as just a formatting string, or the constructor invocation can also include values to be applied by the FTO#format() method. At runtime the format() method can override any argument list provided at instantiation, but the latter is not lost.

Constant Summary collapse

@@EnabledEffectors =

Hash of all currently enabled effectors, keyed by their sort key.

{}
@@EffectorKeys =

Ordered array of effector keys (used to index @@EnabledEffectors)

[]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(text = nil, *args) ⇒ FTO

Any argument list is supplied at object instantiation can be temporarily overridden when the FTO#format() method is invoked.

:call-seq:

new<i>()</i> => <i>FTO object</i>
new<i>(String)</i> => <i>FTO object</i>
new<i>(String, arg [, ...])</i> => <i>FTO object</i>


293
294
295
296
297
# File 'lib/fto.rb', line 293

def initialize(text=nil, *args)
  String_initialize(text)
  @safe = true
  @args = args
end

Instance Attribute Details

#safeObject

Not yet implemented Controls whether the final string is built safely and conservatively, or if the output of each effector can alter the input to subsequent ones.



250
251
252
# File 'lib/fto.rb', line 250

def safe
  @safe
end

Class Method Details

.clearEffectorListObject

Clear all effectors from the list (as a prelude to using a different syntax, for instance).

:call-seq:

FTO.clearEffectorList<i>()</i> => <i>nil</i>


306
307
308
309
310
# File 'lib/fto.rb', line 306

def self.clearEffectorList()
  @@RegisteredEffectors.delete_if { |id,e| true }
  self.rebuildEffectorList()
  nil
end

.destroyEffector(id) ⇒ Object

Completely removes the effector with the specified ID from the FTO system. THIS IS NOT REVERSIBLE!

:call-seq:

FTO.destroyEffector<i>(Fixnum)</i> => <i>nil</i>


384
385
386
387
# File 'lib/fto.rb', line 384

def self.destroyEffector(id)
  @@RegisteredEffectors.delete(id)
  self.rebuildEffectorList()
end

.disableEffector(id) ⇒ Object

Disables the effector with the specified ID (such as from FTO.findEffectors()). This is a no-op if the effector is already disabled.

:call-seq:

FTO.disableEffector<i>(Fixnum)</i> => <i>nil</i>


397
398
399
400
401
402
403
# File 'lib/fto.rb', line 397

def self.disableEffector(id)
  if ((e = @@RegisteredEffectors[id]).nil?)
    raise RuntimeError, _('No such effector ') + "ID\##{id}"
  end
  e.disable
  nil
end

.effectorsObject

Debugging class method to access list of registered effectors



264
265
266
# File 'lib/fto.rb', line 264

def self.effectors()        # :nodoc:
  @@RegisteredEffectors
end

.eKeysObject

Debugging class method to access list of effector keys.



271
272
273
# File 'lib/fto.rb', line 271

def self.eKeys()            # :nodoc:
  @@EffectorKeys
end

.enableEffector(id) ⇒ Object

Enables the effector with the specified ID (found in the effector’s id attribute). This is a no-op if the effector is already enabled.

:call-seq:

FTO.enableEffector<i>(Fixnum)</i> => <i>nil</i>


369
370
371
372
373
374
375
# File 'lib/fto.rb', line 369

def self.enableEffector(id)
  if ((e = @@RegisteredEffectors[id]).nil?)
    raise RuntimeError, _('No such effector ') + "ID\##{id}"
  end
  e.enabled = true
  self.rebuildEffectorList()
end

.findEffectors(pattern) ⇒ Object

Returns an array of registered effectors whose names (name attribute) match the specified pattern.

:call-seq:

FTO.findEffectors<i>(String)</i> => <i>Array</i>
FTO.findEffectors<i>(Regexp)</i> => <i>Array</i>


413
414
415
416
417
# File 'lib/fto.rb', line 413

def self.findEffectors(pattern)
  pattern = Regexp.new(pattern) unless (pattern.class == Regexp)
  matches = @@RegisteredEffectors.select { |id,e| e.name.match(pattern) }
  matches.collect { |id,e| e }
end

.rebuildEffectorListObject

:stopdoc:

This class method rebuilds the regular expression and the hash of enabled effectors. It needs to be invoked any time an effector is added, destroyed, enabled, or disabled. It’s for internal use only.



347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/fto.rb', line 347

def self.rebuildEffectorList()
  enabled = @@RegisteredEffectors.select { |id,e| e.enabled? }
  @@EffectorKeys = []
  @@EffectorKeys = enabled.collect { |k,e| e.sortKey }.sort
  @@EffectorKeys.freeze
  @@EnabledEffectors = {}
  enabled.each { |k,e| @@EnabledEffectors[e.sortKey] = e }
  @@EnabledEffectors.freeze
  @@regex = Regexp.new("(#{@@EffectorKeys.collect {|k| @@EnabledEffectors[k].reMatch}.join(')|(')})")
  @@regex.freeze
  nil
end

.regexObject

Debugging class method to access regular expression used to find effectors.



279
280
281
# File 'lib/fto.rb', line 279

def self.regex()            # :nodoc:
  @@regex
end

.registerEffector(*args) ⇒ Object

Add an effector description to the list of those which will be processed by the FTO#format() method.

:call-seq:

FTO.registerEffector<i>(FormatText::Effector)</i> => <i>nil</i>
FTO.registerEffector<i>({ :symattr => value [, ...] })</i> => <i>nil</i>


320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/fto.rb', line 320

def self.registerEffector(*args)
  if ((args.length == 1) && (args[0].class.name.match(/Effector$/)))
    newE = args[0]
  else
    newE = Effector.new('placeholder')
    if ((args.length == 1) && (args[0].class == Hash))
      args[0].each do |key,val|
        eval("newE.#{key.to_s} = val")
      end
    else
      newE = Effector.new(args)
    end
  end
  key = sprintf('%06d-%s', newE.priority, newE.name)
  newE.sortKey = key
  @@RegisteredEffectors[newE.id] = newE
  self.rebuildEffectorList()
  nil
end

Instance Method Details

#format(*argListp) ⇒ Object

Process the formatting string, optionally with a runtime argument list. The argument list can either be a list of values, an array of values, or a FormatText::Context object. (The latter is intended only for internal use with recursion.)

:call-seq:

format<i>()</i> => <i>String</i>
format<i>(arg [, ...])</i> => <i>String</i>
format<i>(Array)</i> => <i>String</i>
format<i>(FormatText::Context)</i> => <i>String</i> (<u>internal use only</u>)


432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/fto.rb', line 432

def format(*argListp)
  argList = argListp.empty? ? @args.clone : argListp
  if ((argList.length == 1) && (argList[0].class == Array)) && 
    argList = argList[0]
  end
  #
  # It's possible we were passed a Context object so we can
  # recurse.  If so, use its values for some of these.
  #
  if ((argList.length == 1) && (argList[0].class == FormatText::Context))
    eContext = argList[0]
    usedArgs = eContext.usedArgs
    argList = eContext.argList
  else
    usedArgs = []
    eContext = Context.new({
                             :ftoObj   => self,
                             :usedArgs => usedArgs,
                             :argList  => argList
                           })
  end
  input = self.to_s
  output = input.clone
  effector = sMatched = nil
  while (m = input.match(@@regex))
    #
    # Find out which effector was matched.  The index in .captures
    # will be the same as the index in @effectors.
    #
    m.captures.length.times do |i|
      next if (m.captures[i].nil?)
      eContext.effectorObj = effector = @@EnabledEffectors[@@EffectorKeys[i]]
      eContext.sMatched = sMatched = m.captures[i]
      eContext.reuseArg = false
      break
    end
    #
    # Call the workhorse for this descriptor
    #
    replacement = effector.code.call(eContext)
    output.sub!(sMatched, replacement)
    input.sub!(sMatched, '')
    #
    # Mark the item at the front of the argument list as having
    # been used, if the effector agrees.  Assume that an argument
    # was actually used if we're moving it, and that the 'last
    # argument used' hasn't changed if the effector has set
    # _reuseArg_.
    #
    unless (eContext.reuseArg)
      usedArgs.push(argList.shift)
      eContext.lastArgUsed = usedArgs.last
    end
  end
  output
end

#String_initializeObject

:stopdoc:

We do this in order to call super on String, but since our argument list is different, we need to finesse it a little. Nobody’s business but ours.



258
# File 'lib/fto.rb', line 258

alias_method(:String_initialize, :initialize)