activefacts 0.8.8 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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