activefacts 0.8.8 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -161,7 +161,7 @@ module ActiveFacts
161
161
  key = rr.key.compact
162
162
  role_refs_by_reading_and_key[[reading, key]] = rr
163
163
  key
164
- end.sort
164
+ end.sort_by{|a| a.map{|k|k.to_s}}
165
165
  raise "Fact types may not have duplicate roles" if keys.uniq.size < keys.size
166
166
  (hash[keys] ||= []) << reading
167
167
  hash
@@ -7,6 +7,9 @@ module ActiveFacts
7
7
  attr_accessor :qualifiers, :context_note
8
8
  attr_reader :fact_type, :reading, :role_sequence # These are the Metamodel objects
9
9
  attr_reader :side_effects
10
+ attr_writer :fact_type # Assigned for a bare (existential) objectification fact
11
+ attr_accessor :fact # When binding fact instances the fact goes here
12
+ attr_accessor :objectified_as # The Reading::RoleRef which objectified this fact type
10
13
 
11
14
  def initialize role_refs_and_words, qualifiers = [], context_note = nil
12
15
  @phrases = role_refs_and_words
@@ -27,6 +30,16 @@ module ActiveFacts
27
30
  !@phrases[0].literal
28
31
  end
29
32
 
33
+ def display
34
+ "#{
35
+ @qualifiers && @qualifiers.size > 0 ? @qualifiers.inspect+' ' : nil
36
+ }#{
37
+ @phrases.map{|p| p.to_s }*" "
38
+ }#{
39
+ @context_note && ' '+@context_note.inspect
40
+ }"
41
+ end
42
+
30
43
  def inspect
31
44
  "#{@qualifiers && @qualifiers.size > 0 ? @qualifiers.inspect+' ' : nil}#{@phrases.map{|p|p.inspect}*" "}#{@context_note && ' '+@context_note.inspect}"
32
45
  end
@@ -48,7 +61,7 @@ module ActiveFacts
48
61
  end
49
62
 
50
63
  def includes_literals
51
- role_refs.detect{|role_ref| role_ref.literal }
64
+ role_refs.detect{|role_ref| role_ref.literal || (ja = role_ref.objectification_join and ja.detect{|jr| jr.includes_literals })}
52
65
  end
53
66
 
54
67
  def bind_roles context
@@ -85,6 +98,7 @@ module ActiveFacts
85
98
  # As this match may not necessarily be used (depending on the side effects),
86
99
  # no change is made to this Reading object - those will be done later.
87
100
  def match_existing_fact_type context
101
+ raise "Internal error, reading already matched, should not match again" if @fact_type
88
102
  rrs = role_refs
89
103
  players = rrs.map{|rr| rr.player}
90
104
  raise "Must identify players before matching fact types" if players.include? nil
@@ -97,13 +111,16 @@ module ActiveFacts
97
111
 
98
112
  # Match existing fact types in objectification joins first:
99
113
  rrs.each do |role_ref|
100
- next unless objectification_join = role_ref.objectification_join && role_ref.objectification_join[0]
101
- objectified_fact_type =
102
- objectification_join.match_existing_fact_type(context)
103
- raise "Unrecognised fact type #{objectification_join.inspect} in #{self.class}" unless objectified_fact_type
104
- objectified_as = objectified_fact_type.entity_type
105
- raise "Fact type #{objectification_join.reading.expand} is not objectified as #{role_ref.inspect}" unless objectified_as || objectified_as != role_ref.player
106
- role_ref.objectification_of = objectified_fact_type
114
+ next unless joins = role_ref.objectification_join and !joins.empty?
115
+ role_ref.objectification_join.each do |oj|
116
+ ft = oj.match_existing_fact_type(context)
117
+ raise "Unrecognised fact type #{oj.display}" unless ft
118
+ if (ft && ft.entity_type == role_ref.player)
119
+ role_ref.objectification_of = ft
120
+ oj.objectified_as = role_ref
121
+ end
122
+ end
123
+ raise "#{role_ref.inspect} contains objectification joins that do not objectify it" unless role_ref.objectification_of
107
124
  end
108
125
 
109
126
  # For each role player, find the compatible types (the set of all subtypes and supertypes).
@@ -673,6 +690,16 @@ module ActiveFacts
673
690
  }>"
674
691
  end
675
692
 
693
+ def to_s
694
+ "#{
695
+ @quantifier && @quantifier.inspect+' ' }#{
696
+ @leading_adjective && @leading_adjective.sub(/ |$/,'- ').sub(/ *$/,' ') }#{
697
+ @role_name || @term }#{
698
+ @trailing_adjective && ' '+@trailing_adjective.sub(/(.* |^)/, '\1-') }#{
699
+ @literal && ' '+@literal.inspect }#{
700
+ @value_constraint && ' '+@value_constraint.inspect }"
701
+ end
702
+
676
703
  def <=>(other)
677
704
  ( 4*(@term <=> other.term) +
678
705
  2*((@leading_adjective||'') <=> (other.leading_adjective||'')) +
@@ -11,11 +11,12 @@ module ActiveFacts
11
11
  # In some situations a Binding will have some RoleRefs with the same adjectives,
12
12
  # and one or more RoleRefs with no adjectives - this is called "loose binding".
13
13
  class Binding
14
- attr_reader :player # The Concept (object type)
15
- attr_reader :refs # an array of the RoleRefs
14
+ attr_reader :player # The Concept (object type)
15
+ attr_reader :refs # an array of the RoleRefs
16
16
  attr_reader :role_name
17
- attr_accessor :rebound_to # Loose binding may set this to another binding
17
+ attr_accessor :rebound_to # Loose binding may set this to another binding
18
18
  attr_accessor :join_node
19
+ attr_accessor :instance # When binding fact instances, the instance goes here
19
20
 
20
21
  def initialize player, role_name = nil
21
22
  @player = player
@@ -365,7 +365,7 @@ module ActiveFacts
365
365
  verbaliser.roles_have_same_player(joined_roles) if join_over
366
366
  end
367
367
 
368
- verbaliser.prepare_role_sequence(c.role_sequence)
368
+ verbaliser.prepare_role_sequence(c.role_sequence, join_over)
369
369
 
370
370
  expanded_readings = verbaliser.verbalise_over_role_sequence(c.role_sequence, nil, role_proximity)
371
371
  if c.min_frequency == 1 && c.max_frequency == nil and c.role_sequence.all_role_ref.size == 2
@@ -389,13 +389,24 @@ module ActiveFacts
389
389
  verbaliser = ActiveFacts::Metamodel::Verbaliser.new
390
390
 
391
391
  # Tell the verbaliser all we know, so it can figure out which players to subscript:
392
- transposed_role_refs.each { |role_refs| verbaliser.role_refs_are_subtype_joined role_refs }
393
- role_sequences.each do |role_sequence|
394
- verbaliser.prepare_role_sequence role_sequence
392
+ players = []
393
+ debug :subscript, "Preparing join across projected roles in set comparison constraint" do
394
+ transposed_role_refs.each do |role_refs|
395
+ verbaliser.role_refs_are_subtype_joined role_refs
396
+ join_over, = ActiveFacts::Metamodel.join_roles_over(role_refs.map{|rr| rr.role})
397
+ players << join_over
398
+ end
399
+ end
400
+ debug :subscript, "Preparing join between roles in set comparison constraint" do
401
+ role_sequences.each do |role_sequence|
402
+ debug :subscript, "role sequence is #{role_sequence.describe}" do
403
+ verbaliser.prepare_role_sequence role_sequence
404
+ end
405
+ end
395
406
  end
396
407
  verbaliser.create_subscripts
397
408
 
398
- if role_sequences.detect{|scr| scr.all_role_ref.detect{|rr| rr.join_node}}
409
+ if role_sequences.detect{|scr| scr.all_role_ref.detect{|rr| rr.join_role}}
399
410
  # This set constraint has an explicit join. Verbalise it.
400
411
 
401
412
  readings_list = role_sequences.
@@ -481,7 +481,7 @@ module ActiveFacts
481
481
  # Skip uniqueness constraints that cover all roles of a fact type, they're implicit
482
482
  fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
483
483
  if fact_types.size == 1 &&
484
- !c.role_sequence.all_role_ref.detect{|rr| rr.join_node } &&
484
+ !c.role_sequence.all_role_ref.detect{|rr| rr.join_role } &&
485
485
  c.max_frequency == 1 && # Uniqueness
486
486
  fact_types[0].all_role.size == c.role_sequence.all_role_ref.size
487
487
  # debugger if !$constraint_id || c.constraint_id.object_id == $foo
@@ -417,7 +417,7 @@ module ActiveFacts
417
417
  debug :orm, "Creating role #{name} nr#{fact_type.all_role.size} of #{fact_type.fact_type_id} played by #{concept.name}"
418
418
 
419
419
  role = @by_id[id] = @constellation.Role(fact_type, fact_type.all_role.size, :concept => concept)
420
- role.role_name = name if name
420
+ role.role_name = name if name && name != concept.name
421
421
  debug :orm, "Fact #{fact_name} (id #{fact_type.fact_type_id.object_id}) role #{x['Name']} is played by #{concept.name}, role is #{role.object_id}"
422
422
 
423
423
  x_vr = x.xpath("orm:ValueRestriction/orm:RoleValueConstraint")
@@ -700,12 +700,19 @@ module ActiveFacts
700
700
  end
701
701
 
702
702
  mc_id = nil
703
+
703
704
  if (mc = @mandatory_constraints_by_rs[role_sequence])
704
705
  # Remove absorbed mandatory constraints, leaving residual ones.
705
706
  debug :orm, "Absorbing MC #{mc['Name']} over #{role_sequence.describe}"
706
707
  @mandatory_constraints_by_rs.delete(role_sequence)
707
708
  mc_id = mc['id']
708
709
  @mandatory_constraint_rs_by_id.delete(mc['id'])
710
+ elsif (fts = role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq).size == 1 and
711
+ fts[0].entity_type
712
+ # this uniqueness constraint is an internal UC on an objectified fact type,
713
+ # so the covered roles are always mandatory (wrt the OFT)
714
+ # That is, the phantom roles are mandatory, even if the visible roles are not.
715
+ mc = true
709
716
  else
710
717
  debug :orm, "No MC to absorb over #{role_sequence.describe}"
711
718
  end
@@ -739,6 +746,39 @@ module ActiveFacts
739
746
  end
740
747
  end
741
748
 
749
+ def subtype_join_step(join, ti)
750
+ subtype_node = join.all_join_node.detect{|jn| jn.concept == ti.subtype } ||
751
+ @constellation.JoinNode(join, join.all_join_node.size, :concept => ti.subtype)
752
+ supertype_node = join.all_join_node.detect{|jn| jn.concept == ti.supertype } ||
753
+ @constellation.JoinNode(join, join.all_join_node.size, :concept => ti.supertype)
754
+ rs = @constellation.RoleSequence(:new)
755
+ @constellation.RoleRef(rs, 0, :role => ti.subtype_role)
756
+ sub_jr = @constellation.JoinRole(subtype_node, ti.subtype_role)
757
+ @constellation.RoleRef(rs, 1, :role => ti.supertype_role)
758
+ sup_jr = @constellation.JoinRole(supertype_node, ti.supertype_role)
759
+ js = @constellation.JoinStep(sub_jr, sup_jr, :fact_type => ti)
760
+ debug :join, "New subtyping join step #{js.describe}"
761
+ js
762
+ end
763
+
764
+ # Make as many join steps as it takes to get from subtype to supertype
765
+ def subtype_join_steps(join, subtype, supertype)
766
+ primary_ti = nil
767
+ other_ti = nil
768
+ subtype.all_type_inheritance_as_subtype.each do |ti|
769
+ next unless ti.supertype.supertypes_transitive.include? supertype
770
+ if ti.provides_identification
771
+ primary_ti ||= ti
772
+ else
773
+ other_ti ||= ti
774
+ end
775
+ end
776
+ ti = primary_ti || other_ti
777
+ # Make supertype join steps first:
778
+ (ti.supertype == supertype ? [] : subtype_join_steps(join, ti.supertype, supertype)) +
779
+ [subtype_join_step(join, ti)]
780
+ end
781
+
742
782
  # Equality and subset joins involve two or more role sequences,
743
783
  # and the respective roles from each sequence must be compatible,
744
784
  # Compatibility might involve subtyping joins but not objectification joins
@@ -774,9 +814,12 @@ module ActiveFacts
774
814
  # For each role_sequence, find the object type over which the join is implied (nil if no join)
775
815
  sequence_join_over = []
776
816
  if role_sequences[0].all_role_ref.size > 1 # There are joins within each sequence.
777
- sequence_join_over = role_sequences.map do |rs|
817
+ sequence_join_over = []
818
+ sequence_joined_roles = []
819
+ role_sequences.map do |rs|
778
820
  join_over, joined_roles = *ActiveFacts::Metamodel.join_roles_over(rs.all_role_ref.map{|rr| rr.role})
779
- join_over
821
+ sequence_join_over << join_over
822
+ sequence_joined_roles << joined_roles
780
823
  end
781
824
  end
782
825
 
@@ -792,97 +835,133 @@ module ActiveFacts
792
835
  end_points.zip(end_joins).map{|(p,j)| p.name+(j ? ' & subtypes':'')}*', '
793
836
  }#{
794
837
  if role_sequences[0].all_role_ref.size > 1
795
- ", joined over #{sequence_join_over.map{|o| o ? o.name : '(none)'}*', '}"
838
+ ", joined over #{
839
+ sequence_join_over.zip(sequence_joined_roles).map{|o, roles|
840
+ (o ? o.name : '(none)') +
841
+ (roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '')
842
+ }*', '}"
796
843
  else
797
844
  ''
798
845
  end
799
846
  }" do
800
847
 
801
- end
802
- return # REVISIT: Disabled until we get the join verbaliser working.
803
- while false
804
-
805
848
  # There may be one join per role sequence:
806
- role_sequences.zip(sequence_join_over).map do |role_sequence, join_over|
849
+ role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
807
850
  # Skip if there's no join here (sequence join nor end-point subset join)
808
- role_refs = role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
809
- next unless join_over or role_refs.detect{|rr| rr.role.concept != end_points[rr.ordinal]}
851
+ role_refs = role_sequence.all_role_ref_in_order
852
+ if !join_over and !role_refs.detect{|rr| rr.role.concept != end_points[rr.ordinal]}
853
+ # No sequence join nor end_point join here
854
+ next
855
+ end
810
856
 
857
+ # A RoleSequence for the actual join end-points
811
858
  replacement_rs = @constellation.RoleSequence(:new)
859
+
812
860
  join = @constellation.Join(:new)
813
861
  join_node = nil
814
- role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.each_with_index do |role_ref, i|
862
+ join_role = nil
863
+ role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
864
+
865
+ # Each role_ref is to an object joined via joined_role to join_node (or which will be the join_node)
866
+
867
+ # Create a join node for the actual end-point (supertype of the constrained roles)
815
868
  end_point = end_points[i]
816
869
  debug :join, "Join Node #{join.all_join_node.size} is for #{end_point.name}"
817
870
  end_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => end_point)
818
- role_node = end_node
819
-
820
- if role_ref.role.concept != end_point
821
- subtype = role_ref.role.concept
822
- debug :join, "Making subtyping join step #{join.all_join_node.size} from #{subtype.name} to #{end_point.name}" do
823
- role_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => role_ref.role.concept)
824
- ti = role_ref.role.concept.all_type_inheritance_as_subtype.detect{|ti| ti.supertype == end_point}
825
- raise "REVISIT: Can't yet handle multiple subtyping levels in subtyping join" unless ti
826
-
827
- # Make a new role sequence over the subtyping fact type:
828
- rs = @constellation.RoleSequence(:new)
829
- @constellation.RoleRef(rs, 0, :role => ti.all_role.detect{|r| r.concept == role_ref.role.concept}, :join_node => role_node)
830
- @constellation.RoleRef(rs, 1, :role => ti.all_role.detect{|r| r.concept == end_point}, :join_node => end_node)
831
871
 
832
- js = @constellation.JoinStep(role_node, end_node, :fact_type => ti)
833
- debug :join, "New subtyping join step #{js.describe}"
872
+ # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
873
+ role_node = end_node
874
+ end_role = role_ref.role
875
+
876
+ # Create subtyping join steps at the end-point, if needed:
877
+ projecting_jr = nil
878
+ constrained_join_role = nil
879
+ if (subtype = role_ref.role.concept) != end_point
880
+ debug :join, "Making subtyping join steps from #{subtype.name} to #{end_point.name}" do
881
+ # There may be more than one supertyping level. Make the steps:
882
+ subtyping_steps = subtype_join_steps(join, subtype, end_point)
883
+ js = subtyping_steps[0]
884
+ constrained_join_role = subtyping_steps[-1].input_join_role
885
+
886
+ # Replace the constrained role and node with the supertype ones:
887
+ end_node = join.all_join_node.detect{|jn| jn.concept == end_point }
888
+ projecting_jr = js.output_join_role
889
+ end_role = js.fact_type.all_role.detect{|r| r.concept == end_point }
890
+ role_node = join.all_join_node.detect{|jn| jn.concept == role_ref.role.concept }
834
891
  end
835
892
  end
836
893
 
837
- @constellation.RoleRef(replacement_rs, 0, :role => role_ref.role, :join_node => end_node)
838
-
839
- # REVISIT: Something here needs to handle unaries, which have a join step over two copies of the same JoinNode
894
+ raise "Internal error: making illegal reference to join node" if end_role.concept != end_node.concept
895
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role)
896
+ projecting_jr ||= (constrained_join_role = @constellation.JoinRole(end_node, end_role))
897
+ projecting_jr.role_ref = rr # Project this RoleRef
840
898
 
841
899
  if join_over
842
- if !join_node
900
+ if !join_node # Create the JoinNode when processing the first role
843
901
  debug :join, "Join Node #{join.all_join_node.size} is over #{join_over.name}"
844
902
  join_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => join_over)
845
903
  end
846
904
  debug :join, "Making join step from #{end_point.name} to #{join_over.name}" do
847
- role = role_ref.role
848
- # Detect an objectification step. Here, the role is in an objectified type, but the end_point isn't another role player of that fact type... or is there a better test?
849
- if join_over == role.fact_type.entity_type
850
- p join_over.name
851
- p role.fact_type.default_reading
852
- p end_point.name
853
- raise "REVISIT: Incomplete"
854
- elsif end_point == role.fact_type.entity_type
855
- fact_type = role.implicit_fact_type
856
- # REVISIT: Might these be reversed... sometimes?
857
- end_role = fact_type.all_role.single
858
- join_role = role
859
- else
860
- fact_type = role.fact_type
861
- end_role = role
862
- # This is unambiguous, since it's come from NORMA which only supports (unambiguous) implicit joins:
863
- join_role = role.fact_type.all_role.detect{|r| r.concept == join_over}
864
- # debugger unless join_role && end_role
865
- end
866
-
867
905
  rs = @constellation.RoleSequence(:new)
868
906
  # Detect the fact type over which we're joining (may involve objectification)
869
- @constellation.RoleRef(rs, 0, :role => end_role, :join_node => end_node)
870
- @constellation.RoleRef(rs, 1, :role => join_role, :join_node => join_node)
871
- js = @constellation.JoinStep(end_node, join_node, :fact_type => fact_type)
907
+ raise "Internal error: making illegal reference to join node" if role_ref.role.concept != role_node.concept
908
+ @constellation.RoleRef(rs, 0, :role => role_ref.role)
909
+ role_jr = @constellation.JoinRole(role_node, role_ref.role)
910
+ raise "Internal error: making illegal reference to join node" if joined_role.concept != join_node.concept
911
+ @constellation.RoleRef(rs, 1, :role => joined_role)
912
+ join_jr = @constellation.JoinRole(join_node, joined_role)
913
+
914
+ js = @constellation.JoinStep(role_jr, join_jr, :fact_type => joined_role.fact_type)
872
915
  debug :join, "New join step #{js.describe}"
873
916
  end
874
917
  else
875
918
  debug :join, "Need join step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
876
- if role_ref.role.fact_type.all_role.size > 1
877
- raise unimplemented
919
+ if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
920
+ # Here we have an end join (step already created) but no sequence join
921
+ if join_node
922
+ raise "Internal error: making illegal join step" if role_ref.role.concept != role_node.concept
923
+ join_jr = @constellation.JoinRole(join_node, join_role)
924
+ role_jr = @constellation.JoinRole(role_node, role_ref.role)
925
+ js = @constellation.JoinStep(join_jr, role_jr, :fact_type => role_ref.role.fact_type)
926
+ roles -= [join_role, role_ref.role]
927
+ roles.each do |incidental_role|
928
+ jn = @constellation.JoinNode(join, join.all_join_node.size, :concept => incidental_role.concept)
929
+ jr = @constellation.JoinRole(jn, incidental_role, :join_step => js)
930
+ end
931
+ else
932
+ if role_sequence.all_role_ref.size > 1
933
+ join_node = role_node
934
+ join_role = role_ref.role
935
+ else
936
+ # There's no join in this role sequence, so we'd drop of fthe bottom without doing the right things. Why?
937
+ # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
938
+ # I think the "then" code causes it to drop out the bottom without making the step (which is otherwise made in every case, see CompanyDirectorEmployee for example)
939
+ role_jr = @constellation.JoinRole(role_node, role_ref.role)
940
+ js = nil
941
+ role_ref.role.fact_type.all_role.each do |role|
942
+ next if role == role_jr.role
943
+ next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
944
+ jn = @constellation.JoinNode(join, join.all_join_node.size, :concept => role.concept)
945
+ jr = @constellation.JoinRole(jn, role)
946
+ if js
947
+ jr.join_step = js # Incidental role
948
+ else
949
+ js = @constellation.JoinStep(role_jr, jr, :fact_type => role_ref.role.fact_type)
950
+ end
951
+ end
952
+ end
953
+ end
878
954
  else
879
- js = @constellation.JoinStep(role_node, role_node, :fact_type => role_ref.role.fact_type)
955
+ # Unary fact type, make a Join Step from and to the constrained_join_role
956
+ jr = @constellation.JoinRole(constrained_join_role.join_node, role_ref.role)
957
+ js = @constellation.JoinStep(jr, jr, :fact_type => role_ref.role.fact_type)
880
958
  end
881
959
  end
882
960
  end
883
961
 
884
962
  # Thoroughly check that this is a valid join
885
963
  join.validate
964
+ debug :join, "Join has projected nodes #{replacement_rs.describe}"
886
965
 
887
966
  # Constrain the replacement role sequence, which has the attached join:
888
967
  role_sequences[role_sequences.index(role_sequence)] = replacement_rs
@@ -11,7 +11,41 @@
11
11
  $debug_keys = nil
12
12
  $debug_available = {}
13
13
 
14
- def debug_enabled(args)
14
+ def debug_initialize
15
+ # First time, initialise the tracing environment
16
+ $debug_indent = 0
17
+ $debug_keys = {}
18
+ if (e = ENV["DEBUG"])
19
+ e.split(/[^_a-zA-Z0-9]/).each{|k| debug_enable(k) }
20
+ if $debug_keys[:help]
21
+ at_exit {
22
+ $stderr.puts "---\nDebugging keys available: #{$debug_available.keys.map{|s| s.to_s}.sort*", "}"
23
+ }
24
+ end
25
+ end
26
+ end
27
+
28
+ def debug_keys
29
+ $debug_available.keys
30
+ end
31
+
32
+ def debug_enabled key
33
+ !key.empty? && $debug_keys[key.to_sym]
34
+ end
35
+
36
+ def debug_enable key
37
+ !key.empty? && $debug_keys[key.to_sym] = true
38
+ end
39
+
40
+ def debug_disable key
41
+ !key.empty? && $debug_keys.delete(key.to_sym)
42
+ end
43
+
44
+ def debug_toggle key
45
+ !key.empty? and debug_enabled(key) ? (debug_disable(key); false) : (debug_enable(key); true)
46
+ end
47
+
48
+ def debug_selected(args)
15
49
  # Figure out whether this trace is enabled and nests:
16
50
  control = (!args.empty? && Symbol === args[0]) ? args.shift : :all
17
51
  key = control.to_s.sub(/_\Z/, '').to_sym
@@ -24,24 +58,14 @@
24
58
  end
25
59
 
26
60
  def debug(*args, &block)
27
- unless $debug_indent
28
- # First time, initialise the tracing environment
29
- $debug_indent = 0
30
- $debug_keys = {}
31
- if (e = ENV["DEBUG"])
32
- e.split(/[^_a-zA-Z0-9]/).each{|k| $debug_keys[k.to_sym] = true }
33
- if $debug_keys[:help]
34
- at_exit {
35
- $stderr.puts "---\nDebugging keys available: #{$debug_available.keys.map{|s| s.to_s}.sort*", "}"
36
- }
37
- end
38
- end
39
- end
61
+ debug_initialize unless $debug_indent
40
62
 
41
- enabled, show_key, old_nested = debug_enabled(args)
63
+ enabled, show_key, old_nested = debug_selected(args)
42
64
 
43
65
  # Emit the message if enabled or a parent is:
44
- puts "\##{show_key} "+" "*$debug_indent + args.join(' ') if args.size > 0 && enabled == 1
66
+ if args.size > 0 && enabled == 1
67
+ puts "\##{show_key} "+" "*$debug_indent + args.join(' ')
68
+ end
45
69
 
46
70
  if block
47
71
  begin