hologram 0.6.0 → 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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -4
  3. data/Rakefile +5 -0
  4. data/hologram.gemspec +1 -1
  5. data/lib/hologram.rb +13 -445
  6. data/lib/hologram/display_message.rb +79 -0
  7. data/lib/hologram/doc_block_collection.rb +48 -0
  8. data/lib/hologram/doc_builder.rb +196 -0
  9. data/lib/hologram/doc_parser.rb +125 -0
  10. data/lib/hologram/document_block.rb +36 -0
  11. data/lib/hologram/template_variables.rb +21 -0
  12. data/lib/hologram/version.rb +1 -1
  13. data/lib/template/doc_assets/_header.html +7 -2
  14. data/lib/template/hologram_config.yml +3 -0
  15. data/spec/display_message_spec.rb +115 -0
  16. data/spec/doc_block_collection_spec.rb +80 -0
  17. data/spec/doc_builder_spec.rb +92 -0
  18. data/spec/doc_parser_spec.rb +89 -0
  19. data/spec/document_block_spec.rb +62 -0
  20. data/spec/fixtures/source/components/background/backgrounds.css +46 -0
  21. data/spec/fixtures/source/components/button/buttons.css +87 -0
  22. data/spec/fixtures/source/components/button/skin/buttonSkins.css +113 -0
  23. data/spec/fixtures/source/components/index.md +23 -0
  24. data/spec/fixtures/source/config.yml +17 -0
  25. data/spec/fixtures/source/extra/css/screen.css +1 -0
  26. data/spec/fixtures/source/templates/_footer.html +9 -0
  27. data/spec/fixtures/source/templates/_header.html +57 -0
  28. data/spec/fixtures/source/templates/static/css/doc.css +132 -0
  29. data/spec/fixtures/styleguide/base_css.html +170 -0
  30. data/spec/fixtures/styleguide/extra/css/screen.css +1 -0
  31. data/spec/fixtures/styleguide/index.html +84 -0
  32. data/spec/fixtures/styleguide/static/css/doc.css +132 -0
  33. data/spec/spec_helper.rb +7 -0
  34. metadata +66 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 24abd28899897760d163bbf8dd975e4589436af0
4
- data.tar.gz: ac2d7bf8b43b646802107f20284378c77bfa26f3
3
+ metadata.gz: 76d2ba97aebf57d28a1af14bb938face091b9f7a
4
+ data.tar.gz: 4c257fc06a73984a890d15d11e42f46ae5150203
5
5
  SHA512:
6
- metadata.gz: 9793211c0cf19e31fd8c5384ec17f149ebfec48ff596477a94b48507b6f467519d146962f32f8e2aa7196773e4c0d272bbc1555f0bf2be9cf3062d2be9915fdb
7
- data.tar.gz: f9308f6c7136fcefd2bd7188c583d699355071ca6ffb24e2586a9d57156db8e56c4f2b9b42d62c64b632aa516c2b910bd26117a71ca0bbc6f3f982d9234ddce7
6
+ metadata.gz: 21c7f40342561dd6ea32edaf1e76390f08d0b7dfbb2c64fce8e60596227e2a562e90cde16478e052930a4205dbcae738385943da5eec8eeb23bdf06209f7a323
7
+ data.tar.gz: 56dc6706a6ff01a7bcbfbe874f27d6c5bd5723cd809c9fe976be6027b0f92a1fff4610f20f34280ffaf13f1c7fe001cafa3a725a0ce6fdcb9613ed6c61f8808f
data/README.md CHANGED
@@ -66,13 +66,13 @@ Your config file needs to contain the following key/value pairs
66
66
  (header/footer, etc), styleguide specific CSS, javascript and any
67
67
  images. Hologram specifically looks for two files: `_header.html` and
68
68
  `_footer.html`, these are used to start and end every html page
69
- hologram generates.
69
+ hologram generates.
70
70
 
71
71
  Hologram treats `_header.html` and `_footer.html`
72
72
  as ERB files for each page that is generated you can access the
73
73
  `title`, `file_name`, and `blocks`. `blocks` is a list of each
74
74
  documenation block on the page. Each item in the list has a `title`,
75
- `name`, `category`, and optionally a `parent`. This is useful for, say, building a menu that lists each component.
75
+ `name`, `category`, and optionally a `parent`. This is useful for, say, building a menu that lists each component.
76
76
  **Nota Bene:** Filenames that begin with underscores will not be copied into the destination folder.
77
77
 
78
78
 
@@ -150,9 +150,9 @@ but it specifically looks for the following keys:
150
150
  components in the same category will be written to the same page.
151
151
  * **name**: This is used for grouping components, by assigning
152
152
  a name a component can be referenced in another component as a parent.
153
- * **parent**: (Optional.) This should be the **name** of another components. If this is set the current component will be displayed as a section within the **parent**'s documentation.
153
+ * **parent**: (Optional.) This should be the **name** of another components. If this is set the current component will be displayed as a section within the **parent**'s documentation.
154
154
 
155
- For example, you might have a component with the **name** *buttons* and another component named *buttonSkins*. You could set the **parent** for the *buttonSkins* component to be *buttons*. It would then nest the *buttonSkins* documentation inside the *buttons* documentation.
155
+ For example, you might have a component with the **name** *buttons* and another component named *buttonSkins*. You could set the **parent** for the *buttonSkins* component to be *buttons*. It would then nest the *buttonSkins* documentation inside the *buttons* documentation.
156
156
 
157
157
  Each level of nesting (components are infinitely nestable) will have a heading tag that represents its depth. In the above example *buttons* would have an `<h1>` and *buttonSkins* would have an `<h2>`. This you can [see this exact example in our demo repo](https://github.com/trulia/hologram-example/tree/master/components/button), and the output of this nesting [in our demo styleguide](http://trulia.github.io/hologram-example/base_css.html#Buttons).
158
158
 
@@ -183,6 +183,9 @@ The following preprocessors/file types are supported by Hologram:
183
183
  - Javascript (.js)
184
184
  - Markdown (.md, .markdown)
185
185
 
186
+ ## Extensions and Plugins
187
+ - [Guard Hologram](https://github.com/kmayer/guard-hologram) is a sweet little gem that uses guard to monitor changes to your hologram project and rebuilds your styleguide on the fly as you make changes.
188
+
186
189
  ## Contributing
187
190
 
188
191
  1. Fork it
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -14,8 +14,8 @@ Gem::Specification.new do |spec|
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.add_dependency "redcarpet", "~> 2.2.2"
17
- spec.add_dependency "sass", "~> 3.2.7"
18
17
  spec.add_dependency "pygments.rb", "~> 0.4.2"
18
+ spec.add_dependency "rspec", "~> 2.14.1"
19
19
 
20
20
  spec.files = `git ls-files`.split($/)
21
21
  spec.executables = ['hologram']
@@ -1,5 +1,3 @@
1
- require "hologram/version"
2
-
3
1
  require 'redcarpet'
4
2
  require 'yaml'
5
3
  require 'pygments'
@@ -7,450 +5,20 @@ require 'fileutils'
7
5
  require 'pathname'
8
6
  require 'erb'
9
7
 
8
+ require 'hologram/version'
9
+ require 'hologram/document_block'
10
+ require 'hologram/doc_block_collection'
11
+ require 'hologram/doc_parser'
12
+ require 'hologram/doc_builder'
13
+ require 'hologram/template_variables'
14
+ require 'hologram/display_message'
15
+
10
16
  require 'hologram_markdown_renderer'
11
17
 
12
18
  module Hologram
13
-
14
- class DocumentBlock
15
- attr_accessor :name, :parent, :children, :title, :category, :markdown, :config, :heading
16
-
17
- def initialize(config = nil, markdown = nil)
18
- @children = {}
19
- set_members(config, markdown) if config and markdown
20
- end
21
-
22
- def set_members(config, markdown)
23
- @name = config['name']
24
- @category = config['category']
25
- @title = config['title']
26
- @parent = config['parent']
27
- @markdown = markdown
28
- end
29
-
30
- def get_hash
31
- {:name => @name,
32
- :parent => @parent,
33
- :category => @category,
34
- :title => @title
35
- }
36
- end
37
-
38
- def is_valid?
39
- @name && @markdown
40
- end
41
-
42
- # sets the header tag based on how deep your nesting is
43
- def markdown_with_heading(heading = 1)
44
- @markdown = "\n\n<h#{heading.to_s} id=\"#{@name}\">#{@title}</h#{heading.to_s}>" + @markdown
45
- end
46
- end
47
-
48
-
49
- class DocBuilder
50
- attr_accessor :doc_blocks, :config, :pages
51
-
52
- INIT_TEMPLATE_PATH = File.expand_path('./template/', File.dirname(__FILE__)) + '/'
53
- INIT_TEMPLATE_FILES = [
54
- INIT_TEMPLATE_PATH + '/hologram_config.yml',
55
- INIT_TEMPLATE_PATH + '/doc_assets',
56
- ]
57
-
58
- def init(args)
59
- @pages = {}
60
- @supported_extensions = ['.css', '.scss', '.less', '.sass', '.styl', '.js', '.md', '.markdown' ]
61
-
62
- begin
63
- if args[0] == 'init' then
64
-
65
- if File.exists?("hologram_config.yml")
66
- puts "Cowardly refusing to overwrite existing hologram_config.yml".yellow
67
- else
68
- FileUtils.cp_r INIT_TEMPLATE_FILES, Dir.pwd
69
- puts "Created the following files and directories:"
70
- puts " hologram_config.yml"
71
- puts " doc_assets/"
72
- puts " doc_assets/_header.html"
73
- puts " doc_assets/_footer.html"
74
- end
75
- else
76
- begin
77
- config_file = args[0] ? args[0] : 'hologram_config.yml'
78
-
79
- begin
80
- @config = YAML::load_file(config_file)
81
- rescue
82
- DisplayMessage.error("Could not load config file, try 'hologram init' to get started")
83
- end
84
-
85
- validate_config
86
-
87
- current_path = Dir.pwd
88
- base_path = Pathname.new(config_file)
89
- Dir.chdir(base_path.dirname)
90
-
91
- # the real work happens here.
92
- build_docs
93
-
94
- Dir.chdir(current_path)
95
- puts "Build completed. (-: ".green
96
- rescue RuntimeError => e
97
- DisplayMessage.error("#{e}")
98
- end
99
- end
100
- end
101
- end
102
-
103
-
104
- private
105
- def build_docs
106
- # Create the output directory if it doesn't exist
107
- FileUtils.mkdir_p(config['destination']) unless File.directory?(config['destination'])
108
-
109
- begin
110
- input_directory = Pathname.new(config['source']).realpath
111
- rescue
112
- DisplayMessage.error("Can not read source directory, does it exist?")
113
- end
114
-
115
- output_directory = Pathname.new(config['destination']).realpath
116
- doc_assets = Pathname.new(config['documentation_assets']).realpath unless !File.directory?(config['documentation_assets'])
117
-
118
- if doc_assets.nil?
119
- DisplayMessage.warning("Could not find documentation assets at #{config['documentation_assets']}")
120
- end
121
-
122
- # recursively traverse our directory structure looking for files that
123
- # match our "parseable" file types. Open those files pulling out any
124
- # comments matching the hologram doc style /*doc */ and create DocBlock
125
- # objects from those comments, then add those to a collection object which
126
- # is then returned.
127
- doc_block_collection = process_dir(input_directory)
128
-
129
- # doc blocks can define parent/child relationships that will nest their
130
- # documentation appropriately. we can't put everything into that structure
131
- # on our first pass through because there is no guarantee we'll parse files
132
- # in the correct order. This step takes the full collection and creates the
133
- # proper structure.
134
- doc_block_collection.create_nested_structure
135
-
136
- # hand off our properly nested collection to the output generator
137
- build_pages_from_doc_blocks(doc_block_collection.doc_blocks)
138
-
139
- # if we have an index category defined in our config copy that
140
- # page to index.html
141
- if config['index']
142
- if @pages.has_key?(config['index'] + '.html')
143
- @pages['index.html'] = @pages[config['index'] + '.html']
144
- else
145
- DisplayMessage.warning("Could not generate index.html, there was no content generated for the category #{config['index']}.")
146
- end
147
- end
148
-
149
- write_docs(output_directory, doc_assets)
150
-
151
- # Copy over dependencies
152
- if config['dependencies']
153
- config['dependencies'].each do |dir|
154
- begin
155
- dirpath = Pathname.new(dir).realpath
156
- if File.directory?("#{dir}")
157
- `rm -rf #{output_directory}/#{dirpath.basename}`
158
- `cp -R #{dirpath} #{output_directory}/#{dirpath.basename}`
159
- end
160
- rescue
161
- DisplayMessage.warning("Could not copy dependency: #{dir}")
162
- end
163
- end
164
- end
165
-
166
- if !doc_assets.nil?
167
- Dir.foreach(doc_assets) do |item|
168
- # ignore . and .. directories and files that start with
169
- # underscore
170
- next if item == '.' or item == '..' or item.start_with?('_')
171
- `rm -rf #{output_directory}/#{item}`
172
- `cp -R #{doc_assets}/#{item} #{output_directory}/#{item}`
173
- end
174
- end
175
- end
176
-
177
-
178
- def process_dir(base_directory)
179
- #get all directories in our library folder
180
- doc_block_collection = DocBlockCollection.new
181
- directories = Dir.glob("#{base_directory}/**/*/")
182
- directories.unshift(base_directory)
183
-
184
- directories.each do |directory|
185
- # filter and sort the files in our directory
186
- files = []
187
- Dir.foreach(directory).select{ |file| is_supported_file_type?(file) }.each do |file|
188
- files << file
189
- end
190
- files.sort!
191
- process_files(files, directory, doc_block_collection)
192
- end
193
- doc_block_collection
194
- end
195
-
196
-
197
- def process_files(files, directory, doc_block_collection)
198
- files.each do |input_file|
199
- if input_file.end_with?('md')
200
- @pages[File.basename(input_file, '.md') + '.html'] = {:md => File.read("#{directory}/#{input_file}"), :blocks => []}
201
- else
202
- process_file("#{directory}/#{input_file}", doc_block_collection)
203
- end
204
- end
205
- end
206
-
207
-
208
- def process_file(file, doc_block_collection)
209
- file_str = File.read(file)
210
- # get any comment blocks that match the patterns:
211
- # .sass: //doc (follow by other lines proceeded by a space)
212
- # other types: /*doc ... */
213
- if file.end_with?('.sass')
214
- hologram_comments = file_str.scan(/\s*\/\/doc\s*((( [^\n]*\n)|\n)+)/)
215
- else
216
- hologram_comments = file_str.scan(/^\s*\/\*doc(.*?)\*\//m)
217
- end
218
- return unless hologram_comments
219
-
220
- hologram_comments.each do |comment_block|
221
- doc_block_collection.add_doc_block(comment_block[0])
222
- end
223
- end
224
-
225
-
226
- def build_pages_from_doc_blocks(doc_blocks, output_file = nil, depth = 1)
227
- doc_blocks.sort.map do |key, doc_block|
228
-
229
- # if the doc_block has a category set then use that, this will be
230
- # true of all top level doc_blocks. The output file they set will then
231
- # be passed into the recursive call for adding children to the output
232
- output_file = get_file_name(doc_block.category) if doc_block.category
233
-
234
- if !@pages.has_key?(output_file)
235
- @pages[output_file] = {:md => "", :blocks => []}
236
- end
237
-
238
- @pages[output_file][:blocks].push(doc_block.get_hash)
239
- @pages[output_file][:md] << doc_block.markdown_with_heading(depth)
240
-
241
- if doc_block.children
242
- depth += 1
243
- build_pages_from_doc_blocks(doc_block.children, output_file, depth)
244
- depth -= 1
245
- end
246
- end
247
- end
248
-
249
-
250
- def write_docs(output_directory, doc_assets)
251
- # load the markdown renderer we are going to use
252
- renderer = get_markdown_renderer
253
-
254
- if File.exists?("#{doc_assets}/_header.html")
255
- header_erb = ERB.new(File.read("#{doc_assets}/_header.html"))
256
- elsif File.exists?("#{doc_assets}/header.html")
257
- header_erb = ERB.new(File.read("#{doc_assets}/header.html"))
258
- else
259
- header_erb = nil
260
- DisplayMessage.warning("No _header.html found in documentation assets. Without this your css/header will not be included on the generated pages.")
261
- end
262
-
263
- if File.exists?("#{doc_assets}/_footer.html")
264
- footer_erb = ERB.new(File.read("#{doc_assets}/_footer.html"))
265
- elsif File.exists?("#{doc_assets}/footer.html")
266
- footer_erb = ERB.new(File.read("#{doc_assets}/footer.html"))
267
- else
268
- footer_erb = nil
269
- DisplayMessage.warning("No _footer.html found in documentation assets. This might be okay to ignore...")
270
- end
271
-
272
- #generate html from markdown
273
- @pages.each do |file_name, page|
274
- fh = get_fh(output_directory, file_name)
275
-
276
- title = page[:blocks].empty? ? "" : page[:blocks][0][:category]
277
-
278
- tpl_vars = TemplateVariables.new(title, file_name, page[:blocks])
279
-
280
- # generate doc nav html
281
- unless header_erb.nil?
282
- fh.write(header_erb.result(tpl_vars.get_binding))
283
- end
284
-
285
- # write the docs
286
- begin
287
- fh.write(renderer.render(page[:md]))
288
- rescue Exception => e
289
- DisplayMessage.error(e.message)
290
- end
291
-
292
- # write the footer
293
- unless footer_erb.nil?
294
- fh.write(footer_erb.result(tpl_vars.get_binding))
295
- end
296
-
297
- fh.close()
298
- end
299
- end
300
-
301
-
302
- def get_markdown_renderer
303
- if config['custom_markdown'].nil?
304
- renderer = Redcarpet::Markdown.new(HologramMarkdownRenderer, { :fenced_code_blocks => true, :tables => true })
305
- else
306
- begin
307
- load config['custom_markdown']
308
- renderer_class = File.basename(config['custom_markdown'], '.rb').split(/_/).map(&:capitalize).join
309
- puts "Custom markdown renderer #{renderer_class} loaded."
310
- renderer = Redcarpet::Markdown.new(Module.const_get(renderer_class), { :fenced_code_blocks => true, :tables => true })
311
- rescue LoadError => e
312
- DisplayMessage.error("Could not load #{config['custom_markdown']}.")
313
- rescue NameError => e
314
- DisplayMessage.error("Class #{renderer_class} not found in #{config['custom_markdown']}.")
315
- end
316
- end
317
- renderer
318
- end
319
-
320
-
321
- def validate_config
322
- unless @config.key?('source')
323
- DisplayMessage.error("No source directory specified in the config file")
324
- end
325
-
326
- unless @config.key?('destination')
327
- DisplayMessage.error("No destination directory specified in the config")
328
- end
329
-
330
- unless @config.key?('documentation_assets')
331
- DisplayMessage.error("No documentation assets directory specified")
332
- end
333
- end
334
-
335
-
336
- def is_supported_file_type?(file)
337
- @supported_extensions.include?(File.extname(file))
338
- end
339
-
340
-
341
- def get_file_name(str)
342
- str = str.gsub(' ', '_').downcase + '.html'
343
- end
344
-
345
-
346
- def get_fh(output_directory, output_file)
347
- File.open("#{output_directory}/#{output_file}", 'w')
348
- end
349
- end
350
-
351
-
352
- #Helper class for binding things for ERB
353
- class TemplateVariables
354
- attr_accessor :title, :file_name, :blocks
355
-
356
- def initialize(title, file_name, blocks)
357
- @title = title
358
- @file_name = file_name
359
- @blocks = blocks
360
- end
361
-
362
- def get_binding
363
- binding()
364
- end
365
- end
366
-
367
- class DocBlockCollection
368
- attr_accessor :doc_blocks
369
-
370
- def initialize
371
- @doc_blocks = {}
372
- end
373
-
374
- # this should throw an error if we have a match, but no yaml_match
375
- def add_doc_block(comment_block)
376
- yaml_match = /^\s*---\s(.*?)\s---$/m.match(comment_block)
377
- return unless yaml_match
378
-
379
- markdown = comment_block.sub(yaml_match[0], '')
380
-
381
- begin
382
- config = YAML::load(yaml_match[1])
383
- rescue
384
- DisplayMessage.error("Could not parse YAML:\n#{yaml_match[1]}")
385
- end
386
-
387
- if config['name'].nil?
388
- DisplayMessage.warning("Missing required name config value. This hologram comment will be skipped. \n #{config.inspect}")
389
- else
390
- doc_block = DocumentBlock.new(config, markdown)
391
- end
392
-
393
- @doc_blocks[doc_block.name] = doc_block if doc_block.is_valid?
394
- end
395
-
396
- def create_nested_structure
397
- blocks_to_remove_from_top_level = []
398
- @doc_blocks.each do |key, doc_block|
399
- # don't do anything to top level doc_blocks
400
- next if !doc_block.parent
401
-
402
- parent = @doc_blocks[doc_block.parent]
403
- parent.children[doc_block.name] = doc_block
404
- doc_block.parent = parent
405
- blocks_to_remove_from_top_level << doc_block.name
406
- end
407
-
408
- blocks_to_remove_from_top_level.each do |key|
409
- @doc_blocks.delete(key)
410
- end
411
- end
412
- end
413
-
414
- end
415
-
416
-
417
-
418
- class DisplayMessage
419
- def self.error(message)
420
- if RUBY_VERSION.to_f > 1.8 then
421
- puts "(\u{256F}\u{00B0}\u{25A1}\u{00B0}\u{FF09}\u{256F}".green + "\u{FE35} \u{253B}\u{2501}\u{253B} ".yellow + " Build not complete.".red
422
- else
423
- puts "Build not complete.".red
424
- end
425
- puts " #{message}"
426
- exit 1
427
- end
428
-
429
- def self.warning(message)
430
- puts "Warning: ".yellow + message
431
- end
432
- end
433
-
434
-
435
- class String
436
- # colorization
437
- def colorize(color_code)
438
- "\e[#{color_code}m#{self}\e[0m"
439
- end
440
-
441
- def red
442
- colorize(31)
443
- end
444
-
445
- def green
446
- colorize(32)
447
- end
448
-
449
- def yellow
450
- colorize(33)
451
- end
452
-
453
- def pink
454
- colorize(35)
455
- end
19
+ INIT_TEMPLATE_PATH = File.expand_path('./template/', File.dirname(__FILE__)) + '/'
20
+ INIT_TEMPLATE_FILES = [
21
+ INIT_TEMPLATE_PATH + '/hologram_config.yml',
22
+ INIT_TEMPLATE_PATH + '/doc_assets',
23
+ ]
456
24
  end