volt 0.9.6 → 0.9.7.pre2

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 (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