volt 0.9.6 → 0.9.7.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/CHANGELOG.md +26 -0
  4. data/Gemfile +6 -1
  5. data/README.md +2 -0
  6. data/Rakefile +1 -0
  7. data/app/volt/models/active_volt_instance.rb +8 -6
  8. data/app/volt/models/user.rb +11 -2
  9. data/app/volt/models/volt_app_property.rb +8 -0
  10. data/app/volt/tasks/query_tasks.rb +23 -31
  11. data/app/volt/tasks/store_tasks.rb +2 -2
  12. data/app/volt/tasks/volt_admin_tasks.rb +24 -0
  13. data/docs/UPGRADE_GUIDE.md +6 -0
  14. data/lib/volt.rb +19 -12
  15. data/lib/volt/boot.rb +1 -0
  16. data/lib/volt/cli.rb +19 -8
  17. data/lib/volt/cli/console.rb +0 -1
  18. data/lib/volt/cli/generators.rb +14 -3
  19. data/lib/volt/cli/migrate.rb +26 -0
  20. data/lib/volt/config.rb +17 -4
  21. data/lib/volt/controllers/http_controller.rb +12 -0
  22. data/lib/volt/data_stores/base_adaptor_client.rb +2 -2
  23. data/lib/volt/data_stores/base_adaptor_server.rb +2 -0
  24. data/lib/volt/data_stores/data_store.rb +20 -14
  25. data/lib/volt/extra_core/class.rb +28 -14
  26. data/lib/volt/extra_core/hash.rb +5 -0
  27. data/lib/volt/extra_core/string.rb +3 -1
  28. data/lib/volt/helpers/time.rb +9 -43
  29. data/lib/volt/helpers/time/calculations.rb +204 -0
  30. data/lib/volt/helpers/time/distance.rb +63 -0
  31. data/lib/volt/helpers/time/duration.rb +71 -0
  32. data/lib/volt/helpers/time/local_calculations.rb +49 -0
  33. data/lib/volt/helpers/time/local_volt_time.rb +23 -0
  34. data/lib/volt/helpers/time/numeric.rb +59 -0
  35. data/lib/volt/helpers/time/volt_time.rb +170 -0
  36. data/lib/volt/models.rb +5 -0
  37. data/lib/volt/models/array_model.rb +33 -6
  38. data/lib/volt/models/associations.rb +146 -23
  39. data/lib/volt/models/buffer.rb +38 -41
  40. data/lib/volt/models/cursor.rb +15 -0
  41. data/lib/volt/models/errors.rb +11 -0
  42. data/lib/volt/models/field_helpers.rb +108 -68
  43. data/lib/volt/models/helpers/array_model.rb +4 -0
  44. data/lib/volt/models/helpers/base.rb +8 -1
  45. data/lib/volt/models/helpers/change_helpers.rb +31 -12
  46. data/lib/volt/models/helpers/defaults.rb +15 -0
  47. data/lib/volt/models/location.rb +20 -6
  48. data/lib/volt/models/migrations/migration.rb +23 -0
  49. data/lib/volt/models/migrations/migration_runner.rb +146 -0
  50. data/lib/volt/models/model.rb +38 -1
  51. data/lib/volt/models/permissions.rb +8 -1
  52. data/lib/volt/models/persistors/array_store.rb +87 -8
  53. data/lib/volt/models/persistors/base.rb +19 -0
  54. data/lib/volt/models/persistors/model_store.rb +1 -1
  55. data/lib/volt/models/persistors/page.rb +4 -1
  56. data/lib/volt/models/persistors/query/query_identifier.rb +102 -0
  57. data/lib/volt/models/persistors/query/query_listener.rb +57 -12
  58. data/lib/volt/models/root_models/root_models.rb +19 -0
  59. data/lib/volt/models/url.rb +11 -2
  60. data/lib/volt/models/validations/validations.rb +5 -2
  61. data/lib/volt/models/validators/type_validator.rb +11 -0
  62. data/lib/volt/models/validators/unique_validator.rb +2 -2
  63. data/lib/volt/page/bindings/attribute_binding.rb +23 -1
  64. data/lib/volt/page/targets/attribute_section.rb +7 -0
  65. data/lib/volt/page/targets/binding_document/component_node.rb +44 -18
  66. data/lib/volt/page/targets/binding_document/tag_node.rb +41 -0
  67. data/lib/volt/page/tasks.rb +16 -8
  68. data/lib/volt/queries/live_query.rb +109 -0
  69. data/lib/volt/queries/live_query_pool.rb +58 -0
  70. data/lib/volt/queries/live_subquery.rb +0 -0
  71. data/lib/volt/queries/query_association_splitter.rb +31 -0
  72. data/lib/volt/queries/query_diff.rb +100 -0
  73. data/lib/volt/queries/query_runner.rb +110 -0
  74. data/lib/volt/queries/query_subscription.rb +80 -0
  75. data/lib/volt/queries/query_subscription_pool.rb +37 -0
  76. data/lib/volt/reactive/eventable.rb +8 -0
  77. data/lib/volt/reactive/reactive_array.rb +0 -4
  78. data/lib/volt/router/routes.rb +81 -31
  79. data/lib/volt/server/message_bus/base_message_bus.rb +9 -3
  80. data/lib/volt/server/message_bus/peer_to_peer.rb +6 -6
  81. data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
  82. data/lib/volt/server/middleware/default_middleware_stack.rb +12 -8
  83. data/lib/volt/server/rack/component_paths.rb +31 -4
  84. data/lib/volt/server/rack/http_content_types.rb +62 -0
  85. data/lib/volt/server/rack/http_resource.rb +1 -1
  86. data/lib/volt/server/rack/index_files.rb +8 -1
  87. data/lib/volt/server/rack/opal_files.rb +16 -1
  88. data/lib/volt/server/rack/sprockets_helpers_setup.rb +32 -1
  89. data/lib/volt/server/socket_connection_handler.rb +16 -7
  90. data/lib/volt/server/template_handlers/sprockets_component_handler.rb +5 -3
  91. data/lib/volt/spec/capybara.rb +4 -3
  92. data/lib/volt/spec/setup.rb +5 -0
  93. data/lib/volt/tasks/dispatcher.rb +3 -1
  94. data/lib/volt/utils/data_transformer.rb +4 -4
  95. data/lib/volt/utils/ejson.rb +19 -6
  96. data/lib/volt/utils/promise_extensions.rb +1 -1
  97. data/lib/volt/utils/time_opal_patch.rb +749 -0
  98. data/lib/volt/utils/time_patch.rb +11 -4
  99. data/lib/volt/version.rb +1 -1
  100. data/lib/volt/volt/app.rb +19 -11
  101. data/lib/volt/volt/properties.rb +24 -0
  102. data/lib/volt/volt/server_setup/app.rb +30 -7
  103. data/lib/volt/volt/users.rb +15 -3
  104. data/spec/apps/kitchen_sink/Gemfile +5 -1
  105. data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
  106. data/spec/apps/kitchen_sink/app/main/controllers/save_controller.rb +1 -1
  107. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +4 -0
  108. data/spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb +4 -2
  109. data/spec/apps/kitchen_sink/app/main/models/post.rb +0 -1
  110. data/spec/apps/kitchen_sink/app/main/models/todo.rb +4 -0
  111. data/spec/apps/kitchen_sink/app/main/views/mailers/reset_password.html +10 -0
  112. data/spec/apps/kitchen_sink/app/main/views/todos/index.html +2 -0
  113. data/spec/apps/kitchen_sink/config/app.rb +2 -0
  114. data/spec/apps/migrations/config/db/migrations/1445111704_migration1.rb +7 -0
  115. data/spec/apps/migrations/config/db/migrations/1445113517_migration2.rb +7 -0
  116. data/spec/apps/migrations/config/db/migrations/1445115200_migration3.rb +7 -0
  117. data/spec/extra_core/class_spec.rb +10 -0
  118. data/spec/helpers/distance_spec.rb +35 -0
  119. data/spec/helpers/duration_spec.rb +160 -0
  120. data/spec/helpers/volt_time_spec.rb +275 -0
  121. data/spec/integration/callbacks_spec.rb +2 -1
  122. data/spec/integration/http_endpoints_spec.rb +4 -0
  123. data/spec/integration/save_spec.rb +1 -1
  124. data/spec/integration/todos_spec.rb +7 -5
  125. data/spec/models/array_model_spec.rb +17 -3
  126. data/spec/models/associations_spec.rb +48 -1
  127. data/spec/models/field_helpers_spec.rb +7 -3
  128. data/spec/models/migrations/migration_runner_spec.rb +69 -0
  129. data/spec/models/model_spec.rb +42 -8
  130. data/spec/models/permissions_spec.rb +20 -8
  131. data/spec/models/persistors/array_store_spec.rb +18 -0
  132. data/spec/models/persistors/page_spec.rb +15 -10
  133. data/spec/models/persistors/store_spec.rb +13 -3
  134. data/spec/models/url_spec.rb +4 -3
  135. data/spec/models/user_spec.rb +6 -3
  136. data/spec/models/user_validation_spec.rb +3 -3
  137. data/spec/models/validations_spec.rb +4 -0
  138. data/spec/models/validators/block_validations_spec.rb +9 -5
  139. data/spec/models/validators/email_validator_spec.rb +2 -0
  140. data/spec/models/validators/lifecycle_callbacks_spec.rb +86 -0
  141. data/spec/models/validators/unique_validator_spec.rb +1 -0
  142. data/spec/page/path_string_renderer_spec.rb +5 -0
  143. data/spec/queries/live_query_spec.rb +16 -0
  144. data/spec/queries/query_association_splitter_spec.rb +14 -0
  145. data/spec/queries/query_diff_spec.rb +132 -0
  146. data/spec/queries/query_identifier_spec.rb +98 -0
  147. data/spec/queries/query_runner_spec.rb +63 -0
  148. data/spec/queries/query_tracker_spec.rb +141 -0
  149. data/spec/router/routes_spec.rb +52 -21
  150. data/spec/server/middleware/rack_content_types_spec.rb +78 -0
  151. data/spec/server/rack/asset_files_spec.rb +38 -30
  152. data/spec/spec_helper.rb +8 -0
  153. data/spec/utils/ejson_spec.rb +9 -8
  154. data/spec/utils/ejson_volt_time_spec.rb +65 -0
  155. data/templates/migration/migration.rb.tt +9 -0
  156. data/templates/newgem/gitignore.tt +1 -0
  157. data/templates/project/Gemfile.tt +19 -2
  158. data/templates/project/README.md.tt +6 -1
  159. data/templates/project/app/main/config/dependencies.rb +6 -0
  160. data/templates/project/config/app.rb.tt +18 -4
  161. data/volt.gemspec +2 -2
  162. metadata +73 -16
  163. data/app/volt/tasks/live_query/live_query_pool.rb +0 -48
  164. data/app/volt/tasks/live_query/query_tracker.rb +0 -92
  165. data/spec/tasks/live_query_spec.rb +0 -18
  166. data/spec/tasks/query_tasks.rb +0 -7
  167. data/spec/tasks/query_tracker_spec.rb +0 -145
@@ -25,7 +25,7 @@ module Volt
25
25
  private
26
26
 
27
27
  def routes_match?(request)
28
- @router.url_to_params(request.method, request.path)
28
+ @router.url_to_params(request.method, request.path_info)
29
29
  end
30
30
 
31
31
  # Find the correct controller and call the correct action on it.
@@ -30,7 +30,14 @@ module Volt
30
30
  end
31
31
 
32
32
  def html
33
- index_path = File.expand_path(File.join(Volt.root, 'config/base/index.html'))
33
+ # Check for a precompiled
34
+ index_path = File.expand_path(File.join(Volt.root, 'public/index.html'))
35
+
36
+ # Next check for one in config/base
37
+ unless File.exists?(index_path)
38
+ index_path = File.expand_path(File.join(Volt.root, 'config/base/index.html'))
39
+ end
40
+
34
41
  html = File.read(index_path)
35
42
 
36
43
  ERB.new(html, nil, '-').result(binding)
@@ -82,7 +82,22 @@ module Volt
82
82
  environment = @environment
83
83
 
84
84
  builder.map(app_url) do
85
- run environment
85
+ # This map block takes over all serving for /app, so here we serve out
86
+ # of public/app if the file isn't found in sprockets.
87
+ # TODO: We should really let Rack::Cascade run from the default
88
+ # middleware stack.
89
+ not_found = lambda { |env| [404, {}, []] }
90
+ opts = {
91
+ urls: [''],
92
+ root: ['public/app'],
93
+ header_rules: [
94
+ [:all, { 'Cache-Control' => 'public, max-age=86400' }]
95
+ ]
96
+ }
97
+ static = Rack::Static.new(not_found, opts)
98
+
99
+ run Rack::Cascade.new([environment, static])
100
+ # run environment
86
101
  end
87
102
 
88
103
  # Remove dup paths
@@ -1,3 +1,4 @@
1
+ require 'sprockets'
1
2
  require 'sprockets-helpers'
2
3
 
3
4
  module Volt
@@ -38,6 +39,7 @@ module Volt
38
39
  # We "freedom-patch" sprockets-helpers asset_path method to
39
40
  # automatically link assets.
40
41
  define_method(:asset_path) do |source, options = {}|
42
+ # puts "AP: #{source.inspect}"
41
43
  relative_path = source =~ /^[.][.]\//
42
44
  if relative_path
43
45
  component_root = logical_path.gsub(/\/[^\/]+$/, '')
@@ -103,4 +105,33 @@ module Volt
103
105
  output.join('/')
104
106
  end
105
107
  end
106
- end
108
+ end
109
+
110
+
111
+ # module Sprockets
112
+ # class Context
113
+ # # We have to reinclude this because of some ```require``` thread saftey issues.
114
+ # def find_asset_path(uri, source, options = {})
115
+ # if Helpers.manifest && options[:manifest] != false
116
+ # manifest_path = Helpers.manifest.assets[uri.path]
117
+ # return Helpers::ManifestPath.new(uri, manifest_path, options) if manifest_path
118
+ # end
119
+
120
+ # if Sprockets::Helpers.are_using_sprockets_3
121
+ # resolved = assets_environment.resolve(uri.path)
122
+
123
+ # if resolved
124
+ # return Helpers::AssetPath.new(uri, assets_environment[uri.path], options)
125
+ # else
126
+ # return Helpers::FilePath.new(uri, options)
127
+ # end
128
+ # else
129
+ # assets_environment.resolve(uri.path) do |path|
130
+ # return Helpers::AssetPath.new(uri, assets_environment[path], options)
131
+ # end
132
+
133
+ # return Helpers::FilePath.new(uri, options)
134
+ # end
135
+ # end
136
+ # end
137
+ # end
@@ -1,6 +1,12 @@
1
1
  require 'volt/utils/ejson'
2
2
  require File.join(File.dirname(__FILE__), '../../../app/volt/tasks/query_tasks')
3
3
 
4
+ # SocketConnectionHandler is often referred to as "channel" when on the server
5
+ # side.
6
+ #
7
+ # NOTE: On the forking server, the "channel" is actually a DRb reference back
8
+ # to the main process. So we should try to put as few objects as possible
9
+ # in here.
4
10
  module Volt
5
11
  class SocketConnectionHandler
6
12
  # Create one instance of the dispatcher
@@ -13,11 +19,14 @@ module Volt
13
19
  def initialize(session, *args)
14
20
  @session = session
15
21
 
16
- @@channels ||= []
17
- @@channels << self
22
+ # Keep a list of all channels
23
+ @@all_channels ||= []
24
+ @@all_channels << self
18
25
 
19
26
  # Trigger a client connect event
20
- @@dispatcher.volt_app.trigger!("client_connect")
27
+ if @@dispatcher.respond_to?(:volt_app)
28
+ @@dispatcher.volt_app.trigger!("client_connect")
29
+ end
21
30
 
22
31
  end
23
32
 
@@ -49,13 +58,13 @@ module Volt
49
58
  end
50
59
 
51
60
  def self.channels
52
- @@channels
61
+ @@all_channels
53
62
  end
54
63
 
55
64
  # Sends a message to all, optionally skipping a users channel
56
65
  def self.send_message_all(skip_channel = nil, *args)
57
- return unless defined?(@@channels)
58
- @@channels.each do |channel|
66
+ return unless defined?(@@all_channels)
67
+ @@all_channels.each do |channel|
59
68
  next if skip_channel && channel == skip_channel
60
69
  channel.send_message(*args)
61
70
  end
@@ -108,7 +117,7 @@ module Volt
108
117
  unless @closed
109
118
  @closed = true
110
119
  # Remove ourself from the available channels
111
- @@channels.delete(self)
120
+ @@all_channels.delete(self)
112
121
 
113
122
  begin
114
123
  @@dispatcher.close_channel(self)
@@ -54,10 +54,12 @@ module Sprockets
54
54
  data = env.read_file(input[:filename], input[:content_type])
55
55
  end
56
56
 
57
- # dependencies = Set.new(input[:metadata][:dependencies])
58
- # dependencies += [env.build_file_digest_uri(input[:filename])]
57
+ dependencies = Set.new(input[:metadata][:dependencies])
58
+ dependencies += [env.build_file_digest_uri(input[:filename])]
59
59
 
60
- dependencies = input[:metadata][:dependencies]
60
+ # For some reason this version doesn't work, even though its used
61
+ # elsewhere in sprockets
62
+ # dependencies = input[:metadata][:dependencies]
61
63
  # dependencies.merge(env.build_file_digest_uri(input[:filename]))
62
64
 
63
65
  { data: data, dependencies: dependencies }
@@ -35,6 +35,10 @@ module Volt
35
35
  require 'selenium-webdriver'
36
36
  require 'volt/server'
37
37
 
38
+
39
+ # Setup server, use existing booted app
40
+ Capybara.app = Server.new(app_path, volt_app).app
41
+
38
42
  case RUNNING_SERVER
39
43
  when 'thin'
40
44
  Capybara.server do |app, port|
@@ -48,9 +52,6 @@ module Volt
48
52
  end.run.join
49
53
  end
50
54
  end
51
-
52
- # Setup server, use existing booted app
53
- Capybara.app = Server.new(app_path, volt_app).app
54
55
  end
55
56
  end
56
57
  end
@@ -56,6 +56,7 @@ module Volt
56
56
  @__store_accessed = true
57
57
  volt_app.store
58
58
  end
59
+ let(:cleanup_db) { @__store_accessed = true }
59
60
  let(:volt_app) { volt_app }
60
61
  let(:params) { volt_app.params }
61
62
 
@@ -65,6 +66,10 @@ module Volt
65
66
  if url.instance_variable_get('@params')
66
67
  url.instance_variable_set('@params', nil)
67
68
  end
69
+
70
+ # Reset volt_app
71
+ $volt_app = volt_app
72
+ Thread.current['volt_app'] = volt_app
68
73
  end
69
74
 
70
75
  if RUBY_PLATFORM != 'opal'
@@ -7,6 +7,8 @@ require 'timeout'
7
7
  module Volt
8
8
  # The task dispatcher is responsible for taking incoming messages
9
9
  # from the socket channel and dispatching them to the proper handler.
10
+ #
11
+ # On the forking server, this runs in the child.
10
12
  class Dispatcher
11
13
  # When we pass the dispatcher over DRb, don't send a copy, just proxy.
12
14
  include DRb::DRbUndumped
@@ -51,7 +53,7 @@ module Volt
51
53
  # server, returning the result to the client.
52
54
  # Tasks returning a promise will wait to return.
53
55
  def dispatch(channel, message)
54
- # Dispatch the task in the worker pool. Pas in the meta data
56
+ # Dispatch the task in the worker pool. Pass in the meta data
55
57
  @worker_pool.post do
56
58
  begin
57
59
  dispatch_in_thread(channel, message)
@@ -9,16 +9,16 @@ module Volt
9
9
  # the block the value. The return value from the block replaces the
10
10
  # previous value.
11
11
  # NOTE: This does not yield hashes or arrays.
12
- def self.transform(data, &block)
12
+ def self.transform(data, transform_keys=true, &block)
13
13
  if data.is_a?(Hash)
14
14
  data.map do |key, value|
15
- key = transform(key, &block)
16
- value = transform(value, &block)
15
+ key = transform(key, transform_keys, &block) if transform_keys
16
+ value = transform(value, transform_keys, &block)
17
17
  [key, value]
18
18
  end.to_h
19
19
  elsif data.is_a?(Array)
20
20
  data.map do |value|
21
- transform(value, &block)
21
+ transform(value, transform_keys, &block)
22
22
  end
23
23
  else
24
24
  # yield to the trasnformer
@@ -1,5 +1,12 @@
1
1
  require 'json'
2
2
 
3
+ # We auto require time on the server, so we can decode VoltTime's in the parent
4
+ # process on the ForkingServer. (Since no user code is booted in the forking
5
+ # server). On the client the user has to require it if they want to use it.
6
+ unless RUBY_PLATFORM == 'opal'
7
+ require 'volt/helpers/time'
8
+ end
9
+
3
10
  module Volt
4
11
  class EJSON
5
12
  class NonEjsonType < Exception ; end
@@ -24,9 +31,15 @@ module Volt
24
31
  return escape.map do |key, value|
25
32
  [key, decode(value)]
26
33
  end.to_h
27
- elsif obj.size == 1 && (time = obj['$date'])
28
- if time.is_a?(Fixnum)
29
- return Time.at(time / 1000.0)
34
+ elsif obj.size == 1
35
+ if (time = obj['$date'])
36
+ if defined?(VoltTime)
37
+ if time.is_a?(Numeric)
38
+ return VoltTime.at(time / 1000.0)
39
+ end
40
+ else
41
+ raise "VoltTime is not defined, be sure to require 'volt/helpers/time'."
42
+ end
30
43
  end
31
44
  end
32
45
 
@@ -44,15 +57,15 @@ module Volt
44
57
  elsif Hash === obj
45
58
  obj.map do |key, value|
46
59
  if key == '$date'
60
+ value = {key => encode(value)}
47
61
  key = '$escape'
48
- value = {'$date' => encode(value)}
49
62
  else
50
63
  value = encode(value)
51
64
  end
52
65
 
53
66
  [key, value]
54
67
  end.to_h
55
- elsif Time === obj
68
+ elsif (defined?(VoltTime) && VoltTime === obj)
56
69
  {'$date' => obj.to_i * 1_000}
57
70
  elsif OTHER_VALID_CLASSES.any? {|klass| obj.is_a?(klass) }
58
71
  obj
@@ -62,4 +75,4 @@ module Volt
62
75
  end
63
76
  end
64
77
  end
65
- end
78
+ end
@@ -110,7 +110,7 @@ class Promise
110
110
  end
111
111
 
112
112
  if error
113
- if error.is_a?(RSpec::Expectations::ExpectationNotMetError)
113
+ if defined?(Rspec) && error.is_a?(RSpec::Expectations::ExpectationNotMetError)
114
114
  # re-raise
115
115
  raise error
116
116
  end
@@ -0,0 +1,749 @@
1
+ # Copied from https://github.com/opal/opal/blob/dd81fb4bfe337c0321ef1a8f7def2a3049a3fd83/opal/corelib/time.rb
2
+ # This patches time to the level in Opal 0.9.0 so that Volt can continue to run against Opal 0.8.0 and
3
+ # implement VoltTime
4
+
5
+ require 'corelib/comparable'
6
+
7
+ class Time
8
+ include Comparable
9
+
10
+ %x{
11
+ var days_of_week = #{%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday]},
12
+ short_days = #{%w[Sun Mon Tue Wed Thu Fri Sat]},
13
+ short_months = #{%w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]},
14
+ long_months = #{%w[January February March April May June July August September October November December]};
15
+ }
16
+
17
+ def self.at(seconds, frac = undefined)
18
+ %x{
19
+ var result;
20
+
21
+ if (#{Time === seconds}) {
22
+ if (frac !== undefined) {
23
+ #{raise TypeError, "can't convert Time into an exact number"}
24
+ }
25
+ result = new Date(seconds.getTime());
26
+ result.is_utc = seconds.is_utc;
27
+ return result;
28
+ }
29
+
30
+ if (!seconds.$$is_number) {
31
+ seconds = #{Opal.coerce_to!(seconds, Integer, :to_int)};
32
+ }
33
+
34
+ if (frac === undefined) {
35
+ return new Date(seconds * 1000);
36
+ }
37
+
38
+ if (!frac.$$is_number) {
39
+ frac = #{Opal.coerce_to!(frac, Integer, :to_int)};
40
+ }
41
+
42
+ return new Date(seconds * 1000 + (frac / 1000));
43
+ }
44
+ end
45
+
46
+ %x{
47
+ function time_params(year, month, day, hour, min, sec) {
48
+ if (year.$$is_string) {
49
+ year = parseInt(year, 10);
50
+ } else {
51
+ year = #{Opal.coerce_to!(`year`, Integer, :to_int)};
52
+ }
53
+
54
+ if (month === nil) {
55
+ month = 1;
56
+ } else if (!month.$$is_number) {
57
+ if (#{`month`.respond_to?(:to_str)}) {
58
+ month = #{`month`.to_str};
59
+ switch (month.toLowerCase()) {
60
+ case 'jan': month = 1; break;
61
+ case 'feb': month = 2; break;
62
+ case 'mar': month = 3; break;
63
+ case 'apr': month = 4; break;
64
+ case 'may': month = 5; break;
65
+ case 'jun': month = 6; break;
66
+ case 'jul': month = 7; break;
67
+ case 'aug': month = 8; break;
68
+ case 'sep': month = 9; break;
69
+ case 'oct': month = 10; break;
70
+ case 'nov': month = 11; break;
71
+ case 'dec': month = 12; break;
72
+ default: month = #{`month`.to_i};
73
+ }
74
+ } else {
75
+ month = #{Opal.coerce_to!(`month`, Integer, :to_int)};
76
+ }
77
+ }
78
+
79
+ if (month < 1 || month > 12) {
80
+ #{raise ArgumentError, "month out of range: #{`month`}"}
81
+ }
82
+ month = month - 1;
83
+
84
+ if (day === nil) {
85
+ day = 1;
86
+ } else if (day.$$is_string) {
87
+ day = parseInt(day, 10);
88
+ } else {
89
+ day = #{Opal.coerce_to!(`day`, Integer, :to_int)};
90
+ }
91
+
92
+ if (day < 1 || day > 31) {
93
+ #{raise ArgumentError, "day out of range: #{`day`}"}
94
+ }
95
+
96
+ if (hour === nil) {
97
+ hour = 0;
98
+ } else if (hour.$$is_string) {
99
+ hour = parseInt(hour, 10);
100
+ } else {
101
+ hour = #{Opal.coerce_to!(`hour`, Integer, :to_int)};
102
+ }
103
+
104
+ if (hour < 0 || hour > 24) {
105
+ #{raise ArgumentError, "hour out of range: #{`hour`}"}
106
+ }
107
+
108
+ if (min === nil) {
109
+ min = 0;
110
+ } else if (min.$$is_string) {
111
+ min = parseInt(min, 10);
112
+ } else {
113
+ min = #{Opal.coerce_to!(`min`, Integer, :to_int)};
114
+ }
115
+
116
+ if (min < 0 || min > 59) {
117
+ #{raise ArgumentError, "min out of range: #{`min`}"}
118
+ }
119
+
120
+ if (sec === nil) {
121
+ sec = 0;
122
+ } else if (!sec.$$is_number) {
123
+ if (sec.$$is_string) {
124
+ sec = parseInt(sec, 10);
125
+ } else {
126
+ sec = #{Opal.coerce_to!(`sec`, Integer, :to_int)};
127
+ }
128
+ }
129
+
130
+ if (sec < 0 || sec > 60) {
131
+ #{raise ArgumentError, "sec out of range: #{`sec`}"}
132
+ }
133
+
134
+ return [year, month, day, hour, min, sec];
135
+ }
136
+ }
137
+
138
+ def self.new(year = undefined, month = nil, day = nil, hour = nil, min = nil, sec = nil, utc_offset = nil)
139
+ %x{
140
+ var args, result;
141
+
142
+ if (year === undefined) {
143
+ return new Date();
144
+ }
145
+
146
+ if (utc_offset !== nil) {
147
+ #{raise ArgumentError, 'Opal does not support explicitly specifying UTC offset for Time'}
148
+ }
149
+
150
+ args = time_params(year, month, day, hour, min, sec);
151
+ year = args[0];
152
+ month = args[1];
153
+ day = args[2];
154
+ hour = args[3];
155
+ min = args[4];
156
+ sec = args[5];
157
+
158
+ result = new Date(year, month, day, hour, min, 0, sec * 1000);
159
+ if (year < 100) {
160
+ result.setFullYear(year);
161
+ }
162
+ return result;
163
+ }
164
+ end
165
+
166
+ def self.local(year, month = nil, day = nil, hour = nil, min = nil, sec = nil, millisecond = nil)
167
+ %x{
168
+ var args, result;
169
+
170
+ if (arguments.length === 10) {
171
+ args = $slice.call(arguments);
172
+ year = args[5];
173
+ month = args[4];
174
+ day = args[3];
175
+ hour = args[2];
176
+ min = args[1];
177
+ sec = args[0];
178
+ }
179
+
180
+ args = time_params(year, month, day, hour, min, sec);
181
+ year = args[0];
182
+ month = args[1];
183
+ day = args[2];
184
+ hour = args[3];
185
+ min = args[4];
186
+ sec = args[5];
187
+
188
+ result = new Date(year, month, day, hour, min, 0, sec * 1000);
189
+ if (year < 100) {
190
+ result.setFullYear(year);
191
+ }
192
+ return result;
193
+ }
194
+ end
195
+
196
+ def self.gm(year, month = nil, day = nil, hour = nil, min = nil, sec = nil, millisecond = nil)
197
+ %x{
198
+ var args, result;
199
+
200
+ if (arguments.length === 10) {
201
+ args = $slice.call(arguments);
202
+ year = args[5];
203
+ month = args[4];
204
+ day = args[3];
205
+ hour = args[2];
206
+ min = args[1];
207
+ sec = args[0];
208
+ }
209
+
210
+ args = time_params(year, month, day, hour, min, sec);
211
+ year = args[0];
212
+ month = args[1];
213
+ day = args[2];
214
+ hour = args[3];
215
+ min = args[4];
216
+ sec = args[5];
217
+
218
+ result = new Date(Date.UTC(year, month, day, hour, min, 0, sec * 1000));
219
+ if (year < 100) {
220
+ result.setUTCFullYear(year);
221
+ }
222
+ result.is_utc = true;
223
+ return result;
224
+ }
225
+ end
226
+
227
+ class << self
228
+ alias mktime local
229
+ alias utc gm
230
+ end
231
+
232
+ def self.now
233
+ new
234
+ end
235
+
236
+ def +(other)
237
+ if Time === other
238
+ raise TypeError, "time + time?"
239
+ end
240
+
241
+ %x{
242
+ if (!other.$$is_number) {
243
+ other = #{Opal.coerce_to!(other, Integer, :to_int)};
244
+ }
245
+ var result = new Date(self.getTime() + (other * 1000));
246
+ result.is_utc = self.is_utc;
247
+ return result;
248
+ }
249
+ end
250
+
251
+ def -(other)
252
+ if Time === other
253
+ return `(self.getTime() - other.getTime()) / 1000`
254
+ end
255
+
256
+ %x{
257
+ if (!other.$$is_number) {
258
+ other = #{Opal.coerce_to!(other, Integer, :to_int)};
259
+ }
260
+ var result = new Date(self.getTime() - (other * 1000));
261
+ result.is_utc = self.is_utc;
262
+ return result;
263
+ }
264
+ end
265
+
266
+ def <=>(other)
267
+ if Time === other
268
+ to_f <=> other.to_f
269
+ else
270
+ r = other <=> self
271
+ if r.nil?
272
+ nil
273
+ elsif r > 0
274
+ -1
275
+ elsif r < 0
276
+ 1
277
+ else
278
+ 0
279
+ end
280
+ end
281
+ end
282
+
283
+ def ==(other)
284
+ `#{to_f} === #{other.to_f}`
285
+ end
286
+
287
+ def asctime
288
+ strftime '%a %b %e %H:%M:%S %Y'
289
+ end
290
+
291
+ alias ctime asctime
292
+
293
+ def day
294
+ `self.is_utc ? self.getUTCDate() : self.getDate()`
295
+ end
296
+
297
+ def yday
298
+ %x{
299
+ // http://javascript.about.com/library/bldayyear.htm
300
+ var onejan = new Date(self.getFullYear(), 0, 1);
301
+ return Math.ceil((self - onejan) / 86400000);
302
+ }
303
+ end
304
+
305
+ def isdst
306
+ %x{
307
+ var jan = new Date(self.getFullYear(), 0, 1),
308
+ jul = new Date(self.getFullYear(), 6, 1);
309
+ return self.getTimezoneOffset() < Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
310
+ }
311
+ end
312
+
313
+ alias dst? isdst
314
+
315
+ def dup
316
+ copy = `new Date(self.getTime())`
317
+
318
+ copy.copy_instance_variables(self)
319
+ copy.initialize_dup(self)
320
+
321
+ copy
322
+ end
323
+
324
+ def eql?(other)
325
+ other.is_a?(Time) && (self <=> other).zero?
326
+ end
327
+
328
+ def friday?
329
+ `#{wday} == 5`
330
+ end
331
+
332
+ def hash
333
+ `'Time:' + self.getTime()`
334
+ end
335
+
336
+ def hour
337
+ `self.is_utc ? self.getUTCHours() : self.getHours()`
338
+ end
339
+
340
+ def inspect
341
+ if utc?
342
+ strftime '%Y-%m-%d %H:%M:%S UTC'
343
+ else
344
+ strftime '%Y-%m-%d %H:%M:%S %z'
345
+ end
346
+ end
347
+
348
+ alias mday day
349
+
350
+ def min
351
+ `self.is_utc ? self.getUTCMinutes() : self.getMinutes()`
352
+ end
353
+
354
+ def mon
355
+ `(self.is_utc ? self.getUTCMonth() : self.getMonth()) + 1`
356
+ end
357
+
358
+ def monday?
359
+ `#{wday} == 1`
360
+ end
361
+
362
+ alias month mon
363
+
364
+ def saturday?
365
+ `#{wday} == 6`
366
+ end
367
+
368
+ def sec
369
+ `self.is_utc ? self.getUTCSeconds() : self.getSeconds()`
370
+ end
371
+
372
+ def succ
373
+ %x{
374
+ var result = new Date(self.getTime() + 1000);
375
+ result.is_utc = self.is_utc;
376
+ return result;
377
+ }
378
+ end
379
+
380
+ def usec
381
+ `self.getMilliseconds() * 1000`
382
+ end
383
+
384
+ def zone
385
+ %x{
386
+ var string = self.toString(),
387
+ result;
388
+
389
+ if (string.indexOf('(') == -1) {
390
+ result = string.match(/[A-Z]{3,4}/)[0];
391
+ }
392
+ else {
393
+ result = string.match(/\([^)]+\)/)[0].match(/[A-Z]/g).join('');
394
+ }
395
+
396
+ if (result == "GMT" && /(GMT\W*\d{4})/.test(string)) {
397
+ return RegExp.$1;
398
+ }
399
+ else {
400
+ return result;
401
+ }
402
+ }
403
+ end
404
+
405
+ def getgm
406
+ %x{
407
+ var result = new Date(self.getTime());
408
+ result.is_utc = true;
409
+ return result;
410
+ }
411
+ end
412
+
413
+ alias getutc getgm
414
+
415
+ def gmtime
416
+ %x{
417
+ self.is_utc = true;
418
+ return self;
419
+ }
420
+ end
421
+
422
+ alias utc gmtime
423
+
424
+ def gmt?
425
+ `self.is_utc === true`
426
+ end
427
+
428
+ def gmt_offset
429
+ `-self.getTimezoneOffset() * 60`
430
+ end
431
+
432
+ def strftime(format)
433
+ %x{
434
+ return format.replace(/%([\-_#^0]*:{0,2})(\d+)?([EO]*)(.)/g, function(full, flags, width, _, conv) {
435
+ var result = "",
436
+ zero = flags.indexOf('0') !== -1,
437
+ pad = flags.indexOf('-') === -1,
438
+ blank = flags.indexOf('_') !== -1,
439
+ upcase = flags.indexOf('^') !== -1,
440
+ invert = flags.indexOf('#') !== -1,
441
+ colons = (flags.match(':') || []).length;
442
+
443
+ width = parseInt(width, 10);
444
+
445
+ if (zero && blank) {
446
+ if (flags.indexOf('0') < flags.indexOf('_')) {
447
+ zero = false;
448
+ }
449
+ else {
450
+ blank = false;
451
+ }
452
+ }
453
+
454
+ switch (conv) {
455
+ case 'Y':
456
+ result += #{year};
457
+ break;
458
+
459
+ case 'C':
460
+ zero = !blank;
461
+ result += Math.round(#{year} / 100);
462
+ break;
463
+
464
+ case 'y':
465
+ zero = !blank;
466
+ result += (#{year} % 100);
467
+ break;
468
+
469
+ case 'm':
470
+ zero = !blank;
471
+ result += #{mon};
472
+ break;
473
+
474
+ case 'B':
475
+ result += long_months[#{mon} - 1];
476
+ break;
477
+
478
+ case 'b':
479
+ case 'h':
480
+ blank = !zero;
481
+ result += short_months[#{mon} - 1];
482
+ break;
483
+
484
+ case 'd':
485
+ zero = !blank
486
+ result += #{day};
487
+ break;
488
+
489
+ case 'e':
490
+ blank = !zero
491
+ result += #{day};
492
+ break;
493
+
494
+ case 'j':
495
+ result += #{yday};
496
+ break;
497
+
498
+ case 'H':
499
+ zero = !blank;
500
+ result += #{hour};
501
+ break;
502
+
503
+ case 'k':
504
+ blank = !zero;
505
+ result += #{hour};
506
+ break;
507
+
508
+ case 'I':
509
+ zero = !blank;
510
+ result += (#{hour} % 12 || 12);
511
+ break;
512
+
513
+ case 'l':
514
+ blank = !zero;
515
+ result += (#{hour} % 12 || 12);
516
+ break;
517
+
518
+ case 'P':
519
+ result += (#{hour} >= 12 ? "pm" : "am");
520
+ break;
521
+
522
+ case 'p':
523
+ result += (#{hour} >= 12 ? "PM" : "AM");
524
+ break;
525
+
526
+ case 'M':
527
+ zero = !blank;
528
+ result += #{min};
529
+ break;
530
+
531
+ case 'S':
532
+ zero = !blank;
533
+ result += #{sec}
534
+ break;
535
+
536
+ case 'L':
537
+ zero = !blank;
538
+ width = isNaN(width) ? 3 : width;
539
+ result += self.getMilliseconds();
540
+ break;
541
+
542
+ case 'N':
543
+ width = isNaN(width) ? 9 : width;
544
+ result += #{`self.getMilliseconds().toString()`.rjust(3, '0')};
545
+ result = #{`result`.ljust(`width`, '0')};
546
+ break;
547
+
548
+ case 'z':
549
+ var offset = self.getTimezoneOffset(),
550
+ hours = Math.floor(Math.abs(offset) / 60),
551
+ minutes = Math.abs(offset) % 60;
552
+
553
+ result += offset < 0 ? "+" : "-";
554
+ result += hours < 10 ? "0" : "";
555
+ result += hours;
556
+
557
+ if (colons > 0) {
558
+ result += ":";
559
+ }
560
+
561
+ result += minutes < 10 ? "0" : "";
562
+ result += minutes;
563
+
564
+ if (colons > 1) {
565
+ result += ":00";
566
+ }
567
+
568
+ break;
569
+
570
+ case 'Z':
571
+ result += #{zone};
572
+ break;
573
+
574
+ case 'A':
575
+ result += days_of_week[#{wday}];
576
+ break;
577
+
578
+ case 'a':
579
+ result += short_days[#{wday}];
580
+ break;
581
+
582
+ case 'u':
583
+ result += (#{wday} + 1);
584
+ break;
585
+
586
+ case 'w':
587
+ result += #{wday};
588
+ break;
589
+
590
+ case 'V':
591
+ result += #{cweek_cyear[0].to_s.rjust(2, "0")};
592
+ break;
593
+
594
+ case 'G':
595
+ result += #{cweek_cyear[1]};
596
+ break;
597
+
598
+ case 'g':
599
+ result += #{cweek_cyear[1][-2..-1]};
600
+ break;
601
+
602
+ case 's':
603
+ result += #{to_i};
604
+ break;
605
+
606
+ case 'n':
607
+ result += "\n";
608
+ break;
609
+
610
+ case 't':
611
+ result += "\t";
612
+ break;
613
+
614
+ case '%':
615
+ result += "%";
616
+ break;
617
+
618
+ case 'c':
619
+ result += #{strftime('%a %b %e %T %Y')};
620
+ break;
621
+
622
+ case 'D':
623
+ case 'x':
624
+ result += #{strftime('%m/%d/%y')};
625
+ break;
626
+
627
+ case 'F':
628
+ result += #{strftime('%Y-%m-%d')};
629
+ break;
630
+
631
+ case 'v':
632
+ result += #{strftime('%e-%^b-%4Y')};
633
+ break;
634
+
635
+ case 'r':
636
+ result += #{strftime('%I:%M:%S %p')};
637
+ break;
638
+
639
+ case 'R':
640
+ result += #{strftime('%H:%M')};
641
+ break;
642
+
643
+ case 'T':
644
+ case 'X':
645
+ result += #{strftime('%H:%M:%S')};
646
+ break;
647
+
648
+ default:
649
+ return full;
650
+ }
651
+
652
+ if (upcase) {
653
+ result = result.toUpperCase();
654
+ }
655
+
656
+ if (invert) {
657
+ result = result.replace(/[A-Z]/, function(c) { c.toLowerCase() }).
658
+ replace(/[a-z]/, function(c) { c.toUpperCase() });
659
+ }
660
+
661
+ if (pad && (zero || blank)) {
662
+ result = #{`result`.rjust(`isNaN(width) ? 2 : width`, `blank ? " " : "0"`)};
663
+ }
664
+
665
+ return result;
666
+ });
667
+ }
668
+ end
669
+
670
+ def sunday?
671
+ `#{wday} == 0`
672
+ end
673
+
674
+ def thursday?
675
+ `#{wday} == 4`
676
+ end
677
+
678
+ def to_a
679
+ [sec, min, hour, day, month, year, wday, yday, isdst, zone]
680
+ end
681
+
682
+ def to_f
683
+ `self.getTime() / 1000`
684
+ end
685
+
686
+ def to_i
687
+ `parseInt(self.getTime() / 1000, 10)`
688
+ end
689
+
690
+ alias to_s inspect
691
+
692
+ def tuesday?
693
+ `#{wday} == 2`
694
+ end
695
+
696
+ alias tv_sec sec
697
+
698
+ alias tv_usec usec
699
+
700
+ alias utc? gmt?
701
+
702
+ alias gmtoff gmt_offset
703
+ alias utc_offset gmt_offset
704
+
705
+ def wday
706
+ `self.is_utc ? self.getUTCDay() : self.getDay()`
707
+ end
708
+
709
+ def wednesday?
710
+ `#{wday} == 3`
711
+ end
712
+
713
+ def year
714
+ `self.is_utc ? self.getUTCFullYear() : self.getFullYear()`
715
+ end
716
+
717
+ def cweek_cyear
718
+ jan01 = Time.new(self.year, 1, 1)
719
+ jan01_wday = jan01.wday
720
+ first_monday = 0
721
+ year = self.year
722
+ if jan01_wday <= 4 && jan01_wday != 0
723
+ #Jan 01 is in the first week of the year
724
+ offset = jan01_wday-1
725
+ else
726
+ #Jan 01 is in the last week of the previous year
727
+ offset = jan01_wday-7-1
728
+ offset = -1 if offset == -8 #Adjust if Jan 01 is a Sunday
729
+ end
730
+
731
+ week = ((self.yday+offset)/7.00).ceil
732
+
733
+ if week <= 0
734
+ #Get the last week of the previous year
735
+ return Time.new(self.year-1, 12, 31).cweek_cyear
736
+ elsif week == 53
737
+ #Find out whether this is actually week 53 or already week 01 of the following year
738
+ dec31 = Time.new(self.year, 12, 31)
739
+ dec31_wday = dec31.wday
740
+ if dec31_wday <= 3 && dec31_wday != 0
741
+ week = 1
742
+ year += 1
743
+ end
744
+ end
745
+
746
+ [week, year]
747
+
748
+ end
749
+ end