Shazburg-webby 0.9.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 (172) hide show
  1. data/History.txt +176 -0
  2. data/Manifest.txt +171 -0
  3. data/README.txt +92 -0
  4. data/Rakefile +54 -0
  5. data/bin/webby +8 -0
  6. data/bin/webby-gen +8 -0
  7. data/examples/blog/Sitefile +7 -0
  8. data/examples/blog/tasks/blog.rake +72 -0
  9. data/examples/blog/templates/atom_feed.erb +40 -0
  10. data/examples/blog/templates/blog/month.erb +22 -0
  11. data/examples/blog/templates/blog/post.erb +16 -0
  12. data/examples/blog/templates/blog/year.erb +22 -0
  13. data/examples/presentation/Sitefile +10 -0
  14. data/examples/presentation/content/css/uv/twilight.css +137 -0
  15. data/examples/presentation/content/presentation/_sample_code.txt +10 -0
  16. data/examples/presentation/content/presentation/index.txt +63 -0
  17. data/examples/presentation/content/presentation/s5/blank.gif +0 -0
  18. data/examples/presentation/content/presentation/s5/bodybg.gif +0 -0
  19. data/examples/presentation/content/presentation/s5/framing.css +23 -0
  20. data/examples/presentation/content/presentation/s5/iepngfix.htc +42 -0
  21. data/examples/presentation/content/presentation/s5/opera.css +7 -0
  22. data/examples/presentation/content/presentation/s5/outline.css +15 -0
  23. data/examples/presentation/content/presentation/s5/pretty.css +86 -0
  24. data/examples/presentation/content/presentation/s5/print.css +1 -0
  25. data/examples/presentation/content/presentation/s5/s5-core.css +9 -0
  26. data/examples/presentation/content/presentation/s5/slides.css +3 -0
  27. data/examples/presentation/content/presentation/s5/slides.js +553 -0
  28. data/examples/presentation/layouts/presentation.txt +43 -0
  29. data/examples/presentation/templates/_code_partial.erb +13 -0
  30. data/examples/presentation/templates/presentation.erb +40 -0
  31. data/examples/tumblog/Sitefile +9 -0
  32. data/examples/tumblog/content/css/tumblog.css +308 -0
  33. data/examples/tumblog/content/images/tumblog/permalink.gif +0 -0
  34. data/examples/tumblog/content/images/tumblog/rss.gif +0 -0
  35. data/examples/tumblog/content/tumblog/200806/the-noble-chicken/index.txt +12 -0
  36. data/examples/tumblog/content/tumblog/200807/historical-perspectives-on-the-classic-chicken-joke/index.txt +12 -0
  37. data/examples/tumblog/content/tumblog/200807/mad-city-chickens/index.txt +10 -0
  38. data/examples/tumblog/content/tumblog/200807/the-wisdom-of-the-dutch/index.txt +11 -0
  39. data/examples/tumblog/content/tumblog/200807/up-a-tree/index.txt +13 -0
  40. data/examples/tumblog/content/tumblog/index.txt +37 -0
  41. data/examples/tumblog/content/tumblog/rss.txt +37 -0
  42. data/examples/tumblog/layouts/tumblog/default.txt +44 -0
  43. data/examples/tumblog/layouts/tumblog/post.txt +15 -0
  44. data/examples/tumblog/lib/tumblog_helper.rb +32 -0
  45. data/examples/tumblog/tasks/tumblog.rake +30 -0
  46. data/examples/tumblog/templates/atom_feed.erb +40 -0
  47. data/examples/tumblog/templates/tumblog/conversation.erb +12 -0
  48. data/examples/tumblog/templates/tumblog/link.erb +10 -0
  49. data/examples/tumblog/templates/tumblog/photo.erb +13 -0
  50. data/examples/tumblog/templates/tumblog/post.erb +12 -0
  51. data/examples/tumblog/templates/tumblog/quote.erb +11 -0
  52. data/examples/webby/Sitefile +19 -0
  53. data/examples/webby/content/css/background.gif +0 -0
  54. data/examples/webby/content/css/blueprint/print.css +76 -0
  55. data/examples/webby/content/css/blueprint/screen.css +696 -0
  56. data/examples/webby/content/css/coderay.css +96 -0
  57. data/examples/webby/content/css/site.css +184 -0
  58. data/examples/webby/content/css/uv/twilight.css +137 -0
  59. data/examples/webby/content/index.txt +37 -0
  60. data/examples/webby/content/manual/index.txt +430 -0
  61. data/examples/webby/content/reference/index.txt +202 -0
  62. data/examples/webby/content/release-notes/rel-0-9-0/index.txt +73 -0
  63. data/examples/webby/content/robots.txt +6 -0
  64. data/examples/webby/content/script/jquery.corner.js +152 -0
  65. data/examples/webby/content/script/jquery.js +31 -0
  66. data/examples/webby/content/sitemap.txt +31 -0
  67. data/examples/webby/content/tips_and_tricks/index.txt +96 -0
  68. data/examples/webby/content/tutorial/index.txt +131 -0
  69. data/examples/webby/layouts/default.txt +55 -0
  70. data/examples/webby/templates/page.erb +10 -0
  71. data/examples/website/Sitefile +7 -0
  72. data/examples/website/content/css/blueprint/License.txt +21 -0
  73. data/examples/website/content/css/blueprint/Readme.txt +100 -0
  74. data/examples/website/content/css/blueprint/compressed/print.css +76 -0
  75. data/examples/website/content/css/blueprint/compressed/screen.css +696 -0
  76. data/examples/website/content/css/blueprint/lib/forms.css +45 -0
  77. data/examples/website/content/css/blueprint/lib/grid.css +193 -0
  78. data/examples/website/content/css/blueprint/lib/grid.png +0 -0
  79. data/examples/website/content/css/blueprint/lib/ie.css +30 -0
  80. data/examples/website/content/css/blueprint/lib/reset.css +39 -0
  81. data/examples/website/content/css/blueprint/lib/typography.css +116 -0
  82. data/examples/website/content/css/blueprint/plugins/buttons/Readme +31 -0
  83. data/examples/website/content/css/blueprint/plugins/buttons/buttons.css +97 -0
  84. data/examples/website/content/css/blueprint/plugins/buttons/icons/cross.png +0 -0
  85. data/examples/website/content/css/blueprint/plugins/buttons/icons/key.png +0 -0
  86. data/examples/website/content/css/blueprint/plugins/buttons/icons/tick.png +0 -0
  87. data/examples/website/content/css/blueprint/plugins/css-classes/Readme +14 -0
  88. data/examples/website/content/css/blueprint/plugins/css-classes/css-classes.css +24 -0
  89. data/examples/website/content/css/blueprint/plugins/fancy-type/Readme +22 -0
  90. data/examples/website/content/css/blueprint/plugins/fancy-type/fancy-type-compressed.css +5 -0
  91. data/examples/website/content/css/blueprint/plugins/fancy-type/fancy-type.css +74 -0
  92. data/examples/website/content/css/blueprint/print.css +68 -0
  93. data/examples/website/content/css/blueprint/screen.css +22 -0
  94. data/examples/website/content/css/coderay.css +111 -0
  95. data/examples/website/content/css/site.css +67 -0
  96. data/examples/website/content/index.txt +19 -0
  97. data/examples/website/layouts/default.txt +58 -0
  98. data/examples/website/lib/breadcrumbs.rb +28 -0
  99. data/examples/website/templates/_partial.erb +10 -0
  100. data/examples/website/templates/page.erb +18 -0
  101. data/examples/website/templates/presentation.erb +40 -0
  102. data/lib/webby.rb +227 -0
  103. data/lib/webby/apps.rb +12 -0
  104. data/lib/webby/apps/generator.rb +283 -0
  105. data/lib/webby/apps/main.rb +221 -0
  106. data/lib/webby/auto_builder.rb +83 -0
  107. data/lib/webby/builder.rb +183 -0
  108. data/lib/webby/core_ext/enumerable.rb +11 -0
  109. data/lib/webby/core_ext/hash.rb +28 -0
  110. data/lib/webby/core_ext/kernel.rb +21 -0
  111. data/lib/webby/core_ext/string.rb +163 -0
  112. data/lib/webby/core_ext/time.rb +9 -0
  113. data/lib/webby/filters.rb +91 -0
  114. data/lib/webby/filters/basepath.rb +97 -0
  115. data/lib/webby/filters/erb.rb +9 -0
  116. data/lib/webby/filters/haml.rb +18 -0
  117. data/lib/webby/filters/markdown.rb +16 -0
  118. data/lib/webby/filters/outline.rb +308 -0
  119. data/lib/webby/filters/sass.rb +17 -0
  120. data/lib/webby/filters/slides.rb +56 -0
  121. data/lib/webby/filters/textile.rb +16 -0
  122. data/lib/webby/filters/tidy.rb +76 -0
  123. data/lib/webby/helpers.rb +30 -0
  124. data/lib/webby/helpers/capture_helper.rb +141 -0
  125. data/lib/webby/helpers/coderay_helper.rb +69 -0
  126. data/lib/webby/helpers/graphviz_helper.rb +136 -0
  127. data/lib/webby/helpers/tag_helper.rb +65 -0
  128. data/lib/webby/helpers/tex_img_helper.rb +133 -0
  129. data/lib/webby/helpers/ultraviolet_helper.rb +63 -0
  130. data/lib/webby/helpers/url_helper.rb +235 -0
  131. data/lib/webby/link_validator.rb +152 -0
  132. data/lib/webby/renderer.rb +379 -0
  133. data/lib/webby/resources.rb +96 -0
  134. data/lib/webby/resources/db.rb +251 -0
  135. data/lib/webby/resources/file.rb +221 -0
  136. data/lib/webby/resources/layout.rb +63 -0
  137. data/lib/webby/resources/page.rb +118 -0
  138. data/lib/webby/resources/partial.rb +79 -0
  139. data/lib/webby/resources/resource.rb +160 -0
  140. data/lib/webby/resources/static.rb +52 -0
  141. data/lib/webby/stelan/mktemp.rb +135 -0
  142. data/lib/webby/stelan/paginator.rb +150 -0
  143. data/lib/webby/stelan/spawner.rb +339 -0
  144. data/lib/webby/tasks/build.rake +27 -0
  145. data/lib/webby/tasks/create.rake +22 -0
  146. data/lib/webby/tasks/deploy.rake +22 -0
  147. data/lib/webby/tasks/growl.rake +15 -0
  148. data/lib/webby/tasks/heel.rake +28 -0
  149. data/lib/webby/tasks/validate.rake +19 -0
  150. data/spec/core_ext/hash_spec.rb +47 -0
  151. data/spec/core_ext/string_spec.rb +110 -0
  152. data/spec/core_ext/time_spec.rb +19 -0
  153. data/spec/spec.opts +1 -0
  154. data/spec/spec_helper.rb +14 -0
  155. data/spec/webby/apps/generator_spec.rb +111 -0
  156. data/spec/webby/apps/main_spec.rb +75 -0
  157. data/spec/webby/helpers/capture_helper_spec.rb +56 -0
  158. data/spec/webby/resources/file_spec.rb +104 -0
  159. data/spec/webby/resources_spec.rb +17 -0
  160. data/tasks/ann.rake +81 -0
  161. data/tasks/bones.rake +21 -0
  162. data/tasks/gem.rake +126 -0
  163. data/tasks/git.rake +41 -0
  164. data/tasks/manifest.rake +49 -0
  165. data/tasks/notes.rake +28 -0
  166. data/tasks/post_load.rake +39 -0
  167. data/tasks/rdoc.rake +51 -0
  168. data/tasks/rubyforge.rake +57 -0
  169. data/tasks/setup.rb +268 -0
  170. data/tasks/spec.rake +55 -0
  171. data/tasks/website.rake +38 -0
  172. metadata +287 -0
@@ -0,0 +1,83 @@
1
+ require 'directory_watcher'
2
+
3
+ module Webby
4
+
5
+ # The AutoBuilder class is used to monitor the content and layouts folders
6
+ # and to compile the resource files only when they are modified. If a
7
+ # layout is modified, then all resources that depend upon the layout are
8
+ # compiled.
9
+ #
10
+ class AutoBuilder
11
+
12
+ # TODO: hit Ctrl-C once to rebuild everything, and hit it twice to stop the autobuild loop
13
+
14
+ # call-seq:
15
+ # AutoBuilder.run
16
+ #
17
+ # Creates a new AutoBuilder and sets it running. This method will only
18
+ # return when the user presses Ctrl-C.
19
+ #
20
+ def self.run
21
+ self.new.run
22
+ end
23
+
24
+ # call-seq:
25
+ # AutoBuilder.new
26
+ #
27
+ # Create a new AutoBuilder class.
28
+ #
29
+ def initialize
30
+ @log = Logging::Logger[self]
31
+
32
+ @builder = Builder.new
33
+ @watcher = DirectoryWatcher.new '.', :interval => 2
34
+ @watcher.add_observer self
35
+
36
+ glob = []
37
+ glob << File.join(::Webby.site.layout_dir, '**', '*')
38
+ glob << File.join(::Webby.site.content_dir, '**', '*')
39
+ @watcher.glob = glob
40
+ end
41
+
42
+ # call-seq:
43
+ # update( *events )
44
+ #
45
+ # The update method is called by the DirectoryWatcher when files have been
46
+ # modified, added, or deleted. An array of events is passed to his method,
47
+ # and each event contains the event type and the path to the file.
48
+ #
49
+ def update( *events )
50
+ ary = events.find_all {|evt| evt.type != :removed}
51
+ return if ary.empty?
52
+
53
+ ary.each do |evt|
54
+ @log.debug "changed #{evt.path}"
55
+ next unless test ?f, evt.path
56
+ next if evt.path =~ ::Webby.exclude
57
+ Resources.new evt.path
58
+ end
59
+
60
+ @builder.run :load_files => false
61
+ rescue => err
62
+ @log.error err
63
+ end
64
+
65
+ # call-seq:
66
+ # run
67
+ #
68
+ # Starts the DirectoryWatcher running and waits till the user presses
69
+ # Ctrl-C to stop the watcher thread.
70
+ #
71
+ def run
72
+ @log.info 'starting autobuild (Ctrl-C to stop)'
73
+
74
+ Signal.trap('INT') {@watcher.stop}
75
+
76
+ @watcher.start
77
+ @watcher.join
78
+ end
79
+
80
+ end # class AutoBuilder
81
+ end # module Webby
82
+
83
+ # EOF
@@ -0,0 +1,183 @@
1
+ require 'find'
2
+ require 'fileutils'
3
+ require 'erb'
4
+
5
+ module Webby
6
+
7
+ # The Builder class performs the work of scanning the content folder,
8
+ # creating Resource objects, and converting / copying the contents to the
9
+ # output folder as needed.
10
+ #
11
+ class Builder
12
+
13
+ class << self
14
+
15
+ # call-seq:
16
+ # Builder.run( :rebuild => false )
17
+ #
18
+ # Create a new instance of the Builder class and invoke the run method.
19
+ # If the <code>:rebuild</code> option is given as +true+, then all pages
20
+ # will be recreated / copied.
21
+ #
22
+ def run( opts = {} )
23
+ self.new.run opts
24
+ end
25
+
26
+ # call-seq:
27
+ # Builder.create( page, :from => template, :locals => {} )
28
+ #
29
+ # This mehod is used to create a new _page_ in the content folder based
30
+ # on the specified template. _page_ is the relative path to the new page
31
+ # from the <code>content/</code> folder. The _template_ is the name of
32
+ # the template to use from the <code>templates/</code> folder.
33
+ #
34
+ def create( page, opts = {} )
35
+ tmpl = opts[:from]
36
+ raise Error, "template not given" unless tmpl
37
+
38
+ name = ::Webby::Resources::File.basename(page)
39
+ ext = ::Webby::Resources::File.extname(page)
40
+ dir = ::File.dirname(page)
41
+ dir = '' if dir == '.'
42
+
43
+ if tmpl.pathmap('%n') =~ %r/^_/
44
+ page = ::File.join(::Webby.site.content_dir, dir, '_'+name)
45
+ page << '.' << (ext.empty? ? 'txt' : ext)
46
+ elsif ::Webby.site.create_mode == 'directory' and name != 'index'
47
+ page = ::File.join(::Webby.site.content_dir, dir, name, 'index')
48
+ page << '.' << (ext.empty? ? 'txt' : ext)
49
+ else
50
+ page = ::File.join(::Webby.site.content_dir, page)
51
+ page << '.txt' if ext.empty?
52
+ end
53
+ raise Error, "#{page} already exists" if test ?e, page
54
+
55
+ Logging::Logger[self].info "creating #{page}"
56
+ FileUtils.mkdir_p ::File.dirname(page)
57
+
58
+ context = scope
59
+ opts[:locals].each do |k,v|
60
+ Thread.current[:value] = v
61
+ definition = "#{k} = Thread.current[:value]"
62
+ eval(definition, context)
63
+ end if opts.has_key?(:locals)
64
+
65
+ str = ERB.new(::File.read(tmpl), nil, '-').result(context)
66
+ ::File.open(page, 'w') {|fd| fd.write str}
67
+
68
+ page
69
+ end
70
+
71
+ # call-seq:
72
+ # Builder.new_page_info => [page, title, directory]
73
+ #
74
+ def new_page_info
75
+ args = Webby.site.args
76
+
77
+ if args.raw.empty?
78
+ task_name = Rake.application.top_level_tasks.first
79
+ raise "Usage: webby #{task_name} path"
80
+ end
81
+
82
+ [args.page, args.title, args.dir]
83
+ end
84
+
85
+
86
+ private
87
+
88
+ # Returns the binding in the scope of the Builder class object.
89
+ #
90
+ def scope() binding end
91
+
92
+ end # class << self
93
+
94
+ # call-seq:
95
+ # Builder.new
96
+ #
97
+ # Creates a new Builder object for creating pages from the content and
98
+ # layout directories.
99
+ #
100
+ def initialize
101
+ @log = Logging::Logger[self]
102
+ end
103
+
104
+ # call-seq:
105
+ # run( :rebuild => false, :load_files => true )
106
+ #
107
+ # Runs the Webby builder by loading in the layout files from the
108
+ # <code>layouts/</code> folder and the content from the
109
+ # <code>contents/</code> folder. Content is analyzed, and those that need
110
+ # to be copied or compiled (filtered using ERB, Texttile, Markdown, etc.)
111
+ # are handled. The results are placed in the <code>output/</code> folder.
112
+ #
113
+ # If the <code>:rebuild</code> flag is set to +true+, then all content is
114
+ # copied and/or compiled to the output folder.
115
+ #
116
+ # A content file can mark itself as dirty by setting the +dirty+ flag to
117
+ # +true+ in the meta-data of the file. This will cause the contenet to
118
+ # always be compiled when the builder is run. Conversely, setting the
119
+ # dirty flag to +false+ will cause the content to never be compiled or
120
+ # copied to the output folder.
121
+ #
122
+ # A content file needs to be built if the age of the file is less then the
123
+ # age of the output product -- i.e. the content file has been modified
124
+ # more recently than the output file.
125
+ #
126
+ def run( opts = {} )
127
+ opts[:load_files] = true unless opts.has_key?(:load_files)
128
+
129
+ unless test(?d, output_dir)
130
+ @log.info "creating #{output_dir}"
131
+ FileUtils.mkdir output_dir
132
+ end
133
+
134
+ load_files if opts[:load_files]
135
+
136
+ Resources.pages.each do |page|
137
+ next unless page.dirty? or opts[:rebuild]
138
+
139
+ @log.info "creating #{page.destination}"
140
+
141
+ # make sure the directory exists
142
+ FileUtils.mkdir_p ::File.dirname(page.destination)
143
+
144
+ # copy the resource to the output directory if it is static
145
+ if page.instance_of? Resources::Static
146
+ FileUtils.cp page.path, page.destination
147
+ FileUtils.chmod 0644, page.destination
148
+
149
+ # otherwise, layout the resource and write the results to
150
+ # the output directory
151
+ else Renderer.write(page) end
152
+ end
153
+
154
+ # touch the cairn so we know when the website was last generated
155
+ FileUtils.touch ::Webby.cairn
156
+
157
+ nil
158
+ end
159
+
160
+
161
+ private
162
+
163
+ # Scan the <code>layouts/</code> folder and the <code>content/</code>
164
+ # folder and create a new Resource object for each file found there.
165
+ #
166
+ def load_files
167
+ ::Find.find(layout_dir, content_dir) do |path|
168
+ next unless test ?f, path
169
+ next if path =~ ::Webby.exclude
170
+ Resources.new path
171
+ end
172
+ end
173
+
174
+ %w(output_dir layout_dir content_dir).each do |key|
175
+ self.class_eval <<-CODE
176
+ def #{key}( ) ::Webby.site.#{key} end
177
+ CODE
178
+ end
179
+
180
+ end # class Builder
181
+ end # module Webby
182
+
183
+ # EOF
@@ -0,0 +1,11 @@
1
+
2
+ module Enumerable
3
+
4
+ def injecting( initial )
5
+ inject(initial) do |memo, obj|
6
+ yield(memo, obj); memo
7
+ end
8
+ end
9
+ end # module Enumerable
10
+
11
+ # EOF
@@ -0,0 +1,28 @@
1
+
2
+ class Hash
3
+
4
+ def sanitize!
5
+ h = self.injecting({}) do |h, (k, v)|
6
+ h[k] = case v
7
+ when 'none', 'nil'; nil
8
+ when 'true', 'yes'; true
9
+ when 'false', 'no'; false
10
+ else v end
11
+ end
12
+ self.replace h
13
+ end
14
+
15
+ def stringify_keys
16
+ h = {}
17
+ self.each {|k,v| h[k.to_s] = v}
18
+ return h
19
+ end
20
+
21
+ def symbolize_keys
22
+ h = {}
23
+ self.each {|k,v| h[k.to_sym] = v}
24
+ return h
25
+ end
26
+ end # class Hash
27
+
28
+ # EOF
@@ -0,0 +1,21 @@
1
+
2
+ module Kernel
3
+
4
+ # :stopdoc:
5
+ WINDOWS = %r/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM
6
+ DEV_NULL = WINDOWS ? 'NUL:' : '/dev/null'
7
+ # :startdoc:
8
+
9
+ def cmd_available?( *args )
10
+ io = [STDOUT.dup, STDERR.dup]
11
+ STDOUT.reopen DEV_NULL
12
+ STDERR.reopen DEV_NULL
13
+ system(*(args.flatten))
14
+ ensure
15
+ STDOUT.reopen io.first
16
+ STDERR.reopen io.last
17
+ $stdout, $stderr = STDOUT, STDERR
18
+ end
19
+ end # module Kernel
20
+
21
+ # EOF
@@ -0,0 +1,163 @@
1
+
2
+ class String
3
+
4
+ def self.small_words
5
+ @small_words ||= %w(a an and as at but by en for if in of on or the to v[.]? via vs[.]?)
6
+ end
7
+
8
+ def /( path )
9
+ ::File.join(self, path)
10
+ end
11
+
12
+ def titlecase
13
+ swrgxp = self.class.small_words.join('|')
14
+
15
+ parts = self.split( %r/( [:.;?!][ ] | (?:[ ]|^)["“] )/x )
16
+ parts.each do |part|
17
+ part.gsub!(%r/\b[[:alpha:]][[:lower:].'’]*\b/) do |s|
18
+ s =~ %r/\w+\.\w+/ ? s : s.capitalize
19
+ end
20
+
21
+ # Lowercase the small words
22
+ part.gsub!(%r/\b(#{swrgxp})\b/i) {|w| w.downcase}
23
+
24
+ # If the first word is a small word, then capitalize it
25
+ part.gsub!(%r/\A([[:punct:]]*)(#{swrgxp})\b/) {$1 + $2.capitalize}
26
+
27
+ # If the last word is a small word, then capitalize it
28
+ part.gsub!(%r/\b(#{swrgxp})([^\w\s]*)\z/) {$1.capitalize + $2}
29
+ end
30
+
31
+ str = parts.join
32
+
33
+ # Special cases:
34
+ str.gsub!(/ V(s?)\. /, ' v\1. ') # "v." and "vs."
35
+ str.gsub!(/(['’])S\b/, '\1s') # 'S (otherwise you get "the SEC'S decision")
36
+ str.gsub!(/\b(AT&T|Q&A)\b/i) { |w| w.upcase } # "AT&T" and "Q&A", which get tripped up.
37
+
38
+ str
39
+ end
40
+
41
+ # Borrowed from the excellent StringEx library: git://github.com/rsl/stringex.git
42
+
43
+ # Create a URI-friendly representation of the string.
44
+ def to_url
45
+ remove_formatting.downcase.replace_whitespace("-").collapse("-")
46
+ end
47
+
48
+ # Performs multiple text manipulations. Essentially a shortcut for typing them all. View source
49
+ # below to see which methods are run.
50
+ def remove_formatting
51
+ strip_html_tags.convert_accented_entities.convert_misc_entities.convert_misc_characters.collapse
52
+ end
53
+
54
+ # Removes HTML tags from text. This code is simplified from Tobias Luettke's regular expression
55
+ # in Typo[http://typosphere.org].
56
+ def strip_html_tags(leave_whitespace = false)
57
+ name = /[\w:_-]+/
58
+ value = /([A-Za-z0-9]+|('[^']*?'|"[^"]*?"))/
59
+ attr = /(#{name}(\s*=\s*#{value})?)/
60
+ rx = /<[!\/?\[]?(#{name}|--)(\s+(#{attr}(\s+#{attr})*))?\s*([!\/?\]]+|--)?>/
61
+ (leave_whitespace) ? gsub(rx, "").strip : gsub(rx, "").gsub(/\s+/, " ").strip
62
+ end
63
+
64
+ # Converts HTML entities into the respective non-accented letters. Examples:
65
+ #
66
+ # "&aacute;".convert_accented_entities # => "a"
67
+ # "&ccedil;".convert_accented_entities # => "c"
68
+ # "&egrave;".convert_accented_entities # => "e"
69
+ # "&icirc;".convert_accented_entities # => "i"
70
+ # "&oslash;".convert_accented_entities # => "o"
71
+ # "&uuml;".convert_accented_entities # => "u"
72
+ #
73
+ # Note: This does not do any conversion of Unicode/Ascii accented-characters. For that
74
+ # functionality please use <tt>to_ascii</tt>.
75
+ def convert_accented_entities
76
+ gsub(/&([A-Za-z])(grave|acute|circ|tilde|uml|ring|cedil|slash);/, '\1')
77
+ end
78
+
79
+ # Converts HTML entities (taken from common Textile/RedCloth formattings) into plain text formats.
80
+ #
81
+ # Note: This isn't an attempt at complete conversion of HTML entities, just those most likely
82
+ # to be generated by Textile.
83
+ def convert_misc_entities
84
+ dummy = dup
85
+ {
86
+ "#822[01]" => "\"",
87
+ "#821[67]" => "'",
88
+ "#8230" => "...",
89
+ "#8211" => "-",
90
+ "#8212" => "--",
91
+ "#215" => "x",
92
+ "gt" => ">",
93
+ "lt" => "<",
94
+ "(#8482|trade)" => "(tm)",
95
+ "(#174|reg)" => "(r)",
96
+ "(#169|copy)" => "(c)",
97
+ "(#38|amp)" => "and",
98
+ "nbsp" => " ",
99
+ "(#162|cent)" => " cent",
100
+ "(#163|pound)" => " pound",
101
+ "(#188|frac14)" => "one fourth",
102
+ "(#189|frac12)" => "half",
103
+ "(#190|frac34)" => "three fourths",
104
+ "(#176|deg)" => " degrees"
105
+ }.each do |textiled, normal|
106
+ dummy.gsub!(/&#{textiled};/, normal)
107
+ end
108
+ dummy.gsub(/&[^;]+;/, "")
109
+ end
110
+
111
+ # Converts various common plaintext characters to a more URI-friendly representation.
112
+ # Examples:
113
+ #
114
+ # "foo & bar".convert_misc_characters # => "foo and bar"
115
+ # "Chanel #9".convert_misc_characters # => "Chanel number nine"
116
+ # "user@host".convert_misc_characters # => "user at host"
117
+ # "google.com".convert_misc_characters # => "google dot com"
118
+ # "$10".convert_misc_characters # => "10 dollars"
119
+ # "*69".convert_misc_characters # => "star 69"
120
+ # "100%".convert_misc_characters # => "100 percent"
121
+ # "windows/mac/linux".convert_misc_characters # => "windows slash mac slash linux"
122
+ #
123
+ # Note: Because this method will convert any & symbols to the string "and",
124
+ # you should run any methods which convert HTML entities (convert_html_entities and convert_misc_entities)
125
+ # before running this method.
126
+ def convert_misc_characters
127
+ dummy = dup.gsub(/\.{3,}/, " dot dot dot ") # Catch ellipses before single dot rule!
128
+ {
129
+ /\s*&\s*/ => "and",
130
+ /\s*#/ => "number",
131
+ /\s*@\s*/ => "at",
132
+ /(\S|^)\.(\S)/ => '\1 dot \2',
133
+ /(\s|^)\$(\d*)(\s|$)/ => '\2 dollars',
134
+ /\s*\*\s*/ => "star",
135
+ /\s*%\s*/ => "percent",
136
+ /\s*(\\|\/)\s*/ => "slash",
137
+ }.each do |found, replaced|
138
+ replaced = " #{replaced} " unless replaced =~ /\\1/
139
+ dummy.gsub!(found, replaced)
140
+ end
141
+ dummy = dummy.gsub(/(^|\w)'(\w|$)/, '\1\2').gsub(/[\.,:;()\[\]\/\?!\^'"_]/, " ")
142
+ end
143
+
144
+ # Replace runs of whitespace in string. Defaults to a single space but any replacement
145
+ # string may be specified as an argument. Examples:
146
+ #
147
+ # "Foo bar".replace_whitespace # => "Foo bar"
148
+ # "Foo bar".replace_whitespace("-") # => "Foo-bar"
149
+ def replace_whitespace(replace = " ")
150
+ gsub(/\s+/, replace)
151
+ end
152
+
153
+ # Removes specified character from the beginning and/or end of the string and then performs
154
+ # <tt>String#squeeze(character)</tt>, condensing runs of the character within the string.
155
+ #
156
+ # Note: This method has been superceded by ActiveSupport's squish method.
157
+ def collapse(character = " ")
158
+ sub(/^#{character}*/, "").sub(/#{character}*$/, "").squeeze(character)
159
+ end
160
+
161
+ end # class String
162
+
163
+ # EOF