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.
- data/History.txt +176 -0
- data/Manifest.txt +171 -0
- data/README.txt +92 -0
- data/Rakefile +54 -0
- data/bin/webby +8 -0
- data/bin/webby-gen +8 -0
- data/examples/blog/Sitefile +7 -0
- data/examples/blog/tasks/blog.rake +72 -0
- data/examples/blog/templates/atom_feed.erb +40 -0
- data/examples/blog/templates/blog/month.erb +22 -0
- data/examples/blog/templates/blog/post.erb +16 -0
- data/examples/blog/templates/blog/year.erb +22 -0
- data/examples/presentation/Sitefile +10 -0
- data/examples/presentation/content/css/uv/twilight.css +137 -0
- data/examples/presentation/content/presentation/_sample_code.txt +10 -0
- data/examples/presentation/content/presentation/index.txt +63 -0
- data/examples/presentation/content/presentation/s5/blank.gif +0 -0
- data/examples/presentation/content/presentation/s5/bodybg.gif +0 -0
- data/examples/presentation/content/presentation/s5/framing.css +23 -0
- data/examples/presentation/content/presentation/s5/iepngfix.htc +42 -0
- data/examples/presentation/content/presentation/s5/opera.css +7 -0
- data/examples/presentation/content/presentation/s5/outline.css +15 -0
- data/examples/presentation/content/presentation/s5/pretty.css +86 -0
- data/examples/presentation/content/presentation/s5/print.css +1 -0
- data/examples/presentation/content/presentation/s5/s5-core.css +9 -0
- data/examples/presentation/content/presentation/s5/slides.css +3 -0
- data/examples/presentation/content/presentation/s5/slides.js +553 -0
- data/examples/presentation/layouts/presentation.txt +43 -0
- data/examples/presentation/templates/_code_partial.erb +13 -0
- data/examples/presentation/templates/presentation.erb +40 -0
- data/examples/tumblog/Sitefile +9 -0
- data/examples/tumblog/content/css/tumblog.css +308 -0
- data/examples/tumblog/content/images/tumblog/permalink.gif +0 -0
- data/examples/tumblog/content/images/tumblog/rss.gif +0 -0
- data/examples/tumblog/content/tumblog/200806/the-noble-chicken/index.txt +12 -0
- data/examples/tumblog/content/tumblog/200807/historical-perspectives-on-the-classic-chicken-joke/index.txt +12 -0
- data/examples/tumblog/content/tumblog/200807/mad-city-chickens/index.txt +10 -0
- data/examples/tumblog/content/tumblog/200807/the-wisdom-of-the-dutch/index.txt +11 -0
- data/examples/tumblog/content/tumblog/200807/up-a-tree/index.txt +13 -0
- data/examples/tumblog/content/tumblog/index.txt +37 -0
- data/examples/tumblog/content/tumblog/rss.txt +37 -0
- data/examples/tumblog/layouts/tumblog/default.txt +44 -0
- data/examples/tumblog/layouts/tumblog/post.txt +15 -0
- data/examples/tumblog/lib/tumblog_helper.rb +32 -0
- data/examples/tumblog/tasks/tumblog.rake +30 -0
- data/examples/tumblog/templates/atom_feed.erb +40 -0
- data/examples/tumblog/templates/tumblog/conversation.erb +12 -0
- data/examples/tumblog/templates/tumblog/link.erb +10 -0
- data/examples/tumblog/templates/tumblog/photo.erb +13 -0
- data/examples/tumblog/templates/tumblog/post.erb +12 -0
- data/examples/tumblog/templates/tumblog/quote.erb +11 -0
- data/examples/webby/Sitefile +19 -0
- data/examples/webby/content/css/background.gif +0 -0
- data/examples/webby/content/css/blueprint/print.css +76 -0
- data/examples/webby/content/css/blueprint/screen.css +696 -0
- data/examples/webby/content/css/coderay.css +96 -0
- data/examples/webby/content/css/site.css +184 -0
- data/examples/webby/content/css/uv/twilight.css +137 -0
- data/examples/webby/content/index.txt +37 -0
- data/examples/webby/content/manual/index.txt +430 -0
- data/examples/webby/content/reference/index.txt +202 -0
- data/examples/webby/content/release-notes/rel-0-9-0/index.txt +73 -0
- data/examples/webby/content/robots.txt +6 -0
- data/examples/webby/content/script/jquery.corner.js +152 -0
- data/examples/webby/content/script/jquery.js +31 -0
- data/examples/webby/content/sitemap.txt +31 -0
- data/examples/webby/content/tips_and_tricks/index.txt +96 -0
- data/examples/webby/content/tutorial/index.txt +131 -0
- data/examples/webby/layouts/default.txt +55 -0
- data/examples/webby/templates/page.erb +10 -0
- data/examples/website/Sitefile +7 -0
- data/examples/website/content/css/blueprint/License.txt +21 -0
- data/examples/website/content/css/blueprint/Readme.txt +100 -0
- data/examples/website/content/css/blueprint/compressed/print.css +76 -0
- data/examples/website/content/css/blueprint/compressed/screen.css +696 -0
- data/examples/website/content/css/blueprint/lib/forms.css +45 -0
- data/examples/website/content/css/blueprint/lib/grid.css +193 -0
- data/examples/website/content/css/blueprint/lib/grid.png +0 -0
- data/examples/website/content/css/blueprint/lib/ie.css +30 -0
- data/examples/website/content/css/blueprint/lib/reset.css +39 -0
- data/examples/website/content/css/blueprint/lib/typography.css +116 -0
- data/examples/website/content/css/blueprint/plugins/buttons/Readme +31 -0
- data/examples/website/content/css/blueprint/plugins/buttons/buttons.css +97 -0
- data/examples/website/content/css/blueprint/plugins/buttons/icons/cross.png +0 -0
- data/examples/website/content/css/blueprint/plugins/buttons/icons/key.png +0 -0
- data/examples/website/content/css/blueprint/plugins/buttons/icons/tick.png +0 -0
- data/examples/website/content/css/blueprint/plugins/css-classes/Readme +14 -0
- data/examples/website/content/css/blueprint/plugins/css-classes/css-classes.css +24 -0
- data/examples/website/content/css/blueprint/plugins/fancy-type/Readme +22 -0
- data/examples/website/content/css/blueprint/plugins/fancy-type/fancy-type-compressed.css +5 -0
- data/examples/website/content/css/blueprint/plugins/fancy-type/fancy-type.css +74 -0
- data/examples/website/content/css/blueprint/print.css +68 -0
- data/examples/website/content/css/blueprint/screen.css +22 -0
- data/examples/website/content/css/coderay.css +111 -0
- data/examples/website/content/css/site.css +67 -0
- data/examples/website/content/index.txt +19 -0
- data/examples/website/layouts/default.txt +58 -0
- data/examples/website/lib/breadcrumbs.rb +28 -0
- data/examples/website/templates/_partial.erb +10 -0
- data/examples/website/templates/page.erb +18 -0
- data/examples/website/templates/presentation.erb +40 -0
- data/lib/webby.rb +227 -0
- data/lib/webby/apps.rb +12 -0
- data/lib/webby/apps/generator.rb +283 -0
- data/lib/webby/apps/main.rb +221 -0
- data/lib/webby/auto_builder.rb +83 -0
- data/lib/webby/builder.rb +183 -0
- data/lib/webby/core_ext/enumerable.rb +11 -0
- data/lib/webby/core_ext/hash.rb +28 -0
- data/lib/webby/core_ext/kernel.rb +21 -0
- data/lib/webby/core_ext/string.rb +163 -0
- data/lib/webby/core_ext/time.rb +9 -0
- data/lib/webby/filters.rb +91 -0
- data/lib/webby/filters/basepath.rb +97 -0
- data/lib/webby/filters/erb.rb +9 -0
- data/lib/webby/filters/haml.rb +18 -0
- data/lib/webby/filters/markdown.rb +16 -0
- data/lib/webby/filters/outline.rb +308 -0
- data/lib/webby/filters/sass.rb +17 -0
- data/lib/webby/filters/slides.rb +56 -0
- data/lib/webby/filters/textile.rb +16 -0
- data/lib/webby/filters/tidy.rb +76 -0
- data/lib/webby/helpers.rb +30 -0
- data/lib/webby/helpers/capture_helper.rb +141 -0
- data/lib/webby/helpers/coderay_helper.rb +69 -0
- data/lib/webby/helpers/graphviz_helper.rb +136 -0
- data/lib/webby/helpers/tag_helper.rb +65 -0
- data/lib/webby/helpers/tex_img_helper.rb +133 -0
- data/lib/webby/helpers/ultraviolet_helper.rb +63 -0
- data/lib/webby/helpers/url_helper.rb +235 -0
- data/lib/webby/link_validator.rb +152 -0
- data/lib/webby/renderer.rb +379 -0
- data/lib/webby/resources.rb +96 -0
- data/lib/webby/resources/db.rb +251 -0
- data/lib/webby/resources/file.rb +221 -0
- data/lib/webby/resources/layout.rb +63 -0
- data/lib/webby/resources/page.rb +118 -0
- data/lib/webby/resources/partial.rb +79 -0
- data/lib/webby/resources/resource.rb +160 -0
- data/lib/webby/resources/static.rb +52 -0
- data/lib/webby/stelan/mktemp.rb +135 -0
- data/lib/webby/stelan/paginator.rb +150 -0
- data/lib/webby/stelan/spawner.rb +339 -0
- data/lib/webby/tasks/build.rake +27 -0
- data/lib/webby/tasks/create.rake +22 -0
- data/lib/webby/tasks/deploy.rake +22 -0
- data/lib/webby/tasks/growl.rake +15 -0
- data/lib/webby/tasks/heel.rake +28 -0
- data/lib/webby/tasks/validate.rake +19 -0
- data/spec/core_ext/hash_spec.rb +47 -0
- data/spec/core_ext/string_spec.rb +110 -0
- data/spec/core_ext/time_spec.rb +19 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/webby/apps/generator_spec.rb +111 -0
- data/spec/webby/apps/main_spec.rb +75 -0
- data/spec/webby/helpers/capture_helper_spec.rb +56 -0
- data/spec/webby/resources/file_spec.rb +104 -0
- data/spec/webby/resources_spec.rb +17 -0
- data/tasks/ann.rake +81 -0
- data/tasks/bones.rake +21 -0
- data/tasks/gem.rake +126 -0
- data/tasks/git.rake +41 -0
- data/tasks/manifest.rake +49 -0
- data/tasks/notes.rake +28 -0
- data/tasks/post_load.rake +39 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +57 -0
- data/tasks/setup.rb +268 -0
- data/tasks/spec.rake +55 -0
- data/tasks/website.rake +38 -0
- 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,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
|
+
# "á".convert_accented_entities # => "a"
|
67
|
+
# "ç".convert_accented_entities # => "c"
|
68
|
+
# "è".convert_accented_entities # => "e"
|
69
|
+
# "î".convert_accented_entities # => "i"
|
70
|
+
# "ø".convert_accented_entities # => "o"
|
71
|
+
# "ü".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
|