Module: Terminology

Defined in:
lib/_appscript/terminology.rb

Overview

TERMINOLOGY TABLES BUILDER

Constant Summary collapse

@@_terminology_cache =
{}

Class Method Summary collapse

Class Method Details

._make_reference_table(properties, elements, commands) ⇒ Object



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/_appscript/terminology.rb', line 300

def Terminology._make_reference_table(properties, elements, commands)
  # builds tables used for constructing references and commands
  reference_by_code = DefaultTerminology::ReferenceByCode.clone
  reference_by_name = DefaultTerminology::ReferenceByName.clone
  [[:element, elements, 'e'], [:property, properties, 'p']].each do |kind, table, prefix|
    # note: if property and element names are same (e.g. 'file' in BBEdit), will pack as property specifier unless it's a special case (i.e. see :text below). Note that there is currently no way to override this, i.e. to force appscript to pack it as an all-elements specifier instead (in AS, this would be done by prepending the 'every' keyword), so clients would need to use aem for that (but could add an 'all' method to Reference class if there was demand for a built-in workaround)
    table.each_with_index do |item, i|
      name, code = item
      # If an application-defined name overlaps an existing type name but has a different code, append '_' to avoid collision:
      name += '_' if DefaultTerminology::TypeCodeByName.fetch(name, code) != code
      reference_by_code[prefix + code] = name # to handle synonyms, if same code appears more than once then use name from last definition in list
      name, code = table[-i - 1]
      name += '_' if DefaultTerminology::TypeCodeByName.fetch(name, code) != code
      begin
        reference_by_name[name.intern] = [kind, code] # to handle synonyms, if same name appears more than once then use code from first definition in list
      rescue ArgumentError # ignore #intern error if name is empty string
      end
    end
  end
  if reference_by_name.has_key?(:text) # special case: AppleScript always packs 'text of...' as all-elements specifier
    reference_by_name[:text][0] = :element
  end
  commands.reverse.each do |name, code, args| # to handle synonyms, if two commands have same name but different codes, only the first definition should be used (iterating over the commands list in reverse ensures this)
    # Avoid collisions between default commands and application-defined commands with same name but different code (e.g. 'get' and 'set' in InDesign CS2):
    name += '_' if DefaultTerminology::CommandCodeByName.fetch(name, code) != code
    dct = {}
    args.each do |arg_name, arg_code|
      begin
        dct[arg_name.intern] = arg_code
      rescue ArgumentError # ignore #intern error if name is empty string
      end
    end
    begin
      reference_by_name[name.intern] = [:command, [code, dct]]
    rescue ArgumentError # ignore #intern error if name is empty string
    end
  end
  return reference_by_code, reference_by_name
end

._make_type_table(classes, enums, properties) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/_appscript/terminology.rb', line 276

def Terminology._make_type_table(classes, enums, properties)
  # builds tables used for converting symbols to/from AEType, AEEnums
  type_by_code = DefaultTerminology::TypeByCode.clone
  type_by_name = DefaultTerminology::TypeByName.clone
  [[AEM::AEType, properties], [AEM::AEEnum, enums], [AEM::AEType, classes]].each do |klass, table|
    table.each_with_index do |item, i|
      name, code = item
      # If an application-defined name overlaps an existing type name but has a different code, append '_' to avoid collision:
      name += '_' if DefaultTerminology::TypeCodeByName.fetch(name, code) != code
      begin
        type_by_code[code] = name.intern # to handle synonyms, if same code appears more than once then use name from last definition in list
      rescue ArgumentError # ignore #intern error if name is empty string
      end
      name, code = table[-i - 1]
      name += '_' if DefaultTerminology::TypeCodeByName.fetch(name, code) != code
      begin
        type_by_name[name.intern] = klass.new(code) # to handle synonyms, if same name appears more than once then use code from first definition in list
      rescue ArgumentError # ignore #intern error if name is empty string
      end
    end
  end
  return [type_by_code, type_by_name]
end

.aetes_for_app(aem_app) ⇒ Object



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/_appscript/terminology.rb', line 407

def Terminology.aetes_for_app(aem_app)
  begin
    begin
      aetes = aem_app.event('ascrgdte', {'----' => 0}).send(120 * 60)
    rescue AEM::EventError => e
      if  e.number == -192 # aete resource not found
        aetes = []
      else
        raise
      end
    end
  rescue => err
    raise RuntimeError, "Can't get terminology for application (#{aem_app}): #{err}"
  end
  aetes = [aetes] if not aetes.is_a?(Array)
  return aetes
end

.default_tablesObject

public



375
376
377
378
# File 'lib/_appscript/terminology.rb', line 375

def Terminology.default_tables
  # [typebycode, typebyname, referencebycode, referencebyname]
  return _make_type_table([], [], []) + _make_reference_table([], [], [])
end

.dump(app_name, module_name, out_path) ⇒ Object

public



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
# File 'lib/_appscript/terminology.rb', line 439

def Terminology.dump(app_name, module_name, out_path)
  # Export application terminology tables as a Ruby module
  # app_path : string -- name or path of application
  # module_name : string -- name of generated module (must be a valid Ruby constant)
  # out_path : string -- module file to write
  #
  # Generates a Ruby module containing an application's basic terminology
  # (names and codes) as used by appscript.
  #
  # Call the #dump method to dump faulty aetes to Ruby module, e.g.:
  #
  #   Terminology.dump('MyApp', 'MyAppGlue', '/path/to/ruby/modules/myappglue.rb')
  #
  # Patch any errors by hand, then import the patched module into your script
  # and pass it to appscript's app() constructor via its 'terms' argument, e.g.:
  #
  #   require 'appscript'; include Appscript
  #   require 'myappglue'
  #
  #   myapp = app('MyApp', terms => MyAppGlue)
  #
  # Note that dumped terminologies aren't used by appscript's built-in help system.
  #
  app_path = FindApp.by_name(app_name)
  # Get aete(s)
  aetes = Terminology.aetes_for_app(Application.by_path(app_path))
  aetes.delete_if { |aete| not(aete.is_a?(AE::AEDesc) and aete.type == KAE::TypeAETE) }
  tables = TerminologyParser.build_tables_for_aetes(aetes)
  Terminology.dump_tables(tables, module_name, app_path, out_path)
end

.dump_tables(tables, module_name, source_path, out_path) ⇒ Object



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/_appscript/terminology.rb', line 340

def Terminology.dump_tables(tables, module_name, source_path, out_path)
  # Parse aete(s) into intermediate tables, suitable for use by Terminology#tables_for_module
  if not(/^[A-Z][A-Za-z0-9_]*$/ === module_name)
    raise RuntimeError, "Invalid module name."
  end
  # Write module
  File.open(out_path, "w") do |f|
    f.puts "module #{module_name}"
    f.puts "\tVersion = 1.1"
    f.puts "\tPath = #{source_path.inspect}"
    f.puts
    (["Classes", "Enumerators", "Properties", "Elements"].zip(tables[0,4])).each do |name, table|
      f.puts "\t#{name} = ["
      table.sort.each do |item|
        f.puts "\t\t#{item.inspect},"
      end
      f.puts "\t]"
      f.puts
    end
    f.puts "\tCommands = ["
    tables[4].sort.each do |name, code, params|
      f.puts "\t\t[#{name.inspect}, #{code.inspect}, ["
      params.each do |item|
        f.puts "\t\t\t#{item.inspect},"
      end
      f.puts "\t\t]],"
    end
    f.puts "\t]"
    f.puts "end"
  end
end

.tables_for_aetes(aetes) ⇒ Object



380
381
382
383
384
385
386
# File 'lib/_appscript/terminology.rb', line 380

def Terminology.tables_for_aetes(aetes)
  # Build terminology tables from a list of unpacked aete byte strings.
  # Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
  aetes = aetes.reject { |aete| not(aete.is_a?(AE::AEDesc) and aete.type == KAE::TypeAETE and aete.data != '') }
  classes, enums, properties, elements, commands = TerminologyParser.build_tables_for_aetes(aetes)
  return _make_type_table(classes, enums, properties) + _make_reference_table(properties, elements, commands)
end

.tables_for_app(aem_app) ⇒ Object



425
426
427
428
429
430
431
432
433
434
# File 'lib/_appscript/terminology.rb', line 425

def Terminology.tables_for_app(aem_app)
  # Build terminology tables for an application.
  # app : AEM::Application
  # Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
  if not @@_terminology_cache.has_key?(aem_app.identity)
    aetes = Terminology.aetes_for_app(aem_app)
    @@_terminology_cache[aem_app.identity] = Terminology.tables_for_aetes(aetes)
  end
  return @@_terminology_cache[aem_app.identity]
end

.tables_for_module(terms) ⇒ Object



390
391
392
393
394
395
396
397
398
# File 'lib/_appscript/terminology.rb', line 390

def Terminology.tables_for_module(terms)
  # Build terminology tables from a dumped terminology module.
  # Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
  if terms::Version != 1.1
    raise RuntimeError, "Unsupported terminology module version: #{terms::Version} (requires version 1.1)."
  end
  return _make_type_table(terms::Classes, terms::Enumerators, terms::Properties) \
    + _make_reference_table(terms::Properties, terms::Elements, terms::Commands)
end

.tables_for_parsed_sdef(terms) ⇒ Object



400
401
402
403
404
405
# File 'lib/_appscript/terminology.rb', line 400

def Terminology.tables_for_parsed_sdef(terms)
  # Build terminology tables from an SdefParser instance.
  # Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
  return _make_type_table(terms.classes, terms.enumerators, terms.properties) \
    + _make_reference_table(terms.properties, terms.elements, terms.commands)
end