yodel 0.0.1

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 (200) hide show
  1. data/.document +5 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +63 -0
  5. data/LICENSE +1 -0
  6. data/README.rdoc +20 -0
  7. data/Rakefile +1 -0
  8. data/bin/yodel +4 -0
  9. data/lib/yodel.rb +22 -0
  10. data/lib/yodel/application/application.rb +44 -0
  11. data/lib/yodel/application/extension.rb +59 -0
  12. data/lib/yodel/application/request_handler.rb +48 -0
  13. data/lib/yodel/application/yodel.rb +25 -0
  14. data/lib/yodel/command/command.rb +94 -0
  15. data/lib/yodel/command/deploy.rb +67 -0
  16. data/lib/yodel/command/dns_server.rb +16 -0
  17. data/lib/yodel/command/installer.rb +229 -0
  18. data/lib/yodel/config/config.rb +30 -0
  19. data/lib/yodel/config/environment.rb +16 -0
  20. data/lib/yodel/config/yodel.rb +21 -0
  21. data/lib/yodel/exceptions/destroyed_record.rb +2 -0
  22. data/lib/yodel/exceptions/domain_not_found.rb +16 -0
  23. data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
  24. data/lib/yodel/exceptions/exceptions.rb +3 -0
  25. data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
  26. data/lib/yodel/exceptions/invalid_field.rb +2 -0
  27. data/lib/yodel/exceptions/invalid_index.rb +2 -0
  28. data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
  29. data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
  30. data/lib/yodel/exceptions/layout_not_found.rb +2 -0
  31. data/lib/yodel/exceptions/mass_assignment.rb +2 -0
  32. data/lib/yodel/exceptions/missing_migration.rb +2 -0
  33. data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
  34. data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
  35. data/lib/yodel/exceptions/unauthorised.rb +2 -0
  36. data/lib/yodel/exceptions/unknown_field.rb +2 -0
  37. data/lib/yodel/middleware/development_server.rb +180 -0
  38. data/lib/yodel/middleware/error_pages.rb +72 -0
  39. data/lib/yodel/middleware/public_assets.rb +78 -0
  40. data/lib/yodel/middleware/request.rb +16 -0
  41. data/lib/yodel/middleware/site_detector.rb +22 -0
  42. data/lib/yodel/mime_types/default_mime_set.rb +28 -0
  43. data/lib/yodel/mime_types/mime_type.rb +68 -0
  44. data/lib/yodel/mime_types/mime_type_set.rb +41 -0
  45. data/lib/yodel/mime_types/mime_types.rb +6 -0
  46. data/lib/yodel/mime_types/yodel.rb +15 -0
  47. data/lib/yodel/models/api/api.rb +1 -0
  48. data/lib/yodel/models/api/api_call.rb +87 -0
  49. data/lib/yodel/models/core/associations/association.rb +37 -0
  50. data/lib/yodel/models/core/associations/associations.rb +22 -0
  51. data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
  52. data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
  53. data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
  54. data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
  55. data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
  56. data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
  57. data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
  58. data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
  59. data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
  60. data/lib/yodel/models/core/associations/record_association.rb +38 -0
  61. data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
  62. data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
  63. data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
  64. data/lib/yodel/models/core/attachments/attachment.rb +73 -0
  65. data/lib/yodel/models/core/attachments/image.rb +38 -0
  66. data/lib/yodel/models/core/core.rb +15 -0
  67. data/lib/yodel/models/core/fields/alias_field.rb +32 -0
  68. data/lib/yodel/models/core/fields/array_field.rb +64 -0
  69. data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
  70. data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
  71. data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
  72. data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
  73. data/lib/yodel/models/core/fields/color_field.rb +4 -0
  74. data/lib/yodel/models/core/fields/date_field.rb +35 -0
  75. data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
  76. data/lib/yodel/models/core/fields/email_field.rb +10 -0
  77. data/lib/yodel/models/core/fields/enum_field.rb +33 -0
  78. data/lib/yodel/models/core/fields/field.rb +154 -0
  79. data/lib/yodel/models/core/fields/fields.rb +29 -0
  80. data/lib/yodel/models/core/fields/fields_field.rb +31 -0
  81. data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
  82. data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
  83. data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
  84. data/lib/yodel/models/core/fields/function_field.rb +28 -0
  85. data/lib/yodel/models/core/fields/hash_field.rb +54 -0
  86. data/lib/yodel/models/core/fields/html_field.rb +15 -0
  87. data/lib/yodel/models/core/fields/image_field.rb +11 -0
  88. data/lib/yodel/models/core/fields/integer_field.rb +25 -0
  89. data/lib/yodel/models/core/fields/password_field.rb +21 -0
  90. data/lib/yodel/models/core/fields/self_field.rb +27 -0
  91. data/lib/yodel/models/core/fields/string_field.rb +15 -0
  92. data/lib/yodel/models/core/fields/tags_field.rb +7 -0
  93. data/lib/yodel/models/core/fields/text_field.rb +7 -0
  94. data/lib/yodel/models/core/fields/time_field.rb +36 -0
  95. data/lib/yodel/models/core/functions/function.rb +471 -0
  96. data/lib/yodel/models/core/functions/functions.rb +2 -0
  97. data/lib/yodel/models/core/functions/trigger.rb +14 -0
  98. data/lib/yodel/models/core/log/log.rb +33 -0
  99. data/lib/yodel/models/core/log/log_entry.rb +12 -0
  100. data/lib/yodel/models/core/model/abstract_model.rb +59 -0
  101. data/lib/yodel/models/core/model/model.rb +460 -0
  102. data/lib/yodel/models/core/model/mongo_model.rb +25 -0
  103. data/lib/yodel/models/core/model/site_model.rb +17 -0
  104. data/lib/yodel/models/core/mongo/mongo.rb +3 -0
  105. data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
  106. data/lib/yodel/models/core/mongo/query.rb +68 -0
  107. data/lib/yodel/models/core/mongo/record_index.rb +89 -0
  108. data/lib/yodel/models/core/record/abstract_record.rb +411 -0
  109. data/lib/yodel/models/core/record/embedded_record.rb +47 -0
  110. data/lib/yodel/models/core/record/mongo_record.rb +83 -0
  111. data/lib/yodel/models/core/record/record.rb +386 -0
  112. data/lib/yodel/models/core/record/section.rb +21 -0
  113. data/lib/yodel/models/core/record/site_record.rb +31 -0
  114. data/lib/yodel/models/core/site/migration.rb +52 -0
  115. data/lib/yodel/models/core/site/remote.rb +61 -0
  116. data/lib/yodel/models/core/site/site.rb +202 -0
  117. data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
  118. data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
  119. data/lib/yodel/models/core/validations/errors.rb +51 -0
  120. data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
  121. data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
  122. data/lib/yodel/models/core/validations/format_validation.rb +10 -0
  123. data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
  124. data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
  125. data/lib/yodel/models/core/validations/length_validation.rb +28 -0
  126. data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
  127. data/lib/yodel/models/core/validations/required_validation.rb +9 -0
  128. data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
  129. data/lib/yodel/models/core/validations/validation.rb +39 -0
  130. data/lib/yodel/models/core/validations/validations.rb +15 -0
  131. data/lib/yodel/models/email/email.rb +79 -0
  132. data/lib/yodel/models/migrations/01_record_model.rb +29 -0
  133. data/lib/yodel/models/migrations/02_page_model.rb +45 -0
  134. data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
  135. data/lib/yodel/models/migrations/04_group_model.rb +61 -0
  136. data/lib/yodel/models/migrations/05_user_model.rb +24 -0
  137. data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
  138. data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
  139. data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
  140. data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
  141. data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
  142. data/lib/yodel/models/migrations/11_email_model.rb +28 -0
  143. data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
  144. data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
  145. data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
  146. data/lib/yodel/models/models.rb +8 -0
  147. data/lib/yodel/models/pages/form_builder.rb +379 -0
  148. data/lib/yodel/models/pages/html_decorator.rb +132 -0
  149. data/lib/yodel/models/pages/layout.rb +120 -0
  150. data/lib/yodel/models/pages/menu.rb +32 -0
  151. data/lib/yodel/models/pages/page.rb +378 -0
  152. data/lib/yodel/models/pages/pages.rb +7 -0
  153. data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
  154. data/lib/yodel/models/pages/redirect_page.rb +11 -0
  155. data/lib/yodel/models/search/search.rb +1 -0
  156. data/lib/yodel/models/search/search_page.rb +58 -0
  157. data/lib/yodel/models/security/facebook_login_page.rb +55 -0
  158. data/lib/yodel/models/security/group.rb +10 -0
  159. data/lib/yodel/models/security/guests_group.rb +5 -0
  160. data/lib/yodel/models/security/login_page.rb +20 -0
  161. data/lib/yodel/models/security/logout_page.rb +13 -0
  162. data/lib/yodel/models/security/noone_group.rb +5 -0
  163. data/lib/yodel/models/security/owner_group.rb +8 -0
  164. data/lib/yodel/models/security/password.rb +5 -0
  165. data/lib/yodel/models/security/password_reset_page.rb +47 -0
  166. data/lib/yodel/models/security/security.rb +10 -0
  167. data/lib/yodel/models/security/user.rb +33 -0
  168. data/lib/yodel/public/core/css/core.css +257 -0
  169. data/lib/yodel/public/core/css/reset.css +48 -0
  170. data/lib/yodel/public/core/images/cross.png +0 -0
  171. data/lib/yodel/public/core/images/spinner.gif +0 -0
  172. data/lib/yodel/public/core/images/tick.png +0 -0
  173. data/lib/yodel/public/core/images/yodel.png +0 -0
  174. data/lib/yodel/public/core/js/jquery.min.js +18 -0
  175. data/lib/yodel/public/core/js/json2.js +480 -0
  176. data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
  177. data/lib/yodel/request/authentication.rb +76 -0
  178. data/lib/yodel/request/flash.rb +28 -0
  179. data/lib/yodel/request/request.rb +4 -0
  180. data/lib/yodel/requires.rb +47 -0
  181. data/lib/yodel/task_queue/queue_daemon.rb +33 -0
  182. data/lib/yodel/task_queue/queue_worker.rb +32 -0
  183. data/lib/yodel/task_queue/stats_thread.rb +27 -0
  184. data/lib/yodel/task_queue/task.rb +62 -0
  185. data/lib/yodel/task_queue/task_queue.rb +40 -0
  186. data/lib/yodel/types/date.rb +5 -0
  187. data/lib/yodel/types/object_id.rb +11 -0
  188. data/lib/yodel/types/time.rb +5 -0
  189. data/lib/yodel/version.rb +3 -0
  190. data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
  191. data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
  192. data/system/etc/resolver/yodel +2 -0
  193. data/system/usr/local/bin/yodel_command_runner +2 -0
  194. data/system/usr/local/etc/yodel/development_settings.rb +28 -0
  195. data/system/usr/local/etc/yodel/production_settings.rb +27 -0
  196. data/system/var/log/yodel.log +0 -0
  197. data/test/helper.rb +18 -0
  198. data/test/test_yodel.rb +4 -0
  199. data/yodel.gemspec +47 -0
  200. metadata +501 -0
@@ -0,0 +1,16 @@
1
+ require 'rubydns'
2
+
3
+ class DNSServer
4
+ def self.start
5
+ @resolv = Resolv::DNS.new
6
+ RubyDNS::run_server(:listen => [[:udp, "0.0.0.0", Yodel.config.dns_port], [:tcp, "0.0.0.0", Yodel.config.dns_port]]) do
7
+ match(/yodel/) do |match_data, transaction|
8
+ transaction.respond!("127.0.0.1")
9
+ end
10
+
11
+ otherwise do |transaction|
12
+ transaction.passthrough!(@resolv)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,229 @@
1
+ require 'highline'
2
+ require 'tempfile'
3
+ require 'ember'
4
+ require 'etc'
5
+
6
+ class Installer
7
+ # ----------------------------------------
8
+ # Template variables
9
+ # ----------------------------------------
10
+ attr_reader :database_hostname, :database_port, :database_name,
11
+ :sites_root, :ruby_path, :web_port, :dns_port,
12
+ :user, :group, :public_directory
13
+
14
+ def default_sites_root
15
+ if `uname -a` =~ /Darwin/
16
+ File.expand_path('~/Sites')
17
+ else
18
+ '/var/www'
19
+ end
20
+ end
21
+
22
+ def default_ruby_path
23
+ if Config.ruby =~ /rubies\/(.*)\/bin/
24
+ `rvm wrapper #{$1} yodel`
25
+ `which yodel_ruby`.strip
26
+ else
27
+ Config.ruby
28
+ end
29
+ end
30
+
31
+ def assign_default_user_and_group
32
+ pwnam = Etc.getpwnam(Etc.getlogin)
33
+ @user = pwnam.uid
34
+ @group = pwnam.gid
35
+ end
36
+
37
+ def default_group
38
+ end
39
+
40
+ def initialize
41
+ # assign default values; not all values have associated questions
42
+ # presented to the user depen
43
+ @database_hostname = 'localhost'
44
+ @database_port = 27017
45
+ @database_name = 'yodel'
46
+ @web_port = 80
47
+ @dns_port = 2828
48
+ @public_directory = '/var/www'
49
+ @sites_root = default_sites_root
50
+ @ruby_path = default_ruby_path
51
+ assign_default_user_and_group
52
+ end
53
+
54
+
55
+ # ----------------------------------------
56
+ # Helpers
57
+ # ----------------------------------------
58
+ def system_path
59
+ @system_path ||= File.join(File.dirname(__FILE__), '..', '..', '..', 'system')
60
+ end
61
+
62
+ def report(verb, noun)
63
+ @h.say "<%= color('#{verb}', GREEN) %>\t#{noun}"
64
+ end
65
+
66
+ def escape_quotes(str)
67
+ str.gsub("'", "\\\\'")
68
+ end
69
+
70
+ def install(file, permissions='0644', file_name=nil)
71
+ if file_name
72
+ dest_path = File.join('/', File.dirname(file), file_name)
73
+ else
74
+ dest_path = File.join('/', file)
75
+ end
76
+ source_path = File.join(system_path, file)
77
+ temp_file = Tempfile.new('yodel')
78
+ report('installing', dest_path)
79
+
80
+ # render the file template
81
+ temp_file.write Ember::Template.new(IO.read(source_path), {source_file: source_path}).render(binding)
82
+ temp_file.close
83
+
84
+ # create parent directories, copy the rendered file and set permissions
85
+ `sudo mkdir -p #{File.dirname(dest_path)}`
86
+ `sudo cp #{temp_file.path} #{dest_path}`
87
+ `sudo chmod #{permissions} #{dest_path}`
88
+
89
+ # delete the temp file used for rendering
90
+ temp_file.unlink
91
+ end
92
+
93
+
94
+ # ----------------------------------------
95
+ # Q & A
96
+ # ----------------------------------------
97
+ def install_system_files
98
+ @h = h = HighLine.new
99
+ h.say "\n<%= color('Welcome to Yodel', BOLD) %>\n\n"
100
+
101
+ # environment
102
+ h.say "<%= color('Environment', BOLD) %>"
103
+ h.say "Yodel can run in development (local) mode or as a production server. Press"
104
+ h.say "enter to setup Yodel in development mode, or enter 'production' to setup"
105
+ h.say "a production server."
106
+ @environment = h.ask("Environment: ") do |q|
107
+ q.default = 'development'
108
+ q.in = %w{development production}
109
+ q.responses[:ask_on_error] = :question
110
+ end
111
+
112
+ # local sites directory
113
+ h.say "\n<%= color('1/3', RED) %> <%= color('Sites Directory', BOLD) %>"
114
+ h.say "Yodel stores each of your sites in a single directory. You may place this directory"
115
+ h.say "anywhere, but all sites created by Yodel must be accessible from it. Press enter to"
116
+ h.say "accept the default directory, or enter a new path."
117
+ @sites_root = h.ask("Sites directory: ") do |q|
118
+ q.default = @sites_root
119
+ q.responses[:ask_on_error] = :question
120
+ end
121
+ @sites_root = escape_quotes(@sites_root)
122
+
123
+ # web server port
124
+ if @environment == 'development'
125
+ h.say "\n<%= color('2/3', RED) %> <%= color('Local Server', BOLD) %>"
126
+ h.say "By default the yodel server will run on port 80, meaning you can access yodel"
127
+ h.say "sites by visiting <http://sitename.yodel/>. If you already have Apache or"
128
+ h.say "another web server running on this port, enter a different port (such as 8080)"
129
+ @web_port = h.ask("Use port: ", Integer) do |q|
130
+ q.default = @web_port
131
+ q.responses[:ask_on_error] = :question
132
+ end
133
+ else
134
+ h.say "\n<%= color('2/3', RED) %> <%= color('Public Directory', BOLD) %>"
135
+ h.say "Yodel symlinks the public directory of sites served by the production server"
136
+ h.say "to a directory visible to the fronting web server. The domain of a site is"
137
+ h.say "used as the link name, meaning /var/www/domain.com could e.g link to"
138
+ h.say "/var/git/ID/public. This makes serving public assets from a fronting server"
139
+ h.say "possible by rewriting the request path to include the domain of the request."
140
+ @public_directory = h.ask("Public directory: ") do |q|
141
+ q.default = @public_directory
142
+ q.responses[:ask_on_error] = :question
143
+ end
144
+ @public_directory = escape_quotes(@public_directory)
145
+ end
146
+
147
+ h.say "\n<%= color('3/3', RED) %> <%= color('Installing files', BOLD) %>"
148
+ h.say "Yodel will now install the necessary system files. You may be asked to enter"
149
+ h.say "your local user password.\n\n"
150
+ h.say "-----------------------------------------------------------------------------\n\n"
151
+
152
+ # install system files
153
+ if `uname -a` =~ /Darwin/
154
+ install_mac_files
155
+ else
156
+ install_linux_files
157
+ end
158
+
159
+ # start yodel for environment installation
160
+ report('starting', 'yodel')
161
+ require '../../yodel'
162
+ Yodel.config.extensions_folder = $extensions_folder if $extensions_folder
163
+ Yodel.load_extensions
164
+
165
+ # install an environment support site
166
+ report('installing', "#{@environment} environment support site")
167
+ site = Site.new
168
+ site.name = "yodel"
169
+ site.domains = ['yodel', 'localhost', '127.0.0.1']
170
+ extension = Yodel.extensions["yodel_#{@environment}_environment"]
171
+ puts "Installation environment (#{@environment}) not found" and exit(1) if extension.nil?
172
+ site.root_directory = extension.lib_dir
173
+ site.save
174
+ Migration.run_migrations(site)
175
+
176
+ h.say "\n-----------------------------------------------------------------------------"
177
+ h.say "\n<%= color('Installation complete', BOLD) %>"
178
+ h.say "Visit http://yodel#{":#{@web_port}" if @web_port != 80}/ to setup accounts you have on a remote Yodel server."
179
+ h.say "You can create a new site by visiting <http://sitename.yodel#{":#{@web_port}" if @web_port != 80}/> and"
180
+ h.say "following the instructions\n\n"
181
+ end
182
+
183
+
184
+ # ----------------------------------------
185
+ # OS X
186
+ # ----------------------------------------
187
+ def install_mac_files
188
+ install 'etc/resolver/yodel'
189
+ install 'Library/LaunchDaemons/com.yodelcms.dns.plist'
190
+ install 'Library/LaunchDaemons/com.yodelcms.server.plist'
191
+ install 'usr/local/bin/yodel_command_runner', '0777'
192
+ install 'var/log/yodel.log', '0666'
193
+
194
+ case @environment
195
+ when 'development'
196
+ install 'usr/local/etc/yodel/development_settings.rb', '0644', 'settings.rb'
197
+ when 'production'
198
+ install 'usr/local/etc/yodel/production_settings.rb', '0644', 'settings.rb'
199
+ end
200
+
201
+ report('starting', 'dns server')
202
+ if `sudo launchctl list` =~ /\d+.+com.yodelcms.dns$/
203
+ `sudo launchctl unload /Library/LaunchDaemons/com.yodelcms.dns.plist`
204
+ end
205
+ `sudo launchctl load /Library/LaunchDaemons/com.yodelcms.dns.plist`
206
+
207
+ report('starting', 'web server')
208
+ if `sudo launchctl list` =~ /\d+.+com.yodelcms.server$/
209
+ `sudo launchctl unload /Library/LaunchDaemons/com.yodelcms.server.plist`
210
+ end
211
+ `sudo launchctl load /Library/LaunchDaemons/com.yodelcms.server.plist`
212
+ end
213
+
214
+
215
+ # ----------------------------------------
216
+ # Linux
217
+ # ----------------------------------------
218
+ def install_linux_files
219
+ install 'usr/local/bin/yodel_command_runner', '0777'
220
+ install 'var/log/yodel.log', '0666'
221
+
222
+ case @environment
223
+ when 'development'
224
+ install 'usr/local/etc/yodel/development_settings.rb', '0644', 'settings.rb'
225
+ when 'production'
226
+ install 'usr/local/etc/yodel/production_settings.rb', '0644', 'settings.rb'
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,30 @@
1
+ Dir.chdir(File.dirname(__FILE__)) do
2
+ require './environment'
3
+ require './yodel'
4
+ end
5
+
6
+ class YodelConfig
7
+ def initialize
8
+ @options = {
9
+ 'yodel_migration_directory' => File.join(File.dirname(__FILE__), '..', 'models', 'migrations'),
10
+ 'public_directories' => [File.join(File.dirname(__FILE__), '..', 'public')],
11
+ 'layout_directories' => [],
12
+ 'extensions' => []
13
+ }
14
+ end
15
+
16
+ def method_missing(method, *args)
17
+ method = method.to_s
18
+ if method[-1] == '='
19
+ @options[method[0...-1]] = args[0]
20
+ elsif method[-1] == '?'
21
+ @options.has_key?(method[0...-1])
22
+ else
23
+ @options[method]
24
+ end
25
+ end
26
+
27
+ def define(&block)
28
+ yield self
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ class Environment
2
+ def initialize
3
+ @env = ENV['YODEL_ENV'] || 'development'
4
+ end
5
+
6
+ def method_missing(sym, *args)
7
+ sym = sym.to_s
8
+ if sym.end_with?('!')
9
+ @env = sym[0..-2]
10
+ elsif sym.end_with?('?')
11
+ @env == sym[0..-2]
12
+ else
13
+ super
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ module Yodel
2
+ MODELS_DIRECTORY_NAME = 'models'
3
+ PUBLIC_DIRECTORY_NAME = 'public'
4
+ LAYOUTS_DIRECTORY_NAME = 'layouts'
5
+ PARTIALS_DIRECTORY_NAME = 'partials'
6
+ MIGRATIONS_DIRECTORY_NAME = 'migrations'
7
+ ATTACHMENTS_DIRECTORY_NAME = 'attachments'
8
+ EXTENSION_LIB_DIRECTORY_NAME = 'lib'
9
+ YODEL_MIGRATIONS_DIRECTORY_NAME = 'yodel'
10
+ EXTENSION_MIGRATIONS_DIRECTORY_NAME = 'extensions'
11
+ SITE_MIGRATIONS_DIRECTORY_NAME = 'site'
12
+ SITE_YML_FILE_NAME = 'site.yml'
13
+
14
+ def self.config
15
+ @config ||= YodelConfig.new
16
+ end
17
+
18
+ def self.env
19
+ @env ||= Environment.new
20
+ end
21
+ end
@@ -0,0 +1,2 @@
1
+ class DestroyedRecord < StandardError
2
+ end
@@ -0,0 +1,16 @@
1
+ class DomainNotFound < StandardError
2
+ attr_reader :domain
3
+ def initialize(domain, port)
4
+ @domain = domain
5
+ @port = (port == 80 ? nil : port)
6
+ super()
7
+ end
8
+
9
+ def error
10
+ ["The site '#{@domain.gsub('.yodel', '')}' has not been created yet"]
11
+ end
12
+
13
+ def description
14
+ "<form action='http://yodel#{':' if @port}#{@port}/sites' method='post' class='inline'><input type='hidden' name='name'' value='#{@domain}'><a href='#' onclick='submit()'>Create a new site</a></form> or try a different address."
15
+ end
16
+ end
@@ -0,0 +1,2 @@
1
+ class DuplicateLayout < StandardError
2
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.join(File.dirname(__FILE__), '*.rb')].each do |path|
2
+ require path
3
+ end
@@ -0,0 +1,2 @@
1
+ class InconsistentLockState < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class InvalidField < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class InvalidIndex < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class InvalidMixin < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class InvalidModelField < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class LayoutNotFound < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class MassAssignment < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class MissingMigration < StandardError
2
+ end
@@ -0,0 +1,15 @@
1
+ class MissingRootDirectory < StandardError
2
+ def initialize(site, port)
3
+ @port = (port == 80 ? nil : port)
4
+ @site = site
5
+ super()
6
+ end
7
+
8
+ def error
9
+ ["The root directory for #{@site.name} is missing"]
10
+ end
11
+
12
+ def description
13
+ "You can <a href='http://yodel#{':' if @port}#{@port}/sites?id=#{@site.id}'>select a new root directory</a>, or rename the existing directory back to '#{File.basename(@site.root_directory)}'."
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ class UnableToAcquireLock < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class Unauthorised < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class UnknownField < StandardError
2
+ end
@@ -0,0 +1,180 @@
1
+ require 'rack/utils'
2
+ require 'rack/mime'
3
+ require 'thread'
4
+
5
+ class StreamClosed < StandardError
6
+ end
7
+
8
+ class DevelopmentServer
9
+ SEPARATORS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
10
+
11
+ class Message
12
+ attr_accessor :status, :headers, :body, :env, :data, :message_type
13
+ MESSAGE_TYPES = {request: 0, response: 1, restart: 2, exit: 3}
14
+
15
+ def request?; @message_type == :request; end
16
+ def response?; @message_type == :response; end
17
+ def restart?; @message_type == :restart; end
18
+ def exit?; @message_type == :exit; end
19
+
20
+ def initialize(message_type)
21
+ @message_type = message_type
22
+ end
23
+
24
+ def self.read(socket)
25
+ message = Message.new(nil)
26
+ message.message_type = MESSAGE_TYPES.keys[read_int(socket)]
27
+
28
+ if message.request? || message.response?
29
+ length = read_int(socket)
30
+ message.data = Marshal.load(socket.read(length))
31
+
32
+ if message.request?
33
+ message.data['rack.input'] = StringIO.new(message.data['rack.input'])
34
+ message.data['rack.errors'] = STDERR
35
+ elsif message.response?
36
+ message.data[2] = [message.data[2]]
37
+ end
38
+ end
39
+
40
+ message
41
+ end
42
+
43
+ def write(socket)
44
+ write_int(MESSAGE_TYPES[@message_type], socket)
45
+ payload = nil
46
+
47
+ if request?
48
+ # convert input (StringIO) to a normal string, and
49
+ # remove the reference to STDERR for transmission
50
+ @env['rack.input'] = @env['rack.input'].read if @env['rack.input'].is_a?(StringIO)
51
+ @env['rack.errors'] = nil
52
+ payload = Marshal.dump(@env)
53
+ elsif response?
54
+ body = ''
55
+ @body.each {|chunk| body << chunk.to_s}
56
+ payload = Marshal.dump([@status, @headers, body])
57
+ end
58
+
59
+ if payload
60
+ write_int(payload.length, socket)
61
+ socket.write(payload)
62
+ end
63
+ end
64
+
65
+ private
66
+ def self.read_int(socket)
67
+ int = socket.read(4)
68
+ raise StreamClosed if int.nil?
69
+ int.unpack('L').first
70
+ end
71
+
72
+ def write_int(int, socket)
73
+ socket.write([int].pack('L'))
74
+ end
75
+ end
76
+
77
+ def initialize
78
+ spawn_server
79
+ @mutex = Mutex.new
80
+ @pid = nil
81
+ Signal.trap("SIGTERM") do
82
+ self.kill_child
83
+ Process.kill("SIGINT", Process.pid)
84
+ end
85
+ end
86
+
87
+ def kill_child
88
+ begin
89
+ Process.kill("SIGTERM", @pid) if @pid
90
+ @pid = nil
91
+ @client_socket.close
92
+ rescue
93
+ end
94
+ end
95
+
96
+ def call(env)
97
+ @mutex.lock
98
+ parts = Rack::Utils.unescape(env["PATH_INFO"]).split(SEPARATORS)
99
+ return [403, {"Content-Type" => "text/plain"}, "Forbidden"] if parts.include? ".."
100
+
101
+ # pass the request through to a yodel server. if the server
102
+ # responds with a restart message (yodel source files have
103
+ # changed and need to be re-loaded) spawn a new server and
104
+ # write the request again (assume the request is successful)
105
+ request = Message.new(:request)
106
+ request.env = env
107
+ request.write(@client_socket)
108
+ response = Message.read(@client_socket)
109
+
110
+ if response.restart?
111
+ spawn_server
112
+ request.write(@client_socket)
113
+ response = Message.read(@client_socket)
114
+ end
115
+
116
+ # response data is a valid rack response
117
+ response.data
118
+ ensure
119
+ @mutex.unlock
120
+ end
121
+
122
+ protected
123
+ def spawn_server
124
+ # http requests are passed between the client (development server)
125
+ # and the server (yodel server) over a socket pair. Pipe's aren't
126
+ # used because the server may be forked for a long time and respond
127
+ # to multiple requests before being reaped
128
+ @client_socket.close unless @client_socket.nil?
129
+ @client_socket, @server_socket = UNIXSocket.pair
130
+
131
+ # child process loads yodel and responds to the request; if any
132
+ # source files have been modified since the server was started, send
133
+ # a restart message to the client and exit
134
+ @pid = Process.fork
135
+ if @pid.nil?
136
+ @client_socket.close
137
+ require File.join(File.dirname(__FILE__), '..', '..', 'yodel')
138
+ closed = false
139
+
140
+ @application = Application.new
141
+ @modification_times = $LOADED_FEATURES.each_with_object({}) do |path, mtimes|
142
+ mtimes[path] = File.mtime(path) if File.exist?(path)
143
+ end
144
+
145
+ loop do
146
+ begin
147
+ # block until a new request is received
148
+ request = Message.read(@server_socket)
149
+
150
+ # check for modified files
151
+ @modification_times.each do |path, modified_time|
152
+ if File.exist?(path) && File.mtime(path) > modified_time
153
+ message = Message.new(:restart)
154
+ message.write(@server_socket)
155
+ @server_socket.close
156
+ closed = true
157
+ break
158
+ end
159
+ end
160
+
161
+ # kill the server if a file was changed
162
+ break if closed
163
+
164
+ # otherwise respond to the request
165
+ if request.request?
166
+ response = Message.new(:response)
167
+ response.status, response.headers, response.body = @application.call(request.data)
168
+ response.write(@server_socket)
169
+ end
170
+ rescue Interrupt, StreamClosed
171
+ exit
172
+ end
173
+ end
174
+
175
+ else
176
+ Process.detach(@pid)
177
+ @server_socket.close
178
+ end
179
+ end
180
+ end