pdf-labels 1.0.0

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.
Files changed (142) hide show
  1. data/History.txt +8 -0
  2. data/LICENCE +38 -0
  3. data/Manifest.txt +141 -0
  4. data/README.txt +72 -0
  5. data/Rakefile +30 -0
  6. data/lib/alias.rb +8 -0
  7. data/lib/glabel_template.rb +36 -0
  8. data/lib/label.rb +52 -0
  9. data/lib/layout.rb +13 -0
  10. data/lib/length_node.rb +47 -0
  11. data/lib/markup.rb +25 -0
  12. data/lib/pdf_label_page.rb +171 -0
  13. data/lib/pdf_labels.rb +6 -0
  14. data/lib/template.rb +37 -0
  15. data/templates/avery-iso-templates.xml +222 -0
  16. data/templates/avery-other-templates.xml +21 -0
  17. data/templates/avery-us-templates.xml +599 -0
  18. data/templates/glabels-2.0.dtd +329 -0
  19. data/templates/misc-iso-templates.xml +434 -0
  20. data/templates/misc-other-templates.xml +21 -0
  21. data/templates/misc-us-templates.xml +183 -0
  22. data/templates/paper-sizes.xml +37 -0
  23. data/templates/zweckform-iso-templates.xml +197 -0
  24. data/test/test_pdf_label_page.rb +91 -0
  25. data/vendor/color.rb +87 -0
  26. data/vendor/color/cmyk.rb +182 -0
  27. data/vendor/color/css.rb +27 -0
  28. data/vendor/color/grayscale.rb +135 -0
  29. data/vendor/color/hsl.rb +130 -0
  30. data/vendor/color/palette.rb +15 -0
  31. data/vendor/color/palette/gimp.rb +107 -0
  32. data/vendor/color/palette/monocontrast.rb +180 -0
  33. data/vendor/color/rgb-colors.rb +189 -0
  34. data/vendor/color/rgb.rb +311 -0
  35. data/vendor/color/rgb/metallic.rb +28 -0
  36. data/vendor/color/yiq.rb +78 -0
  37. data/vendor/pdf/charts.rb +13 -0
  38. data/vendor/pdf/charts/stddev.rb +433 -0
  39. data/vendor/pdf/grid.rb +135 -0
  40. data/vendor/pdf/math.rb +108 -0
  41. data/vendor/pdf/pagenumbers.rb +288 -0
  42. data/vendor/pdf/quickref.rb +331 -0
  43. data/vendor/pdf/simpletable.rb +947 -0
  44. data/vendor/pdf/techbook.rb +901 -0
  45. data/vendor/pdf/writer.rb +2801 -0
  46. data/vendor/pdf/writer/arc4.rb +63 -0
  47. data/vendor/pdf/writer/fontmetrics.rb +202 -0
  48. data/vendor/pdf/writer/fonts/Courier-Bold.afm +342 -0
  49. data/vendor/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
  50. data/vendor/pdf/writer/fonts/Courier-Oblique.afm +342 -0
  51. data/vendor/pdf/writer/fonts/Courier.afm +342 -0
  52. data/vendor/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
  53. data/vendor/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
  54. data/vendor/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
  55. data/vendor/pdf/writer/fonts/Helvetica.afm +3051 -0
  56. data/vendor/pdf/writer/fonts/Symbol.afm +213 -0
  57. data/vendor/pdf/writer/fonts/Times-Bold.afm +2588 -0
  58. data/vendor/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
  59. data/vendor/pdf/writer/fonts/Times-Italic.afm +2667 -0
  60. data/vendor/pdf/writer/fonts/Times-Roman.afm +2419 -0
  61. data/vendor/pdf/writer/fonts/ZapfDingbats.afm +225 -0
  62. data/vendor/pdf/writer/graphics.rb +813 -0
  63. data/vendor/pdf/writer/graphics/imageinfo.rb +365 -0
  64. data/vendor/pdf/writer/lang.rb +44 -0
  65. data/vendor/pdf/writer/lang/en.rb +104 -0
  66. data/vendor/pdf/writer/object.rb +23 -0
  67. data/vendor/pdf/writer/object/action.rb +40 -0
  68. data/vendor/pdf/writer/object/annotation.rb +42 -0
  69. data/vendor/pdf/writer/object/catalog.rb +39 -0
  70. data/vendor/pdf/writer/object/contents.rb +69 -0
  71. data/vendor/pdf/writer/object/destination.rb +40 -0
  72. data/vendor/pdf/writer/object/encryption.rb +53 -0
  73. data/vendor/pdf/writer/object/font.rb +68 -0
  74. data/vendor/pdf/writer/object/fontdescriptor.rb +34 -0
  75. data/vendor/pdf/writer/object/fontencoding.rb +40 -0
  76. data/vendor/pdf/writer/object/image.rb +308 -0
  77. data/vendor/pdf/writer/object/info.rb +79 -0
  78. data/vendor/pdf/writer/object/outline.rb +30 -0
  79. data/vendor/pdf/writer/object/outlines.rb +30 -0
  80. data/vendor/pdf/writer/object/page.rb +195 -0
  81. data/vendor/pdf/writer/object/pages.rb +115 -0
  82. data/vendor/pdf/writer/object/procset.rb +46 -0
  83. data/vendor/pdf/writer/object/viewerpreferences.rb +74 -0
  84. data/vendor/pdf/writer/ohash.rb +58 -0
  85. data/vendor/pdf/writer/oreader.rb +25 -0
  86. data/vendor/pdf/writer/state.rb +48 -0
  87. data/vendor/pdf/writer/strokestyle.rb +140 -0
  88. data/vendor/transaction/simple.rb +693 -0
  89. data/vendor/transaction/simple/group.rb +133 -0
  90. data/vendor/transaction/simple/threadsafe.rb +52 -0
  91. data/vendor/transaction/simple/threadsafe/group.rb +23 -0
  92. data/vendor/xml-mapping/ChangeLog +128 -0
  93. data/vendor/xml-mapping/LICENSE +56 -0
  94. data/vendor/xml-mapping/README +386 -0
  95. data/vendor/xml-mapping/README_XPATH +175 -0
  96. data/vendor/xml-mapping/Rakefile +214 -0
  97. data/vendor/xml-mapping/TODO.txt +32 -0
  98. data/vendor/xml-mapping/doc/xpath_impl_notes.txt +119 -0
  99. data/vendor/xml-mapping/examples/company.rb +34 -0
  100. data/vendor/xml-mapping/examples/company.xml +26 -0
  101. data/vendor/xml-mapping/examples/company_usage.intin.rb +19 -0
  102. data/vendor/xml-mapping/examples/company_usage.intout +39 -0
  103. data/vendor/xml-mapping/examples/order.rb +61 -0
  104. data/vendor/xml-mapping/examples/order.xml +54 -0
  105. data/vendor/xml-mapping/examples/order_signature_enhanced.rb +7 -0
  106. data/vendor/xml-mapping/examples/order_signature_enhanced.xml +9 -0
  107. data/vendor/xml-mapping/examples/order_signature_enhanced_usage.intin.rb +12 -0
  108. data/vendor/xml-mapping/examples/order_signature_enhanced_usage.intout +16 -0
  109. data/vendor/xml-mapping/examples/order_usage.intin.rb +73 -0
  110. data/vendor/xml-mapping/examples/order_usage.intout +147 -0
  111. data/vendor/xml-mapping/examples/time_augm.intin.rb +19 -0
  112. data/vendor/xml-mapping/examples/time_augm.intout +23 -0
  113. data/vendor/xml-mapping/examples/time_node.rb +27 -0
  114. data/vendor/xml-mapping/examples/xpath_create_new.intin.rb +85 -0
  115. data/vendor/xml-mapping/examples/xpath_create_new.intout +181 -0
  116. data/vendor/xml-mapping/examples/xpath_docvsroot.intin.rb +30 -0
  117. data/vendor/xml-mapping/examples/xpath_docvsroot.intout +34 -0
  118. data/vendor/xml-mapping/examples/xpath_ensure_created.intin.rb +62 -0
  119. data/vendor/xml-mapping/examples/xpath_ensure_created.intout +114 -0
  120. data/vendor/xml-mapping/examples/xpath_pathological.intin.rb +42 -0
  121. data/vendor/xml-mapping/examples/xpath_pathological.intout +56 -0
  122. data/vendor/xml-mapping/examples/xpath_usage.intin.rb +51 -0
  123. data/vendor/xml-mapping/examples/xpath_usage.intout +57 -0
  124. data/vendor/xml-mapping/install.rb +40 -0
  125. data/vendor/xml-mapping/lib/xml/mapping.rb +14 -0
  126. data/vendor/xml-mapping/lib/xml/mapping/base.rb +571 -0
  127. data/vendor/xml-mapping/lib/xml/mapping/standard_nodes.rb +343 -0
  128. data/vendor/xml-mapping/lib/xml/mapping/version.rb +8 -0
  129. data/vendor/xml-mapping/lib/xml/xxpath.rb +354 -0
  130. data/vendor/xml-mapping/test/all_tests.rb +6 -0
  131. data/vendor/xml-mapping/test/company.rb +56 -0
  132. data/vendor/xml-mapping/test/documents_folders.rb +33 -0
  133. data/vendor/xml-mapping/test/fixtures/bookmarks1.xml +24 -0
  134. data/vendor/xml-mapping/test/fixtures/company1.xml +85 -0
  135. data/vendor/xml-mapping/test/fixtures/documents_folders.xml +71 -0
  136. data/vendor/xml-mapping/test/fixtures/documents_folders2.xml +30 -0
  137. data/vendor/xml-mapping/test/multiple_mappings.rb +80 -0
  138. data/vendor/xml-mapping/test/tests_init.rb +2 -0
  139. data/vendor/xml-mapping/test/xml_mapping_adv_test.rb +84 -0
  140. data/vendor/xml-mapping/test/xml_mapping_test.rb +201 -0
  141. data/vendor/xml-mapping/test/xpath_test.rb +273 -0
  142. metadata +191 -0
@@ -0,0 +1,25 @@
1
+ #--
2
+ # PDF::Writer for Ruby.
3
+ # http://rubyforge.org/projects/ruby-pdf/
4
+ # Copyright 2003 - 2005 Austin Ziegler.
5
+ #
6
+ # Licensed under a MIT-style licence. See LICENCE in the main distribution
7
+ # for full licensing information.
8
+ #
9
+ # $Id: oreader.rb,v 1.2 2005/05/16 03:59:21 austin Exp $
10
+ #++
11
+ module PDF::Writer::OffsetReader
12
+ def read_o(length = 1, offset = nil)
13
+ @offset ||= 0
14
+ @offset = offset if offset
15
+ ret = self[@offset, length]
16
+ @offset += length
17
+ ret
18
+ end
19
+ def offset
20
+ @offset
21
+ end
22
+ def offset=(o)
23
+ @offset = o || 0
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ #--
2
+ # PDF::Writer for Ruby.
3
+ # http://rubyforge.org/projects/ruby-pdf/
4
+ # Copyright 2003 - 2005 Austin Ziegler.
5
+ #
6
+ # Licensed under a MIT-style licence. See LICENCE in the main distribution
7
+ # for full licensing information.
8
+ #
9
+ # $Id: state.rb,v 1.2 2005/05/16 03:59:21 austin Exp $
10
+ #++
11
+ class PDF::Writer
12
+ class State
13
+ attr_accessor :fill_color
14
+ attr_accessor :stroke_color
15
+ attr_accessor :text_render_style
16
+ attr_accessor :stroke_style
17
+
18
+ def initialize(vals = {})
19
+ @fill_color = vals[:fill_color]
20
+ @stroke_color = vals[:stroke_color]
21
+ @text_render_style = vals[:text_render_style]
22
+ @stroke_style = vals[:stroke_style]
23
+
24
+ yield self if block_given?
25
+ end
26
+
27
+ def blank?
28
+ @fill_color.nil? and @stroke_color.nil? and @stroke_style.nil?
29
+ end
30
+ end
31
+
32
+ class StateStack < ::Array
33
+ alias_method :__push__, :push
34
+ # alias_method :__pop__, :pop
35
+
36
+ def push(obj)
37
+ return self if obj.nil?
38
+ raise TypeError unless obj.kind_of?(PDF::Writer::State)
39
+ return self if obj.blank?
40
+ __push__(obj)
41
+ end
42
+
43
+ # def pop
44
+ # ret = __pop__
45
+ # ret
46
+ # end
47
+ end
48
+ end
@@ -0,0 +1,140 @@
1
+ #--
2
+ # PDF::Writer for Ruby.
3
+ # http://rubyforge.org/projects/ruby-pdf/
4
+ # Copyright 2003 - 2005 Austin Ziegler.
5
+ #
6
+ # Licensed under a MIT-style licence. See LICENCE in the main distribution
7
+ # for full licensing information.
8
+ #
9
+ # $Id: strokestyle.rb,v 1.6 2005/10/12 14:41:41 austin Exp $
10
+ #++
11
+ # A class that represents a style with which lines will be drawn.
12
+ class PDF::Writer::StrokeStyle
13
+ LINE_CAPS = { :butt => 0, :round => 1, :square => 2 }
14
+ LINE_JOINS = { :miter => 0, :round => 1, :bevel => 2 }
15
+ SOLID_LINE = { :pattern => [], :phase => 0 }
16
+
17
+ def initialize(width = 1, options = {})
18
+ @width = width
19
+ @cap = options[:cap]
20
+ @join = options[:join]
21
+ @dash = options[:dash]
22
+ @miter_limit = options[:miter_limit]
23
+
24
+ yield self if block_given?
25
+ end
26
+
27
+ DEFAULT = self.new(1, :cap => :butt, :join => :miter, :dash => SOLID_LINE)
28
+
29
+ # The thickness of the line in PDF units.
30
+ attr_accessor :width
31
+ # The type of cap to put on the line.
32
+ #
33
+ # <tt>:butt</tt>:: The stroke is squared off at the endpoint of the
34
+ # path. There is no projection beyond the end of the
35
+ # path.
36
+ # <tt>:round</tt>:: A semicircular arc with a diameter equal to the
37
+ # line width is drawn around the endpoint and filled
38
+ # in.
39
+ # <tt>:square</tt>:: The stroke continues beyond the endpoint of the
40
+ # path for a distance equal to half the line width
41
+ # and is squared off.
42
+ # +nil+:: Keeps the current line cap.
43
+ attr_accessor :cap
44
+ remove_method :cap= ;
45
+ def cap=(c) #:nodoc:
46
+ if c.nil? or LINE_CAPS.include?(c)
47
+ @cap = c
48
+ else
49
+ raise ArgumentError, "Line cap styles must be nil (none), butt, round, or square."
50
+ end
51
+ end
52
+ # How two lines join together.
53
+ #
54
+ # <tt>:miter</tt>:: The outer edges of the strokes for the two
55
+ # segments are extended until they meet at an angle,
56
+ # as in a picture frame. If the segments meet at too
57
+ # sharp an angle (as defined by the #miter_limit), a
58
+ # bevel join is used instead.
59
+ # <tt>:round</tt>:: An arc of a circle with a diameter equal to the
60
+ # line width is drawn around the point where the two
61
+ # segments meet, connecting the outer edges of the
62
+ # strokes for the two segments. This pie-slice
63
+ # shaped figure is filled in, producing a rounded
64
+ # corner.
65
+ # <tt>:bevel</tt>:: The two segments are finished with butt caps and
66
+ # the the resulting notch beyond the ends of the
67
+ # segments is filled with a triangle, forming a
68
+ # flattened edge on the join.
69
+ # +nil+:: Keeps the current line join.
70
+ attr_accessor :join
71
+ remove_method :join= ;
72
+ def join=(j) #:nodoc:
73
+ if j.nil? or LINE_JOINS.include?(j)
74
+ @join = j
75
+ else
76
+ raise ArgumentError, "Line join styles must be nil (none), miter, round, or bevel."
77
+ end
78
+ end
79
+ # When two line segments meet and <tt>:miter</tt> joins have been
80
+ # specified, the miter may extend far beyond the thickness of the line
81
+ # stroking the path. #miter_limit imposes a maximum ratio miter length
82
+ # to line width at which point the join will be converted from a miter
83
+ # to a bevel. Adobe points out that the ratio is directly related to the
84
+ # angle between the segments in user space. With [p] representing the
85
+ # angle at which the segments meet:
86
+ #
87
+ # miter_length / line_width == 1 / (sin ([p] / 2))
88
+ #
89
+ # A miter limit of 1.414 converts miters to bevels for [p] less than 90
90
+ # degrees, a limit of 2.0 converts them for [p] less than 60 degrees,
91
+ # and a limit of 10.0 converts them for [p] less than approximately 11.5
92
+ # degrees.
93
+ attr_accessor :miter_limit
94
+ # Controls the pattern of dashes and gaps used to stroke paths. This
95
+ # value must either be +nil+, or a hash with the following values:
96
+ #
97
+ # <tt>:pattern</tt>:: An array of numbers specifying the lengths (in PDF
98
+ # userspace units) of alternating dashes and gaps.
99
+ # The array is processed cyclically, so that a
100
+ # <tt>:pattern</tt> of [3] represents three units
101
+ # on, three units off, and a <tt>:pattern</tt> of
102
+ # [2, 1] represents two units on, one unit off.
103
+ #
104
+ # # - represents on, _ represents off
105
+ # ---___---___--- # pattern [3]
106
+ # --_--_--_--_--_ # pattern [2, 1]
107
+ #
108
+ # <tt>:phase</tt>:: The offset in the <tt>:pattern</tt> where the
109
+ # drawing of the stroke begins. Using a
110
+ # <tt>:phase</tt> of 1, the <tt>:pattern</tt> [3]
111
+ # will start offset by one phase, for two units on,
112
+ # three units off, three units on.
113
+ #
114
+ # --___---___---_ # pattern [3], phase 1
115
+ # -_--_--_--_--_- # pattern [2, 1], phase 1
116
+ #
117
+ # The constant SOLID_LINE may be used to restore line drawing to a solid
118
+ # line; this corresponds to an empty pattern with zero phase ([] 0).
119
+ #
120
+ # Dashed lines wrap around curves and corners just as solid stroked
121
+ # lines do, with normal cap and join handling with no consideration of
122
+ # the dash pattern. A path with several subpaths treats each subpath
123
+ # independently; the complete dash pattern is restarted at the beginning
124
+ # of each subpath.
125
+ attr_accessor :dash
126
+
127
+ def render(debug = false)
128
+ s = ""
129
+ s << "#{width} w" if @width > 0
130
+ s << " #{LINE_CAPS[@cap]} J" if @cap
131
+ s << " #{LINE_JOINS[@join]} j" if @join
132
+ s << " #{@miter_limit} M" if @miter_limit
133
+ if @dash
134
+ s << " ["
135
+ @dash[:pattern].each { |len| s << " #{len}" }
136
+ s << " ] #{@dash[:phase] or 0} d"
137
+ end
138
+ s
139
+ end
140
+ end
@@ -0,0 +1,693 @@
1
+ # :title: Transaction::Simple -- Active Object Transaction Support for Ruby
2
+ # :main: Transaction::Simple
3
+ #
4
+ # == Licence
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a
7
+ # copy of this software and associated documentation files (the "Software"),
8
+ # to deal in the Software without restriction, including without limitation
9
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
+ # and/or sell copies of the Software, and to permit persons to whom the
11
+ # Software is furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22
+ # DEALINGS IN THE SOFTWARE.
23
+ #--
24
+ # Transaction::Simple
25
+ # Simple object transaction support for Ruby
26
+ # Version 1.3.0
27
+ #
28
+ # Copyright (c) 2003 - 2005 Austin Ziegler
29
+ #
30
+ # $Id: simple.rb,v 1.5 2005/05/05 16:16:49 austin Exp $
31
+ #++
32
+ # The "Transaction" namespace can be used for additional transaction
33
+ # support objects and modules.
34
+ module Transaction
35
+ # A standard exception for transaction errors.
36
+ class TransactionError < StandardError; end
37
+ # The TransactionAborted exception is used to indicate when a
38
+ # transaction has been aborted in the block form.
39
+ class TransactionAborted < Exception; end
40
+ # The TransactionCommitted exception is used to indicate when a
41
+ # transaction has been committed in the block form.
42
+ class TransactionCommitted < Exception; end
43
+
44
+ te = "Transaction Error: %s"
45
+
46
+ Messages = {
47
+ :bad_debug_object =>
48
+ te % "the transaction debug object must respond to #<<.",
49
+ :unique_names =>
50
+ te % "named transactions must be unique.",
51
+ :no_transaction_open =>
52
+ te % "no transaction open.",
53
+ :cannot_rewind_no_transaction =>
54
+ te % "cannot rewind; there is no current transaction.",
55
+ :cannot_rewind_named_transaction =>
56
+ te % "cannot rewind to transaction %s because it does not exist.",
57
+ :cannot_rewind_transaction_before_block =>
58
+ te % "cannot rewind a transaction started before the execution block.",
59
+ :cannot_abort_no_transaction =>
60
+ te % "cannot abort; there is no current transaction.",
61
+ :cannot_abort_transaction_before_block =>
62
+ te % "cannot abort a transaction started before the execution block.",
63
+ :cannot_abort_named_transaction =>
64
+ te % "cannot abort nonexistant transaction %s.",
65
+ :cannot_commit_no_transaction =>
66
+ te % "cannot commit; there is no current transaction.",
67
+ :cannot_commit_transaction_before_block =>
68
+ te % "cannot commit a transaction started before the execution block.",
69
+ :cannot_commit_named_transaction =>
70
+ te % "cannot commit nonexistant transaction %s.",
71
+ :cannot_start_empty_block_transaction =>
72
+ te % "cannot start a block transaction with no objects.",
73
+ :cannot_obtain_transaction_lock =>
74
+ te % "cannot obtain transaction lock for #%s.",
75
+ }
76
+
77
+ # = Transaction::Simple for Ruby
78
+ # Simple object transaction support for Ruby
79
+ #
80
+ # == Introduction
81
+ # Transaction::Simple provides a generic way to add active transaction
82
+ # support to objects. The transaction methods added by this module will
83
+ # work with most objects, excluding those that cannot be
84
+ # <i>Marshal</i>ed (bindings, procedure objects, IO instances, or
85
+ # singleton objects).
86
+ #
87
+ # The transactions supported by Transaction::Simple are not backed
88
+ # transactions; they are not associated with any sort of data store.
89
+ # They are "live" transactions occurring in memory and in the object
90
+ # itself. This is to allow "test" changes to be made to an object
91
+ # before making the changes permanent.
92
+ #
93
+ # Transaction::Simple can handle an "infinite" number of transaction
94
+ # levels (limited only by memory). If I open two transactions, commit
95
+ # the second, but abort the first, the object will revert to the
96
+ # original version.
97
+ #
98
+ # Transaction::Simple supports "named" transactions, so that multiple
99
+ # levels of transactions can be committed, aborted, or rewound by
100
+ # referring to the appropriate name of the transaction. Names may be any
101
+ # object *except* +nil+. As with Hash keys, String names will be
102
+ # duplicated and frozen before using.
103
+ #
104
+ # Copyright:: Copyright � 2003 - 2005 by Austin Ziegler
105
+ # Version:: 1.3.0
106
+ # Licence:: MIT-Style
107
+ #
108
+ # Thanks to David Black for help with the initial concept that led to
109
+ # this library.
110
+ #
111
+ # == Usage
112
+ # include 'transaction/simple'
113
+ #
114
+ # v = "Hello, you." # -> "Hello, you."
115
+ # v.extend(Transaction::Simple) # -> "Hello, you."
116
+ #
117
+ # v.start_transaction # -> ... (a Marshal string)
118
+ # v.transaction_open? # -> true
119
+ # v.gsub!(/you/, "world") # -> "Hello, world."
120
+ #
121
+ # v.rewind_transaction # -> "Hello, you."
122
+ # v.transaction_open? # -> true
123
+ #
124
+ # v.gsub!(/you/, "HAL") # -> "Hello, HAL."
125
+ # v.abort_transaction # -> "Hello, you."
126
+ # v.transaction_open? # -> false
127
+ #
128
+ # v.start_transaction # -> ... (a Marshal string)
129
+ # v.start_transaction # -> ... (a Marshal string)
130
+ #
131
+ # v.transaction_open? # -> true
132
+ # v.gsub!(/you/, "HAL") # -> "Hello, HAL."
133
+ #
134
+ # v.commit_transaction # -> "Hello, HAL."
135
+ # v.transaction_open? # -> true
136
+ # v.abort_transaction # -> "Hello, you."
137
+ # v.transaction_open? # -> false
138
+ #
139
+ # == Named Transaction Usage
140
+ # v = "Hello, you." # -> "Hello, you."
141
+ # v.extend(Transaction::Simple) # -> "Hello, you."
142
+ #
143
+ # v.start_transaction(:first) # -> ... (a Marshal string)
144
+ # v.transaction_open? # -> true
145
+ # v.transaction_open?(:first) # -> true
146
+ # v.transaction_open?(:second) # -> false
147
+ # v.gsub!(/you/, "world") # -> "Hello, world."
148
+ #
149
+ # v.start_transaction(:second) # -> ... (a Marshal string)
150
+ # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
151
+ # v.rewind_transaction(:first) # -> "Hello, you."
152
+ # v.transaction_open? # -> true
153
+ # v.transaction_open?(:first) # -> true
154
+ # v.transaction_open?(:second) # -> false
155
+ #
156
+ # v.gsub!(/you/, "world") # -> "Hello, world."
157
+ # v.start_transaction(:second) # -> ... (a Marshal string)
158
+ # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
159
+ # v.transaction_name # -> :second
160
+ # v.abort_transaction(:first) # -> "Hello, you."
161
+ # v.transaction_open? # -> false
162
+ #
163
+ # v.start_transaction(:first) # -> ... (a Marshal string)
164
+ # v.gsub!(/you/, "world") # -> "Hello, world."
165
+ # v.start_transaction(:second) # -> ... (a Marshal string)
166
+ # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
167
+ #
168
+ # v.commit_transaction(:first) # -> "Hello, HAL."
169
+ # v.transaction_open? # -> false
170
+ #
171
+ # == Block Usage
172
+ # v = "Hello, you." # -> "Hello, you."
173
+ # Transaction::Simple.start(v) do |tv|
174
+ # # v has been extended with Transaction::Simple and an unnamed
175
+ # # transaction has been started.
176
+ # tv.transaction_open? # -> true
177
+ # tv.gsub!(/you/, "world") # -> "Hello, world."
178
+ #
179
+ # tv.rewind_transaction # -> "Hello, you."
180
+ # tv.transaction_open? # -> true
181
+ #
182
+ # tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
183
+ # # The following breaks out of the transaction block after
184
+ # # aborting the transaction.
185
+ # tv.abort_transaction # -> "Hello, you."
186
+ # end
187
+ # # v still has Transaction::Simple applied from here on out.
188
+ # v.transaction_open? # -> false
189
+ #
190
+ # Transaction::Simple.start(v) do |tv|
191
+ # tv.start_transaction # -> ... (a Marshal string)
192
+ #
193
+ # tv.transaction_open? # -> true
194
+ # tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
195
+ #
196
+ # # If #commit_transaction were called without having started a
197
+ # # second transaction, then it would break out of the transaction
198
+ # # block after committing the transaction.
199
+ # tv.commit_transaction # -> "Hello, HAL."
200
+ # tv.transaction_open? # -> true
201
+ # tv.abort_transaction # -> "Hello, you."
202
+ # end
203
+ # v.transaction_open? # -> false
204
+ #
205
+ # == Named Transaction Usage
206
+ # v = "Hello, you." # -> "Hello, you."
207
+ # v.extend(Transaction::Simple) # -> "Hello, you."
208
+ #
209
+ # v.start_transaction(:first) # -> ... (a Marshal string)
210
+ # v.transaction_open? # -> true
211
+ # v.transaction_open?(:first) # -> true
212
+ # v.transaction_open?(:second) # -> false
213
+ # v.gsub!(/you/, "world") # -> "Hello, world."
214
+ #
215
+ # v.start_transaction(:second) # -> ... (a Marshal string)
216
+ # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
217
+ # v.rewind_transaction(:first) # -> "Hello, you."
218
+ # v.transaction_open? # -> true
219
+ # v.transaction_open?(:first) # -> true
220
+ # v.transaction_open?(:second) # -> false
221
+ #
222
+ # v.gsub!(/you/, "world") # -> "Hello, world."
223
+ # v.start_transaction(:second) # -> ... (a Marshal string)
224
+ # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
225
+ # v.transaction_name # -> :second
226
+ # v.abort_transaction(:first) # -> "Hello, you."
227
+ # v.transaction_open? # -> false
228
+ #
229
+ # v.start_transaction(:first) # -> ... (a Marshal string)
230
+ # v.gsub!(/you/, "world") # -> "Hello, world."
231
+ # v.start_transaction(:second) # -> ... (a Marshal string)
232
+ # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
233
+ #
234
+ # v.commit_transaction(:first) # -> "Hello, HAL."
235
+ # v.transaction_open? # -> false
236
+ #
237
+ # == Thread Safety
238
+ # Threadsafe version of Transaction::Simple and
239
+ # Transaction::Simple::Group exist; these are loaded from
240
+ # 'transaction/simple/threadsafe' and
241
+ # 'transaction/simple/threadsafe/group', respectively, and are
242
+ # represented in Ruby code as Transaction::Simple::ThreadSafe and
243
+ # Transaction::Simple::ThreadSafe::Group, respectively.
244
+ #
245
+ # == Contraindications
246
+ # While Transaction::Simple is very useful, it has some severe
247
+ # limitations that must be understood. Transaction::Simple:
248
+ #
249
+ # * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed
250
+ # cannot use Transaction::Simple. In my experience, this affects
251
+ # singleton objects more often than any other object. It may be that
252
+ # Ruby 2.0 will solve this problem.
253
+ # * does not manage resources. Resources external to the object and its
254
+ # instance variables are not managed at all. However, all instance
255
+ # variables and objects "belonging" to those instance variables are
256
+ # managed. If there are object reference counts to be handled,
257
+ # Transaction::Simple will probably cause problems.
258
+ # * is not inherently thread-safe. In the ACID ("atomic, consistent,
259
+ # isolated, durable") test, Transaction::Simple provides CD, but it is
260
+ # up to the user of Transaction::Simple to provide isolation and
261
+ # atomicity. Transactions should be considered "critical sections" in
262
+ # multi-threaded applications. If thread safety and atomicity is
263
+ # absolutely required, use Transaction::Simple::ThreadSafe, which uses
264
+ # a Mutex object to synchronize the accesses on the object during the
265
+ # transaction operations.
266
+ # * does not necessarily maintain Object#__id__ values on rewind or
267
+ # abort. This may change for future versions that will be Ruby 1.8 or
268
+ # better *only*. Certain objects that support #replace will maintain
269
+ # Object#__id__.
270
+ # * Can be a memory hog if you use many levels of transactions on many
271
+ # objects.
272
+ #
273
+ module Simple
274
+ TRANSACTION_SIMPLE_VERSION = '1.3.0'
275
+
276
+ # Sets the Transaction::Simple debug object. It must respond to #<<.
277
+ # Sets the transaction debug object. Debugging will be performed
278
+ # automatically if there's a debug object. The generic transaction
279
+ # error class.
280
+ def self.debug_io=(io)
281
+ if io.nil?
282
+ @tdi = nil
283
+ @debugging = false
284
+ else
285
+ unless io.respond_to?(:<<)
286
+ raise TransactionError, Messages[:bad_debug_object]
287
+ end
288
+ @tdi = io
289
+ @debugging = true
290
+ end
291
+ end
292
+
293
+ # Returns +true+ if we are debugging.
294
+ def self.debugging?
295
+ @debugging
296
+ end
297
+
298
+ # Returns the Transaction::Simple debug object. It must respond to
299
+ # #<<.
300
+ def self.debug_io
301
+ @tdi ||= ""
302
+ @tdi
303
+ end
304
+
305
+ # If +name+ is +nil+ (default), then returns +true+ if there is
306
+ # currently a transaction open.
307
+ #
308
+ # If +name+ is specified, then returns +true+ if there is currently a
309
+ # transaction that responds to +name+ open.
310
+ def transaction_open?(name = nil)
311
+ if name.nil?
312
+ if Transaction::Simple.debugging?
313
+ Transaction::Simple.debug_io << "Transaction " <<
314
+ "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
315
+ end
316
+ return (not @__transaction_checkpoint__.nil?)
317
+ else
318
+ if Transaction::Simple.debugging?
319
+ Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " <<
320
+ "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
321
+ end
322
+ return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
323
+ end
324
+ end
325
+
326
+ # Returns the current name of the transaction. Transactions not
327
+ # explicitly named are named +nil+.
328
+ def transaction_name
329
+ if @__transaction_checkpoint__.nil?
330
+ raise TransactionError, Messages[:no_transaction_open]
331
+ end
332
+ if Transaction::Simple.debugging?
333
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
334
+ "Transaction Name: #{@__transaction_names__[-1].inspect}\n"
335
+ end
336
+ if @__transaction_names__[-1].kind_of?(String)
337
+ @__transaction_names__[-1].dup
338
+ else
339
+ @__transaction_names__[-1]
340
+ end
341
+ end
342
+
343
+ # Starts a transaction. Stores the current object state. If a
344
+ # transaction name is specified, the transaction will be named.
345
+ # Transaction names must be unique. Transaction names of +nil+ will be
346
+ # treated as unnamed transactions.
347
+ def start_transaction(name = nil)
348
+ @__transaction_level__ ||= 0
349
+ @__transaction_names__ ||= []
350
+
351
+ if name.nil?
352
+ @__transaction_names__ << nil
353
+ ss = "" if Transaction::Simple.debugging?
354
+ else
355
+ if @__transaction_names__.include?(name)
356
+ raise TransactionError, Messages[:unique_names]
357
+ end
358
+ name = name.dup.freeze if name.kind_of?(String)
359
+ @__transaction_names__ << name
360
+ ss = "(#{name.inspect})" if Transaction::Simple.debugging?
361
+ end
362
+
363
+ @__transaction_level__ += 1
364
+
365
+ if Transaction::Simple.debugging?
366
+ Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " <<
367
+ "Start Transaction#{ss}\n"
368
+ end
369
+
370
+ @__transaction_checkpoint__ = Marshal.dump(self)
371
+ end
372
+
373
+ # Rewinds the transaction. If +name+ is specified, then the
374
+ # intervening transactions will be aborted and the named transaction
375
+ # will be rewound. Otherwise, only the current transaction is rewound.
376
+ def rewind_transaction(name = nil)
377
+ if @__transaction_checkpoint__.nil?
378
+ raise TransactionError, Messages[:cannot_rewind_no_transaction]
379
+ end
380
+
381
+ # Check to see if we are trying to rewind a transaction that is
382
+ # outside of the current transaction block.
383
+ if @__transaction_block__ and name
384
+ nix = @__transaction_names__.index(name) + 1
385
+ if nix < @__transaction_block__
386
+ raise TransactionError, Messages[:cannot_rewind_transaction_before_block]
387
+ end
388
+ end
389
+
390
+ if name.nil?
391
+ __rewind_this_transaction
392
+ ss = "" if Transaction::Simple.debugging?
393
+ else
394
+ unless @__transaction_names__.include?(name)
395
+ raise TransactionError, Messages[:cannot_rewind_named_transaction] % name.inspect
396
+ end
397
+ ss = "(#{name})" if Transaction::Simple.debugging?
398
+
399
+ while @__transaction_names__[-1] != name
400
+ @__transaction_checkpoint__ = __rewind_this_transaction
401
+ if Transaction::Simple.debugging?
402
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
403
+ "Rewind Transaction#{ss}\n"
404
+ end
405
+ @__transaction_level__ -= 1
406
+ @__transaction_names__.pop
407
+ end
408
+ __rewind_this_transaction
409
+ end
410
+ if Transaction::Simple.debugging?
411
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
412
+ "Rewind Transaction#{ss}\n"
413
+ end
414
+ self
415
+ end
416
+
417
+ # Aborts the transaction. Resets the object state to what it was
418
+ # before the transaction was started and closes the transaction. If
419
+ # +name+ is specified, then the intervening transactions and the named
420
+ # transaction will be aborted. Otherwise, only the current transaction
421
+ # is aborted.
422
+ #
423
+ # If the current or named transaction has been started by a block
424
+ # (Transaction::Simple.start), then the execution of the block will be
425
+ # halted with +break+ +self+.
426
+ def abort_transaction(name = nil)
427
+ if @__transaction_checkpoint__.nil?
428
+ raise TransactionError, Messages[:cannot_abort_no_transaction]
429
+ end
430
+
431
+ # Check to see if we are trying to abort a transaction that is
432
+ # outside of the current transaction block. Otherwise, raise
433
+ # TransactionAborted if they are the same.
434
+ if @__transaction_block__ and name
435
+ nix = @__transaction_names__.index(name) + 1
436
+ if nix < @__transaction_block__
437
+ raise TransactionError, Messages[:cannot_abort_transaction_before_block]
438
+ end
439
+
440
+ raise TransactionAborted if @__transaction_block__ == nix
441
+ end
442
+
443
+ raise TransactionAborted if @__transaction_block__ == @__transaction_level__
444
+
445
+ if name.nil?
446
+ __abort_transaction(name)
447
+ else
448
+ unless @__transaction_names__.include?(name)
449
+ raise TransactionError, Messages[:cannot_abort_named_transaction] % name.inspect
450
+ end
451
+ __abort_transaction(name) while @__transaction_names__.include?(name)
452
+ end
453
+ self
454
+ end
455
+
456
+ # If +name+ is +nil+ (default), the current transaction level is
457
+ # closed out and the changes are committed.
458
+ #
459
+ # If +name+ is specified and +name+ is in the list of named
460
+ # transactions, then all transactions are closed and committed until
461
+ # the named transaction is reached.
462
+ def commit_transaction(name = nil)
463
+ if @__transaction_checkpoint__.nil?
464
+ raise TransactionError, Messages[:cannot_commit_no_transaction]
465
+ end
466
+ @__transaction_block__ ||= nil
467
+
468
+ # Check to see if we are trying to commit a transaction that is
469
+ # outside of the current transaction block. Otherwise, raise
470
+ # TransactionCommitted if they are the same.
471
+ if @__transaction_block__ and name
472
+ nix = @__transaction_names__.index(name) + 1
473
+ if nix < @__transaction_block__
474
+ raise TransactionError, Messages[:cannot_commit_transaction_before_block]
475
+ end
476
+
477
+ raise TransactionCommitted if @__transaction_block__ == nix
478
+ end
479
+
480
+ raise TransactionCommitted if @__transaction_block__ == @__transaction_level__
481
+
482
+ if name.nil?
483
+ ss = "" if Transaction::Simple.debugging?
484
+ __commit_transaction
485
+ if Transaction::Simple.debugging?
486
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
487
+ "Commit Transaction#{ss}\n"
488
+ end
489
+ else
490
+ unless @__transaction_names__.include?(name)
491
+ raise TransactionError, Messages[:cannot_commit_named_transaction] % name.inspect
492
+ end
493
+ ss = "(#{name})" if Transaction::Simple.debugging?
494
+
495
+ while @__transaction_names__[-1] != name
496
+ if Transaction::Simple.debugging?
497
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
498
+ "Commit Transaction#{ss}\n"
499
+ end
500
+ __commit_transaction
501
+ end
502
+ if Transaction::Simple.debugging?
503
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
504
+ "Commit Transaction#{ss}\n"
505
+ end
506
+ __commit_transaction
507
+ end
508
+
509
+ self
510
+ end
511
+
512
+ # Alternative method for calling the transaction methods. An optional
513
+ # name can be specified for named transaction support.
514
+ #
515
+ # #transaction(:start):: #start_transaction
516
+ # #transaction(:rewind):: #rewind_transaction
517
+ # #transaction(:abort):: #abort_transaction
518
+ # #transaction(:commit):: #commit_transaction
519
+ # #transaction(:name):: #transaction_name
520
+ # #transaction:: #transaction_open?
521
+ def transaction(action = nil, name = nil)
522
+ case action
523
+ when :start
524
+ start_transaction(name)
525
+ when :rewind
526
+ rewind_transaction(name)
527
+ when :abort
528
+ abort_transaction(name)
529
+ when :commit
530
+ commit_transaction(name)
531
+ when :name
532
+ transaction_name
533
+ when nil
534
+ transaction_open?(name)
535
+ end
536
+ end
537
+
538
+ # Allows specific variables to be excluded from transaction support.
539
+ # Must be done after extending the object but before starting the
540
+ # first transaction on the object.
541
+ #
542
+ # vv.transaction_exclusions << "@io"
543
+ def transaction_exclusions
544
+ @transaction_exclusions ||= []
545
+ end
546
+
547
+ class << self
548
+ def __common_start(name, vars, &block)
549
+ if vars.empty?
550
+ raise TransactionError, Messages[:cannot_start_empty_block_transaction]
551
+ end
552
+
553
+ if block
554
+ begin
555
+ vlevel = {}
556
+
557
+ vars.each do |vv|
558
+ vv.extend(Transaction::Simple)
559
+ vv.start_transaction(name)
560
+ vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
561
+ vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
562
+ end
563
+
564
+ yield(*vars)
565
+ rescue TransactionAborted
566
+ vars.each do |vv|
567
+ if name.nil? and vv.transaction_open?
568
+ loop do
569
+ tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
570
+ vv.instance_variable_set(:@__transaction_block__, -1)
571
+ break if tlevel < vlevel[vv.__id__]
572
+ vv.abort_transaction if vv.transaction_open?
573
+ end
574
+ elsif vv.transaction_open?(name)
575
+ vv.instance_variable_set(:@__transaction_block__, -1)
576
+ vv.abort_transaction(name)
577
+ end
578
+ end
579
+ rescue TransactionCommitted
580
+ nil
581
+ ensure
582
+ vars.each do |vv|
583
+ if name.nil? and vv.transaction_open?
584
+ loop do
585
+ tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
586
+ break if tlevel < vlevel[vv.__id__]
587
+ vv.instance_variable_set(:@__transaction_block__, -1)
588
+ vv.commit_transaction if vv.transaction_open?
589
+ end
590
+ elsif vv.transaction_open?(name)
591
+ vv.instance_variable_set(:@__transaction_block__, -1)
592
+ vv.commit_transaction(name)
593
+ end
594
+ end
595
+ end
596
+ else
597
+ vars.each do |vv|
598
+ vv.extend(Transaction::Simple)
599
+ vv.start_transaction(name)
600
+ end
601
+ end
602
+ end
603
+ private :__common_start
604
+
605
+ def start_named(name, *vars, &block)
606
+ __common_start(name, vars, &block)
607
+ end
608
+
609
+ def start(*vars, &block)
610
+ __common_start(nil, vars, &block)
611
+ end
612
+ end
613
+
614
+ def __abort_transaction(name = nil) #:nodoc:
615
+ @__transaction_checkpoint__ = __rewind_this_transaction
616
+
617
+ if name.nil?
618
+ ss = "" if Transaction::Simple.debugging?
619
+ else
620
+ ss = "(#{name.inspect})" if Transaction::Simple.debugging?
621
+ end
622
+
623
+ if Transaction::Simple.debugging?
624
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
625
+ "Abort Transaction#{ss}\n"
626
+ end
627
+ @__transaction_level__ -= 1
628
+ @__transaction_names__.pop
629
+ if @__transaction_level__ < 1
630
+ @__transaction_level__ = 0
631
+ @__transaction_names__ = []
632
+ end
633
+ end
634
+
635
+ TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc:
636
+ SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
637
+
638
+ def __rewind_this_transaction #:nodoc:
639
+ rr = Marshal.restore(@__transaction_checkpoint__)
640
+
641
+ begin
642
+ self.replace(rr) if respond_to?(:replace)
643
+ rescue
644
+ nil
645
+ end
646
+
647
+ rr.instance_variables.each do |vv|
648
+ next if SKIP_TRANSACTION_VARS.include?(vv)
649
+ next if self.transaction_exclusions.include?(vv)
650
+ if respond_to?(:instance_variable_get)
651
+ instance_variable_set(vv, rr.instance_variable_get(vv))
652
+ else
653
+ instance_eval(%q|#{vv} = rr.instance_eval("#{vv}")|)
654
+ end
655
+ end
656
+
657
+ new_ivar = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS
658
+ new_ivar.each do |vv|
659
+ if respond_to?(:instance_variable_set)
660
+ instance_variable_set(vv, nil)
661
+ else
662
+ instance_eval(%q|#{vv} = nil|)
663
+ end
664
+ end
665
+
666
+ if respond_to?(:instance_variable_get)
667
+ rr.instance_variable_get(TRANSACTION_CHECKPOINT)
668
+ else
669
+ rr.instance_eval(TRANSACTION_CHECKPOINT)
670
+ end
671
+ end
672
+
673
+ def __commit_transaction #:nodoc:
674
+ if respond_to?(:instance_variable_get)
675
+ @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
676
+ else
677
+ @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
678
+ end
679
+
680
+ @__transaction_level__ -= 1
681
+ @__transaction_names__.pop
682
+
683
+ if @__transaction_level__ < 1
684
+ @__transaction_level__ = 0
685
+ @__transaction_names__ = []
686
+ end
687
+ end
688
+
689
+ private :__abort_transaction
690
+ private :__rewind_this_transaction
691
+ private :__commit_transaction
692
+ end
693
+ end