cmless 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cmless.rb +145 -0
  3. metadata +46 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bdb15f8e0031cc6134590b4476267f2de933d79b
4
+ data.tar.gz: c9648c323e096ff9a2bf767507ebf18a1ee0e5af
5
+ SHA512:
6
+ metadata.gz: b59096de7971a1f6695581fbf8ad61c856f5df2fa0da03b4ad372f02982d03daa446d019f89ed593b26806801eb57f33166ea1c6039ac38d90bf7c9474ef862e
7
+ data.tar.gz: 9df87894d707f101b566a58d781086e7173bb50a16541a1016c1d61e1b7f84e544de8d70e80b624e86eb29f602ad7e30eb1425b3b3652db8c253fb17d9a397a8
data/lib/cmless.rb ADDED
@@ -0,0 +1,145 @@
1
+ # require_relative 'cmless/cmless'
2
+ # If this becomes multiple files, then require a subdirectory.
3
+ # but if it's just one file, it's fine here.
4
+
5
+ require 'redcarpet'
6
+ require 'singleton'
7
+ require 'nokogiri'
8
+
9
+ class Cmless
10
+
11
+ attr_reader :path
12
+ attr_reader :title
13
+
14
+ private
15
+
16
+ # You should use find_by_path rather than creating your own objects.
17
+ def initialize(file_path)
18
+ @path = self.class.path_from_file_path(file_path)
19
+ Nokogiri::HTML(Markdowner.instance.render(File.read(file_path))).tap do |doc|
20
+ @title = doc.xpath('//h1').first.remove.text
21
+
22
+ html_methods = self.class.instance_methods.
23
+ select { |method| method.to_s.match(/\_html$/) }
24
+
25
+ if html_methods.include?(:head_html)
26
+ self.instance_variable_set('@head_html', Cmless.extract_head_html(doc))
27
+ html_methods.delete(:head_html)
28
+ end
29
+
30
+ if html_methods.include?(:body_html)
31
+ self.instance_variable_set('@body_html', Cmless.extract_body_html(doc))
32
+ html_methods.delete(:body_html)
33
+ end
34
+
35
+ html_methods.each do |method|
36
+ h2_name = method.to_s.gsub(/\_html$/, '').gsub('_',' ').capitalize
37
+ variable_name = "@#{method.to_s}"
38
+ self.instance_variable_set(variable_name, Cmless.extract_html(doc, h2_name))
39
+ end
40
+
41
+ doc.text.strip.tap do |extra|
42
+ escaped = extra.gsub("\n",'\\n').gsub("\t",'\\t')
43
+ fail("#{file_path} has extra unused text: '#{escaped}'") unless extra == ''
44
+ end
45
+ end
46
+ end
47
+
48
+ public
49
+
50
+ # Instance methods:
51
+
52
+ def ancestors
53
+ @ancestors ||= begin
54
+ split = path.split('/')
55
+ (1..split.size-1).to_a.map do |i|
56
+ self.class.objects_by_path[split[0,i].join('/')]
57
+ end
58
+ end
59
+ end
60
+
61
+ def children
62
+ @children ||= begin
63
+ self.class.objects_by_path.select do |other_path, other_object|
64
+ other_path.match(/^#{path}\/[^\/]+$/) # TODO: escape
65
+ end.map do |other_path, other_object|
66
+ other_object
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ # Class methods:
73
+
74
+ def self.objects_by_path
75
+ @objects_by_path ||=
76
+ begin
77
+ unless File.directory?(self.root_path)
78
+ raise StandardError.new("#{self.root_path} is not a directory")
79
+ end
80
+ Hash[
81
+ Dir[Pathname(self.root_path) + '**/*.md'].sort.map do |path|
82
+ object = self.new(path)
83
+ [object.path, object]
84
+ end
85
+ ]
86
+ end
87
+ end
88
+
89
+ def self.find_by_path(path)
90
+ self.objects_by_path[path] || raise(IndexError.new("'#{path}' is not a valid path under '#{self.root_path}'; Expected one of #{self.objects_by_path.keys}"))
91
+ end
92
+
93
+ def self.path_from_file_path(file_path)
94
+ file_path.to_s.gsub(self.root_path.to_s+'/', '').gsub(/\.md$/, '')
95
+ end
96
+
97
+ def self.extract_html(doc, title)
98
+ following_siblings = []
99
+ doc.xpath("//h2[text()='#{title}']").first.tap do |header|
100
+ raise IndexError.new("Can't find header '#{title}'") unless header
101
+ while header.next_element && !header.next_element.name.match(/h2/) do
102
+ following_siblings.push(header.next_element.remove)
103
+ end
104
+ header.remove
105
+ end
106
+ following_siblings.map { |el| el.to_s }.join
107
+ end
108
+
109
+ def self.extract_head_html(doc)
110
+ siblings = []
111
+ body = doc.xpath('//body').first
112
+ while body.children.first && !body.children.first.name.match(/h2/)
113
+ siblings.push(body.children.first.remove)
114
+ end
115
+ siblings.map { |el| el.to_s }.join.strip
116
+ end
117
+
118
+ def self.extract_body_html(doc)
119
+ siblings = []
120
+ body = doc.xpath('//body').first
121
+ while body.children.first
122
+ siblings.push(body.children.first.remove)
123
+ end
124
+ siblings.map { |el| el.to_s }.join.strip
125
+ end
126
+
127
+
128
+ # Utility class: (This could move.)
129
+
130
+ class Markdowner
131
+ include Singleton
132
+
133
+ def initialize()
134
+ @markdown = Redcarpet::Markdown.new(
135
+ Redcarpet::Render::XHTML.new(with_toc_data: true),
136
+ autolink: true)
137
+ end
138
+
139
+ def render(md_text)
140
+ return unless md_text
141
+ @markdown.render(md_text)
142
+ end
143
+ end
144
+
145
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cmless
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chuck McCallum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'CMS alternative: Maintain content in markdown / Extract HTML and data
14
+ for display'
15
+ email: chuck_mccallum@wgbh.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/cmless.rb
21
+ homepage: https://github.com/WGBH/cmless
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.2.3
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: CMS, but less
45
+ test_files: []
46
+ has_rdoc: