hologram 0.6.0 → 1.0.0

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