creationix-milk 0.0.9

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 (88) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/LICENSE +27 -0
  4. data/README.rdoc +7 -0
  5. data/Rakefile +62 -0
  6. data/VERSION +1 -0
  7. data/bin/milk +22 -0
  8. data/lib/milk.rb +82 -0
  9. data/lib/milk/application.rb +227 -0
  10. data/lib/milk/component.rb +142 -0
  11. data/lib/milk/field.rb +60 -0
  12. data/lib/milk/fields/component.haml +8 -0
  13. data/lib/milk/fields/component_array.haml +3 -0
  14. data/lib/milk/fields/component_array.rb +4 -0
  15. data/lib/milk/fields/image_chooser.haml +7 -0
  16. data/lib/milk/fields/image_chooser.rb +26 -0
  17. data/lib/milk/fields/login.haml +90 -0
  18. data/lib/milk/fields/markdown_field.haml +1 -0
  19. data/lib/milk/fields/markdown_field.rb +4 -0
  20. data/lib/milk/fields/page_chooser.haml +3 -0
  21. data/lib/milk/fields/page_chooser.rb +17 -0
  22. data/lib/milk/fields/sprite_chooser.haml +6 -0
  23. data/lib/milk/fields/sprite_chooser.rb +19 -0
  24. data/lib/milk/fields/text_field.haml +1 -0
  25. data/lib/milk/fields/text_field.rb +4 -0
  26. data/lib/milk/fields/xhtml.haml +253 -0
  27. data/lib/milk/haxe.rb +139 -0
  28. data/lib/milk/page.rb +139 -0
  29. data/lib/milk/tasks.rb +51 -0
  30. data/milk.gemspec +137 -0
  31. data/site_template/Rakefile +3 -0
  32. data/site_template/config.ru +16 -0
  33. data/site_template/config/config.yaml +31 -0
  34. data/site_template/config/foot.yaml +3 -0
  35. data/site_template/config/head.yaml +18 -0
  36. data/site_template/config/users.yaml +18 -0
  37. data/site_template/design/0-reset.sass +170 -0
  38. data/site_template/design/1-text.sass +65 -0
  39. data/site_template/design/960.sass +305 -0
  40. data/site_template/design/body.haml +1 -0
  41. data/site_template/design/body.rb +4 -0
  42. data/site_template/design/button.haml +2 -0
  43. data/site_template/design/button.rb +16 -0
  44. data/site_template/design/foot.haml +1 -0
  45. data/site_template/design/foot.rb +4 -0
  46. data/site_template/design/foot.sass +3 -0
  47. data/site_template/design/head.haml +3 -0
  48. data/site_template/design/head.rb +4 -0
  49. data/site_template/design/head.sass +27 -0
  50. data/site_template/design/page.haml +2 -0
  51. data/site_template/design/page.sass +7 -0
  52. data/site_template/design/sprites.sass +0 -0
  53. data/site_template/design/xhtml.haml +15 -0
  54. data/site_template/pages/About.yaml +14 -0
  55. data/site_template/pages/Home.yaml +14 -0
  56. data/site_template/pages/News.yaml +14 -0
  57. data/site_template/pages/NotFound.yaml +14 -0
  58. data/site_template/pages/Products.yaml +14 -0
  59. data/site_template/public/cache/About/index.html +42 -0
  60. data/site_template/public/cache/Home/index.html +44 -0
  61. data/site_template/public/cache/News/index.html +42 -0
  62. data/site_template/public/cache/Products/index.html +42 -0
  63. data/site_template/public/favicon.ico +0 -0
  64. data/site_template/public/images/README.txt +2 -0
  65. data/site_template/public/js/jquery-1.3.2.min.js +19 -0
  66. data/site_template/public/js/jquery-ui-1.7.2.custom.min.js +34 -0
  67. data/site_template/public/js/jquery.json-1.3.min.js +37 -0
  68. data/site_template/public/robots.txt +0 -0
  69. data/site_template/public/skin/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  70. data/site_template/public/skin/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  71. data/site_template/public/skin/images/ui-bg_flat_10_000000_40x100.png +0 -0
  72. data/site_template/public/skin/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  73. data/site_template/public/skin/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  74. data/site_template/public/skin/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  75. data/site_template/public/skin/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  76. data/site_template/public/skin/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  77. data/site_template/public/skin/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  78. data/site_template/public/skin/images/ui-icons_222222_256x240.png +0 -0
  79. data/site_template/public/skin/images/ui-icons_228ef1_256x240.png +0 -0
  80. data/site_template/public/skin/images/ui-icons_ef8c08_256x240.png +0 -0
  81. data/site_template/public/skin/images/ui-icons_ffd27a_256x240.png +0 -0
  82. data/site_template/public/skin/images/ui-icons_ffffff_256x240.png +0 -0
  83. data/site_template/public/skin/jquery-ui-1.7.2.custom.css +406 -0
  84. data/site_template/public/style/style.css +157 -0
  85. data/site_template/tmp/restart.txt +1 -0
  86. data/test/milk_test.rb +7 -0
  87. data/test/test_helper.rb +10 -0
  88. metadata +170 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .bzr
7
+ .bzrignore
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2009, Tim Caswell
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the Creationix nor the names of its contributors may
15
+ be used to endorse or promote products derived from this software without
16
+ specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = milk
2
+
3
+ Milk is a super simple cms for small sites that want a no nonsense way to edit webpages.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 Tim Caswell. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "milk"
8
+ gem.summary = %Q{Milk is a rack based content management system built for ease of use and simplicity. Milk tastes great with and without cookies.}
9
+ gem.email = "tim@creationix.com"
10
+ gem.homepage = "http://github.com/creationix/milk"
11
+ gem.authors = ["Tim Caswell"]
12
+ gem.rubyforge_project = "milk"
13
+ gem.required_ruby_version = '>=1.9'
14
+ gem.add_dependency('rack', '>= 1.0.0')
15
+ gem.add_dependency('maruku', '>= 0.6.0')
16
+ gem.add_dependency('haml', '>= 2.0.9')
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+
20
+ Jeweler::RubyforgeTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |test|
35
+ test.libs << 'test'
36
+ test.pattern = 'test/**/*_test.rb'
37
+ test.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
42
+ end
43
+ end
44
+
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ if File.exist?('VERSION.yml')
51
+ config = YAML.load(File.read('VERSION.yml'))
52
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
53
+ else
54
+ version = ""
55
+ end
56
+
57
+ rdoc.rdoc_dir = 'rdoc'
58
+ rdoc.title = "milk #{version}"
59
+ rdoc.rdoc_files.include('README*')
60
+ rdoc.rdoc_files.include('lib/**/*.rb')
61
+ end
62
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.9
data/bin/milk ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+
5
+ if ARGV.length != 1
6
+ puts "\nMilk site generator\n"
7
+ puts "\nUsage:\n\tmilk [target project]\n\n"
8
+ exit 1
9
+ end
10
+
11
+ target_path = File.absolute_path(ARGV[0])
12
+ if File.directory?(target_path)
13
+ puts "\nERROR: there already exists a folder #{target_path}\n"
14
+ exit 1
15
+ end
16
+
17
+ puts "\nCreating milk site at #{target_path}\n"
18
+ FileUtils.mkdir_p(target_path, :verbose => true)
19
+ MILK_ROOT = target_path
20
+ require 'milk'
21
+ FileUtils.cp_r(Milk::TEMPLATE_DIR+"/.", target_path, :verbose => true)
22
+ puts "\nDone!\n\n"
data/lib/milk.rb ADDED
@@ -0,0 +1,82 @@
1
+ # Autoload some useful libraries
2
+ autoload 'Haml', 'haml'
3
+ autoload 'Sass', 'sass'
4
+ autoload 'Maruku', 'maruku'
5
+ autoload 'YAML', 'yaml'
6
+ autoload 'FileUtils', 'fileutils'
7
+
8
+ # Set up our main namespace with autoloaded parts
9
+ module Milk
10
+ VERSION = '0.0.5'
11
+
12
+ LIB_DIR = File.dirname(__FILE__)
13
+ BIN_DIR ||= File.absolute_path(File.dirname(__FILE__)+"/../../local/bin")
14
+ TEMPLATE_DIR = File.absolute_path(LIB_DIR+"/../site_template")
15
+ def self.get_milk_root
16
+ c = caller(1).collect { |line| line.split(':').first }
17
+ c = c.select { |line| line !~ /\/gems\// }
18
+ File.absolute_path(File.dirname(c.first))
19
+ end
20
+ MILK_ROOT ||= get_milk_root
21
+ CONFIG_DIR = MILK_ROOT + "/config"
22
+
23
+ # Load overrides from config file
24
+ config_file = CONFIG_DIR+"/config.yaml"
25
+ YAML.load(open(CONFIG_DIR+"/config.yaml")).each_pair do |key, value|
26
+ eval("#{key} = #{value.inspect}")
27
+ end if File.file?(config_file)
28
+
29
+ # Set defaults otherwise
30
+ COMPONENTS_DIR ||= MILK_ROOT + "/design"
31
+ PAGES_DIR ||= MILK_ROOT + "/pages"
32
+ PUBLIC_DIR ||= MILK_ROOT + "/public"
33
+ FIELDS_DIR ||= LIB_DIR + "/milk/fields"
34
+
35
+ $LOAD_PATH.unshift(LIB_DIR) unless $LOAD_PATH.include?(LIB_DIR)
36
+ autoload :Application, "milk/application"
37
+ autoload :Component, "milk/component"
38
+ autoload :Page, "milk/page"
39
+ autoload :Haxe, "milk/haxe"
40
+ autoload :Field, "milk/field"
41
+ autoload :Fields, "milk/field"
42
+
43
+ end
44
+
45
+ # Autoload the components of the user space app into the root namespace for easy use
46
+ Dir.glob(Milk::COMPONENTS_DIR + "/*.rb").each do |c|
47
+ name = c.split('/').last.gsub(/(.*)\.rb/) { $1 }
48
+ class_name = name.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
49
+ path = c.gsub(/(.*)\.rb/) { $1 }
50
+ autoload class_name.to_sym, path
51
+ end
52
+
53
+ # Include metaid for easy metaclass management
54
+ class Object
55
+ # The hidden singleton lurks behind everyone
56
+ def metaclass; class << self; self; end; end
57
+ def meta_eval &blk; metaclass.instance_eval &blk; end
58
+
59
+ # Adds methods to a metaclass
60
+ def meta_def name, &blk
61
+ meta_eval { define_method name, &blk }
62
+ end
63
+
64
+ # Defines an instance method within a class
65
+ def class_def name, &blk
66
+ class_eval { define_method name, &blk }
67
+ end
68
+ end
69
+
70
+ # Add ability to get constants from a string name.
71
+ # WARNING: this is akin to eval for security concerns.
72
+ class String
73
+ def constantize
74
+ const = Object
75
+ self.split("::").each do |part|
76
+ const = const.const_get(part)
77
+ end
78
+ const
79
+ end
80
+ end
81
+
82
+
@@ -0,0 +1,227 @@
1
+ module Milk
2
+ class Application
3
+
4
+ PAGE_PATH_REGEX = /^\/([a-zA-Z0-9_]+(\/[a-zA-Z0-9_]+)*)+\/*$/
5
+ EDIT_PATH_REGEX = /^\/([a-zA-Z0-9_]+(\/[a-zA-Z0-9_]+)*)+\/edit\/*$/
6
+
7
+ attr_reader :req
8
+
9
+ def initialize(require_ssl=false)
10
+ @require_ssl = require_ssl
11
+ end
12
+
13
+ def route
14
+ path = @req.path_info
15
+
16
+ if path == '/'
17
+ # Special case for root
18
+ path = '/Home'
19
+ end
20
+
21
+ # Fallback to match everything
22
+ regex = /(.*)/
23
+
24
+ # Route the request to the right callback
25
+ https = @req.env['HTTPS'] == 'on'
26
+ action = case
27
+ when @req.get?
28
+ case
29
+ when path == "/logout"
30
+ :logout
31
+ when path =~ EDIT_PATH_REGEX
32
+ regex = EDIT_PATH_REGEX
33
+ if @require_ssl && !https
34
+ :https_redirect
35
+ else
36
+ :edit
37
+ end
38
+ when path =~ PAGE_PATH_REGEX
39
+ regex = PAGE_PATH_REGEX
40
+ :view
41
+ end
42
+ when @req.delete?
43
+ if path =~ PAGE_PATH_REGEX
44
+ regex = PAGE_PATH_REGEX
45
+ :delete
46
+ end
47
+ when @req.post?
48
+ if path == '/login'
49
+ :login
50
+ elsif path =~ PAGE_PATH_REGEX
51
+ regex = PAGE_PATH_REGEX
52
+ :preview
53
+ end
54
+ when @req.put?
55
+ if path =~ PAGE_PATH_REGEX
56
+ regex = PAGE_PATH_REGEX
57
+ :save
58
+ end
59
+ end || :not_found
60
+
61
+ page_name = regex.match(path)[1]
62
+
63
+ if (action == :view || action == :edit)
64
+ begin
65
+ page = Milk::Page.find(page_name)
66
+ rescue Milk::PageNotFoundError
67
+ action = :not_found
68
+ end
69
+ end
70
+
71
+ if (action == :preview || action == :save)
72
+ page = Milk::Page.json_unserialize(YAML.load(@req.body.read), page_name)
73
+ end
74
+
75
+ if !@user && [:edit, :save, :delete].include?(action)
76
+ action = :login_form
77
+ end
78
+
79
+ return action, page_name, page
80
+ end
81
+
82
+ def obfuscate(value)
83
+ require 'base64'
84
+ len = Milk::SECRET.length
85
+ result = (0...value.length).collect { |i| value[i].ord ^ Milk::SECRET[i%len].ord }
86
+ Base64.encode64(result.pack("C*"))
87
+ end
88
+
89
+ def decode(code)
90
+ require 'base64'
91
+ len = Milk::SECRET.length
92
+ value = Base64.decode64(code)
93
+ result = (0...value.length).collect { |i| value[i].ord ^ Milk::SECRET[i%len].ord }
94
+ result.pack("C*")
95
+ end
96
+
97
+ def hash(email, password)
98
+ require 'digest/md5'
99
+ Digest::MD5.hexdigest("#{password}")
100
+ end
101
+
102
+ def logout()
103
+ @resp.delete_cookie('auth', :path => "/")
104
+ @resp.redirect(@req.params['dest'])
105
+ end
106
+
107
+ def flash(message=nil)
108
+ @resp.delete_cookie('flash', :path => "/") unless message
109
+ @resp.set_cookie('flash', :path => "/", :value => message) if message
110
+ @req.cookies['flash']
111
+ end
112
+
113
+ def login()
114
+ email = @req.params['email']
115
+ if email.length > 0
116
+ user = users[email]
117
+ if user
118
+ expected = user["hash"]
119
+ actual = hash(email, @req.params['password'])
120
+ if actual == expected
121
+ @resp.set_cookie('auth', :path => "/", :value => obfuscate(email), :secure=>@require_ssl, :httponly=>true)
122
+ else
123
+ flash "Incorrect password for user #{email}"
124
+ end
125
+ else
126
+ flash "User #{email} not found"
127
+ end
128
+ else
129
+ flash "Please enter user email and password"
130
+ end
131
+ @resp.redirect(@req.params['dest'])
132
+ end
133
+
134
+ def users
135
+ users_file = Milk::CONFIG_DIR+"/users.yaml"
136
+ YAML.load(open(users_file).read)
137
+ end
138
+
139
+ def load_user
140
+ @user = nil
141
+ if current = @req.cookies['auth']
142
+ email = decode(current)
143
+ @user = users[email]
144
+ @resp.delete_cookie('auth', :path => "/") unless @user
145
+ end
146
+ end
147
+
148
+ # Rack call interface
149
+ def call(env)
150
+ @req = Rack::Request.new(env)
151
+ @resp = Rack::Response.new
152
+ load_user
153
+
154
+ # Route the request
155
+ action, page_name, @page = route
156
+
157
+ # Send proper mime types for browsers that claim to accept it
158
+ @resp["Content-Type"] =
159
+ if env['HTTP_ACCEPT'].include? "application/xhtml+xml"
160
+ "application/xhtml+xml"
161
+ "text/html"
162
+ else
163
+ "text/html"
164
+ end
165
+
166
+ case action
167
+ when :not_found
168
+ @resp.status = 404
169
+ page = Milk::Page.find('NotFound')
170
+ Milk::Application.join_tree(page, self)
171
+ @resp.write page.view
172
+ when :view
173
+ Milk::Application.join_tree(@page, self)
174
+ html = @page.view
175
+ @page.save_to_cache(html) if Milk::USE_CACHE
176
+ @resp.write html
177
+ when :https_redirect
178
+ @resp.redirect('https://' + @req.host + @req.fullpath)
179
+ when :http_redirect
180
+ @resp.redirect('http://' + @req.host + @req.fullpath)
181
+ when :edit
182
+ Milk::Application.join_tree(@page, self)
183
+ @resp.write @page.edit
184
+ when :save
185
+ Milk::Application.join_tree(@page, self)
186
+ @resp.write @page.save
187
+ when :preview
188
+ Milk::Application.join_tree(@page, self)
189
+ @resp.write @page.preview
190
+ when :login_form
191
+ filename = FIELDS_DIR + "/login.haml"
192
+ @resp.write(::Haml::Engine.new(File.read(filename), :filename => filename).render(self))
193
+ when :login
194
+ login
195
+ when :logout
196
+ logout
197
+ when :access_denied
198
+ @resp.staus = 403
199
+ @resp.write "Access Denied"
200
+ else
201
+ @resp.status = 500
202
+ @resp.write action.to_s
203
+ end
204
+ @resp.finish
205
+ end
206
+
207
+ # method that walks an object linking Milk objects to eachother
208
+ def self.join_tree(obj, parent)
209
+ if [Milk::Page, Milk::Component, Milk::Application].any? {|klass| obj.kind_of? klass}
210
+ obj.parent = parent
211
+ obj.instance_variables.each do |name|
212
+ var = obj.instance_variable_get(name)
213
+ if var.class == Array
214
+ var.each do |subvar|
215
+ join_tree(subvar, obj)
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ end
223
+
224
+ class PageNotFoundError < Exception
225
+ end
226
+ end
227
+