yodel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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