caracal 0.1.0

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