caracal 0.1.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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +941 -0
  6. data/Rakefile +2 -0
  7. data/caracal.gemspec +27 -0
  8. data/lib/caracal.rb +31 -0
  9. data/lib/caracal/core/file_name.rb +39 -0
  10. data/lib/caracal/core/fonts.rb +75 -0
  11. data/lib/caracal/core/images.rb +37 -0
  12. data/lib/caracal/core/line_breaks.rb +29 -0
  13. data/lib/caracal/core/list_styles.rb +92 -0
  14. data/lib/caracal/core/lists.rb +57 -0
  15. data/lib/caracal/core/models/base_model.rb +51 -0
  16. data/lib/caracal/core/models/border_model.rb +120 -0
  17. data/lib/caracal/core/models/font_model.rb +64 -0
  18. data/lib/caracal/core/models/image_model.rb +118 -0
  19. data/lib/caracal/core/models/line_break_model.rb +15 -0
  20. data/lib/caracal/core/models/link_model.rb +65 -0
  21. data/lib/caracal/core/models/list_item_model.rb +105 -0
  22. data/lib/caracal/core/models/list_model.rb +130 -0
  23. data/lib/caracal/core/models/list_style_model.rb +129 -0
  24. data/lib/caracal/core/models/margin_model.rb +76 -0
  25. data/lib/caracal/core/models/page_break_model.rb +15 -0
  26. data/lib/caracal/core/models/page_number_model.rb +69 -0
  27. data/lib/caracal/core/models/page_size_model.rb +70 -0
  28. data/lib/caracal/core/models/paragraph_model.rb +141 -0
  29. data/lib/caracal/core/models/relationship_model.rb +108 -0
  30. data/lib/caracal/core/models/rule_model.rb +27 -0
  31. data/lib/caracal/core/models/style_model.rb +134 -0
  32. data/lib/caracal/core/models/table_cell_model.rb +155 -0
  33. data/lib/caracal/core/models/table_model.rb +206 -0
  34. data/lib/caracal/core/models/text_model.rb +92 -0
  35. data/lib/caracal/core/page_breaks.rb +29 -0
  36. data/lib/caracal/core/page_numbers.rb +51 -0
  37. data/lib/caracal/core/page_settings.rb +72 -0
  38. data/lib/caracal/core/relationships.rb +90 -0
  39. data/lib/caracal/core/rules.rb +35 -0
  40. data/lib/caracal/core/styles.rb +86 -0
  41. data/lib/caracal/core/tables.rb +41 -0
  42. data/lib/caracal/core/text.rb +73 -0
  43. data/lib/caracal/document.rb +242 -0
  44. data/lib/caracal/errors.rb +23 -0
  45. data/lib/caracal/renderers/app_renderer.rb +41 -0
  46. data/lib/caracal/renderers/content_types_renderer.rb +53 -0
  47. data/lib/caracal/renderers/core_renderer.rb +44 -0
  48. data/lib/caracal/renderers/document_renderer.rb +349 -0
  49. data/lib/caracal/renderers/fonts_renderer.rb +56 -0
  50. data/lib/caracal/renderers/footer_renderer.rb +69 -0
  51. data/lib/caracal/renderers/numbering_renderer.rb +87 -0
  52. data/lib/caracal/renderers/package_relationships_renderer.rb +50 -0
  53. data/lib/caracal/renderers/relationships_renderer.rb +48 -0
  54. data/lib/caracal/renderers/settings_renderer.rb +58 -0
  55. data/lib/caracal/renderers/styles_renderer.rb +163 -0
  56. data/lib/caracal/renderers/xml_renderer.rb +83 -0
  57. data/lib/caracal/version.rb +3 -0
  58. data/lib/tilt/caracal.rb +21 -0
  59. data/spec/lib/caracal/core/file_name_spec.rb +54 -0
  60. data/spec/lib/caracal/core/fonts_spec.rb +119 -0
  61. data/spec/lib/caracal/core/images_spec.rb +25 -0
  62. data/spec/lib/caracal/core/line_breaks_spec.rb +25 -0
  63. data/spec/lib/caracal/core/list_styles_spec.rb +121 -0
  64. data/spec/lib/caracal/core/lists_spec.rb +43 -0
  65. data/spec/lib/caracal/core/models/base_model_spec.rb +38 -0
  66. data/spec/lib/caracal/core/models/border_model_spec.rb +159 -0
  67. data/spec/lib/caracal/core/models/font_model_spec.rb +92 -0
  68. data/spec/lib/caracal/core/models/image_model_spec.rb +192 -0
  69. data/spec/lib/caracal/core/models/line_break_model_spec.rb +21 -0
  70. data/spec/lib/caracal/core/models/link_model_spec.rb +139 -0
  71. data/spec/lib/caracal/core/models/list_item_model_spec.rb +190 -0
  72. data/spec/lib/caracal/core/models/list_model_spec.rb +178 -0
  73. data/spec/lib/caracal/core/models/list_style_model_spec.rb +212 -0
  74. data/spec/lib/caracal/core/models/margin_model_spec.rb +111 -0
  75. data/spec/lib/caracal/core/models/page_break_model_spec.rb +21 -0
  76. data/spec/lib/caracal/core/models/page_number_model_spec.rb +101 -0
  77. data/spec/lib/caracal/core/models/page_size_model_spec.rb +91 -0
  78. data/spec/lib/caracal/core/models/paragraph_model_spec.rb +162 -0
  79. data/spec/lib/caracal/core/models/relationship_model_spec.rb +183 -0
  80. data/spec/lib/caracal/core/models/rule_model_spec.rb +108 -0
  81. data/spec/lib/caracal/core/models/style_model_spec.rb +187 -0
  82. data/spec/lib/caracal/core/models/table_cell_model_spec.rb +221 -0
  83. data/spec/lib/caracal/core/models/table_model_spec.rb +222 -0
  84. data/spec/lib/caracal/core/models/text_model_spec.rb +132 -0
  85. data/spec/lib/caracal/core/page_breaks_spec.rb +25 -0
  86. data/spec/lib/caracal/core/page_numbers_spec.rb +80 -0
  87. data/spec/lib/caracal/core/page_settings_spec.rb +143 -0
  88. data/spec/lib/caracal/core/relationships_spec.rb +119 -0
  89. data/spec/lib/caracal/core/rules_spec.rb +25 -0
  90. data/spec/lib/caracal/core/styles_spec.rb +129 -0
  91. data/spec/lib/caracal/core/tables_spec.rb +25 -0
  92. data/spec/lib/caracal/core/text_spec.rb +52 -0
  93. data/spec/lib/caracal/errors_spec.rb +10 -0
  94. data/spec/spec_helper.rb +8 -0
  95. metadata +245 -0
@@ -0,0 +1,90 @@
1
+ require 'caracal/core/models/relationship_model'
2
+ require 'caracal/errors'
3
+
4
+
5
+ module Caracal
6
+ module Core
7
+
8
+ # This module encapsulates all the functionality related to registering and
9
+ # retrieving relationships.
10
+ #
11
+ module Relationships
12
+ def self.included(base)
13
+ base.class_eval do
14
+
15
+ #-------------------------------------------------------------
16
+ # Configuration
17
+ #-------------------------------------------------------------
18
+
19
+ attr_reader :relationship_counter
20
+
21
+
22
+ #-------------------------------------------------------------
23
+ # Class Methods
24
+ #-------------------------------------------------------------
25
+
26
+ def self.default_relationships
27
+ [
28
+ { target: 'fontTable.xml', type: :font },
29
+ { target: 'footer1.xml', type: :footer },
30
+ { target: 'numbering.xml', type: :numbering },
31
+ { target: 'settings.xml', type: :setting },
32
+ { target: 'styles.xml', type: :style }
33
+ ]
34
+ end
35
+
36
+
37
+ #-------------------------------------------------------------
38
+ # Public Methods
39
+ #-------------------------------------------------------------
40
+
41
+ #============== ATTRIBUTES ==========================
42
+
43
+ def relationship(**options, &block)
44
+ id = relationship_counter.to_i + 1
45
+ options.merge!({ id: id })
46
+
47
+ model = Caracal::Core::Models::RelationshipModel.new(options, &block)
48
+ if model.valid?
49
+ @relationship_counter = id
50
+ rel = register_relationship(model)
51
+ else
52
+ raise Caracal::Errors::InvalidModelError, 'relationship must specify the :id, :target, and :type attributes.'
53
+ end
54
+ rel
55
+ end
56
+
57
+
58
+ #============== GETTERS =============================
59
+
60
+ def relationships
61
+ @relationships ||= []
62
+ end
63
+
64
+ def find_relationship(target)
65
+ relationships.find { |r| r.matches?(target) }
66
+ end
67
+
68
+
69
+ #============== REGISTRATION ========================
70
+
71
+ def register_relationship(model)
72
+ unless r = find_relationship(model.relationship_target)
73
+ relationships << model
74
+ r = model
75
+ end
76
+ r
77
+ end
78
+
79
+ def unregister_relationship(target)
80
+ if r = find_relationship(target)
81
+ relationships.delete(r)
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,35 @@
1
+ require 'caracal/core/models/rule_model'
2
+ require 'caracal/errors'
3
+
4
+
5
+ module Caracal
6
+ module Core
7
+
8
+ # This module encapsulates all the functionality related to adding
9
+ # horizontal rules to the document.
10
+ #
11
+ module Rules
12
+ def self.included(base)
13
+ base.class_eval do
14
+
15
+ #-------------------------------------------------------------
16
+ # Public Methods
17
+ #-------------------------------------------------------------
18
+
19
+ def hr(**options, &block)
20
+ model = Caracal::Core::Models::RuleModel.new(options, &block)
21
+
22
+ if model.valid?
23
+ contents << model
24
+ else
25
+ raise Caracal::Errors::InvalidModelError, 'Horizontal rules require non-zero :size and :spacing values.'
26
+ end
27
+ model
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,86 @@
1
+ require 'caracal/core/models/style_model'
2
+ require 'caracal/errors'
3
+
4
+
5
+ module Caracal
6
+ module Core
7
+
8
+ # This module encapsulates all the functionality related to defining
9
+ # paragraph styles.
10
+ #
11
+ module Styles
12
+ def self.included(base)
13
+ base.class_eval do
14
+
15
+ #-------------------------------------------------------------
16
+ # Class Methods
17
+ #-------------------------------------------------------------
18
+
19
+ def self.default_styles
20
+ [
21
+ { id: 'Normal', name: 'normal', font: 'Arial', size: 20, line: 320, color: '333333' },
22
+ { id: 'Heading1', name: 'heading 1', font: 'Palatino', size: 36, bottom: 120 },
23
+ { id: 'Heading2', name: 'heading 2', font: 'Arial', size: 26, top: 120, bottom: 160, bold: true },
24
+ { id: 'Heading3', name: 'heading 3', font: 'Arial', size: 24, top: 120, bottom: 160, bold: true, italic: true, color: '666666' },
25
+ { id: 'Heading4', name: 'heading 4', font: 'Palatino', size: 24, top: 120, bottom: 120, bold: true },
26
+ { id: 'Heading5', name: 'heading 5', font: 'Arial', size: 22, top: 120, bottom: 120, bold: true },
27
+ { id: 'Heading6', name: 'heading 6', font: 'Arial', size: 22, top: 120, bottom: 120, underline: true, italic: true, color: '666666' },
28
+ { id: 'Title', name: 'title', font: 'Palatino', size: 60 },
29
+ { id: 'Subtitle', name: 'subtitle', font: 'Arial', size: 28, top: 60 }
30
+ ]
31
+ end
32
+
33
+
34
+ #-------------------------------------------------------------
35
+ # Public Methods
36
+ #-------------------------------------------------------------
37
+
38
+ #============== ATTRIBUTES ==========================
39
+
40
+ def style(**options, &block)
41
+ model = Caracal::Core::Models::StyleModel.new(options, &block)
42
+
43
+ if model.valid?
44
+ register_style(model)
45
+ else
46
+ raise Caracal::Errors::InvalidModelError, 'style must define an :id and :name.'
47
+ end
48
+ model
49
+ end
50
+
51
+
52
+ #============== GETTERS =============================
53
+
54
+ def styles
55
+ @styles ||= []
56
+ end
57
+
58
+ def default_style
59
+ styles.find { |s| s.style_default }
60
+ end
61
+
62
+ def find_style(id)
63
+ styles.find { |s| s.matches?(id) }
64
+ end
65
+
66
+
67
+ #============== REGISTRATION ========================
68
+
69
+ def register_style(model)
70
+ unregister_style(model.style_id)
71
+ styles << model
72
+ model
73
+ end
74
+
75
+ def unregister_style(id)
76
+ if s = find_style(id)
77
+ styles.delete(s)
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,41 @@
1
+ require 'caracal/core/models/table_model'
2
+ require 'caracal/errors'
3
+
4
+
5
+ module Caracal
6
+ module Core
7
+
8
+ # This module encapsulates all the functionality related to adding tables
9
+ # to the document.
10
+ #
11
+ module Tables
12
+ def self.included(base)
13
+ base.class_eval do
14
+
15
+ #-------------------------------------------------------------
16
+ # Public Methods
17
+ #-------------------------------------------------------------
18
+
19
+ def table(data, **options, &block)
20
+ options.merge!({ data: data })
21
+
22
+ model = Caracal::Core::Models::TableModel.new(options, &block)
23
+ if respond_to?(:page_width)
24
+ container_width = page_width - page_margin_left - page_margin_right
25
+ model.calculate_width(container_width)
26
+ end
27
+
28
+ if model.valid?
29
+ contents << model
30
+ else
31
+ raise Caracal::Errors::InvalidModelError, 'Table must be provided data for at least one cell.'
32
+ end
33
+ model
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ require 'caracal/core/models/paragraph_model'
2
+ require 'caracal/errors'
3
+
4
+
5
+ module Caracal
6
+ module Core
7
+
8
+ # This module encapsulates all the functionality related to adding text
9
+ # to the document.
10
+ #
11
+ module Text
12
+ def self.included(base)
13
+ base.class_eval do
14
+
15
+ #-------------------------------------------------------------
16
+ # Public Methods
17
+ #-------------------------------------------------------------
18
+
19
+ #============== PARAGRAPHS ==========================
20
+
21
+ def p(*text, **options, &block)
22
+ text.flatten!
23
+ options.merge!( { content: text[0] }) unless text[0].nil?
24
+
25
+ model = Caracal::Core::Models::ParagraphModel.new(options, &block)
26
+ if model.valid?
27
+ contents << model
28
+ else
29
+ raise Caracal::Errors::InvalidModelError, 'Paragraphs and headings, which delegate to the :p command, require at least one text string.'
30
+ end
31
+ model
32
+ end
33
+
34
+
35
+ #============== HEADINGS ============================
36
+
37
+ # All heading methods simply delegate to the paragraph
38
+ # model with an explicitly set style class.
39
+ #
40
+ [:h1, :h2, :h3, :h4, :h5, :h6].each do |cmd|
41
+ define_method "#{ cmd }" do |*text, **options, &block|
42
+ options.merge!({ style: style_id_for_header(cmd) })
43
+ p(text, options, &block)
44
+ end
45
+ end
46
+
47
+
48
+ #-------------------------------------------------------------
49
+ # Private Methods
50
+ #-------------------------------------------------------------
51
+ private
52
+
53
+ # This method translates the html-like command to the
54
+ # corresponding style id.
55
+ #
56
+ def style_id_for_header(command)
57
+ case command.to_s
58
+ when 'h1' then 'Heading1'
59
+ when 'h2' then 'Heading2'
60
+ when 'h3' then 'Heading3'
61
+ when 'h4' then 'Heading4'
62
+ when 'h5' then 'Heading5'
63
+ when 'h6' then 'Heading6'
64
+ else 'Normal'
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,242 @@
1
+ require 'open-uri'
2
+ require 'zip'
3
+
4
+ require 'caracal/core/file_name'
5
+ require 'caracal/core/fonts'
6
+ require 'caracal/core/images'
7
+ require 'caracal/core/line_breaks'
8
+ require 'caracal/core/list_styles'
9
+ require 'caracal/core/lists'
10
+ require 'caracal/core/page_breaks'
11
+ require 'caracal/core/page_numbers'
12
+ require 'caracal/core/page_settings'
13
+ require 'caracal/core/relationships'
14
+ require 'caracal/core/rules'
15
+ require 'caracal/core/styles'
16
+ require 'caracal/core/tables'
17
+ require 'caracal/core/text'
18
+
19
+ require 'caracal/renderers/app_renderer'
20
+ require 'caracal/renderers/content_types_renderer'
21
+ require 'caracal/renderers/core_renderer'
22
+ require 'caracal/renderers/document_renderer'
23
+ require 'caracal/renderers/fonts_renderer'
24
+ require 'caracal/renderers/footer_renderer'
25
+ require 'caracal/renderers/numbering_renderer'
26
+ require 'caracal/renderers/package_relationships_renderer'
27
+ require 'caracal/renderers/relationships_renderer'
28
+ require 'caracal/renderers/settings_renderer'
29
+ require 'caracal/renderers/styles_renderer'
30
+
31
+
32
+ module Caracal
33
+ class Document
34
+
35
+ #-------------------------------------------------------------
36
+ # Configuration
37
+ #-------------------------------------------------------------
38
+
39
+ # mixins (order is important)
40
+ include Caracal::Core::FileName
41
+
42
+ include Caracal::Core::Relationships
43
+ include Caracal::Core::Fonts
44
+ include Caracal::Core::PageSettings
45
+ include Caracal::Core::PageNumbers
46
+ include Caracal::Core::Styles
47
+ include Caracal::Core::ListStyles
48
+
49
+ include Caracal::Core::Images
50
+ include Caracal::Core::LineBreaks
51
+ include Caracal::Core::Lists
52
+ include Caracal::Core::PageBreaks
53
+ include Caracal::Core::Rules
54
+ include Caracal::Core::Tables
55
+ include Caracal::Core::Text
56
+
57
+
58
+ #-------------------------------------------------------------
59
+ # Public Class Methods
60
+ #-------------------------------------------------------------
61
+
62
+ #============ GETTERS ===================================
63
+
64
+ # This method returns an array of models which constitute the
65
+ # set of instructions for producing the document content.
66
+ #
67
+ def contents
68
+ @contents ||= []
69
+ end
70
+
71
+
72
+ #============ OUTPUT ====================================
73
+
74
+ # This method renders a new Word document and returns it as a
75
+ # a string.
76
+ #
77
+ def self.render(f_name = nil, &block)
78
+ docx = new(f_name, &block)
79
+ buffer = docx.render
80
+
81
+ buffer.rewind
82
+ buffer.sysread
83
+ end
84
+
85
+ # This method renders a new Word document and saves it to the
86
+ # file system.
87
+ #
88
+ def self.save(f_name = nil, &block)
89
+ docx = new(f_name, &block)
90
+ buffer = docx.render
91
+
92
+ File.open("./#{ docx.name }", 'w') { |f| f.write(buffer.string) }
93
+ end
94
+
95
+
96
+
97
+ #-------------------------------------------------------------
98
+ # Public Instance Methods
99
+ #-------------------------------------------------------------
100
+
101
+ # This method instantiates a new word document.
102
+ #
103
+ def initialize(name = nil, &block)
104
+ file_name(name)
105
+
106
+ page_size
107
+ page_margins top: 1440, bottom: 1440, left: 1440, right: 1440
108
+ page_numbers
109
+
110
+ [:relationship, :font, :style, :list_style].each do |method|
111
+ collection = self.class.send("default_#{ method }s")
112
+ collection.each do |item|
113
+ send(method, item)
114
+ end
115
+ end
116
+
117
+ if block_given?
118
+ (block.arity < 1) ? instance_eval(&block) : block[self]
119
+ end
120
+ end
121
+
122
+
123
+ #============ RENDERING =================================
124
+
125
+ # This method renders the word document instance into
126
+ # a string buffer. Order is important!
127
+ #
128
+ def render
129
+ buffer = ::Zip::OutputStream.write_buffer do |zip|
130
+ render_package_relationships(zip)
131
+ render_content_types(zip)
132
+ render_app(zip)
133
+ render_core(zip)
134
+ render_fonts(zip)
135
+ render_footer(zip)
136
+ render_settings(zip)
137
+ render_styles(zip)
138
+ render_document(zip)
139
+ render_relationships(zip) # Must go here: Depends on document renderer
140
+ render_media(zip) # Must go here: Depends on document renderer
141
+ render_numbering(zip) # Must go here: Depends on document renderer
142
+ end
143
+ end
144
+
145
+
146
+
147
+ #-------------------------------------------------------------
148
+ # Private Instance Methods
149
+ #-------------------------------------------------------------
150
+ private
151
+
152
+ #============ RENDERERS =====================================
153
+
154
+ def render_app(zip)
155
+ content = ::Caracal::Renderers::AppRenderer.render(self)
156
+
157
+ zip.put_next_entry('docProps/app.xml')
158
+ zip.write(content)
159
+ end
160
+
161
+ def render_content_types(zip)
162
+ content = ::Caracal::Renderers::ContentTypesRenderer.render(self)
163
+
164
+ zip.put_next_entry('[Content_Types].xml')
165
+ zip.write(content)
166
+ end
167
+
168
+ def render_core(zip)
169
+ content = ::Caracal::Renderers::CoreRenderer.render(self)
170
+
171
+ zip.put_next_entry('docProps/core.xml')
172
+ zip.write(content)
173
+ end
174
+
175
+ def render_document(zip)
176
+ content = ::Caracal::Renderers::DocumentRenderer.render(self)
177
+
178
+ zip.put_next_entry('word/document.xml')
179
+ zip.write(content)
180
+ end
181
+
182
+ def render_fonts(zip)
183
+ content = ::Caracal::Renderers::FontsRenderer.render(self)
184
+
185
+ zip.put_next_entry('word/fontTable.xml')
186
+ zip.write(content)
187
+ end
188
+
189
+ def render_footer(zip)
190
+ content = ::Caracal::Renderers::FooterRenderer.render(self)
191
+
192
+ zip.put_next_entry('word/footer1.xml')
193
+ zip.write(content)
194
+ end
195
+
196
+ def render_media(zip)
197
+ images = relationships.select { |r| r.relationship_type == :image }
198
+ images.each do |rel|
199
+ content = open(rel.relationship_target).read
200
+
201
+ zip.put_next_entry("word/#{ rel.formatted_target }")
202
+ zip.write(content)
203
+ end
204
+ end
205
+
206
+ def render_numbering(zip)
207
+ content = ::Caracal::Renderers::NumberingRenderer.render(self)
208
+
209
+ zip.put_next_entry('word/numbering.xml')
210
+ zip.write(content)
211
+ end
212
+
213
+ def render_package_relationships(zip)
214
+ content = ::Caracal::Renderers::PackageRelationshipsRenderer.render(self)
215
+
216
+ zip.put_next_entry('_rels/.rels')
217
+ zip.write(content)
218
+ end
219
+
220
+ def render_relationships(zip)
221
+ content = ::Caracal::Renderers::RelationshipsRenderer.render(self)
222
+
223
+ zip.put_next_entry('word/_rels/document.xml.rels')
224
+ zip.write(content)
225
+ end
226
+
227
+ def render_settings(zip)
228
+ content = ::Caracal::Renderers::SettingsRenderer.render(self)
229
+
230
+ zip.put_next_entry('word/settings.xml')
231
+ zip.write(content)
232
+ end
233
+
234
+ def render_styles(zip)
235
+ content = ::Caracal::Renderers::StylesRenderer.render(self)
236
+
237
+ zip.put_next_entry('word/styles.xml')
238
+ zip.write(content)
239
+ end
240
+
241
+ end
242
+ end