From 54d80e838137e85102b8781f157a4d472a2db200 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 30 Jan 2020 22:57:59 +1300 Subject: [PATCH 001/166] Add notify_pipe option to select child file descriptor. --- lib/async/container/notify/pipe.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index f316763..bed79a1 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -58,9 +58,11 @@ def before_exec(arguments) def before_spawn(arguments, options) environment = environment_for(arguments) - options[3] = @io + notify_pipe = options.delete(:notify_pipe) || 3 + + options[notify_pipe] = @io - environment[NOTIFY_PIPE] = "3" + environment[NOTIFY_PIPE] = notify_pipe.to_s end def send(**message) From b666952543db55a624c6917f05e9b8454328b5c5 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 30 Jan 2020 23:00:36 +1300 Subject: [PATCH 002/166] Handle process reaping a bit more quickly. --- lib/async/container/process.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 3f51a8c..097c1c5 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -147,9 +147,13 @@ def terminate! def wait unless @status - sleep(0.1) pid, @status = ::Process.wait2(@pid, ::Process::WNOHANG) + if @status.nil? + sleep(0.01) + pid, @status = ::Process.wait2(@pid, ::Process::WNOHANG) + end + if @status.nil? Async.logger.warn(self) {"Process #{@pid} is blocking, has it exited?"} pid, @status = ::Process.wait2(@pid) From 9925ccd47e375f4f784dd32e1f16c810aca9034a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 30 Jan 2020 23:01:22 +1300 Subject: [PATCH 003/166] Reduce test time. --- spec/async/container/notify/notify.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/async/container/notify/notify.rb b/spec/async/container/notify/notify.rb index 5eb9ea1..ac0e725 100755 --- a/spec/async/container/notify/notify.rb +++ b/spec/async/container/notify/notify.rb @@ -5,11 +5,11 @@ class MyController < Async::Container::Controller def setup(container) container.run(restart: false) do |instance| - sleep(rand(1..8)) + sleep(rand) instance.ready! - sleep(1) + sleep(rand) end end end From 72623091a5de314e4a6cd4d3e7bd788114f15190 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 30 Jan 2020 23:14:22 +1300 Subject: [PATCH 004/166] Don't try to wait if pid is nil (can happen if fork fails). --- lib/async/container/process.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 097c1c5..4fa387d 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -146,7 +146,7 @@ def terminate! end def wait - unless @status + if @pid && @status.nil? pid, @status = ::Process.wait2(@pid, ::Process::WNOHANG) if @status.nil? From d9637b030f0dc8bb138ea797d10813485553f5e1 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 30 Jan 2020 23:15:24 +1300 Subject: [PATCH 005/166] Whitespace. --- lib/async/container/notify/pipe.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index bed79a1..a3fe3b0 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -59,9 +59,8 @@ def before_spawn(arguments, options) environment = environment_for(arguments) notify_pipe = options.delete(:notify_pipe) || 3 - - options[notify_pipe] = @io + options[notify_pipe] = @io environment[NOTIFY_PIPE] = notify_pipe.to_s end From cf5ee2ca4ba6fd2cbb5d4e3fc821cda5c993bfd1 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 30 Jan 2020 23:25:04 +1300 Subject: [PATCH 006/166] Improve robustness of `#before_spawn` and prefer stdout. TruffleRuby is having issues with `3 => io` redirects. --- lib/async/container/notify/pipe.rb | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index a3fe3b0..30bc111 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -58,10 +58,25 @@ def before_exec(arguments) def before_spawn(arguments, options) environment = environment_for(arguments) - notify_pipe = options.delete(:notify_pipe) || 3 + # Use `notify_pipe` option if specified: + if notify_pipe = options.delete(:notify_pipe) + options[notify_pipe] = @io + environment[NOTIFY_PIPE] = notify_pipe.to_s - options[notify_pipe] = @io - environment[NOTIFY_PIPE] = notify_pipe.to_s + # Use stdout if it's not redirected: + elsif !options.key?(:out) + options[:out] = @io + environment[NOTIFY_PIPE] = "1" + + # Use fileno 3 if it's available: + elsif !options.key?(3) + options[3] = @io + environment[NOTIFY_PIPE] = 3 + + # Otherwise, give up! + else + raise ArgumentError, "Please specify valid file descriptor for notify_pipe!" + end end def send(**message) From b7cb28d4a9b66f23e6f64c2c9e6baa53e46604ff Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 30 Jan 2020 23:44:47 +1300 Subject: [PATCH 007/166] Fix up TruffleRuby issues. --- lib/async/container/process.rb | 6 +++--- lib/async/container/thread.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 4fa387d..12224d0 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -147,16 +147,16 @@ def terminate! def wait if @pid && @status.nil? - pid, @status = ::Process.wait2(@pid, ::Process::WNOHANG) + _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) if @status.nil? sleep(0.01) - pid, @status = ::Process.wait2(@pid, ::Process::WNOHANG) + _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) end if @status.nil? Async.logger.warn(self) {"Process #{@pid} is blocking, has it exited?"} - pid, @status = ::Process.wait2(@pid) + _, @status = ::Process.wait2(@pid) end end diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index 0d0651b..06c8ef8 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -72,7 +72,7 @@ def exec(*arguments, ready: true, **options) begin pid = ::Process.spawn(*arguments, **options) ensure - _, status = ::Process.wait2(@pid) + _, status = ::Process.wait2(pid) raise Exit, status end @@ -100,7 +100,7 @@ def initialize(name: nil) begin @thread.join rescue Exit => exit - finished(exit.result) + finished(exit.error) rescue Interrupt # Graceful shutdown. finished From a00e814ce378bfd8263a2deda5e8e732c248d989 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 31 Jan 2020 00:05:11 +1300 Subject: [PATCH 008/166] Fix options handling for `Process.spawn`. --- lib/async/container/thread.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index 06c8ef8..e7dcf6b 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -70,7 +70,8 @@ def exec(*arguments, ready: true, **options) end begin - pid = ::Process.spawn(*arguments, **options) + # TODO prefer **options... but it doesn't support redirections on < 2.7 + pid = ::Process.spawn(*arguments, options) ensure _, status = ::Process.wait2(pid) From 99ea8a9862861a386a9421e921a6b6be03cff8d1 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 31 Jan 2020 00:10:48 +1300 Subject: [PATCH 009/166] Validate state from notification pipe. --- lib/async/container/generic.rb | 2 ++ spec/async/container/notify/pipe_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 3f6dafb..ebb6128 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -52,6 +52,8 @@ def initialize(**options) @keyed = {} end + attr :state + def to_s "#{self.class} with #{@statistics.spawns} spawns and #{@statistics.failures} failures." end diff --git a/spec/async/container/notify/pipe_spec.rb b/spec/async/container/notify/pipe_spec.rb index eb66a80..ae5ae98 100644 --- a/spec/async/container/notify/pipe_spec.rb +++ b/spec/async/container/notify/pipe_spec.rb @@ -30,6 +30,12 @@ instance.exec("bundle", "exec", notify_script, ready: false) end + # Wait for the state to be updated by the child process: + container.sleep + + child, state = container.state.first + expect(state).to be == {:status=>"Initializing..."} + container.wait expect(container.statistics).to have_attributes(failures: 0) From e4f52e3dac151f6afca7385f5e9d50826e3672a6 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 31 Jan 2020 00:49:59 +1300 Subject: [PATCH 010/166] Trying to fix pipe spec on < 2.6? --- lib/async/container/notify/pipe.rb | 2 +- lib/async/container/process.rb | 2 +- spec/async/container/notify/pipe_spec.rb | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index 30bc111..7b73c81 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -71,7 +71,7 @@ def before_spawn(arguments, options) # Use fileno 3 if it's available: elsif !options.key?(3) options[3] = @io - environment[NOTIFY_PIPE] = 3 + environment[NOTIFY_PIPE] = "3" # Otherwise, give up! else diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 12224d0..be18ac2 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -101,7 +101,7 @@ def initialize(name: nil) @status = nil @pid = nil - @pid = yield self + @pid = yield(self) # The parent process won't be writing to the channel: self.close_write diff --git a/spec/async/container/notify/pipe_spec.rb b/spec/async/container/notify/pipe_spec.rb index ae5ae98..4b91890 100644 --- a/spec/async/container/notify/pipe_spec.rb +++ b/spec/async/container/notify/pipe_spec.rb @@ -27,7 +27,10 @@ container = Async::Container.new container.spawn(restart: false) do |instance| - instance.exec("bundle", "exec", notify_script, ready: false) + instance.exec( + # "bundle", "exec", "--keep-file-descriptors", + notify_script, ready: false + ) end # Wait for the state to be updated by the child process: From a387abb92aaa8f5d042006d9a08a777f58443718 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 31 Jan 2020 01:07:14 +1300 Subject: [PATCH 011/166] Consistent handling of options for `#spawn` and `#exec`. --- lib/async/container/notify/pipe.rb | 9 --------- lib/async/container/process.rb | 4 ++-- spec/async/container/notify/pipe_spec.rb | 2 +- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index 7b73c81..bd5a36f 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -44,15 +44,6 @@ def initialize(io) @io = io end - def before_exec(arguments) - @io.close_on_exec = false - - # Insert or duplicate the environment hash which is the first argument: - environment = environment_for(arguments) - - environment[NOTIFY_PIPE] = @io.fileno.to_s - end - # Inserts or duplicates the environment given an argument array. # Sets or clears it in a way that is suitable for {::Process.spawn}. def before_spawn(arguments, options) diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index be18ac2..b6c224c 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -58,10 +58,10 @@ def exec(*arguments, ready: true, **options) if ready self.ready!(status: "(exec)") if ready else - self.before_exec(arguments) + self.before_spawn(arguments, options) end - ::Process.exec(*arguments, **options) + ::Process.exec(*arguments, options) end end diff --git a/spec/async/container/notify/pipe_spec.rb b/spec/async/container/notify/pipe_spec.rb index 4b91890..c106958 100644 --- a/spec/async/container/notify/pipe_spec.rb +++ b/spec/async/container/notify/pipe_spec.rb @@ -37,7 +37,7 @@ container.sleep child, state = container.state.first - expect(state).to be == {:status=>"Initializing..."} + expect(state).to be == {status: "Initializing..."} container.wait From 10971109e4399074ce2be1f42f8e03ecf309ef0b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 31 Jan 2020 01:13:31 +1300 Subject: [PATCH 012/166] Use bundle exec to ensure gems are available. --- spec/async/container/notify/pipe_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/async/container/notify/pipe_spec.rb b/spec/async/container/notify/pipe_spec.rb index c106958..3789c42 100644 --- a/spec/async/container/notify/pipe_spec.rb +++ b/spec/async/container/notify/pipe_spec.rb @@ -28,7 +28,7 @@ container.spawn(restart: false) do |instance| instance.exec( - # "bundle", "exec", "--keep-file-descriptors", + "bundle", "exec", "--keep-file-descriptors", notify_script, ready: false ) end From b78c45c18a132e403d7df2541b5490e3012d17bc Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 31 Jan 2020 01:39:07 +1300 Subject: [PATCH 013/166] Remove unused gems. --- Gemfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Gemfile b/Gemfile index d9bf345..56af646 100644 --- a/Gemfile +++ b/Gemfile @@ -14,7 +14,4 @@ end group :test do gem 'benchmark-ips' gem 'ruby-prof', platforms: :mri - - gem 'simplecov' - gem 'coveralls', require: false end From 81d96ee7baf60e8025e9b5634fd994be4d3d422d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 31 Jan 2020 01:56:28 +1300 Subject: [PATCH 014/166] Increase reliability of test. --- spec/async/container/controller_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/async/container/controller_spec.rb b/spec/async/container/controller_spec.rb index 2cdfc78..45307ae 100644 --- a/spec/async/container/controller_spec.rb +++ b/spec/async/container/controller_spec.rb @@ -31,18 +31,18 @@ def subject.setup(container) container.spawn(key: "test") do |instance| instance.ready! - sleep(0.1) + sleep(0.2) @output.write(".") @output.flush - sleep(0.2) + sleep(0.4) end container.spawn do |instance| instance.ready! - sleep(0.2) + sleep(0.3) @output.write(",") @output.flush From 78943fd6dbdf42395fafac0c14dbce7e0183fa41 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 2 Feb 2020 02:08:19 +1300 Subject: [PATCH 015/166] Fix duplicate `#reloading!`, missing `#restarting!`. --- lib/async/container/notify/pipe.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index bd5a36f..a6fe506 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -81,10 +81,10 @@ def ready!(**message) send(ready: true, **message) end - def reloading!(**message) + def restarting!(**message) message[:ready] = false message[:reloading] = true - message[:status] ||= "Reloading..." + message[:status] ||= "Restarting..." send(**message) end From 5c5b5fc7681c42c8ed8f1ee29dad181a25fce5af Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 2 Feb 2020 02:08:45 +1300 Subject: [PATCH 016/166] Prefer `frozen_string_literal: true`. --- Gemfile | 2 ++ Rakefile | 2 ++ examples/async.rb | 1 + examples/channel.rb | 1 + examples/channels/client.rb | 1 + examples/container.rb | 1 + examples/isolate.rb | 1 + examples/minimal.rb | 1 + examples/test.rb | 1 + examples/threads.rb | 1 + examples/title.rb | 1 + examples/udppipe.rb | 1 + lib/async/container.rb | 2 ++ lib/async/container/best.rb | 2 ++ lib/async/container/channel.rb | 2 ++ lib/async/container/controller.rb | 2 ++ lib/async/container/error.rb | 2 ++ lib/async/container/forked.rb | 2 ++ lib/async/container/generic.rb | 2 ++ lib/async/container/group.rb | 2 ++ lib/async/container/hybrid.rb | 2 ++ lib/async/container/keyed.rb | 2 ++ lib/async/container/notify.rb | 1 + lib/async/container/notify/client.rb | 1 + lib/async/container/notify/pipe.rb | 1 + lib/async/container/notify/server.rb | 1 + lib/async/container/notify/socket.rb | 1 + lib/async/container/process.rb | 2 ++ lib/async/container/statistics.rb | 2 ++ lib/async/container/thread.rb | 2 ++ lib/async/container/threaded.rb | 2 ++ lib/async/container/version.rb | 2 ++ spec/async/container/controller_spec.rb | 2 ++ spec/async/container/forked_spec.rb | 2 ++ spec/async/container/hybrid_spec.rb | 2 ++ spec/async/container/notify/notify.rb | 1 + spec/async/container/notify/pipe_spec.rb | 2 ++ spec/async/container/notify_spec.rb | 2 ++ spec/async/container/shared_examples.rb | 2 ++ spec/async/container/threaded_spec.rb | 2 ++ spec/async/container_spec.rb | 2 ++ spec/spec_helper.rb | 1 + 42 files changed, 67 insertions(+) diff --git a/Gemfile b/Gemfile index 56af646..b0d366c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' # Specify your gem's dependencies in utopia.gemspec diff --git a/Rakefile b/Rakefile index f5cbbfd..366c7f8 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "bundler/gem_tasks" require "rspec/core/rake_task" diff --git a/examples/async.rb b/examples/async.rb index d8abb43..656edd2 100644 --- a/examples/async.rb +++ b/examples/async.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'kernel/sync' diff --git a/examples/channel.rb b/examples/channel.rb index 1510c58..5b92a0b 100644 --- a/examples/channel.rb +++ b/examples/channel.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'json' diff --git a/examples/channels/client.rb b/examples/channels/client.rb index b1c78b3..360100b 100644 --- a/examples/channels/client.rb +++ b/examples/channels/client.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'msgpack' require 'async/io' diff --git a/examples/container.rb b/examples/container.rb index 49c67e5..c8a4b23 100755 --- a/examples/container.rb +++ b/examples/container.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require '../lib/async/container/controller' require '../lib/async/container/forked' diff --git a/examples/isolate.rb b/examples/isolate.rb index 084e459..86d0499 100644 --- a/examples/isolate.rb +++ b/examples/isolate.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # We define end of life-cycle in terms of "Interrupt" (SIGINT), "Terminate" (SIGTERM) and "Kill" (SIGKILL, does not invoke user code). class Terminate < Interrupt diff --git a/examples/minimal.rb b/examples/minimal.rb index 28b4f5e..d46ce12 100644 --- a/examples/minimal.rb +++ b/examples/minimal.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true class Threaded def initialize(&block) diff --git a/examples/test.rb b/examples/test.rb index d72686c..e2e94f8 100644 --- a/examples/test.rb +++ b/examples/test.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require_relative 'group' require_relative 'thread' diff --git a/examples/threads.rb b/examples/threads.rb index a3b13e1..c8d73db 100755 --- a/examples/threads.rb +++ b/examples/threads.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true puts "Process pid: #{Process.pid}" diff --git a/examples/title.rb b/examples/title.rb index 47875a0..2328f72 100755 --- a/examples/title.rb +++ b/examples/title.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true Process.setproctitle "Preparing for sleep..." diff --git a/examples/udppipe.rb b/examples/udppipe.rb index 3d096b9..a8fe334 100644 --- a/examples/udppipe.rb +++ b/examples/udppipe.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require 'async/io' require 'async/io/endpoint' diff --git a/lib/async/container.rb b/lib/async/container.rb index e3e1228..016fd57 100644 --- a/lib/async/container.rb +++ b/lib/async/container.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/best.rb b/lib/async/container/best.rb index 4af94f0..c666d81 100644 --- a/lib/async/container/best.rb +++ b/lib/async/container/best.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/channel.rb b/lib/async/container/channel.rb index 1428f43..baa652b 100644 --- a/lib/async/container/channel.rb +++ b/lib/async/container/channel.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index d114571..7ae07f9 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index 65bd58a..ed92ea0 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 61be0ec..790533c 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index ebb6128..09c3ca5 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 67b305f..b2b5d53 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/hybrid.rb b/lib/async/container/hybrid.rb index c310af0..6456de3 100644 --- a/lib/async/container/hybrid.rb +++ b/lib/async/container/hybrid.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/keyed.rb b/lib/async/container/keyed.rb index cfb1ba3..ede6178 100644 --- a/lib/async/container/keyed.rb +++ b/lib/async/container/keyed.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/notify.rb b/lib/async/container/notify.rb index 71515c2..d614400 100644 --- a/lib/async/container/notify.rb +++ b/lib/async/container/notify.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # # Copyright, 2020, by Samuel G. D. Williams. # diff --git a/lib/async/container/notify/client.rb b/lib/async/container/notify/client.rb index 34be9e1..c2480f2 100644 --- a/lib/async/container/notify/client.rb +++ b/lib/async/container/notify/client.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # # Copyright, 2020, by Samuel G. D. Williams. # diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index a6fe506..0c32c58 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # # Copyright, 2020, by Samuel G. D. Williams. # diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index 76e04ca..e1756d4 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # # Copyright, 2020, by Samuel G. D. Williams. # diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index 34d7323..afba5dc 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # # Copyright, 2020, by Samuel G. D. Williams. # diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index b6c224c..06c25d0 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/statistics.rb b/lib/async/container/statistics.rb index 47ef852..80c97ea 100644 --- a/lib/async/container/statistics.rb +++ b/lib/async/container/statistics.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index e7dcf6b..ea1ddef 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index 57f0171..d50aa16 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 81d0be6..98db485 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/async/container/controller_spec.rb b/spec/async/container/controller_spec.rb index 45307ae..c408b19 100644 --- a/spec/async/container/controller_spec.rb +++ b/spec/async/container/controller_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/async/container/forked_spec.rb b/spec/async/container/forked_spec.rb index b3f09d2..682e7fb 100644 --- a/spec/async/container/forked_spec.rb +++ b/spec/async/container/forked_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/async/container/hybrid_spec.rb b/spec/async/container/hybrid_spec.rb index 5db1e52..1fb5797 100644 --- a/spec/async/container/hybrid_spec.rb +++ b/spec/async/container/hybrid_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/async/container/notify/notify.rb b/spec/async/container/notify/notify.rb index ac0e725..e47912c 100755 --- a/spec/async/container/notify/notify.rb +++ b/spec/async/container/notify/notify.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require_relative '../../../../lib/async/container' diff --git a/spec/async/container/notify/pipe_spec.rb b/spec/async/container/notify/pipe_spec.rb index 3789c42..80a87bb 100644 --- a/spec/async/container/notify/pipe_spec.rb +++ b/spec/async/container/notify/pipe_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/async/container/notify_spec.rb b/spec/async/container/notify_spec.rb index fc70655..ec47377 100644 --- a/spec/async/container/notify_spec.rb +++ b/spec/async/container/notify_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/async/container/shared_examples.rb b/spec/async/container/shared_examples.rb index 85eb1a0..f0c61a0 100644 --- a/spec/async/container/shared_examples.rb +++ b/spec/async/container/shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/async/container/threaded_spec.rb b/spec/async/container/threaded_spec.rb index 440a469..0510d42 100644 --- a/spec/async/container/threaded_spec.rb +++ b/spec/async/container/threaded_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/async/container_spec.rb b/spec/async/container_spec.rb index a9df396..9d2d480 100644 --- a/spec/async/container_spec.rb +++ b/spec/async/container_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d6bf0fb..74c7269 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'covered/rspec' From be39475d1bbd2950a8f4d3fa5eb2eedfcc03425c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 2 Feb 2020 02:09:12 +1300 Subject: [PATCH 017/166] Bump version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 98db485..a121610 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.0" + VERSION = "0.16.1" end end From 6ca2e1a7844bf3d5a489b8bf4b89e09650bb7720 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 2 Feb 2020 02:15:10 +1300 Subject: [PATCH 018/166] Experiment with improved `#to_s`. --- lib/async/container/generic.rb | 2 +- lib/async/container/process.rb | 8 +------- lib/async/container/thread.rb | 6 +----- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 09c3ca5..69fe9fb 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -144,7 +144,7 @@ def spawn(name: nil, restart: false, key: nil, &block) end if status.success? - Async.logger.info(self) {"#{child} #{status}"} + Async.logger.info(self) {"#{child} exited with #{status}"} else @statistics.failure! Async.logger.error(self) {status} diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 06c25d0..53a46f1 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -119,13 +119,7 @@ def name= value attr :name def to_s - if @status - "\#<#{self.class} #{@name} -> #{@status}>" - elsif @pid - "\#<#{self.class} #{@name} -> #{@pid}>" - else - "\#<#{self.class} #{@name}>" - end + "\#<#{self.class} #{@name}>" end def close diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index ea1ddef..d422760 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -124,11 +124,7 @@ def name end def to_s - if @status - "\#<#{self.class} #{@thread.name} -> #{@status}>" - else - "\#<#{self.class} #{@thread.name}>" - end + "\#<#{self.class} #{@thread.name}>" end def close From e6159489b97ceeff208186c3c8498bb6c8cc3c8f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 29 Feb 2020 10:52:48 +1300 Subject: [PATCH 019/166] If no process readiness protocol is available, log to the console. --- lib/async/container/notify.rb | 4 +- lib/async/container/notify/console.rb | 70 +++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 lib/async/container/notify/console.rb diff --git a/lib/async/container/notify.rb b/lib/async/container/notify.rb index d614400..3d7d09d 100644 --- a/lib/async/container/notify.rb +++ b/lib/async/container/notify.rb @@ -23,6 +23,7 @@ require_relative 'notify/pipe' require_relative 'notify/socket' +require_relative 'notify/console' module Async module Container @@ -34,7 +35,8 @@ def self.open! # Select the best available client: @@client ||= ( Pipe.open! || - Socket.open! + Socket.open! || + Console.open! ) end end diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb new file mode 100644 index 0000000..1fe4f9b --- /dev/null +++ b/lib/async/container/notify/console.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# +# Copyright, 2020, by Samuel G. D. Williams. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +require_relative 'client' + +require 'console/logger' + +module Async + module Container + module Notify + class Console < Client + def self.open!(logger = ::Console.logger) + self.new(logger) + end + + def initialize(logger) + @logger = logger + end + + def send(level: :debug, **message) + @logger.send(level, self) {message[:status]} + end + + def ready!(**message) + send(ready: true, **message) + end + + def restarting!(**message) + message[:ready] = false + message[:reloading] = true + message[:status] ||= "Restarting..." + + send(**message) + end + + def reloading!(**message) + message[:ready] = false + message[:reloading] = true + message[:status] ||= "Reloading..." + + send(**message) + end + + def error!(text, **message) + send(status: text, level: :error, **message) + end + end + end + end +end From 437ba12da6d3ef20fe75890701fc89906730277d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Mar 2020 23:40:32 +1300 Subject: [PATCH 020/166] Don't ignore exceptions... --- lib/async/container/controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 7ae07f9..1924001 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -142,6 +142,8 @@ def restart rescue # If we are leaving this function with an exception, try to kill the container: container&.stop(false) + + raise end def reload From 17b2ca0dd594416d3ae27ba9385f77ba4f2e73fe Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Mar 2020 23:40:50 +1300 Subject: [PATCH 021/166] Ensure stop is always called, no matter what. --- lib/async/container/controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 1924001..be73ddc 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -190,8 +190,8 @@ def run if handler = @signals[exception.signo] begin handler.call - rescue ContainerError => failure - Async.logger.error(self) {failure} + rescue ContainerError => error + Async.logger.error(self) {error} end else raise @@ -202,9 +202,9 @@ def run self.stop(true) rescue Terminate self.stop(false) - else - self.stop(true) ensure + self.stop(true) + # Restore the interrupt handler: Signal.trap(:INT, interrupt_action) Signal.trap(:TERM, terminate_action) From c34a9e8967726fea51da5b9c79b0b0eeffc0592c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Mar 2020 23:40:56 +1300 Subject: [PATCH 022/166] Bump version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index a121610..5b7d3a3 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.1" + VERSION = "0.16.2" end end From 45a2f5dab80e21647a14bc79cf1ce869f5f42ebe Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Mar 2020 23:45:35 +1300 Subject: [PATCH 023/166] Add spec for container initialization failure. --- lib/async/container/controller.rb | 13 +++++++------ spec/async/container/controller_spec.rb | 11 +++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index be73ddc..bb8b5a1 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -28,9 +28,10 @@ module Async module Container - class ContainerError < Error + class InitializationError < Error def initialize(container) super("Could not create container!") + @container = container end @@ -117,7 +118,7 @@ def restart rescue @notify&.error!($!.to_s) - raise ContainerError, container + raise InitializationError, container end # Wait for all child processes to enter the ready state. @@ -130,7 +131,7 @@ def restart container.stop - raise ContainerError, container + raise InitializationError, container end # Make this swap as atomic as possible: @@ -154,7 +155,7 @@ def reload begin self.setup(@container) rescue - raise ContainerError, container + raise InitializationError, container end # Wait for all child processes to enter the ready state. @@ -165,7 +166,7 @@ def reload if @container.failed? @notify.error!("Container failed!") - raise ContainerError, @container + raise InitializationError, @container else @notify&.ready! end @@ -190,7 +191,7 @@ def run if handler = @signals[exception.signo] begin handler.call - rescue ContainerError => error + rescue InitializationError => error Async.logger.error(self) {error} end else diff --git a/spec/async/container/controller_spec.rb b/spec/async/container/controller_spec.rb index c408b19..6a4f3c4 100644 --- a/spec/async/container/controller_spec.rb +++ b/spec/async/container/controller_spec.rb @@ -91,5 +91,16 @@ def subject.setup(container) subject.stop end + + it "propagates exceptions" do + def subject.setup(container) + raise "Boom!" + end + + expect do + subject.run + end.to raise_exception(Async::Container::InitializationError) + end + end end From fc73303b1220d0b81dbfc6f2afce539ff71e6a06 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sat, 14 Mar 2020 22:34:29 +0100 Subject: [PATCH 024/166] Examples: implicit return, avoid unused variable --- examples/channel.rb | 2 +- examples/channels/client.rb | 7 +++---- examples/minimal.rb | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/channel.rb b/examples/channel.rb index 5b92a0b..3593f54 100644 --- a/examples/channel.rb +++ b/examples/channel.rb @@ -13,7 +13,7 @@ def after_fork def receive if data = @in.gets - return JSON.parse(data, symbolize_names: true) + JSON.parse(data, symbolize_names: true) end end diff --git a/examples/channels/client.rb b/examples/channels/client.rb index 360100b..bd18284 100644 --- a/examples/channels/client.rb +++ b/examples/channels/client.rb @@ -11,13 +11,14 @@ # end # # def << object -# return :object +# :object # end # # def [] key # return +# end # end -# +# # class Proxy < BasicObject # def initialize(bus, name) # @bus = bus @@ -62,8 +63,6 @@ # class Channel # def self.pipe # input, output = Async::IO.pipe -# -# # end # # def initialize(input, output) diff --git a/examples/minimal.rb b/examples/minimal.rb index d46ce12..b68b907 100644 --- a/examples/minimal.rb +++ b/examples/minimal.rb @@ -39,7 +39,7 @@ def wait @waiter = nil end - return @status + @status end protected @@ -86,9 +86,9 @@ def terminate! def wait unless @status - pid, @status = ::Process.wait(@pid) + _pid, @status = ::Process.wait(@pid) end - return @status + @status end end From 45f07defa9e7a5920b3d707a6efc13505ac29fdb Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 18 Mar 2020 15:32:36 +1300 Subject: [PATCH 025/166] Using `stdout` was a bad idea. It breaks `binding.irb` in child processes. --- lib/async/container/notify/pipe.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index 0c32c58..f467bef 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -56,9 +56,10 @@ def before_spawn(arguments, options) environment[NOTIFY_PIPE] = notify_pipe.to_s # Use stdout if it's not redirected: - elsif !options.key?(:out) - options[:out] = @io - environment[NOTIFY_PIPE] = "1" + # This can cause issues if the user expects stdout to be connected to a terminal. + # elsif !options.key?(:out) + # options[:out] = @io + # environment[NOTIFY_PIPE] = "1" # Use fileno 3 if it's available: elsif !options.key?(3) From cad2a87bee73eaf88792a00d151b7a328c30786e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 18 Mar 2020 15:33:14 +1300 Subject: [PATCH 026/166] Bump version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 5b7d3a3..9766571 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.2" + VERSION = "0.16.3" end end From 55f70f87dec105256a740c277827e046f57a29ba Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sat, 14 Mar 2020 22:10:49 +0100 Subject: [PATCH 027/166] fix: allow Thread#name= to set name --- lib/async/container/thread.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index d422760..f85df3f 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -116,7 +116,7 @@ def initialize(name: nil) end def name= value - @thread.name = name + @thread.name = value end def name From 4e50387a550d7b4d2c406d7ba11c4d7b6838d199 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sat, 14 Mar 2020 22:01:58 +0100 Subject: [PATCH 028/166] Avoid Ruby warning on unused variable --- lib/async/container/notify/server.rb | 2 +- spec/async/container/forked_spec.rb | 2 +- spec/async/container/notify/pipe_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index e1756d4..d7f687b 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -98,7 +98,7 @@ def close def receive while true - data, address, flags, *controls = @bound.recvmsg(MAXIMUM_MESSAGE_SIZE) + data, _address, _flags, *_controls = @bound.recvmsg(MAXIMUM_MESSAGE_SIZE) message = Server.load(data) diff --git a/spec/async/container/forked_spec.rb b/spec/async/container/forked_spec.rb index 682e7fb..7da8a75 100644 --- a/spec/async/container/forked_spec.rb +++ b/spec/async/container/forked_spec.rb @@ -45,7 +45,7 @@ 3.times do trigger.last.puts "die" - child_pid = pids.first.gets + _child_pid = pids.first.gets end thread.kill diff --git a/spec/async/container/notify/pipe_spec.rb b/spec/async/container/notify/pipe_spec.rb index 80a87bb..e53b6be 100644 --- a/spec/async/container/notify/pipe_spec.rb +++ b/spec/async/container/notify/pipe_spec.rb @@ -38,7 +38,7 @@ # Wait for the state to be updated by the child process: container.sleep - child, state = container.state.first + _child, state = container.state.first expect(state).to be == {status: "Initializing..."} container.wait From d1884dbecfd06b1b6de6534268244a2aef6d8837 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 21 Mar 2020 11:43:35 +1300 Subject: [PATCH 029/166] Add timer quantum to fix flaky tests. --- spec/async/container/controller_spec.rb | 6 +++--- spec/async/container/shared_examples.rb | 4 ++-- spec/spec_helper.rb | 8 +++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/spec/async/container/controller_spec.rb b/spec/async/container/controller_spec.rb index 6a4f3c4..86f1545 100644 --- a/spec/async/container/controller_spec.rb +++ b/spec/async/container/controller_spec.rb @@ -33,18 +33,18 @@ def subject.setup(container) container.spawn(key: "test") do |instance| instance.ready! - sleep(0.2) + sleep(0.2 * QUANTUM) @output.write(".") @output.flush - sleep(0.4) + sleep(0.4 * QUANTUM) end container.spawn do |instance| instance.ready! - sleep(0.3) + sleep(0.3 * QUANTUM) @output.write(",") @output.flush diff --git a/spec/async/container/shared_examples.rb b/spec/async/container/shared_examples.rb index f0c61a0..9bd9a31 100644 --- a/spec/async/container/shared_examples.rb +++ b/spec/async/container/shared_examples.rb @@ -51,11 +51,11 @@ describe '#sleep' do it "can sleep for a short time" do subject.spawn do - sleep(0.2) + sleep(0.2 * QUANTUM) raise "Boom" end - subject.sleep(0.1) + subject.sleep(0.1 * QUANTUM) expect(subject.statistics).to have_attributes(failures: 0) subject.wait diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 74c7269..6171c85 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,10 +5,16 @@ # Shared rspec helpers: require "async/rspec" +if RUBY_PLATFORM =~ /darwin/i + QUANTUM = 2.0 +else + QUANTUM = 1.0 +end + RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" - + config.expect_with :rspec do |c| c.syntax = :expect end From d285b5e371367622b5833b6aaf6c831ea2b0c079 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 25 Mar 2020 15:35:34 +1300 Subject: [PATCH 030/166] Fix handling of container termination and add specs. --- lib/async/container/group.rb | 15 ++---- spec/async/container/controller_spec.rb | 1 - spec/async/container/dots.rb | 34 +++++++++++++ spec/async/container/signal_spec.rb | 66 +++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 13 deletions(-) create mode 100755 spec/async/container/dots.rb create mode 100644 spec/async/container/signal_spec.rb diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index b2b5d53..0bb740c 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -81,13 +81,12 @@ def terminate end def stop(timeout = 1) - # Handle legacy `graceful = true` argument: + # Use a default timeout if not specified: + timeout = 1 if timeout == true + if timeout start_time = Async::Clock.now - # Use a default timeout if not specified: - timeout = 1 if timeout == true - self.interrupt while self.any? @@ -103,14 +102,6 @@ def stop(timeout = 1) end end - # Timeout can also be `graceful = false`: - if timeout - self.interrupt - self.sleep(timeout) - end - - self.wait_for_children(duration) - # Terminate all children: self.terminate diff --git a/spec/async/container/controller_spec.rb b/spec/async/container/controller_spec.rb index 86f1545..f8cc044 100644 --- a/spec/async/container/controller_spec.rb +++ b/spec/async/container/controller_spec.rb @@ -101,6 +101,5 @@ def subject.setup(container) subject.run end.to raise_exception(Async::Container::InitializationError) end - end end diff --git a/spec/async/container/dots.rb b/spec/async/container/dots.rb new file mode 100755 index 0000000..b8bde26 --- /dev/null +++ b/spec/async/container/dots.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../../../lib/async/container/controller' +require_relative '../../../lib/async/container/forked' + +Console.logger.debug! + +class Dots < Async::Container::Controller + def setup(container) + container.run(name: "dots", count: 1, restart: true) do |instance| + instance.ready! + + sleep 1 + + $stdout.write "." + $stdout.flush + + sleep + rescue Async::Container::Interrupt + $stdout.write("I") + rescue Async::Container::Terminate + $stdout.write("T") + end + end +end + +controller = Dots.new + +begin + controller.run +ensure + $stderr.puts $! +end diff --git a/spec/async/container/signal_spec.rb b/spec/async/container/signal_spec.rb new file mode 100644 index 0000000..dbdb556 --- /dev/null +++ b/spec/async/container/signal_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# Copyright, 2019, by Samuel G. D. Williams. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +require "async/container/controller" + +RSpec.describe Async::Container::Controller do + let(:controller_path) {File.expand_path("dots.rb", __dir__)} + + let(:pipe) {IO.pipe} + let(:input) {pipe.first} + let(:output) {pipe.last} + + let(:pid) {Process.spawn(controller_path, out: output)} + + before do + pid + output.close + end + + after do + Process.kill(:KILL, pid) + end + + it "restarts children when receiving SIGHUP" do + expect(input.read(1)).to be == '.' + + Process.kill(:HUP, pid) + + expect(input.read(2)).to be == 'I.' + end + + it "exits gracefully when receiving SIGINT" do + expect(input.read(1)).to be == '.' + + Process.kill(:INT, pid) + + expect(input.read).to be == 'I' + end + + it "exits gracefully when receiving SIGTERM" do + expect(input.read(1)).to be == '.' + + Process.kill(:TERM, pid) + + expect(input.read).to be == 'T' + end +end From daae3f1e1795a6bdacceacb1886df6e07099d3bc Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 25 Mar 2020 15:36:00 +1300 Subject: [PATCH 031/166] Log entire message when using `Notify::Console`. --- lib/async/container/notify/console.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb index 1fe4f9b..eb27bf6 100644 --- a/lib/async/container/notify/console.rb +++ b/lib/async/container/notify/console.rb @@ -38,7 +38,7 @@ def initialize(logger) end def send(level: :debug, **message) - @logger.send(level, self) {message[:status]} + @logger.send(level, self) {message} end def ready!(**message) From cf360aecbeff289af5834d0094d227410bced063 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 25 Mar 2020 15:36:08 +1300 Subject: [PATCH 032/166] Bump version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 9766571..43a78d2 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.3" + VERSION = "0.16.4" end end From fecec4daca7365279d6655d7be06d25682551e93 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 25 Mar 2020 15:45:18 +1300 Subject: [PATCH 033/166] Simplify dots implementation. --- spec/async/container/dots.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/spec/async/container/dots.rb b/spec/async/container/dots.rb index b8bde26..bf92cef 100755 --- a/spec/async/container/dots.rb +++ b/spec/async/container/dots.rb @@ -2,7 +2,6 @@ # frozen_string_literal: true require_relative '../../../lib/async/container/controller' -require_relative '../../../lib/async/container/forked' Console.logger.debug! @@ -27,8 +26,4 @@ def setup(container) controller = Dots.new -begin - controller.run -ensure - $stderr.puts $! -end +controller.run From f68ba317fe3cbcbab1c797db7f5ba346d1c2edbe Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 25 Mar 2020 15:46:33 +1300 Subject: [PATCH 034/166] Launch child controller using `bundle exec`. --- spec/async/container/signal_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/async/container/signal_spec.rb b/spec/async/container/signal_spec.rb index dbdb556..4ef808e 100644 --- a/spec/async/container/signal_spec.rb +++ b/spec/async/container/signal_spec.rb @@ -29,7 +29,7 @@ let(:input) {pipe.first} let(:output) {pipe.last} - let(:pid) {Process.spawn(controller_path, out: output)} + let(:pid) {Process.spawn("bundle", "exec", controller_path, out: output)} before do pid From d2318f9d38cb489ecc6f4535b2b0defc5bac60de Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 25 Mar 2020 15:53:20 +1300 Subject: [PATCH 035/166] Update development workflow & remove travis. --- .github/workflows/development.yml | 3 +-- .travis.yml | 21 --------------------- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 9e7a772..985b186 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -11,7 +11,6 @@ jobs: - macos ruby: - - 2.4 - 2.5 - 2.6 - 2.7 @@ -25,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v1 - - uses: actions/setup-ruby@v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} - name: Install dependencies diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2a3ba38..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: ruby -dist: xenial -cache: bundler - -matrix: - include: - - rvm: 2.4 - - rvm: 2.5 - - rvm: 2.6 - # - rvm: 2.6 - # os: osx - - rvm: 2.6 - env: COVERAGE=BriefSummary,Coveralls - - rvm: 2.7 - - rvm: truffleruby - - rvm: jruby-head - env: JRUBY_OPTS="--debug -X+O" - - rvm: ruby-head - allow_failures: - - rvm: ruby-head - - rvm: jruby-head From c4b51c51cdeaf8262c410297f7779f92aaa59a54 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 25 Mar 2020 19:36:51 +1300 Subject: [PATCH 036/166] Add jruby to test matrix. --- .github/workflows/development.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 985b186..0ec256c 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -14,6 +14,7 @@ jobs: - 2.5 - 2.6 - 2.7 + - jruby include: - os: 'ubuntu' From be037684aba4b3d02211c411bc6839a8adbe7994 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 18 May 2020 01:12:27 +1200 Subject: [PATCH 037/166] Fix hybrid container implementation. --- lib/async/container/controller.rb | 5 +- lib/async/container/group.rb | 2 + lib/async/container/hybrid.rb | 13 ++++- lib/async/container/thread.rb | 1 + spec/async/container/controller_spec.rb | 43 ++++++++++++++++ spec/async/container/dots.rb | 2 +- spec/async/container/signal_spec.rb | 66 ------------------------- 7 files changed, 62 insertions(+), 70 deletions(-) delete mode 100644 spec/async/container/signal_spec.rb diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index bb8b5a1..423fd2f 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -55,7 +55,9 @@ def initialize(notify: Notify.open!) @signals = {} - trap(SIGHUP, &self.method(:restart)) + trap(SIGHUP) do + self.restart + end end def state_string @@ -138,6 +140,7 @@ def restart old_container = @container @container = container + Async.logger.debug(self, "Stopping old container...") old_container&.stop @notify&.ready! rescue diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 0bb740c..071cc07 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -69,12 +69,14 @@ def wait end def interrupt + Async.logger.debug(self, "Sending interrupt to #{@running.size} running processes...") @running.each_value do |fiber| fiber.resume(Interrupt) end end def terminate + Async.logger.debug(self, "Sending terminate to #{@running.size} running processes...") @running.each_value do |fiber| fiber.resume(Terminate) end diff --git a/lib/async/container/hybrid.rb b/lib/async/container/hybrid.rb index 6456de3..11395a3 100644 --- a/lib/async/container/hybrid.rb +++ b/lib/async/container/hybrid.rb @@ -33,12 +33,21 @@ def run(count: nil, forks: nil, threads: nil, **options, &block) threads = (count / forks).ceil forks.times do - self.spawn(**options) do - container = Threaded::Container.new + self.spawn(**options) do |instance| + container = Threaded.new container.run(count: threads, **options, &block) + container.wait_until_ready + instance.ready! + container.wait + rescue Async::Container::Terminate + # Stop it immediately: + container.stop(false) + ensure + # Stop it gracefully (also code path for Interrupt): + container.stop end end diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index f85df3f..f3f129a 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -21,6 +21,7 @@ # THE SOFTWARE. require_relative 'channel' +require_relative 'error' require_relative 'notify/pipe' require 'async/logger' diff --git a/spec/async/container/controller_spec.rb b/spec/async/container/controller_spec.rb index f8cc044..063710e 100644 --- a/spec/async/container/controller_spec.rb +++ b/spec/async/container/controller_spec.rb @@ -102,4 +102,47 @@ def subject.setup(container) end.to raise_exception(Async::Container::InitializationError) end end + + context 'with signals' do + let(:controller_path) {File.expand_path("dots.rb", __dir__)} + + let(:pipe) {IO.pipe} + let(:input) {pipe.first} + let(:output) {pipe.last} + + let(:pid) {Process.spawn("bundle", "exec", controller_path, out: output)} + + before do + pid + output.close + end + + after do + Process.kill(:KILL, pid) + end + + it "restarts children when receiving SIGHUP" do + expect(input.read(1)).to be == '.' + + Process.kill(:HUP, pid) + + expect(input.read(2)).to be == 'I.' + end + + it "exits gracefully when receiving SIGINT" do + expect(input.read(1)).to be == '.' + + Process.kill(:INT, pid) + + expect(input.read).to be == 'I' + end + + it "exits gracefully when receiving SIGTERM" do + expect(input.read(1)).to be == '.' + + Process.kill(:TERM, pid) + + expect(input.read).to be == 'T' + end + end end diff --git a/spec/async/container/dots.rb b/spec/async/container/dots.rb index bf92cef..323fb7d 100755 --- a/spec/async/container/dots.rb +++ b/spec/async/container/dots.rb @@ -3,7 +3,7 @@ require_relative '../../../lib/async/container/controller' -Console.logger.debug! +# Console.logger.debug! class Dots < Async::Container::Controller def setup(container) diff --git a/spec/async/container/signal_spec.rb b/spec/async/container/signal_spec.rb deleted file mode 100644 index 4ef808e..0000000 --- a/spec/async/container/signal_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2019, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require "async/container/controller" - -RSpec.describe Async::Container::Controller do - let(:controller_path) {File.expand_path("dots.rb", __dir__)} - - let(:pipe) {IO.pipe} - let(:input) {pipe.first} - let(:output) {pipe.last} - - let(:pid) {Process.spawn("bundle", "exec", controller_path, out: output)} - - before do - pid - output.close - end - - after do - Process.kill(:KILL, pid) - end - - it "restarts children when receiving SIGHUP" do - expect(input.read(1)).to be == '.' - - Process.kill(:HUP, pid) - - expect(input.read(2)).to be == 'I.' - end - - it "exits gracefully when receiving SIGINT" do - expect(input.read(1)).to be == '.' - - Process.kill(:INT, pid) - - expect(input.read).to be == 'I' - end - - it "exits gracefully when receiving SIGTERM" do - expect(input.read(1)).to be == '.' - - Process.kill(:TERM, pid) - - expect(input.read).to be == 'T' - end -end From a4d44ae76559aa1242d38067a2a844542ed1b74c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 18 May 2020 01:13:47 +1200 Subject: [PATCH 038/166] Prefer bake. --- Rakefile | 8 -------- async-container.gemspec | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 Rakefile diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 366c7f8..0000000 --- a/Rakefile +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require "bundler/gem_tasks" -require "rspec/core/rake_task" - -RSpec::Core::RakeTask.new(:test) - -task :default => :test diff --git a/async-container.gemspec b/async-container.gemspec index e23b1dd..758b865 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -30,5 +30,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "covered" spec.add_development_dependency "bundler" spec.add_development_dependency "rspec", "~> 3.6" - spec.add_development_dependency "rake" + spec.add_development_dependency "bake-bundler" end From f788ff19712cc456caf5edbcf72cfca26d660924 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 18 May 2020 01:13:49 +1200 Subject: [PATCH 039/166] Patch version bump. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 43a78d2..ee6a98e 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.4" + VERSION = "0.16.5" end end From 8349c4c2b371f7aee6e39784726c2f766f2ffe9d Mon Sep 17 00:00:00 2001 From: jaml Date: Sat, 14 Mar 2020 21:42:18 +0100 Subject: [PATCH 040/166] Remove already inheriged methods from `Notify::Pipe` --- lib/async/container/notify/pipe.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index f467bef..a4d9613 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -83,22 +83,6 @@ def ready!(**message) send(ready: true, **message) end - def restarting!(**message) - message[:ready] = false - message[:reloading] = true - message[:status] ||= "Restarting..." - - send(**message) - end - - def reloading!(**message) - message[:ready] = false - message[:reloading] = true - message[:status] ||= "Reloading..." - - send(**message) - end - private def environment_for(arguments) From ac844b141b8360a083865de511c5c7fe00c6bc45 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 6 Jun 2020 19:42:56 +1200 Subject: [PATCH 041/166] Add support for environment variable to control processor count. See for more details. --- lib/async/container/generic.rb | 17 +++++++++++++---- spec/async/container_spec.rb | 20 ++++++++++++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 69fe9fb..1b2fdd7 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -30,11 +30,20 @@ module Async module Container + ASYNC_CONTAINER_PROCESSOR_COUNT = 'ASYNC_CONTAINER_PROCESSOR_COUNT' + + # The processor count which may be used for the default number of container threads/processes. You can override the value provided by the system by specifying the ASYNC_CONTAINER_PROCESSOR_COUNT environment variable. # @return [Integer] the number of hardware processors which can run threads/processes simultaneously. - def self.processor_count - Etc.nprocessors - rescue - 2 + def self.processor_count(env = ENV) + count = env.fetch(ASYNC_CONTAINER_PROCESSOR_COUNT) do + Etc.nprocessors rescue 1 + end.to_i + + if count < 1 + raise RuntimeError, "Invalid processor count #{count}!" + end + + return count end class Generic diff --git a/spec/async/container_spec.rb b/spec/async/container_spec.rb index 9d2d480..ff80b28 100644 --- a/spec/async/container_spec.rb +++ b/spec/async/container_spec.rb @@ -23,8 +23,24 @@ require "async/container" RSpec.describe Async::Container do - it "can get processor count" do - expect(Async::Container.processor_count).to be >= 1 + describe '.processor_count' do + it "can get processor count" do + expect(Async::Container.processor_count).to be >= 1 + end + + it "can override the processor count" do + env = {'ASYNC_CONTAINER_PROCESSOR_COUNT' => '8'} + + expect(Async::Container.processor_count(env)).to be == 8 + end + + it "fails on invalid processor count" do + env = {'ASYNC_CONTAINER_PROCESSOR_COUNT' => '-1'} + + expect do + Async::Container.processor_count(env) + end.to raise_error(/Invalid processor count/) + end end it "can get best container class" do From 0c6762e39e887785103dd38db8a9235bf689c2ad Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 6 Jun 2020 19:52:30 +1200 Subject: [PATCH 042/166] Modernize gem. --- .github/workflows/development.yml | 32 ++++++++++++++++--------- .gitignore | 4 ++-- Gemfile | 19 --------------- async-container.gemspec | 39 +++++++++++++------------------ gems.rb | 6 +++++ 5 files changed, 45 insertions(+), 55 deletions(-) delete mode 100644 Gemfile create mode 100644 gems.rb diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 0ec256c..9da541b 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -1,9 +1,12 @@ name: Development -on: [push] +on: [push, pull_request] jobs: test: + runs-on: ${{matrix.os}}-latest + continue-on-error: ${{matrix.experimental}} + strategy: matrix: os: @@ -14,23 +17,30 @@ jobs: - 2.5 - 2.6 - 2.7 - - jruby + + experimental: [false] + env: [""] include: - - os: 'ubuntu' - ruby: '2.6' - env: COVERAGE=PartialSummary,Coveralls - - runs-on: ${{matrix.os}}-latest + - os: ubuntu + ruby: truffleruby + experimental: true + - os: ubuntu + ruby: jruby + experimental: true + - os: ubuntu + ruby: head + experimental: true steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} + - name: Install dependencies - run: | - command -v bundler || gem install bundler - bundle install + run: ${{matrix.env}} bundle install + - name: Run tests + timeout-minutes: 5 run: ${{matrix.env}} bundle exec rspec diff --git a/.gitignore b/.gitignore index 2250076..443d421 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ .bundle .config .yardoc -Gemfile.lock +/gems.locked InstalledFiles _yardoc coverage @@ -18,4 +18,4 @@ tmp .tags* documentation/run/* documentation/public/code/* -.rspec_status \ No newline at end of file +.rspec_status diff --git a/Gemfile b/Gemfile deleted file mode 100644 index b0d366c..0000000 --- a/Gemfile +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -# Specify your gem's dependencies in utopia.gemspec -gemspec - -group :development do - gem 'pry' - gem 'guard-rspec' - gem 'guard-yard' - - gem 'yard' -end - -group :test do - gem 'benchmark-ips' - gem 'ruby-prof', platforms: :mri -end diff --git a/async-container.gemspec b/async-container.gemspec index 758b865..dd7abc6 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -1,34 +1,27 @@ -require_relative 'lib/async/container/version' +require_relative "lib/async/container/version" Gem::Specification.new do |spec| - spec.name = "async-container" - spec.version = Async::Container::VERSION - spec.authors = ["Samuel Williams"] - spec.email = ["samuel.williams@oriontransfer.co.nz"] - spec.description = <<-EOF - Provides containers for servers which provide concurrency policies, e.g. threads, processes. - EOF - spec.summary = "Async is an asynchronous I/O framework based on nio4r." - spec.homepage = "https://github.com/socketry/async-container" - spec.license = "MIT" - - spec.files = `git ls-files`.split($/) - spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) } - spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) - spec.require_paths = ["lib"] + spec.name = "async-container" + spec.version = Async::Container::VERSION - spec.required_ruby_version = "~> 2.0" + spec.summary = "Abstract container-based parallelism using threads and processes where appropriate." + spec.authors = ["Samuel Williams"] + spec.license = "MIT" - spec.add_runtime_dependency "process-group" + spec.homepage = "https://github.com/socketry/async-container" - spec.add_runtime_dependency "async", "~> 1.0" - spec.add_runtime_dependency "async-io", "~> 1.26" + spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) + + spec.required_ruby_version = "~> 2.0" - spec.add_development_dependency "async-rspec", "~> 1.1" + spec.add_dependency "async", "~> 1.0" + spec.add_dependency "async-io", "~> 1.26" - spec.add_development_dependency "covered" + spec.add_development_dependency "async-rspec", "~> 1.1" + spec.add_development_dependency "bake-bundler" + spec.add_development_dependency "bake-modernize" spec.add_development_dependency "bundler" + spec.add_development_dependency "covered" spec.add_development_dependency "rspec", "~> 3.6" - spec.add_development_dependency "bake-bundler" end diff --git a/gems.rb b/gems.rb new file mode 100644 index 0000000..b79ac9e --- /dev/null +++ b/gems.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +# Specify your gem's dependencies in utopia.gemspec +gemspec From 654408b13cd88da9c2a9941308e678f2833442ed Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 6 Jun 2020 19:59:31 +1200 Subject: [PATCH 043/166] Patch version bump. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index ee6a98e..6ef9ff4 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.5" + VERSION = "0.16.6" end end From 2013d8138e862dea3b0fbf0e4536d149caf9033a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 12 Jun 2020 02:27:34 +1200 Subject: [PATCH 044/166] Playing around with multi-process MessagePack job queue. --- examples/queue/server.rb | 99 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 examples/queue/server.rb diff --git a/examples/queue/server.rb b/examples/queue/server.rb new file mode 100644 index 0000000..09bc551 --- /dev/null +++ b/examples/queue/server.rb @@ -0,0 +1,99 @@ + +require 'async' +require 'async/container' +require 'async/io/unix_endpoint' +require 'async/io/shared_endpoint' +require 'msgpack' + +class Wrapper < MessagePack::Factory + def initialize + super() + + # self.register_type(0x00, Object, packer: @bus.method(:temporary), unpacker: @bus.method(:[])) + + self.register_type(0x01, Symbol) + self.register_type(0x02, Exception, + packer: ->(exception){Marshal.dump(exception)}, + unpacker: ->(data){Marshal.load(data)}, + ) + + self.register_type(0x03, Class, + packer: ->(klass){Marshal.dump(klass)}, + unpacker: ->(data){Marshal.load(data)}, + ) + end +end + +endpoint = Async::IO::Endpoint.unix('test.ipc') +wrapper = Wrapper.new + +container = Async::Container.new + +bound_endpoint = Sync do + Async::IO::SharedEndpoint.bound(endpoint) +end + +container.spawn do |instance| + Async do + queue = 8.times.to_a + Console.logger.info(self) {"Hosting the queue..."} + + instance.ready! + + bound_endpoint.accept do |peer| + Console.logger.info(self) {"Incoming connection from #{peer}..."} + + packer = wrapper.packer(peer) + unpacker = wrapper.unpacker(peer) + + unpacker.each do |message| + command, *arguments = message + + case command + when :ready + if job = queue.pop + packer.write([:job, job]) + packer.flush + else + peer.close_write + break + end + when :status + Console.logger.info("Job Status") {arguments} + else + Console.logger.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"} + end + end + end + end +end + +container.run do |instance| + Async do |task| + endpoint.connect do |peer| + instance.ready! + + packer = wrapper.packer(peer) + unpacker = wrapper.unpacker(peer) + + packer.write([:ready]) + packer.flush + + unpacker.each do |message| + command, *arguments = message + + case command + when :job + task.sleep(*arguments) + packer.write([:status, *arguments]) + packer.write([:ready]) + packer.flush + else + Console.logger.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"} + end + end + end + end +end + +container.wait From d6ae408e2c26ecf80ae495ef494c258ec0b5f880 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Wed, 8 Jul 2020 13:42:21 +0200 Subject: [PATCH 045/166] gemspec: Set Ruby req to 2.5+ implicit begin-rescue-end are used, so we need it --- async-container.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/async-container.gemspec b/async-container.gemspec index dd7abc6..a1a0c27 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |spec| spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) - spec.required_ruby_version = "~> 2.0" + spec.required_ruby_version = "~> 2.5" spec.add_dependency "async", "~> 1.0" spec.add_dependency "async-io", "~> 1.26" From 1b202ad41713d051ce39b6167fae44c5f231c7b9 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 9 Jul 2020 15:00:35 +1200 Subject: [PATCH 046/166] Modernize gem. --- .editorconfig | 1 - README.md | 34 +++++++++++----------------------- async-container.gemspec | 2 +- gems.rb | 7 +++++++ 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/.editorconfig b/.editorconfig index 269d98a..538ba2b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,4 +3,3 @@ root = true [*] indent_style = tab indent_size = 2 - diff --git a/README.md b/README.md index d6e7fd3..d2fd3cd 100644 --- a/README.md +++ b/README.md @@ -2,33 +2,21 @@ Provides containers which implement concurrency policy for high-level servers (and potentially clients). -[![Actions Status](https://github.com/socketry/async-container/workflows/Development/badge.svg)](https://github.com/socketry/async-container/actions?workflow=Development) -[![Code Climate](https://codeclimate.com/github/socketry/async-container.svg)](https://codeclimate.com/github/socketry/async-container) -[![Coverage Status](https://coveralls.io/repos/socketry/async-container/badge.svg)](https://coveralls.io/r/socketry/async-container) +[![Development Status](https://github.com/socketry/async-container/workflows/Development/badge.svg)](https://github.com/socketry/async-container/actions?workflow=Development) ## Installation -Add this line to your application's Gemfile: - -```ruby -gem "async-container" +``` bash +$ bundle add async-container ``` -And then execute: - - $ bundle - -Or install it yourself as: - - $ gem install async - ## Usage ### Container A container represents a set of child processes (or threads) which are doing work for you. -```ruby +``` ruby require 'async/container' Async.logger.debug! @@ -50,7 +38,7 @@ Async.logger.debug "Finished." The controller provides the life-cycle management for one or more containers of processes. It provides behaviour like starting, restarting, reloading and stopping. You can see some [example implementations in Falcon](https://github.com/socketry/falcon/blob/master/lib/falcon/controller/). If the process running the controller receives `SIGHUP` it will recreate the container gracefully. -```ruby +``` ruby require 'async/container' Async.logger.debug! @@ -77,7 +65,7 @@ controller.run `SIGINT` is the interrupt signal. The terminal sends it to the foreground process when the user presses **ctrl-c**. The default behavior is to terminate the process, but it can be caught or ignored. The intention is to provide a mechanism for an orderly, graceful shutdown. -`SIGQUIT` is the dump core signal. The terminal sends it to the foreground process when the user presses **ctrl-\**. The default behavior is to terminate the process and dump core, but it can be caught or ignored. The intention is to provide a mechanism for the user to abort the process. You can look at `SIGINT` as "user-initiated happy termination" and `SIGQUIT` as "user-initiated unhappy termination." +`SIGQUIT` is the dump core signal. The terminal sends it to the foreground process when the user presses **ctrl-\\**. The default behavior is to terminate the process and dump core, but it can be caught or ignored. The intention is to provide a mechanism for the user to abort the process. You can look at `SIGINT` as "user-initiated happy termination" and `SIGQUIT` as "user-initiated unhappy termination." `SIGTERM` is the termination signal. The default behavior is to terminate the process, but it also can be caught or ignored. The intention is to kill the process, gracefully or not, but to first allow it a chance to cleanup. @@ -109,11 +97,11 @@ WantedBy=multi-user.target ## Contributing -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request ## License diff --git a/async-container.gemspec b/async-container.gemspec index a1a0c27..d0337ed 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/socketry/async-container" spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) - + spec.required_ruby_version = "~> 2.5" spec.add_dependency "async", "~> 1.0" diff --git a/gems.rb b/gems.rb index b79ac9e..71c88f8 100644 --- a/gems.rb +++ b/gems.rb @@ -4,3 +4,10 @@ # Specify your gem's dependencies in utopia.gemspec gemspec + +group :maintenance, optional: true do + gem "bake-bundler" + gem "bake-modernize" + + gem "utopia-project" +end From ad1efba62d643beffbd48a83895a7ffd3719f776 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 9 Jul 2020 17:24:25 +1200 Subject: [PATCH 047/166] Updated implementation and documentation. --- lib/async/container.rb | 1 - lib/async/container/best.rb | 12 ++++-- lib/async/container/channel.rb | 13 +++++++ lib/async/container/controller.rb | 48 ++++++++++++++++-------- lib/async/container/error.rb | 13 +++++++ lib/async/container/forked.rb | 6 ++- lib/async/container/generic.rb | 49 ++++++++++++++++++++----- lib/async/container/group.rb | 19 +++++++++- lib/async/container/hybrid.rb | 5 +++ lib/async/container/keyed.rb | 12 ++++++ lib/async/container/notify.rb | 9 ++--- lib/async/container/notify/client.rb | 17 ++++++++- lib/async/container/notify/console.rb | 29 ++++----------- lib/async/container/notify/pipe.rb | 12 +++--- lib/async/container/notify/server.rb | 1 - lib/async/container/notify/socket.rb | 15 +++++++- lib/async/container/process.rb | 26 +++++++++++++ lib/async/container/statistics.rb | 16 ++++++++ lib/async/container/thread.rb | 43 ++++++++++++++++++++-- lib/async/container/threaded.rb | 6 ++- spec/async/container/controller_spec.rb | 2 +- 21 files changed, 282 insertions(+), 72 deletions(-) diff --git a/lib/async/container.rb b/lib/async/container.rb index 016fd57..9ef7e80 100644 --- a/lib/async/container.rb +++ b/lib/async/container.rb @@ -23,7 +23,6 @@ require_relative 'container/controller' module Async - # Containers execute one or more "instances" which typically contain a reactor. A container spawns "instances" using threads and/or processes. Because these are resources that must be cleaned up some how (either by `join` or `waitpid`), their creation is deferred until the user invokes `Container#wait`. When executed this way, the container guarantees that all "instances" will be complete once `Container#wait` returns. Containers are constructs for achieving parallelism, and are not designed to be used directly for concurrency. Typically, you'd create one or more container, add some tasks to it, and then wait for it to complete. module Container end end diff --git a/lib/async/container/best.rb b/lib/async/container/best.rb index c666d81..2505362 100644 --- a/lib/async/container/best.rb +++ b/lib/async/container/best.rb @@ -25,12 +25,16 @@ require_relative 'hybrid' module Async - # Containers execute one or more "instances" which typically contain a reactor. A container spawns "instances" using threads and/or processes. Because these are resources that must be cleaned up some how (either by `join` or `waitpid`), their creation is deferred until the user invokes `Container#wait`. When executed this way, the container guarantees that all "instances" will be complete once `Container#wait` returns. Containers are constructs for achieving parallelism, and are not designed to be used directly for concurrency. Typically, you'd create one or more container, add some tasks to it, and then wait for it to complete. module Container + # Whether the underlying process supports fork. + # @returns [Boolean] def self.fork? ::Process.respond_to?(:fork) && ::Process.respond_to?(:setpgid) end + # Determins the best container class based on the underlying Ruby implementation. + # Some platforms, including JRuby, don't support fork. Applications which just want a reasonable default can use this method. + # @returns [Class] def self.best_container_class if fork? return Forked @@ -39,8 +43,10 @@ def self.best_container_class end end - def self.new(*arguments) - best_container_class.new(*arguments) + # Create an instance of the best container class. + # @returns [Generic] Typically an instance of either {Forked} or {Threaded} containers. + def self.new(*arguments, **options) + best_container_class.new(*arguments, **options) end end end diff --git a/lib/async/container/channel.rb b/lib/async/container/channel.rb index baa652b..6be4eba 100644 --- a/lib/async/container/channel.rb +++ b/lib/async/container/channel.rb @@ -24,27 +24,40 @@ module Async module Container + # Provides a basic multi-thread/multi-process uni-directional communication channel. class Channel + # Initialize the channel using a pipe. def initialize @in, @out = ::IO.pipe end + # The input end of the pipe. + # @attribute [IO] attr :in + + # The output end of the pipe. + # @attribute [IO] attr :out + # Close the input end of the pipe. def close_read @in.close end + # Close the output end of the pipe. def close_write @out.close end + # Close both ends of the pipe. def close close_read close_write end + # Receive an object from the pipe. + # Internally, prefers to receive newline formatted JSON, otherwise returns a hash table with a single key `:line` which contains the line of data that could not be parsed as JSON. + # @returns [Hash] def receive if data = @in.gets begin diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 423fd2f..6a94341 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -28,17 +28,8 @@ module Async module Container - class InitializationError < Error - def initialize(container) - super("Could not create container!") - - @container = container - end - - attr :container - end - - # Manages the life-cycle of a container. + # Manages the life-cycle of one or more containers in order to support a persistent system. + # e.g. a web server, job server or some other long running system. class Controller SIGHUP = Signal.list["HUP"] SIGINT = Signal.list["INT"] @@ -46,6 +37,8 @@ class Controller SIGUSR1 = Signal.list["USR1"] SIGUSR2 = Signal.list["USR2"] + # Initialize the controller. + # @parameter notify [Notify::Client] A client used for process readiness notifications. def initialize(notify: Notify.open!) @container = nil @@ -60,6 +53,8 @@ def initialize(notify: Notify.open!) end end + # The state of the controller. + # @returns [String] def state_string if running? "running" @@ -68,42 +63,61 @@ def state_string end end + # A human readable representation of the controller. + # @returns [String] def to_s "#{self.class} #{state_string}" end + # Trap the specified signal. + # @parameters signal [Symbol] The signal to trap, e.g. `:INT`. + # @parameters block [Proc] The signal handler to invoke. def trap(signal, &block) @signals[signal] = block end + # The current container being managed by the controller. attr :container + # Create a container for the controller. + # Can be overridden by a sub-class. + # @returns [Generic] A specific container instance to use. def create_container Container.new end + # Whether the controller has a running container. + # @returns [Boolean] def running? !!@container end + # Wait for the underlying container to start. def wait @container&.wait end + # Spawn container instances into the given container. + # Should be overridden by a sub-class. + # @parameter container [Generic] The container, generally from {#create_container}. def setup(container) # Don't do this, otherwise calling super is risky for sub-classes: # raise NotImplementedError, "Container setup is must be implemented in derived class!" end + # Start the container unless it's already running. def start self.restart unless @container end + # Stop the container if it's running. + # @parameter graceful [Boolean] Whether to give the children instances time to shut down or to kill them immediately. def stop(graceful = true) @container&.stop(graceful) @container = nil end + # Restart the container. A new container is created, and if successful, any old container is terminated gracefully. def restart if @container @notify&.restarting! @@ -120,7 +134,7 @@ def restart rescue @notify&.error!($!.to_s) - raise InitializationError, container + raise SetupError, container end # Wait for all child processes to enter the ready state. @@ -133,7 +147,7 @@ def restart container.stop - raise InitializationError, container + raise SetupError, container end # Make this swap as atomic as possible: @@ -150,6 +164,7 @@ def restart raise end + # Reload the existing container. Children instances will be reloaded using `SIGHUP`. def reload @notify&.reloading! @@ -158,7 +173,7 @@ def reload begin self.setup(@container) rescue - raise InitializationError, container + raise SetupError, container end # Wait for all child processes to enter the ready state. @@ -169,12 +184,13 @@ def reload if @container.failed? @notify.error!("Container failed!") - raise InitializationError, @container + raise SetupError, @container else @notify&.ready! end end + # Enter the controller run loop, trapping `SIGINT` and `SIGTERM`. def run # I thought this was the default... but it doesn't always raise an exception unless you do this explicitly. interrupt_action = Signal.trap(:INT) do @@ -194,7 +210,7 @@ def run if handler = @signals[exception.signo] begin handler.call - rescue InitializationError => error + rescue SetupError => error Async.logger.error(self) {error} end else diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index ed92ea0..2c3b7ff 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -27,6 +27,7 @@ class Error < StandardError Interrupt = ::Interrupt + # Similar to {Interrupt}, but represents `SIGTERM`. class Terminate < SignalException SIGTERM = Signal.list['TERM'] @@ -34,5 +35,17 @@ def initialize super(SIGTERM) end end + + # Represents the error which occured when a container failed to start up correctly. + class SetupError < Error + def initialize(container) + super("Could not create container!") + + @container = container + end + + # The container that failed. + attr :container + end end end diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 790533c..0d40ec1 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -24,13 +24,17 @@ require_relative 'process' module Async - # Manages a reactor within one or more threads. module Container + # A multi-process container which uses {Process.fork}. class Forked < Generic + # Indicates that this is a multi-process container. def self.multiprocess? true end + # Start a named child process and execute the provided block in it. + # @parameter name [String] The name (title) of the child process. + # @parameter block [Proc] The block to execute in the child process. def start(name, &block) Process.fork(name: name, &block) end diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 1b2fdd7..545f597 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -30,10 +30,12 @@ module Async module Container + # An environment variable key to override {.processor_count}. ASYNC_CONTAINER_PROCESSOR_COUNT = 'ASYNC_CONTAINER_PROCESSOR_COUNT' - # The processor count which may be used for the default number of container threads/processes. You can override the value provided by the system by specifying the ASYNC_CONTAINER_PROCESSOR_COUNT environment variable. - # @return [Integer] the number of hardware processors which can run threads/processes simultaneously. + # The processor count which may be used for the default number of container threads/processes. You can override the value provided by the system by specifying the `ASYNC_CONTAINER_PROCESSOR_COUNT` environment variable. + # @returns [Integer] The number of hardware processors which can run threads/processes simultaneously. + # @raises [RuntimeError] If the process count is invalid. def self.processor_count(env = ENV) count = env.fetch(ASYNC_CONTAINER_PROCESSOR_COUNT) do Etc.nprocessors rescue 1 @@ -46,6 +48,7 @@ def self.processor_count(env = ENV) return count end + # A base class for implementing containers. class Generic def self.run(*arguments, **options, &block) self.new.run(*arguments, **options, &block) @@ -65,27 +68,35 @@ def initialize(**options) attr :state + # A human readable representation of the container. + # @returns [String] def to_s "#{self.class} with #{@statistics.spawns} spawns and #{@statistics.failures} failures." end + # Look up a child process by key. + # A key could be a symbol, a file path, or something else which the child instance represents. def [] key @keyed[key]&.value end + # Statistics relating to the behavior of children instances. + # @attribute [Statistics] attr :statistics + # Whether any failures have occurred within the container. + # @returns [Boolean] def failed? @statistics.failed? end - # Whether there are running tasks. + # Whether the container has running children instances. def running? @group.running? end # Sleep until some state change occurs. - # @param duration [Integer] the maximum amount of time to sleep for. + # @parameter duration [Numeric] the maximum amount of time to sleep for. def sleep(duration = nil) @group.sleep(duration) end @@ -95,11 +106,17 @@ def wait @group.wait end + # Returns true if all children instances have the specified status flag set. + # e.g. `:ready`. + # This state is updated by the process readiness protocol mechanism. See {Notify::Client} for more details. + # @returns [Boolean] def status?(flag) # This also returns true if all processes have exited/failed: @state.all?{|_, state| state[flag]} end + # Wait until all the children instances have indicated that they are ready. + # @returns [Boolean] The children all became ready. def wait_until_ready while true Async.logger.debug(self) do |buffer| @@ -117,6 +134,8 @@ def wait_until_ready end end + # Stop the children instances. + # @parameter timeout [Boolean | Numeric] Whether to stop gracefully, or a specific timeout. def stop(timeout = true) @running = false @group.stop(timeout) @@ -128,6 +147,10 @@ def stop(timeout = true) @running = true end + # Spawn a child instance into the container. + # @parameter name [String] The name of the child instance. + # @parameter restart [Boolean] Whether to restart the child instance if it fails. + # @parameter key [Symbol] A key used for reloading child instances. def spawn(name: nil, restart: false, key: nil, &block) name ||= UNNAMED @@ -172,12 +195,8 @@ def spawn(name: nil, restart: false, key: nil, &block) return true end - def async(**options, &block) - spawn(**options) do |instance| - Async::Reactor.run(instance, &block) - end - end - + # Run multiple instances of the same block in the container. + # @parameter count [Integer] The number of instances to start. def run(count: Container.processor_count, **options, &block) count.times do spawn(**options, &block) @@ -186,6 +205,14 @@ def run(count: Container.processor_count, **options, &block) return self end + # @deprecated Please use {spawn} or {run} instead. + def async(**options, &block) + spawn(**options) do |instance| + Async::Reactor.run(instance, &block) + end + end + + # Reload the container's keyed instances. def reload @keyed.each_value(&:clear!) @@ -200,6 +227,7 @@ def reload return dirty end + # Mark the container's keyed instance which ensures that it won't be discarded. def mark?(key) if key if value = @keyed[key] @@ -212,6 +240,7 @@ def mark?(key) return false end + # Whether a child instance exists for the given key. def key?(key) if key @keyed.key?(key) diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 071cc07..92184d2 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -21,7 +21,6 @@ # THE SOFTWARE. require 'fiber' - require 'async/clock' require_relative 'error' @@ -30,6 +29,7 @@ module Async module Container # Manages a group of running processes. class Group + # Initialize an empty group. def initialize @running = {} @@ -40,19 +40,25 @@ def initialize # @attribute [Hash] the running tasks, indexed by IO. attr :running + # Whether the group contains any running processes. + # @returns [Boolean] def running? @running.any? end + # Whether the group contains any running processes. + # @returns [Boolean] def any? @running.any? end + # Whether the group is empty. + # @returns [Boolean] def empty? @running.empty? end - # This method sleeps for at most the specified duration. + # Sleep for at most the specified duration until some state change occurs. def sleep(duration) self.resume self.suspend @@ -60,6 +66,7 @@ def sleep(duration) self.wait_for_children(duration) end + # Begin any outstanding queued processes and wait for them indefinitely. def wait self.resume @@ -68,6 +75,8 @@ def wait end end + # Interrupt all running processes. + # This resumes the controlling fiber with an instance of {Interrupt}. def interrupt Async.logger.debug(self, "Sending interrupt to #{@running.size} running processes...") @running.each_value do |fiber| @@ -75,6 +84,8 @@ def interrupt end end + # Terminate all running processes. + # This resumes the controlling fiber with an instance of {Terminate}. def terminate Async.logger.debug(self, "Sending terminate to #{@running.size} running processes...") @running.each_value do |fiber| @@ -82,6 +93,8 @@ def terminate end end + # Stop all child processes using {#terminate}. + # @parameter timeout [Boolean | Numeric | Nil] If specified, invoke a graceful shutdown using {#interrupt} first. def stop(timeout = 1) # Use a default timeout if not specified: timeout = 1 if timeout == true @@ -111,6 +124,7 @@ def stop(timeout = 1) self.wait end + # Wait for a message in the specified {Channel}. def wait_for(channel) io = channel.in @@ -137,6 +151,7 @@ def wait_for(channel) def wait_for_children(duration = nil) if !@running.empty? + # Maybe consider using a proper event loop here: readable, _, _ = ::IO.select(@running.keys, nil, nil, duration) readable&.each do |io| diff --git a/lib/async/container/hybrid.rb b/lib/async/container/hybrid.rb index 11395a3..28fb47e 100644 --- a/lib/async/container/hybrid.rb +++ b/lib/async/container/hybrid.rb @@ -25,7 +25,12 @@ module Async module Container + # Provides a hybrid multi-process multi-thread container. class Hybrid < Forked + # Run multiple instances of the same block in the container. + # @parameter count [Integer] The number of instances to start. + # @parameter forks [Integer] The number of processes to fork. + # @parameter threads [Integer] the number of threads to start. def run(count: nil, forks: nil, threads: nil, **options, &block) processor_count = Container.processor_count count ||= processor_count ** 2 diff --git a/lib/async/container/keyed.rb b/lib/async/container/keyed.rb index ede6178..e6ee0a9 100644 --- a/lib/async/container/keyed.rb +++ b/lib/async/container/keyed.rb @@ -22,6 +22,8 @@ module Async module Container + # Tracks a key/value pair such that unmarked keys can be identified and cleaned up. + # This helps implement persistent processes that start up child processes per directory or configuration file. If those directories and/or configuration files are removed, the child process can then be cleaned up automatically, because those key/value pairs will not be marked when reloading the container. class Keyed def initialize(key, value) @key = key @@ -29,21 +31,31 @@ def initialize(key, value) @marked = true end + # The key. Normally a symbol or a file-system path. + # @attribute [Object] attr :key + + # The value. Normally a child instance of some sort. + # @attribute [Object] attr :value + # Has the instance been marked? + # @returns [Boolean] def marked? @marked end + # Mark the instance. This will indiciate that the value is still in use/active. def mark! @marked = true end + # Clear the instance. This is normally done before reloading a container. def clear! @marked = false end + # Stop the instance if it was not marked. def stop? unless @marked @value.stop diff --git a/lib/async/container/notify.rb b/lib/async/container/notify.rb index 3d7d09d..706a732 100644 --- a/lib/async/container/notify.rb +++ b/lib/async/container/notify.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -28,12 +27,12 @@ module Async module Container module Notify - # We cache the client on a per-process basis. Because that's the relevant scope for process readiness protocols. - @@client = nil + @client = nil + # Select the best available notification client. + # We cache the client on a per-process basis. Because that's the relevant scope for process readiness protocols. def self.open! - # Select the best available client: - @@client ||= ( + @client ||= ( Pipe.open! || Socket.open! || Console.open! diff --git a/lib/async/container/notify/client.rb b/lib/async/container/notify/client.rb index c2480f2..b274e4c 100644 --- a/lib/async/container/notify/client.rb +++ b/lib/async/container/notify/client.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -23,12 +22,17 @@ module Async module Container + # Handles the details of several process readiness protocols. module Notify class Client + # Notify the parent controller that the child has become ready, with a brief status message. + # @parameters message [Hash] Additional details to send with the message. def ready!(**message) send(ready: true, **message) end + # Notify the parent controller that the child is reloading. + # @parameters message [Hash] Additional details to send with the message. def reloading!(**message) message[:ready] = false message[:reloading] = true @@ -37,6 +41,8 @@ def reloading!(**message) send(**message) end + # Notify the parent controller that the child is restarting. + # @parameters message [Hash] Additional details to send with the message. def restarting!(**message) message[:ready] = false message[:reloading] = true @@ -45,14 +51,23 @@ def restarting!(**message) send(**message) end + # Notify the parent controller that the child is stopping. + # @parameters message [Hash] Additional details to send with the message. def stopping!(**message) message[:stopping] = true + + send(**message) end + # Notify the parent controller of a status change. + # @parameters text [String] The details of the status change. def status!(text) send(status: text) end + # Notify the parent controller of an error condition. + # @parameters text [String] The details of the error condition. + # @parameters message [Hash] Additional details to send with the message. def error!(text, **message) send(status: text, **message) end diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb index eb27bf6..16362f7 100644 --- a/lib/async/container/notify/console.rb +++ b/lib/async/container/notify/console.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -28,39 +27,27 @@ module Async module Container module Notify + # Implements a general process readiness protocol with output to the local console. class Console < Client + # Open a notification client attached to the current console. def self.open!(logger = ::Console.logger) self.new(logger) end + # Initialize the notification client. + # @parameter logger [Console::Logger] The console logger instance to send messages to. def initialize(logger) @logger = logger end + # Send a message to the console. def send(level: :debug, **message) @logger.send(level, self) {message} end - def ready!(**message) - send(ready: true, **message) - end - - def restarting!(**message) - message[:ready] = false - message[:reloading] = true - message[:status] ||= "Restarting..." - - send(**message) - end - - def reloading!(**message) - message[:ready] = false - message[:reloading] = true - message[:status] ||= "Reloading..." - - send(**message) - end - + # Send an error message to the console. + # @parameters text [String] The details of the error condition. + # @parameters message [Hash] Additional details to send with the message. def error!(text, **message) send(status: text, level: :error, **message) end diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index a4d9613..92ad125 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -28,9 +27,12 @@ module Async module Container module Notify + # Implements a process readiness protocol using an inherited pipe file descriptor. class Pipe < Client + # The environment variable key which contains the pipe file descriptor. NOTIFY_PIPE = 'NOTIFY_PIPE' + # Open a notification client attached to the current {NOTIFY_PIPE} if possible. def self.open!(environment = ENV) if descriptor = environment.delete(NOTIFY_PIPE) self.new(::IO.for_fd(descriptor.to_i)) @@ -41,6 +43,8 @@ def self.open!(environment = ENV) return nil end + # Initialize the notification client. + # @parameter io [IO] An IO instance used for sending messages. def initialize(io) @io = io end @@ -72,6 +76,8 @@ def before_spawn(arguments, options) end end + # Formats the message using JSON and sends it to the parent controller. + # This is suitable for use with {Channel}. def send(**message) data = ::JSON.dump(message) @@ -79,10 +85,6 @@ def send(**message) @io.flush end - def ready!(**message) - send(ready: true, **message) - end - private def environment_for(arguments) diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index d7f687b..dce527e 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index afba5dc..4b5f746 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -30,21 +29,31 @@ module Async module Container module Notify + # Implements the systemd NOTIFY_SOCKET process readiness protocol. + # See for more details of the underlying protocol. class Socket < Client + # The name of the environment variable which contains the path to the notification socket. NOTIFY_SOCKET = 'NOTIFY_SOCKET' + + # The maximum allowed size of the UDP message. MAXIMUM_MESSAGE_SIZE = 4096 + # Open a notification client attached to the current {NOTIFY_SOCKET} if possible. def self.open!(environment = ENV) if path = environment.delete(NOTIFY_SOCKET) self.new(path) end end + # Initialize the notification client. + # @parameter path [String] The path to the UNIX socket used for sending messages to the process manager. def initialize(path) @path = path @endpoint = IO::Endpoint.unix(path, ::Socket::SOCK_DGRAM) end + # Dump a message in the format requied by `sd_notify`. + # @parameter message [Hash] Keys and values should be string convertible objects. Values which are `true`/`false` are converted to `1`/`0` respectively. def dump(message) buffer = String.new @@ -62,6 +71,8 @@ def dump(message) return buffer end + # Send the given message. + # @parameter message [Hash] def send(**message) data = dump(message) @@ -76,6 +87,8 @@ def send(**message) end end + # Send the specified error. + # `sd_notify` requires an `errno` key, which defaults to `-1` to indicate a generic error. def error!(text, **message) message[:errno] ||= -1 diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 53a46f1..7ce0db6 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -27,8 +27,12 @@ module Async module Container + # Represents a running child process from the point of view of the parent container. class Process < Channel + # Represents a running child process from the point of view of the child process. class Instance < Notify::Pipe + # Wrap an instance around the {Process} instance from within the forked child. + # @parameter process [Process] The process intance to wrap. def self.for(process) instance = self.new(process.out) @@ -46,16 +50,22 @@ def initialize(io) @name = nil end + # Set the process title to the specified value. + # @parameter value [String] The name of the process. def name= value if @name = value ::Process.setproctitle(@name) end end + # The name of the process. + # @returns [String] def name @name end + # Replace the current child process with a different one. Forwards arguments and options to {::Process.exec}. + # This method replaces the child process with the new executable, thus this method never returns. def exec(*arguments, ready: true, **options) if ready self.ready!(status: "(exec)") if ready @@ -63,10 +73,13 @@ def exec(*arguments, ready: true, **options) self.before_spawn(arguments, options) end + # TODO prefer **options... but it doesn't support redirections on < 2.7 ::Process.exec(*arguments, options) end end + # Fork a child process appropriate for a container. + # @returns [Process] def self.fork(**options) self.new(**options) do |process| ::Process.fork do @@ -96,6 +109,8 @@ def self.fork(**options) # end # end + # Initialize the process. + # @parameter name [String] The name to use for the child process. def initialize(name: nil) super() @@ -109,6 +124,8 @@ def initialize(name: nil) self.close_write end + # Set the name of the process. + # Invokes {::Process.setproctitle} if invoked in the child process. def name= value @name = value @@ -116,12 +133,17 @@ def name= value ::Process.setproctitle(@name) if @pid.nil? end + # The name of the process. + # @attribute [String] attr :name + # A human readable representation of the process. + # @returns [String] def to_s "\#<#{self.class} #{@name}>" end + # Invoke {#terminate!} and then {#wait} for the child process to exit. def close self.terminate! self.wait @@ -129,18 +151,22 @@ def close super end + # Send `SIGINT` to the child process. def interrupt! unless @status ::Process.kill(:INT, @pid) end end + # Send `SIGTERM` to the child process. def terminate! unless @status ::Process.kill(:TERM, @pid) end end + # Wait for the child process to exit. + # @returns [::Process::Status] The process exit status. def wait if @pid && @status.nil? _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) diff --git a/lib/async/container/statistics.rb b/lib/async/container/statistics.rb index 80c97ea..be194f9 100644 --- a/lib/async/container/statistics.rb +++ b/lib/async/container/statistics.rb @@ -24,6 +24,7 @@ module Async module Container + # Tracks various statistics relating to child instances in a container. class Statistics def initialize @spawns = 0 @@ -31,26 +32,41 @@ def initialize @failures = 0 end + # How many child instances have been spawned. + # @attribute [Integer] attr :spawns + + # How many child instances have been restarted. + # @attribute [Integer] attr :restarts + + # How many child instances have failed. + # @attribute [Integer] attr :failures + # Increment the number of spawns by 1. def spawn! @spawns += 1 end + # Increment the number of restarts by 1. def restart! @restarts += 1 end + # Increment the number of failures by 1. def failure! @failures += 1 end + # Whether there have been any failures. + # @returns [Boolean] If the failure count is greater than 0. def failed? @failures > 0 end + # Append another statistics instance into this one. + # @parameter other [Statistics] The statistics to append. def << other @spawns += other.spawns @restarts += other.restarts diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index f3f129a..9bf50e4 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -28,14 +28,22 @@ module Async module Container + # Represents a running child thread from the point of view of the parent container. class Thread < Channel + # Used to propagate the exit status of a child process invoked by {Instance#exec}. class Exit < Exception + # Initialize the exit status. + # @parameter status [::Process::Status] The process exit status. def initialize(status) @status = status end + # The process exit status. + # @attribute [::Process::Status] attr :status + # The process exit status if it was an error. + # @returns [::Process::Status | Nil] def error unless status.success? status @@ -43,7 +51,10 @@ def error end end + # Represents a running child thread from the point of view of the child thread. class Instance < Notify::Pipe + # Wrap an instance around the {Thread} instance from within the threaded child. + # @parameter thread [Thread] The thread intance to wrap. def self.for(thread) instance = self.new(thread.out) @@ -57,14 +68,20 @@ def initialize(io) super end + # Set the name of the thread. + # @parameter value [String] The name to set. def name= value @thread.name = value end + # Get the name of the thread. + # @returns [String] def name @thread.name end + # Execute a child process using {::Process.spawn}. In order to simulate {::Process.exec}, an {Exit} instance is raised to propagage exit status. + # This creates the illusion that this method does not return (normally). def exec(*arguments, ready: true, **options) if ready self.ready!(status: "(spawn)") if ready @@ -91,6 +108,8 @@ def self.fork(**options) end end + # Initialize the thread. + # @parameter name [String] The name to use for the child thread. def initialize(name: nil) super() @@ -116,18 +135,25 @@ def initialize(name: nil) end end + # Set the name of the thread. + # @parameter value [String] The name to set. def name= value @thread.name = value end + # Get the name of the thread. + # @returns [String] def name @thread.name end + # A human readable representation of the thread. + # @returns [String] def to_s "\#<#{self.class} #{@thread.name}>" end + # Invoke {#terminate!} and then {#wait} for the child thread to exit. def close self.terminate! self.wait @@ -135,14 +161,18 @@ def close super end + # Raise {Interrupt} in the child thread. def interrupt! @thread.raise(Interrupt) end + # Raise {Terminate} in the child thread. def terminate! @thread.raise(Terminate) end + # Wait for the thread to exit and return he exit status. + # @returns [Status] def wait if @waiter @waiter.join @@ -152,15 +182,21 @@ def wait return @status end + # A pseudo exit-status wrapper. class Status - def initialize(result = nil) - @result = result + # Initialise the status. + # @parameter error [::Process::Status] The exit status of the child thread. + def initialize(error = nil) + @error = error end + # Whether the status represents a successful outcome. + # @returns [Boolean] def success? - @result.nil? + @error.nil? end + # A human readable representation of the status. def to_s "\#<#{self.class} #{success? ? "success" : "failure"}>" end @@ -168,6 +204,7 @@ def to_s protected + # Invoked by the @waiter thread to indicate the outcome of the child thread. def finished(error = nil) if error Async.logger.error(self) {error} diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index d50aa16..5341cfb 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -24,13 +24,17 @@ require_relative 'thread' module Async - # Manages a reactor within one or more threads. module Container + # A multi-thread container which uses {Thread.fork}. class Threaded < Generic + # Indicates that this is not a multi-process container. def self.multiprocess? false end + # Start a named child thread and execute the provided block in it. + # @parameter name [String] The name (title) of the child process. + # @parameter block [Proc] The block to execute in the child process. def start(name, &block) Thread.fork(name: name, &block) end diff --git a/spec/async/container/controller_spec.rb b/spec/async/container/controller_spec.rb index 063710e..be3e899 100644 --- a/spec/async/container/controller_spec.rb +++ b/spec/async/container/controller_spec.rb @@ -99,7 +99,7 @@ def subject.setup(container) expect do subject.run - end.to raise_exception(Async::Container::InitializationError) + end.to raise_exception(Async::Container::SetupError) end end From c45df684ab41c8032bdd8d93fd47641747a72816 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 9 Jul 2020 17:24:36 +1200 Subject: [PATCH 048/166] Remove legacy Guardfile. --- Guardfile | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 Guardfile diff --git a/Guardfile b/Guardfile deleted file mode 100644 index b4f9196..0000000 --- a/Guardfile +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -directories %w(lib spec) -clearing :on - -guard :rspec, cmd: "bundle exec rspec" do - watch(%r{^spec/.+_spec\.rb$}) - watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } - watch("spec/spec_helper.rb") { "spec" } -end - -guard 'yard', :port => '8808' do - watch(%r{^lib/(.+)\.rb$}) -end From 5244c987898c21f8b955427cdaec1b14da90db40 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 9 Jul 2020 17:24:51 +1200 Subject: [PATCH 049/166] Modernize README. --- README.md | 111 ++++++++---------------------------------------------- 1 file changed, 16 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index d2fd3cd..9418254 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,34 @@ # Async::Container -Provides containers which implement concurrency policy for high-level servers (and potentially clients). +Provides containers which implement parallelism for clients and servers. [![Development Status](https://github.com/socketry/async-container/workflows/Development/badge.svg)](https://github.com/socketry/async-container/actions?workflow=Development) -## Installation +## Features -``` bash -$ bundle add async-container -``` +- Supports multi-process, multi-thread and hybrid containers. +- Automatic scalability based on physical hardware. +- Direct integration with [systemd](https://www.freedesktop.org/software/systemd/man/sd_notify.html) using `$NOTIFY_SOCKET`. +- Internal process readiness protocol for handling state changes. +- Automatic restart of failed processes. ## Usage -### Container - -A container represents a set of child processes (or threads) which are doing work for you. - -``` ruby -require 'async/container' - -Async.logger.debug! - -container = Async::Container.new - -container.async do |task| - task.logger.debug "Sleeping..." - task.sleep(1) - task.logger.debug "Waking up!" -end - -Async.logger.debug "Waiting for container..." -container.wait -Async.logger.debug "Finished." -``` - -### Controller - -The controller provides the life-cycle management for one or more containers of processes. It provides behaviour like starting, restarting, reloading and stopping. You can see some [example implementations in Falcon](https://github.com/socketry/falcon/blob/master/lib/falcon/controller/). If the process running the controller receives `SIGHUP` it will recreate the container gracefully. - -``` ruby -require 'async/container' - -Async.logger.debug! - -class Controller < Async::Container::Controller - def setup(container) - container.async do |task| - while true - Async.logger.debug("Sleeping...") - task.sleep(1) - end - end - end -end - -controller = Controller.new - -controller.run - -# If you send SIGHUP to this process, it will recreate the container. -``` - -### Signal Handling - -`SIGINT` is the interrupt signal. The terminal sends it to the foreground process when the user presses **ctrl-c**. The default behavior is to terminate the process, but it can be caught or ignored. The intention is to provide a mechanism for an orderly, graceful shutdown. - -`SIGQUIT` is the dump core signal. The terminal sends it to the foreground process when the user presses **ctrl-\\**. The default behavior is to terminate the process and dump core, but it can be caught or ignored. The intention is to provide a mechanism for the user to abort the process. You can look at `SIGINT` as "user-initiated happy termination" and `SIGQUIT` as "user-initiated unhappy termination." - -`SIGTERM` is the termination signal. The default behavior is to terminate the process, but it also can be caught or ignored. The intention is to kill the process, gracefully or not, but to first allow it a chance to cleanup. - -`SIGKILL` is the kill signal. The only behavior is to kill the process, immediately. As the process cannot catch the signal, it cannot cleanup, and thus this is a signal of last resort. - -`SIGSTOP` is the pause signal. The only behavior is to pause the process; the signal cannot be caught or ignored. The shell uses pausing (and its counterpart, resuming via `SIGCONT`) to implement job control. - -### Integration - -#### systemd - -Install a template file into `/etc/systemd/system/`: - -``` -# my-daemon.service -[Unit] -Description=My Daemon -AssertPathExists=/srv/ - -[Service] -Type=notify -WorkingDirectory=/srv/my-daemon -ExecStart=bundle exec my-daemon -Nice=5 - -[Install] -WantedBy=multi-user.target -``` +Please see the [project documentation](https://socketry.github.io/async-container/). ## Contributing -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request +We welcome contributions to this project. -## License +1. Fork it. +2. Create your feature branch (`git checkout -b my-new-feature`). +3. Commit your changes (`git commit -am 'Add some feature'`). +4. Push to the branch (`git push origin my-new-feature`). +5. Create new Pull Request. -Released under the MIT license. +## License -Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). +Copyright, 2017, by [Samuel G. D. Williams](https://www.codeotaku.com). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 9b2f1cdcac43f798024a184597e9329c5608d92d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 9 Jul 2020 17:25:03 +1200 Subject: [PATCH 050/166] Add static documentation. --- docs/.nojekyll | 0 .../jquery-litebox/jquery.litebox.css | 23 + .../jquery-litebox/jquery.litebox.gallery.css | 48 + .../jquery-litebox/jquery.litebox.js | 30 + .../base/jquery.syntax.brush.apache.css | 12 + .../base/jquery.syntax.brush.applescript.css | 5 + .../base/jquery.syntax.brush.assembly.css | 8 + .../base/jquery.syntax.brush.bash-script.css | 4 + .../base/jquery.syntax.brush.bash.css | 2 + .../base/jquery.syntax.brush.clang.css | 6 + .../base/jquery.syntax.brush.css.css | 14 + .../base/jquery.syntax.brush.diff.css | 16 + .../base/jquery.syntax.brush.html.css | 5 + .../base/jquery.syntax.brush.ocaml.css | 3 + .../base/jquery.syntax.brush.protobuf.css | 2 + .../base/jquery.syntax.brush.python.css | 6 + .../base/jquery.syntax.brush.ruby.css | 2 + .../base/jquery.syntax.brush.xml.css | 18 + .../jquery-syntax/base/jquery.syntax.core.css | 58 + .../base/jquery.syntax.editor.css | 6 + docs/_components/jquery-syntax/base/theme.js | 1 + .../bright/jquery.syntax.core.css | 27 + .../_components/jquery-syntax/bright/theme.js | 1 + .../jquery.syntax.brush.apache.js | 3 + .../jquery.syntax.brush.applescript.js | 5 + .../jquery.syntax.brush.assembly.js | 3 + .../jquery.syntax.brush.bash-script.js | 4 + .../jquery-syntax/jquery.syntax.brush.bash.js | 2 + .../jquery.syntax.brush.basic.js | 5 + .../jquery.syntax.brush.clang.js | 5 + .../jquery.syntax.brush.csharp.js | 4 + .../jquery-syntax/jquery.syntax.brush.css.js | 5 + .../jquery-syntax/jquery.syntax.brush.diff.js | 2 + .../jquery-syntax/jquery.syntax.brush.go.js | 3 + .../jquery.syntax.brush.haskell.js | 3 + .../jquery-syntax/jquery.syntax.brush.html.js | 4 + .../jquery-syntax/jquery.syntax.brush.io.js | 3 + .../jquery-syntax/jquery.syntax.brush.java.js | 4 + .../jquery.syntax.brush.javascript.js | 3 + .../jquery-syntax/jquery.syntax.brush.kai.js | 2 + .../jquery-syntax/jquery.syntax.brush.lisp.js | 2 + .../jquery-syntax/jquery.syntax.brush.lua.js | 3 + .../jquery.syntax.brush.nginx.js | 2 + .../jquery.syntax.brush.ocaml.js | 4 + .../jquery-syntax/jquery.syntax.brush.ooc.js | 4 + .../jquery.syntax.brush.pascal.js | 4 + .../jquery.syntax.brush.perl5.js | 3 + .../jquery.syntax.brush.php-script.js | 4 + .../jquery-syntax/jquery.syntax.brush.php.js | 2 + .../jquery.syntax.brush.plain.js | 2 + .../jquery.syntax.brush.protobuf.js | 3 + .../jquery.syntax.brush.python.js | 5 + .../jquery-syntax/jquery.syntax.brush.ruby.js | 5 + .../jquery.syntax.brush.scala.js | 4 + .../jquery.syntax.brush.smalltalk.js | 2 + .../jquery-syntax/jquery.syntax.brush.sql.js | 4 + .../jquery.syntax.brush.super-collider.js | 3 + .../jquery.syntax.brush.swift.js | 3 + .../jquery.syntax.brush.trenni.js | 2 + .../jquery-syntax/jquery.syntax.brush.xml.js | 4 + .../jquery-syntax/jquery.syntax.brush.yaml.js | 2 + .../jquery-syntax/jquery.syntax.cache.js | 7 + .../jquery-syntax/jquery.syntax.core.js | 34 + .../jquery-syntax/jquery.syntax.editor.js | 11 + .../jquery-syntax/jquery.syntax.js | 8 + .../jquery-syntax/jquery.syntax.min.js | 13 + .../paper/jquery.syntax.core.css | 31 + docs/_components/jquery-syntax/paper/theme.js | 1 + docs/_components/jquery/jquery.js | 10872 ++++++++++++++++ docs/_components/jquery/jquery.min.js | 2 + docs/_components/jquery/jquery.min.map | 1 + docs/_components/jquery/jquery.slim.js | 8777 +++++++++++++ docs/_components/jquery/jquery.slim.min.js | 2 + docs/_components/jquery/jquery.slim.min.map | 1 + docs/_static/icon.png | Bin 0 -> 516 bytes docs/_static/site.css | 216 + docs/guides/getting-started/index.html | 111 + docs/guides/index.html | 39 + docs/index.html | 77 + .../source/Async/Container/Channel/index.html | 97 + .../Async/Container/Controller/index.html | 277 + docs/source/Async/Container/Error/index.html | 41 + docs/source/Async/Container/Forked/index.html | 62 + .../source/Async/Container/Generic/index.html | 306 + docs/source/Async/Container/Hybrid/index.html | 82 + docs/source/Async/Container/Keyed/index.html | 87 + .../Async/Container/Notify/Client/index.html | 87 + .../Async/Container/Notify/Console/index.html | 72 + .../Async/Container/Notify/Pipe/index.html | 105 + .../Notify/Server/Context/index.html | 41 + .../Async/Container/Notify/Server/index.html | 46 + .../Async/Container/Notify/Socket/index.html | 121 + docs/source/Async/Container/Notify/index.html | 62 + .../Container/Process/Instance/index.html | 100 + .../source/Async/Container/Process/index.html | 172 + .../Async/Container/SetupError/index.html | 43 + .../Async/Container/Statistics/index.html | 104 + .../Async/Container/Terminate/index.html | 42 + .../Async/Container/Thread/Exit/index.html | 73 + .../Container/Thread/Instance/index.html | 99 + .../Async/Container/Thread/Status/index.html | 71 + docs/source/Async/Container/Thread/index.html | 164 + .../Async/Container/Threaded/index.html | 62 + docs/source/Async/Container/index.html | 122 + docs/source/Async/index.html | 46 + docs/source/index.html | 963 ++ guides/getting-started/README.md | 101 + 107 files changed, 24275 insertions(+) create mode 100644 docs/.nojekyll create mode 100644 docs/_components/jquery-litebox/jquery.litebox.css create mode 100644 docs/_components/jquery-litebox/jquery.litebox.gallery.css create mode 100644 docs/_components/jquery-litebox/jquery.litebox.js create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.apache.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.applescript.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.assembly.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.bash-script.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.bash.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.clang.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.css.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.diff.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.html.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.ocaml.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.protobuf.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.python.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.ruby.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.xml.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.core.css create mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.editor.css create mode 100644 docs/_components/jquery-syntax/base/theme.js create mode 100644 docs/_components/jquery-syntax/bright/jquery.syntax.core.css create mode 100644 docs/_components/jquery-syntax/bright/theme.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.apache.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.applescript.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.assembly.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.bash-script.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.bash.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.basic.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.clang.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.csharp.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.css.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.diff.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.go.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.haskell.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.html.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.io.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.java.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.javascript.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.kai.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.lisp.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.lua.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.nginx.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.ocaml.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.ooc.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.pascal.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.perl5.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.php-script.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.php.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.plain.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.protobuf.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.python.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.ruby.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.scala.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.smalltalk.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.sql.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.super-collider.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.swift.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.trenni.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.xml.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.yaml.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.cache.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.core.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.editor.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.js create mode 100644 docs/_components/jquery-syntax/jquery.syntax.min.js create mode 100644 docs/_components/jquery-syntax/paper/jquery.syntax.core.css create mode 100644 docs/_components/jquery-syntax/paper/theme.js create mode 100644 docs/_components/jquery/jquery.js create mode 100644 docs/_components/jquery/jquery.min.js create mode 100644 docs/_components/jquery/jquery.min.map create mode 100644 docs/_components/jquery/jquery.slim.js create mode 100644 docs/_components/jquery/jquery.slim.min.js create mode 100644 docs/_components/jquery/jquery.slim.min.map create mode 100644 docs/_static/icon.png create mode 100644 docs/_static/site.css create mode 100644 docs/guides/getting-started/index.html create mode 100644 docs/guides/index.html create mode 100644 docs/index.html create mode 100644 docs/source/Async/Container/Channel/index.html create mode 100644 docs/source/Async/Container/Controller/index.html create mode 100644 docs/source/Async/Container/Error/index.html create mode 100644 docs/source/Async/Container/Forked/index.html create mode 100644 docs/source/Async/Container/Generic/index.html create mode 100644 docs/source/Async/Container/Hybrid/index.html create mode 100644 docs/source/Async/Container/Keyed/index.html create mode 100644 docs/source/Async/Container/Notify/Client/index.html create mode 100644 docs/source/Async/Container/Notify/Console/index.html create mode 100644 docs/source/Async/Container/Notify/Pipe/index.html create mode 100644 docs/source/Async/Container/Notify/Server/Context/index.html create mode 100644 docs/source/Async/Container/Notify/Server/index.html create mode 100644 docs/source/Async/Container/Notify/Socket/index.html create mode 100644 docs/source/Async/Container/Notify/index.html create mode 100644 docs/source/Async/Container/Process/Instance/index.html create mode 100644 docs/source/Async/Container/Process/index.html create mode 100644 docs/source/Async/Container/SetupError/index.html create mode 100644 docs/source/Async/Container/Statistics/index.html create mode 100644 docs/source/Async/Container/Terminate/index.html create mode 100644 docs/source/Async/Container/Thread/Exit/index.html create mode 100644 docs/source/Async/Container/Thread/Instance/index.html create mode 100644 docs/source/Async/Container/Thread/Status/index.html create mode 100644 docs/source/Async/Container/Thread/index.html create mode 100644 docs/source/Async/Container/Threaded/index.html create mode 100644 docs/source/Async/Container/index.html create mode 100644 docs/source/Async/index.html create mode 100644 docs/source/index.html create mode 100644 guides/getting-started/README.md diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/_components/jquery-litebox/jquery.litebox.css b/docs/_components/jquery-litebox/jquery.litebox.css new file mode 100644 index 0000000..74e357b --- /dev/null +++ b/docs/_components/jquery-litebox/jquery.litebox.css @@ -0,0 +1,23 @@ + +.litebox.overlay { + background: rgba(0, 0, 0, 0.96); + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 10; + + display: flex; + align-items: center; + justify-content: center; + + box-sizing: border-box; + padding: 1em; +} + +.litebox.overlay img { + flex-shrink: 1; + max-width: 100%; + max-height: 100%; +} diff --git a/docs/_components/jquery-litebox/jquery.litebox.gallery.css b/docs/_components/jquery-litebox/jquery.litebox.gallery.css new file mode 100644 index 0000000..fab1de7 --- /dev/null +++ b/docs/_components/jquery-litebox/jquery.litebox.gallery.css @@ -0,0 +1,48 @@ + +.gallery { + padding: 0.5rem; + + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: center; +} + +.gallery figure { + margin: 1rem; + + background-color: rgb(250, 250, 250); + box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2); + + /* This ensures that our figures never grow bigger than desired. */ + flex-basis: 400px; +} + +.gallery figure a { + display: block; + margin: 0.5rem; +} + +.gallery img { + border: none; + display: block; + + width: 100%; +} + +.gallery figcaption { + display: block; + margin: 0.5rem; + + font-size: 1rem; + text-align: center; + + display: flex; + align-items: center; + justify-content: center; + + /* The figure caption should have a minimum height, if there are multiple + lines of caption, the total size of the figure is likely to be the same as + others. */ + min-height: 4rem; +} diff --git a/docs/_components/jquery-litebox/jquery.litebox.js b/docs/_components/jquery-litebox/jquery.litebox.js new file mode 100644 index 0000000..0922998 --- /dev/null +++ b/docs/_components/jquery-litebox/jquery.litebox.js @@ -0,0 +1,30 @@ + +(function ($, undefined) { + function showImage(element, overlay) { + if (element.href) { + var image = new Image(); + image.src = element.href; + overlay.append(image); + } + } + + $.fn.litebox = function(callback) { + callback = callback || showImage; + + this.on('click', function() { + var overlay = $('
'); + + overlay.on('click', function() { + overlay.remove(); + $('body').css('overflow', 'auto'); + }); + + callback(this, overlay); + + $('body').css('overflow', 'hidden'); + $('body').append(overlay); + + return false; + }); + } +}(jQuery)); diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.apache.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.apache.css new file mode 100644 index 0000000..dfe774f --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.apache.css @@ -0,0 +1,12 @@ +.syntax-theme-base .apache .tag { + color: #00c; } +.syntax-theme-base .apache .tag-name { + color: #00f; } +.syntax-theme-base .apache .tag-name { + font-weight: bold; } + +@media (prefers-color-scheme: dark) { + .syntax-theme-base .apache .tag { + color: #6666ff; } + .syntax-theme-base .apache .tag-name { + color: #9999ff; } } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.applescript.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.applescript.css new file mode 100644 index 0000000..7e873a3 --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.applescript.css @@ -0,0 +1,5 @@ +.syntax-theme-base .applescript { + font-family: Geneva, Helvetica, sans-serif; } + .syntax-theme-base .applescript .keyword { + color: #00f; + font-weight: bold; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.assembly.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.assembly.css new file mode 100644 index 0000000..3530f26 --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.assembly.css @@ -0,0 +1,8 @@ +.syntax-theme-base .assembly .register { + color: #3caa20; + font-style: italic; } +.syntax-theme-base .assembly .label { + font-weight: bold; + color: #2a85b3; } +.syntax-theme-base .assembly .directive { + color: #828a3d; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash-script.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash-script.css new file mode 100644 index 0000000..6105a18 --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash-script.css @@ -0,0 +1,4 @@ +.syntax-theme-base .bash-script .option { + color: #03f; } +.syntax-theme-base .bash-script .env { + font-style: italic; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash.css new file mode 100644 index 0000000..3c69f7e --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash.css @@ -0,0 +1,2 @@ +.syntax-theme-base .bash .stderr { + color: red; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.clang.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.clang.css new file mode 100644 index 0000000..012433b --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.clang.css @@ -0,0 +1,6 @@ +.syntax-theme-base .preprocessor { + color: #63381f; } + +@media (prefers-color-scheme: dark) { + .syntax-theme-base .preprocessor { + color: #c97e52; } } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.css.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.css.css new file mode 100644 index 0000000..6ea2994 --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.css.css @@ -0,0 +1,14 @@ +.syntax-theme-base .css .selector { + color: #458; + font-weight: bold; } +.syntax-theme-base .css .property { + color: teal; } +.syntax-theme-base .css .color-box { + position: relative; } + .syntax-theme-base .css .color-box .sample { + border: 1px solid #000; + position: absolute; + left: 0.2em; + right: 0.2em; + top: 0.2em; + bottom: 0.2em; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.diff.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.diff.css new file mode 100644 index 0000000..1cfc36a --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.diff.css @@ -0,0 +1,16 @@ +.syntax-theme-base .add { + color: green; } +.syntax-theme-base .del { + color: red; } +.syntax-theme-base .insert { + color: green; } +.syntax-theme-base .insert-line { + background-color: #cfc !important; } +.syntax-theme-base .remove { + color: red; } +.syntax-theme-base .remove-line { + background-color: #fcc !important; } +.syntax-theme-base .offset { + color: blue; } +.syntax-theme-base .offset-line { + background-color: #ccf !important; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.html.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.html.css new file mode 100644 index 0000000..138c9ca --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.html.css @@ -0,0 +1,5 @@ +.syntax-theme-base .html .doctype { + font-weight: bold; + color: #938; } +.syntax-theme-base .html .javascript, .syntax-theme-base .html .css { + font-style: italic; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.ocaml.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.ocaml.css new file mode 100644 index 0000000..6bde078 --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.ocaml.css @@ -0,0 +1,3 @@ +.syntax .ocaml .operator { + color: #06C; + font-weight: bold; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.protobuf.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.protobuf.css new file mode 100644 index 0000000..369af79 --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.protobuf.css @@ -0,0 +1,2 @@ +.syntax .protobuf .variable { + font-weight: bold; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.python.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.python.css new file mode 100644 index 0000000..cfd9805 --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.python.css @@ -0,0 +1,6 @@ +.syntax-theme-base .python .decorator { + color: #ffb600; + font-weight: bold; } +.syntax-theme-base .python .builtin { + color: #33f; + font-style: italic; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.ruby.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.ruby.css new file mode 100644 index 0000000..97c50cf --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.ruby.css @@ -0,0 +1,2 @@ +.syntax-theme-base .ruby .string .ruby { + color: #808080; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.xml.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.xml.css new file mode 100644 index 0000000..218f805 --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.brush.xml.css @@ -0,0 +1,18 @@ +.syntax-theme-base .xml .cdata-content { + color: #046; + border-radius: 0.5em; } +.syntax-theme-base .xml .xml-tag, .syntax-theme-base .xml .cdata { + color: #00c; } +.syntax-theme-base .xml .tag-name, .syntax-theme-base .xml .cdata-tag { + color: #00f; + font-weight: bold; } +.syntax-theme-base .xml .namespace { + color: #00c; } +.syntax-theme-base .xml .attribute { + color: #00c; } +.syntax-theme-base .xml .instruction { + color: #00c; } +.syntax-theme-base .xml .entity, .syntax-theme-base .xml .percent-escape { + color: #666; + background-color: #ddd; + border-radius: 0.5em; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.core.css b/docs/_components/jquery-syntax/base/jquery.syntax.core.css new file mode 100644 index 0000000..1d1f2bc --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.core.css @@ -0,0 +1,58 @@ +pre code.syntax-theme-base { + display: block; } + pre code.syntax-theme-base > span { + display: flex; } + pre code.syntax-theme-base .indent { + flex-grow: 0; + flex-shrink: 0; } + pre code.syntax-theme-base .text { + white-space: pre-wrap; + padding-left: 2ch; + text-indent: -2ch; } + +.syntax-theme-base a { + text-decoration: none; + color: inherit; } +.syntax-theme-base .function { + color: #33f; } +.syntax-theme-base .keyword, .syntax-theme-base .access, .syntax-theme-base .option { + color: #7a0968; } +.syntax-theme-base .type { + color: #3239D6; } +.syntax-theme-base .comment { + color: #6ac; } +.syntax-theme-base .constant { + color: #1c00ce; } +.syntax-theme-base .string, .syntax-theme-base .symbol { + color: #c41a15; } +.syntax-theme-base .string .escape { + color: #f99; } +.syntax-theme-base .operator { + color: black; } +.syntax-theme-base .href { + color: #0e0eff; + text-decoration: underline; } +.syntax-theme-base .variable { + color: #466997; } +.syntax-theme-base .highlight { + background-color: #fdfdba; } + +@media (prefers-color-scheme: dark) { + .syntax-theme-base .operator { + color: white; } + .syntax-theme-base .function { + color: #c59de7; } + .syntax-theme-base .keyword, .syntax-theme-base .access, .syntax-theme-base .option { + color: #328eff; } + .syntax-theme-base .type { + color: #37a4ff; } + .syntax-theme-base .comment { + color: #849fca; } + .syntax-theme-base .constant { + color: #c9a452; } + .syntax-theme-base .string, .syntax-theme-base .symbol { + color: #ed7f7d; } + .syntax-theme-base .string .escape { + color: #f99; } + .syntax-theme-base .variable { + color: #6e6dff; } } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.editor.css b/docs/_components/jquery-syntax/base/jquery.syntax.editor.css new file mode 100644 index 0000000..04ab4da --- /dev/null +++ b/docs/_components/jquery-syntax/base/jquery.syntax.editor.css @@ -0,0 +1,6 @@ +.syntax-container.syntax-theme-base div.editor.syntax { + padding: 0.2em !important; + margin: 0 !important; + white-space: pre; } +.syntax-container.syntax-theme-base div.editor.syntax:focus { + outline: none; } diff --git a/docs/_components/jquery-syntax/base/theme.js b/docs/_components/jquery-syntax/base/theme.js new file mode 100644 index 0000000..f67c776 --- /dev/null +++ b/docs/_components/jquery-syntax/base/theme.js @@ -0,0 +1 @@ +Syntax.themes["base"] = [] diff --git a/docs/_components/jquery-syntax/bright/jquery.syntax.core.css b/docs/_components/jquery-syntax/bright/jquery.syntax.core.css new file mode 100644 index 0000000..f268c77 --- /dev/null +++ b/docs/_components/jquery-syntax/bright/jquery.syntax.core.css @@ -0,0 +1,27 @@ +.syntax-container.syntax-theme-bright .syntax { + font-family: Menlo, Monaco, Consolas, monospace; + line-height: 1.5em; } + .syntax-container.syntax-theme-bright .syntax .function { + color: #33f; } + .syntax-container.syntax-theme-bright .syntax .keyword { + color: #3c3; } + .syntax-container.syntax-theme-bright .syntax .access { + color: #ffb600; + font-weight: bold; } + .syntax-container.syntax-theme-bright .syntax .type { + color: #191; + font-weight: bold; } + .syntax-container.syntax-theme-bright .syntax .comment { + color: #6ac; + font-style: italic; } + .syntax-container.syntax-theme-bright .syntax .string, .syntax-container.syntax-theme-bright .syntax .constant { + color: #f33; } + .syntax-container.syntax-theme-bright .syntax .string .escape { + color: #f99; } + .syntax-container.syntax-theme-bright .syntax .operator { + color: #c00; } + .syntax-container.syntax-theme-bright .syntax .href { + color: #00f; + text-decoration: underline; } + .syntax-container.syntax-theme-bright .syntax .variable { + color: #22c; } diff --git a/docs/_components/jquery-syntax/bright/theme.js b/docs/_components/jquery-syntax/bright/theme.js new file mode 100644 index 0000000..623ee10 --- /dev/null +++ b/docs/_components/jquery-syntax/bright/theme.js @@ -0,0 +1 @@ +Syntax.themes["bright"] = ["base"] diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.apache.js b/docs/_components/jquery-syntax/jquery.syntax.brush.apache.js new file mode 100644 index 0000000..3a12786 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.apache.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("apache",function(a){a.push({pattern:/(<(\w+).*?>)/gi,matches:Syntax.extractMatches({klass:"tag",allow:["attribute","tag-name","string"]},{klass:"tag-name",process:Syntax.lib.webLinkProcess("site:http://httpd.apache.org/docs/trunk/ directive",!0)})});a.push({pattern:/(<\/(\w+).*?>)/gi,matches:Syntax.extractMatches({klass:"tag",allow:["tag-name"]},{klass:"tag-name"})});a.push({pattern:/^\s+([A-Z][\w]+)/gm,matches:Syntax.extractMatches({klass:"function",allow:["attribute"],process:Syntax.lib.webLinkProcess("site:http://httpd.apache.org/docs/trunk/ directive", +!0)})});a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.applescript.js b/docs/_components/jquery-syntax/jquery.syntax.brush.applescript.js new file mode 100644 index 0000000..56e2fc2 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.applescript.js @@ -0,0 +1,5 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("applescript",function(a){a.push("after before beginning continue copy each end every from get global in local named of return set some that the then times to where whose with without".split(" "),{klass:"keyword"});a.push("first second third fourth fifth sixth seventh eighth ninth tenth last front back middle".split(" "),{klass:"keyword"});a.push("activate add alias AppleScript ask attachment boolean class constant delete duplicate empty exists false id integer list make message modal modified new no paragraph pi properties quit real record remove rest result reveal reverse run running save string true word yes".split(" "), +{klass:"keyword"});var b=[/(\-\-|#).*$/gm,/\(\*(?:[\s\S]*?\(\*[\s\S]*?\*\))*[\s\S]*?\*\)/gm,Syntax.lib.perlStyleComment.pattern];a.push(Syntax.lib.webLink);a.push(b,{klass:"comment",allow:["href"]});a.push(Syntax.lib.doubleQuotedString);a.push([/\b\d+(st|nd|rd|th)\b/g,/(-)?(\d)+(\.(\d)?)?(E\+(\d)+)?/g],{klass:"constant"});a.push({pattern:/&|\u00ac|=|\u2260|>|<|\u2265|>=|\u2264|<=|\*|\+|-|\/|\u00f7|\^/g,klass:"operator"});a.push({pattern:/\b(?:and|as|div|mod|not|or|return(?!\s&)(ing)?|equals|(is(n't| not)? )?equal( to)?|does(n't| not) equal|(is(n't| not)? )?(greater|less) than( or equal( to)?)?|(comes|does(n't| not) come) (after|before)|is(n't| not)?( in)? (back|front) of|is(n't| not)? behind|is(n't| not)?( (in|contained by))?|does(n't| not) contain|contain(s)?|(start|begin|end)(s)? with|((but|end) )?(consider|ignor)ing|prop(erty)?|(a )?ref(erence)?( to)?|repeat (until|while|with)|((end|exit) )?repeat|((else|end) )?if|else|(end )?(script|tell|try)|(on )?error|(put )?into|(of )?(it|me)|its|my|with (timeout( of)?|transaction)|end (timeout|transaction))\b/g, +klass:"keyword"});a.push({pattern:/\b(?:about|above|against|around|at|below|beneath|beside|between|by|(apart|aside) from|(instead|out) of|into|on(to)?|over|since|thr(ough|u)|under)\b/g,klass:"keyword"});a.push({pattern:/\b(?:adding folder items to|after receiving|choose( ((remote )?application|color|folder|from list|URL))?|clipboard info|set the clipboard to|(the )?clipboard|entire contents|display(ing| (alert|dialog|mode))?|document( (edited|file|nib name))?|file( (name|type))?|(info )?for|giving up after|(name )?extension|quoted form|return(ed)?|second(?! item)(s)?|list (disks|folder)|text item(s| delimiters)?|(Unicode )?text|(disk )?item(s)?|((current|list) )?view|((container|key) )?window|with (data|icon( (caution|note|stop))?|parameter(s)?|prompt|properties|seed|title)|case|diacriticals|hyphens|numeric strings|punctuation|white space|folder creation|application(s( folder)?| (processes|scripts position|support))?|((desktop )?(pictures )?|(documents|downloads|favorites|home|keychain|library|movies|music|public|scripts|sites|system|users|utilities|workflows) )folder|desktop|Folder Action scripts|font(s| panel)?|help|internet plugins|modem scripts|(system )?preferences|printer descriptions|scripting (additions|components)|shared (documents|libraries)|startup (disk|items)|temporary items|trash|on server|in AppleTalk zone|((as|long|short) )?user name|user (ID|locale)|(with )?password|in (bundle( with identifier)?|directory)|(close|open for) access|read|write( permission)?|(g|s)et eof|using( delimiters)?|starting at|default (answer|button|color|country code|entr(y|ies)|identifiers|items|name|location|script editor)|hidden( answer)?|open(ed| (location|untitled))?|error (handling|reporting)|(do( shell)?|load|run|store) script|administrator privileges|altering line endings|get volume settings|(alert|boot|input|mount|output|set) volume|output muted|(fax|random )?number|round(ing)?|up|down|toward zero|to nearest|as taught in school|system (attribute|info)|((AppleScript( Studio)?|system) )?version|(home )?directory|(IPv4|primary Ethernet) address|CPU (type|speed)|physical memory|time (stamp|to GMT)|replacing|ASCII (character|number)|localized string|from table|offset|summarize|beep|delay|say|(empty|multiple) selections allowed|(of|preferred) type|invisibles|showing( package contents)?|editable URL|(File|FTP|News|Media|Web) [Ss]ervers|Telnet hosts|Directory services|Remote applications|waiting until completion|saving( (in|to))?|path (for|to( (((current|frontmost) )?application|resource))?)|POSIX (file|path)|(background|RGB) color|(OK|cancel) button name|cancel button|button(s)?|cubic ((centi)?met(re|er)s|yards|feet|inches)|square ((kilo)?met(re|er)s|miles|yards|feet)|(centi|kilo)?met(re|er)s|miles|yards|feet|inches|lit(re|er)s|gallons|quarts|(kilo)?grams|ounces|pounds|degrees (Celsius|Fahrenheit|Kelvin)|print( (dialog|settings))?|clos(e(able)?|ing)|(de)?miniaturized|miniaturizable|zoom(ed|able)|attribute run|action (method|property|title)|phone|email|((start|end)ing|home) page|((birth|creation|current|custom|modification) )?date|((((phonetic )?(first|last|middle))|computer|host|maiden|related) |nick)?name|aim|icq|jabber|msn|yahoo|address(es)?|save addressbook|should enable action|city|country( code)?|formatte(r|d address)|(palette )?label|state|street|zip|AIM [Hh]andle(s)?|my card|select(ion| all)?|unsaved|(alpha )?value|entr(y|ies)|group|(ICQ|Jabber|MSN) handle|person|people|company|department|icon image|job title|note|organization|suffix|vcard|url|copies|collating|pages (across|down)|request print time|target( printer)?|((GUI Scripting|Script menu) )?enabled|show Computer scripts|(de)?activated|awake from nib|became (key|main)|call method|of (class|object)|center|clicked toolbar item|closed|for document|exposed|(can )?hide|idle|keyboard (down|up)|event( (number|type))?|launch(ed)?|load (image|movie|nib|sound)|owner|log|mouse (down|dragged|entered|exited|moved|up)|move|column|localization|resource|script|register|drag (info|types)|resigned (active|key|main)|resiz(e(d)?|able)|right mouse (down|dragged|up)|scroll wheel|(at )?index|should (close|open( untitled)?|quit( after last window closed)?|zoom)|((proposed|screen) )?bounds|show(n)?|behind|in front of|size (mode|to fit)|update(d| toolbar item)?|was (hidden|miniaturized)|will (become active|close|finish launching|hide|miniaturize|move|open|quit|(resign )?active|((maximum|minimum|proposed) )?size|show|zoom)|bundle|data source|movie|pasteboard|sound|tool(bar| tip)|(color|open|save) panel|coordinate system|frontmost|main( (bundle|menu|window))?|((services|(excluded from )?windows) )?menu|((executable|frameworks|resource|scripts|shared (frameworks|support)) )?path|(selected item )?identifier|data|content(s| view)?|character(s)?|click count|(command|control|option|shift) key down|context|delta (x|y|z)|key( code)?|location|pressure|unmodified characters|types|(first )?responder|playing|(allowed|selectable) identifiers|allows customization|(auto saves )?configuration|visible|image( name)?|menu form representation|tag|user(-| )defaults|associated file name|(auto|needs) display|current field editor|floating|has (resize indicator|shadow)|hides when deactivated|level|minimized (image|title)|opaque|position|release when closed|sheet|title(d)?)\b/g, +klass:"keyword"})}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.assembly.js b/docs/_components/jquery-syntax/jquery.syntax.brush.assembly.js new file mode 100644 index 0000000..f3013d7 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.assembly.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("assembly",function(a){a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push({pattern:/\.[a-zA-Z_][a-zA-Z0-9_]*/gm,klass:"directive"});a.push({pattern:/^[a-zA-Z_][a-zA-Z0-9_]*:/gm,klass:"label"});a.push({pattern:/^\s*([a-zA-Z]+)/gm,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/(-[0-9]+)|(\b[0-9]+)|(\$[0-9]+)/g,klass:"constant"});a.push({pattern:/(\-|\b|\$)(0x[0-9a-f]+|[0-9]+|[a-z0-9_]+)/gi,klass:"constant"});a.push({pattern:/%\w+/g,klass:"register"}); +a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.bash-script.js b/docs/_components/jquery-syntax/jquery.syntax.brush.bash-script.js new file mode 100644 index 0000000..1da775d --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.bash-script.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("bash-script",function(a){a.push(["&&","|",";","{","}"],{klass:"operator"});a.push({pattern:/(?:^|\||;|&&)\s*((?:"([^"]|\\")+"|'([^']|\\')+'|\\\n|.|[ \t])+?)(?=$|\||;|&&)/gmi,matches:Syntax.extractMatches({brush:"bash-statement"})})}); +Syntax.register("bash-statement",function(a){a.push("break case continue do done elif else eq fi for function ge gt if in le lt ne return then until while".split(" "),{klass:"keyword"});a.push("> < = ` -- { } ( ) [ ]".split(" "),{klass:"operator"});a.push({pattern:/\(\((.*?)\)\)/gmi,klass:"expression",allow:["variable","string","operator","constant"]});a.push({pattern:/`([\s\S]+?)`/gmi,matches:Syntax.extractMatches({brush:"bash-script",debug:!0})});a.push(Syntax.lib.perlStyleComment);a.push({pattern:/^\s*((?:\S+?=\$?(?:\[[^\]]+\]|\(\(.*?\)\)|"(?:[^"]|\\")+"|'(?:[^']|\\')+'|\S+)\s*)*)((?:(\\ |\S)+)?)/gmi, +matches:Syntax.extractMatches({klass:"env",allow:["variable","string","operator","constant","expression"]},{klass:"function",allow:["variable","string"]})});a.push({pattern:/(\S+?)=/gmi,matches:Syntax.extractMatches({klass:"variable"}),only:["env"]});a.push({pattern:/\$\w+/g,klass:"variable"});a.push({pattern:/\s\-+[\w-]+/g,klass:"option"});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.bash.js b/docs/_components/jquery-syntax/jquery.syntax.brush.bash.js new file mode 100644 index 0000000..04efaba --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.bash.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.brushes.dependency("bash","bash-script");Syntax.register("bash",function(a){a.push({pattern:/^([\w@:~ ]*?[\$|#])\s+(.*?)$/gm,matches:Syntax.extractMatches({klass:"prompt"},{brush:"bash-script"})});a.push({pattern:/^\-\- .*$/gm,klass:"comment",allow:["href"]});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.webLink);a.push({klass:"stderr",allow:["string","comment","constant","href"]})}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.basic.js b/docs/_components/jquery-syntax/jquery.syntax.brush.basic.js new file mode 100644 index 0000000..e8ac3df --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.basic.js @@ -0,0 +1,5 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.lib.vbStyleComment={pattern:/' .*$/gm,klass:"comment",allow:["href"]}; +Syntax.register("basic",function(a){var b="- & &= * *= / /= \\ = ^ ^= + += = -=".split(" ");b="+ - * / += -= *= /= = := == != ! % ? > < >= <= && || & | ^ . ~ .. >> << >>> <<< >>= <<= >>>= <<<= %= ^= @".split(" ");a.push("CBool CByte CChar CDate CDec CDbl Char CInt CLng CObj Const CShort CSng CStr CType Date Decimal Variant String Short Long Single Double Object Integer Boolean Byte Char".split(" "),{klass:"type"});a.push("AddHandler AddressOf Alias And AndAlso Ansi As Assembly Auto ByRef ByVal Call Case Catch Declare Default Delegate Dim DirectCast Do Each Else ElseIf End Enum Erase Error Event Exit Finally For Function Get GetType GoSub GoTo Handles If Implements Imports In Inherits Interface Is Let Lib Like Loop Mod Module MustOverride Namespace New Next Not On Option Optional Or OrElse Overloads Overridable Overrides ParamArray Preserve Property RaiseEvent ReadOnly ReDim REM RemoveHandler Resume Return Select Set Static Step Stop Structure Sub SyncLock Then Throw To Try TypeOf Unicode Until When While With WithEvents WriteOnly Xor ExternalSource Region Print Class".split(" "),{klass:"keyword", +options:"gi"});a.push(b,{klass:"operator"});a.push("Public Protected Private Shared Friend Shadows MustInherit NotInheritable NotOverridable".split(" "),{klass:"access"});a.push(["Me","MyClass","MyBase","super","True","False","Nothing",/[A-Z][A-Z0-9_]+/g],{klass:"constant"});a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.vbStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.postprocess=function(a,b,c){jQuery(".function", +b).each(function(){var a=jQuery(this).text();jQuery(this).replaceWith(jQuery("").attr("href","http://social.msdn.microsoft.com/Search/en-us?query="+encodeURIComponent(a)).text(a))});return b}}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.clang.js b/docs/_components/jquery-syntax/jquery.syntax.brush.clang.js new file mode 100644 index 0000000..7719237 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.clang.js @@ -0,0 +1,5 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("clang",function(a){a.push("this true false NULL YES NO nil".split(" "),{klass:"constant"});a.push("mutable auto const register typename abstract".split(" "),{klass:"keyword"});a.push("double float int short char long signed unsigned bool void id".split(" "),{klass:"type"});a.push("@interface @implementation @protocol @end @try @throw @catch @finally @class @selector @encode @synchronized @property @synthesize @dynamic struct break continue else for switch case default enum goto register sizeof typedef volatile do extern if return static union while asm dynamic_cast namespace reinterpret_cast try explicit static_cast typeid catch operator template class const_cast inline throw virtual IBOutlet".split(" "), +{klass:"keyword"});a.push("+ * / - & | ~ ! % < = > [ ] new delete in".split(" "),{klass:"operator"});a.push("@private @protected @public @required @optional private protected public friend using".split(" "),{klass:"access"});a.push({pattern:/@property\((.*)\)[^;]+;/gmi,klass:"objective-c-property",allow:"*"});a.push("getter setter readwrite readonly assign retain copy nonatomic".split(" "),{klass:"keyword",only:["objective-c-property"]});a.push({pattern:/@(?=")/g,klass:"string"});a.push(Syntax.lib.camelCaseType); +a.push(Syntax.lib.cStyleType);a.push({pattern:/(?:class|struct|enum|namespace)\s+([^{;\s]+)/gmi,matches:Syntax.extractMatches({klass:"type"})});a.push({pattern:/#.*$/gmi,klass:"preprocessor",allow:["string"]});a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push({pattern:/\w+:(?=.*(\]|;|\{))(?!:)/g,klass:"function"});a.push({pattern:/[^:\[]\s+(\w+)(?=\])/g,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/-\s*(\([^\)]+?\))?\s*(\w+)\s*\{/g, +matches:Syntax.extractMatches({index:2,klass:"function"})});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.csharp.js b/docs/_components/jquery-syntax/jquery.syntax.brush.csharp.js new file mode 100644 index 0000000..6c1daf2 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.csharp.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("csharp",function(a){a.push(["this","true","false","null"],{klass:"constant"});a.push("object bool byte fixed float uint char ulong ushort decimal int sbyte short void long string double".split(" "),{klass:"type"});a.push("abstract add alias ascending base break case catch class const continue default delegate descending do dynamic else enum event explicit extern finally for foreach from get global goto group if implicit in interface into join let lock namespace new operator orderby out override params partial readonly ref remove return sealed select set stackalloc static struct switch throw try unsafe using value var virtual volatile where while yield".split(" "), +{klass:"keyword"});a.push("+ - * / % & | ^ ! ~ && || ++ -- << >> == != < > <= >= = ? new as is sizeof typeof checked unchecked".split(" "),{klass:"operator"});a.push(["public","private","internal","protected"],{klass:"access"});a.push(Syntax.lib.cStyleFunction);a.push({pattern:/(?:\.)([a-z_][a-z0-9_]+)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString); +a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.css.js b/docs/_components/jquery-syntax/jquery.syntax.brush.css.js new file mode 100644 index 0000000..0aa079e --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.css.js @@ -0,0 +1,5 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("css",function(a){var e=[].concat(jQuery.map("AliceBlue AntiqueWhite Aqua Aquamarine Azure Beige Bisque Black BlanchedAlmond Blue BlueViolet Brown BurlyWood CadetBlue Chartreuse Chocolate Coral CornflowerBlue Cornsilk Crimson Cyan DarkBlue DarkCyan DarkGoldenRod DarkGray DarkGreen DarkKhaki DarkMagenta DarkOliveGreen Darkorange DarkOrchid DarkRed DarkSalmon DarkSeaGreen DarkSlateBlue DarkSlateGray DarkTurquoise DarkViolet DeepPink DeepSkyBlue DimGray DodgerBlue FireBrick FloralWhite ForestGreen Fuchsia Gainsboro GhostWhite Gold GoldenRod Gray Green GreenYellow HoneyDew HotPink IndianRed Indigo Ivory Khaki Lavender LavenderBlush LawnGreen LemonChiffon LightBlue LightCoral LightCyan LightGoldenRodYellow LightGrey LightGreen LightPink LightSalmon LightSeaGreen LightSkyBlue LightSlateGray LightSteelBlue LightYellow Lime LimeGreen Linen Magenta Maroon MediumAquaMarine MediumBlue MediumOrchid MediumPurple MediumSeaGreen MediumSlateBlue MediumSpringGreen MediumTurquoise MediumVioletRed MidnightBlue MintCream MistyRose Moccasin NavajoWhite Navy OldLace Olive OliveDrab Orange OrangeRed Orchid PaleGoldenRod PaleGreen PaleTurquoise PaleVioletRed PapayaWhip PeachPuff Peru Pink Plum PowderBlue Purple Red RosyBrown RoyalBlue SaddleBrown Salmon SandyBrown SeaGreen SeaShell Sienna Silver SkyBlue SlateBlue SlateGray Snow SpringGreen SteelBlue Tan Teal Thistle Tomato Turquoise Violet Wheat White WhiteSmoke Yellow YellowGreen".split(" "), +function(a){return"("+Syntax.Brush.convertStringToTokenPattern(a,!0)+")"}),jQuery.map(["#[0-9a-f]{3,6}","rgba?\\(.+?\\)","hsla?\\(.+?\\)"],function(a){return"("+Syntax.Brush.convertStringToTokenPattern(a,!1)+")"}));a.push({pattern:/\(.*?\)/g,allow:"*",disallow:["property"]});a.push({pattern:/\s*([:\.\[\]"'=\s\w#\.\-,]+)\s+\{/gm,matches:Syntax.extractMatches({klass:"selector",allow:["string"]})});a.push({pattern:new RegExp(e.join("|"),"gi"),klass:"color",process:function(a,d){d=Syntax.innerText(a); +var c=document.createElement("span");c.className="colour-box";var b=document.createElement("span");b.className="sample";b.style.backgroundColor=d;b.appendChild(document.createTextNode(" "));c.appendChild(b);a.appendChild(c);return a}});a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.webLink);a.push({pattern:/\{(.|\n)*?\}/g,klass:"properties",allow:"*"});a.push({pattern:/:(.*?(?=\})|(.|\n)*?(?=(\}|;)))/g,matches:Syntax.extractMatches({klass:"value",allow:["color"],only:["properties"]})});a.push({pattern:/([\-\w]+):/g, +matches:Syntax.extractMatches({klass:"property",process:Syntax.lib.webLinkProcess("http://cssdocs.org/")})});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.diff.js b/docs/_components/jquery-syntax/jquery.syntax.brush.diff.js new file mode 100644 index 0000000..7173edc --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.diff.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("diff",function(a){a.push({pattern:/^\+\+\+.*$/gm,klass:"add"});a.push({pattern:/^\-\-\-.*$/gm,klass:"del"});a.push({pattern:/^@@.*@@/gm,klass:"offset"});a.push({pattern:/^\+[^\+]{1}.*$/gm,klass:"insert"});a.push({pattern:/^\-[^\-]{1}.*$/gm,klass:"remove"});a.postprocess=function(a,b,c){$(".insert",b).closest(".source").addClass("insert-line");$(".remove",b).closest(".source").addClass("remove-line");$(".offset",b).closest(".source").addClass("offset-line");return b}}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.go.js b/docs/_components/jquery-syntax/jquery.syntax.brush.go.js new file mode 100644 index 0000000..12a8a49 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.go.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("go",function(a){a.push(["true","false","iota","nil"],{klass:"constant"});a.push([/u?int\d*/g,/float\d+/g,/complex\d+/g,"byte","uintptr","string"],{klass:"type"});a.push("break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var".split(" "),{klass:"keyword"});a.push("+ & += &= && == != - | -= |= || < <= * ^ *= ^= <- > >= / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^=".split(" "), +{klass:"operator"});a.push("append cap close complex copy imag len make new panic print println real recover".split(" "),{klass:"function"});a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.haskell.js b/docs/_components/jquery-syntax/jquery.syntax.brush.haskell.js new file mode 100644 index 0000000..b56f3ae --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.haskell.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("haskell",function(a){a.push(["True","False"],{klass:"constant"});a.push("as;case;of;class;data;data family;data instance;default;deriving;deriving instance;do;forall;foreign;hiding;if;then;else;import;infix;infixl;infixr;instance;let;in;mdo;module;newtype;proc;qualified;rec;type;type family;type instance;where".split(";"),{klass:"keyword"});a.push("` | \\ - -< -<< -> * ? ?? # <- @ ! :: _ ~ > ; { }".split(" "),{klass:"operator"});a.push(Syntax.lib.camelCaseType);a.push({pattern:/\-\-.*$/gm, +klass:"comment",allow:["href"]});a.push({pattern:/\{\-[\s\S]*?\-\}/gm,klass:"comment",allow:["href"]});a.push(Syntax.lib.webLink);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.html.js b/docs/_components/jquery-syntax/jquery.syntax.brush.html.js new file mode 100644 index 0000000..8d40e8d --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.html.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.brushes.dependency("html","xml");Syntax.brushes.dependency("html","javascript");Syntax.brushes.dependency("html","css");Syntax.brushes.dependency("html","php-script");Syntax.brushes.dependency("html","ruby"); +Syntax.register("html",function(a){a.push({pattern:/((.|\n)*?)<\/script>/gmi,matches:Syntax.extractMatches({brush:"javascript"})});a.push({pattern:/((.|\n)*?)<\/style>/gmi,matches:Syntax.extractMatches({brush:"css"})});a.push({pattern:/((<\?php)([\s\S]*?)(\?>))/gm,matches:Syntax.extractMatches({klass:"php-tag",allow:["keyword","php-script"]},{klass:"keyword"},{brush:"php-script"},{klass:"keyword"})});a.push({pattern:/((<\?rb?)([\s\S]*?)(\?>))/gm, +matches:Syntax.extractMatches({klass:"ruby-tag",allow:["keyword","ruby"]},{klass:"keyword"},{brush:"ruby"},{klass:"keyword"})});a.push({pattern:/<%=?(.*?)(%>)/g,klass:"instruction",allow:["string"]});a.push({pattern://g,matches:Syntax.extractMatches({klass:"doctype"})});a.push({pattern:/(%[0-9a-f]{2})/gi,klass:"percent-escape",only:["html"]});a.derives("xml")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.io.js b/docs/_components/jquery-syntax/jquery.syntax.brush.io.js new file mode 100644 index 0000000..c80dae6 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.io.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("io",function(a){a.push(Syntax.lib.cStyleFunction);a.push(["return"],{klass:"keywords"});a.push("::= := or and @ + * / - & | ~ ! % < = > [ ] new delete".split(" "),{klass:"operator"});a.push({pattern:/\b([ \t]+([a-z]+))/gi,matches:Syntax.extractMatches({index:2,klass:"function"})});a.push({pattern:/\)([ \t]+([a-z]+))/gi,matches:Syntax.extractMatches({index:2,klass:"function"})});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment); +a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.java.js b/docs/_components/jquery-syntax/jquery.syntax.brush.java.js new file mode 100644 index 0000000..643d143 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.java.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("java",function(a){a.push(["this","true","false","null"],{klass:"constant"});a.push("void byte short int long float double boolean char".split(" "),{klass:"type"});a.push("abstract continue for switch assert default goto synchronized do if break implements throw else import throws case enum return transient catch extends try final interface static class finally strictfp volatile const native super while".split(" "),{klass:"keyword"});a.push("++ -- ++ -- + - ~ ! * / % + - << >> >>> < > <= >= == != & ^ | && || ? = += -= *= /= %= &= ^= |= <<= >>= >>>= instanceof new delete".split(" "), +{klass:"operator"});a.push(["private","protected","public","package"],{klass:"access"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.cStyleFunction);a.processes["function"]=Syntax.lib.webLinkProcess('java "Developer Documentation"', +!0)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.javascript.js b/docs/_components/jquery-syntax/jquery.syntax.brush.javascript.js new file mode 100644 index 0000000..4af2fa2 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.javascript.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("javascript",function(a){a.push(["this","true","false","null"],{klass:"constant"});a.push("async await break case catch class const continue debugger default delete do else export extends finally for function if import in instanceof let new return super switch this throw try typeof var void while with yield".split(" "),{klass:"keyword"});a.push("+*/-&|~!%<=>".split(""),{klass:"operator"});a.push("implements package protected interface private public".split(" "),{klass:"access"});a.push(Syntax.lib.perlStyleRegularExpression); +a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.kai.js b/docs/_components/jquery-syntax/jquery.syntax.brush.kai.js new file mode 100644 index 0000000..ad26dd1 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.kai.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("kai",function(a){a.push("()[]{}".split(""),{klass:"operator"});a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.webLink);a.push({pattern:/\(([^\s\(\)]+)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/`[a-z]*/gi,klass:"constant"});a.push(Syntax.lib.multiLineDoubleQuotedString);a.push(Syntax.lib.stringEscape)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.lisp.js b/docs/_components/jquery-syntax/jquery.syntax.brush.lisp.js new file mode 100644 index 0000000..c58e79a --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.lisp.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.lib.lispStyleComment={pattern:/(;+) .*$/gm,klass:"comment",allow:["href"]};Syntax.register("lisp",function(a){a.push(["(",")"],{klass:"operator"});a.push(Syntax.lib.lispStyleComment);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.webLink);a.push({pattern:/\(\s*([^\s\(\)]+)/gmi,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/#[a-z]+/gi,klass:"constant"});a.push(Syntax.lib.multiLineDoubleQuotedString);a.push(Syntax.lib.stringEscape)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.lua.js b/docs/_components/jquery-syntax/jquery.syntax.brush.lua.js new file mode 100644 index 0000000..d113a6b --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.lua.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("lua",function(a){a.push(["self","true","false","nil"],{klass:"constant"});a.push("and break do else elseif end false for function if in local nil not or repeat return then true until while".split(" "),{klass:"keyword"});a.push("+ - * / % ^ # .. = == ~= < > <= >= ? :".split(" "),{klass:"operator"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleFunction);a.push({pattern:/\-\-.*$/gm,klass:"comment",allow:["href"]});a.push({pattern:/\-\-\[\[(\n|.)*?\]\]\-\-/gm,klass:"comment", +allow:["href"]});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.nginx.js b/docs/_components/jquery-syntax/jquery.syntax.brush.nginx.js new file mode 100644 index 0000000..76f494a --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.nginx.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("nginx",function(a){a.push({pattern:/((\w+).*?);/g,matches:Syntax.extractMatches({klass:"directive",allow:"*"},{klass:"function",process:Syntax.lib.webLinkProcess("http://nginx.org/r/")})});a.push({pattern:/(\w+).*?{/g,matches:Syntax.extractMatches({klass:"keyword"})});a.push({pattern:/(\$)[\w]+/g,klass:"variable"});a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.ocaml.js b/docs/_components/jquery-syntax/jquery.syntax.brush.ocaml.js new file mode 100644 index 0000000..ce40494 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.ocaml.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("ocaml",function(a){a.push(["private","public"],{klass:"access"});a.push(["true","false"],{klass:"constant"});a.push(["bool","byte","sbyte",/\bu?int\d*\b/g,"nativeint","unativeint","char","string","decimal","unit","void","float32","single","float64","double","list","array","exn","format","fun","option","ref"],{klass:"type"});a.push("abstract and as assert begin class default delegate do done downcast downto elif else end exception extern finally for fun function if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override rec return static struct then to try type upcast use val when while with yield asr land lor lsl lsr lxor mod sig atomic break checked component const constraint constructor continue eager event external fixed functor global include method mixin object parallel process protected pure sealed trait virtual volatile val".split(" "), +{klass:"keyword"});a.push("! <> % & * + - -> / :: := :> :? :?> < = > ? @ ^ _ ` | ~ ' [< >] <| |> [| |] (| |) (* *) in".split(" "),{klass:"operator"});a.push({pattern:/(?:open|new)\s+((?:\.?[a-z][a-z0-9]*)+)/gi,matches:Syntax.extractMatches({klass:"type"})});a.push({pattern:/(?:\.)([a-z_][a-z0-9_]+)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/(?:\(|,)\s*(\w+\s*=)/g,matches:Syntax.extractMatches({klass:"keyword-argument"})});a.push({pattern:/([a-z_][a-z0-9_]*)\s*\((?!\*)/gi, +matches:Syntax.extractMatches({klass:"function"})});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.webLink);a.push({pattern:/\(\*[\s\S]*?\*\)/g,klass:"comment"});a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.ooc.js b/docs/_components/jquery-syntax/jquery.syntax.brush.ooc.js new file mode 100644 index 0000000..50106db --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.ooc.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("ooc",function(a){a.push(["this","super","true","false","null",/[A-Z][A-Z0-9_]+/g],{klass:"constant"});a.push("Int Int8 Int16 Int32 Int64 Int80 Int128 UInt UInt8 UInt16 UInt32 UInt64 UInt80 UInt128 Octet Short UShort Long ULong LLong ULLong Float Double LDouble Float32 Float64 Float128 Char UChar WChar String Void Pointer Bool SizeT This".split(" "),{klass:"type"});a.push("class interface implement abstract extends from const final static import use extern inline proto break continue fallthrough operator if else for while do switch case as in version return include cover func".split(" "), +{klass:"keyword"});a.push("+ - * / += -= *= /= = := == != ! % ? > < >= <= && || & | ^ . ~ .. >> << >>> <<< >>= <<= >>>= <<<= %= ^= @".split(" "),{klass:"operator"});a.push({pattern:/0[xcb][0-9a-fA-F]+/g,klass:"constant"});a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleType);a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString); +a.push(Syntax.lib.stringEscape);a.processes["function"]=Syntax.lib.webLinkProcess("http://docs.ooc-lang.org/search.html?q=")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.pascal.js b/docs/_components/jquery-syntax/jquery.syntax.brush.pascal.js new file mode 100644 index 0000000..4ed199a --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.pascal.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("pascal",function(a){a.push(["true","false","nil"],{klass:"constant",options:"gi"});a.push("absolute abstract all and_then as asm asmname attribute begin bindable c c_language case class const constructor destructor dispose do downto else end except exit export exports external far file finalization finally for forward function goto if implementation import inherited initialization inline interface interrupt is keywords label library module name near new object of on only operator or_else otherwise packed pascal pow private procedure program property protected public published qualified raise record repeat resident restricted segment set then threadvar to try type unit until uses value var view virtual while with".split(" "), +{klass:"keyword",options:"gi"});a.push("+ - * / div mod and or xor shl shr not = >= > <> <= < in :=".split(" "),{klass:"operator",options:"gi"});a.push(Syntax.lib.camelCaseType);a.push({pattern:/\{[\s\S]*?\}/gm,klass:"comment",allow:["href"]});a.push({pattern:/\(\*[\s\S]*?\*\)/gm,klass:"comment",allow:["href"]});a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber); +a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.perl5.js b/docs/_components/jquery-syntax/jquery.syntax.brush.perl5.js new file mode 100644 index 0000000..9da04dd --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.perl5.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("perl5",function(a){a.push(["this","true","false"],{klass:"constant"});a.push("bless caller continue die do dump else elsif eval exit for foreach goto if import last local my next no our package redo ref require return sub tie tied unless untie until use wantarray while".split(" "),{klass:"keyword"});a.push("-> ++ -- ** ! ~ \\ + - =~ !~ * / % x + - . << >> < > <= >= lt gt le ge == != <=> eq ne cmp ~~ & | ^ && || // .. ... ?: = , => not and or xor".split(" "),{klass:"operator"});a.push("abs accept alarm atan2 bind binmode chdir chmod chomp chop chown chr chroot close closedir connect cos crypt defined delete each endgrent endhostent endnetent endprotoent endpwent endservent eof exec exists exp fcntl fileno flock fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getppid getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt glob gmtime grep hex index int ioctl join keys kill lc lcfirst length link listen localtime lock log lstat map mkdir msgctl msgget msgrcv msgsnd oct open opendir ord pack pipe pop pos print printf prototype push quotemeta rand read readdir readline readlink readpipe recv rename reset reverse rewinddir rindex rmdir scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat study substr symlink syscall sysopen sysread sysseek system syswrite tell telldir time times tr truncate uc ucfirst umask undef unlink unpack unshift utime values vec wait waitpid warn write".split(" "), +{klass:"function"});a.push(Syntax.lib.perlStyleRegularExpression);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.webLink);a.push({pattern:/(\$|@|%)\w+/gi,klass:"variable"});a.push({pattern:/__END__[\s\S]*/gm,klass:"comment"});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.php-script.js b/docs/_components/jquery-syntax/jquery.syntax.brush.php-script.js new file mode 100644 index 0000000..6e77b76 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.php-script.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("php-script",function(a){a.push(["this","true","false"],{klass:"constant"});a.push("abstract and as break case cfunction class const continue declare default die do echo else elseif enddeclare endfor endforeach endif endswitch endwhile extends extends for foreach function global if implements include include_once interface old_function or require require_once return static switch throw use var while xor".split(" "),{klass:"keyword"});a.push("+ * / - & | ~ ! % < = > [ ] new".split(" "), +{klass:"operator"});a.push(["private","protected","public"],{klass:"access"});a.push({pattern:/\$[a-z_][a-z0-9]*/gi,klass:"variable"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber); +a.processes["function"]=Syntax.lib.webLinkProcess("http://www.php.net/manual-lookup.php?pattern=")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.php.js b/docs/_components/jquery-syntax/jquery.syntax.brush.php.js new file mode 100644 index 0000000..32336fd --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.php.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.brushes.dependency("php","php-script");Syntax.register("php",function(a){a.push({pattern:/(<\?(php)?)((.|\n)*?)(\?>)/gm,matches:Syntax.extractMatches({klass:"keyword"},null,{brush:"php-script"},null,{klass:"keyword"})})}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.plain.js b/docs/_components/jquery-syntax/jquery.syntax.brush.plain.js new file mode 100644 index 0000000..5d7e151 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.plain.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("plain",function(a){a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.protobuf.js b/docs/_components/jquery-syntax/jquery.syntax.brush.protobuf.js new file mode 100644 index 0000000..4ee2cfe --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.protobuf.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("protobuf",function(a){a.push("enum extend extensions group import max message option package returns rpc service syntax to default".split(" "),{klass:"keyword"});a.push(["true","false"],{klass:"constant"});a.push("bool bytes double fixed32 fixed64 float int32 int64 sfixed32 sfixed64 sint32 sint64 string uint32 uint64".split(" "),{klass:"type"});a.push(["optional","required","repeated"],{klass:"access"});a.push(Syntax.lib.camelCaseType);a.push({pattern:/\s+(\w+)\s*=\s*\d+/g,matches:Syntax.extractMatches({klass:"variable"})}); +a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.python.js b/docs/_components/jquery-syntax/jquery.syntax.brush.python.js new file mode 100644 index 0000000..36e73c7 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.python.js @@ -0,0 +1,5 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("python",function(a){a.push({pattern:/^\s*@\w+/gm,klass:"decorator"});a.push(["self","True","False","None"],{klass:"constant"});a.push("and as assert break class continue def del elif else except exec finally for from global if import in is lambda not or pass print raise return try while with yield".split(" "),{klass:"keyword"});a.push("!= % %= & &= ( ) * ** **= *= + += , - -= . / // //= /= : ; < << <<= <= <> = == > >= >> >>= @ [ ] ^ ^= ` ` { | |= } ~".split(" "),{klass:"operator"}); +a.push("abs all any basestring bin bool callable chr classmethod cmp compile complex delattr dict dir divmod enumerate eval execfile file filter float format frozenset getattr globals hasattr hash help hex id input int isinstance issubclass iter len list locals long map max min next object oct open ord pow print property range raw_input reduce reload repr reversed round set setattr slice sorted staticmethod str sum super tuple type type unichr unicode vars xrange zip __import__ apply buffer coerce intern".split(" "), +{klass:"builtin"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.perlStyleComment);a.push({pattern:/(['"]{3})([^\1])*?\1/gm,klass:"comment"});a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.processes["function"]=Syntax.lib.webLinkProcess("http://docs.python.org/search.html?q=");a.processes.type=Syntax.lib.webLinkProcess("http://docs.python.org/search.html?q="); +a.processes.builtin=Syntax.lib.webLinkProcess("http://docs.python.org/search.html?q=")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.ruby.js b/docs/_components/jquery-syntax/jquery.syntax.brush.ruby.js new file mode 100644 index 0000000..494e5aa --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.ruby.js @@ -0,0 +1,5 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.lib.rubyStyleFunction={pattern:/(?:def\s+|\.)([a-z_][a-z0-9_]+)/gi,matches:Syntax.extractMatches({klass:"function"})};Syntax.lib.rubyStyleSymbol={pattern:/([:]?):\w+/g,klass:"constant",matches:function(a,b){return""!=a[1]?[]:[new Syntax.Match(a.index,a[0].length,b,a[0])]}}; +Syntax.register("ruby",function(a){a.push(["private","protected","public"],{klass:"access"});a.push(["self","super","true","false","nil"],{klass:"constant"});a.push({pattern:/(%[\S])(\{[\s\S]*?\})/g,matches:Syntax.extractMatches({klass:"function"},{klass:"constant"})});a.push({pattern:/`[^`]+`/g,klass:"string"});a.push({pattern:/#\{([^\}]*)\}/g,matches:Syntax.extractMatches({brush:"ruby",only:["string"]})});a.push(Syntax.lib.rubyStyleRegularExpression);a.push({pattern:/(@+|\$)[\w]+/g,klass:"variable"}); +a.push(Syntax.lib.camelCaseType);a.push("alias and begin break case class def define_method defined? do else elsif end ensure false for if in module next not or raise redo rescue retry return then throw undef unless until when while yield block_given?".split(" "),{klass:"keyword"});a.push("+*/-&|~!%<=>".split(""),{klass:"operator"});a.push(Syntax.lib.rubyStyleSymbol);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString); +a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.rubyStyleFunction);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.scala.js b/docs/_components/jquery-syntax/jquery.syntax.brush.scala.js new file mode 100644 index 0000000..7373bc6 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.scala.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.brushes.dependency("scala","xml"); +Syntax.register("scala",function(a){a.push("abstract do finally import object return trait var case catch class else extends for forSome if lazy match new override package private sealed super try type while with yield def final implicit protected throw val".split(" "),{klass:"keyword"});a.push("_ : = => <- <: <% >: # @".split(" "),{klass:"operator"});a.push(["this","null","true","false"],{klass:"constant"});a.push({pattern:/"""[\s\S]*?"""/g,klass:"string"});a.push(Syntax.lib.doubleQuotedString); +a.push({pattern:/(?:def\s+|\.)([a-z_][a-z0-9_]+)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.derives("xml")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.smalltalk.js b/docs/_components/jquery-syntax/jquery.syntax.brush.smalltalk.js new file mode 100644 index 0000000..2ea8804 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.smalltalk.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("smalltalk",function(a){a.push(["self","super","true","false","nil"],{klass:"constant"});a.push(["[","]","|",":=","."],{klass:"operator"});a.push({pattern:/\w+:/g,klass:"function"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.sql.js b/docs/_components/jquery-syntax/jquery.syntax.brush.sql.js new file mode 100644 index 0000000..e4c938b --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.sql.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.lib.sqlStyleComment={pattern:/-- .*$/gm,klass:"comment",allow:["href"]}; +Syntax.register("sql",function(a){a.push("= != < > <= >= + - * / %".split(" "),{klass:"operator"});a.push(Syntax.lib.sqlStyleComment);a.push("A ABORT ABS ABSOLUTE ACCESS ACTION ADA ADD ADMIN AFTER AGGREGATE ALIAS ALL ALLOCATE ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARE ARRAY AS ASC ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTRIBUTE ATTRIBUTES AUDIT AUTHORIZATION AUTO_INCREMENT AVG AVG_ROW_LENGTH BACKUP BACKWARD BEFORE BEGIN BERNOULLI BETWEEN BIGINT BINARY BIT BIT_LENGTH BITVAR BLOB BOOL BOOLEAN BOTH BREADTH BREAK BROWSE BULK BY C CACHE CALL CALLED CARDINALITY CASCADE CASCADED CASE CAST CATALOG CATALOG_NAME CEIL CEILING CHAIN CHANGE CHAR CHAR_LENGTH CHARACTER CHARACTER_LENGTH CHARACTER_SET_CATALOG CHARACTER_SET_NAME CHARACTER_SET_SCHEMA CHARACTERISTICS CHARACTERS CHECK CHECKED CHECKPOINT CHECKSUM CLASS CLASS_ORIGIN CLOB CLOSE CLUSTER CLUSTERED COALESCE COBOL COLLATE COLLATION COLLATION_CATALOG COLLATION_NAME COLLATION_SCHEMA COLLECT COLUMN COLUMN_NAME COLUMNS COMMAND_FUNCTION COMMAND_FUNCTION_CODE COMMENT COMMIT COMMITTED COMPLETION COMPRESS COMPUTE CONDITION CONDITION_NUMBER CONNECT CONNECTION CONNECTION_NAME CONSTRAINT CONSTRAINT_CATALOG CONSTRAINT_NAME CONSTRAINT_SCHEMA CONSTRAINTS CONSTRUCTOR CONTAINS CONTAINSTABLE CONTINUE CONVERSION CONVERT COPY CORR CORRESPONDING COUNT COVAR_POP COVAR_SAMP CREATE CREATEDB CREATEROLE CREATEUSER CROSS CSV CUBE CUME_DIST CURRENT CURRENT_DATE CURRENT_DEFAULT_TRANSFORM_GROUP CURRENT_PATH CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_TRANSFORM_GROUP_FOR_TYPE CURRENT_USER CURSOR CURSOR_NAME CYCLE DATA DATABASE DATABASES DATE DATETIME DATETIME_INTERVAL_CODE DATETIME_INTERVAL_PRECISION DAY DAY_HOUR DAY_MICROSECOND DAY_MINUTE DAY_SECOND DAYOFMONTH DAYOFWEEK DAYOFYEAR DBCC DEALLOCATE DEC DECIMAL DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINED DEFINER DEGREE DELAY_KEY_WRITE DELAYED DELETE DELIMITER DELIMITERS DENSE_RANK DENY DEPTH DEREF DERIVED DESC DESCRIBE DESCRIPTOR DESTROY DESTRUCTOR DETERMINISTIC DIAGNOSTICS DICTIONARY DISABLE DISCONNECT DISK DISPATCH DISTINCT DISTINCTROW DISTRIBUTED DIV DO DOMAIN DOUBLE DROP DUAL DUMMY DUMP DYNAMIC DYNAMIC_FUNCTION DYNAMIC_FUNCTION_CODE EACH ELEMENT ELSE ELSEIF ENABLE ENCLOSED ENCODING ENCRYPTED END END-EXEC ENUM EQUALS ERRLVL ESCAPE ESCAPED EVERY EXCEPT EXCEPTION EXCLUDE EXCLUDING EXCLUSIVE EXEC EXECUTE EXISTING EXISTS EXIT EXP EXPLAIN EXTERNAL EXTRACT FALSE FETCH FIELDS FILE FILLFACTOR FILTER FINAL FIRST FLOAT FLOAT4 FLOAT8 FLOOR FLUSH FOLLOWING FOR FORCE FOREIGN FORTRAN FORWARD FOUND FREE FREETEXT FREETEXTTABLE FREEZE FROM FULL FULLTEXT FUNCTION FUSION G GENERAL GENERATED GET GLOBAL GO GOTO GRANT GRANTED GRANTS GREATEST GROUP GROUPING HANDLER HAVING HEADER HEAP HIERARCHY HIGH_PRIORITY HOLD HOLDLOCK HOST HOSTS HOUR HOUR_MICROSECOND HOUR_MINUTE HOUR_SECOND IDENTIFIED IDENTITY IDENTITY_INSERT IDENTITYCOL IF IGNORE ILIKE IMMEDIATE IMMUTABLE IMPLEMENTATION IMPLICIT IN INCLUDE INCLUDING INCREMENT INDEX INDICATOR INFILE INFIX INHERIT INHERITS INITIAL INITIALIZE INITIALLY INNER INOUT INPUT INSENSITIVE INSERT INSERT_ID INSTANCE INSTANTIABLE INSTEAD INT INT1 INT2 INT3 INT4 INT8 INTEGER INTERSECT INTERSECTION INTERVAL INTO INVOKER IS ISAM ISNULL ISOLATION ITERATE JOIN K KEY KEY_MEMBER KEY_TYPE KEYS KILL LANCOMPILER LANGUAGE LARGE LAST LAST_INSERT_ID LATERAL LEADING LEAST LEAVE LEFT LENGTH LESS LEVEL LIKE LIMIT LINENO LINES LISTEN LN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCATOR LOCK LOGIN LOGS LONG LONGBLOB LONGTEXT LOOP LOW_PRIORITY LOWER M MAP MATCH MATCHED MAX MAX_ROWS MAXEXTENTS MAXVALUE MEDIUMBLOB MEDIUMINT MEDIUMTEXT MEMBER MERGE MESSAGE_LENGTH MESSAGE_OCTET_LENGTH MESSAGE_TEXT METHOD MIDDLEINT MIN MIN_ROWS MINUS MINUTE MINUTE_MICROSECOND MINUTE_SECOND MINVALUE MLSLABEL MOD MODE MODIFIES MODIFY MODULE MONTH MONTHNAME MORE MOVE MULTISET MUMPS MYISAM NAMES NATIONAL NATURAL NCHAR NCLOB NESTING NEW NEXT NO NO_WRITE_TO_BINLOG NOAUDIT NOCHECK NOCOMPRESS NOCREATEDB NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN NONCLUSTERED NONE NORMALIZE NORMALIZED NOSUPERUSER NOT NOTHING NOTIFY NOTNULL NOWAIT NULL NULLABLE NULLIF NULLS NUMBER NUMERIC OBJECT OCTET_LENGTH OCTETS OF OFF OFFLINE OFFSET OFFSETS OIDS OLD ON ONLINE ONLY OPEN OPENDATASOURCE OPENQUERY OPENROWSET OPENXML OPERATION OPERATOR OPTIMIZE OPTION OPTIONALLY OPTIONS OR ORDER ORDERING ORDINALITY OTHERS OUT OUTER OUTFILE OUTPUT OVER OVERLAPS OVERLAY OVERRIDING OWNER PACK_KEYS PAD PARAMETER PARAMETER_MODE PARAMETER_NAME PARAMETER_ORDINAL_POSITION PARAMETER_SPECIFIC_CATALOG PARAMETER_SPECIFIC_NAME PARAMETER_SPECIFIC_SCHEMA PARAMETERS PARTIAL PARTITION PASCAL PASSWORD PATH PCTFREE PERCENT PERCENT_RANK PERCENTILE_CONT PERCENTILE_DISC PLACING PLAN PLI POSITION POSTFIX POWER PRECEDING PRECISION PREFIX PREORDER PREPARE PREPARED PRESERVE PRIMARY PRINT PRIOR PRIVILEGES PROC PROCEDURAL PROCEDURE PROCESS PROCESSLIST PUBLIC PURGE QUOTE RAID0 RAISERROR RANGE RANK RAW READ READS READTEXT REAL RECHECK RECONFIGURE RECURSIVE REF REFERENCES REFERENCING REGEXP REGR_AVGX REGR_AVGY REGR_COUNT REGR_INTERCEPT REGR_R2 REGR_SLOPE REGR_SXX REGR_SXY REGR_SYY REINDEX RELATIVE RELEASE RELOAD RENAME REPEAT REPEATABLE REPLACE REPLICATION REQUIRE RESET RESIGNAL RESOURCE RESTART RESTORE RESTRICT RESULT RETURN RETURNED_CARDINALITY RETURNED_LENGTH RETURNED_OCTET_LENGTH RETURNED_SQLSTATE RETURNS REVOKE RIGHT RLIKE ROLE ROLLBACK ROLLUP ROUTINE ROUTINE_CATALOG ROUTINE_NAME ROUTINE_SCHEMA ROW ROW_COUNT ROW_NUMBER ROWCOUNT ROWGUIDCOL ROWID ROWNUM ROWS RULE SAVE SAVEPOINT SCALE SCHEMA SCHEMA_NAME SCHEMAS SCOPE SCOPE_CATALOG SCOPE_NAME SCOPE_SCHEMA SCROLL SEARCH SECOND SECOND_MICROSECOND SECTION SECURITY SELECT SELF SENSITIVE SEPARATOR SEQUENCE SERIALIZABLE SERVER_NAME SESSION SESSION_USER SET SETOF SETS SETUSER SHARE SHOW SHUTDOWN SIGNAL SIMILAR SIMPLE SIZE SMALLINT SOME SONAME SOURCE SPACE SPATIAL SPECIFIC SPECIFIC_NAME SPECIFICTYPE SQL SQL_BIG_RESULT SQL_BIG_SELECTS SQL_BIG_TABLES SQL_CALC_FOUND_ROWS SQL_LOG_OFF SQL_LOG_UPDATE SQL_LOW_PRIORITY_UPDATES SQL_SELECT_LIMIT SQL_SMALL_RESULT SQL_WARNINGS SQLCA SQLCODE SQLERROR SQLEXCEPTION SQLSTATE SQLWARNING SQRT SSL STABLE START STARTING STATE STATEMENT STATIC STATISTICS STATUS STDDEV_POP STDDEV_SAMP STDIN STDOUT STORAGE STRAIGHT_JOIN STRICT STRING STRUCTURE STYLE SUBCLASS_ORIGIN SUBLIST SUBMULTISET SUBSTRING SUCCESSFUL SUM SUPERUSER SYMMETRIC SYNONYM SYSDATE SYSID SYSTEM SYSTEM_USER TABLE TABLE_NAME TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TERMINATE TERMINATED TEXT TEXTSIZE THAN THEN TIES TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TINYBLOB TINYINT TINYTEXT TO TOAST TOP TOP_LEVEL_COUNT TRAILING TRAN TRANSACTION TRANSACTION_ACTIVE TRANSACTIONS_COMMITTED TRANSACTIONS_ROLLED_BACK TRANSFORM TRANSFORMS TRANSLATE TRANSLATION TREAT TRIGGER TRIGGER_CATALOG TRIGGER_NAME TRIGGER_SCHEMA TRIM TRUE TRUNCATE TRUSTED TSEQUAL TYPE UESCAPE UID UNBOUNDED UNCOMMITTED UNDER UNDO UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOCK UNNAMED UNNEST UNSIGNED UNTIL UPDATE UPDATETEXT UPPER USAGE USE USER USER_DEFINED_TYPE_CATALOG USER_DEFINED_TYPE_CODE USER_DEFINED_TYPE_NAME USER_DEFINED_TYPE_SCHEMA USING UTC_DATE UTC_TIME UTC_TIMESTAMP VACUUM VALID VALIDATE VALIDATOR VALUE VALUES VAR_POP VAR_SAMP VARBINARY VARCHAR VARCHAR2 VARCHARACTER VARIABLE VARIABLES VARYING VERBOSE VIEW VOLATILE WAITFOR WHEN WHENEVER WHERE WHILE WIDTH_BUCKET WINDOW WITH WITHIN WITHOUT WORK WRITE WRITETEXT X509 XOR YEAR YEAR_MONTH ZEROFILL ZONE".split(" "),{klass:"keyword", +options:"gi"});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.super-collider.js b/docs/_components/jquery-syntax/jquery.syntax.brush.super-collider.js new file mode 100644 index 0000000..e59faa5 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.super-collider.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("super-collider",function(a){a.push(["const","arg","classvar","var"],{klass:"keyword"});a.push("`+@:*/-&|~!%<=>".split(""),{klass:"operator"});a.push("thisFunctionDef thisFunction thisMethod thisProcess thisThread this super true false nil inf".split(" "),{klass:"constant"});a.push(Syntax.lib.camelCaseType);a.push({pattern:/\$(\\)?./g,klass:"constant"});a.push({pattern:/\\[a-z_][a-z0-9_]*/gi,klass:"symbol"});a.push({pattern:/'[^']+'/g,klass:"symbol"});a.push(Syntax.lib.cStyleComment); +a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push({pattern:/(?:\.)([a-z_][a-z0-9_]*)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.swift.js b/docs/_components/jquery-syntax/jquery.syntax.brush.swift.js new file mode 100644 index 0000000..e800c16 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.swift.js @@ -0,0 +1,3 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("swift",function(a){a.push(["fileprivate","open","private","public"],{klass:"access"});a.push(["self","super","true","false","nil"],{klass:"constant"});a.push({pattern:/`[^`]+`/g,klass:"identifier"});a.push({pattern:/\\\(([^)]*)\)/g,matches:Syntax.extractMatches({brush:"swift",only:["string"]})});a.push(Syntax.lib.camelCaseType);a.push("associatedtype class deinit enum extension fileprivate func import init inout internal let operator private protocol static struct subscript typealias var break case continue default defer do else fallthrough for guard if in repeat return switch where while as catch is rethrows throw throws try _ #available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation associativity convenience dynamic didSet final get infix indirect lazy left mutating none nonmutating optional override postfix precedence prefix Protocol required right set Type unowned weak willSet".split(" "), +{klass:"keyword"});a.push("+ * / - & | ~ ! % < = > ( ) { } [ ] . , : ; = @ # -> ` ? !".split(" "),{klass:"operator"});a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.trenni.js b/docs/_components/jquery-syntax/jquery.syntax.brush.trenni.js new file mode 100644 index 0000000..ebd9b5e --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.trenni.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.brushes.dependency("trenni","xml");Syntax.brushes.dependency("trenni","ruby");Syntax.register("trenni",function(a){a.push({pattern:/((<\?r)([\s\S]*?)(\?>))/gm,matches:Syntax.extractMatches({klass:"ruby-tag",allow:["keyword","ruby"]},{klass:"keyword"},{brush:"ruby"},{klass:"keyword"})});a.push({pattern:/((#{)([\s\S]*?)(}))/gm,matches:Syntax.extractMatches({klass:"ruby-tag",allow:["keyword","ruby"]},{klass:"keyword"},{brush:"ruby"},{klass:"keyword"})});a.derives("xml")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.xml.js b/docs/_components/jquery-syntax/jquery.syntax.brush.xml.js new file mode 100644 index 0000000..a3ba225 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.xml.js @@ -0,0 +1,4 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.lib.xmlEntity={pattern:/&\w+;/g,klass:"entity"};Syntax.lib.xmlPercentEscape={pattern:/(%[0-9a-f]{2})/gi,klass:"percent-escape",only:["string"]}; +Syntax.register("xml-tag",function(a){a.push({pattern:/<\/?((?:[^:\s>]+:)?)([^\s>]+)(\s[^>]*)?\/?>/g,matches:Syntax.extractMatches({klass:"namespace"},{klass:"tag-name"})});a.push({pattern:/([^=\s]+)=(".*?"|'.*?'|[^\s>]+)/g,matches:Syntax.extractMatches({klass:"attribute",only:["tag"]},{klass:"string",only:["tag"]})});a.push(Syntax.lib.xmlEntity);a.push(Syntax.lib.xmlPercentEscape);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString)}); +Syntax.register("xml",function(a){a.push({pattern:/()/gm,matches:Syntax.extractMatches({klass:"cdata",allow:["cdata-content","cdata-tag"]},{klass:"cdata-tag"},{klass:"cdata-content"},{klass:"cdata-tag"})});a.push(Syntax.lib.xmlComment);a.push({pattern:/<[^>\-\s]([^>'"!\/;\?@\[\]^`\{\}\|]|"[^"]*"|'[^']')*[\/?]?>/g,brush:"xml-tag"});a.push(Syntax.lib.xmlEntity);a.push(Syntax.lib.xmlPercentEscape);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.yaml.js b/docs/_components/jquery-syntax/jquery.syntax.brush.yaml.js new file mode 100644 index 0000000..0b04f4e --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.brush.yaml.js @@ -0,0 +1,2 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.register("yaml",function(a){a.push({pattern:/^\s*#.*$/gm,klass:"comment",allow:["href"]});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push({pattern:/(&|\*)[a-z0-9]+/gi,klass:"constant"});a.push({pattern:/(.*?):/gi,matches:Syntax.extractMatches({klass:"keyword"})});a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.cache.js b/docs/_components/jquery-syntax/jquery.syntax.cache.js new file mode 100644 index 0000000..5e0a209 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.cache.js @@ -0,0 +1,7 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Syntax.alias("apache",[]);Syntax.alias("applescript",[]);Syntax.alias("assembly",["asm"]);Syntax.alias("bash-script",[]);Syntax.alias("bash",[]);Syntax.alias("basic",["vb"]);Syntax.alias("clang",["cpp","c++","c","objective-c"]);Syntax.alias("csharp",["c-sharp","c#"]);Syntax.alias("css",[]);Syntax.alias("diff",["patch"]);Syntax.alias("go",[]);Syntax.alias("haskell",[]);Syntax.alias("html",[]);Syntax.alias("io",[]);Syntax.alias("java",[]);Syntax.alias("javascript",["js","actionscript"]); +Syntax.alias("kai",[]);Syntax.alias("lisp",["scheme","clojure"]);Syntax.alias("lua",[]);Syntax.alias("nginx",[]);Syntax.alias("ocaml",["ml","sml","fsharp"]);Syntax.alias("ooc",[]);Syntax.alias("pascal",["delphi"]);Syntax.alias("perl5",[]);Syntax.alias("php-script",[]);Syntax.alias("php",[]);Syntax.alias("plain",["text"]);Syntax.alias("protobuf",[]);Syntax.alias("python",[]);Syntax.alias("ruby",[]);Syntax.alias("scala",[]);Syntax.alias("smalltalk",[]);Syntax.alias("sql",[]); +Syntax.alias("super-collider",["sc"]);Syntax.alias("swift",[]);Syntax.alias("html",[]);Syntax.alias("xml",[]);Syntax.alias("yaml",[]);Syntax.styles["jquery.syntax.brush.apache"]=["base/jquery.syntax.brush.apache.css"];Syntax.styles["jquery.syntax.brush.applescript"]=["base/jquery.syntax.brush.applescript.css"];Syntax.styles["jquery.syntax.brush.assembly"]=["base/jquery.syntax.brush.assembly.css"];Syntax.styles["jquery.syntax.brush.bash-script"]=["base/jquery.syntax.brush.bash-script.css"]; +Syntax.styles["jquery.syntax.brush.bash"]=["base/jquery.syntax.brush.bash.css"];Syntax.styles["jquery.syntax.brush.clang"]=["base/jquery.syntax.brush.clang.css"];Syntax.styles["jquery.syntax.brush.css"]=["base/jquery.syntax.brush.css.css"];Syntax.styles["jquery.syntax.brush.diff"]=["base/jquery.syntax.brush.diff.css"];Syntax.styles["jquery.syntax.brush.html"]=["base/jquery.syntax.brush.html.css"];Syntax.styles["jquery.syntax.brush.ocaml"]=["base/jquery.syntax.brush.ocaml.css"]; +Syntax.styles["jquery.syntax.brush.protobuf"]=["base/jquery.syntax.brush.protobuf.css"];Syntax.styles["jquery.syntax.brush.python"]=["base/jquery.syntax.brush.python.css"];Syntax.styles["jquery.syntax.brush.ruby"]=["base/jquery.syntax.brush.ruby.css"];Syntax.styles["jquery.syntax.brush.xml"]=["base/jquery.syntax.brush.xml.css"];Syntax.styles["jquery.syntax.core"]=["base/jquery.syntax.core.css","bright/jquery.syntax.core.css","paper/jquery.syntax.core.css"];Syntax.styles["jquery.syntax.editor"]=["base/jquery.syntax.editor.css"]; +Syntax.themes.base=[];Syntax.themes.bright=["base"];Syntax.themes.paper=["base"]; diff --git a/docs/_components/jquery-syntax/jquery.syntax.core.js b/docs/_components/jquery-syntax/jquery.syntax.core.js new file mode 100644 index 0000000..a3025ce --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.core.js @@ -0,0 +1,34 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +RegExp.prototype.indexOf||(RegExp.indexOf=function(b,a){return b[0].indexOf(b[a])+b.index});RegExp.prototype.escape||(RegExp.escape=function(b){return b.replace(/[\-\[\]{}()*+?.\\\^$|,#\s]/g,"\\$&")});String.prototype.repeat||(String.prototype.repeat=function(b){return Array(b+1).join(this)});Syntax.innerText=function(b){if(!b)return"";if("BR"==b.nodeName)return"\n";if(b.textContent)var a=b.textContent;else document.body.innerText&&(a=b.innerText);return a.replace(/\r\n?/g,"\n")}; +Syntax.extractTextFromSelection=function(b){for(var a="",c=0;c)/gm,klass:"comment"};Syntax.lib.webLink={pattern:/\w+:\/\/[\w\-.\/?%&=@:;#]*/g,klass:"href"};Syntax.lib.hexNumber={pattern:/\b0x[0-9a-fA-F]+/g,klass:"constant"};Syntax.lib.decimalNumber={pattern:/\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/g,klass:"constant"}; +Syntax.lib.doubleQuotedString={pattern:/"([^\\"\n]|\\.)*"/g,klass:"string"};Syntax.lib.singleQuotedString={pattern:/'([^\\'\n]|\\.)*'/g,klass:"string"};Syntax.lib.multiLineDoubleQuotedString={pattern:/"([^\\"]|\\.)*"/g,klass:"string"};Syntax.lib.multiLineSingleQuotedString={pattern:/'([^\\']|\\.)*'/g,klass:"string"};Syntax.lib.stringEscape={pattern:/\\./g,klass:"escape",only:["string"]}; +Syntax.Match=function(b,a,c,d){this.offset=b;this.endOffset=b+a;this.length=a;this.expression=c;this.value=d;this.children=[];this.next=this.parent=null};Syntax.Match.prototype.shift=function(b,a){this.adjust(b,null,a);for(var c=0;c=this.offset&&b.endOffset<=this.endOffset};Syntax.Match.defaultReduceCallback=function(b,a){"string"===typeof b&&(b=document.createTextNode(b));a.appendChild(b)}; +Syntax.Match.prototype.reduce=function(b,a){var c=this.offset;var d=this.expression&&this.expression.element?this.expression.element.cloneNode(!1):document.createElement("span");b=b||Syntax.Match.defaultReduceCallback;this.expression&&this.expression.klass&&(0this.endOffset&&console.log("Syntax Warning: Start position "+c+" exceeds end of value "+this.endOffset);a&&(d=a(d,this));return d}; +Syntax.Match.prototype.canContain=function(b){return b.expression.force?!0:this.complete?!1:b.expression.only?!0:"undefined"===typeof this.expression.allow||jQuery.isArray(this.expression.disallow)&&-1!==jQuery.inArray(b.expression.klass,this.expression.disallow)?!1:"*"===this.expression.allow||jQuery.isArray(this.expression.allow)&&-1!==jQuery.inArray(b.expression.klass,this.expression.allow)?!0:!1}; +Syntax.Match.prototype.canHaveChild=function(b){if(b=b.expression.only){for(var a=this;null!==a;){if(-1!==jQuery.inArray(a.expression.klass,b))return!0;if((a=a.parent)&&a.complete)break}return!1}return!0};Syntax.Match.prototype._splice=function(b,a){return this.canHaveChild(a)?(this.children.splice(b,0,a),a.parent=this,a.expression.owner||(a.expression.owner=this.expression.owner),this):null}; +Syntax.Match.prototype.insert=function(b,a){if(!this.contains(b))return null;if(a){a=this;for(var c=0;c=c.endOffset)){if(c.contains(b))return c._insert(b);b=b.bisectAtOffsets([c.offset,c.endOffset]);b[0]&&this._splice(a,b[0]);b[1]&&c.insert(b[1]);if(b[2])b=b[2];else return this}}this._splice(this.children.length,b)}; +Syntax.Match.prototype.bisectAtOffsets=function(b){var a=[],c=this.offset,d=null,e=jQuery.merge([],this.children);b=b.slice(0);b.push(this.endOffset);b.sort(function(a,b){return a-b});for(var f=0;fthis.endOffset)break;g").attr("href",this.innerHTML).text(this.innerHTML))});e(f,a,b,d)})}; +Syntax.extractBrushName=function(b){b=b.toLowerCase();var a=b.match(/(brush|language)-([\S]+)/);if(a)return a[2];b=b.split(/ /);if(-1!==jQuery.inArray("syntax",b))for(a=0;aa.start&&d>a.start;)if(this.current.lines[c-1]==b.lines[d-1])--c,--d;else break;a.end=d;a.originalEnd=c;for(a.difference=b.lines.length-this.current.lines.length;0');c.append(b.children());var d=new Syntax.Editor(c.get(0)),f=function(b){var c=d.getClientState(),e=d.updateChangedLines();0>e.difference&&0').append(c)}; diff --git a/docs/_components/jquery-syntax/jquery.syntax.js b/docs/_components/jquery-syntax/jquery.syntax.js new file mode 100644 index 0000000..2349764 --- /dev/null +++ b/docs/_components/jquery-syntax/jquery.syntax.js @@ -0,0 +1,8 @@ +// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. +Function.prototype.bind||(Function.prototype.bind=function(a){var b=Array.prototype.slice.call(arguments,1),c=this;return function(){return c.apply(a,b)}});function ResourceLoader(a){this.dependencies={};this.loading={};this.loader=a}ResourceLoader.prototype._finish=function(a){var b=this.dependencies[a];if(b){a=this._loaded.bind(this,a);for(var c=0;c");jQuery("head").append(b);Syntax.cacheStyleSheets||(a=a+"?"+Math.random());b.attr({rel:"stylesheet", +type:"text/css",href:a})},getScript:function(a,b){var c=document.createElement("script");c.onreadystatechange=function(){!this.onload||"loaded"!=this.readyState&&"complete"!=this.readyState||(this.onload(),this.onload=null)};c.onload=b;c.type="text/javascript";Syntax.cacheScripts||(a=a+"?"+Math.random());c.src=a;document.getElementsByTagName("head")[0].appendChild(c)},getResource:function(a,b,c){Syntax.detectRoot();a=a+"."+b;if(b=this.styles[a])for(var d=0;d");jQuery("head").append(b);Syntax.cacheStyleSheets||(a=a+"?"+Math.random());b.attr({rel:"stylesheet", +type:"text/css",href:a})},getScript:function(a,b){var c=document.createElement("script");c.onreadystatechange=function(){!this.onload||"loaded"!=this.readyState&&"complete"!=this.readyState||(this.onload(),this.onload=null)};c.onload=b;c.type="text/javascript";Syntax.cacheScripts||(a=a+"?"+Math.random());c.src=a;document.getElementsByTagName("head")[0].appendChild(c)},getResource:function(a,b,c){Syntax.detectRoot();a=a+"."+b;if(b=this.styles[a])for(var d=0;d span:nth-child(odd) { + background-color: rgba(0, 0, 0, 0.05); } diff --git a/docs/_components/jquery-syntax/paper/theme.js b/docs/_components/jquery-syntax/paper/theme.js new file mode 100644 index 0000000..ea7e7ce --- /dev/null +++ b/docs/_components/jquery-syntax/paper/theme.js @@ -0,0 +1 @@ +Syntax.themes["paper"] = ["base"] diff --git a/docs/_components/jquery/jquery.js b/docs/_components/jquery/jquery.js new file mode 100644 index 0000000..5093733 --- /dev/null +++ b/docs/_components/jquery/jquery.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + +

Getting Started

+ +

This guide explains how to use async-container to build basic scalable systems.

+

Installation

+

Add the gem to your project:

+
$ bundle add async-container
+
+

Core Concepts

+

async-container has several core concepts:

+
    +
  • class Async::Container::Forked and class Async::Container::Threaded are used to manage one or more child processes and threads respectively for parallel execution. While threads share the address space which can reduce overall memory usage, processes have better isolation and fault tolerance.
  • +
  • class Async::Container::Controller manages one or more containers and handles graceful restarts. Containers should be implemented in such a way that multiple containers can be running at the same time.
  • +
+

Containers

+

A container represents a set of child processes (or threads) which are doing work for you.

+
require 'async/container'
+
+Async.logger.debug!
+
+container = Async::Container.new
+
+container.async do |task|
+	task.logger.debug "Sleeping..."
+	task.sleep(1)
+	task.logger.debug "Waking up!"
+end
+
+Async.logger.debug "Waiting for container..."
+container.wait
+Async.logger.debug "Finished."
+
+

Controllers

+

The controller provides the life-cycle management for one or more containers of processes. It provides behaviour like starting, restarting, reloading and stopping. You can see some example implementations in Falcon. If the process running the controller receives SIGHUP it will recreate the container gracefully.

+
require 'async/container'
+
+Async.logger.debug!
+
+class Controller < Async::Container::Controller
+	def setup(container)
+		container.async do |task|
+			while true
+				Async.logger.debug("Sleeping...")
+				task.sleep(1)
+			end
+		end
+	end
+end
+
+controller = Controller.new
+
+controller.run
+
+# If you send SIGHUP to this process, it will recreate the container.
+
+

Signal Handling

+

SIGINT is the interrupt signal. The terminal sends it to the foreground process when the user presses ctrl-c. The default behavior is to terminate the process, but it can be caught or ignored. The intention is to provide a mechanism for an orderly, graceful shutdown.

+

SIGQUIT is the dump core signal. The terminal sends it to the foreground process when the user presses ctrl-\. The default behavior is to terminate the process and dump core, but it can be caught or ignored. The intention is to provide a mechanism for the user to abort the process. You can look at SIGINT as "user-initiated happy termination" and SIGQUIT as "user-initiated unhappy termination."

+

SIGTERM is the termination signal. The default behavior is to terminate the process, but it also can be caught or ignored. The intention is to kill the process, gracefully or not, but to first allow it a chance to cleanup.

+

SIGKILL is the kill signal. The only behavior is to kill the process, immediately. As the process cannot catch the signal, it cannot cleanup, and thus this is a signal of last resort.

+

SIGSTOP is the pause signal. The only behavior is to pause the process; the signal cannot be caught or ignored. The shell uses pausing (and its counterpart, resuming via SIGCONT) to implement job control.

+

Integration

+

systemd

+

Install a template file into /etc/systemd/system/:

+
# my-daemon.service
+[Unit]
+Description=My Daemon
+AssertPathExists=/srv/
+
+[Service]
+Type=notify
+WorkingDirectory=/srv/my-daemon
+ExecStart=bundle exec my-daemon
+Nice=5
+
+[Install]
+WantedBy=multi-user.target
+
+
+ + + + + \ No newline at end of file diff --git a/docs/guides/index.html b/docs/guides/index.html new file mode 100644 index 0000000..c610cb2 --- /dev/null +++ b/docs/guides/index.html @@ -0,0 +1,39 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Guides

+ +
+ +
+ + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..301225f --- /dev/null +++ b/docs/index.html @@ -0,0 +1,77 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container

Provides containers which implement parallelism for clients and servers.

+

Development Status

+

Features

+
    +
  • Supports multi-process, multi-thread and hybrid containers.
  • +
  • Automatic scalability based on physical hardware.
  • +
  • Direct integration with systemd using $NOTIFY_SOCKET.
  • +
  • Internal process readiness protocol for handling state changes.
  • +
  • Automatic restart of failed processes.
  • +
+

Usage

+

Please browse the source code index or refer to the guides below.

+ +

Getting Started

+ +

This guide explains how to use async-container to build basic scalable systems.

+ + +

Contributing

+

We welcome contributions to this project.

+
    +
  1. Fork it.
  2. +
  3. Create your feature branch (git checkout -b my-new-feature).
  4. +
  5. Commit your changes (git commit -am 'Add some feature').
  6. +
  7. Push to the branch (git push origin my-new-feature).
  8. +
  9. Create new Pull Request.
  10. +
+

License

+

Copyright, 2017, by Samuel G. D. Williams.

+

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+

The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software.

+

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.

+
+ + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Channel/index.html b/docs/source/Async/Container/Channel/index.html new file mode 100644 index 0000000..da42353 --- /dev/null +++ b/docs/source/Async/Container/Channel/index.html @@ -0,0 +1,97 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Channel

+ +
+

Provides a basic multi-thread/multi-process uni-directional communication channel.

+ + +

Definitions

+ +

def initialize

Initialize the channel using a pipe.

+
+

Implementation

+
def initialize
+	@in, @out = ::IO.pipe
+end
+

attr :in

The input end of the pipe.

+
+

Signature

+
+ attribute IO
+
+

attr :out

The output end of the pipe.

+
+

Signature

+
+ attribute IO
+
+

def close_read

Close the input end of the pipe.

+
+

Implementation

+
def close_read
+	@in.close
+end
+

def close_write

Close the output end of the pipe.

+
+

Implementation

+
def close_write
+	@out.close
+end
+

def close

Close both ends of the pipe.

+
+

Implementation

+
def close
+	close_read
+	close_write
+end
+

def receive

Receive an object from the pipe. +Internally, prefers to receive newline formatted JSON, otherwise returns a hash table with a single key :line which contains the line of data that could not be parsed as JSON.

+
+

Signature

+
+ returns Hash
+
+
+

Implementation

+
def receive
+	if data = @in.gets
+		begin
+			return JSON.parse(data, symbolize_names: true)
+		rescue
+			return {line: data}
+		end
+	end
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Controller/index.html b/docs/source/Async/Container/Controller/index.html new file mode 100644 index 0000000..af20725 --- /dev/null +++ b/docs/source/Async/Container/Controller/index.html @@ -0,0 +1,277 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Controller

+ +
+

Manages the life-cycle of one or more containers in order to support a persistent system. +e.g. a web server, job server or some other long running system.

+ + +

Definitions

+ +

def initialize(notify: Notify.open!)

Initialize the controller.

+
+

Signature

+
+ parameter notify Notify::Client

A client used for process readiness notifications.

+
+
+
+

Implementation

+
def initialize(notify: Notify.open!)
+	@container = nil
+	
+	if @notify = notify
+		@notify.status!("Initializing...")
+	end
+	
+	@signals = {}
+	
+	trap(SIGHUP) do
+		self.restart
+	end
+end
+

def state_string

The state of the controller.

+
+

Signature

+
+ returns String
+
+
+

Implementation

+
def state_string
+	if running?
+		"running"
+	else
+		"stopped"
+	end
+end
+

def to_s

A human readable representation of the controller.

+
+

Signature

+
+ returns String
+
+
+

Implementation

+
def to_s
+	"#{self.class} #{state_string}"
+end
+

def trap(signal, &block)

Trap the specified signal.

+
+

Implementation

+
def trap(signal, &block)
+	@signals[signal] = block
+end
+

attr :container

The current container being managed by the controller.

+

def create_container

Create a container for the controller. +Can be overridden by a sub-class.

+
+

Signature

+
+ returns Generic

A specific container instance to use.

+
+
+
+

Implementation

+
def create_container
+	Container.new
+end
+

def running?

Whether the controller has a running container.

+
+

Signature

+
+ returns Boolean
+
+
+

Implementation

+
def running?
+	!!@container
+end
+

def wait

Wait for the underlying container to start.

+
+

Implementation

+
def wait
+	@container&.wait
+end
+

def setup(container)

Spawn container instances into the given container. +Should be overridden by a sub-class.

+
+

Signature

+
+ parameter container Generic

The container, generally from #create_container.

+
+
+
+

Implementation

+
def setup(container)
+	# Don't do this, otherwise calling super is risky for sub-classes:
+	# raise NotImplementedError, "Container setup is must be implemented in derived class!"
+end
+

def start

Start the container unless it's already running.

+
+

Implementation

+
def start
+	self.restart unless @container
+end
+

def stop(graceful = true)

Stop the container if it's running.

+
+

Signature

+
+ parameter graceful Boolean

Whether to give the children instances time to shut down or to kill them immediately.

+
+
+
+

Implementation

+
def stop(graceful = true)
+	@container&.stop(graceful)
+	@container = nil
+end
+

def restart

Restart the container. A new container is created, and if successful, any old container is terminated gracefully.

+
+

Implementation

+
def restart
+	if @container
+		@notify&.restarting!
+		
+		Async.logger.debug(self) {"Restarting container..."}
+	else
+		Async.logger.debug(self) {"Starting container..."}
+	end
+	
+	container = self.create_container
+	
+	begin
+		self.setup(container)
+	rescue
+		@notify&.error!($!.to_s)
+		
+		raise SetupError, container
+	end
+	
+	# Wait for all child processes to enter the ready state.
+	Async.logger.debug(self, "Waiting for startup...")
+	container.wait_until_ready
+	Async.logger.debug(self, "Finished startup.")
+	
+	if container.failed?
+		@notify&.error!($!.to_s)
+		
+		container.stop
+		
+		raise SetupError, container
+	end
+	
+	# Make this swap as atomic as possible:
+	old_container = @container
+	@container = container
+	
+	Async.logger.debug(self, "Stopping old container...")
+	old_container&.stop
+	@notify&.ready!
+rescue
+	# If we are leaving this function with an exception, try to kill the container:
+	container&.stop(false)
+	
+	raise
+end
+

def reload

Reload the existing container. Children instances will be reloaded using SIGHUP.

+
+

Implementation

+
def reload
+	@notify&.reloading!
+	
+	Async.logger.info(self) {"Reloading container: #{@container}..."}
+	
+	begin
+		self.setup(@container)
+	rescue
+		raise SetupError, container
+	end
+	
+	# Wait for all child processes to enter the ready state.
+	Async.logger.debug(self, "Waiting for startup...")
+	@container.wait_until_ready
+	Async.logger.debug(self, "Finished startup.")
+	
+	if @container.failed?
+		@notify.error!("Container failed!")
+		
+		raise SetupError, @container
+	else
+		@notify&.ready!
+	end
+end
+

def run

Enter the controller run loop, trapping SIGINT and SIGTERM.

+
+

Implementation

+
def run
+	# I thought this was the default... but it doesn't always raise an exception unless you do this explicitly.
+	interrupt_action = Signal.trap(:INT) do
+		raise Interrupt
+	end
+	
+	terminate_action = Signal.trap(:TERM) do
+		raise Terminate
+	end
+	
+	self.start
+	
+	while @container&.running?
+		begin
+			@container.wait
+		rescue SignalException => exception
+			if handler = @signals[exception.signo]
+				begin
+					handler.call
+				rescue SetupError => error
+					Async.logger.error(self) {error}
+				end
+			else
+				raise
+			end
+		end
+	end
+rescue Interrupt
+	self.stop(true)
+rescue Terminate
+	self.stop(false)
+ensure
+	self.stop(true)
+	
+	# Restore the interrupt handler:
+	Signal.trap(:INT, interrupt_action)
+	Signal.trap(:TERM, terminate_action)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Error/index.html b/docs/source/Async/Container/Error/index.html new file mode 100644 index 0000000..70ff007 --- /dev/null +++ b/docs/source/Async/Container/Error/index.html @@ -0,0 +1,41 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Error

+ +
+ + +

Definitions

+ +
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Forked/index.html b/docs/source/Async/Container/Forked/index.html new file mode 100644 index 0000000..826ac56 --- /dev/null +++ b/docs/source/Async/Container/Forked/index.html @@ -0,0 +1,62 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Forked

+ +
+

A multi-process container which uses Async::Container::Process.fork.

+ + +

Definitions

+ +

def self.multiprocess?

Indicates that this is a multi-process container.

+
+

Implementation

+
def self.multiprocess?
+	true
+end
+

def start(name, &block)

Start a named child process and execute the provided block in it.

+
+

Signature

+
+ parameter name String

The name (title) of the child process.

+
+ parameter block Proc

The block to execute in the child process.

+
+
+
+

Implementation

+
def start(name, &block)
+	Process.fork(name: name, &block)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Generic/index.html b/docs/source/Async/Container/Generic/index.html new file mode 100644 index 0000000..664280f --- /dev/null +++ b/docs/source/Async/Container/Generic/index.html @@ -0,0 +1,306 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Generic

+ +
+

A base class for implementing containers.

+ + +

Definitions

+ +

def run(count: Container.processor_count, **options, &block)

Run multiple instances of the same block in the container.

+
+

Signature

+
+ parameter count Integer

The number of instances to start.

+
+
+
+

Implementation

+
def run(count: Container.processor_count, **options, &block)
+	count.times do
+		spawn(**options, &block)
+	end
+	
+	return self
+end
+

def to_s

A human readable representation of the container.

+
+

Signature

+
+ returns String
+
+
+

Implementation

+
def to_s
+	"#{self.class} with #{@statistics.spawns} spawns and #{@statistics.failures} failures."
+end
+

def [] key

Look up a child process by key. +A key could be a symbol, a file path, or something else which the child instance represents.

+
+

Implementation

+
def [] key
+	@keyed[key]&.value
+end
+

attr :statistics

Statistics relating to the behavior of children instances.

+
+

Signature

+
+ attribute Statistics
+
+

def failed?

Whether any failures have occurred within the container.

+
+

Signature

+
+ returns Boolean
+
+
+

Implementation

+
def failed?
+	@statistics.failed?
+end
+

def running?

Whether the container has running children instances.

+
+

Implementation

+
def running?
+	@group.running?
+end
+

def sleep(duration = nil)

Sleep until some state change occurs.

+
+

Signature

+
+ parameter duration Numeric

the maximum amount of time to sleep for.

+
+
+
+

Implementation

+
def sleep(duration = nil)
+	@group.sleep(duration)
+end
+

def wait

Wait until all spawned tasks are completed.

+
+

Implementation

+
def wait
+	@group.wait
+end
+

def status?(flag)

Returns true if all children instances have the specified status flag set. +e.g. :ready. +This state is updated by the process readiness protocol mechanism. See class Async::Container::Notify::Client for more details.

+
+

Signature

+
+ returns Boolean
+
+
+

Implementation

+
def status?(flag)
+	# This also returns true if all processes have exited/failed:
+	@state.all?{|_, state| state[flag]}
+end
+

def wait_until_ready

Wait until all the children instances have indicated that they are ready.

+
+

Signature

+
+ returns Boolean

The children all became ready.

+
+
+
+

Implementation

+
def wait_until_ready
+	while true
+		Async.logger.debug(self) do |buffer|
+			buffer.puts "Waiting for ready:"
+			@state.each do |child, state|
+				buffer.puts "\t#{child.class}: #{state.inspect}"
+			end
+		end
+		
+		self.sleep
+		
+		if self.status?(:ready)
+			return true
+		end
+	end
+end
+

def stop(timeout = true)

Stop the children instances.

+
+

Signature

+
+ parameter timeout Boolean | Numeric

Whether to stop gracefully, or a specific timeout.

+
+
+
+

Implementation

+
def stop(timeout = true)
+	@running = false
+	@group.stop(timeout)
+	
+	if @group.running?
+		Async.logger.warn(self) {"Group is still running after stopping it!"}
+	end
+ensure
+	@running = true
+end
+

def spawn(name: nil, restart: false, key: nil, &block)

Spawn a child instance into the container.

+
+

Signature

+
+ parameter name String

The name of the child instance.

+
+ parameter restart Boolean

Whether to restart the child instance if it fails.

+
+ parameter key Symbol

A key used for reloading child instances.

+
+
+
+

Implementation

+
def spawn(name: nil, restart: false, key: nil, &block)
+	name ||= UNNAMED
+	
+	if mark?(key)
+		Async.logger.debug(self) {"Reusing existing child for #{key}: #{name}"}
+		return false
+	end
+	
+	@statistics.spawn!
+	
+	Fiber.new do
+		while @running
+			child = self.start(name, &block)
+			
+			state = insert(key, child)
+			
+			begin
+				status = @group.wait_for(child) do |message|
+					state.update(message)
+				end
+			ensure
+				delete(key, child)
+			end
+			
+			if status.success?
+				Async.logger.info(self) {"#{child} exited with #{status}"}
+			else
+				@statistics.failure!
+				Async.logger.error(self) {status}
+			end
+			
+			if restart
+				@statistics.restart!
+			else
+				break
+			end
+		end
+	# ensure
+	# 	Async.logger.error(self) {$!} if $!
+	end.resume
+	
+	return true
+end
+

def async(**options, &block)

+

Signature

+
+ deprecated

Please use Async::Container::Generic#spawn or Async::Container::Generic.run instead.

+
+
+
+

Implementation

+
def async(**options, &block)
+	spawn(**options) do |instance|
+		Async::Reactor.run(instance, &block)
+	end
+end
+

def reload

Reload the container's keyed instances.

+
+

Implementation

+
def reload
+	@keyed.each_value(&:clear!)
+	
+	yield
+	
+	dirty = false
+	
+	@keyed.delete_if do |key, value|
+		value.stop? && (dirty = true)
+	end
+	
+	return dirty
+end
+

def mark?(key)

Mark the container's keyed instance which ensures that it won't be discarded.

+
+

Implementation

+
def mark?(key)
+	if key
+		if value = @keyed[key]
+			value.mark!
+			
+			return true
+		end
+	end
+	
+	return false
+end
+

def key?(key)

Whether a child instance exists for the given key.

+
+

Implementation

+
def key?(key)
+	if key
+		@keyed.key?(key)
+	end
+end
+

def insert(key, child)

Register the child (value) as running.

+
+

Implementation

+
def insert(key, child)
+	if key
+		@keyed[key] = Keyed.new(key, child)
+	end
+	
+	state = {}
+	
+	@state[child] = state
+	
+	return state
+end
+

def delete(key, child)

Clear the child (value) as running.

+
+

Implementation

+
def delete(key, child)
+	if key
+		@keyed.delete(key)
+	end
+	
+	@state.delete(child)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Hybrid/index.html b/docs/source/Async/Container/Hybrid/index.html new file mode 100644 index 0000000..c777d7d --- /dev/null +++ b/docs/source/Async/Container/Hybrid/index.html @@ -0,0 +1,82 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Hybrid

+ +
+

Provides a hybrid multi-process multi-thread container.

+ + +

Definitions

+ +

def run(count: nil, forks: nil, threads: nil, **options, &block)

Run multiple instances of the same block in the container.

+
+

Signature

+
+ parameter count Integer

The number of instances to start.

+
+ parameter forks Integer

The number of processes to fork.

+
+ parameter threads Integer

the number of threads to start.

+
+
+
+

Implementation

+
def run(count: nil, forks: nil, threads: nil, **options, &block)
+	processor_count = Container.processor_count
+	count ||= processor_count ** 2
+	forks ||= [processor_count, count].min
+	threads = (count / forks).ceil
+	
+	forks.times do
+		self.spawn(**options) do |instance|
+			container = Threaded.new
+			
+			container.run(count: threads, **options, &block)
+			
+			container.wait_until_ready
+			instance.ready!
+			
+			container.wait
+		rescue Async::Container::Terminate
+			# Stop it immediately:
+			container.stop(false)
+		ensure
+			# Stop it gracefully (also code path for Interrupt):
+			container.stop
+		end
+	end
+	
+	return self
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Keyed/index.html b/docs/source/Async/Container/Keyed/index.html new file mode 100644 index 0000000..8420a0e --- /dev/null +++ b/docs/source/Async/Container/Keyed/index.html @@ -0,0 +1,87 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Keyed

+ +
+

Tracks a key/value pair such that unmarked keys can be identified and cleaned up. +This helps implement persistent processes that start up child processes per directory or configuration file. If those directories and/or configuration files are removed, the child process can then be cleaned up automatically, because those key/value pairs will not be marked when reloading the container.

+ + +

Definitions

+ +

attr :key

The key. Normally a symbol or a file-system path.

+
+

Signature

+
+ attribute Object
+
+

attr :value

The value. Normally a child instance of some sort.

+
+

Signature

+
+ attribute Object
+
+

def marked?

Has the instance been marked?

+
+

Signature

+
+ returns Boolean
+
+
+

Implementation

+
def marked?
+	@marked
+end
+

def mark!

Mark the instance. This will indiciate that the value is still in use/active.

+
+

Implementation

+
def mark!
+	@marked = true
+end
+

def clear!

Clear the instance. This is normally done before reloading a container.

+
+

Implementation

+
def clear!
+	@marked = false
+end
+

def stop?

Stop the instance if it was not marked.

+
+

Implementation

+
def stop?
+	unless @marked
+		@value.stop
+		return true
+	end
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Client/index.html b/docs/source/Async/Container/Notify/Client/index.html new file mode 100644 index 0000000..83c5b5f --- /dev/null +++ b/docs/source/Async/Container/Notify/Client/index.html @@ -0,0 +1,87 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Notify::Client

+ +
+ + +

Definitions

+ +

def ready!(**message)

Notify the parent controller that the child has become ready, with a brief status message.

+
+

Implementation

+
def ready!(**message)
+	send(ready: true, **message)
+end
+

def reloading!(**message)

Notify the parent controller that the child is reloading.

+
+

Implementation

+
def reloading!(**message)
+	message[:ready] = false
+	message[:reloading] = true
+	message[:status] ||= "Reloading..."
+	
+	send(**message)
+end
+

def restarting!(**message)

Notify the parent controller that the child is restarting.

+
+

Implementation

+
def restarting!(**message)
+	message[:ready] = false
+	message[:reloading] = true
+	message[:status] ||= "Restarting..."
+	
+	send(**message)
+end
+

def stopping!(**message)

Notify the parent controller that the child is stopping.

+
+

Implementation

+
def stopping!(**message)
+	message[:stopping] = true
+	
+	send(**message)
+end
+

def status!(text)

Notify the parent controller of a status change.

+
+

Implementation

+
def status!(text)
+	send(status: text)
+end
+

def error!(text, **message)

Notify the parent controller of an error condition.

+
+

Implementation

+
def error!(text, **message)
+	send(status: text, **message)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Console/index.html b/docs/source/Async/Container/Notify/Console/index.html new file mode 100644 index 0000000..cdbf4e6 --- /dev/null +++ b/docs/source/Async/Container/Notify/Console/index.html @@ -0,0 +1,72 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Notify::Console

+ +
+

Implements a general process readiness protocol with output to the local console.

+ + +

Definitions

+ +

def self.open!(logger = ::Console.logger)

Open a notification client attached to the current console.

+
+

Implementation

+
def self.open!(logger = ::Console.logger)
+	self.new(logger)
+end
+

def initialize(logger)

Initialize the notification client.

+
+

Signature

+
+ parameter logger Console::Logger

The console logger instance to send messages to.

+
+
+
+

Implementation

+
def initialize(logger)
+	@logger = logger
+end
+

def send(level: :debug, **message)

Send a message to the console.

+
+

Implementation

+
def send(level: :debug, **message)
+	@logger.send(level, self) {message}
+end
+

def error!(text, **message)

Send an error message to the console.

+
+

Implementation

+
def error!(text, **message)
+	send(status: text, level: :error, **message)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Pipe/index.html b/docs/source/Async/Container/Notify/Pipe/index.html new file mode 100644 index 0000000..0896678 --- /dev/null +++ b/docs/source/Async/Container/Notify/Pipe/index.html @@ -0,0 +1,105 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Notify::Pipe

+ +
+

Implements a process readiness protocol using an inherited pipe file descriptor.

+ + +

Definitions

+ +

NOTIFY_PIPE = 'NOTIFY_PIPE'

The environment variable key which contains the pipe file descriptor.

+

def self.open!(environment = ENV)

Open a notification client attached to the current NOTIFY_PIPE = 'NOTIFY_PIPE' if possible.

+
+

Implementation

+
def self.open!(environment = ENV)
+	if descriptor = environment.delete(NOTIFY_PIPE)
+		self.new(::IO.for_fd(descriptor.to_i))
+	end
+rescue Errno::EBADF => error
+	Async.logger.error(self) {error}
+	
+	return nil
+end
+

def initialize(io)

Initialize the notification client.

+
+

Signature

+
+ parameter io IO

An IO instance used for sending messages.

+
+
+
+

Implementation

+
def initialize(io)
+	@io = io
+end
+

def before_spawn(arguments, options)

Inserts or duplicates the environment given an argument array. +Sets or clears it in a way that is suitable for ::Process.spawn.

+
+

Implementation

+
def before_spawn(arguments, options)
+	environment = environment_for(arguments)
+	
+	# Use `notify_pipe` option if specified:
+	if notify_pipe = options.delete(:notify_pipe)
+		options[notify_pipe] = @io
+		environment[NOTIFY_PIPE] = notify_pipe.to_s
+	
+	# Use stdout if it's not redirected:
+	# This can cause issues if the user expects stdout to be connected to a terminal.
+	# elsif !options.key?(:out)
+	# 	options[:out] = @io
+	# 	environment[NOTIFY_PIPE] = "1"
+	
+	# Use fileno 3 if it's available:
+	elsif !options.key?(3)
+		options[3] = @io
+		environment[NOTIFY_PIPE] = "3"
+	
+	# Otherwise, give up!
+	else
+		raise ArgumentError, "Please specify valid file descriptor for notify_pipe!"
+	end
+end
+

def send(**message)

Formats the message using JSON and sends it to the parent controller. +This is suitable for use with class Async::Container::Channel.

+
+

Implementation

+
def send(**message)
+	data = ::JSON.dump(message)
+	
+	@io.puts(data)
+	@io.flush
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Server/Context/index.html b/docs/source/Async/Container/Notify/Server/Context/index.html new file mode 100644 index 0000000..26496ab --- /dev/null +++ b/docs/source/Async/Container/Notify/Server/Context/index.html @@ -0,0 +1,41 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Notify::Server::Context

+ +
+ + +

Definitions

+ +
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Server/index.html b/docs/source/Async/Container/Notify/Server/index.html new file mode 100644 index 0000000..7053af1 --- /dev/null +++ b/docs/source/Async/Container/Notify/Server/index.html @@ -0,0 +1,46 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Notify::Server

+ +
+ +

Nested

+ + + +

Definitions

+ +
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Socket/index.html b/docs/source/Async/Container/Notify/Socket/index.html new file mode 100644 index 0000000..9ee3e94 --- /dev/null +++ b/docs/source/Async/Container/Notify/Socket/index.html @@ -0,0 +1,121 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Notify::Socket

+ +
+

Implements the systemd NOTIFY_SOCKET process readiness protocol. +See https://www.freedesktop.org/software/systemd/man/sd_notify.html for more details of the underlying protocol.

+ + +

Definitions

+ +

NOTIFY_SOCKET = 'NOTIFY_SOCKET'

The name of the environment variable which contains the path to the notification socket.

+

MAXIMUM_MESSAGE_SIZE = 4096

The maximum allowed size of the UDP message.

+

def self.open!(environment = ENV)

Open a notification client attached to the current NOTIFY_SOCKET = 'NOTIFY_SOCKET' if possible.

+
+

Implementation

+
def self.open!(environment = ENV)
+	if path = environment.delete(NOTIFY_SOCKET)
+		self.new(path)
+	end
+end
+

def initialize(path)

Initialize the notification client.

+
+

Signature

+
+ parameter path String

The path to the UNIX socket used for sending messages to the process manager.

+
+
+
+

Implementation

+
def initialize(path)
+	@path = path
+	@endpoint = IO::Endpoint.unix(path, ::Socket::SOCK_DGRAM)
+end
+

def dump(message)

Dump a message in the format requied by sd_notify.

+
+

Signature

+
+ parameter message Hash

Keys and values should be string convertible objects. Values which are true/false are converted to 1/0 respectively.

+
+
+
+

Implementation

+
def dump(message)
+	buffer = String.new
+	
+	message.each do |key, value|
+		# Conversions required by NOTIFY_SOCKET specifications:
+		if value == true
+			value = 1
+		elsif value == false
+			value = 0
+		end
+		
+		buffer << "#{key.to_s.upcase}=#{value}\n"
+	end
+	
+	return buffer
+end
+

def send(**message)

Send the given message.

+
+

Signature

+
+ parameter message Hash
+
+
+

Implementation

+
def send(**message)
+	data = dump(message)
+	
+	if data.bytesize > MAXIMUM_MESSAGE_SIZE
+		raise ArgumentError, "Message length #{message.bytesize} exceeds #{MAXIMUM_MESSAGE_SIZE}: #{message.inspect}"
+	end
+	
+	Sync do
+		@endpoint.connect do |peer|
+			peer.send(data)
+		end
+	end
+end
+

def error!(text, **message)

Send the specified error. +sd_notify requires an errno key, which defaults to -1 to indicate a generic error.

+
+

Implementation

+
def error!(text, **message)
+	message[:errno] ||= -1
+	
+	send(status: text, **message)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/index.html b/docs/source/Async/Container/Notify/index.html new file mode 100644 index 0000000..c5da848 --- /dev/null +++ b/docs/source/Async/Container/Notify/index.html @@ -0,0 +1,62 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Notify

+ +
+

Handles the details of several process readiness protocols.

+ +

Nested

+ + + +

Definitions

+ +

def self.open!

Select the best available notification client. +We cache the client on a per-process basis. Because that's the relevant scope for process readiness protocols.

+
+

Implementation

+
def self.open!
+	@client ||= (
+		Pipe.open! ||
+		Socket.open! ||
+		Console.open!
+	)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Process/Instance/index.html b/docs/source/Async/Container/Process/Instance/index.html new file mode 100644 index 0000000..76223a9 --- /dev/null +++ b/docs/source/Async/Container/Process/Instance/index.html @@ -0,0 +1,100 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Process::Instance

+ +
+

Represents a running child process from the point of view of the child process.

+ + +

Definitions

+ +

def self.for(process)

Wrap an instance around the class Async::Container::Process instance from within the forked child.

+
+

Signature

+
+ parameter process Process

The process intance to wrap.

+
+
+
+

Implementation

+
def self.for(process)
+	instance = self.new(process.out)
+	
+	# The child process won't be reading from the channel:
+	process.close_read
+	
+	instance.name = process.name
+	
+	return instance
+end
+

def name= value

Set the process title to the specified value.

+
+

Signature

+
+ parameter value String

The name of the process.

+
+
+
+

Implementation

+
def name= value
+	if @name = value
+		::Process.setproctitle(@name)
+	end
+end
+

def name

The name of the process.

+
+

Signature

+
+ returns String
+
+
+

Implementation

+
def name
+	@name
+end
+

def exec(*arguments, ready: true, **options)

Replace the current child process with a different one. Forwards arguments and options to ::Process.exec. +This method replaces the child process with the new executable, thus this method never returns.

+
+

Implementation

+
def exec(*arguments, ready: true, **options)
+	if ready
+		self.ready!(status: "(exec)") if ready
+	else
+		self.before_spawn(arguments, options)
+	end
+	
+	# TODO prefer **options... but it doesn't support redirections on < 2.7
+	::Process.exec(*arguments, options)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Process/index.html b/docs/source/Async/Container/Process/index.html new file mode 100644 index 0000000..8249eef --- /dev/null +++ b/docs/source/Async/Container/Process/index.html @@ -0,0 +1,172 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Process

+ +
+

Represents a running child process from the point of view of the parent container.

+ +

Nested

+ + + +

Definitions

+ +

def self.fork(**options)

Fork a child process appropriate for a container.

+
+

Signature

+
+ returns Process
+
+
+

Implementation

+
def self.fork(**options)
+	self.new(**options) do |process|
+		::Process.fork do
+			Signal.trap(:INT) {raise Interrupt}
+			Signal.trap(:TERM) {raise Terminate}
+			
+			begin
+				yield Instance.for(process)
+			rescue Interrupt
+				# Graceful exit.
+			rescue Exception => error
+				Async.logger.error(self) {error}
+				
+				exit!(1)
+			end
+		end
+	end
+end
+

def initialize(name: nil)

Initialize the process.

+
+

Signature

+
+ parameter name String

The name to use for the child process.

+
+
+
+

Implementation

+
def initialize(name: nil)
+	super()
+	
+	@name = name
+	@status = nil
+	@pid = nil
+	
+	@pid = yield(self)
+	
+	# The parent process won't be writing to the channel:
+	self.close_write
+end
+

def name= value

Set the name of the process. +Invokes ::Process.setproctitle if invoked in the child process.

+
+

Implementation

+
def name= value
+	@name = value
+	
+	# If we are the child process:
+	::Process.setproctitle(@name) if @pid.nil?
+end
+

attr :name

The name of the process.

+
+

Signature

+
+ attribute String
+
+

def to_s

A human readable representation of the process.

+
+

Signature

+
+ returns String
+
+
+

Implementation

+
def to_s
+	"\#<#{self.class} #{@name}>"
+end
+

def close

Invoke #terminate! and then #wait for the child process to exit.

+
+

Implementation

+
def close
+	self.terminate!
+	self.wait
+ensure
+	super
+end
+

def interrupt!

Send SIGINT to the child process.

+
+

Implementation

+
def interrupt!
+	unless @status
+		::Process.kill(:INT, @pid)
+	end
+end
+

def terminate!

Send SIGTERM to the child process.

+
+

Implementation

+
def terminate!
+	unless @status
+		::Process.kill(:TERM, @pid)
+	end
+end
+

def wait

Wait for the child process to exit.

+
+

Signature

+
+ returns ::Process::Status

The process exit status.

+
+
+
+

Implementation

+
def wait
+	if @pid && @status.nil?
+		_, @status = ::Process.wait2(@pid, ::Process::WNOHANG)
+		
+		if @status.nil?
+			sleep(0.01)
+			_, @status = ::Process.wait2(@pid, ::Process::WNOHANG)
+		end
+		
+		if @status.nil?
+			Async.logger.warn(self) {"Process #{@pid} is blocking, has it exited?"}
+			_, @status = ::Process.wait2(@pid)
+		end
+	end
+	
+	return @status
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/SetupError/index.html b/docs/source/Async/Container/SetupError/index.html new file mode 100644 index 0000000..e331ac6 --- /dev/null +++ b/docs/source/Async/Container/SetupError/index.html @@ -0,0 +1,43 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::SetupError

+ +
+

Represents the error which occured when a container failed to start up correctly.

+ + +

Definitions

+ +

attr :container

The container that failed.

+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Statistics/index.html b/docs/source/Async/Container/Statistics/index.html new file mode 100644 index 0000000..46cc3ea --- /dev/null +++ b/docs/source/Async/Container/Statistics/index.html @@ -0,0 +1,104 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Statistics

+ +
+

Tracks various statistics relating to child instances in a container.

+ + +

Definitions

+ +

attr :spawns

How many child instances have been spawned.

+
+

Signature

+
+ attribute Integer
+
+

attr :restarts

How many child instances have been restarted.

+
+

Signature

+
+ attribute Integer
+
+

attr :failures

How many child instances have failed.

+
+

Signature

+
+ attribute Integer
+
+

def spawn!

Increment the number of spawns by 1.

+
+

Implementation

+
def spawn!
+	@spawns += 1
+end
+

def restart!

Increment the number of restarts by 1.

+
+

Implementation

+
def restart!
+	@restarts += 1
+end
+

def failure!

Increment the number of failures by 1.

+
+

Implementation

+
def failure!
+	@failures += 1
+end
+

def failed?

Whether there have been any failures.

+
+

Signature

+
+ returns Boolean

If the failure count is greater than 0.

+
+
+
+

Implementation

+
def failed?
+	@failures > 0
+end
+

def << other

Append another statistics instance into this one.

+
+

Signature

+
+ parameter other Statistics

The statistics to append.

+
+
+
+

Implementation

+
def << other
+	@spawns += other.spawns
+	@restarts += other.restarts
+	@failures += other.failures
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Terminate/index.html b/docs/source/Async/Container/Terminate/index.html new file mode 100644 index 0000000..03ad481 --- /dev/null +++ b/docs/source/Async/Container/Terminate/index.html @@ -0,0 +1,42 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Terminate

+ +
+

Similar to Interrupt = ::Interrupt, but represents SIGTERM.

+ + +

Definitions

+ +
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Thread/Exit/index.html b/docs/source/Async/Container/Thread/Exit/index.html new file mode 100644 index 0000000..6ea7f74 --- /dev/null +++ b/docs/source/Async/Container/Thread/Exit/index.html @@ -0,0 +1,73 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Thread::Exit

+ +
+

Used to propagate the exit status of a child process invoked by Async::Container::Thread::Instance#exec.

+ + +

Definitions

+ +

def initialize(status)

Initialize the exit status.

+
+

Signature

+
+ parameter status ::Process::Status

The process exit status.

+
+
+
+

Implementation

+
def initialize(status)
+	@status = status
+end
+

attr :status

The process exit status.

+
+

Signature

+
+ attribute ::Process::Status
+
+

def error

The process exit status if it was an error.

+
+

Signature

+
+ returns ::Process::Status | Nil
+
+
+

Implementation

+
def error
+	unless status.success?
+		status
+	end
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Thread/Instance/index.html b/docs/source/Async/Container/Thread/Instance/index.html new file mode 100644 index 0000000..9fa0931 --- /dev/null +++ b/docs/source/Async/Container/Thread/Instance/index.html @@ -0,0 +1,99 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Thread::Instance

+ +
+

Represents a running child thread from the point of view of the child thread.

+ + +

Definitions

+ +

def self.for(thread)

Wrap an instance around the class Async::Container::Thread instance from within the threaded child.

+
+

Signature

+
+ parameter thread Thread

The thread intance to wrap.

+
+
+
+

Implementation

+
def self.for(thread)
+	instance = self.new(thread.out)
+	
+	return instance
+end
+

def name= value

Set the name of the thread.

+
+

Signature

+
+ parameter value String

The name to set.

+
+
+
+

Implementation

+
def name= value
+	@thread.name = value
+end
+

def name

Get the name of the thread.

+
+

Signature

+
+ returns String
+
+
+

Implementation

+
def name
+	@thread.name
+end
+

def exec(*arguments, ready: true, **options)

Execute a child process using ::Process.spawn. In order to simulate ::Process.exec, an class Async::Container::Thread::Exit instance is raised to propagage exit status. +This creates the illusion that this method does not return (normally).

+
+

Implementation

+
def exec(*arguments, ready: true, **options)
+	if ready
+		self.ready!(status: "(spawn)") if ready
+	else
+		self.before_spawn(arguments, options)
+	end
+	
+	begin
+		# TODO prefer **options... but it doesn't support redirections on < 2.7
+		pid = ::Process.spawn(*arguments, options)
+	ensure
+		_, status = ::Process.wait2(pid)
+		
+		raise Exit, status
+	end
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Thread/Status/index.html b/docs/source/Async/Container/Thread/Status/index.html new file mode 100644 index 0000000..e40e233 --- /dev/null +++ b/docs/source/Async/Container/Thread/Status/index.html @@ -0,0 +1,71 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Thread::Status

+ +
+

A pseudo exit-status wrapper.

+ + +

Definitions

+ +

def initialize(error = nil)

Initialise the status.

+
+

Signature

+
+ parameter error ::Process::Status

The exit status of the child thread.

+
+
+
+

Implementation

+
def initialize(error = nil)
+	@error = error
+end
+

def success?

Whether the status represents a successful outcome.

+
+

Signature

+
+ returns Boolean
+
+
+

Implementation

+
def success?
+	@error.nil?
+end
+

def to_s

A human readable representation of the status.

+
+

Implementation

+
def to_s
+	"\#<#{self.class} #{success? ? "success" : "failure"}>"
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Thread/index.html b/docs/source/Async/Container/Thread/index.html new file mode 100644 index 0000000..ebf415e --- /dev/null +++ b/docs/source/Async/Container/Thread/index.html @@ -0,0 +1,164 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Thread

+ +
+

Represents a running child thread from the point of view of the parent container.

+ +

Nested

+ + + +

Definitions

+ +

def initialize(name: nil)

Initialize the thread.

+
+

Signature

+
+ parameter name String

The name to use for the child thread.

+
+
+
+

Implementation

+
def initialize(name: nil)
+	super()
+	
+	@status = nil
+	
+	@thread = yield(self)
+	@thread.report_on_exception = false
+	@thread.name = name
+	
+	@waiter = ::Thread.new do
+		begin
+			@thread.join
+		rescue Exit => exit
+			finished(exit.error)
+		rescue Interrupt
+			# Graceful shutdown.
+			finished
+		rescue Exception => error
+			finished(error)
+		else
+			finished
+		end
+	end
+end
+

def name= value

Set the name of the thread.

+
+

Signature

+
+ parameter value String

The name to set.

+
+
+
+

Implementation

+
def name= value
+	@thread.name = value
+end
+

def name

Get the name of the thread.

+
+

Signature

+
+ returns String
+
+
+

Implementation

+
def name
+	@thread.name
+end
+

def to_s

A human readable representation of the thread.

+
+

Signature

+
+ returns String
+
+
+

Implementation

+
def to_s
+	"\#<#{self.class} #{@thread.name}>"
+end
+

def close

Invoke #terminate! and then #wait for the child thread to exit.

+
+

Implementation

+
def close
+	self.terminate!
+	self.wait
+ensure
+	super
+end
+

def interrupt!

Raise Interrupt = ::Interrupt in the child thread.

+
+

Implementation

+
def interrupt!
+	@thread.raise(Interrupt)
+end
+

def terminate!

Raise class Async::Container::Terminate in the child thread.

+
+

Implementation

+
def terminate!
+	@thread.raise(Terminate)
+end
+

def wait

Wait for the thread to exit and return he exit status.

+
+

Signature

+
+ returns Status
+
+
+

Implementation

+
def wait
+	if @waiter
+		@waiter.join
+		@waiter = nil
+	end
+	
+	return @status
+end
+

def finished(error = nil)

Invoked by the @waiter thread to indicate the outcome of the child thread.

+
+

Implementation

+
def finished(error = nil)
+	if error
+		Async.logger.error(self) {error}
+	end
+	
+	@status = Status.new(error)
+	self.close_write
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/Threaded/index.html b/docs/source/Async/Container/Threaded/index.html new file mode 100644 index 0000000..bc8fbef --- /dev/null +++ b/docs/source/Async/Container/Threaded/index.html @@ -0,0 +1,62 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container::Threaded

+ +
+

A multi-thread container which uses Async::Container::Thread.fork.

+ + +

Definitions

+ +

def self.multiprocess?

Indicates that this is not a multi-process container.

+
+

Implementation

+
def self.multiprocess?
+	false
+end
+

def start(name, &block)

Start a named child thread and execute the provided block in it.

+
+

Signature

+
+ parameter name String

The name (title) of the child process.

+
+ parameter block Proc

The block to execute in the child process.

+
+
+
+

Implementation

+
def start(name, &block)
+	Thread.fork(name: name, &block)
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/Container/index.html b/docs/source/Async/Container/index.html new file mode 100644 index 0000000..d09397c --- /dev/null +++ b/docs/source/Async/Container/index.html @@ -0,0 +1,122 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async::Container

+ +
+ +

Nested

+ + + +

Definitions

+ +

def self.fork?

Whether the underlying process supports fork.

+
+

Signature

+
+ returns Boolean
+
+
+

Implementation

+
def self.fork?
+	::Process.respond_to?(:fork) && ::Process.respond_to?(:setpgid)
+end
+

def self.best_container_class

Determins the best container class based on the underlying Ruby implementation. +Some platforms, including JRuby, don't support fork. Applications which just want a reasonable default can use this method.

+
+

Signature

+
+ returns Class
+
+
+

Implementation

+
def self.best_container_class
+	if fork?
+		return Forked
+	else
+		return Threaded
+	end
+end
+

def self.new(*arguments, **options)

Create an instance of the best container class.

+
+

Signature

+
+ returns Generic

Typically an instance of either class Async::Container::Forked or class Async::Container::Threaded containers.

+
+
+
+

Implementation

+
def self.new(*arguments, **options)
+	best_container_class.new(*arguments, **options)
+end
+

ASYNC_CONTAINER_PROCESSOR_COUNT = 'ASYNC_CONTAINER_PROCESSOR_COUNT'

An environment variable key to override .processor_count.

+

def self.processor_count(env = ENV)

The processor count which may be used for the default number of container threads/processes. You can override the value provided by the system by specifying the ASYNC_CONTAINER_PROCESSOR_COUNT environment variable.

+
+

Signature

+
+ returns Integer

The number of hardware processors which can run threads/processes simultaneously.

+
+ raises RuntimeError

If the process count is invalid.

+
+
+
+

Implementation

+
def self.processor_count(env = ENV)
+	count = env.fetch(ASYNC_CONTAINER_PROCESSOR_COUNT) do
+		Etc.nprocessors rescue 1
+	end.to_i
+	
+	if count < 1
+		raise RuntimeError, "Invalid processor count #{count}!"
+	end
+	
+	return count
+end
+
+ + + + + \ No newline at end of file diff --git a/docs/source/Async/index.html b/docs/source/Async/index.html new file mode 100644 index 0000000..0fd672a --- /dev/null +++ b/docs/source/Async/index.html @@ -0,0 +1,46 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Async

+ +
+ +

Nested

+ + + +

Definitions

+ +
+ + + + + \ No newline at end of file diff --git a/docs/source/index.html b/docs/source/index.html new file mode 100644 index 0000000..e98f4c4 --- /dev/null +++ b/docs/source/index.html @@ -0,0 +1,963 @@ + + + + + Codestin Search App + + + + + + + + + + + + + + + + + +

Source

+ +
+
+ + + + + \ No newline at end of file diff --git a/guides/getting-started/README.md b/guides/getting-started/README.md new file mode 100644 index 0000000..dee127a --- /dev/null +++ b/guides/getting-started/README.md @@ -0,0 +1,101 @@ +# Getting Started + +This guide explains how to use `async-container` to build basic scalable systems. + +## Installation + +Add the gem to your project: + +~~~ bash +$ bundle add async-container +~~~ + +## Core Concepts + +`async-container` has several core concepts: + +- {ruby Async::Container::Forked} and {ruby Async::Container::Threaded} are used to manage one or more child processes and threads respectively for parallel execution. While threads share the address space which can reduce overall memory usage, processes have better isolation and fault tolerance. +- {ruby Async::Container::Controller} manages one or more containers and handles graceful restarts. Containers should be implemented in such a way that multiple containers can be running at the same time. + +## Containers + +A container represents a set of child processes (or threads) which are doing work for you. + +``` ruby +require 'async/container' + +Async.logger.debug! + +container = Async::Container.new + +container.async do |task| + task.logger.debug "Sleeping..." + task.sleep(1) + task.logger.debug "Waking up!" +end + +Async.logger.debug "Waiting for container..." +container.wait +Async.logger.debug "Finished." +``` + +## Controllers + +The controller provides the life-cycle management for one or more containers of processes. It provides behaviour like starting, restarting, reloading and stopping. You can see some [example implementations in Falcon](https://github.com/socketry/falcon/blob/master/lib/falcon/controller/). If the process running the controller receives `SIGHUP` it will recreate the container gracefully. + +``` ruby +require 'async/container' + +Async.logger.debug! + +class Controller < Async::Container::Controller + def setup(container) + container.async do |task| + while true + Async.logger.debug("Sleeping...") + task.sleep(1) + end + end + end +end + +controller = Controller.new + +controller.run + +# If you send SIGHUP to this process, it will recreate the container. +``` + +## Signal Handling + +`SIGINT` is the interrupt signal. The terminal sends it to the foreground process when the user presses **ctrl-c**. The default behavior is to terminate the process, but it can be caught or ignored. The intention is to provide a mechanism for an orderly, graceful shutdown. + +`SIGQUIT` is the dump core signal. The terminal sends it to the foreground process when the user presses **ctrl-\\**. The default behavior is to terminate the process and dump core, but it can be caught or ignored. The intention is to provide a mechanism for the user to abort the process. You can look at `SIGINT` as "user-initiated happy termination" and `SIGQUIT` as "user-initiated unhappy termination." + +`SIGTERM` is the termination signal. The default behavior is to terminate the process, but it also can be caught or ignored. The intention is to kill the process, gracefully or not, but to first allow it a chance to cleanup. + +`SIGKILL` is the kill signal. The only behavior is to kill the process, immediately. As the process cannot catch the signal, it cannot cleanup, and thus this is a signal of last resort. + +`SIGSTOP` is the pause signal. The only behavior is to pause the process; the signal cannot be caught or ignored. The shell uses pausing (and its counterpart, resuming via `SIGCONT`) to implement job control. + +## Integration + +### systemd + +Install a template file into `/etc/systemd/system/`: + +``` +# my-daemon.service +[Unit] +Description=My Daemon +AssertPathExists=/srv/ + +[Service] +Type=notify +WorkingDirectory=/srv/my-daemon +ExecStart=bundle exec my-daemon +Nice=5 + +[Install] +WantedBy=multi-user.target +``` From 2a550926067dadfb443e3870cce00cfb4d4b1f8b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 9 Jul 2020 17:44:52 +1200 Subject: [PATCH 051/166] Update `.gitignore`. --- .gitignore | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 443d421..3468ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,12 @@ -*.gem -*.rbc -.bundle -.config -.yardoc -/gems.locked -InstalledFiles -_yardoc -coverage -doc/ -lib/bundler/man -pkg -rdoc -spec/reports -test/tmp -test/version_tmp -tmp -.tags* -documentation/run/* -documentation/public/code/* +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking .rspec_status +gems.locked From 643d889af4b027a0189b8fb11579b8ade467bd58 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 9 Jul 2020 17:45:05 +1200 Subject: [PATCH 052/166] Patch version bump. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 6ef9ff4..ac62fdd 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.6" + VERSION = "0.16.7" end end From 24e7c6b1d0f3b6c9b62ed35b6c58b38a1e7da1bd Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 10 Jul 2020 23:12:39 +1200 Subject: [PATCH 053/166] Test against `truffleruby-head`. --- .github/workflows/development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 9da541b..be90aaf 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -23,7 +23,7 @@ jobs: include: - os: ubuntu - ruby: truffleruby + ruby: truffleruby-head experimental: true - os: ubuntu ruby: jruby From 06078f5e643cbb3994a6bdddbba8ce85cdc361b6 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 19 Jul 2020 01:45:07 +1200 Subject: [PATCH 054/166] Remove redundant gems. --- async-container.gemspec | 2 -- 1 file changed, 2 deletions(-) diff --git a/async-container.gemspec b/async-container.gemspec index d0337ed..43581aa 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -19,8 +19,6 @@ Gem::Specification.new do |spec| spec.add_dependency "async-io", "~> 1.26" spec.add_development_dependency "async-rspec", "~> 1.1" - spec.add_development_dependency "bake-bundler" - spec.add_development_dependency "bake-modernize" spec.add_development_dependency "bundler" spec.add_development_dependency "covered" spec.add_development_dependency "rspec", "~> 3.6" From 74cc92b5fc64019b6e6b0ab547caf8f980d760ec Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 19 Jul 2020 01:45:30 +1200 Subject: [PATCH 055/166] Explicitly handle HUP. --- lib/async/container/controller.rb | 5 +++++ lib/async/container/error.rb | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 6a94341..119c6e1 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -201,6 +201,10 @@ def run raise Terminate end + hangup_action = Signal.trap(:HUP) do + raise Hangup + end + self.start while @container&.running? @@ -228,6 +232,7 @@ def run # Restore the interrupt handler: Signal.trap(:INT, interrupt_action) Signal.trap(:TERM, terminate_action) + Signal.trap(:HUP, hangup_action) end end end diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index 2c3b7ff..3558a74 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -36,6 +36,14 @@ def initialize end end + class Hangup < SignalException + SIGHUP = Signal.list['HUP'] + + def initialize + super(SIGHUP) + end + end + # Represents the error which occured when a container failed to start up correctly. class SetupError < Error def initialize(container) From 32db33c29a40cebab404e0bc97bdb65c04e5115a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 19 Sep 2020 11:58:47 +1200 Subject: [PATCH 056/166] Increase number of jobs. --- examples/queue/server.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/queue/server.rb b/examples/queue/server.rb index 09bc551..438a82f 100644 --- a/examples/queue/server.rb +++ b/examples/queue/server.rb @@ -35,7 +35,7 @@ def initialize container.spawn do |instance| Async do - queue = 8.times.to_a + queue = 500_000.times.to_a Console.logger.info(self) {"Hosting the queue..."} instance.ready! @@ -59,7 +59,7 @@ def initialize break end when :status - Console.logger.info("Job Status") {arguments} + # Console.logger.info("Job Status") {arguments} else Console.logger.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"} end @@ -84,7 +84,7 @@ def initialize case command when :job - task.sleep(*arguments) + # task.sleep(*arguments) packer.write([:status, *arguments]) packer.write([:ready]) packer.flush @@ -97,3 +97,5 @@ def initialize end container.wait + +Console.logger.info(self) {"Done!"} From 343e6c6f9321de57e9d4c18f2c12950dc7a7d232 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 19 Sep 2020 12:00:07 +1200 Subject: [PATCH 057/166] Allow to run on Ruby 3. --- async-container.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/async-container.gemspec b/async-container.gemspec index 43581aa..97f5c42 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |spec| spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) - spec.required_ruby_version = "~> 2.5" + spec.required_ruby_version = ">= 2.5" spec.add_dependency "async", "~> 1.0" spec.add_dependency "async-io", "~> 1.26" From c8de3459fc19eccec3085cc58fb5f04204cd2004 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 19 Sep 2020 12:53:09 +1200 Subject: [PATCH 058/166] Patch version bump. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index ac62fdd..0421618 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.7" + VERSION = "0.16.8" end end From d06363fa302b740a38fd1d4045c8599d168428c3 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 May 2021 02:19:43 +1200 Subject: [PATCH 059/166] Refactor `Async.logger` -> `Console.logger`. --- examples/container.rb | 12 ++++++------ guides/getting-started/README.md | 10 +++++----- lib/async/container/controller.rb | 18 +++++++++--------- lib/async/container/generic.rb | 12 ++++++------ lib/async/container/group.rb | 4 ++-- lib/async/container/notify/pipe.rb | 2 +- lib/async/container/process.rb | 4 ++-- lib/async/container/thread.rb | 4 +--- 8 files changed, 32 insertions(+), 34 deletions(-) diff --git a/examples/container.rb b/examples/container.rb index c8a4b23..5a6e632 100755 --- a/examples/container.rb +++ b/examples/container.rb @@ -4,15 +4,15 @@ require '../lib/async/container/controller' require '../lib/async/container/forked' -Async.logger.debug! +Console.logger.debug! -Async.logger.debug(self, "Starting up...") +Console.logger.debug(self, "Starting up...") controller = Async::Container::Controller.new do |container| - Async.logger.debug(self, "Setting up container...") + Console.logger.debug(self, "Setting up container...") container.run(count: 1, restart: true) do - Async.logger.debug(self, "Child process started.") + Console.logger.debug(self, "Child process started.") while true sleep 1 @@ -22,12 +22,12 @@ end end ensure - Async.logger.debug(self, "Child process exiting:", $!) + Console.logger.debug(self, "Child process exiting:", $!) end end begin controller.run ensure - Async.logger.debug(controller, "Parent process exiting:", $!) + Console.logger.debug(controller, "Parent process exiting:", $!) end diff --git a/guides/getting-started/README.md b/guides/getting-started/README.md index dee127a..a6641f9 100644 --- a/guides/getting-started/README.md +++ b/guides/getting-started/README.md @@ -24,7 +24,7 @@ A container represents a set of child processes (or threads) which are doing wor ``` ruby require 'async/container' -Async.logger.debug! +Console.logger.debug! container = Async::Container.new @@ -34,9 +34,9 @@ container.async do |task| task.logger.debug "Waking up!" end -Async.logger.debug "Waiting for container..." +Console.logger.debug "Waiting for container..." container.wait -Async.logger.debug "Finished." +Console.logger.debug "Finished." ``` ## Controllers @@ -46,13 +46,13 @@ The controller provides the life-cycle management for one or more containers of ``` ruby require 'async/container' -Async.logger.debug! +Console.logger.debug! class Controller < Async::Container::Controller def setup(container) container.async do |task| while true - Async.logger.debug("Sleeping...") + Console.logger.debug("Sleeping...") task.sleep(1) end end diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 119c6e1..507ebc3 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -122,9 +122,9 @@ def restart if @container @notify&.restarting! - Async.logger.debug(self) {"Restarting container..."} + Console.logger.debug(self) {"Restarting container..."} else - Async.logger.debug(self) {"Starting container..."} + Console.logger.debug(self) {"Starting container..."} end container = self.create_container @@ -138,9 +138,9 @@ def restart end # Wait for all child processes to enter the ready state. - Async.logger.debug(self, "Waiting for startup...") + Console.logger.debug(self, "Waiting for startup...") container.wait_until_ready - Async.logger.debug(self, "Finished startup.") + Console.logger.debug(self, "Finished startup.") if container.failed? @notify&.error!($!.to_s) @@ -154,7 +154,7 @@ def restart old_container = @container @container = container - Async.logger.debug(self, "Stopping old container...") + Console.logger.debug(self, "Stopping old container...") old_container&.stop @notify&.ready! rescue @@ -168,7 +168,7 @@ def restart def reload @notify&.reloading! - Async.logger.info(self) {"Reloading container: #{@container}..."} + Console.logger.info(self) {"Reloading container: #{@container}..."} begin self.setup(@container) @@ -177,9 +177,9 @@ def reload end # Wait for all child processes to enter the ready state. - Async.logger.debug(self, "Waiting for startup...") + Console.logger.debug(self, "Waiting for startup...") @container.wait_until_ready - Async.logger.debug(self, "Finished startup.") + Console.logger.debug(self, "Finished startup.") if @container.failed? @notify.error!("Container failed!") @@ -215,7 +215,7 @@ def run begin handler.call rescue SetupError => error - Async.logger.error(self) {error} + Console.logger.error(self) {error} end else raise diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 545f597..03e8bba 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -119,7 +119,7 @@ def status?(flag) # @returns [Boolean] The children all became ready. def wait_until_ready while true - Async.logger.debug(self) do |buffer| + Console.logger.debug(self) do |buffer| buffer.puts "Waiting for ready:" @state.each do |child, state| buffer.puts "\t#{child.class}: #{state.inspect}" @@ -141,7 +141,7 @@ def stop(timeout = true) @group.stop(timeout) if @group.running? - Async.logger.warn(self) {"Group is still running after stopping it!"} + Console.logger.warn(self) {"Group is still running after stopping it!"} end ensure @running = true @@ -155,7 +155,7 @@ def spawn(name: nil, restart: false, key: nil, &block) name ||= UNNAMED if mark?(key) - Async.logger.debug(self) {"Reusing existing child for #{key}: #{name}"} + Console.logger.debug(self) {"Reusing existing child for #{key}: #{name}"} return false end @@ -176,10 +176,10 @@ def spawn(name: nil, restart: false, key: nil, &block) end if status.success? - Async.logger.info(self) {"#{child} exited with #{status}"} + Console.logger.info(self) {"#{child} exited with #{status}"} else @statistics.failure! - Async.logger.error(self) {status} + Console.logger.error(self) {status} end if restart @@ -189,7 +189,7 @@ def spawn(name: nil, restart: false, key: nil, &block) end end # ensure - # Async.logger.error(self) {$!} if $! + # Console.logger.error(self) {$!} if $! end.resume return true diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 92184d2..9477afd 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -78,7 +78,7 @@ def wait # Interrupt all running processes. # This resumes the controlling fiber with an instance of {Interrupt}. def interrupt - Async.logger.debug(self, "Sending interrupt to #{@running.size} running processes...") + Console.logger.debug(self, "Sending interrupt to #{@running.size} running processes...") @running.each_value do |fiber| fiber.resume(Interrupt) end @@ -87,7 +87,7 @@ def interrupt # Terminate all running processes. # This resumes the controlling fiber with an instance of {Terminate}. def terminate - Async.logger.debug(self, "Sending terminate to #{@running.size} running processes...") + Console.logger.debug(self, "Sending terminate to #{@running.size} running processes...") @running.each_value do |fiber| fiber.resume(Terminate) end diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index 92ad125..d9571fc 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -38,7 +38,7 @@ def self.open!(environment = ENV) self.new(::IO.for_fd(descriptor.to_i)) end rescue Errno::EBADF => error - Async.logger.error(self) {error} + Console.logger.error(self) {error} return nil end diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 7ce0db6..d7b5e9a 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -91,7 +91,7 @@ def self.fork(**options) rescue Interrupt # Graceful exit. rescue Exception => error - Async.logger.error(self) {error} + Console.logger.error(self) {error} exit!(1) end @@ -177,7 +177,7 @@ def wait end if @status.nil? - Async.logger.warn(self) {"Process #{@pid} is blocking, has it exited?"} + Console.logger.warn(self) {"Process #{@pid} is blocking, has it exited?"} _, @status = ::Process.wait2(@pid) end end diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index 9bf50e4..5dad31b 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -24,8 +24,6 @@ require_relative 'error' require_relative 'notify/pipe' -require 'async/logger' - module Async module Container # Represents a running child thread from the point of view of the parent container. @@ -207,7 +205,7 @@ def to_s # Invoked by the @waiter thread to indicate the outcome of the child thread. def finished(error = nil) if error - Async.logger.error(self) {error} + Console.logger.error(self) {error} end @status = Status.new(error) From f7b265597fe2b742c4c05a10befdc121b8d6cdaf Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 May 2021 02:20:25 +1200 Subject: [PATCH 060/166] Modernize gem. --- .github/workflows/development.yml | 7 +- .github/workflows/documentation.yml | 32 + README.md | 10 +- docs/.nojekyll | 0 .../jquery-litebox/jquery.litebox.css | 23 - .../jquery-litebox/jquery.litebox.gallery.css | 48 - .../jquery-litebox/jquery.litebox.js | 30 - .../base/jquery.syntax.brush.apache.css | 12 - .../base/jquery.syntax.brush.applescript.css | 5 - .../base/jquery.syntax.brush.assembly.css | 8 - .../base/jquery.syntax.brush.bash-script.css | 4 - .../base/jquery.syntax.brush.bash.css | 2 - .../base/jquery.syntax.brush.clang.css | 6 - .../base/jquery.syntax.brush.css.css | 14 - .../base/jquery.syntax.brush.diff.css | 16 - .../base/jquery.syntax.brush.html.css | 5 - .../base/jquery.syntax.brush.ocaml.css | 3 - .../base/jquery.syntax.brush.protobuf.css | 2 - .../base/jquery.syntax.brush.python.css | 6 - .../base/jquery.syntax.brush.ruby.css | 2 - .../base/jquery.syntax.brush.xml.css | 18 - .../jquery-syntax/base/jquery.syntax.core.css | 58 - .../base/jquery.syntax.editor.css | 6 - docs/_components/jquery-syntax/base/theme.js | 1 - .../bright/jquery.syntax.core.css | 27 - .../_components/jquery-syntax/bright/theme.js | 1 - .../jquery.syntax.brush.apache.js | 3 - .../jquery.syntax.brush.applescript.js | 5 - .../jquery.syntax.brush.assembly.js | 3 - .../jquery.syntax.brush.bash-script.js | 4 - .../jquery-syntax/jquery.syntax.brush.bash.js | 2 - .../jquery.syntax.brush.basic.js | 5 - .../jquery.syntax.brush.clang.js | 5 - .../jquery.syntax.brush.csharp.js | 4 - .../jquery-syntax/jquery.syntax.brush.css.js | 5 - .../jquery-syntax/jquery.syntax.brush.diff.js | 2 - .../jquery-syntax/jquery.syntax.brush.go.js | 3 - .../jquery.syntax.brush.haskell.js | 3 - .../jquery-syntax/jquery.syntax.brush.html.js | 4 - .../jquery-syntax/jquery.syntax.brush.io.js | 3 - .../jquery-syntax/jquery.syntax.brush.java.js | 4 - .../jquery.syntax.brush.javascript.js | 3 - .../jquery-syntax/jquery.syntax.brush.kai.js | 2 - .../jquery-syntax/jquery.syntax.brush.lisp.js | 2 - .../jquery-syntax/jquery.syntax.brush.lua.js | 3 - .../jquery.syntax.brush.nginx.js | 2 - .../jquery.syntax.brush.ocaml.js | 4 - .../jquery-syntax/jquery.syntax.brush.ooc.js | 4 - .../jquery.syntax.brush.pascal.js | 4 - .../jquery.syntax.brush.perl5.js | 3 - .../jquery.syntax.brush.php-script.js | 4 - .../jquery-syntax/jquery.syntax.brush.php.js | 2 - .../jquery.syntax.brush.plain.js | 2 - .../jquery.syntax.brush.protobuf.js | 3 - .../jquery.syntax.brush.python.js | 5 - .../jquery-syntax/jquery.syntax.brush.ruby.js | 5 - .../jquery.syntax.brush.scala.js | 4 - .../jquery.syntax.brush.smalltalk.js | 2 - .../jquery-syntax/jquery.syntax.brush.sql.js | 4 - .../jquery.syntax.brush.super-collider.js | 3 - .../jquery.syntax.brush.swift.js | 3 - .../jquery.syntax.brush.trenni.js | 2 - .../jquery-syntax/jquery.syntax.brush.xml.js | 4 - .../jquery-syntax/jquery.syntax.brush.yaml.js | 2 - .../jquery-syntax/jquery.syntax.cache.js | 7 - .../jquery-syntax/jquery.syntax.core.js | 34 - .../jquery-syntax/jquery.syntax.editor.js | 11 - .../jquery-syntax/jquery.syntax.js | 8 - .../jquery-syntax/jquery.syntax.min.js | 13 - .../paper/jquery.syntax.core.css | 31 - docs/_components/jquery-syntax/paper/theme.js | 1 - docs/_components/jquery/jquery.js | 10872 ---------------- docs/_components/jquery/jquery.min.js | 2 - docs/_components/jquery/jquery.min.map | 1 - docs/_components/jquery/jquery.slim.js | 8777 ------------- docs/_components/jquery/jquery.slim.min.js | 2 - docs/_components/jquery/jquery.slim.min.map | 1 - docs/_static/icon.png | Bin 516 -> 0 bytes docs/_static/site.css | 216 - docs/guides/getting-started/index.html | 111 - docs/guides/index.html | 39 - docs/index.html | 77 - .../source/Async/Container/Channel/index.html | 97 - .../Async/Container/Controller/index.html | 277 - docs/source/Async/Container/Error/index.html | 41 - docs/source/Async/Container/Forked/index.html | 62 - .../source/Async/Container/Generic/index.html | 306 - docs/source/Async/Container/Hybrid/index.html | 82 - docs/source/Async/Container/Keyed/index.html | 87 - .../Async/Container/Notify/Client/index.html | 87 - .../Async/Container/Notify/Console/index.html | 72 - .../Async/Container/Notify/Pipe/index.html | 105 - .../Notify/Server/Context/index.html | 41 - .../Async/Container/Notify/Server/index.html | 46 - .../Async/Container/Notify/Socket/index.html | 121 - docs/source/Async/Container/Notify/index.html | 62 - .../Container/Process/Instance/index.html | 100 - .../source/Async/Container/Process/index.html | 172 - .../Async/Container/SetupError/index.html | 43 - .../Async/Container/Statistics/index.html | 104 - .../Async/Container/Terminate/index.html | 42 - .../Async/Container/Thread/Exit/index.html | 73 - .../Container/Thread/Instance/index.html | 99 - .../Async/Container/Thread/Status/index.html | 71 - docs/source/Async/Container/Thread/index.html | 164 - .../Async/Container/Threaded/index.html | 62 - docs/source/Async/Container/index.html | 122 - docs/source/Async/index.html | 46 - docs/source/index.html | 963 -- 109 files changed, 40 insertions(+), 24183 deletions(-) create mode 100644 .github/workflows/documentation.yml delete mode 100644 docs/.nojekyll delete mode 100644 docs/_components/jquery-litebox/jquery.litebox.css delete mode 100644 docs/_components/jquery-litebox/jquery.litebox.gallery.css delete mode 100644 docs/_components/jquery-litebox/jquery.litebox.js delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.apache.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.applescript.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.assembly.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.bash-script.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.bash.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.clang.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.css.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.diff.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.html.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.ocaml.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.protobuf.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.python.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.ruby.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.brush.xml.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.core.css delete mode 100644 docs/_components/jquery-syntax/base/jquery.syntax.editor.css delete mode 100644 docs/_components/jquery-syntax/base/theme.js delete mode 100644 docs/_components/jquery-syntax/bright/jquery.syntax.core.css delete mode 100644 docs/_components/jquery-syntax/bright/theme.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.apache.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.applescript.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.assembly.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.bash-script.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.bash.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.basic.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.clang.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.csharp.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.css.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.diff.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.go.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.haskell.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.html.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.io.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.java.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.javascript.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.kai.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.lisp.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.lua.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.nginx.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.ocaml.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.ooc.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.pascal.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.perl5.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.php-script.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.php.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.plain.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.protobuf.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.python.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.ruby.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.scala.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.smalltalk.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.sql.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.super-collider.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.swift.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.trenni.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.xml.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.brush.yaml.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.cache.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.core.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.editor.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.js delete mode 100644 docs/_components/jquery-syntax/jquery.syntax.min.js delete mode 100644 docs/_components/jquery-syntax/paper/jquery.syntax.core.css delete mode 100644 docs/_components/jquery-syntax/paper/theme.js delete mode 100644 docs/_components/jquery/jquery.js delete mode 100644 docs/_components/jquery/jquery.min.js delete mode 100644 docs/_components/jquery/jquery.min.map delete mode 100644 docs/_components/jquery/jquery.slim.js delete mode 100644 docs/_components/jquery/jquery.slim.min.js delete mode 100644 docs/_components/jquery/jquery.slim.min.map delete mode 100644 docs/_static/icon.png delete mode 100644 docs/_static/site.css delete mode 100644 docs/guides/getting-started/index.html delete mode 100644 docs/guides/index.html delete mode 100644 docs/index.html delete mode 100644 docs/source/Async/Container/Channel/index.html delete mode 100644 docs/source/Async/Container/Controller/index.html delete mode 100644 docs/source/Async/Container/Error/index.html delete mode 100644 docs/source/Async/Container/Forked/index.html delete mode 100644 docs/source/Async/Container/Generic/index.html delete mode 100644 docs/source/Async/Container/Hybrid/index.html delete mode 100644 docs/source/Async/Container/Keyed/index.html delete mode 100644 docs/source/Async/Container/Notify/Client/index.html delete mode 100644 docs/source/Async/Container/Notify/Console/index.html delete mode 100644 docs/source/Async/Container/Notify/Pipe/index.html delete mode 100644 docs/source/Async/Container/Notify/Server/Context/index.html delete mode 100644 docs/source/Async/Container/Notify/Server/index.html delete mode 100644 docs/source/Async/Container/Notify/Socket/index.html delete mode 100644 docs/source/Async/Container/Notify/index.html delete mode 100644 docs/source/Async/Container/Process/Instance/index.html delete mode 100644 docs/source/Async/Container/Process/index.html delete mode 100644 docs/source/Async/Container/SetupError/index.html delete mode 100644 docs/source/Async/Container/Statistics/index.html delete mode 100644 docs/source/Async/Container/Terminate/index.html delete mode 100644 docs/source/Async/Container/Thread/Exit/index.html delete mode 100644 docs/source/Async/Container/Thread/Instance/index.html delete mode 100644 docs/source/Async/Container/Thread/Status/index.html delete mode 100644 docs/source/Async/Container/Thread/index.html delete mode 100644 docs/source/Async/Container/Threaded/index.html delete mode 100644 docs/source/Async/Container/index.html delete mode 100644 docs/source/Async/index.html delete mode 100644 docs/source/index.html diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index be90aaf..3cf1d52 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -17,13 +17,14 @@ jobs: - 2.5 - 2.6 - 2.7 + - 3.0 experimental: [false] env: [""] include: - os: ubuntu - ruby: truffleruby-head + ruby: truffleruby experimental: true - os: ubuntu ruby: jruby @@ -37,9 +38,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} - - - name: Install dependencies - run: ${{matrix.env}} bundle install + bundler-cache: true - name: Run tests timeout-minutes: 5 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..4a4a663 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,32 @@ +name: Documentation + +on: + push: + branches: + - master + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + env: + BUNDLE_WITH: maintenance + with: + ruby-version: 2.7 + bundler-cache: true + + - name: Installing packages + run: sudo apt-get install wget + + - name: Generate documentation + timeout-minutes: 5 + run: bundle exec bake utopia:project:static + + - name: Deploy documentation + uses: JamesIves/github-pages-deploy-action@4.0.0 + with: + branch: docs + folder: docs diff --git a/README.md b/README.md index 9418254..d9a30da 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Provides containers which implement parallelism for clients and servers. ## Features -- Supports multi-process, multi-thread and hybrid containers. -- Automatic scalability based on physical hardware. -- Direct integration with [systemd](https://www.freedesktop.org/software/systemd/man/sd_notify.html) using `$NOTIFY_SOCKET`. -- Internal process readiness protocol for handling state changes. -- Automatic restart of failed processes. + - Supports multi-process, multi-thread and hybrid containers. + - Automatic scalability based on physical hardware. + - Direct integration with [systemd](https://www.freedesktop.org/software/systemd/man/sd_notify.html) using `$NOTIFY_SOCKET`. + - Internal process readiness protocol for handling state changes. + - Automatic restart of failed processes. ## Usage diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/docs/_components/jquery-litebox/jquery.litebox.css b/docs/_components/jquery-litebox/jquery.litebox.css deleted file mode 100644 index 74e357b..0000000 --- a/docs/_components/jquery-litebox/jquery.litebox.css +++ /dev/null @@ -1,23 +0,0 @@ - -.litebox.overlay { - background: rgba(0, 0, 0, 0.96); - position: fixed; - top: 0; - left: 0; - height: 100%; - width: 100%; - z-index: 10; - - display: flex; - align-items: center; - justify-content: center; - - box-sizing: border-box; - padding: 1em; -} - -.litebox.overlay img { - flex-shrink: 1; - max-width: 100%; - max-height: 100%; -} diff --git a/docs/_components/jquery-litebox/jquery.litebox.gallery.css b/docs/_components/jquery-litebox/jquery.litebox.gallery.css deleted file mode 100644 index fab1de7..0000000 --- a/docs/_components/jquery-litebox/jquery.litebox.gallery.css +++ /dev/null @@ -1,48 +0,0 @@ - -.gallery { - padding: 0.5rem; - - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: center; -} - -.gallery figure { - margin: 1rem; - - background-color: rgb(250, 250, 250); - box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2); - - /* This ensures that our figures never grow bigger than desired. */ - flex-basis: 400px; -} - -.gallery figure a { - display: block; - margin: 0.5rem; -} - -.gallery img { - border: none; - display: block; - - width: 100%; -} - -.gallery figcaption { - display: block; - margin: 0.5rem; - - font-size: 1rem; - text-align: center; - - display: flex; - align-items: center; - justify-content: center; - - /* The figure caption should have a minimum height, if there are multiple - lines of caption, the total size of the figure is likely to be the same as - others. */ - min-height: 4rem; -} diff --git a/docs/_components/jquery-litebox/jquery.litebox.js b/docs/_components/jquery-litebox/jquery.litebox.js deleted file mode 100644 index 0922998..0000000 --- a/docs/_components/jquery-litebox/jquery.litebox.js +++ /dev/null @@ -1,30 +0,0 @@ - -(function ($, undefined) { - function showImage(element, overlay) { - if (element.href) { - var image = new Image(); - image.src = element.href; - overlay.append(image); - } - } - - $.fn.litebox = function(callback) { - callback = callback || showImage; - - this.on('click', function() { - var overlay = $('
'); - - overlay.on('click', function() { - overlay.remove(); - $('body').css('overflow', 'auto'); - }); - - callback(this, overlay); - - $('body').css('overflow', 'hidden'); - $('body').append(overlay); - - return false; - }); - } -}(jQuery)); diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.apache.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.apache.css deleted file mode 100644 index dfe774f..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.apache.css +++ /dev/null @@ -1,12 +0,0 @@ -.syntax-theme-base .apache .tag { - color: #00c; } -.syntax-theme-base .apache .tag-name { - color: #00f; } -.syntax-theme-base .apache .tag-name { - font-weight: bold; } - -@media (prefers-color-scheme: dark) { - .syntax-theme-base .apache .tag { - color: #6666ff; } - .syntax-theme-base .apache .tag-name { - color: #9999ff; } } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.applescript.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.applescript.css deleted file mode 100644 index 7e873a3..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.applescript.css +++ /dev/null @@ -1,5 +0,0 @@ -.syntax-theme-base .applescript { - font-family: Geneva, Helvetica, sans-serif; } - .syntax-theme-base .applescript .keyword { - color: #00f; - font-weight: bold; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.assembly.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.assembly.css deleted file mode 100644 index 3530f26..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.assembly.css +++ /dev/null @@ -1,8 +0,0 @@ -.syntax-theme-base .assembly .register { - color: #3caa20; - font-style: italic; } -.syntax-theme-base .assembly .label { - font-weight: bold; - color: #2a85b3; } -.syntax-theme-base .assembly .directive { - color: #828a3d; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash-script.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash-script.css deleted file mode 100644 index 6105a18..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash-script.css +++ /dev/null @@ -1,4 +0,0 @@ -.syntax-theme-base .bash-script .option { - color: #03f; } -.syntax-theme-base .bash-script .env { - font-style: italic; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash.css deleted file mode 100644 index 3c69f7e..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.bash.css +++ /dev/null @@ -1,2 +0,0 @@ -.syntax-theme-base .bash .stderr { - color: red; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.clang.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.clang.css deleted file mode 100644 index 012433b..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.clang.css +++ /dev/null @@ -1,6 +0,0 @@ -.syntax-theme-base .preprocessor { - color: #63381f; } - -@media (prefers-color-scheme: dark) { - .syntax-theme-base .preprocessor { - color: #c97e52; } } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.css.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.css.css deleted file mode 100644 index 6ea2994..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.css.css +++ /dev/null @@ -1,14 +0,0 @@ -.syntax-theme-base .css .selector { - color: #458; - font-weight: bold; } -.syntax-theme-base .css .property { - color: teal; } -.syntax-theme-base .css .color-box { - position: relative; } - .syntax-theme-base .css .color-box .sample { - border: 1px solid #000; - position: absolute; - left: 0.2em; - right: 0.2em; - top: 0.2em; - bottom: 0.2em; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.diff.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.diff.css deleted file mode 100644 index 1cfc36a..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.diff.css +++ /dev/null @@ -1,16 +0,0 @@ -.syntax-theme-base .add { - color: green; } -.syntax-theme-base .del { - color: red; } -.syntax-theme-base .insert { - color: green; } -.syntax-theme-base .insert-line { - background-color: #cfc !important; } -.syntax-theme-base .remove { - color: red; } -.syntax-theme-base .remove-line { - background-color: #fcc !important; } -.syntax-theme-base .offset { - color: blue; } -.syntax-theme-base .offset-line { - background-color: #ccf !important; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.html.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.html.css deleted file mode 100644 index 138c9ca..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.html.css +++ /dev/null @@ -1,5 +0,0 @@ -.syntax-theme-base .html .doctype { - font-weight: bold; - color: #938; } -.syntax-theme-base .html .javascript, .syntax-theme-base .html .css { - font-style: italic; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.ocaml.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.ocaml.css deleted file mode 100644 index 6bde078..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.ocaml.css +++ /dev/null @@ -1,3 +0,0 @@ -.syntax .ocaml .operator { - color: #06C; - font-weight: bold; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.protobuf.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.protobuf.css deleted file mode 100644 index 369af79..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.protobuf.css +++ /dev/null @@ -1,2 +0,0 @@ -.syntax .protobuf .variable { - font-weight: bold; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.python.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.python.css deleted file mode 100644 index cfd9805..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.python.css +++ /dev/null @@ -1,6 +0,0 @@ -.syntax-theme-base .python .decorator { - color: #ffb600; - font-weight: bold; } -.syntax-theme-base .python .builtin { - color: #33f; - font-style: italic; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.ruby.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.ruby.css deleted file mode 100644 index 97c50cf..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.ruby.css +++ /dev/null @@ -1,2 +0,0 @@ -.syntax-theme-base .ruby .string .ruby { - color: #808080; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.brush.xml.css b/docs/_components/jquery-syntax/base/jquery.syntax.brush.xml.css deleted file mode 100644 index 218f805..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.brush.xml.css +++ /dev/null @@ -1,18 +0,0 @@ -.syntax-theme-base .xml .cdata-content { - color: #046; - border-radius: 0.5em; } -.syntax-theme-base .xml .xml-tag, .syntax-theme-base .xml .cdata { - color: #00c; } -.syntax-theme-base .xml .tag-name, .syntax-theme-base .xml .cdata-tag { - color: #00f; - font-weight: bold; } -.syntax-theme-base .xml .namespace { - color: #00c; } -.syntax-theme-base .xml .attribute { - color: #00c; } -.syntax-theme-base .xml .instruction { - color: #00c; } -.syntax-theme-base .xml .entity, .syntax-theme-base .xml .percent-escape { - color: #666; - background-color: #ddd; - border-radius: 0.5em; } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.core.css b/docs/_components/jquery-syntax/base/jquery.syntax.core.css deleted file mode 100644 index 1d1f2bc..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.core.css +++ /dev/null @@ -1,58 +0,0 @@ -pre code.syntax-theme-base { - display: block; } - pre code.syntax-theme-base > span { - display: flex; } - pre code.syntax-theme-base .indent { - flex-grow: 0; - flex-shrink: 0; } - pre code.syntax-theme-base .text { - white-space: pre-wrap; - padding-left: 2ch; - text-indent: -2ch; } - -.syntax-theme-base a { - text-decoration: none; - color: inherit; } -.syntax-theme-base .function { - color: #33f; } -.syntax-theme-base .keyword, .syntax-theme-base .access, .syntax-theme-base .option { - color: #7a0968; } -.syntax-theme-base .type { - color: #3239D6; } -.syntax-theme-base .comment { - color: #6ac; } -.syntax-theme-base .constant { - color: #1c00ce; } -.syntax-theme-base .string, .syntax-theme-base .symbol { - color: #c41a15; } -.syntax-theme-base .string .escape { - color: #f99; } -.syntax-theme-base .operator { - color: black; } -.syntax-theme-base .href { - color: #0e0eff; - text-decoration: underline; } -.syntax-theme-base .variable { - color: #466997; } -.syntax-theme-base .highlight { - background-color: #fdfdba; } - -@media (prefers-color-scheme: dark) { - .syntax-theme-base .operator { - color: white; } - .syntax-theme-base .function { - color: #c59de7; } - .syntax-theme-base .keyword, .syntax-theme-base .access, .syntax-theme-base .option { - color: #328eff; } - .syntax-theme-base .type { - color: #37a4ff; } - .syntax-theme-base .comment { - color: #849fca; } - .syntax-theme-base .constant { - color: #c9a452; } - .syntax-theme-base .string, .syntax-theme-base .symbol { - color: #ed7f7d; } - .syntax-theme-base .string .escape { - color: #f99; } - .syntax-theme-base .variable { - color: #6e6dff; } } diff --git a/docs/_components/jquery-syntax/base/jquery.syntax.editor.css b/docs/_components/jquery-syntax/base/jquery.syntax.editor.css deleted file mode 100644 index 04ab4da..0000000 --- a/docs/_components/jquery-syntax/base/jquery.syntax.editor.css +++ /dev/null @@ -1,6 +0,0 @@ -.syntax-container.syntax-theme-base div.editor.syntax { - padding: 0.2em !important; - margin: 0 !important; - white-space: pre; } -.syntax-container.syntax-theme-base div.editor.syntax:focus { - outline: none; } diff --git a/docs/_components/jquery-syntax/base/theme.js b/docs/_components/jquery-syntax/base/theme.js deleted file mode 100644 index f67c776..0000000 --- a/docs/_components/jquery-syntax/base/theme.js +++ /dev/null @@ -1 +0,0 @@ -Syntax.themes["base"] = [] diff --git a/docs/_components/jquery-syntax/bright/jquery.syntax.core.css b/docs/_components/jquery-syntax/bright/jquery.syntax.core.css deleted file mode 100644 index f268c77..0000000 --- a/docs/_components/jquery-syntax/bright/jquery.syntax.core.css +++ /dev/null @@ -1,27 +0,0 @@ -.syntax-container.syntax-theme-bright .syntax { - font-family: Menlo, Monaco, Consolas, monospace; - line-height: 1.5em; } - .syntax-container.syntax-theme-bright .syntax .function { - color: #33f; } - .syntax-container.syntax-theme-bright .syntax .keyword { - color: #3c3; } - .syntax-container.syntax-theme-bright .syntax .access { - color: #ffb600; - font-weight: bold; } - .syntax-container.syntax-theme-bright .syntax .type { - color: #191; - font-weight: bold; } - .syntax-container.syntax-theme-bright .syntax .comment { - color: #6ac; - font-style: italic; } - .syntax-container.syntax-theme-bright .syntax .string, .syntax-container.syntax-theme-bright .syntax .constant { - color: #f33; } - .syntax-container.syntax-theme-bright .syntax .string .escape { - color: #f99; } - .syntax-container.syntax-theme-bright .syntax .operator { - color: #c00; } - .syntax-container.syntax-theme-bright .syntax .href { - color: #00f; - text-decoration: underline; } - .syntax-container.syntax-theme-bright .syntax .variable { - color: #22c; } diff --git a/docs/_components/jquery-syntax/bright/theme.js b/docs/_components/jquery-syntax/bright/theme.js deleted file mode 100644 index 623ee10..0000000 --- a/docs/_components/jquery-syntax/bright/theme.js +++ /dev/null @@ -1 +0,0 @@ -Syntax.themes["bright"] = ["base"] diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.apache.js b/docs/_components/jquery-syntax/jquery.syntax.brush.apache.js deleted file mode 100644 index 3a12786..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.apache.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("apache",function(a){a.push({pattern:/(<(\w+).*?>)/gi,matches:Syntax.extractMatches({klass:"tag",allow:["attribute","tag-name","string"]},{klass:"tag-name",process:Syntax.lib.webLinkProcess("site:http://httpd.apache.org/docs/trunk/ directive",!0)})});a.push({pattern:/(<\/(\w+).*?>)/gi,matches:Syntax.extractMatches({klass:"tag",allow:["tag-name"]},{klass:"tag-name"})});a.push({pattern:/^\s+([A-Z][\w]+)/gm,matches:Syntax.extractMatches({klass:"function",allow:["attribute"],process:Syntax.lib.webLinkProcess("site:http://httpd.apache.org/docs/trunk/ directive", -!0)})});a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.applescript.js b/docs/_components/jquery-syntax/jquery.syntax.brush.applescript.js deleted file mode 100644 index 56e2fc2..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.applescript.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("applescript",function(a){a.push("after before beginning continue copy each end every from get global in local named of return set some that the then times to where whose with without".split(" "),{klass:"keyword"});a.push("first second third fourth fifth sixth seventh eighth ninth tenth last front back middle".split(" "),{klass:"keyword"});a.push("activate add alias AppleScript ask attachment boolean class constant delete duplicate empty exists false id integer list make message modal modified new no paragraph pi properties quit real record remove rest result reveal reverse run running save string true word yes".split(" "), -{klass:"keyword"});var b=[/(\-\-|#).*$/gm,/\(\*(?:[\s\S]*?\(\*[\s\S]*?\*\))*[\s\S]*?\*\)/gm,Syntax.lib.perlStyleComment.pattern];a.push(Syntax.lib.webLink);a.push(b,{klass:"comment",allow:["href"]});a.push(Syntax.lib.doubleQuotedString);a.push([/\b\d+(st|nd|rd|th)\b/g,/(-)?(\d)+(\.(\d)?)?(E\+(\d)+)?/g],{klass:"constant"});a.push({pattern:/&|\u00ac|=|\u2260|>|<|\u2265|>=|\u2264|<=|\*|\+|-|\/|\u00f7|\^/g,klass:"operator"});a.push({pattern:/\b(?:and|as|div|mod|not|or|return(?!\s&)(ing)?|equals|(is(n't| not)? )?equal( to)?|does(n't| not) equal|(is(n't| not)? )?(greater|less) than( or equal( to)?)?|(comes|does(n't| not) come) (after|before)|is(n't| not)?( in)? (back|front) of|is(n't| not)? behind|is(n't| not)?( (in|contained by))?|does(n't| not) contain|contain(s)?|(start|begin|end)(s)? with|((but|end) )?(consider|ignor)ing|prop(erty)?|(a )?ref(erence)?( to)?|repeat (until|while|with)|((end|exit) )?repeat|((else|end) )?if|else|(end )?(script|tell|try)|(on )?error|(put )?into|(of )?(it|me)|its|my|with (timeout( of)?|transaction)|end (timeout|transaction))\b/g, -klass:"keyword"});a.push({pattern:/\b(?:about|above|against|around|at|below|beneath|beside|between|by|(apart|aside) from|(instead|out) of|into|on(to)?|over|since|thr(ough|u)|under)\b/g,klass:"keyword"});a.push({pattern:/\b(?:adding folder items to|after receiving|choose( ((remote )?application|color|folder|from list|URL))?|clipboard info|set the clipboard to|(the )?clipboard|entire contents|display(ing| (alert|dialog|mode))?|document( (edited|file|nib name))?|file( (name|type))?|(info )?for|giving up after|(name )?extension|quoted form|return(ed)?|second(?! item)(s)?|list (disks|folder)|text item(s| delimiters)?|(Unicode )?text|(disk )?item(s)?|((current|list) )?view|((container|key) )?window|with (data|icon( (caution|note|stop))?|parameter(s)?|prompt|properties|seed|title)|case|diacriticals|hyphens|numeric strings|punctuation|white space|folder creation|application(s( folder)?| (processes|scripts position|support))?|((desktop )?(pictures )?|(documents|downloads|favorites|home|keychain|library|movies|music|public|scripts|sites|system|users|utilities|workflows) )folder|desktop|Folder Action scripts|font(s| panel)?|help|internet plugins|modem scripts|(system )?preferences|printer descriptions|scripting (additions|components)|shared (documents|libraries)|startup (disk|items)|temporary items|trash|on server|in AppleTalk zone|((as|long|short) )?user name|user (ID|locale)|(with )?password|in (bundle( with identifier)?|directory)|(close|open for) access|read|write( permission)?|(g|s)et eof|using( delimiters)?|starting at|default (answer|button|color|country code|entr(y|ies)|identifiers|items|name|location|script editor)|hidden( answer)?|open(ed| (location|untitled))?|error (handling|reporting)|(do( shell)?|load|run|store) script|administrator privileges|altering line endings|get volume settings|(alert|boot|input|mount|output|set) volume|output muted|(fax|random )?number|round(ing)?|up|down|toward zero|to nearest|as taught in school|system (attribute|info)|((AppleScript( Studio)?|system) )?version|(home )?directory|(IPv4|primary Ethernet) address|CPU (type|speed)|physical memory|time (stamp|to GMT)|replacing|ASCII (character|number)|localized string|from table|offset|summarize|beep|delay|say|(empty|multiple) selections allowed|(of|preferred) type|invisibles|showing( package contents)?|editable URL|(File|FTP|News|Media|Web) [Ss]ervers|Telnet hosts|Directory services|Remote applications|waiting until completion|saving( (in|to))?|path (for|to( (((current|frontmost) )?application|resource))?)|POSIX (file|path)|(background|RGB) color|(OK|cancel) button name|cancel button|button(s)?|cubic ((centi)?met(re|er)s|yards|feet|inches)|square ((kilo)?met(re|er)s|miles|yards|feet)|(centi|kilo)?met(re|er)s|miles|yards|feet|inches|lit(re|er)s|gallons|quarts|(kilo)?grams|ounces|pounds|degrees (Celsius|Fahrenheit|Kelvin)|print( (dialog|settings))?|clos(e(able)?|ing)|(de)?miniaturized|miniaturizable|zoom(ed|able)|attribute run|action (method|property|title)|phone|email|((start|end)ing|home) page|((birth|creation|current|custom|modification) )?date|((((phonetic )?(first|last|middle))|computer|host|maiden|related) |nick)?name|aim|icq|jabber|msn|yahoo|address(es)?|save addressbook|should enable action|city|country( code)?|formatte(r|d address)|(palette )?label|state|street|zip|AIM [Hh]andle(s)?|my card|select(ion| all)?|unsaved|(alpha )?value|entr(y|ies)|group|(ICQ|Jabber|MSN) handle|person|people|company|department|icon image|job title|note|organization|suffix|vcard|url|copies|collating|pages (across|down)|request print time|target( printer)?|((GUI Scripting|Script menu) )?enabled|show Computer scripts|(de)?activated|awake from nib|became (key|main)|call method|of (class|object)|center|clicked toolbar item|closed|for document|exposed|(can )?hide|idle|keyboard (down|up)|event( (number|type))?|launch(ed)?|load (image|movie|nib|sound)|owner|log|mouse (down|dragged|entered|exited|moved|up)|move|column|localization|resource|script|register|drag (info|types)|resigned (active|key|main)|resiz(e(d)?|able)|right mouse (down|dragged|up)|scroll wheel|(at )?index|should (close|open( untitled)?|quit( after last window closed)?|zoom)|((proposed|screen) )?bounds|show(n)?|behind|in front of|size (mode|to fit)|update(d| toolbar item)?|was (hidden|miniaturized)|will (become active|close|finish launching|hide|miniaturize|move|open|quit|(resign )?active|((maximum|minimum|proposed) )?size|show|zoom)|bundle|data source|movie|pasteboard|sound|tool(bar| tip)|(color|open|save) panel|coordinate system|frontmost|main( (bundle|menu|window))?|((services|(excluded from )?windows) )?menu|((executable|frameworks|resource|scripts|shared (frameworks|support)) )?path|(selected item )?identifier|data|content(s| view)?|character(s)?|click count|(command|control|option|shift) key down|context|delta (x|y|z)|key( code)?|location|pressure|unmodified characters|types|(first )?responder|playing|(allowed|selectable) identifiers|allows customization|(auto saves )?configuration|visible|image( name)?|menu form representation|tag|user(-| )defaults|associated file name|(auto|needs) display|current field editor|floating|has (resize indicator|shadow)|hides when deactivated|level|minimized (image|title)|opaque|position|release when closed|sheet|title(d)?)\b/g, -klass:"keyword"})}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.assembly.js b/docs/_components/jquery-syntax/jquery.syntax.brush.assembly.js deleted file mode 100644 index f3013d7..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.assembly.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("assembly",function(a){a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push({pattern:/\.[a-zA-Z_][a-zA-Z0-9_]*/gm,klass:"directive"});a.push({pattern:/^[a-zA-Z_][a-zA-Z0-9_]*:/gm,klass:"label"});a.push({pattern:/^\s*([a-zA-Z]+)/gm,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/(-[0-9]+)|(\b[0-9]+)|(\$[0-9]+)/g,klass:"constant"});a.push({pattern:/(\-|\b|\$)(0x[0-9a-f]+|[0-9]+|[a-z0-9_]+)/gi,klass:"constant"});a.push({pattern:/%\w+/g,klass:"register"}); -a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.bash-script.js b/docs/_components/jquery-syntax/jquery.syntax.brush.bash-script.js deleted file mode 100644 index 1da775d..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.bash-script.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("bash-script",function(a){a.push(["&&","|",";","{","}"],{klass:"operator"});a.push({pattern:/(?:^|\||;|&&)\s*((?:"([^"]|\\")+"|'([^']|\\')+'|\\\n|.|[ \t])+?)(?=$|\||;|&&)/gmi,matches:Syntax.extractMatches({brush:"bash-statement"})})}); -Syntax.register("bash-statement",function(a){a.push("break case continue do done elif else eq fi for function ge gt if in le lt ne return then until while".split(" "),{klass:"keyword"});a.push("> < = ` -- { } ( ) [ ]".split(" "),{klass:"operator"});a.push({pattern:/\(\((.*?)\)\)/gmi,klass:"expression",allow:["variable","string","operator","constant"]});a.push({pattern:/`([\s\S]+?)`/gmi,matches:Syntax.extractMatches({brush:"bash-script",debug:!0})});a.push(Syntax.lib.perlStyleComment);a.push({pattern:/^\s*((?:\S+?=\$?(?:\[[^\]]+\]|\(\(.*?\)\)|"(?:[^"]|\\")+"|'(?:[^']|\\')+'|\S+)\s*)*)((?:(\\ |\S)+)?)/gmi, -matches:Syntax.extractMatches({klass:"env",allow:["variable","string","operator","constant","expression"]},{klass:"function",allow:["variable","string"]})});a.push({pattern:/(\S+?)=/gmi,matches:Syntax.extractMatches({klass:"variable"}),only:["env"]});a.push({pattern:/\$\w+/g,klass:"variable"});a.push({pattern:/\s\-+[\w-]+/g,klass:"option"});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.bash.js b/docs/_components/jquery-syntax/jquery.syntax.brush.bash.js deleted file mode 100644 index 04efaba..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.bash.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.brushes.dependency("bash","bash-script");Syntax.register("bash",function(a){a.push({pattern:/^([\w@:~ ]*?[\$|#])\s+(.*?)$/gm,matches:Syntax.extractMatches({klass:"prompt"},{brush:"bash-script"})});a.push({pattern:/^\-\- .*$/gm,klass:"comment",allow:["href"]});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.webLink);a.push({klass:"stderr",allow:["string","comment","constant","href"]})}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.basic.js b/docs/_components/jquery-syntax/jquery.syntax.brush.basic.js deleted file mode 100644 index e8ac3df..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.basic.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.lib.vbStyleComment={pattern:/' .*$/gm,klass:"comment",allow:["href"]}; -Syntax.register("basic",function(a){var b="- & &= * *= / /= \\ = ^ ^= + += = -=".split(" ");b="+ - * / += -= *= /= = := == != ! % ? > < >= <= && || & | ^ . ~ .. >> << >>> <<< >>= <<= >>>= <<<= %= ^= @".split(" ");a.push("CBool CByte CChar CDate CDec CDbl Char CInt CLng CObj Const CShort CSng CStr CType Date Decimal Variant String Short Long Single Double Object Integer Boolean Byte Char".split(" "),{klass:"type"});a.push("AddHandler AddressOf Alias And AndAlso Ansi As Assembly Auto ByRef ByVal Call Case Catch Declare Default Delegate Dim DirectCast Do Each Else ElseIf End Enum Erase Error Event Exit Finally For Function Get GetType GoSub GoTo Handles If Implements Imports In Inherits Interface Is Let Lib Like Loop Mod Module MustOverride Namespace New Next Not On Option Optional Or OrElse Overloads Overridable Overrides ParamArray Preserve Property RaiseEvent ReadOnly ReDim REM RemoveHandler Resume Return Select Set Static Step Stop Structure Sub SyncLock Then Throw To Try TypeOf Unicode Until When While With WithEvents WriteOnly Xor ExternalSource Region Print Class".split(" "),{klass:"keyword", -options:"gi"});a.push(b,{klass:"operator"});a.push("Public Protected Private Shared Friend Shadows MustInherit NotInheritable NotOverridable".split(" "),{klass:"access"});a.push(["Me","MyClass","MyBase","super","True","False","Nothing",/[A-Z][A-Z0-9_]+/g],{klass:"constant"});a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.vbStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.postprocess=function(a,b,c){jQuery(".function", -b).each(function(){var a=jQuery(this).text();jQuery(this).replaceWith(jQuery("").attr("href","http://social.msdn.microsoft.com/Search/en-us?query="+encodeURIComponent(a)).text(a))});return b}}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.clang.js b/docs/_components/jquery-syntax/jquery.syntax.brush.clang.js deleted file mode 100644 index 7719237..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.clang.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("clang",function(a){a.push("this true false NULL YES NO nil".split(" "),{klass:"constant"});a.push("mutable auto const register typename abstract".split(" "),{klass:"keyword"});a.push("double float int short char long signed unsigned bool void id".split(" "),{klass:"type"});a.push("@interface @implementation @protocol @end @try @throw @catch @finally @class @selector @encode @synchronized @property @synthesize @dynamic struct break continue else for switch case default enum goto register sizeof typedef volatile do extern if return static union while asm dynamic_cast namespace reinterpret_cast try explicit static_cast typeid catch operator template class const_cast inline throw virtual IBOutlet".split(" "), -{klass:"keyword"});a.push("+ * / - & | ~ ! % < = > [ ] new delete in".split(" "),{klass:"operator"});a.push("@private @protected @public @required @optional private protected public friend using".split(" "),{klass:"access"});a.push({pattern:/@property\((.*)\)[^;]+;/gmi,klass:"objective-c-property",allow:"*"});a.push("getter setter readwrite readonly assign retain copy nonatomic".split(" "),{klass:"keyword",only:["objective-c-property"]});a.push({pattern:/@(?=")/g,klass:"string"});a.push(Syntax.lib.camelCaseType); -a.push(Syntax.lib.cStyleType);a.push({pattern:/(?:class|struct|enum|namespace)\s+([^{;\s]+)/gmi,matches:Syntax.extractMatches({klass:"type"})});a.push({pattern:/#.*$/gmi,klass:"preprocessor",allow:["string"]});a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push({pattern:/\w+:(?=.*(\]|;|\{))(?!:)/g,klass:"function"});a.push({pattern:/[^:\[]\s+(\w+)(?=\])/g,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/-\s*(\([^\)]+?\))?\s*(\w+)\s*\{/g, -matches:Syntax.extractMatches({index:2,klass:"function"})});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.csharp.js b/docs/_components/jquery-syntax/jquery.syntax.brush.csharp.js deleted file mode 100644 index 6c1daf2..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.csharp.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("csharp",function(a){a.push(["this","true","false","null"],{klass:"constant"});a.push("object bool byte fixed float uint char ulong ushort decimal int sbyte short void long string double".split(" "),{klass:"type"});a.push("abstract add alias ascending base break case catch class const continue default delegate descending do dynamic else enum event explicit extern finally for foreach from get global goto group if implicit in interface into join let lock namespace new operator orderby out override params partial readonly ref remove return sealed select set stackalloc static struct switch throw try unsafe using value var virtual volatile where while yield".split(" "), -{klass:"keyword"});a.push("+ - * / % & | ^ ! ~ && || ++ -- << >> == != < > <= >= = ? new as is sizeof typeof checked unchecked".split(" "),{klass:"operator"});a.push(["public","private","internal","protected"],{klass:"access"});a.push(Syntax.lib.cStyleFunction);a.push({pattern:/(?:\.)([a-z_][a-z0-9_]+)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString); -a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.css.js b/docs/_components/jquery-syntax/jquery.syntax.brush.css.js deleted file mode 100644 index 0aa079e..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.css.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("css",function(a){var e=[].concat(jQuery.map("AliceBlue AntiqueWhite Aqua Aquamarine Azure Beige Bisque Black BlanchedAlmond Blue BlueViolet Brown BurlyWood CadetBlue Chartreuse Chocolate Coral CornflowerBlue Cornsilk Crimson Cyan DarkBlue DarkCyan DarkGoldenRod DarkGray DarkGreen DarkKhaki DarkMagenta DarkOliveGreen Darkorange DarkOrchid DarkRed DarkSalmon DarkSeaGreen DarkSlateBlue DarkSlateGray DarkTurquoise DarkViolet DeepPink DeepSkyBlue DimGray DodgerBlue FireBrick FloralWhite ForestGreen Fuchsia Gainsboro GhostWhite Gold GoldenRod Gray Green GreenYellow HoneyDew HotPink IndianRed Indigo Ivory Khaki Lavender LavenderBlush LawnGreen LemonChiffon LightBlue LightCoral LightCyan LightGoldenRodYellow LightGrey LightGreen LightPink LightSalmon LightSeaGreen LightSkyBlue LightSlateGray LightSteelBlue LightYellow Lime LimeGreen Linen Magenta Maroon MediumAquaMarine MediumBlue MediumOrchid MediumPurple MediumSeaGreen MediumSlateBlue MediumSpringGreen MediumTurquoise MediumVioletRed MidnightBlue MintCream MistyRose Moccasin NavajoWhite Navy OldLace Olive OliveDrab Orange OrangeRed Orchid PaleGoldenRod PaleGreen PaleTurquoise PaleVioletRed PapayaWhip PeachPuff Peru Pink Plum PowderBlue Purple Red RosyBrown RoyalBlue SaddleBrown Salmon SandyBrown SeaGreen SeaShell Sienna Silver SkyBlue SlateBlue SlateGray Snow SpringGreen SteelBlue Tan Teal Thistle Tomato Turquoise Violet Wheat White WhiteSmoke Yellow YellowGreen".split(" "), -function(a){return"("+Syntax.Brush.convertStringToTokenPattern(a,!0)+")"}),jQuery.map(["#[0-9a-f]{3,6}","rgba?\\(.+?\\)","hsla?\\(.+?\\)"],function(a){return"("+Syntax.Brush.convertStringToTokenPattern(a,!1)+")"}));a.push({pattern:/\(.*?\)/g,allow:"*",disallow:["property"]});a.push({pattern:/\s*([:\.\[\]"'=\s\w#\.\-,]+)\s+\{/gm,matches:Syntax.extractMatches({klass:"selector",allow:["string"]})});a.push({pattern:new RegExp(e.join("|"),"gi"),klass:"color",process:function(a,d){d=Syntax.innerText(a); -var c=document.createElement("span");c.className="colour-box";var b=document.createElement("span");b.className="sample";b.style.backgroundColor=d;b.appendChild(document.createTextNode(" "));c.appendChild(b);a.appendChild(c);return a}});a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.webLink);a.push({pattern:/\{(.|\n)*?\}/g,klass:"properties",allow:"*"});a.push({pattern:/:(.*?(?=\})|(.|\n)*?(?=(\}|;)))/g,matches:Syntax.extractMatches({klass:"value",allow:["color"],only:["properties"]})});a.push({pattern:/([\-\w]+):/g, -matches:Syntax.extractMatches({klass:"property",process:Syntax.lib.webLinkProcess("http://cssdocs.org/")})});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.diff.js b/docs/_components/jquery-syntax/jquery.syntax.brush.diff.js deleted file mode 100644 index 7173edc..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.diff.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("diff",function(a){a.push({pattern:/^\+\+\+.*$/gm,klass:"add"});a.push({pattern:/^\-\-\-.*$/gm,klass:"del"});a.push({pattern:/^@@.*@@/gm,klass:"offset"});a.push({pattern:/^\+[^\+]{1}.*$/gm,klass:"insert"});a.push({pattern:/^\-[^\-]{1}.*$/gm,klass:"remove"});a.postprocess=function(a,b,c){$(".insert",b).closest(".source").addClass("insert-line");$(".remove",b).closest(".source").addClass("remove-line");$(".offset",b).closest(".source").addClass("offset-line");return b}}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.go.js b/docs/_components/jquery-syntax/jquery.syntax.brush.go.js deleted file mode 100644 index 12a8a49..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.go.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("go",function(a){a.push(["true","false","iota","nil"],{klass:"constant"});a.push([/u?int\d*/g,/float\d+/g,/complex\d+/g,"byte","uintptr","string"],{klass:"type"});a.push("break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var".split(" "),{klass:"keyword"});a.push("+ & += &= && == != - | -= |= || < <= * ^ *= ^= <- > >= / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^=".split(" "), -{klass:"operator"});a.push("append cap close complex copy imag len make new panic print println real recover".split(" "),{klass:"function"});a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.haskell.js b/docs/_components/jquery-syntax/jquery.syntax.brush.haskell.js deleted file mode 100644 index b56f3ae..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.haskell.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("haskell",function(a){a.push(["True","False"],{klass:"constant"});a.push("as;case;of;class;data;data family;data instance;default;deriving;deriving instance;do;forall;foreign;hiding;if;then;else;import;infix;infixl;infixr;instance;let;in;mdo;module;newtype;proc;qualified;rec;type;type family;type instance;where".split(";"),{klass:"keyword"});a.push("` | \\ - -< -<< -> * ? ?? # <- @ ! :: _ ~ > ; { }".split(" "),{klass:"operator"});a.push(Syntax.lib.camelCaseType);a.push({pattern:/\-\-.*$/gm, -klass:"comment",allow:["href"]});a.push({pattern:/\{\-[\s\S]*?\-\}/gm,klass:"comment",allow:["href"]});a.push(Syntax.lib.webLink);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.html.js b/docs/_components/jquery-syntax/jquery.syntax.brush.html.js deleted file mode 100644 index 8d40e8d..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.html.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.brushes.dependency("html","xml");Syntax.brushes.dependency("html","javascript");Syntax.brushes.dependency("html","css");Syntax.brushes.dependency("html","php-script");Syntax.brushes.dependency("html","ruby"); -Syntax.register("html",function(a){a.push({pattern:/((.|\n)*?)<\/script>/gmi,matches:Syntax.extractMatches({brush:"javascript"})});a.push({pattern:/((.|\n)*?)<\/style>/gmi,matches:Syntax.extractMatches({brush:"css"})});a.push({pattern:/((<\?php)([\s\S]*?)(\?>))/gm,matches:Syntax.extractMatches({klass:"php-tag",allow:["keyword","php-script"]},{klass:"keyword"},{brush:"php-script"},{klass:"keyword"})});a.push({pattern:/((<\?rb?)([\s\S]*?)(\?>))/gm, -matches:Syntax.extractMatches({klass:"ruby-tag",allow:["keyword","ruby"]},{klass:"keyword"},{brush:"ruby"},{klass:"keyword"})});a.push({pattern:/<%=?(.*?)(%>)/g,klass:"instruction",allow:["string"]});a.push({pattern://g,matches:Syntax.extractMatches({klass:"doctype"})});a.push({pattern:/(%[0-9a-f]{2})/gi,klass:"percent-escape",only:["html"]});a.derives("xml")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.io.js b/docs/_components/jquery-syntax/jquery.syntax.brush.io.js deleted file mode 100644 index c80dae6..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.io.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("io",function(a){a.push(Syntax.lib.cStyleFunction);a.push(["return"],{klass:"keywords"});a.push("::= := or and @ + * / - & | ~ ! % < = > [ ] new delete".split(" "),{klass:"operator"});a.push({pattern:/\b([ \t]+([a-z]+))/gi,matches:Syntax.extractMatches({index:2,klass:"function"})});a.push({pattern:/\)([ \t]+([a-z]+))/gi,matches:Syntax.extractMatches({index:2,klass:"function"})});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment); -a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.java.js b/docs/_components/jquery-syntax/jquery.syntax.brush.java.js deleted file mode 100644 index 643d143..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.java.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("java",function(a){a.push(["this","true","false","null"],{klass:"constant"});a.push("void byte short int long float double boolean char".split(" "),{klass:"type"});a.push("abstract continue for switch assert default goto synchronized do if break implements throw else import throws case enum return transient catch extends try final interface static class finally strictfp volatile const native super while".split(" "),{klass:"keyword"});a.push("++ -- ++ -- + - ~ ! * / % + - << >> >>> < > <= >= == != & ^ | && || ? = += -= *= /= %= &= ^= |= <<= >>= >>>= instanceof new delete".split(" "), -{klass:"operator"});a.push(["private","protected","public","package"],{klass:"access"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.cStyleFunction);a.processes["function"]=Syntax.lib.webLinkProcess('java "Developer Documentation"', -!0)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.javascript.js b/docs/_components/jquery-syntax/jquery.syntax.brush.javascript.js deleted file mode 100644 index 4af2fa2..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.javascript.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("javascript",function(a){a.push(["this","true","false","null"],{klass:"constant"});a.push("async await break case catch class const continue debugger default delete do else export extends finally for function if import in instanceof let new return super switch this throw try typeof var void while with yield".split(" "),{klass:"keyword"});a.push("+*/-&|~!%<=>".split(""),{klass:"operator"});a.push("implements package protected interface private public".split(" "),{klass:"access"});a.push(Syntax.lib.perlStyleRegularExpression); -a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.kai.js b/docs/_components/jquery-syntax/jquery.syntax.brush.kai.js deleted file mode 100644 index ad26dd1..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.kai.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("kai",function(a){a.push("()[]{}".split(""),{klass:"operator"});a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.webLink);a.push({pattern:/\(([^\s\(\)]+)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/`[a-z]*/gi,klass:"constant"});a.push(Syntax.lib.multiLineDoubleQuotedString);a.push(Syntax.lib.stringEscape)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.lisp.js b/docs/_components/jquery-syntax/jquery.syntax.brush.lisp.js deleted file mode 100644 index c58e79a..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.lisp.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.lib.lispStyleComment={pattern:/(;+) .*$/gm,klass:"comment",allow:["href"]};Syntax.register("lisp",function(a){a.push(["(",")"],{klass:"operator"});a.push(Syntax.lib.lispStyleComment);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.webLink);a.push({pattern:/\(\s*([^\s\(\)]+)/gmi,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/#[a-z]+/gi,klass:"constant"});a.push(Syntax.lib.multiLineDoubleQuotedString);a.push(Syntax.lib.stringEscape)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.lua.js b/docs/_components/jquery-syntax/jquery.syntax.brush.lua.js deleted file mode 100644 index d113a6b..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.lua.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("lua",function(a){a.push(["self","true","false","nil"],{klass:"constant"});a.push("and break do else elseif end false for function if in local nil not or repeat return then true until while".split(" "),{klass:"keyword"});a.push("+ - * / % ^ # .. = == ~= < > <= >= ? :".split(" "),{klass:"operator"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleFunction);a.push({pattern:/\-\-.*$/gm,klass:"comment",allow:["href"]});a.push({pattern:/\-\-\[\[(\n|.)*?\]\]\-\-/gm,klass:"comment", -allow:["href"]});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.nginx.js b/docs/_components/jquery-syntax/jquery.syntax.brush.nginx.js deleted file mode 100644 index 76f494a..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.nginx.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("nginx",function(a){a.push({pattern:/((\w+).*?);/g,matches:Syntax.extractMatches({klass:"directive",allow:"*"},{klass:"function",process:Syntax.lib.webLinkProcess("http://nginx.org/r/")})});a.push({pattern:/(\w+).*?{/g,matches:Syntax.extractMatches({klass:"keyword"})});a.push({pattern:/(\$)[\w]+/g,klass:"variable"});a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.ocaml.js b/docs/_components/jquery-syntax/jquery.syntax.brush.ocaml.js deleted file mode 100644 index ce40494..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.ocaml.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("ocaml",function(a){a.push(["private","public"],{klass:"access"});a.push(["true","false"],{klass:"constant"});a.push(["bool","byte","sbyte",/\bu?int\d*\b/g,"nativeint","unativeint","char","string","decimal","unit","void","float32","single","float64","double","list","array","exn","format","fun","option","ref"],{klass:"type"});a.push("abstract and as assert begin class default delegate do done downcast downto elif else end exception extern finally for fun function if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override rec return static struct then to try type upcast use val when while with yield asr land lor lsl lsr lxor mod sig atomic break checked component const constraint constructor continue eager event external fixed functor global include method mixin object parallel process protected pure sealed trait virtual volatile val".split(" "), -{klass:"keyword"});a.push("! <> % & * + - -> / :: := :> :? :?> < = > ? @ ^ _ ` | ~ ' [< >] <| |> [| |] (| |) (* *) in".split(" "),{klass:"operator"});a.push({pattern:/(?:open|new)\s+((?:\.?[a-z][a-z0-9]*)+)/gi,matches:Syntax.extractMatches({klass:"type"})});a.push({pattern:/(?:\.)([a-z_][a-z0-9_]+)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push({pattern:/(?:\(|,)\s*(\w+\s*=)/g,matches:Syntax.extractMatches({klass:"keyword-argument"})});a.push({pattern:/([a-z_][a-z0-9_]*)\s*\((?!\*)/gi, -matches:Syntax.extractMatches({klass:"function"})});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.webLink);a.push({pattern:/\(\*[\s\S]*?\*\)/g,klass:"comment"});a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.ooc.js b/docs/_components/jquery-syntax/jquery.syntax.brush.ooc.js deleted file mode 100644 index 50106db..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.ooc.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("ooc",function(a){a.push(["this","super","true","false","null",/[A-Z][A-Z0-9_]+/g],{klass:"constant"});a.push("Int Int8 Int16 Int32 Int64 Int80 Int128 UInt UInt8 UInt16 UInt32 UInt64 UInt80 UInt128 Octet Short UShort Long ULong LLong ULLong Float Double LDouble Float32 Float64 Float128 Char UChar WChar String Void Pointer Bool SizeT This".split(" "),{klass:"type"});a.push("class interface implement abstract extends from const final static import use extern inline proto break continue fallthrough operator if else for while do switch case as in version return include cover func".split(" "), -{klass:"keyword"});a.push("+ - * / += -= *= /= = := == != ! % ? > < >= <= && || & | ^ . ~ .. >> << >>> <<< >>= <<= >>>= <<<= %= ^= @".split(" "),{klass:"operator"});a.push({pattern:/0[xcb][0-9a-fA-F]+/g,klass:"constant"});a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleType);a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString); -a.push(Syntax.lib.stringEscape);a.processes["function"]=Syntax.lib.webLinkProcess("http://docs.ooc-lang.org/search.html?q=")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.pascal.js b/docs/_components/jquery-syntax/jquery.syntax.brush.pascal.js deleted file mode 100644 index 4ed199a..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.pascal.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("pascal",function(a){a.push(["true","false","nil"],{klass:"constant",options:"gi"});a.push("absolute abstract all and_then as asm asmname attribute begin bindable c c_language case class const constructor destructor dispose do downto else end except exit export exports external far file finalization finally for forward function goto if implementation import inherited initialization inline interface interrupt is keywords label library module name near new object of on only operator or_else otherwise packed pascal pow private procedure program property protected public published qualified raise record repeat resident restricted segment set then threadvar to try type unit until uses value var view virtual while with".split(" "), -{klass:"keyword",options:"gi"});a.push("+ - * / div mod and or xor shl shr not = >= > <> <= < in :=".split(" "),{klass:"operator",options:"gi"});a.push(Syntax.lib.camelCaseType);a.push({pattern:/\{[\s\S]*?\}/gm,klass:"comment",allow:["href"]});a.push({pattern:/\(\*[\s\S]*?\*\)/gm,klass:"comment",allow:["href"]});a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber); -a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.perl5.js b/docs/_components/jquery-syntax/jquery.syntax.brush.perl5.js deleted file mode 100644 index 9da04dd..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.perl5.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("perl5",function(a){a.push(["this","true","false"],{klass:"constant"});a.push("bless caller continue die do dump else elsif eval exit for foreach goto if import last local my next no our package redo ref require return sub tie tied unless untie until use wantarray while".split(" "),{klass:"keyword"});a.push("-> ++ -- ** ! ~ \\ + - =~ !~ * / % x + - . << >> < > <= >= lt gt le ge == != <=> eq ne cmp ~~ & | ^ && || // .. ... ?: = , => not and or xor".split(" "),{klass:"operator"});a.push("abs accept alarm atan2 bind binmode chdir chmod chomp chop chown chr chroot close closedir connect cos crypt defined delete each endgrent endhostent endnetent endprotoent endpwent endservent eof exec exists exp fcntl fileno flock fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getppid getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt glob gmtime grep hex index int ioctl join keys kill lc lcfirst length link listen localtime lock log lstat map mkdir msgctl msgget msgrcv msgsnd oct open opendir ord pack pipe pop pos print printf prototype push quotemeta rand read readdir readline readlink readpipe recv rename reset reverse rewinddir rindex rmdir scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat study substr symlink syscall sysopen sysread sysseek system syswrite tell telldir time times tr truncate uc ucfirst umask undef unlink unpack unshift utime values vec wait waitpid warn write".split(" "), -{klass:"function"});a.push(Syntax.lib.perlStyleRegularExpression);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.webLink);a.push({pattern:/(\$|@|%)\w+/gi,klass:"variable"});a.push({pattern:/__END__[\s\S]*/gm,klass:"comment"});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.php-script.js b/docs/_components/jquery-syntax/jquery.syntax.brush.php-script.js deleted file mode 100644 index 6e77b76..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.php-script.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("php-script",function(a){a.push(["this","true","false"],{klass:"constant"});a.push("abstract and as break case cfunction class const continue declare default die do echo else elseif enddeclare endfor endforeach endif endswitch endwhile extends extends for foreach function global if implements include include_once interface old_function or require require_once return static switch throw use var while xor".split(" "),{klass:"keyword"});a.push("+ * / - & | ~ ! % < = > [ ] new".split(" "), -{klass:"operator"});a.push(["private","protected","public"],{klass:"access"});a.push({pattern:/\$[a-z_][a-z0-9]*/gi,klass:"variable"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber); -a.processes["function"]=Syntax.lib.webLinkProcess("http://www.php.net/manual-lookup.php?pattern=")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.php.js b/docs/_components/jquery-syntax/jquery.syntax.brush.php.js deleted file mode 100644 index 32336fd..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.php.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.brushes.dependency("php","php-script");Syntax.register("php",function(a){a.push({pattern:/(<\?(php)?)((.|\n)*?)(\?>)/gm,matches:Syntax.extractMatches({klass:"keyword"},null,{brush:"php-script"},null,{klass:"keyword"})})}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.plain.js b/docs/_components/jquery-syntax/jquery.syntax.brush.plain.js deleted file mode 100644 index 5d7e151..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.plain.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("plain",function(a){a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.protobuf.js b/docs/_components/jquery-syntax/jquery.syntax.brush.protobuf.js deleted file mode 100644 index 4ee2cfe..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.protobuf.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("protobuf",function(a){a.push("enum extend extensions group import max message option package returns rpc service syntax to default".split(" "),{klass:"keyword"});a.push(["true","false"],{klass:"constant"});a.push("bool bytes double fixed32 fixed64 float int32 int64 sfixed32 sfixed64 sint32 sint64 string uint32 uint64".split(" "),{klass:"type"});a.push(["optional","required","repeated"],{klass:"access"});a.push(Syntax.lib.camelCaseType);a.push({pattern:/\s+(\w+)\s*=\s*\d+/g,matches:Syntax.extractMatches({klass:"variable"})}); -a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.python.js b/docs/_components/jquery-syntax/jquery.syntax.brush.python.js deleted file mode 100644 index 36e73c7..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.python.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("python",function(a){a.push({pattern:/^\s*@\w+/gm,klass:"decorator"});a.push(["self","True","False","None"],{klass:"constant"});a.push("and as assert break class continue def del elif else except exec finally for from global if import in is lambda not or pass print raise return try while with yield".split(" "),{klass:"keyword"});a.push("!= % %= & &= ( ) * ** **= *= + += , - -= . / // //= /= : ; < << <<= <= <> = == > >= >> >>= @ [ ] ^ ^= ` ` { | |= } ~".split(" "),{klass:"operator"}); -a.push("abs all any basestring bin bool callable chr classmethod cmp compile complex delattr dict dir divmod enumerate eval execfile file filter float format frozenset getattr globals hasattr hash help hex id input int isinstance issubclass iter len list locals long map max min next object oct open ord pow print property range raw_input reduce reload repr reversed round set setattr slice sorted staticmethod str sum super tuple type type unichr unicode vars xrange zip __import__ apply buffer coerce intern".split(" "), -{klass:"builtin"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.perlStyleComment);a.push({pattern:/(['"]{3})([^\1])*?\1/gm,klass:"comment"});a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.processes["function"]=Syntax.lib.webLinkProcess("http://docs.python.org/search.html?q=");a.processes.type=Syntax.lib.webLinkProcess("http://docs.python.org/search.html?q="); -a.processes.builtin=Syntax.lib.webLinkProcess("http://docs.python.org/search.html?q=")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.ruby.js b/docs/_components/jquery-syntax/jquery.syntax.brush.ruby.js deleted file mode 100644 index 494e5aa..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.ruby.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.lib.rubyStyleFunction={pattern:/(?:def\s+|\.)([a-z_][a-z0-9_]+)/gi,matches:Syntax.extractMatches({klass:"function"})};Syntax.lib.rubyStyleSymbol={pattern:/([:]?):\w+/g,klass:"constant",matches:function(a,b){return""!=a[1]?[]:[new Syntax.Match(a.index,a[0].length,b,a[0])]}}; -Syntax.register("ruby",function(a){a.push(["private","protected","public"],{klass:"access"});a.push(["self","super","true","false","nil"],{klass:"constant"});a.push({pattern:/(%[\S])(\{[\s\S]*?\})/g,matches:Syntax.extractMatches({klass:"function"},{klass:"constant"})});a.push({pattern:/`[^`]+`/g,klass:"string"});a.push({pattern:/#\{([^\}]*)\}/g,matches:Syntax.extractMatches({brush:"ruby",only:["string"]})});a.push(Syntax.lib.rubyStyleRegularExpression);a.push({pattern:/(@+|\$)[\w]+/g,klass:"variable"}); -a.push(Syntax.lib.camelCaseType);a.push("alias and begin break case class def define_method defined? do else elsif end ensure false for if in module next not or raise redo rescue retry return then throw undef unless until when while yield block_given?".split(" "),{klass:"keyword"});a.push("+*/-&|~!%<=>".split(""),{klass:"operator"});a.push(Syntax.lib.rubyStyleSymbol);a.push(Syntax.lib.perlStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString); -a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.rubyStyleFunction);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.scala.js b/docs/_components/jquery-syntax/jquery.syntax.brush.scala.js deleted file mode 100644 index 7373bc6..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.scala.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.brushes.dependency("scala","xml"); -Syntax.register("scala",function(a){a.push("abstract do finally import object return trait var case catch class else extends for forSome if lazy match new override package private sealed super try type while with yield def final implicit protected throw val".split(" "),{klass:"keyword"});a.push("_ : = => <- <: <% >: # @".split(" "),{klass:"operator"});a.push(["this","null","true","false"],{klass:"constant"});a.push({pattern:/"""[\s\S]*?"""/g,klass:"string"});a.push(Syntax.lib.doubleQuotedString); -a.push({pattern:/(?:def\s+|\.)([a-z_][a-z0-9_]+)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.cStyleFunction);a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.derives("xml")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.smalltalk.js b/docs/_components/jquery-syntax/jquery.syntax.brush.smalltalk.js deleted file mode 100644 index 2ea8804..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.smalltalk.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("smalltalk",function(a){a.push(["self","super","true","false","nil"],{klass:"constant"});a.push(["[","]","|",":=","."],{klass:"operator"});a.push({pattern:/\w+:/g,klass:"function"});a.push(Syntax.lib.camelCaseType);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.sql.js b/docs/_components/jquery-syntax/jquery.syntax.brush.sql.js deleted file mode 100644 index e4c938b..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.sql.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.lib.sqlStyleComment={pattern:/-- .*$/gm,klass:"comment",allow:["href"]}; -Syntax.register("sql",function(a){a.push("= != < > <= >= + - * / %".split(" "),{klass:"operator"});a.push(Syntax.lib.sqlStyleComment);a.push("A ABORT ABS ABSOLUTE ACCESS ACTION ADA ADD ADMIN AFTER AGGREGATE ALIAS ALL ALLOCATE ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARE ARRAY AS ASC ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTRIBUTE ATTRIBUTES AUDIT AUTHORIZATION AUTO_INCREMENT AVG AVG_ROW_LENGTH BACKUP BACKWARD BEFORE BEGIN BERNOULLI BETWEEN BIGINT BINARY BIT BIT_LENGTH BITVAR BLOB BOOL BOOLEAN BOTH BREADTH BREAK BROWSE BULK BY C CACHE CALL CALLED CARDINALITY CASCADE CASCADED CASE CAST CATALOG CATALOG_NAME CEIL CEILING CHAIN CHANGE CHAR CHAR_LENGTH CHARACTER CHARACTER_LENGTH CHARACTER_SET_CATALOG CHARACTER_SET_NAME CHARACTER_SET_SCHEMA CHARACTERISTICS CHARACTERS CHECK CHECKED CHECKPOINT CHECKSUM CLASS CLASS_ORIGIN CLOB CLOSE CLUSTER CLUSTERED COALESCE COBOL COLLATE COLLATION COLLATION_CATALOG COLLATION_NAME COLLATION_SCHEMA COLLECT COLUMN COLUMN_NAME COLUMNS COMMAND_FUNCTION COMMAND_FUNCTION_CODE COMMENT COMMIT COMMITTED COMPLETION COMPRESS COMPUTE CONDITION CONDITION_NUMBER CONNECT CONNECTION CONNECTION_NAME CONSTRAINT CONSTRAINT_CATALOG CONSTRAINT_NAME CONSTRAINT_SCHEMA CONSTRAINTS CONSTRUCTOR CONTAINS CONTAINSTABLE CONTINUE CONVERSION CONVERT COPY CORR CORRESPONDING COUNT COVAR_POP COVAR_SAMP CREATE CREATEDB CREATEROLE CREATEUSER CROSS CSV CUBE CUME_DIST CURRENT CURRENT_DATE CURRENT_DEFAULT_TRANSFORM_GROUP CURRENT_PATH CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_TRANSFORM_GROUP_FOR_TYPE CURRENT_USER CURSOR CURSOR_NAME CYCLE DATA DATABASE DATABASES DATE DATETIME DATETIME_INTERVAL_CODE DATETIME_INTERVAL_PRECISION DAY DAY_HOUR DAY_MICROSECOND DAY_MINUTE DAY_SECOND DAYOFMONTH DAYOFWEEK DAYOFYEAR DBCC DEALLOCATE DEC DECIMAL DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINED DEFINER DEGREE DELAY_KEY_WRITE DELAYED DELETE DELIMITER DELIMITERS DENSE_RANK DENY DEPTH DEREF DERIVED DESC DESCRIBE DESCRIPTOR DESTROY DESTRUCTOR DETERMINISTIC DIAGNOSTICS DICTIONARY DISABLE DISCONNECT DISK DISPATCH DISTINCT DISTINCTROW DISTRIBUTED DIV DO DOMAIN DOUBLE DROP DUAL DUMMY DUMP DYNAMIC DYNAMIC_FUNCTION DYNAMIC_FUNCTION_CODE EACH ELEMENT ELSE ELSEIF ENABLE ENCLOSED ENCODING ENCRYPTED END END-EXEC ENUM EQUALS ERRLVL ESCAPE ESCAPED EVERY EXCEPT EXCEPTION EXCLUDE EXCLUDING EXCLUSIVE EXEC EXECUTE EXISTING EXISTS EXIT EXP EXPLAIN EXTERNAL EXTRACT FALSE FETCH FIELDS FILE FILLFACTOR FILTER FINAL FIRST FLOAT FLOAT4 FLOAT8 FLOOR FLUSH FOLLOWING FOR FORCE FOREIGN FORTRAN FORWARD FOUND FREE FREETEXT FREETEXTTABLE FREEZE FROM FULL FULLTEXT FUNCTION FUSION G GENERAL GENERATED GET GLOBAL GO GOTO GRANT GRANTED GRANTS GREATEST GROUP GROUPING HANDLER HAVING HEADER HEAP HIERARCHY HIGH_PRIORITY HOLD HOLDLOCK HOST HOSTS HOUR HOUR_MICROSECOND HOUR_MINUTE HOUR_SECOND IDENTIFIED IDENTITY IDENTITY_INSERT IDENTITYCOL IF IGNORE ILIKE IMMEDIATE IMMUTABLE IMPLEMENTATION IMPLICIT IN INCLUDE INCLUDING INCREMENT INDEX INDICATOR INFILE INFIX INHERIT INHERITS INITIAL INITIALIZE INITIALLY INNER INOUT INPUT INSENSITIVE INSERT INSERT_ID INSTANCE INSTANTIABLE INSTEAD INT INT1 INT2 INT3 INT4 INT8 INTEGER INTERSECT INTERSECTION INTERVAL INTO INVOKER IS ISAM ISNULL ISOLATION ITERATE JOIN K KEY KEY_MEMBER KEY_TYPE KEYS KILL LANCOMPILER LANGUAGE LARGE LAST LAST_INSERT_ID LATERAL LEADING LEAST LEAVE LEFT LENGTH LESS LEVEL LIKE LIMIT LINENO LINES LISTEN LN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCATOR LOCK LOGIN LOGS LONG LONGBLOB LONGTEXT LOOP LOW_PRIORITY LOWER M MAP MATCH MATCHED MAX MAX_ROWS MAXEXTENTS MAXVALUE MEDIUMBLOB MEDIUMINT MEDIUMTEXT MEMBER MERGE MESSAGE_LENGTH MESSAGE_OCTET_LENGTH MESSAGE_TEXT METHOD MIDDLEINT MIN MIN_ROWS MINUS MINUTE MINUTE_MICROSECOND MINUTE_SECOND MINVALUE MLSLABEL MOD MODE MODIFIES MODIFY MODULE MONTH MONTHNAME MORE MOVE MULTISET MUMPS MYISAM NAMES NATIONAL NATURAL NCHAR NCLOB NESTING NEW NEXT NO NO_WRITE_TO_BINLOG NOAUDIT NOCHECK NOCOMPRESS NOCREATEDB NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN NONCLUSTERED NONE NORMALIZE NORMALIZED NOSUPERUSER NOT NOTHING NOTIFY NOTNULL NOWAIT NULL NULLABLE NULLIF NULLS NUMBER NUMERIC OBJECT OCTET_LENGTH OCTETS OF OFF OFFLINE OFFSET OFFSETS OIDS OLD ON ONLINE ONLY OPEN OPENDATASOURCE OPENQUERY OPENROWSET OPENXML OPERATION OPERATOR OPTIMIZE OPTION OPTIONALLY OPTIONS OR ORDER ORDERING ORDINALITY OTHERS OUT OUTER OUTFILE OUTPUT OVER OVERLAPS OVERLAY OVERRIDING OWNER PACK_KEYS PAD PARAMETER PARAMETER_MODE PARAMETER_NAME PARAMETER_ORDINAL_POSITION PARAMETER_SPECIFIC_CATALOG PARAMETER_SPECIFIC_NAME PARAMETER_SPECIFIC_SCHEMA PARAMETERS PARTIAL PARTITION PASCAL PASSWORD PATH PCTFREE PERCENT PERCENT_RANK PERCENTILE_CONT PERCENTILE_DISC PLACING PLAN PLI POSITION POSTFIX POWER PRECEDING PRECISION PREFIX PREORDER PREPARE PREPARED PRESERVE PRIMARY PRINT PRIOR PRIVILEGES PROC PROCEDURAL PROCEDURE PROCESS PROCESSLIST PUBLIC PURGE QUOTE RAID0 RAISERROR RANGE RANK RAW READ READS READTEXT REAL RECHECK RECONFIGURE RECURSIVE REF REFERENCES REFERENCING REGEXP REGR_AVGX REGR_AVGY REGR_COUNT REGR_INTERCEPT REGR_R2 REGR_SLOPE REGR_SXX REGR_SXY REGR_SYY REINDEX RELATIVE RELEASE RELOAD RENAME REPEAT REPEATABLE REPLACE REPLICATION REQUIRE RESET RESIGNAL RESOURCE RESTART RESTORE RESTRICT RESULT RETURN RETURNED_CARDINALITY RETURNED_LENGTH RETURNED_OCTET_LENGTH RETURNED_SQLSTATE RETURNS REVOKE RIGHT RLIKE ROLE ROLLBACK ROLLUP ROUTINE ROUTINE_CATALOG ROUTINE_NAME ROUTINE_SCHEMA ROW ROW_COUNT ROW_NUMBER ROWCOUNT ROWGUIDCOL ROWID ROWNUM ROWS RULE SAVE SAVEPOINT SCALE SCHEMA SCHEMA_NAME SCHEMAS SCOPE SCOPE_CATALOG SCOPE_NAME SCOPE_SCHEMA SCROLL SEARCH SECOND SECOND_MICROSECOND SECTION SECURITY SELECT SELF SENSITIVE SEPARATOR SEQUENCE SERIALIZABLE SERVER_NAME SESSION SESSION_USER SET SETOF SETS SETUSER SHARE SHOW SHUTDOWN SIGNAL SIMILAR SIMPLE SIZE SMALLINT SOME SONAME SOURCE SPACE SPATIAL SPECIFIC SPECIFIC_NAME SPECIFICTYPE SQL SQL_BIG_RESULT SQL_BIG_SELECTS SQL_BIG_TABLES SQL_CALC_FOUND_ROWS SQL_LOG_OFF SQL_LOG_UPDATE SQL_LOW_PRIORITY_UPDATES SQL_SELECT_LIMIT SQL_SMALL_RESULT SQL_WARNINGS SQLCA SQLCODE SQLERROR SQLEXCEPTION SQLSTATE SQLWARNING SQRT SSL STABLE START STARTING STATE STATEMENT STATIC STATISTICS STATUS STDDEV_POP STDDEV_SAMP STDIN STDOUT STORAGE STRAIGHT_JOIN STRICT STRING STRUCTURE STYLE SUBCLASS_ORIGIN SUBLIST SUBMULTISET SUBSTRING SUCCESSFUL SUM SUPERUSER SYMMETRIC SYNONYM SYSDATE SYSID SYSTEM SYSTEM_USER TABLE TABLE_NAME TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TERMINATE TERMINATED TEXT TEXTSIZE THAN THEN TIES TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TINYBLOB TINYINT TINYTEXT TO TOAST TOP TOP_LEVEL_COUNT TRAILING TRAN TRANSACTION TRANSACTION_ACTIVE TRANSACTIONS_COMMITTED TRANSACTIONS_ROLLED_BACK TRANSFORM TRANSFORMS TRANSLATE TRANSLATION TREAT TRIGGER TRIGGER_CATALOG TRIGGER_NAME TRIGGER_SCHEMA TRIM TRUE TRUNCATE TRUSTED TSEQUAL TYPE UESCAPE UID UNBOUNDED UNCOMMITTED UNDER UNDO UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOCK UNNAMED UNNEST UNSIGNED UNTIL UPDATE UPDATETEXT UPPER USAGE USE USER USER_DEFINED_TYPE_CATALOG USER_DEFINED_TYPE_CODE USER_DEFINED_TYPE_NAME USER_DEFINED_TYPE_SCHEMA USING UTC_DATE UTC_TIME UTC_TIMESTAMP VACUUM VALID VALIDATE VALIDATOR VALUE VALUES VAR_POP VAR_SAMP VARBINARY VARCHAR VARCHAR2 VARCHARACTER VARIABLE VARIABLES VARYING VERBOSE VIEW VOLATILE WAITFOR WHEN WHENEVER WHERE WHILE WIDTH_BUCKET WINDOW WITH WITHIN WITHOUT WORK WRITE WRITETEXT X509 XOR YEAR YEAR_MONTH ZEROFILL ZONE".split(" "),{klass:"keyword", -options:"gi"});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.super-collider.js b/docs/_components/jquery-syntax/jquery.syntax.brush.super-collider.js deleted file mode 100644 index e59faa5..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.super-collider.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("super-collider",function(a){a.push(["const","arg","classvar","var"],{klass:"keyword"});a.push("`+@:*/-&|~!%<=>".split(""),{klass:"operator"});a.push("thisFunctionDef thisFunction thisMethod thisProcess thisThread this super true false nil inf".split(" "),{klass:"constant"});a.push(Syntax.lib.camelCaseType);a.push({pattern:/\$(\\)?./g,klass:"constant"});a.push({pattern:/\\[a-z_][a-z0-9_]*/gi,klass:"symbol"});a.push({pattern:/'[^']+'/g,klass:"symbol"});a.push(Syntax.lib.cStyleComment); -a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push({pattern:/(?:\.)([a-z_][a-z0-9_]*)/gi,matches:Syntax.extractMatches({klass:"function"})});a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.swift.js b/docs/_components/jquery-syntax/jquery.syntax.brush.swift.js deleted file mode 100644 index e800c16..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.swift.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("swift",function(a){a.push(["fileprivate","open","private","public"],{klass:"access"});a.push(["self","super","true","false","nil"],{klass:"constant"});a.push({pattern:/`[^`]+`/g,klass:"identifier"});a.push({pattern:/\\\(([^)]*)\)/g,matches:Syntax.extractMatches({brush:"swift",only:["string"]})});a.push(Syntax.lib.camelCaseType);a.push("associatedtype class deinit enum extension fileprivate func import init inout internal let operator private protocol static struct subscript typealias var break case continue default defer do else fallthrough for guard if in repeat return switch where while as catch is rethrows throw throws try _ #available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation associativity convenience dynamic didSet final get infix indirect lazy left mutating none nonmutating optional override postfix precedence prefix Protocol required right set Type unowned weak willSet".split(" "), -{klass:"keyword"});a.push("+ * / - & | ~ ! % < = > ( ) { } [ ] . , : ; = @ # -> ` ? !".split(" "),{klass:"operator"});a.push(Syntax.lib.cStyleComment);a.push(Syntax.lib.cppStyleComment);a.push(Syntax.lib.webLink);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push(Syntax.lib.stringEscape);a.push(Syntax.lib.decimalNumber);a.push(Syntax.lib.hexNumber);a.push(Syntax.lib.cStyleFunction)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.trenni.js b/docs/_components/jquery-syntax/jquery.syntax.brush.trenni.js deleted file mode 100644 index ebd9b5e..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.trenni.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.brushes.dependency("trenni","xml");Syntax.brushes.dependency("trenni","ruby");Syntax.register("trenni",function(a){a.push({pattern:/((<\?r)([\s\S]*?)(\?>))/gm,matches:Syntax.extractMatches({klass:"ruby-tag",allow:["keyword","ruby"]},{klass:"keyword"},{brush:"ruby"},{klass:"keyword"})});a.push({pattern:/((#{)([\s\S]*?)(}))/gm,matches:Syntax.extractMatches({klass:"ruby-tag",allow:["keyword","ruby"]},{klass:"keyword"},{brush:"ruby"},{klass:"keyword"})});a.derives("xml")}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.xml.js b/docs/_components/jquery-syntax/jquery.syntax.brush.xml.js deleted file mode 100644 index a3ba225..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.xml.js +++ /dev/null @@ -1,4 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.lib.xmlEntity={pattern:/&\w+;/g,klass:"entity"};Syntax.lib.xmlPercentEscape={pattern:/(%[0-9a-f]{2})/gi,klass:"percent-escape",only:["string"]}; -Syntax.register("xml-tag",function(a){a.push({pattern:/<\/?((?:[^:\s>]+:)?)([^\s>]+)(\s[^>]*)?\/?>/g,matches:Syntax.extractMatches({klass:"namespace"},{klass:"tag-name"})});a.push({pattern:/([^=\s]+)=(".*?"|'.*?'|[^\s>]+)/g,matches:Syntax.extractMatches({klass:"attribute",only:["tag"]},{klass:"string",only:["tag"]})});a.push(Syntax.lib.xmlEntity);a.push(Syntax.lib.xmlPercentEscape);a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString)}); -Syntax.register("xml",function(a){a.push({pattern:/()/gm,matches:Syntax.extractMatches({klass:"cdata",allow:["cdata-content","cdata-tag"]},{klass:"cdata-tag"},{klass:"cdata-content"},{klass:"cdata-tag"})});a.push(Syntax.lib.xmlComment);a.push({pattern:/<[^>\-\s]([^>'"!\/;\?@\[\]^`\{\}\|]|"[^"]*"|'[^']')*[\/?]?>/g,brush:"xml-tag"});a.push(Syntax.lib.xmlEntity);a.push(Syntax.lib.xmlPercentEscape);a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.brush.yaml.js b/docs/_components/jquery-syntax/jquery.syntax.brush.yaml.js deleted file mode 100644 index 0b04f4e..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.brush.yaml.js +++ /dev/null @@ -1,2 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.register("yaml",function(a){a.push({pattern:/^\s*#.*$/gm,klass:"comment",allow:["href"]});a.push(Syntax.lib.singleQuotedString);a.push(Syntax.lib.doubleQuotedString);a.push({pattern:/(&|\*)[a-z0-9]+/gi,klass:"constant"});a.push({pattern:/(.*?):/gi,matches:Syntax.extractMatches({klass:"keyword"})});a.push(Syntax.lib.webLink)}); diff --git a/docs/_components/jquery-syntax/jquery.syntax.cache.js b/docs/_components/jquery-syntax/jquery.syntax.cache.js deleted file mode 100644 index 5e0a209..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.cache.js +++ /dev/null @@ -1,7 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Syntax.alias("apache",[]);Syntax.alias("applescript",[]);Syntax.alias("assembly",["asm"]);Syntax.alias("bash-script",[]);Syntax.alias("bash",[]);Syntax.alias("basic",["vb"]);Syntax.alias("clang",["cpp","c++","c","objective-c"]);Syntax.alias("csharp",["c-sharp","c#"]);Syntax.alias("css",[]);Syntax.alias("diff",["patch"]);Syntax.alias("go",[]);Syntax.alias("haskell",[]);Syntax.alias("html",[]);Syntax.alias("io",[]);Syntax.alias("java",[]);Syntax.alias("javascript",["js","actionscript"]); -Syntax.alias("kai",[]);Syntax.alias("lisp",["scheme","clojure"]);Syntax.alias("lua",[]);Syntax.alias("nginx",[]);Syntax.alias("ocaml",["ml","sml","fsharp"]);Syntax.alias("ooc",[]);Syntax.alias("pascal",["delphi"]);Syntax.alias("perl5",[]);Syntax.alias("php-script",[]);Syntax.alias("php",[]);Syntax.alias("plain",["text"]);Syntax.alias("protobuf",[]);Syntax.alias("python",[]);Syntax.alias("ruby",[]);Syntax.alias("scala",[]);Syntax.alias("smalltalk",[]);Syntax.alias("sql",[]); -Syntax.alias("super-collider",["sc"]);Syntax.alias("swift",[]);Syntax.alias("html",[]);Syntax.alias("xml",[]);Syntax.alias("yaml",[]);Syntax.styles["jquery.syntax.brush.apache"]=["base/jquery.syntax.brush.apache.css"];Syntax.styles["jquery.syntax.brush.applescript"]=["base/jquery.syntax.brush.applescript.css"];Syntax.styles["jquery.syntax.brush.assembly"]=["base/jquery.syntax.brush.assembly.css"];Syntax.styles["jquery.syntax.brush.bash-script"]=["base/jquery.syntax.brush.bash-script.css"]; -Syntax.styles["jquery.syntax.brush.bash"]=["base/jquery.syntax.brush.bash.css"];Syntax.styles["jquery.syntax.brush.clang"]=["base/jquery.syntax.brush.clang.css"];Syntax.styles["jquery.syntax.brush.css"]=["base/jquery.syntax.brush.css.css"];Syntax.styles["jquery.syntax.brush.diff"]=["base/jquery.syntax.brush.diff.css"];Syntax.styles["jquery.syntax.brush.html"]=["base/jquery.syntax.brush.html.css"];Syntax.styles["jquery.syntax.brush.ocaml"]=["base/jquery.syntax.brush.ocaml.css"]; -Syntax.styles["jquery.syntax.brush.protobuf"]=["base/jquery.syntax.brush.protobuf.css"];Syntax.styles["jquery.syntax.brush.python"]=["base/jquery.syntax.brush.python.css"];Syntax.styles["jquery.syntax.brush.ruby"]=["base/jquery.syntax.brush.ruby.css"];Syntax.styles["jquery.syntax.brush.xml"]=["base/jquery.syntax.brush.xml.css"];Syntax.styles["jquery.syntax.core"]=["base/jquery.syntax.core.css","bright/jquery.syntax.core.css","paper/jquery.syntax.core.css"];Syntax.styles["jquery.syntax.editor"]=["base/jquery.syntax.editor.css"]; -Syntax.themes.base=[];Syntax.themes.bright=["base"];Syntax.themes.paper=["base"]; diff --git a/docs/_components/jquery-syntax/jquery.syntax.core.js b/docs/_components/jquery-syntax/jquery.syntax.core.js deleted file mode 100644 index a3025ce..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.core.js +++ /dev/null @@ -1,34 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -RegExp.prototype.indexOf||(RegExp.indexOf=function(b,a){return b[0].indexOf(b[a])+b.index});RegExp.prototype.escape||(RegExp.escape=function(b){return b.replace(/[\-\[\]{}()*+?.\\\^$|,#\s]/g,"\\$&")});String.prototype.repeat||(String.prototype.repeat=function(b){return Array(b+1).join(this)});Syntax.innerText=function(b){if(!b)return"";if("BR"==b.nodeName)return"\n";if(b.textContent)var a=b.textContent;else document.body.innerText&&(a=b.innerText);return a.replace(/\r\n?/g,"\n")}; -Syntax.extractTextFromSelection=function(b){for(var a="",c=0;c)/gm,klass:"comment"};Syntax.lib.webLink={pattern:/\w+:\/\/[\w\-.\/?%&=@:;#]*/g,klass:"href"};Syntax.lib.hexNumber={pattern:/\b0x[0-9a-fA-F]+/g,klass:"constant"};Syntax.lib.decimalNumber={pattern:/\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/g,klass:"constant"}; -Syntax.lib.doubleQuotedString={pattern:/"([^\\"\n]|\\.)*"/g,klass:"string"};Syntax.lib.singleQuotedString={pattern:/'([^\\'\n]|\\.)*'/g,klass:"string"};Syntax.lib.multiLineDoubleQuotedString={pattern:/"([^\\"]|\\.)*"/g,klass:"string"};Syntax.lib.multiLineSingleQuotedString={pattern:/'([^\\']|\\.)*'/g,klass:"string"};Syntax.lib.stringEscape={pattern:/\\./g,klass:"escape",only:["string"]}; -Syntax.Match=function(b,a,c,d){this.offset=b;this.endOffset=b+a;this.length=a;this.expression=c;this.value=d;this.children=[];this.next=this.parent=null};Syntax.Match.prototype.shift=function(b,a){this.adjust(b,null,a);for(var c=0;c=this.offset&&b.endOffset<=this.endOffset};Syntax.Match.defaultReduceCallback=function(b,a){"string"===typeof b&&(b=document.createTextNode(b));a.appendChild(b)}; -Syntax.Match.prototype.reduce=function(b,a){var c=this.offset;var d=this.expression&&this.expression.element?this.expression.element.cloneNode(!1):document.createElement("span");b=b||Syntax.Match.defaultReduceCallback;this.expression&&this.expression.klass&&(0this.endOffset&&console.log("Syntax Warning: Start position "+c+" exceeds end of value "+this.endOffset);a&&(d=a(d,this));return d}; -Syntax.Match.prototype.canContain=function(b){return b.expression.force?!0:this.complete?!1:b.expression.only?!0:"undefined"===typeof this.expression.allow||jQuery.isArray(this.expression.disallow)&&-1!==jQuery.inArray(b.expression.klass,this.expression.disallow)?!1:"*"===this.expression.allow||jQuery.isArray(this.expression.allow)&&-1!==jQuery.inArray(b.expression.klass,this.expression.allow)?!0:!1}; -Syntax.Match.prototype.canHaveChild=function(b){if(b=b.expression.only){for(var a=this;null!==a;){if(-1!==jQuery.inArray(a.expression.klass,b))return!0;if((a=a.parent)&&a.complete)break}return!1}return!0};Syntax.Match.prototype._splice=function(b,a){return this.canHaveChild(a)?(this.children.splice(b,0,a),a.parent=this,a.expression.owner||(a.expression.owner=this.expression.owner),this):null}; -Syntax.Match.prototype.insert=function(b,a){if(!this.contains(b))return null;if(a){a=this;for(var c=0;c=c.endOffset)){if(c.contains(b))return c._insert(b);b=b.bisectAtOffsets([c.offset,c.endOffset]);b[0]&&this._splice(a,b[0]);b[1]&&c.insert(b[1]);if(b[2])b=b[2];else return this}}this._splice(this.children.length,b)}; -Syntax.Match.prototype.bisectAtOffsets=function(b){var a=[],c=this.offset,d=null,e=jQuery.merge([],this.children);b=b.slice(0);b.push(this.endOffset);b.sort(function(a,b){return a-b});for(var f=0;fthis.endOffset)break;g").attr("href",this.innerHTML).text(this.innerHTML))});e(f,a,b,d)})}; -Syntax.extractBrushName=function(b){b=b.toLowerCase();var a=b.match(/(brush|language)-([\S]+)/);if(a)return a[2];b=b.split(/ /);if(-1!==jQuery.inArray("syntax",b))for(a=0;aa.start&&d>a.start;)if(this.current.lines[c-1]==b.lines[d-1])--c,--d;else break;a.end=d;a.originalEnd=c;for(a.difference=b.lines.length-this.current.lines.length;0');c.append(b.children());var d=new Syntax.Editor(c.get(0)),f=function(b){var c=d.getClientState(),e=d.updateChangedLines();0>e.difference&&0').append(c)}; diff --git a/docs/_components/jquery-syntax/jquery.syntax.js b/docs/_components/jquery-syntax/jquery.syntax.js deleted file mode 100644 index 2349764..0000000 --- a/docs/_components/jquery-syntax/jquery.syntax.js +++ /dev/null @@ -1,8 +0,0 @@ -// This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License. -Function.prototype.bind||(Function.prototype.bind=function(a){var b=Array.prototype.slice.call(arguments,1),c=this;return function(){return c.apply(a,b)}});function ResourceLoader(a){this.dependencies={};this.loading={};this.loader=a}ResourceLoader.prototype._finish=function(a){var b=this.dependencies[a];if(b){a=this._loaded.bind(this,a);for(var c=0;c");jQuery("head").append(b);Syntax.cacheStyleSheets||(a=a+"?"+Math.random());b.attr({rel:"stylesheet", -type:"text/css",href:a})},getScript:function(a,b){var c=document.createElement("script");c.onreadystatechange=function(){!this.onload||"loaded"!=this.readyState&&"complete"!=this.readyState||(this.onload(),this.onload=null)};c.onload=b;c.type="text/javascript";Syntax.cacheScripts||(a=a+"?"+Math.random());c.src=a;document.getElementsByTagName("head")[0].appendChild(c)},getResource:function(a,b,c){Syntax.detectRoot();a=a+"."+b;if(b=this.styles[a])for(var d=0;d");jQuery("head").append(b);Syntax.cacheStyleSheets||(a=a+"?"+Math.random());b.attr({rel:"stylesheet", -type:"text/css",href:a})},getScript:function(a,b){var c=document.createElement("script");c.onreadystatechange=function(){!this.onload||"loaded"!=this.readyState&&"complete"!=this.readyState||(this.onload(),this.onload=null)};c.onload=b;c.type="text/javascript";Syntax.cacheScripts||(a=a+"?"+Math.random());c.src=a;document.getElementsByTagName("head")[0].appendChild(c)},getResource:function(a,b,c){Syntax.detectRoot();a=a+"."+b;if(b=this.styles[a])for(var d=0;d span:nth-child(odd) { - background-color: rgba(0, 0, 0, 0.05); } diff --git a/docs/_components/jquery-syntax/paper/theme.js b/docs/_components/jquery-syntax/paper/theme.js deleted file mode 100644 index ea7e7ce..0000000 --- a/docs/_components/jquery-syntax/paper/theme.js +++ /dev/null @@ -1 +0,0 @@ -Syntax.themes["paper"] = ["base"] diff --git a/docs/_components/jquery/jquery.js b/docs/_components/jquery/jquery.js deleted file mode 100644 index 5093733..0000000 --- a/docs/_components/jquery/jquery.js +++ /dev/null @@ -1,10872 +0,0 @@ -/*! - * jQuery JavaScript Library v3.5.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-04T22:49Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -var document = window.document; - - - - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.5.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.5 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-03-14 - */ -( function( window ) { -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rhtml = /HTML$/i, - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - var high = "0x" + escape.slice( 1 ) - 0x10000; - - return nonHex ? - - // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android<4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem.namespaceURI, - docElem = ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ - - }; - }, - - "CHILD": function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - "not": markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element (issue #299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - "has": markFunction( function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - } ), - - "contains": markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); - }, - - "selected": function( elem ) { - - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo( function() { - return [ 0 ]; - } ), - - "last": createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - "even": createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "odd": createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var documentElement = document.documentElement; - - - - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { - - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var notAsync, result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); - this[ type ](); - result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - return result.value; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { - - // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - delegateType: delegateType - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px"; - tr.style.height = "1px"; - trChild.style.height = "9px"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( - dataPriv.get( cur, "events" ) || Object.create( null ) - )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( _i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script - if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - var i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - - - - - - - - -

Getting Started

- -

This guide explains how to use async-container to build basic scalable systems.

-

Installation

-

Add the gem to your project:

-
$ bundle add async-container
-
-

Core Concepts

-

async-container has several core concepts:

-
    -
  • class Async::Container::Forked and class Async::Container::Threaded are used to manage one or more child processes and threads respectively for parallel execution. While threads share the address space which can reduce overall memory usage, processes have better isolation and fault tolerance.
  • -
  • class Async::Container::Controller manages one or more containers and handles graceful restarts. Containers should be implemented in such a way that multiple containers can be running at the same time.
  • -
-

Containers

-

A container represents a set of child processes (or threads) which are doing work for you.

-
require 'async/container'
-
-Async.logger.debug!
-
-container = Async::Container.new
-
-container.async do |task|
-	task.logger.debug "Sleeping..."
-	task.sleep(1)
-	task.logger.debug "Waking up!"
-end
-
-Async.logger.debug "Waiting for container..."
-container.wait
-Async.logger.debug "Finished."
-
-

Controllers

-

The controller provides the life-cycle management for one or more containers of processes. It provides behaviour like starting, restarting, reloading and stopping. You can see some example implementations in Falcon. If the process running the controller receives SIGHUP it will recreate the container gracefully.

-
require 'async/container'
-
-Async.logger.debug!
-
-class Controller < Async::Container::Controller
-	def setup(container)
-		container.async do |task|
-			while true
-				Async.logger.debug("Sleeping...")
-				task.sleep(1)
-			end
-		end
-	end
-end
-
-controller = Controller.new
-
-controller.run
-
-# If you send SIGHUP to this process, it will recreate the container.
-
-

Signal Handling

-

SIGINT is the interrupt signal. The terminal sends it to the foreground process when the user presses ctrl-c. The default behavior is to terminate the process, but it can be caught or ignored. The intention is to provide a mechanism for an orderly, graceful shutdown.

-

SIGQUIT is the dump core signal. The terminal sends it to the foreground process when the user presses ctrl-\. The default behavior is to terminate the process and dump core, but it can be caught or ignored. The intention is to provide a mechanism for the user to abort the process. You can look at SIGINT as "user-initiated happy termination" and SIGQUIT as "user-initiated unhappy termination."

-

SIGTERM is the termination signal. The default behavior is to terminate the process, but it also can be caught or ignored. The intention is to kill the process, gracefully or not, but to first allow it a chance to cleanup.

-

SIGKILL is the kill signal. The only behavior is to kill the process, immediately. As the process cannot catch the signal, it cannot cleanup, and thus this is a signal of last resort.

-

SIGSTOP is the pause signal. The only behavior is to pause the process; the signal cannot be caught or ignored. The shell uses pausing (and its counterpart, resuming via SIGCONT) to implement job control.

-

Integration

-

systemd

-

Install a template file into /etc/systemd/system/:

-
# my-daemon.service
-[Unit]
-Description=My Daemon
-AssertPathExists=/srv/
-
-[Service]
-Type=notify
-WorkingDirectory=/srv/my-daemon
-ExecStart=bundle exec my-daemon
-Nice=5
-
-[Install]
-WantedBy=multi-user.target
-
-
- - - - - \ No newline at end of file diff --git a/docs/guides/index.html b/docs/guides/index.html deleted file mode 100644 index c610cb2..0000000 --- a/docs/guides/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Guides

- -
- -
- - - - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 301225f..0000000 --- a/docs/index.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container

Provides containers which implement parallelism for clients and servers.

-

Development Status

-

Features

-
    -
  • Supports multi-process, multi-thread and hybrid containers.
  • -
  • Automatic scalability based on physical hardware.
  • -
  • Direct integration with systemd using $NOTIFY_SOCKET.
  • -
  • Internal process readiness protocol for handling state changes.
  • -
  • Automatic restart of failed processes.
  • -
-

Usage

-

Please browse the source code index or refer to the guides below.

- -

Getting Started

- -

This guide explains how to use async-container to build basic scalable systems.

- - -

Contributing

-

We welcome contributions to this project.

-
    -
  1. Fork it.
  2. -
  3. Create your feature branch (git checkout -b my-new-feature).
  4. -
  5. Commit your changes (git commit -am 'Add some feature').
  6. -
  7. Push to the branch (git push origin my-new-feature).
  8. -
  9. Create new Pull Request.
  10. -
-

License

-

Copyright, 2017, by Samuel G. D. Williams.

-

Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions:

-

The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software.

-

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE.

-
- - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Channel/index.html b/docs/source/Async/Container/Channel/index.html deleted file mode 100644 index da42353..0000000 --- a/docs/source/Async/Container/Channel/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Channel

- -
-

Provides a basic multi-thread/multi-process uni-directional communication channel.

- - -

Definitions

- -

def initialize

Initialize the channel using a pipe.

-
-

Implementation

-
def initialize
-	@in, @out = ::IO.pipe
-end
-

attr :in

The input end of the pipe.

-
-

Signature

-
- attribute IO
-
-

attr :out

The output end of the pipe.

-
-

Signature

-
- attribute IO
-
-

def close_read

Close the input end of the pipe.

-
-

Implementation

-
def close_read
-	@in.close
-end
-

def close_write

Close the output end of the pipe.

-
-

Implementation

-
def close_write
-	@out.close
-end
-

def close

Close both ends of the pipe.

-
-

Implementation

-
def close
-	close_read
-	close_write
-end
-

def receive

Receive an object from the pipe. -Internally, prefers to receive newline formatted JSON, otherwise returns a hash table with a single key :line which contains the line of data that could not be parsed as JSON.

-
-

Signature

-
- returns Hash
-
-
-

Implementation

-
def receive
-	if data = @in.gets
-		begin
-			return JSON.parse(data, symbolize_names: true)
-		rescue
-			return {line: data}
-		end
-	end
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Controller/index.html b/docs/source/Async/Container/Controller/index.html deleted file mode 100644 index af20725..0000000 --- a/docs/source/Async/Container/Controller/index.html +++ /dev/null @@ -1,277 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Controller

- -
-

Manages the life-cycle of one or more containers in order to support a persistent system. -e.g. a web server, job server or some other long running system.

- - -

Definitions

- -

def initialize(notify: Notify.open!)

Initialize the controller.

-
-

Signature

-
- parameter notify Notify::Client

A client used for process readiness notifications.

-
-
-
-

Implementation

-
def initialize(notify: Notify.open!)
-	@container = nil
-	
-	if @notify = notify
-		@notify.status!("Initializing...")
-	end
-	
-	@signals = {}
-	
-	trap(SIGHUP) do
-		self.restart
-	end
-end
-

def state_string

The state of the controller.

-
-

Signature

-
- returns String
-
-
-

Implementation

-
def state_string
-	if running?
-		"running"
-	else
-		"stopped"
-	end
-end
-

def to_s

A human readable representation of the controller.

-
-

Signature

-
- returns String
-
-
-

Implementation

-
def to_s
-	"#{self.class} #{state_string}"
-end
-

def trap(signal, &block)

Trap the specified signal.

-
-

Implementation

-
def trap(signal, &block)
-	@signals[signal] = block
-end
-

attr :container

The current container being managed by the controller.

-

def create_container

Create a container for the controller. -Can be overridden by a sub-class.

-
-

Signature

-
- returns Generic

A specific container instance to use.

-
-
-
-

Implementation

-
def create_container
-	Container.new
-end
-

def running?

Whether the controller has a running container.

-
-

Signature

-
- returns Boolean
-
-
-

Implementation

-
def running?
-	!!@container
-end
-

def wait

Wait for the underlying container to start.

-
-

Implementation

-
def wait
-	@container&.wait
-end
-

def setup(container)

Spawn container instances into the given container. -Should be overridden by a sub-class.

-
-

Signature

-
- parameter container Generic

The container, generally from #create_container.

-
-
-
-

Implementation

-
def setup(container)
-	# Don't do this, otherwise calling super is risky for sub-classes:
-	# raise NotImplementedError, "Container setup is must be implemented in derived class!"
-end
-

def start

Start the container unless it's already running.

-
-

Implementation

-
def start
-	self.restart unless @container
-end
-

def stop(graceful = true)

Stop the container if it's running.

-
-

Signature

-
- parameter graceful Boolean

Whether to give the children instances time to shut down or to kill them immediately.

-
-
-
-

Implementation

-
def stop(graceful = true)
-	@container&.stop(graceful)
-	@container = nil
-end
-

def restart

Restart the container. A new container is created, and if successful, any old container is terminated gracefully.

-
-

Implementation

-
def restart
-	if @container
-		@notify&.restarting!
-		
-		Async.logger.debug(self) {"Restarting container..."}
-	else
-		Async.logger.debug(self) {"Starting container..."}
-	end
-	
-	container = self.create_container
-	
-	begin
-		self.setup(container)
-	rescue
-		@notify&.error!($!.to_s)
-		
-		raise SetupError, container
-	end
-	
-	# Wait for all child processes to enter the ready state.
-	Async.logger.debug(self, "Waiting for startup...")
-	container.wait_until_ready
-	Async.logger.debug(self, "Finished startup.")
-	
-	if container.failed?
-		@notify&.error!($!.to_s)
-		
-		container.stop
-		
-		raise SetupError, container
-	end
-	
-	# Make this swap as atomic as possible:
-	old_container = @container
-	@container = container
-	
-	Async.logger.debug(self, "Stopping old container...")
-	old_container&.stop
-	@notify&.ready!
-rescue
-	# If we are leaving this function with an exception, try to kill the container:
-	container&.stop(false)
-	
-	raise
-end
-

def reload

Reload the existing container. Children instances will be reloaded using SIGHUP.

-
-

Implementation

-
def reload
-	@notify&.reloading!
-	
-	Async.logger.info(self) {"Reloading container: #{@container}..."}
-	
-	begin
-		self.setup(@container)
-	rescue
-		raise SetupError, container
-	end
-	
-	# Wait for all child processes to enter the ready state.
-	Async.logger.debug(self, "Waiting for startup...")
-	@container.wait_until_ready
-	Async.logger.debug(self, "Finished startup.")
-	
-	if @container.failed?
-		@notify.error!("Container failed!")
-		
-		raise SetupError, @container
-	else
-		@notify&.ready!
-	end
-end
-

def run

Enter the controller run loop, trapping SIGINT and SIGTERM.

-
-

Implementation

-
def run
-	# I thought this was the default... but it doesn't always raise an exception unless you do this explicitly.
-	interrupt_action = Signal.trap(:INT) do
-		raise Interrupt
-	end
-	
-	terminate_action = Signal.trap(:TERM) do
-		raise Terminate
-	end
-	
-	self.start
-	
-	while @container&.running?
-		begin
-			@container.wait
-		rescue SignalException => exception
-			if handler = @signals[exception.signo]
-				begin
-					handler.call
-				rescue SetupError => error
-					Async.logger.error(self) {error}
-				end
-			else
-				raise
-			end
-		end
-	end
-rescue Interrupt
-	self.stop(true)
-rescue Terminate
-	self.stop(false)
-ensure
-	self.stop(true)
-	
-	# Restore the interrupt handler:
-	Signal.trap(:INT, interrupt_action)
-	Signal.trap(:TERM, terminate_action)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Error/index.html b/docs/source/Async/Container/Error/index.html deleted file mode 100644 index 70ff007..0000000 --- a/docs/source/Async/Container/Error/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Error

- -
- - -

Definitions

- -
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Forked/index.html b/docs/source/Async/Container/Forked/index.html deleted file mode 100644 index 826ac56..0000000 --- a/docs/source/Async/Container/Forked/index.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Forked

- -
-

A multi-process container which uses Async::Container::Process.fork.

- - -

Definitions

- -

def self.multiprocess?

Indicates that this is a multi-process container.

-
-

Implementation

-
def self.multiprocess?
-	true
-end
-

def start(name, &block)

Start a named child process and execute the provided block in it.

-
-

Signature

-
- parameter name String

The name (title) of the child process.

-
- parameter block Proc

The block to execute in the child process.

-
-
-
-

Implementation

-
def start(name, &block)
-	Process.fork(name: name, &block)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Generic/index.html b/docs/source/Async/Container/Generic/index.html deleted file mode 100644 index 664280f..0000000 --- a/docs/source/Async/Container/Generic/index.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Generic

- -
-

A base class for implementing containers.

- - -

Definitions

- -

def run(count: Container.processor_count, **options, &block)

Run multiple instances of the same block in the container.

-
-

Signature

-
- parameter count Integer

The number of instances to start.

-
-
-
-

Implementation

-
def run(count: Container.processor_count, **options, &block)
-	count.times do
-		spawn(**options, &block)
-	end
-	
-	return self
-end
-

def to_s

A human readable representation of the container.

-
-

Signature

-
- returns String
-
-
-

Implementation

-
def to_s
-	"#{self.class} with #{@statistics.spawns} spawns and #{@statistics.failures} failures."
-end
-

def [] key

Look up a child process by key. -A key could be a symbol, a file path, or something else which the child instance represents.

-
-

Implementation

-
def [] key
-	@keyed[key]&.value
-end
-

attr :statistics

Statistics relating to the behavior of children instances.

-
-

Signature

-
- attribute Statistics
-
-

def failed?

Whether any failures have occurred within the container.

-
-

Signature

-
- returns Boolean
-
-
-

Implementation

-
def failed?
-	@statistics.failed?
-end
-

def running?

Whether the container has running children instances.

-
-

Implementation

-
def running?
-	@group.running?
-end
-

def sleep(duration = nil)

Sleep until some state change occurs.

-
-

Signature

-
- parameter duration Numeric

the maximum amount of time to sleep for.

-
-
-
-

Implementation

-
def sleep(duration = nil)
-	@group.sleep(duration)
-end
-

def wait

Wait until all spawned tasks are completed.

-
-

Implementation

-
def wait
-	@group.wait
-end
-

def status?(flag)

Returns true if all children instances have the specified status flag set. -e.g. :ready. -This state is updated by the process readiness protocol mechanism. See class Async::Container::Notify::Client for more details.

-
-

Signature

-
- returns Boolean
-
-
-

Implementation

-
def status?(flag)
-	# This also returns true if all processes have exited/failed:
-	@state.all?{|_, state| state[flag]}
-end
-

def wait_until_ready

Wait until all the children instances have indicated that they are ready.

-
-

Signature

-
- returns Boolean

The children all became ready.

-
-
-
-

Implementation

-
def wait_until_ready
-	while true
-		Async.logger.debug(self) do |buffer|
-			buffer.puts "Waiting for ready:"
-			@state.each do |child, state|
-				buffer.puts "\t#{child.class}: #{state.inspect}"
-			end
-		end
-		
-		self.sleep
-		
-		if self.status?(:ready)
-			return true
-		end
-	end
-end
-

def stop(timeout = true)

Stop the children instances.

-
-

Signature

-
- parameter timeout Boolean | Numeric

Whether to stop gracefully, or a specific timeout.

-
-
-
-

Implementation

-
def stop(timeout = true)
-	@running = false
-	@group.stop(timeout)
-	
-	if @group.running?
-		Async.logger.warn(self) {"Group is still running after stopping it!"}
-	end
-ensure
-	@running = true
-end
-

def spawn(name: nil, restart: false, key: nil, &block)

Spawn a child instance into the container.

-
-

Signature

-
- parameter name String

The name of the child instance.

-
- parameter restart Boolean

Whether to restart the child instance if it fails.

-
- parameter key Symbol

A key used for reloading child instances.

-
-
-
-

Implementation

-
def spawn(name: nil, restart: false, key: nil, &block)
-	name ||= UNNAMED
-	
-	if mark?(key)
-		Async.logger.debug(self) {"Reusing existing child for #{key}: #{name}"}
-		return false
-	end
-	
-	@statistics.spawn!
-	
-	Fiber.new do
-		while @running
-			child = self.start(name, &block)
-			
-			state = insert(key, child)
-			
-			begin
-				status = @group.wait_for(child) do |message|
-					state.update(message)
-				end
-			ensure
-				delete(key, child)
-			end
-			
-			if status.success?
-				Async.logger.info(self) {"#{child} exited with #{status}"}
-			else
-				@statistics.failure!
-				Async.logger.error(self) {status}
-			end
-			
-			if restart
-				@statistics.restart!
-			else
-				break
-			end
-		end
-	# ensure
-	# 	Async.logger.error(self) {$!} if $!
-	end.resume
-	
-	return true
-end
-

def async(**options, &block)

-

Signature

-
- deprecated

Please use Async::Container::Generic#spawn or Async::Container::Generic.run instead.

-
-
-
-

Implementation

-
def async(**options, &block)
-	spawn(**options) do |instance|
-		Async::Reactor.run(instance, &block)
-	end
-end
-

def reload

Reload the container's keyed instances.

-
-

Implementation

-
def reload
-	@keyed.each_value(&:clear!)
-	
-	yield
-	
-	dirty = false
-	
-	@keyed.delete_if do |key, value|
-		value.stop? && (dirty = true)
-	end
-	
-	return dirty
-end
-

def mark?(key)

Mark the container's keyed instance which ensures that it won't be discarded.

-
-

Implementation

-
def mark?(key)
-	if key
-		if value = @keyed[key]
-			value.mark!
-			
-			return true
-		end
-	end
-	
-	return false
-end
-

def key?(key)

Whether a child instance exists for the given key.

-
-

Implementation

-
def key?(key)
-	if key
-		@keyed.key?(key)
-	end
-end
-

def insert(key, child)

Register the child (value) as running.

-
-

Implementation

-
def insert(key, child)
-	if key
-		@keyed[key] = Keyed.new(key, child)
-	end
-	
-	state = {}
-	
-	@state[child] = state
-	
-	return state
-end
-

def delete(key, child)

Clear the child (value) as running.

-
-

Implementation

-
def delete(key, child)
-	if key
-		@keyed.delete(key)
-	end
-	
-	@state.delete(child)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Hybrid/index.html b/docs/source/Async/Container/Hybrid/index.html deleted file mode 100644 index c777d7d..0000000 --- a/docs/source/Async/Container/Hybrid/index.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Hybrid

- -
-

Provides a hybrid multi-process multi-thread container.

- - -

Definitions

- -

def run(count: nil, forks: nil, threads: nil, **options, &block)

Run multiple instances of the same block in the container.

-
-

Signature

-
- parameter count Integer

The number of instances to start.

-
- parameter forks Integer

The number of processes to fork.

-
- parameter threads Integer

the number of threads to start.

-
-
-
-

Implementation

-
def run(count: nil, forks: nil, threads: nil, **options, &block)
-	processor_count = Container.processor_count
-	count ||= processor_count ** 2
-	forks ||= [processor_count, count].min
-	threads = (count / forks).ceil
-	
-	forks.times do
-		self.spawn(**options) do |instance|
-			container = Threaded.new
-			
-			container.run(count: threads, **options, &block)
-			
-			container.wait_until_ready
-			instance.ready!
-			
-			container.wait
-		rescue Async::Container::Terminate
-			# Stop it immediately:
-			container.stop(false)
-		ensure
-			# Stop it gracefully (also code path for Interrupt):
-			container.stop
-		end
-	end
-	
-	return self
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Keyed/index.html b/docs/source/Async/Container/Keyed/index.html deleted file mode 100644 index 8420a0e..0000000 --- a/docs/source/Async/Container/Keyed/index.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Keyed

- -
-

Tracks a key/value pair such that unmarked keys can be identified and cleaned up. -This helps implement persistent processes that start up child processes per directory or configuration file. If those directories and/or configuration files are removed, the child process can then be cleaned up automatically, because those key/value pairs will not be marked when reloading the container.

- - -

Definitions

- -

attr :key

The key. Normally a symbol or a file-system path.

-
-

Signature

-
- attribute Object
-
-

attr :value

The value. Normally a child instance of some sort.

-
-

Signature

-
- attribute Object
-
-

def marked?

Has the instance been marked?

-
-

Signature

-
- returns Boolean
-
-
-

Implementation

-
def marked?
-	@marked
-end
-

def mark!

Mark the instance. This will indiciate that the value is still in use/active.

-
-

Implementation

-
def mark!
-	@marked = true
-end
-

def clear!

Clear the instance. This is normally done before reloading a container.

-
-

Implementation

-
def clear!
-	@marked = false
-end
-

def stop?

Stop the instance if it was not marked.

-
-

Implementation

-
def stop?
-	unless @marked
-		@value.stop
-		return true
-	end
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Client/index.html b/docs/source/Async/Container/Notify/Client/index.html deleted file mode 100644 index 83c5b5f..0000000 --- a/docs/source/Async/Container/Notify/Client/index.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Notify::Client

- -
- - -

Definitions

- -

def ready!(**message)

Notify the parent controller that the child has become ready, with a brief status message.

-
-

Implementation

-
def ready!(**message)
-	send(ready: true, **message)
-end
-

def reloading!(**message)

Notify the parent controller that the child is reloading.

-
-

Implementation

-
def reloading!(**message)
-	message[:ready] = false
-	message[:reloading] = true
-	message[:status] ||= "Reloading..."
-	
-	send(**message)
-end
-

def restarting!(**message)

Notify the parent controller that the child is restarting.

-
-

Implementation

-
def restarting!(**message)
-	message[:ready] = false
-	message[:reloading] = true
-	message[:status] ||= "Restarting..."
-	
-	send(**message)
-end
-

def stopping!(**message)

Notify the parent controller that the child is stopping.

-
-

Implementation

-
def stopping!(**message)
-	message[:stopping] = true
-	
-	send(**message)
-end
-

def status!(text)

Notify the parent controller of a status change.

-
-

Implementation

-
def status!(text)
-	send(status: text)
-end
-

def error!(text, **message)

Notify the parent controller of an error condition.

-
-

Implementation

-
def error!(text, **message)
-	send(status: text, **message)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Console/index.html b/docs/source/Async/Container/Notify/Console/index.html deleted file mode 100644 index cdbf4e6..0000000 --- a/docs/source/Async/Container/Notify/Console/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Notify::Console

- -
-

Implements a general process readiness protocol with output to the local console.

- - -

Definitions

- -

def self.open!(logger = ::Console.logger)

Open a notification client attached to the current console.

-
-

Implementation

-
def self.open!(logger = ::Console.logger)
-	self.new(logger)
-end
-

def initialize(logger)

Initialize the notification client.

-
-

Signature

-
- parameter logger Console::Logger

The console logger instance to send messages to.

-
-
-
-

Implementation

-
def initialize(logger)
-	@logger = logger
-end
-

def send(level: :debug, **message)

Send a message to the console.

-
-

Implementation

-
def send(level: :debug, **message)
-	@logger.send(level, self) {message}
-end
-

def error!(text, **message)

Send an error message to the console.

-
-

Implementation

-
def error!(text, **message)
-	send(status: text, level: :error, **message)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Pipe/index.html b/docs/source/Async/Container/Notify/Pipe/index.html deleted file mode 100644 index 0896678..0000000 --- a/docs/source/Async/Container/Notify/Pipe/index.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Notify::Pipe

- -
-

Implements a process readiness protocol using an inherited pipe file descriptor.

- - -

Definitions

- -

NOTIFY_PIPE = 'NOTIFY_PIPE'

The environment variable key which contains the pipe file descriptor.

-

def self.open!(environment = ENV)

Open a notification client attached to the current NOTIFY_PIPE = 'NOTIFY_PIPE' if possible.

-
-

Implementation

-
def self.open!(environment = ENV)
-	if descriptor = environment.delete(NOTIFY_PIPE)
-		self.new(::IO.for_fd(descriptor.to_i))
-	end
-rescue Errno::EBADF => error
-	Async.logger.error(self) {error}
-	
-	return nil
-end
-

def initialize(io)

Initialize the notification client.

-
-

Signature

-
- parameter io IO

An IO instance used for sending messages.

-
-
-
-

Implementation

-
def initialize(io)
-	@io = io
-end
-

def before_spawn(arguments, options)

Inserts or duplicates the environment given an argument array. -Sets or clears it in a way that is suitable for ::Process.spawn.

-
-

Implementation

-
def before_spawn(arguments, options)
-	environment = environment_for(arguments)
-	
-	# Use `notify_pipe` option if specified:
-	if notify_pipe = options.delete(:notify_pipe)
-		options[notify_pipe] = @io
-		environment[NOTIFY_PIPE] = notify_pipe.to_s
-	
-	# Use stdout if it's not redirected:
-	# This can cause issues if the user expects stdout to be connected to a terminal.
-	# elsif !options.key?(:out)
-	# 	options[:out] = @io
-	# 	environment[NOTIFY_PIPE] = "1"
-	
-	# Use fileno 3 if it's available:
-	elsif !options.key?(3)
-		options[3] = @io
-		environment[NOTIFY_PIPE] = "3"
-	
-	# Otherwise, give up!
-	else
-		raise ArgumentError, "Please specify valid file descriptor for notify_pipe!"
-	end
-end
-

def send(**message)

Formats the message using JSON and sends it to the parent controller. -This is suitable for use with class Async::Container::Channel.

-
-

Implementation

-
def send(**message)
-	data = ::JSON.dump(message)
-	
-	@io.puts(data)
-	@io.flush
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Server/Context/index.html b/docs/source/Async/Container/Notify/Server/Context/index.html deleted file mode 100644 index 26496ab..0000000 --- a/docs/source/Async/Container/Notify/Server/Context/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Notify::Server::Context

- -
- - -

Definitions

- -
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Server/index.html b/docs/source/Async/Container/Notify/Server/index.html deleted file mode 100644 index 7053af1..0000000 --- a/docs/source/Async/Container/Notify/Server/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Notify::Server

- -
- -

Nested

- - - -

Definitions

- -
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/Socket/index.html b/docs/source/Async/Container/Notify/Socket/index.html deleted file mode 100644 index 9ee3e94..0000000 --- a/docs/source/Async/Container/Notify/Socket/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Notify::Socket

- -
-

Implements the systemd NOTIFY_SOCKET process readiness protocol. -See https://www.freedesktop.org/software/systemd/man/sd_notify.html for more details of the underlying protocol.

- - -

Definitions

- -

NOTIFY_SOCKET = 'NOTIFY_SOCKET'

The name of the environment variable which contains the path to the notification socket.

-

MAXIMUM_MESSAGE_SIZE = 4096

The maximum allowed size of the UDP message.

-

def self.open!(environment = ENV)

Open a notification client attached to the current NOTIFY_SOCKET = 'NOTIFY_SOCKET' if possible.

-
-

Implementation

-
def self.open!(environment = ENV)
-	if path = environment.delete(NOTIFY_SOCKET)
-		self.new(path)
-	end
-end
-

def initialize(path)

Initialize the notification client.

-
-

Signature

-
- parameter path String

The path to the UNIX socket used for sending messages to the process manager.

-
-
-
-

Implementation

-
def initialize(path)
-	@path = path
-	@endpoint = IO::Endpoint.unix(path, ::Socket::SOCK_DGRAM)
-end
-

def dump(message)

Dump a message in the format requied by sd_notify.

-
-

Signature

-
- parameter message Hash

Keys and values should be string convertible objects. Values which are true/false are converted to 1/0 respectively.

-
-
-
-

Implementation

-
def dump(message)
-	buffer = String.new
-	
-	message.each do |key, value|
-		# Conversions required by NOTIFY_SOCKET specifications:
-		if value == true
-			value = 1
-		elsif value == false
-			value = 0
-		end
-		
-		buffer << "#{key.to_s.upcase}=#{value}\n"
-	end
-	
-	return buffer
-end
-

def send(**message)

Send the given message.

-
-

Signature

-
- parameter message Hash
-
-
-

Implementation

-
def send(**message)
-	data = dump(message)
-	
-	if data.bytesize > MAXIMUM_MESSAGE_SIZE
-		raise ArgumentError, "Message length #{message.bytesize} exceeds #{MAXIMUM_MESSAGE_SIZE}: #{message.inspect}"
-	end
-	
-	Sync do
-		@endpoint.connect do |peer|
-			peer.send(data)
-		end
-	end
-end
-

def error!(text, **message)

Send the specified error. -sd_notify requires an errno key, which defaults to -1 to indicate a generic error.

-
-

Implementation

-
def error!(text, **message)
-	message[:errno] ||= -1
-	
-	send(status: text, **message)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Notify/index.html b/docs/source/Async/Container/Notify/index.html deleted file mode 100644 index c5da848..0000000 --- a/docs/source/Async/Container/Notify/index.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Notify

- -
-

Handles the details of several process readiness protocols.

- -

Nested

- - - -

Definitions

- -

def self.open!

Select the best available notification client. -We cache the client on a per-process basis. Because that's the relevant scope for process readiness protocols.

-
-

Implementation

-
def self.open!
-	@client ||= (
-		Pipe.open! ||
-		Socket.open! ||
-		Console.open!
-	)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Process/Instance/index.html b/docs/source/Async/Container/Process/Instance/index.html deleted file mode 100644 index 76223a9..0000000 --- a/docs/source/Async/Container/Process/Instance/index.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Process::Instance

- -
-

Represents a running child process from the point of view of the child process.

- - -

Definitions

- -

def self.for(process)

Wrap an instance around the class Async::Container::Process instance from within the forked child.

-
-

Signature

-
- parameter process Process

The process intance to wrap.

-
-
-
-

Implementation

-
def self.for(process)
-	instance = self.new(process.out)
-	
-	# The child process won't be reading from the channel:
-	process.close_read
-	
-	instance.name = process.name
-	
-	return instance
-end
-

def name= value

Set the process title to the specified value.

-
-

Signature

-
- parameter value String

The name of the process.

-
-
-
-

Implementation

-
def name= value
-	if @name = value
-		::Process.setproctitle(@name)
-	end
-end
-

def name

The name of the process.

-
-

Signature

-
- returns String
-
-
-

Implementation

-
def name
-	@name
-end
-

def exec(*arguments, ready: true, **options)

Replace the current child process with a different one. Forwards arguments and options to ::Process.exec. -This method replaces the child process with the new executable, thus this method never returns.

-
-

Implementation

-
def exec(*arguments, ready: true, **options)
-	if ready
-		self.ready!(status: "(exec)") if ready
-	else
-		self.before_spawn(arguments, options)
-	end
-	
-	# TODO prefer **options... but it doesn't support redirections on < 2.7
-	::Process.exec(*arguments, options)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Process/index.html b/docs/source/Async/Container/Process/index.html deleted file mode 100644 index 8249eef..0000000 --- a/docs/source/Async/Container/Process/index.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Process

- -
-

Represents a running child process from the point of view of the parent container.

- -

Nested

- - - -

Definitions

- -

def self.fork(**options)

Fork a child process appropriate for a container.

-
-

Signature

-
- returns Process
-
-
-

Implementation

-
def self.fork(**options)
-	self.new(**options) do |process|
-		::Process.fork do
-			Signal.trap(:INT) {raise Interrupt}
-			Signal.trap(:TERM) {raise Terminate}
-			
-			begin
-				yield Instance.for(process)
-			rescue Interrupt
-				# Graceful exit.
-			rescue Exception => error
-				Async.logger.error(self) {error}
-				
-				exit!(1)
-			end
-		end
-	end
-end
-

def initialize(name: nil)

Initialize the process.

-
-

Signature

-
- parameter name String

The name to use for the child process.

-
-
-
-

Implementation

-
def initialize(name: nil)
-	super()
-	
-	@name = name
-	@status = nil
-	@pid = nil
-	
-	@pid = yield(self)
-	
-	# The parent process won't be writing to the channel:
-	self.close_write
-end
-

def name= value

Set the name of the process. -Invokes ::Process.setproctitle if invoked in the child process.

-
-

Implementation

-
def name= value
-	@name = value
-	
-	# If we are the child process:
-	::Process.setproctitle(@name) if @pid.nil?
-end
-

attr :name

The name of the process.

-
-

Signature

-
- attribute String
-
-

def to_s

A human readable representation of the process.

-
-

Signature

-
- returns String
-
-
-

Implementation

-
def to_s
-	"\#<#{self.class} #{@name}>"
-end
-

def close

Invoke #terminate! and then #wait for the child process to exit.

-
-

Implementation

-
def close
-	self.terminate!
-	self.wait
-ensure
-	super
-end
-

def interrupt!

Send SIGINT to the child process.

-
-

Implementation

-
def interrupt!
-	unless @status
-		::Process.kill(:INT, @pid)
-	end
-end
-

def terminate!

Send SIGTERM to the child process.

-
-

Implementation

-
def terminate!
-	unless @status
-		::Process.kill(:TERM, @pid)
-	end
-end
-

def wait

Wait for the child process to exit.

-
-

Signature

-
- returns ::Process::Status

The process exit status.

-
-
-
-

Implementation

-
def wait
-	if @pid && @status.nil?
-		_, @status = ::Process.wait2(@pid, ::Process::WNOHANG)
-		
-		if @status.nil?
-			sleep(0.01)
-			_, @status = ::Process.wait2(@pid, ::Process::WNOHANG)
-		end
-		
-		if @status.nil?
-			Async.logger.warn(self) {"Process #{@pid} is blocking, has it exited?"}
-			_, @status = ::Process.wait2(@pid)
-		end
-	end
-	
-	return @status
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/SetupError/index.html b/docs/source/Async/Container/SetupError/index.html deleted file mode 100644 index e331ac6..0000000 --- a/docs/source/Async/Container/SetupError/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::SetupError

- -
-

Represents the error which occured when a container failed to start up correctly.

- - -

Definitions

- -

attr :container

The container that failed.

-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Statistics/index.html b/docs/source/Async/Container/Statistics/index.html deleted file mode 100644 index 46cc3ea..0000000 --- a/docs/source/Async/Container/Statistics/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Statistics

- -
-

Tracks various statistics relating to child instances in a container.

- - -

Definitions

- -

attr :spawns

How many child instances have been spawned.

-
-

Signature

-
- attribute Integer
-
-

attr :restarts

How many child instances have been restarted.

-
-

Signature

-
- attribute Integer
-
-

attr :failures

How many child instances have failed.

-
-

Signature

-
- attribute Integer
-
-

def spawn!

Increment the number of spawns by 1.

-
-

Implementation

-
def spawn!
-	@spawns += 1
-end
-

def restart!

Increment the number of restarts by 1.

-
-

Implementation

-
def restart!
-	@restarts += 1
-end
-

def failure!

Increment the number of failures by 1.

-
-

Implementation

-
def failure!
-	@failures += 1
-end
-

def failed?

Whether there have been any failures.

-
-

Signature

-
- returns Boolean

If the failure count is greater than 0.

-
-
-
-

Implementation

-
def failed?
-	@failures > 0
-end
-

def << other

Append another statistics instance into this one.

-
-

Signature

-
- parameter other Statistics

The statistics to append.

-
-
-
-

Implementation

-
def << other
-	@spawns += other.spawns
-	@restarts += other.restarts
-	@failures += other.failures
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Terminate/index.html b/docs/source/Async/Container/Terminate/index.html deleted file mode 100644 index 03ad481..0000000 --- a/docs/source/Async/Container/Terminate/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Terminate

- -
-

Similar to Interrupt = ::Interrupt, but represents SIGTERM.

- - -

Definitions

- -
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Thread/Exit/index.html b/docs/source/Async/Container/Thread/Exit/index.html deleted file mode 100644 index 6ea7f74..0000000 --- a/docs/source/Async/Container/Thread/Exit/index.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Thread::Exit

- -
-

Used to propagate the exit status of a child process invoked by Async::Container::Thread::Instance#exec.

- - -

Definitions

- -

def initialize(status)

Initialize the exit status.

-
-

Signature

-
- parameter status ::Process::Status

The process exit status.

-
-
-
-

Implementation

-
def initialize(status)
-	@status = status
-end
-

attr :status

The process exit status.

-
-

Signature

-
- attribute ::Process::Status
-
-

def error

The process exit status if it was an error.

-
-

Signature

-
- returns ::Process::Status | Nil
-
-
-

Implementation

-
def error
-	unless status.success?
-		status
-	end
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Thread/Instance/index.html b/docs/source/Async/Container/Thread/Instance/index.html deleted file mode 100644 index 9fa0931..0000000 --- a/docs/source/Async/Container/Thread/Instance/index.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Thread::Instance

- -
-

Represents a running child thread from the point of view of the child thread.

- - -

Definitions

- -

def self.for(thread)

Wrap an instance around the class Async::Container::Thread instance from within the threaded child.

-
-

Signature

-
- parameter thread Thread

The thread intance to wrap.

-
-
-
-

Implementation

-
def self.for(thread)
-	instance = self.new(thread.out)
-	
-	return instance
-end
-

def name= value

Set the name of the thread.

-
-

Signature

-
- parameter value String

The name to set.

-
-
-
-

Implementation

-
def name= value
-	@thread.name = value
-end
-

def name

Get the name of the thread.

-
-

Signature

-
- returns String
-
-
-

Implementation

-
def name
-	@thread.name
-end
-

def exec(*arguments, ready: true, **options)

Execute a child process using ::Process.spawn. In order to simulate ::Process.exec, an class Async::Container::Thread::Exit instance is raised to propagage exit status. -This creates the illusion that this method does not return (normally).

-
-

Implementation

-
def exec(*arguments, ready: true, **options)
-	if ready
-		self.ready!(status: "(spawn)") if ready
-	else
-		self.before_spawn(arguments, options)
-	end
-	
-	begin
-		# TODO prefer **options... but it doesn't support redirections on < 2.7
-		pid = ::Process.spawn(*arguments, options)
-	ensure
-		_, status = ::Process.wait2(pid)
-		
-		raise Exit, status
-	end
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Thread/Status/index.html b/docs/source/Async/Container/Thread/Status/index.html deleted file mode 100644 index e40e233..0000000 --- a/docs/source/Async/Container/Thread/Status/index.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Thread::Status

- -
-

A pseudo exit-status wrapper.

- - -

Definitions

- -

def initialize(error = nil)

Initialise the status.

-
-

Signature

-
- parameter error ::Process::Status

The exit status of the child thread.

-
-
-
-

Implementation

-
def initialize(error = nil)
-	@error = error
-end
-

def success?

Whether the status represents a successful outcome.

-
-

Signature

-
- returns Boolean
-
-
-

Implementation

-
def success?
-	@error.nil?
-end
-

def to_s

A human readable representation of the status.

-
-

Implementation

-
def to_s
-	"\#<#{self.class} #{success? ? "success" : "failure"}>"
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Thread/index.html b/docs/source/Async/Container/Thread/index.html deleted file mode 100644 index ebf415e..0000000 --- a/docs/source/Async/Container/Thread/index.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Thread

- -
-

Represents a running child thread from the point of view of the parent container.

- -

Nested

- - - -

Definitions

- -

def initialize(name: nil)

Initialize the thread.

-
-

Signature

-
- parameter name String

The name to use for the child thread.

-
-
-
-

Implementation

-
def initialize(name: nil)
-	super()
-	
-	@status = nil
-	
-	@thread = yield(self)
-	@thread.report_on_exception = false
-	@thread.name = name
-	
-	@waiter = ::Thread.new do
-		begin
-			@thread.join
-		rescue Exit => exit
-			finished(exit.error)
-		rescue Interrupt
-			# Graceful shutdown.
-			finished
-		rescue Exception => error
-			finished(error)
-		else
-			finished
-		end
-	end
-end
-

def name= value

Set the name of the thread.

-
-

Signature

-
- parameter value String

The name to set.

-
-
-
-

Implementation

-
def name= value
-	@thread.name = value
-end
-

def name

Get the name of the thread.

-
-

Signature

-
- returns String
-
-
-

Implementation

-
def name
-	@thread.name
-end
-

def to_s

A human readable representation of the thread.

-
-

Signature

-
- returns String
-
-
-

Implementation

-
def to_s
-	"\#<#{self.class} #{@thread.name}>"
-end
-

def close

Invoke #terminate! and then #wait for the child thread to exit.

-
-

Implementation

-
def close
-	self.terminate!
-	self.wait
-ensure
-	super
-end
-

def interrupt!

Raise Interrupt = ::Interrupt in the child thread.

-
-

Implementation

-
def interrupt!
-	@thread.raise(Interrupt)
-end
-

def terminate!

Raise class Async::Container::Terminate in the child thread.

-
-

Implementation

-
def terminate!
-	@thread.raise(Terminate)
-end
-

def wait

Wait for the thread to exit and return he exit status.

-
-

Signature

-
- returns Status
-
-
-

Implementation

-
def wait
-	if @waiter
-		@waiter.join
-		@waiter = nil
-	end
-	
-	return @status
-end
-

def finished(error = nil)

Invoked by the @waiter thread to indicate the outcome of the child thread.

-
-

Implementation

-
def finished(error = nil)
-	if error
-		Async.logger.error(self) {error}
-	end
-	
-	@status = Status.new(error)
-	self.close_write
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/Threaded/index.html b/docs/source/Async/Container/Threaded/index.html deleted file mode 100644 index bc8fbef..0000000 --- a/docs/source/Async/Container/Threaded/index.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container::Threaded

- -
-

A multi-thread container which uses Async::Container::Thread.fork.

- - -

Definitions

- -

def self.multiprocess?

Indicates that this is not a multi-process container.

-
-

Implementation

-
def self.multiprocess?
-	false
-end
-

def start(name, &block)

Start a named child thread and execute the provided block in it.

-
-

Signature

-
- parameter name String

The name (title) of the child process.

-
- parameter block Proc

The block to execute in the child process.

-
-
-
-

Implementation

-
def start(name, &block)
-	Thread.fork(name: name, &block)
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/Container/index.html b/docs/source/Async/Container/index.html deleted file mode 100644 index d09397c..0000000 --- a/docs/source/Async/Container/index.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async::Container

- -
- -

Nested

- - - -

Definitions

- -

def self.fork?

Whether the underlying process supports fork.

-
-

Signature

-
- returns Boolean
-
-
-

Implementation

-
def self.fork?
-	::Process.respond_to?(:fork) && ::Process.respond_to?(:setpgid)
-end
-

def self.best_container_class

Determins the best container class based on the underlying Ruby implementation. -Some platforms, including JRuby, don't support fork. Applications which just want a reasonable default can use this method.

-
-

Signature

-
- returns Class
-
-
-

Implementation

-
def self.best_container_class
-	if fork?
-		return Forked
-	else
-		return Threaded
-	end
-end
-

def self.new(*arguments, **options)

Create an instance of the best container class.

-
-

Signature

-
- returns Generic

Typically an instance of either class Async::Container::Forked or class Async::Container::Threaded containers.

-
-
-
-

Implementation

-
def self.new(*arguments, **options)
-	best_container_class.new(*arguments, **options)
-end
-

ASYNC_CONTAINER_PROCESSOR_COUNT = 'ASYNC_CONTAINER_PROCESSOR_COUNT'

An environment variable key to override .processor_count.

-

def self.processor_count(env = ENV)

The processor count which may be used for the default number of container threads/processes. You can override the value provided by the system by specifying the ASYNC_CONTAINER_PROCESSOR_COUNT environment variable.

-
-

Signature

-
- returns Integer

The number of hardware processors which can run threads/processes simultaneously.

-
- raises RuntimeError

If the process count is invalid.

-
-
-
-

Implementation

-
def self.processor_count(env = ENV)
-	count = env.fetch(ASYNC_CONTAINER_PROCESSOR_COUNT) do
-		Etc.nprocessors rescue 1
-	end.to_i
-	
-	if count < 1
-		raise RuntimeError, "Invalid processor count #{count}!"
-	end
-	
-	return count
-end
-
- - - - - \ No newline at end of file diff --git a/docs/source/Async/index.html b/docs/source/Async/index.html deleted file mode 100644 index 0fd672a..0000000 --- a/docs/source/Async/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Async

- -
- -

Nested

- - - -

Definitions

- -
- - - - - \ No newline at end of file diff --git a/docs/source/index.html b/docs/source/index.html deleted file mode 100644 index e98f4c4..0000000 --- a/docs/source/index.html +++ /dev/null @@ -1,963 +0,0 @@ - - - - - Codestin Search App - - - - - - - - - - - - - - - - - -

Source

- -
-
- - - - - \ No newline at end of file From 2b01214dfe34de7f9610fd25b5a4ac4edde44109 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 May 2021 02:33:29 +1200 Subject: [PATCH 061/166] Patch version bump. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 0421618..9df67a1 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.8" + VERSION = "0.16.9" end end From 4cb9ff6de499eec42aa3a509289c16fa6a2c9fef Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 May 2021 14:14:30 +1200 Subject: [PATCH 062/166] Ensure fiber is blocking so that forked process starts in blocking context. --- lib/async/container/generic.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 03e8bba..6d9b203 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -161,7 +161,7 @@ def spawn(name: nil, restart: false, key: nil, &block) @statistics.spawn! - Fiber.new do + fiber do while @running child = self.start(name, &block) @@ -270,6 +270,18 @@ def delete(key, child) @state.delete(child) end + + private + + if Fiber.respond_to?(:blocking) + def fiber(&block) + Fiber.new(blocking: true, &block) + end + else + def fiber(&block) + Fiber.new(&block) + end + end end end end From 4df95712213d51e0e9ceb47953b40feca03a7f18 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 May 2021 14:36:23 +1200 Subject: [PATCH 063/166] Patch version bump. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 9df67a1..56d562b 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.9" + VERSION = "0.16.10" end end From 29b8ef382a291b228bf42643900adde33d661b07 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 May 2021 14:47:19 +1200 Subject: [PATCH 064/166] Fix `blocking: true` and add spec. --- lib/async/container/generic.rb | 2 +- spec/async/container/shared_examples.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 6d9b203..a691e2b 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -273,7 +273,7 @@ def delete(key, child) private - if Fiber.respond_to?(:blocking) + if Fiber.respond_to?(:blocking?) def fiber(&block) Fiber.new(blocking: true, &block) end diff --git a/spec/async/container/shared_examples.rb b/spec/async/container/shared_examples.rb index 9bd9a31..0df3589 100644 --- a/spec/async/container/shared_examples.rb +++ b/spec/async/container/shared_examples.rb @@ -48,6 +48,19 @@ subject.wait end + it "should be blocking", if: Fiber.respond_to?(:blocking?) do + input, output = IO.pipe + + subject.spawn do + output.write(Fiber.blocking? != false) + end + + subject.wait + + output.close + expect(input.read).to be == "true" + end + describe '#sleep' do it "can sleep for a short time" do subject.spawn do From bf3fb68f08ceb1ce8d616369dd5d47cace357f6b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 May 2021 14:53:58 +1200 Subject: [PATCH 065/166] Patch version bump. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 56d562b..9deeb34 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.10" + VERSION = "0.16.11" end end From 08839efd0e63c86fb47ce2d74d77ad181261296d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 16 Jul 2021 19:25:01 +1200 Subject: [PATCH 066/166] Add async stable-v1 and head to test matrix. --- .github/workflows/async-head.yml | 29 +++++++++++++++++++++++++++++ .github/workflows/async-v1.yml | 29 +++++++++++++++++++++++++++++ async-container.gemspec | 4 ++-- gems/async-head.rb | 7 +++++++ gems/async-v1.rb | 7 +++++++ 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/async-head.yml create mode 100644 .github/workflows/async-v1.yml create mode 100644 gems/async-head.rb create mode 100644 gems/async-v1.rb diff --git a/.github/workflows/async-head.yml b/.github/workflows/async-head.yml new file mode 100644 index 0000000..8b8541a --- /dev/null +++ b/.github/workflows/async-head.yml @@ -0,0 +1,29 @@ +name: Async head + +on: [push, pull_request] + +jobs: + test: + runs-on: ${{matrix.os}}-latest + + strategy: + matrix: + os: + - ubuntu + + ruby: + - head + + env: + BUNDLE_GEMFILE: gems/async-head.rb + + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + bundler-cache: true + + - name: Run tests + timeout-minutes: 5 + run: bundle exec rspec diff --git a/.github/workflows/async-v1.yml b/.github/workflows/async-v1.yml new file mode 100644 index 0000000..e013d50 --- /dev/null +++ b/.github/workflows/async-v1.yml @@ -0,0 +1,29 @@ +name: Async v1 + +on: [push, pull_request] + +jobs: + test: + runs-on: ${{matrix.os}}-latest + + strategy: + matrix: + os: + - ubuntu + + ruby: + - 2.7 + + env: + BUNDLE_GEMFILE: gems/async-v1.rb + + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + bundler-cache: true + + - name: Run tests + timeout-minutes: 5 + run: bundle exec rspec diff --git a/async-container.gemspec b/async-container.gemspec index 97f5c42..52b2383 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -15,8 +15,8 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 2.5" - spec.add_dependency "async", "~> 1.0" - spec.add_dependency "async-io", "~> 1.26" + spec.add_dependency "async" + spec.add_dependency "async-io" spec.add_development_dependency "async-rspec", "~> 1.1" spec.add_development_dependency "bundler" diff --git a/gems/async-head.rb b/gems/async-head.rb new file mode 100644 index 0000000..50345f7 --- /dev/null +++ b/gems/async-head.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gemspec path: "../" + +gem 'async', git: "https://github.com/socketry/async" diff --git a/gems/async-v1.rb b/gems/async-v1.rb new file mode 100644 index 0000000..cc6730c --- /dev/null +++ b/gems/async-v1.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gemspec path: "../" + +gem 'async', '~> 1.0' From 84cd1f223ad91e6f62981d9b52fc15cbb82e0dde Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 16 Jul 2021 19:30:35 +1200 Subject: [PATCH 067/166] Patch version bump. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 9deeb34..c0756aa 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -22,6 +22,6 @@ module Async module Container - VERSION = "0.16.11" + VERSION = "0.16.12" end end From 70d797242cba1210702f4562fa51b217087666d0 Mon Sep 17 00:00:00 2001 From: Anton Sozontov Date: Sun, 9 Oct 2022 01:29:55 +0300 Subject: [PATCH 068/166] Don't override given threads number (#22) --- lib/async/container/hybrid.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/hybrid.rb b/lib/async/container/hybrid.rb index 28fb47e..4a27836 100644 --- a/lib/async/container/hybrid.rb +++ b/lib/async/container/hybrid.rb @@ -35,7 +35,7 @@ def run(count: nil, forks: nil, threads: nil, **options, &block) processor_count = Container.processor_count count ||= processor_count ** 2 forks ||= [processor_count, count].min - threads = (count / forks).ceil + threads ||= (count / forks).ceil forks.times do self.spawn(**options) do |instance| From 875c179e6c84494024f0cb1f3e9a787c51a35ceb Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 5 Nov 2022 22:54:01 +1300 Subject: [PATCH 069/166] Modernize gem and move tests to sus. --- .../{async-head.yml => async-head.yaml} | 2 +- .../workflows/{async-v1.yml => async-v1.yaml} | 2 +- .github/workflows/coverage.yaml | 57 +++++++ .github/workflows/documentation.yaml | 61 ++++++++ .github/workflows/documentation.yml | 32 ---- .github/workflows/test-external.yaml | 36 +++++ .../workflows/{development.yml => test.yaml} | 23 +-- .gitignore | 13 +- .mailmap | 2 + .rspec | 3 - .yardopts | 1 - README.md | 49 ------ async-container.gemspec | 13 +- config/external.yaml | 3 + config/sus.rb | 9 ++ examples/async.rb | 3 + examples/channel.rb | 4 + examples/channels/client.rb | 4 + examples/container.rb | 4 + examples/isolate.rb | 4 +- examples/minimal.rb | 4 + examples/queue/server.rb | 4 + examples/test.rb | 3 + examples/threads.rb | 3 + examples/title.rb | 3 + examples/udppipe.rb | 3 + fixtures/a_container.rb | 77 +++++++++ fixtures/timer_quantum.rb | 46 ++++++ gems.rb | 3 + gems/async-head.rb | 3 + gems/async-v1.rb | 3 + .../getting-started/{README.md => readme.md} | 0 lib/async/container.rb | 21 +-- lib/async/container/best.rb | 21 +-- lib/async/container/channel.rb | 21 +-- lib/async/container/controller.rb | 21 +-- lib/async/container/error.rb | 21 +-- lib/async/container/forked.rb | 21 +-- lib/async/container/generic.rb | 21 +-- lib/async/container/group.rb | 23 +-- lib/async/container/hybrid.rb | 22 +-- lib/async/container/keyed.rb | 21 +-- lib/async/container/notify.rb | 21 +-- lib/async/container/notify/client.rb | 21 +-- lib/async/container/notify/console.rb | 21 +-- lib/async/container/notify/pipe.rb | 22 +-- lib/async/container/notify/server.rb | 22 +-- lib/async/container/notify/socket.rb | 21 +-- lib/async/container/process.rb | 21 +-- lib/async/container/statistics.rb | 21 +-- lib/async/container/thread.rb | 22 +-- lib/async/container/threaded.rb | 21 +-- lib/async/container/version.rb | 21 +-- license.md | 25 +++ readme.md | 27 ++++ release.cert | 28 ++++ spec/async/container/controller_spec.rb | 148 ------------------ spec/async/container/forked_spec.rb | 61 -------- spec/async/container/hybrid_spec.rb | 36 ----- spec/async/container/notify/pipe_spec.rb | 48 ------ spec/async/container/notify_spec.rb | 56 ------- spec/async/container/shared_examples.rb | 93 ----------- spec/async/container/threaded_spec.rb | 35 ----- spec/async/container_spec.rb | 57 ------- spec/spec_helper.rb | 21 --- test/async/container.rb | 41 +++++ .../dots.rb => test/async/container/.dots.rb | 3 + test/async/container/controller.rb | 140 +++++++++++++++++ test/async/container/forked.rb | 47 ++++++ test/async/container/hybrid.rb | 19 +++ test/async/container/notify.rb | 41 +++++ .../async/container/notify/.notify.rb | 3 + test/async/container/notify/pipe.rb | 32 ++++ test/async/container/threaded.rb | 16 ++ 74 files changed, 835 insertions(+), 1066 deletions(-) rename .github/workflows/{async-head.yml => async-head.yaml} (94%) rename .github/workflows/{async-v1.yml => async-v1.yaml} (94%) create mode 100644 .github/workflows/coverage.yaml create mode 100644 .github/workflows/documentation.yaml delete mode 100644 .github/workflows/documentation.yml create mode 100644 .github/workflows/test-external.yaml rename .github/workflows/{development.yml => test.yaml} (72%) create mode 100644 .mailmap delete mode 100644 .rspec delete mode 100644 .yardopts delete mode 100644 README.md create mode 100644 config/external.yaml create mode 100644 config/sus.rb create mode 100644 fixtures/a_container.rb create mode 100644 fixtures/timer_quantum.rb rename guides/getting-started/{README.md => readme.md} (100%) create mode 100644 license.md create mode 100644 readme.md create mode 100644 release.cert delete mode 100644 spec/async/container/controller_spec.rb delete mode 100644 spec/async/container/forked_spec.rb delete mode 100644 spec/async/container/hybrid_spec.rb delete mode 100644 spec/async/container/notify/pipe_spec.rb delete mode 100644 spec/async/container/notify_spec.rb delete mode 100644 spec/async/container/shared_examples.rb delete mode 100644 spec/async/container/threaded_spec.rb delete mode 100644 spec/async/container_spec.rb delete mode 100644 spec/spec_helper.rb create mode 100644 test/async/container.rb rename spec/async/container/dots.rb => test/async/container/.dots.rb (87%) create mode 100644 test/async/container/controller.rb create mode 100644 test/async/container/forked.rb create mode 100644 test/async/container/hybrid.rb create mode 100644 test/async/container/notify.rb rename spec/async/container/notify/notify.rb => test/async/container/notify/.notify.rb (81%) create mode 100644 test/async/container/notify/pipe.rb create mode 100644 test/async/container/threaded.rb diff --git a/.github/workflows/async-head.yml b/.github/workflows/async-head.yaml similarity index 94% rename from .github/workflows/async-head.yml rename to .github/workflows/async-head.yaml index 8b8541a..d8dfd74 100644 --- a/.github/workflows/async-head.yml +++ b/.github/workflows/async-head.yaml @@ -26,4 +26,4 @@ jobs: - name: Run tests timeout-minutes: 5 - run: bundle exec rspec + run: bundle exec sus diff --git a/.github/workflows/async-v1.yml b/.github/workflows/async-v1.yaml similarity index 94% rename from .github/workflows/async-v1.yml rename to .github/workflows/async-v1.yaml index e013d50..a57a7f7 100644 --- a/.github/workflows/async-v1.yml +++ b/.github/workflows/async-v1.yaml @@ -26,4 +26,4 @@ jobs: - name: Run tests timeout-minutes: 5 - run: bundle exec rspec + run: bundle exec sus diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml new file mode 100644 index 0000000..5e9afd7 --- /dev/null +++ b/.github/workflows/coverage.yaml @@ -0,0 +1,57 @@ +name: Coverage + +on: [push, pull_request] + +permissions: + contents: read + +env: + CONSOLE_OUTPUT: XTerm + COVERAGE: PartialSummary + +jobs: + test: + name: ${{matrix.ruby}} on ${{matrix.os}} + runs-on: ${{matrix.os}}-latest + + strategy: + matrix: + os: + - ubuntu + - macos + + ruby: + - "3.1" + + steps: + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + bundler-cache: true + + - name: Run tests + timeout-minutes: 5 + run: bundle exec bake test + + - uses: actions/upload-artifact@v2 + with: + name: coverage-${{matrix.os}}-${{matrix.ruby}} + path: .covered.db + + validate: + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1" + bundler-cache: true + + - uses: actions/download-artifact@v3 + + - name: Validate coverage + timeout-minutes: 5 + run: bundle exec bake covered:validate --paths */.covered.db \; diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml new file mode 100644 index 0000000..0e435e2 --- /dev/null +++ b/.github/workflows/documentation.yaml @@ -0,0 +1,61 @@ +name: Documentation + +on: + push: + branches: + - main + + # Allows you to run this workflow manually from the Actions tab: + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages: +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment: +concurrency: + group: "pages" + cancel-in-progress: true + +env: + CONSOLE_OUTPUT: XTerm + BUNDLE_WITH: maintenance + +jobs: + generate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1" + bundler-cache: true + + - name: Installing packages + run: sudo apt-get install wget + + - name: Generate documentation + timeout-minutes: 5 + run: bundle exec bake utopia:project:static --force no + + - name: Upload documentation artifact + uses: actions/upload-pages-artifact@v1 + with: + path: docs + + deploy: + runs-on: ubuntu-latest + + environment: + name: github-pages + url: ${{steps.deployment.outputs.page_url}} + + needs: generate + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index 4a4a663..0000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Documentation - -on: - push: - branches: - - master - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - env: - BUNDLE_WITH: maintenance - with: - ruby-version: 2.7 - bundler-cache: true - - - name: Installing packages - run: sudo apt-get install wget - - - name: Generate documentation - timeout-minutes: 5 - run: bundle exec bake utopia:project:static - - - name: Deploy documentation - uses: JamesIves/github-pages-deploy-action@4.0.0 - with: - branch: docs - folder: docs diff --git a/.github/workflows/test-external.yaml b/.github/workflows/test-external.yaml new file mode 100644 index 0000000..ff2ced8 --- /dev/null +++ b/.github/workflows/test-external.yaml @@ -0,0 +1,36 @@ +name: Test External + +on: [push, pull_request] + +permissions: + contents: read + +env: + CONSOLE_OUTPUT: XTerm + +jobs: + test: + name: ${{matrix.ruby}} on ${{matrix.os}} + runs-on: ${{matrix.os}}-latest + + strategy: + matrix: + os: + - ubuntu + - macos + + ruby: + - "2.7" + - "3.0" + - "3.1" + + steps: + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + bundler-cache: true + + - name: Run tests + timeout-minutes: 10 + run: bundle exec bake test:external diff --git a/.github/workflows/development.yml b/.github/workflows/test.yaml similarity index 72% rename from .github/workflows/development.yml rename to .github/workflows/test.yaml index 3cf1d52..75aa82e 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/test.yaml @@ -1,9 +1,16 @@ -name: Development +name: Test on: [push, pull_request] +permissions: + contents: read + +env: + CONSOLE_OUTPUT: XTerm + jobs: test: + name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} @@ -14,13 +21,11 @@ jobs: - macos ruby: - - 2.5 - - 2.6 - - 2.7 - - 3.0 + - "2.7" + - "3.0" + - "3.1" experimental: [false] - env: [""] include: - os: ubuntu @@ -34,12 +39,12 @@ jobs: experimental: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests - timeout-minutes: 5 - run: ${{matrix.env}} bundle exec rspec + timeout-minutes: 10 + run: bundle exec bake test diff --git a/.gitignore b/.gitignore index 3468ed5..09a72e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,5 @@ /.bundle/ -/.yardoc -/_yardoc/ -/coverage/ -/doc/ /pkg/ -/spec/reports/ -/tmp/ - -# rspec failure tracking -.rspec_status -gems.locked +/gems.locked +/.covered.db +/external diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..6f2eb8e --- /dev/null +++ b/.mailmap @@ -0,0 +1,2 @@ +Yuji Yaginuma +Juan Antonio Martín Lucas diff --git a/.rspec b/.rspec deleted file mode 100644 index 8fbe32d..0000000 --- a/.rspec +++ /dev/null @@ -1,3 +0,0 @@ ---format documentation ---warnings ---require spec_helper \ No newline at end of file diff --git a/.yardopts b/.yardopts deleted file mode 100644 index d742dcb..0000000 --- a/.yardopts +++ /dev/null @@ -1 +0,0 @@ ---markup markdown \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index d9a30da..0000000 --- a/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Async::Container - -Provides containers which implement parallelism for clients and servers. - -[![Development Status](https://github.com/socketry/async-container/workflows/Development/badge.svg)](https://github.com/socketry/async-container/actions?workflow=Development) - -## Features - - - Supports multi-process, multi-thread and hybrid containers. - - Automatic scalability based on physical hardware. - - Direct integration with [systemd](https://www.freedesktop.org/software/systemd/man/sd_notify.html) using `$NOTIFY_SOCKET`. - - Internal process readiness protocol for handling state changes. - - Automatic restart of failed processes. - -## Usage - -Please see the [project documentation](https://socketry.github.io/async-container/). - -## Contributing - -We welcome contributions to this project. - -1. Fork it. -2. Create your feature branch (`git checkout -b my-new-feature`). -3. Commit your changes (`git commit -am 'Add some feature'`). -4. Push to the branch (`git push origin my-new-feature`). -5. Create new Pull Request. - -## License - -Copyright, 2017, by [Samuel G. D. Williams](https://www.codeotaku.com). - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/async-container.gemspec b/async-container.gemspec index 52b2383..eb559ea 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -1,3 +1,4 @@ +# frozen_string_literal: true require_relative "lib/async/container/version" @@ -6,20 +7,24 @@ Gem::Specification.new do |spec| spec.version = Async::Container::VERSION spec.summary = "Abstract container-based parallelism using threads and processes where appropriate." - spec.authors = ["Samuel Williams"] + spec.authors = ["Samuel Williams", "Olle Jonsson", "Anton Sozontov", "Juan Antonio Martín Lucas", "Yuji Yaginuma"] spec.license = "MIT" + spec.cert_chain = ['release.cert'] + spec.signing_key = File.expand_path('~/.gem/release.pem') + spec.homepage = "https://github.com/socketry/async-container" - spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) + spec.files = Dir.glob(['{lib}/**/*', '*.md'], File::FNM_DOTMATCH, base: __dir__) spec.required_ruby_version = ">= 2.5" spec.add_dependency "async" spec.add_dependency "async-io" - spec.add_development_dependency "async-rspec", "~> 1.1" + spec.add_development_dependency "bake-test" + spec.add_development_dependency "bake-test-external" spec.add_development_dependency "bundler" spec.add_development_dependency "covered" - spec.add_development_dependency "rspec", "~> 3.6" + spec.add_development_dependency "sus" end diff --git a/config/external.yaml b/config/external.yaml new file mode 100644 index 0000000..baea42b --- /dev/null +++ b/config/external.yaml @@ -0,0 +1,3 @@ +falcon: + url: https://github.com/socketry/falcon.git + command: bundle exec rspec diff --git a/config/sus.rb b/config/sus.rb new file mode 100644 index 0000000..ad31566 --- /dev/null +++ b/config/sus.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2022, by Samuel Williams. + +require 'covered/sus' +include Covered::Sus + +ENV['CONSOLE_LEVEL'] ||= 'fatal' diff --git a/examples/async.rb b/examples/async.rb index 656edd2..fde882a 100644 --- a/examples/async.rb +++ b/examples/async.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + require 'kernel/sync' class Worker diff --git a/examples/channel.rb b/examples/channel.rb index 3593f54..5592aff 100644 --- a/examples/channel.rb +++ b/examples/channel.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. + require 'json' class Channel diff --git a/examples/channels/client.rb b/examples/channels/client.rb index bd18284..3a6424a 100644 --- a/examples/channels/client.rb +++ b/examples/channels/client.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. + require 'msgpack' require 'async/io' require 'async/io/stream' diff --git a/examples/container.rb b/examples/container.rb index 5a6e632..a30392a 100755 --- a/examples/container.rb +++ b/examples/container.rb @@ -1,6 +1,10 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019, by Yuji Yaginuma. + require '../lib/async/container/controller' require '../lib/async/container/forked' diff --git a/examples/isolate.rb b/examples/isolate.rb index 86d0499..7714224 100644 --- a/examples/isolate.rb +++ b/examples/isolate.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true -# We define end of life-cycle in terms of "Interrupt" (SIGINT), "Terminate" (SIGTERM) and "Kill" (SIGKILL, does not invoke user code). +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + class Terminate < Interrupt end diff --git a/examples/minimal.rb b/examples/minimal.rb index b68b907..d637df7 100644 --- a/examples/minimal.rb +++ b/examples/minimal.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. + class Threaded def initialize(&block) @channel = Channel.new diff --git a/examples/queue/server.rb b/examples/queue/server.rb index 438a82f..8c8c39f 100644 --- a/examples/queue/server.rb +++ b/examples/queue/server.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. require 'async' require 'async/container' diff --git a/examples/test.rb b/examples/test.rb index e2e94f8..91f28e1 100644 --- a/examples/test.rb +++ b/examples/test.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + require_relative 'group' require_relative 'thread' require_relative 'process' diff --git a/examples/threads.rb b/examples/threads.rb index c8d73db..def31e4 100755 --- a/examples/threads.rb +++ b/examples/threads.rb @@ -1,6 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. + puts "Process pid: #{Process.pid}" threads = 10.times.collect do diff --git a/examples/title.rb b/examples/title.rb index 2328f72..d7f0be8 100755 --- a/examples/title.rb +++ b/examples/title.rb @@ -1,6 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2018-2022, by Samuel Williams. + Process.setproctitle "Preparing for sleep..." 10.times do |i| diff --git a/examples/udppipe.rb b/examples/udppipe.rb index a8fe334..1637368 100644 --- a/examples/udppipe.rb +++ b/examples/udppipe.rb @@ -1,6 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + require 'async/io' require 'async/io/endpoint' require 'async/io/unix_endpoint' diff --git a/fixtures/a_container.rb b/fixtures/a_container.rb new file mode 100644 index 0000000..e5ed5fe --- /dev/null +++ b/fixtures/a_container.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. + +AContainer = Sus::Shared("a container") do + let(:container) {subject.new} + + it "can run concurrently" do + input, output = IO.pipe + + container.async do + output.write "Hello World" + end + + container.wait + + output.close + expect(input.read).to be == "Hello World" + end + + it "can run concurrently" do + container.async(name: "Sleepy Jerry") do |task, instance| + 3.times do |i| + instance.name = "Counting Sheep #{i}" + + sleep 0.01 + end + end + + container.wait + end + + it "should be blocking" do + skip "Fiber.blocking? is not supported!" unless Fiber.respond_to?(:blocking?) + + input, output = IO.pipe + + container.spawn do + output.write(Fiber.blocking? != false) + end + + container.wait + + output.close + expect(input.read).to be == "true" + end + + with '#sleep' do + it "can sleep for a short time" do + container.spawn do + sleep(0.01) + raise "Boom" + end + + expect(container.statistics).to have_attributes(failures: be == 0) + + container.wait + + expect(container.statistics).to have_attributes(failures: be == 1) + end + end + + with '#stop' do + it 'can stop the child process' do + container.spawn do + sleep(1) + end + + expect(container).to be(:running?) + + container.stop + + expect(container).not.to be(:running?) + end + end +end diff --git a/fixtures/timer_quantum.rb b/fixtures/timer_quantum.rb new file mode 100644 index 0000000..8728f43 --- /dev/null +++ b/fixtures/timer_quantum.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2022, by Samuel Williams. + +class TimerQuantum + def self.resolve + self.new.to_f + end + + def to_f + precision + end + + private + + def precision + @precision ||= self.measure_host_precision + end + + def measure_host_precision(repeats: 100, duration: 0.01) + # Measure the precision sleep using the monotonic clock: + start_time = self.now + repeats.times do + sleep(duration) + end + end_time = self.now + + actual_duration = end_time - start_time + expected_duration = repeats * duration + + if actual_duration < expected_duration + warn "Invalid precision measurement: #{actual_duration} < #{expected_duration}" + return 0.1 + end + + # This computes the overhead of sleep, called `repeats` times: + return actual_duration - expected_duration + end + + def now + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end +end + +QUANTUM = TIMER_QUANTUM = TimerQuantum.resolve diff --git a/gems.rb b/gems.rb index 71c88f8..26fe797 100644 --- a/gems.rb +++ b/gems.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + source 'https://rubygems.org' # Specify your gem's dependencies in utopia.gemspec diff --git a/gems/async-head.rb b/gems/async-head.rb index 50345f7..10b94ea 100644 --- a/gems/async-head.rb +++ b/gems/async-head.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2021-2022, by Samuel Williams. + source 'https://rubygems.org' gemspec path: "../" diff --git a/gems/async-v1.rb b/gems/async-v1.rb index cc6730c..b32ae2f 100644 --- a/gems/async-v1.rb +++ b/gems/async-v1.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2021-2022, by Samuel Williams. + source 'https://rubygems.org' gemspec path: "../" diff --git a/guides/getting-started/README.md b/guides/getting-started/readme.md similarity index 100% rename from guides/getting-started/README.md rename to guides/getting-started/readme.md diff --git a/lib/async/container.rb b/lib/async/container.rb index 9ef7e80..691afe4 100644 --- a/lib/async/container.rb +++ b/lib/async/container.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2017, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2017-2022, by Samuel Williams. require_relative 'container/controller' diff --git a/lib/async/container/best.rb b/lib/async/container/best.rb index 2505362..7ba4226 100644 --- a/lib/async/container/best.rb +++ b/lib/async/container/best.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2019, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. require_relative 'forked' require_relative 'threaded' diff --git a/lib/async/container/channel.rb b/lib/async/container/channel.rb index 6be4eba..ae61d2c 100644 --- a/lib/async/container/channel.rb +++ b/lib/async/container/channel.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. require 'json' diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 507ebc3..db4459e 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2018, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2018-2022, by Samuel Williams. require_relative 'error' require_relative 'best' diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index 3558a74..62dd62f 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2019, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. module Async module Container diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 0d40ec1..c5dabd3 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2017, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2017-2022, by Samuel Williams. require_relative 'generic' require_relative 'process' diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index a691e2b..6147298 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2018, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. require 'async' diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 9477afd..d691261 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2017, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. require 'fiber' require 'async/clock' @@ -37,7 +20,7 @@ def initialize @queue = nil end - # @attribute [Hash] the running tasks, indexed by IO. + # @attribute [Hash(IO, Fiber)] the running tasks, indexed by IO. attr :running # Whether the group contains any running processes. diff --git a/lib/async/container/hybrid.rb b/lib/async/container/hybrid.rb index 4a27836..1a20b1d 100644 --- a/lib/async/container/hybrid.rb +++ b/lib/async/container/hybrid.rb @@ -1,24 +1,8 @@ # frozen_string_literal: true -# Copyright, 2017, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2022, by Anton Sozontov. require_relative 'forked' require_relative 'threaded' diff --git a/lib/async/container/keyed.rb b/lib/async/container/keyed.rb index e6ee0a9..6e2b753 100644 --- a/lib/async/container/keyed.rb +++ b/lib/async/container/keyed.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2019, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. module Async module Container diff --git a/lib/async/container/notify.rb b/lib/async/container/notify.rb index 706a732..e9707b3 100644 --- a/lib/async/container/notify.rb +++ b/lib/async/container/notify.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. require_relative 'notify/pipe' require_relative 'notify/socket' diff --git a/lib/async/container/notify/client.rb b/lib/async/container/notify/client.rb index b274e4c..63eca29 100644 --- a/lib/async/container/notify/client.rb +++ b/lib/async/container/notify/client.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. module Async module Container diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb index 16362f7..ea94f98 100644 --- a/lib/async/container/notify/console.rb +++ b/lib/async/container/notify/console.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. require_relative 'client' diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index d9571fc..398ef90 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -1,24 +1,8 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020, by Juan Antonio Martín Lucas. require_relative 'client' diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index dce527e..17aaa1a 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -1,24 +1,8 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. require 'async/io' require 'async/io/unix_endpoint' diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index 4b5f746..6b01c39 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. require_relative 'client' diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index d7b5e9a..7cd5358 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. require_relative 'channel' require_relative 'error' diff --git a/lib/async/container/statistics.rb b/lib/async/container/statistics.rb index be194f9..9c84cdd 100644 --- a/lib/async/container/statistics.rb +++ b/lib/async/container/statistics.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2019, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. require 'async/reactor' diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index 5dad31b..bf21ba5 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -1,24 +1,8 @@ # frozen_string_literal: true -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. require_relative 'channel' require_relative 'error' diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index 5341cfb..faa2804 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2017, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2017-2022, by Samuel Williams. require_relative 'generic' require_relative 'thread' diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index c0756aa..3bdd15e 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -1,24 +1,7 @@ # frozen_string_literal: true -# Copyright, 2017, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# Released under the MIT License. +# Copyright, 2017-2022, by Samuel Williams. module Async module Container diff --git a/license.md b/license.md new file mode 100644 index 0000000..d178b01 --- /dev/null +++ b/license.md @@ -0,0 +1,25 @@ +# MIT License + +Copyright, 2017-2022, by Samuel Williams. +Copyright, 2019, by Yuji Yaginuma. +Copyright, 2020, by Olle Jonsson. +Copyright, 2020, by Juan Antonio Martín Lucas. +Copyright, 2022, by Anton Sozontov. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..2d3d286 --- /dev/null +++ b/readme.md @@ -0,0 +1,27 @@ +# Async::Container + +Provides containers which implement parallelism for clients and servers. + +[![Development Status](https://github.com/socketry/async-container/workflows/Test/badge.svg)](https://github.com/socketry/async-container/actions?workflow=Test) + +## Features + + - Supports multi-process, multi-thread and hybrid containers. + - Automatic scalability based on physical hardware. + - Direct integration with [systemd](https://www.freedesktop.org/software/systemd/man/sd_notify.html) using `$NOTIFY_SOCKET`. + - Internal process readiness protocol for handling state changes. + - Automatic restart of failed processes. + +## Usage + +Please see the [project documentation](https://socketry.github.io/async-container/). + +## Contributing + +We welcome contributions to this project. + +1. Fork it. +2. Create your feature branch (`git checkout -b my-new-feature`). +3. Commit your changes (`git commit -am 'Add some feature'`). +4. Push to the branch (`git push origin my-new-feature`). +5. Create new Pull Request. diff --git a/release.cert b/release.cert new file mode 100644 index 0000000..d98e595 --- /dev/null +++ b/release.cert @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11 +ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK +CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz +MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd +MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj +bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB +igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2 +9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW +sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE +e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN +XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss +RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn +tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM +zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW +xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O +BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs +aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs +aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE +cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl +xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/ +c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp +8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws +JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP +eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt +Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8 +voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg= +-----END CERTIFICATE----- diff --git a/spec/async/container/controller_spec.rb b/spec/async/container/controller_spec.rb deleted file mode 100644 index be3e899..0000000 --- a/spec/async/container/controller_spec.rb +++ /dev/null @@ -1,148 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2019, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require "async/container/controller" - -RSpec.describe Async::Container::Controller do - describe '#reload' do - it "can reuse keyed child" do - input, output = IO.pipe - - subject.instance_variable_set(:@output, output) - - def subject.setup(container) - container.spawn(key: "test") do |instance| - instance.ready! - - sleep(0.2 * QUANTUM) - - @output.write(".") - @output.flush - - sleep(0.4 * QUANTUM) - end - - container.spawn do |instance| - instance.ready! - - sleep(0.3 * QUANTUM) - - @output.write(",") - @output.flush - end - end - - subject.start - expect(input.read(2)).to be == ".," - - subject.reload - - expect(input.read(1)).to be == "," - subject.wait - end - end - - describe '#start' do - it "can start up a container" do - expect(subject).to receive(:setup) - - subject.start - - expect(subject).to be_running - expect(subject.container).to_not be_nil - - subject.stop - - expect(subject).to_not be_running - expect(subject.container).to be_nil - end - - it "can spawn a reactor" do - def subject.setup(container) - container.async do |task| - task.sleep 1 - end - end - - subject.start - - statistics = subject.container.statistics - - expect(statistics.spawns).to be == 1 - - subject.stop - end - - it "propagates exceptions" do - def subject.setup(container) - raise "Boom!" - end - - expect do - subject.run - end.to raise_exception(Async::Container::SetupError) - end - end - - context 'with signals' do - let(:controller_path) {File.expand_path("dots.rb", __dir__)} - - let(:pipe) {IO.pipe} - let(:input) {pipe.first} - let(:output) {pipe.last} - - let(:pid) {Process.spawn("bundle", "exec", controller_path, out: output)} - - before do - pid - output.close - end - - after do - Process.kill(:KILL, pid) - end - - it "restarts children when receiving SIGHUP" do - expect(input.read(1)).to be == '.' - - Process.kill(:HUP, pid) - - expect(input.read(2)).to be == 'I.' - end - - it "exits gracefully when receiving SIGINT" do - expect(input.read(1)).to be == '.' - - Process.kill(:INT, pid) - - expect(input.read).to be == 'I' - end - - it "exits gracefully when receiving SIGTERM" do - expect(input.read(1)).to be == '.' - - Process.kill(:TERM, pid) - - expect(input.read).to be == 'T' - end - end -end diff --git a/spec/async/container/forked_spec.rb b/spec/async/container/forked_spec.rb deleted file mode 100644 index 7da8a75..0000000 --- a/spec/async/container/forked_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2018, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require "async/container" -require "async/container/forked" - -require_relative 'shared_examples' - -RSpec.describe Async::Container::Forked, if: Async::Container.fork? do - subject {described_class.new} - - it_behaves_like Async::Container - - it "can restart child" do - trigger = IO.pipe - pids = IO.pipe - - thread = Thread.new do - subject.async(restart: true) do - trigger.first.gets - pids.last.puts Process.pid.to_s - end - - subject.wait - end - - 3.times do - trigger.last.puts "die" - _child_pid = pids.first.gets - end - - thread.kill - thread.join - - expect(subject.statistics.spawns).to be == 1 - expect(subject.statistics.restarts).to be == 2 - end - - it "should be multiprocess" do - expect(described_class).to be_multiprocess - end -end diff --git a/spec/async/container/hybrid_spec.rb b/spec/async/container/hybrid_spec.rb deleted file mode 100644 index 1fb5797..0000000 --- a/spec/async/container/hybrid_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2019, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require 'async/container/hybrid' -require 'async/container/best' - -require_relative 'shared_examples' - -RSpec.describe Async::Container::Hybrid, if: Async::Container.fork? do - subject {described_class.new} - - it_behaves_like Async::Container - - it "should be multiprocess" do - expect(described_class).to be_multiprocess - end -end diff --git a/spec/async/container/notify/pipe_spec.rb b/spec/async/container/notify/pipe_spec.rb deleted file mode 100644 index e53b6be..0000000 --- a/spec/async/container/notify/pipe_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require "async/container/controller" - -RSpec.describe Async::Container::Notify::Pipe do - let(:notify_script) {File.expand_path("notify.rb", __dir__)} - - it "receives notification of child status" do - container = Async::Container.new - - container.spawn(restart: false) do |instance| - instance.exec( - "bundle", "exec", "--keep-file-descriptors", - notify_script, ready: false - ) - end - - # Wait for the state to be updated by the child process: - container.sleep - - _child, state = container.state.first - expect(state).to be == {status: "Initializing..."} - - container.wait - - expect(container.statistics).to have_attributes(failures: 0) - end -end diff --git a/spec/async/container/notify_spec.rb b/spec/async/container/notify_spec.rb deleted file mode 100644 index ec47377..0000000 --- a/spec/async/container/notify_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2020, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require "async/container/controller" -require "async/container/notify/server" - -RSpec.describe Async::Container::Notify, if: Async::Container.fork? do - let(:server) {described_class::Server.open} - let(:notify_socket) {server.path} - let(:client) {described_class::Socket.new(notify_socket)} - - describe '#ready!' do - it "should send message" do - begin - context = server.bind - - pid = fork do - client.ready! - end - - messages = [] - - Sync do - context.receive do |message, address| - messages << message - break - end - end - - expect(messages.last).to include(ready: true) - ensure - context&.close - Process.wait(pid) if pid - end - end - end -end diff --git a/spec/async/container/shared_examples.rb b/spec/async/container/shared_examples.rb deleted file mode 100644 index 0df3589..0000000 --- a/spec/async/container/shared_examples.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2018, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require 'async/rspec/reactor' - -RSpec.shared_examples_for Async::Container do - it "can run concurrently" do - input, output = IO.pipe - - subject.async do - output.write "Hello World" - end - - subject.wait - - output.close - expect(input.read).to be == "Hello World" - end - - it "can run concurrently" do - subject.async(name: "Sleepy Jerry") do |task, instance| - 3.times do |i| - instance.name = "Counting Sheep #{i}" - - sleep 0.01 - end - end - - subject.wait - end - - it "should be blocking", if: Fiber.respond_to?(:blocking?) do - input, output = IO.pipe - - subject.spawn do - output.write(Fiber.blocking? != false) - end - - subject.wait - - output.close - expect(input.read).to be == "true" - end - - describe '#sleep' do - it "can sleep for a short time" do - subject.spawn do - sleep(0.2 * QUANTUM) - raise "Boom" - end - - subject.sleep(0.1 * QUANTUM) - expect(subject.statistics).to have_attributes(failures: 0) - - subject.wait - - expect(subject.statistics).to have_attributes(failures: 1) - end - end - - describe '#stop' do - it 'can stop the child process' do - subject.spawn do - sleep(1) - end - - is_expected.to be_running - - subject.stop - - is_expected.to_not be_running - end - end -end diff --git a/spec/async/container/threaded_spec.rb b/spec/async/container/threaded_spec.rb deleted file mode 100644 index 0510d42..0000000 --- a/spec/async/container/threaded_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2018, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require "async/container/threaded" - -require_relative 'shared_examples' - -RSpec.describe Async::Container::Threaded do - subject {described_class.new} - - it_behaves_like Async::Container - - it "should not be multiprocess" do - expect(described_class).to_not be_multiprocess - end -end diff --git a/spec/async/container_spec.rb b/spec/async/container_spec.rb deleted file mode 100644 index ff80b28..0000000 --- a/spec/async/container_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -# Copyright, 2017, by Samuel G. D. Williams. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -require "async/container" - -RSpec.describe Async::Container do - describe '.processor_count' do - it "can get processor count" do - expect(Async::Container.processor_count).to be >= 1 - end - - it "can override the processor count" do - env = {'ASYNC_CONTAINER_PROCESSOR_COUNT' => '8'} - - expect(Async::Container.processor_count(env)).to be == 8 - end - - it "fails on invalid processor count" do - env = {'ASYNC_CONTAINER_PROCESSOR_COUNT' => '-1'} - - expect do - Async::Container.processor_count(env) - end.to raise_error(/Invalid processor count/) - end - end - - it "can get best container class" do - expect(Async::Container.best_container_class).to_not be_nil - end - - subject {Async::Container.new} - - it "can get best container class" do - expect(subject).to_not be_nil - - subject.stop - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 6171c85..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'covered/rspec' - -# Shared rspec helpers: -require "async/rspec" - -if RUBY_PLATFORM =~ /darwin/i - QUANTUM = 2.0 -else - QUANTUM = 1.0 -end - -RSpec.configure do |config| - # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = ".rspec_status" - - config.expect_with :rspec do |c| - c.syntax = :expect - end -end diff --git a/test/async/container.rb b/test/async/container.rb new file mode 100644 index 0000000..afec4e0 --- /dev/null +++ b/test/async/container.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2017-2022, by Samuel Williams. + +require "async/container" + +describe Async::Container do + with '.processor_count' do + it "can get processor count" do + expect(Async::Container.processor_count).to be >= 1 + end + + it "can override the processor count" do + env = {'ASYNC_CONTAINER_PROCESSOR_COUNT' => '8'} + + expect(Async::Container.processor_count(env)).to be == 8 + end + + it "fails on invalid processor count" do + env = {'ASYNC_CONTAINER_PROCESSOR_COUNT' => '-1'} + + expect do + Async::Container.processor_count(env) + end.to raise_exception(RuntimeError, message: be =~ /Invalid processor count/) + end + end + + it "can get best container class" do + expect(Async::Container.best_container_class).not.to be_nil + end + + with '.new' do + let(:container) {Async::Container.new} + + it "can get best container class" do + expect(container).not.to be_nil + container.stop + end + end +end diff --git a/spec/async/container/dots.rb b/test/async/container/.dots.rb similarity index 87% rename from spec/async/container/dots.rb rename to test/async/container/.dots.rb index 323fb7d..dca311b 100755 --- a/spec/async/container/dots.rb +++ b/test/async/container/.dots.rb @@ -1,6 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + require_relative '../../../lib/async/container/controller' # Console.logger.debug! diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb new file mode 100644 index 0000000..b373207 --- /dev/null +++ b/test/async/container/controller.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2018-2022, by Samuel Williams. + +require "async/container/controller" + +require "timer_quantum" + +describe Async::Container::Controller do + let(:controller) {subject.new} + + with '#reload' do + it "can reuse keyed child" do + input, output = IO.pipe + + controller.instance_variable_set(:@output, output) + + def controller.setup(container) + container.spawn(key: "test") do |instance| + instance.ready! + + sleep(0.2 * QUANTUM) + + @output.write(".") + @output.flush + + sleep(0.4 * QUANTUM) + end + + container.spawn do |instance| + instance.ready! + + sleep(0.3 * QUANTUM) + + @output.write(",") + @output.flush + end + end + + controller.start + expect(input.read(2)).to be == ".," + + controller.reload + + expect(input.read(1)).to be == "," + controller.wait + end + end + + with '#start' do + it "can start up a container" do + expect(controller).to receive(:setup) + + controller.start + + expect(controller).to be(:running?) + expect(controller.container).not.to be_nil + + controller.stop + + expect(controller).not.to be(:running?) + expect(controller.container).to be_nil + end + + it "can spawn a reactor" do + def controller.setup(container) + container.async do |task| + task.sleep 1 + end + end + + controller.start + + statistics = controller.container.statistics + + expect(statistics.spawns).to be == 1 + + controller.stop + end + + it "propagates exceptions" do + def controller.setup(container) + raise "Boom!" + end + + expect do + controller.run + end.to raise_exception(Async::Container::SetupError) + end + end + + with 'signals' do + let(:controller_path) {File.expand_path(".dots.rb", __dir__)} + + let(:pipe) {IO.pipe} + let(:input) {pipe.first} + let(:output) {pipe.last} + + let(:pid) {@pid} + + def before + @pid = Process.spawn("bundle", "exec", controller_path, out: output) + output.close + + super + end + + def after + Process.kill(:TERM, @pid) + Process.wait(@pid) + + super + end + + it "restarts children when receiving SIGHUP" do + expect(input.read(1)).to be == '.' + + Process.kill(:HUP, pid) + + expect(input.read(2)).to be == 'I.' + end + + it "exits gracefully when receiving SIGINT" do + expect(input.read(1)).to be == '.' + + Process.kill(:INT, pid) + + expect(input.read).to be == 'I' + end + + it "exits gracefully when receiving SIGTERM" do + expect(input.read(1)).to be == '.' + + Process.kill(:TERM, pid) + + expect(input.read).to be == 'T' + end + end +end diff --git a/test/async/container/forked.rb b/test/async/container/forked.rb new file mode 100644 index 0000000..0b0b12b --- /dev/null +++ b/test/async/container/forked.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2018-2022, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. + +require "async/container" +require "async/container/forked" + +require 'a_container' + +return unless Async::Container.fork? + +describe Async::Container::Forked do + let(:container) {subject.new} + + it_behaves_like AContainer + + it "can restart child" do + trigger = IO.pipe + pids = IO.pipe + + thread = Thread.new do + container.async(restart: true) do + trigger.first.gets + pids.last.puts Process.pid.to_s + end + + container.wait + end + + 3.times do + trigger.last.puts "die" + _child_pid = pids.first.gets + end + + thread.kill + thread.join + + expect(container.statistics.spawns).to be == 1 + expect(container.statistics.restarts).to be == 2 + end + + it "should be multiprocess" do + expect(subject).to be(:multiprocess?) + end +end diff --git a/test/async/container/hybrid.rb b/test/async/container/hybrid.rb new file mode 100644 index 0000000..0aacb5b --- /dev/null +++ b/test/async/container/hybrid.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. + +require 'async/container/hybrid' +require 'async/container/best' + +require 'a_container' + +return unless Async::Container.fork? + +describe Async::Container::Hybrid do + it_behaves_like AContainer + + it "should be multiprocess" do + expect(subject).to be(:multiprocess?) + end +end diff --git a/test/async/container/notify.rb b/test/async/container/notify.rb new file mode 100644 index 0000000..4a5371f --- /dev/null +++ b/test/async/container/notify.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + +require "async/container/controller" +require "async/container/notify/server" + +return unless Async::Container.fork? + +describe Async::Container::Notify do + let(:server) {subject::Server.open} + let(:notify_socket) {server.path} + let(:client) {subject::Socket.new(notify_socket)} + + with '#ready!' do + it "should send message" do + begin + context = server.bind + + pid = fork do + client.ready! + end + + messages = [] + + Sync do + context.receive do |message, address| + messages << message + break + end + end + + expect(messages.last).to have_keys(ready: be == true) + ensure + context&.close + Process.wait(pid) if pid + end + end + end +end diff --git a/spec/async/container/notify/notify.rb b/test/async/container/notify/.notify.rb similarity index 81% rename from spec/async/container/notify/notify.rb rename to test/async/container/notify/.notify.rb index e47912c..ba232c4 100755 --- a/spec/async/container/notify/notify.rb +++ b/test/async/container/notify/.notify.rb @@ -1,6 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + require_relative '../../../../lib/async/container' class MyController < Async::Container::Controller diff --git a/test/async/container/notify/pipe.rb b/test/async/container/notify/pipe.rb new file mode 100644 index 0000000..73f9557 --- /dev/null +++ b/test/async/container/notify/pipe.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. + +require "async/container/controller" + +describe Async::Container::Notify::Pipe do + let(:notify_script) {File.expand_path(".notify.rb", __dir__)} + + it "receives notification of child status" do + container = Async::Container.new + + container.spawn(restart: false) do |instance| + instance.exec( + "bundle", "exec", "--keep-file-descriptors", + notify_script, ready: false + ) + end + + # Wait for the state to be updated by the child process: + container.sleep + + _child, state = container.state.first + expect(state).to be == {status: "Initializing..."} + + container.wait + + expect(container.statistics).to have_attributes(failures: be == 0) + end +end diff --git a/test/async/container/threaded.rb b/test/async/container/threaded.rb new file mode 100644 index 0000000..c2ece0d --- /dev/null +++ b/test/async/container/threaded.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2018-2022, by Samuel Williams. + +require "async/container/threaded" + +require 'a_container' + +describe Async::Container::Threaded do + it_behaves_like AContainer + + it "should not be multiprocess" do + expect(subject).not.to be(:multiprocess?) + end +end From 0a463a5b4d3e49cec0eb7f67e116cad460b3ceea Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 6 Nov 2022 00:12:46 +1300 Subject: [PATCH 070/166] Add test for `instance.ready!` in asynchronous context. --- fixtures/a_container.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fixtures/a_container.rb b/fixtures/a_container.rb index e5ed5fe..854e613 100644 --- a/fixtures/a_container.rb +++ b/fixtures/a_container.rb @@ -74,4 +74,22 @@ expect(container).not.to be(:running?) end end + + with '#ready' do + it "can notify the ready pipe in an asynchronous context" do + container.run do |instance| + Async do + instance.ready! + end + end + + expect(container).to be(:running?) + + container.wait + + container.stop + + expect(container).not.to be(:running?) + end + end end From 11bee9de5558d3b2e1a4e49c6e9791850a845358 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 6 Nov 2022 00:16:32 +1300 Subject: [PATCH 071/166] Remove TimerQuantum implementation. --- fixtures/timer_quantum.rb | 46 ------------------------------ test/async/container/controller.rb | 8 ++---- 2 files changed, 3 insertions(+), 51 deletions(-) delete mode 100644 fixtures/timer_quantum.rb diff --git a/fixtures/timer_quantum.rb b/fixtures/timer_quantum.rb deleted file mode 100644 index 8728f43..0000000 --- a/fixtures/timer_quantum.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2022, by Samuel Williams. - -class TimerQuantum - def self.resolve - self.new.to_f - end - - def to_f - precision - end - - private - - def precision - @precision ||= self.measure_host_precision - end - - def measure_host_precision(repeats: 100, duration: 0.01) - # Measure the precision sleep using the monotonic clock: - start_time = self.now - repeats.times do - sleep(duration) - end - end_time = self.now - - actual_duration = end_time - start_time - expected_duration = repeats * duration - - if actual_duration < expected_duration - warn "Invalid precision measurement: #{actual_duration} < #{expected_duration}" - return 0.1 - end - - # This computes the overhead of sleep, called `repeats` times: - return actual_duration - expected_duration - end - - def now - Process.clock_gettime(Process::CLOCK_MONOTONIC) - end -end - -QUANTUM = TIMER_QUANTUM = TimerQuantum.resolve diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index b373207..782cab6 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -5,8 +5,6 @@ require "async/container/controller" -require "timer_quantum" - describe Async::Container::Controller do let(:controller) {subject.new} @@ -20,18 +18,18 @@ def controller.setup(container) container.spawn(key: "test") do |instance| instance.ready! - sleep(0.2 * QUANTUM) + sleep(0.2) @output.write(".") @output.flush - sleep(0.4 * QUANTUM) + sleep(0.4) end container.spawn do |instance| instance.ready! - sleep(0.3 * QUANTUM) + sleep(0.3) @output.write(",") @output.flush From 4ccfc99de9fe4973f58d0d124e9e235addf4bce4 Mon Sep 17 00:00:00 2001 From: Anton Sozontov Date: Fri, 11 Nov 2022 08:22:25 +0200 Subject: [PATCH 072/166] Update examples and getting-started guide (#25) --- examples/container.rb | 38 +++++++++++----------------- examples/controller.rb | 43 ++++++++++++++++++++++++++++++++ guides/getting-started/readme.md | 20 +++++++++------ 3 files changed, 70 insertions(+), 31 deletions(-) create mode 100755 examples/controller.rb diff --git a/examples/container.rb b/examples/container.rb index a30392a..301b1c0 100755 --- a/examples/container.rb +++ b/examples/container.rb @@ -4,34 +4,24 @@ # Released under the MIT License. # Copyright, 2019-2022, by Samuel Williams. # Copyright, 2019, by Yuji Yaginuma. +# Copyright, 2022, by Anton Sozontov. -require '../lib/async/container/controller' -require '../lib/async/container/forked' +require '../lib/async/container' Console.logger.debug! -Console.logger.debug(self, "Starting up...") +container = Async::Container.new -controller = Async::Container::Controller.new do |container| - Console.logger.debug(self, "Setting up container...") - - container.run(count: 1, restart: true) do - Console.logger.debug(self, "Child process started.") - - while true - sleep 1 - - if rand < 0.1 - exit(1) - end - end - ensure - Console.logger.debug(self, "Child process exiting:", $!) - end -end +Console.logger.debug "Spawning 2 containers..." -begin - controller.run -ensure - Console.logger.debug(controller, "Parent process exiting:", $!) +2.times do + container.spawn do |task| + Console.logger.debug task, "Sleeping..." + sleep(2) + Console.logger.debug task, "Waking up!" + end end + +Console.logger.debug "Waiting for container..." +container.wait +Console.logger.debug "Finished." diff --git a/examples/controller.rb b/examples/controller.rb new file mode 100755 index 0000000..1712418 --- /dev/null +++ b/examples/controller.rb @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019, by Yuji Yaginuma. +# Copyright, 2022, by Anton Sozontov. + +require '../lib/async/container/controller' + +class Controller < Async::Container::Controller + def setup(container) + container.run(count: 1, restart: true) do |instance| + if container.statistics.failed? + Console.logger.debug(self, "Child process restarted #{container.statistics.restarts} times.") + else + Console.logger.debug(self, "Child process started.") + end + + instance.ready! + + while true + sleep 1 + + Console.logger.debug(self, "Work") + + if rand < 0.5 + Console.logger.debug(self, "Should exit...") + sleep 0.5 + exit(1) + end + end + end + end +end + +Console.logger.debug! + +Console.logger.debug(self, "Starting up...") + +controller = Controller.new + +controller.run diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index a6641f9..a9c6cb4 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -28,10 +28,10 @@ Console.logger.debug! container = Async::Container.new -container.async do |task| - task.logger.debug "Sleeping..." - task.sleep(1) - task.logger.debug "Waking up!" +container.spawn do |task| + Console.logger.debug task, "Sleeping..." + sleep(1) + Console.logger.debug task, "Waking up!" end Console.logger.debug "Waiting for container..." @@ -49,11 +49,17 @@ require 'async/container' Console.logger.debug! class Controller < Async::Container::Controller + def create_container + Async::Container::Forked.new + # or Async::Container::Threaded.new + # or Async::Container::Hybrid.new + end + def setup(container) - container.async do |task| + container.run count: 2, restart: true do |instance| while true - Console.logger.debug("Sleeping...") - task.sleep(1) + Console.logger.debug(instance, "Sleeping...") + sleep(1) end end end From 97ac4bcc8b7e2a328938d5dcdd36a20bcc8991bd Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 11 Nov 2022 17:31:48 +1300 Subject: [PATCH 073/166] Fix TruffleRuby compatibilty with `return` in `class_eval`. Fixes #23. --- test/async/container/forked.rb | 4 +--- test/async/container/hybrid.rb | 4 +--- test/async/container/notify.rb | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/test/async/container/forked.rb b/test/async/container/forked.rb index 0b0b12b..42adf4b 100644 --- a/test/async/container/forked.rb +++ b/test/async/container/forked.rb @@ -9,8 +9,6 @@ require 'a_container' -return unless Async::Container.fork? - describe Async::Container::Forked do let(:container) {subject.new} @@ -44,4 +42,4 @@ it "should be multiprocess" do expect(subject).to be(:multiprocess?) end -end +end if Async::Container.fork? diff --git a/test/async/container/hybrid.rb b/test/async/container/hybrid.rb index 0aacb5b..2e3ad75 100644 --- a/test/async/container/hybrid.rb +++ b/test/async/container/hybrid.rb @@ -8,12 +8,10 @@ require 'a_container' -return unless Async::Container.fork? - describe Async::Container::Hybrid do it_behaves_like AContainer it "should be multiprocess" do expect(subject).to be(:multiprocess?) end -end +end if Async::Container.fork? diff --git a/test/async/container/notify.rb b/test/async/container/notify.rb index 4a5371f..c8cea41 100644 --- a/test/async/container/notify.rb +++ b/test/async/container/notify.rb @@ -6,8 +6,6 @@ require "async/container/controller" require "async/container/notify/server" -return unless Async::Container.fork? - describe Async::Container::Notify do let(:server) {subject::Server.open} let(:notify_socket) {server.path} @@ -38,4 +36,4 @@ end end end -end +end if Async::Container.fork? From df48675dc70139b91bc3257af5b6b0527d38e395 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 11 Dec 2022 21:30:45 +1300 Subject: [PATCH 074/166] Add some examples that were lying around. --- examples/benchmark/scalability.rb | 40 +++++++++++++++++++++++++++++++ examples/http/client.rb | 15 ++++++++++++ examples/http/server.rb | 40 +++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 examples/benchmark/scalability.rb create mode 100644 examples/http/client.rb create mode 100644 examples/http/server.rb diff --git a/examples/benchmark/scalability.rb b/examples/benchmark/scalability.rb new file mode 100644 index 0000000..013c3e7 --- /dev/null +++ b/examples/benchmark/scalability.rb @@ -0,0 +1,40 @@ + +# gem install async-container +gem "async-container" + +require 'async/clock' +require_relative '../../lib/async/container' + +def fibonacci(n) + if n < 2 + return n + else + return fibonacci(n-1) + fibonacci(n-2) + end +end + +require 'sqlite3' + +def work(*) + 512.times do + File.read("/dev/zero", 1024*1024).bytesize + end +end + +def measure_work(container, **options, &block) + duration = Async::Clock.measure do + container.run(**options, &block) + container.wait + end + + puts "Duration for #{container.class}: #{duration}" +end + +threaded = Async::Container::Threaded.new +measure_work(threaded, count: 32, &self.method(:work)) + +forked = Async::Container::Forked.new +measure_work(forked, count: 32, &self.method(:work)) + +hybrid = Async::Container::Hybrid.new +measure_work(hybrid, count: 32, &self.method(:work)) diff --git a/examples/http/client.rb b/examples/http/client.rb new file mode 100644 index 0000000..370997b --- /dev/null +++ b/examples/http/client.rb @@ -0,0 +1,15 @@ + +require 'async' +require 'async/http/endpoint' +require 'async/http/client' + +endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292") + +Async do + client = Async::HTTP::Client.new(endpoint) + + response = client.get("/") + puts response.read +ensure + client&.close +end diff --git a/examples/http/server.rb b/examples/http/server.rb new file mode 100644 index 0000000..ab4a317 --- /dev/null +++ b/examples/http/server.rb @@ -0,0 +1,40 @@ + +require 'async/container' + +require 'async/http/endpoint' +require 'async/http/server' +require 'async/io/shared_endpoint' + +container = Async::Container::Forked.new + +endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292") + +bound_endpoint = Async::Reactor.run do + Async::IO::SharedEndpoint.bound(endpoint) +end.wait + +input, output = Async::IO.pipe +input.write(".") + +Async.logger.info(endpoint) {"Bound to #{bound_endpoint.inspect}"} + +GC.start +GC.compact if GC.respond_to?(:compact) + +container.run(count: 16, restart: true) do + Async do |task| + server = Async::HTTP::Server.for(bound_endpoint, endpoint.protocol, endpoint.scheme) do |request| + Protocol::HTTP::Response[200, {}, ["Hello World"]] + end + + output.read(1) + Async.logger.info(server) {"Starting server..."} + output.write(".") + + server.run + + task.children.each(&:wait) + end +end + +container.wait From 76110991462618e73756cc7e0d7221819a496e57 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 21 Feb 2024 01:54:46 +1300 Subject: [PATCH 075/166] Modernize gem. --- .editorconfig | 4 ++++ .github/workflows/coverage.yaml | 10 +++++----- .github/workflows/documentation.yaml | 11 ++++------- .github/workflows/test-external.yaml | 5 +++-- .github/workflows/test.yaml | 5 +++-- async-container.gemspec | 8 +------- examples/benchmark/scalability.rb | 4 ++++ examples/controller.rb | 2 -- examples/http/client.rb | 4 ++++ examples/http/server.rb | 4 ++++ gems.rb | 10 +++++++++- lib/async/container/group.rb | 2 +- readme.md | 8 ++++++++ 13 files changed, 50 insertions(+), 27 deletions(-) diff --git a/.editorconfig b/.editorconfig index 538ba2b..a6e7d26 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,3 +3,7 @@ root = true [*] indent_style = tab indent_size = 2 + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 5e9afd7..68adbf2 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -21,10 +21,10 @@ jobs: - macos ruby: - - "3.1" + - "3.3" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} @@ -34,7 +34,7 @@ jobs: timeout-minutes: 5 run: bundle exec bake test - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: coverage-${{matrix.os}}-${{matrix.ruby}} path: .covered.db @@ -44,10 +44,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.1" + ruby-version: "3.3" bundler-cache: true - uses: actions/download-artifact@v3 diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 0e435e2..8dc5227 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -5,9 +5,6 @@ on: branches: - main - # Allows you to run this workflow manually from the Actions tab: - workflow_dispatch: - # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages: permissions: contents: read @@ -28,11 +25,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.1" + ruby-version: "3.3" bundler-cache: true - name: Installing packages @@ -43,7 +40,7 @@ jobs: run: bundle exec bake utopia:project:static --force no - name: Upload documentation artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v2 with: path: docs @@ -58,4 +55,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v3 diff --git a/.github/workflows/test-external.yaml b/.github/workflows/test-external.yaml index ff2ced8..18efa2c 100644 --- a/.github/workflows/test-external.yaml +++ b/.github/workflows/test-external.yaml @@ -20,12 +20,13 @@ jobs: - macos ruby: - - "2.7" - "3.0" - "3.1" + - "3.2" + - "3.3" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 75aa82e..1dca864 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,9 +21,10 @@ jobs: - macos ruby: - - "2.7" - "3.0" - "3.1" + - "3.2" + - "3.3" experimental: [false] @@ -39,7 +40,7 @@ jobs: experimental: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} diff --git a/async-container.gemspec b/async-container.gemspec index eb559ea..50c9e7f 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -17,14 +17,8 @@ Gem::Specification.new do |spec| spec.files = Dir.glob(['{lib}/**/*', '*.md'], File::FNM_DOTMATCH, base: __dir__) - spec.required_ruby_version = ">= 2.5" + spec.required_ruby_version = ">= 3.0" spec.add_dependency "async" spec.add_dependency "async-io" - - spec.add_development_dependency "bake-test" - spec.add_development_dependency "bake-test-external" - spec.add_development_dependency "bundler" - spec.add_development_dependency "covered" - spec.add_development_dependency "sus" end diff --git a/examples/benchmark/scalability.rb b/examples/benchmark/scalability.rb index 013c3e7..be4ba4e 100644 --- a/examples/benchmark/scalability.rb +++ b/examples/benchmark/scalability.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2022, by Samuel Williams. # gem install async-container gem "async-container" diff --git a/examples/controller.rb b/examples/controller.rb index 1712418..8e3b00d 100755 --- a/examples/controller.rb +++ b/examples/controller.rb @@ -2,8 +2,6 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. -# Copyright, 2019, by Yuji Yaginuma. # Copyright, 2022, by Anton Sozontov. require '../lib/async/container/controller' diff --git a/examples/http/client.rb b/examples/http/client.rb index 370997b..856d8b3 100644 --- a/examples/http/client.rb +++ b/examples/http/client.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2022, by Samuel Williams. require 'async' require 'async/http/endpoint' diff --git a/examples/http/server.rb b/examples/http/server.rb index ab4a317..83a49d4 100644 --- a/examples/http/server.rb +++ b/examples/http/server.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2022, by Samuel Williams. require 'async/container' diff --git a/gems.rb b/gems.rb index 26fe797..cd4ecf1 100644 --- a/gems.rb +++ b/gems.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2017-2022, by Samuel Williams. source 'https://rubygems.org' @@ -14,3 +14,11 @@ gem "utopia-project" end + +group :test do + gem "sus" + gem "covered" + + gem "bake-test" + gem "bake-test-external" +end diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index d691261..cf9bbed 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2018-2022, by Samuel Williams. require 'fiber' require 'async/clock' diff --git a/readme.md b/readme.md index 2d3d286..dca5437 100644 --- a/readme.md +++ b/readme.md @@ -25,3 +25,11 @@ We welcome contributions to this project. 3. Commit your changes (`git commit -am 'Add some feature'`). 4. Push to the branch (`git push origin my-new-feature`). 5. Create new Pull Request. + +### Developer Certificate of Origin + +This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted. + +### Contributor Covenant + +This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms. From e6c063852242b2e475c65d74ca412e680f34a13d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 21 Feb 2024 01:56:50 +1300 Subject: [PATCH 076/166] Fix external tests. --- config/external.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/external.yaml b/config/external.yaml index baea42b..e4771e9 100644 --- a/config/external.yaml +++ b/config/external.yaml @@ -1,3 +1,3 @@ falcon: url: https://github.com/socketry/falcon.git - command: bundle exec rspec + command: bundle exec bake test From de10dba4ca0ba5dd3cd34dfb4234c49806bce37e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 21 Feb 2024 01:59:44 +1300 Subject: [PATCH 077/166] Run async-v1 tests on Ruby v3.0. --- .github/workflows/async-v1.yaml | 2 +- gems/async-v1.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/async-v1.yaml b/.github/workflows/async-v1.yaml index a57a7f7..bacb5b8 100644 --- a/.github/workflows/async-v1.yaml +++ b/.github/workflows/async-v1.yaml @@ -12,7 +12,7 @@ jobs: - ubuntu ruby: - - 2.7 + - 3.0 env: BUNDLE_GEMFILE: gems/async-v1.rb diff --git a/gems/async-v1.rb b/gems/async-v1.rb index b32ae2f..3f06b42 100644 --- a/gems/async-v1.rb +++ b/gems/async-v1.rb @@ -5,6 +5,6 @@ source 'https://rubygems.org' -gemspec path: "../" +eval_gemfile "../gems.rb" gem 'async', '~> 1.0' From e7036c79d743c26467be42981c12fa7cfc2086a1 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 21 Feb 2024 02:06:26 +1300 Subject: [PATCH 078/166] Fix async-head workflow. --- gems/async-head.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/async-head.rb b/gems/async-head.rb index 10b94ea..d6c840e 100644 --- a/gems/async-head.rb +++ b/gems/async-head.rb @@ -5,6 +5,6 @@ source 'https://rubygems.org' -gemspec path: "../" +eval_gemfile "../gems.rb" gem 'async', git: "https://github.com/socketry/async" From f0b4b5e078184cc64f052d724f1037860342ed4f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 21 Feb 2024 02:12:08 +1300 Subject: [PATCH 079/166] Prefer `Thread.current.raise` to avoid breaking `Thread.handle_interrupt`. (#30) --- examples/minimal.rb | 4 ++-- lib/async/container/controller.rb | 7 ++++--- lib/async/container/generic.rb | 2 ++ lib/async/container/process.rb | 5 +++-- test/async/container/forked.rb | 27 +++++++++++++++++++++++++++ 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/examples/minimal.rb b/examples/minimal.rb index d637df7..742aaf0 100644 --- a/examples/minimal.rb +++ b/examples/minimal.rb @@ -60,8 +60,8 @@ def initialize(&block) @status = nil @pid = Process.fork do - Signal.trap(:INT) {raise Interrupt} - Signal.trap(:INT) {raise Terminate} + Signal.trap(:INT) {::Thread.current.raise(Interrupt)} + Signal.trap(:INT) {::Thread.current.raise(Terminate)} @channel.in.close diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index db4459e..2db6c50 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -176,16 +176,17 @@ def reload # Enter the controller run loop, trapping `SIGINT` and `SIGTERM`. def run # I thought this was the default... but it doesn't always raise an exception unless you do this explicitly. + # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. interrupt_action = Signal.trap(:INT) do - raise Interrupt + ::Thread.current.raise(Interrupt) end terminate_action = Signal.trap(:TERM) do - raise Terminate + ::Thread.current.raise(Terminate) end hangup_action = Signal.trap(:HUP) do - raise Hangup + ::Thread.current.raise(Hangup) end self.start diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 6147298..05a4ec9 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -49,6 +49,8 @@ def initialize(**options) @keyed = {} end + attr :group + attr :state # A human readable representation of the container. diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 7cd5358..aa24b6f 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -66,8 +66,9 @@ def exec(*arguments, ready: true, **options) def self.fork(**options) self.new(**options) do |process| ::Process.fork do - Signal.trap(:INT) {raise Interrupt} - Signal.trap(:TERM) {raise Terminate} + # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. + Signal.trap(:INT) {::Thread.current.raise(Interrupt)} + Signal.trap(:TERM) {::Thread.current.raise(Terminate)} begin yield Instance.for(process) diff --git a/test/async/container/forked.rb b/test/async/container/forked.rb index 42adf4b..f4ed5d4 100644 --- a/test/async/container/forked.rb +++ b/test/async/container/forked.rb @@ -39,6 +39,33 @@ expect(container.statistics.restarts).to be == 2 end + it "can handle interrupts" do + finished = IO.pipe + interrupted = IO.pipe + + container.spawn(restart: true) do |instance| + Thread.handle_interrupt(Interrupt => :never) do + instance.ready! + + finished.first.gets + rescue ::Interrupt + interrupted.last.puts "incorrectly interrupted" + end + rescue ::Interrupt + interrupted.last.puts "correctly interrupted" + end + + container.wait_until_ready + + container.group.interrupt + sleep(0.001) + finished.last.puts "finished" + + expect(interrupted.first.gets).to be == "correctly interrupted\n" + + container.stop + end + it "should be multiprocess" do expect(subject).to be(:multiprocess?) end From 2e76543b11faf7c8a19dfd99dd8d51d45d95ed75 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 21 Feb 2024 09:53:20 +1300 Subject: [PATCH 080/166] Update copyrights. --- examples/benchmark/scalability.rb | 2 +- examples/controller.rb | 1 + examples/http/client.rb | 2 +- examples/http/server.rb | 2 +- examples/minimal.rb | 2 +- gems.rb | 2 +- gems/async-head.rb | 2 +- gems/async-v1.rb | 2 +- lib/async/container/controller.rb | 2 +- lib/async/container/generic.rb | 2 +- lib/async/container/group.rb | 2 +- lib/async/container/process.rb | 2 +- license.md | 2 +- test/async/container/forked.rb | 2 +- 14 files changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/benchmark/scalability.rb b/examples/benchmark/scalability.rb index be4ba4e..2205140 100644 --- a/examples/benchmark/scalability.rb +++ b/examples/benchmark/scalability.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2022, by Samuel Williams. +# Copyright, 2022-2024, by Samuel Williams. # gem install async-container gem "async-container" diff --git a/examples/controller.rb b/examples/controller.rb index 8e3b00d..1a8021a 100755 --- a/examples/controller.rb +++ b/examples/controller.rb @@ -3,6 +3,7 @@ # Released under the MIT License. # Copyright, 2022, by Anton Sozontov. +# Copyright, 2024, by Samuel Williams. require '../lib/async/container/controller' diff --git a/examples/http/client.rb b/examples/http/client.rb index 856d8b3..f9f5811 100644 --- a/examples/http/client.rb +++ b/examples/http/client.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2022, by Samuel Williams. +# Copyright, 2022-2024, by Samuel Williams. require 'async' require 'async/http/endpoint' diff --git a/examples/http/server.rb b/examples/http/server.rb index 83a49d4..bc657a0 100644 --- a/examples/http/server.rb +++ b/examples/http/server.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2022, by Samuel Williams. +# Copyright, 2022-2024, by Samuel Williams. require 'async/container' diff --git a/examples/minimal.rb b/examples/minimal.rb index 742aaf0..da6c1c6 100644 --- a/examples/minimal.rb +++ b/examples/minimal.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. class Threaded diff --git a/gems.rb b/gems.rb index cd4ecf1..97ff4c7 100644 --- a/gems.rb +++ b/gems.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2022, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. source 'https://rubygems.org' diff --git a/gems/async-head.rb b/gems/async-head.rb index d6c840e..07519eb 100644 --- a/gems/async-head.rb +++ b/gems/async-head.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2021-2022, by Samuel Williams. +# Copyright, 2021-2024, by Samuel Williams. source 'https://rubygems.org' diff --git a/gems/async-v1.rb b/gems/async-v1.rb index 3f06b42..26da9af 100644 --- a/gems/async-v1.rb +++ b/gems/async-v1.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2021-2022, by Samuel Williams. +# Copyright, 2021-2024, by Samuel Williams. source 'https://rubygems.org' diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 2db6c50..b5fa2c3 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2022, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. require_relative 'error' require_relative 'best' diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 05a4ec9..7907ce1 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require 'async' diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index cf9bbed..2f56fb7 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2022, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. require 'fiber' require 'async/clock' diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index aa24b6f..71b274b 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative 'channel' require_relative 'error' diff --git a/license.md b/license.md index d178b01..18e2661 100644 --- a/license.md +++ b/license.md @@ -1,6 +1,6 @@ # MIT License -Copyright, 2017-2022, by Samuel Williams. +Copyright, 2017-2024, by Samuel Williams. Copyright, 2019, by Yuji Yaginuma. Copyright, 2020, by Olle Jonsson. Copyright, 2020, by Juan Antonio Martín Lucas. diff --git a/test/async/container/forked.rb b/test/async/container/forked.rb index f4ed5d4..6ed769d 100644 --- a/test/async/container/forked.rb +++ b/test/async/container/forked.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2022, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. require "async/container" From c7c8fe4d71308d05ece9b8b30f086d0f068819c4 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 21 Feb 2024 09:56:51 +1300 Subject: [PATCH 081/166] Modernize `gems.rb`. --- gems.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gems.rb b/gems.rb index 97ff4c7..ff6ed6e 100644 --- a/gems.rb +++ b/gems.rb @@ -5,11 +5,10 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in utopia.gemspec gemspec group :maintenance, optional: true do - gem "bake-bundler" + gem "bake-gem" gem "bake-modernize" gem "utopia-project" From 83a867e24a11cd75bcf821069884206a7dff8be3 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 21 Feb 2024 09:58:15 +1300 Subject: [PATCH 082/166] Bump patch version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 3bdd15e..7080ec0 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.16.12" + VERSION = "0.16.13" end end From 016c430bfa2f57d0b1234438d487e5982ef7558c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 11 Mar 2024 19:23:21 +1300 Subject: [PATCH 083/166] Allow specification of container class as keyword argument. --- lib/async/container/controller.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index b5fa2c3..e959a5f 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -22,8 +22,9 @@ class Controller # Initialize the controller. # @parameter notify [Notify::Client] A client used for process readiness notifications. - def initialize(notify: Notify.open!) + def initialize(notify: Notify.open!, container_class: Container) @container = nil + @container_class = container_class if @notify = notify @notify.status!("Initializing...") @@ -66,7 +67,7 @@ def trap(signal, &block) # Can be overridden by a sub-class. # @returns [Generic] A specific container instance to use. def create_container - Container.new + @container_class.new end # Whether the controller has a running container. From ddc23de18f0aa94452772e61b5cc5181115cf0e4 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 11 Mar 2024 19:24:13 +1300 Subject: [PATCH 084/166] Minor improvements to error handling/logs. --- lib/async/container/controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index e959a5f..8f0433d 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -115,8 +115,8 @@ def restart begin self.setup(container) - rescue - @notify&.error!($!.to_s) + rescue => error + @notify&.error!(error.to_s) raise SetupError, container end @@ -127,7 +127,7 @@ def restart Console.logger.debug(self, "Finished startup.") if container.failed? - @notify&.error!($!.to_s) + @notify&.error!("Container failed to start!") container.stop @@ -166,7 +166,7 @@ def reload Console.logger.debug(self, "Finished startup.") if @container.failed? - @notify.error!("Container failed!") + @notify.error!("Container failed to reload!") raise SetupError, @container else From 382cfc9e1e301c88b15629f42910eb328268bbae Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 11 Mar 2024 19:24:55 +1300 Subject: [PATCH 085/166] Add note that `Controller#restart` is a blue-green style deployment. --- lib/async/container/controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 8f0433d..8e6e587 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -102,6 +102,7 @@ def stop(graceful = true) end # Restart the container. A new container is created, and if successful, any old container is terminated gracefully. + # This is equivalent to a blue-green deployment. def restart if @container @notify&.restarting! From 3e8b502a71a4803f6c1e9f47f8d8ebc7c82e820d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 11 Mar 2024 19:28:00 +1300 Subject: [PATCH 086/166] Update copyrights. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 7080ec0..676a355 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2022, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. module Async module Container From ccf00c297569cb44e332f77f509b5c562e68392a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 11 Mar 2024 19:33:43 +1300 Subject: [PATCH 087/166] Bump minor version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 676a355..73772aa 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.16.13" + VERSION = "0.17.0" end end From be5e26e175e192cb83efbfa56581d7f36f4d75bd Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 09:50:14 +1300 Subject: [PATCH 088/166] Move successful exit log to debug log level. --- lib/async/container/generic.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 7907ce1..aeabe23 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -161,7 +161,7 @@ def spawn(name: nil, restart: false, key: nil, &block) end if status.success? - Console.logger.info(self) {"#{child} exited with #{status}"} + Console.logger.debug(self) {"#{child} exited with #{status}"} else @statistics.failure! Console.logger.error(self) {status} From 9fb616fe8fef4075858783bc8fe3c968a40bf76f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 09:52:26 +1300 Subject: [PATCH 089/166] Bump patch version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 73772aa..f84d2d5 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.17.0" + VERSION = "0.17.1" end end From db97f985d3de68dc3c71842cd8b4504684a5bb2d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 25 Mar 2024 21:45:03 +1300 Subject: [PATCH 090/166] Ensure the container is stopped if it's not correctly started. (#32) --- lib/async/container/controller.rb | 14 +++++++------ test/async/container/.bad.rb | 28 ++++++++++++++++++++++++++ test/async/container/controller.rb | 32 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) create mode 100755 test/async/container/.bad.rb diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 8e6e587..e68ff20 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -135,18 +135,20 @@ def restart raise SetupError, container end - # Make this swap as atomic as possible: + # The following swap should be atomic: old_container = @container @container = container + container = nil + + if old_container + Console.logger.debug(self, "Stopping old container...") + old_container&.stop + end - Console.logger.debug(self, "Stopping old container...") - old_container&.stop @notify&.ready! - rescue + ensure # If we are leaving this function with an exception, try to kill the container: container&.stop(false) - - raise end # Reload the existing container. Children instances will be reloaded using `SIGHUP`. diff --git a/test/async/container/.bad.rb b/test/async/container/.bad.rb new file mode 100755 index 0000000..8c3b4c2 --- /dev/null +++ b/test/async/container/.bad.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + +require_relative '../../../lib/async/container/controller' + +class Bad < Async::Container::Controller + def setup(container) + container.run(name: "bad", count: 1, restart: true) do |instance| + # Deliberately missing call to `instance.ready!`: + # instance.ready! + + $stdout.puts "Ready..." + $stdout.flush + + sleep + ensure + $stdout.puts "Exiting..." + $stdout.flush + end + end +end + +controller = Bad.new + +controller.run diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index 782cab6..5941fd9 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -88,6 +88,38 @@ def controller.setup(container) end end + with 'bad controller' do + let(:controller_path) {File.expand_path(".bad.rb", __dir__)} + + let(:pipe) {IO.pipe} + let(:input) {pipe.first} + let(:output) {pipe.last} + + let(:pid) {@pid} + + def before + @pid = Process.spawn("bundle", "exec", controller_path, out: output) + output.close + + super + end + + def after + Process.kill(:TERM, @pid) + Process.wait(@pid) + + super + end + + it "fails to start" do + expect(input.gets).to be == "Ready...\n" + + Process.kill(:INT, @pid) + + expect(input.gets).to be == "Exiting...\n" + end + end + with 'signals' do let(:controller_path) {File.expand_path(".dots.rb", __dir__)} From 1dc1482cd6db376fbfe4330ffddc1a5ba477f2d7 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 25 Mar 2024 21:46:47 +1300 Subject: [PATCH 091/166] Remove borked example. --- examples/async.rb | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 examples/async.rb diff --git a/examples/async.rb b/examples/async.rb deleted file mode 100644 index fde882a..0000000 --- a/examples/async.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. - -require 'kernel/sync' - -class Worker - def initialize(&block) - - end -end - -Sync do - count.times do - worker = Worker.new(&block) - - status = worker.wait do |message| - - end - - status.success? - status.failed? - end -end From 088d3e6d08321614794cf45e920a49f182583562 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 27 Mar 2024 12:50:36 +1300 Subject: [PATCH 092/166] Expose `graceful_stop` for controlling graceful shutdown. (#31) --- examples/grace/server.rb | 72 +++++++++++++++++++++++++++ lib/async/container/controller.rb | 19 ++++--- lib/async/container/group.rb | 5 ++ lib/async/container/notify/console.rb | 2 +- test/async/container/.bad.rb | 4 +- test/async/container/.dots.rb | 6 +-- test/async/container/.graceful.rb | 45 +++++++++++++++++ test/async/container/controller.rb | 39 +++++++++++++++ 8 files changed, 180 insertions(+), 12 deletions(-) create mode 100755 examples/grace/server.rb create mode 100755 test/async/container/.graceful.rb diff --git a/examples/grace/server.rb b/examples/grace/server.rb new file mode 100755 index 0000000..2efd5d0 --- /dev/null +++ b/examples/grace/server.rb @@ -0,0 +1,72 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2024, by Samuel Williams. + +require '../../lib/async/container' +require 'async/io/host_endpoint' + +Console.logger.debug! + +module SignalWrapper + def self.trap(signal, &block) + signal = signal + + original = Signal.trap(signal) do + ::Signal.trap(signal, original) + block.call + end + end +end + +class Controller < Async::Container::Controller + def initialize(...) + super + + @endpoint = Async::IO::Endpoint.tcp("localhost", 8080) + @bound_endpoint = nil + end + + def start + Console.debug(self) {"Binding to #{@endpoint}"} + @bound_endpoint = Sync{@endpoint.bound} + + super + end + + def setup(container) + container.run count: 2, restart: true do |instance| + SignalWrapper.trap(:INT) do + Console.debug(self) {"Closing bound instance..."} + @bound_endpoint.close + end + + Sync do |task| + Console.info(self) {"Starting bound instance..."} + + instance.ready! + + @bound_endpoint.accept do |peer| + while true + peer.write("#{Time.now.to_s.rjust(32)}: Hello World\n") + sleep 1 + end + end + end + end + end + + def stop(graceful = true) + super + + if @bound_endpoint + @bound_endpoint.close + @bound_endpoint = nil + end + end +end + +controller = Controller.new + +controller.run diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index e68ff20..294450b 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -22,7 +22,7 @@ class Controller # Initialize the controller. # @parameter notify [Notify::Client] A client used for process readiness notifications. - def initialize(notify: Notify.open!, container_class: Container) + def initialize(notify: Notify.open!, container_class: Container, graceful_stop: true) @container = nil @container_class = container_class @@ -35,6 +35,8 @@ def initialize(notify: Notify.open!, container_class: Container) trap(SIGHUP) do self.restart end + + @graceful_stop = graceful_stop end # The state of the controller. @@ -96,7 +98,7 @@ def start # Stop the container if it's running. # @parameter graceful [Boolean] Whether to give the children instances time to shut down or to kill them immediately. - def stop(graceful = true) + def stop(graceful = @graceful_stop) @container&.stop(graceful) @container = nil end @@ -130,7 +132,7 @@ def restart if container.failed? @notify&.error!("Container failed to start!") - container.stop + container.stop(false) raise SetupError, container end @@ -142,7 +144,7 @@ def restart if old_container Console.logger.debug(self, "Stopping old container...") - old_container&.stop + old_container&.stop(@graceful_stop) end @notify&.ready! @@ -165,7 +167,9 @@ def reload # Wait for all child processes to enter the ready state. Console.logger.debug(self, "Waiting for startup...") + @container.wait_until_ready + Console.logger.debug(self, "Finished startup.") if @container.failed? @@ -182,14 +186,17 @@ def run # I thought this was the default... but it doesn't always raise an exception unless you do this explicitly. # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. interrupt_action = Signal.trap(:INT) do + # $stderr.puts "Received INT signal, terminating...", caller ::Thread.current.raise(Interrupt) end terminate_action = Signal.trap(:TERM) do + # $stderr.puts "Received TERM signal, terminating...", caller ::Thread.current.raise(Terminate) end hangup_action = Signal.trap(:HUP) do + # $stderr.puts "Received HUP signal, restarting...", caller ::Thread.current.raise(Hangup) end @@ -211,11 +218,11 @@ def run end end rescue Interrupt - self.stop(true) + self.stop rescue Terminate self.stop(false) ensure - self.stop(true) + self.stop(false) # Restore the interrupt handler: Signal.trap(:INT, interrupt_action) diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 2f56fb7..f3a0989 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -20,6 +20,10 @@ def initialize @queue = nil end + def inspect + "#<#{self.class} running=#{@running.size}>" + end + # @attribute [Hash(IO, Fiber)] the running tasks, indexed by IO. attr :running @@ -133,6 +137,7 @@ def wait_for(channel) protected def wait_for_children(duration = nil) + Console.debug(self, "Waiting for children...", duration: duration) if !@running.empty? # Maybe consider using a proper event loop here: readable, _, _ = ::IO.select(@running.keys, nil, nil, duration) diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb index ea94f98..0c8b0ef 100644 --- a/lib/async/container/notify/console.rb +++ b/lib/async/container/notify/console.rb @@ -13,7 +13,7 @@ module Notify # Implements a general process readiness protocol with output to the local console. class Console < Client # Open a notification client attached to the current console. - def self.open!(logger = ::Console.logger) + def self.open!(logger = ::Console) self.new(logger) end diff --git a/test/async/container/.bad.rb b/test/async/container/.bad.rb index 8c3b4c2..d655b16 100755 --- a/test/async/container/.bad.rb +++ b/test/async/container/.bad.rb @@ -6,6 +6,8 @@ require_relative '../../../lib/async/container/controller' +$stdout.sync = true + class Bad < Async::Container::Controller def setup(container) container.run(name: "bad", count: 1, restart: true) do |instance| @@ -13,12 +15,10 @@ def setup(container) # instance.ready! $stdout.puts "Ready..." - $stdout.flush sleep ensure $stdout.puts "Exiting..." - $stdout.flush end end end diff --git a/test/async/container/.dots.rb b/test/async/container/.dots.rb index dca311b..8050b0f 100755 --- a/test/async/container/.dots.rb +++ b/test/async/container/.dots.rb @@ -6,17 +6,17 @@ require_relative '../../../lib/async/container/controller' -# Console.logger.debug! +$stdout.sync = true class Dots < Async::Container::Controller def setup(container) container.run(name: "dots", count: 1, restart: true) do |instance| instance.ready! - sleep 1 + # This is to avoid race conditions in the controller in test conditions. + sleep 0.1 $stdout.write "." - $stdout.flush sleep rescue Async::Container::Interrupt diff --git a/test/async/container/.graceful.rb b/test/async/container/.graceful.rb new file mode 100755 index 0000000..f5f1e9d --- /dev/null +++ b/test/async/container/.graceful.rb @@ -0,0 +1,45 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. + +require_relative '../../../lib/async/container/controller' + +$stdout.sync = true + +class Graceful < Async::Container::Controller + def setup(container) + container.run(name: "graceful", count: 1, restart: true) do |instance| + instance.ready! + + # This is to avoid race conditions in the controller in test conditions. + sleep 0.1 + + clock = Async::Clock.start + + original_action = Signal.trap(:INT) do + # We ignore the int, but in practical applications you would want start a graceful shutdown. + $stdout.puts "Graceful shutdown...", clock.total + + Signal.trap(:INT, original_action) + end + + $stdout.puts "Ready...", clock.total + + sleep + ensure + $stdout.puts "Exiting...", clock.total + end + end +end + +controller = Graceful.new(graceful_stop: 1) + +begin + controller.run +rescue Async::Container::Terminate + $stdout.puts "Terminated..." +rescue Interrupt + $stdout.puts "Interrupted..." +end diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index 5941fd9..274ed93 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -88,6 +88,45 @@ def controller.setup(container) end end + with 'graceful controller' do + let(:controller_path) {File.expand_path(".graceful.rb", __dir__)} + + let(:pipe) {IO.pipe} + let(:input) {pipe.first} + let(:output) {pipe.last} + + let(:pid) {@pid} + + def before + @pid = Process.spawn("bundle", "exec", controller_path, out: output) + output.close + + super + end + + def after + Process.kill(:TERM, @pid) + Process.wait(@pid) + + super + end + + it "has graceful shutdown" do + expect(input.gets).to be == "Ready...\n" + start_time = input.gets.to_f + + Process.kill(:INT, @pid) + + expect(input.gets).to be == "Graceful shutdown...\n" + graceful_shutdown_time = input.gets.to_f + + expect(input.gets).to be == "Exiting...\n" + exit_time = input.gets.to_f + + expect(exit_time - graceful_shutdown_time).to be >= 1.0 + end + end + with 'bad controller' do let(:controller_path) {File.expand_path(".bad.rb", __dir__)} From 1cf3c08a2ca7b7656b0a6ef46c4a720b8a4432b7 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 27 Mar 2024 13:15:08 +1300 Subject: [PATCH 093/166] Bump minor version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index f84d2d5..fbd7127 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.17.1" + VERSION = "0.18.0" end end From 1e7da0228c196e396ac4f43be8b11ab58d4edab3 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Apr 2024 14:08:02 +1200 Subject: [PATCH 094/166] Tidy up examples and drop dependency on `async-io`. --- .github/workflows/test-external.yaml | 1 - .github/workflows/test.yaml | 1 - async-container.gemspec | 10 ++- examples/channels/client.rb | 107 -------------------------- examples/fan-out/pipe.rb | 29 +++++++ examples/grace/server.rb | 2 +- examples/http/client.rb | 1 + examples/http/server.rb | 17 ++-- examples/queue/server.rb | 25 +++--- examples/threads.rb | 28 ------- examples/title.rb | 16 ---- examples/udppipe.rb | 38 --------- lib/async/container/notify/console.rb | 2 +- test.rb | 19 +++++ test/async/container/.bad.rb | 2 +- test/async/container/.dots.rb | 2 +- test/async/container/.graceful.rb | 2 +- test/async/container/controller.rb | 2 +- 18 files changed, 79 insertions(+), 225 deletions(-) delete mode 100644 examples/channels/client.rb create mode 100755 examples/fan-out/pipe.rb mode change 100644 => 100755 examples/http/client.rb mode change 100644 => 100755 examples/http/server.rb mode change 100644 => 100755 examples/queue/server.rb delete mode 100755 examples/threads.rb delete mode 100755 examples/title.rb delete mode 100644 examples/udppipe.rb create mode 100644 test.rb diff --git a/.github/workflows/test-external.yaml b/.github/workflows/test-external.yaml index 18efa2c..21898f5 100644 --- a/.github/workflows/test-external.yaml +++ b/.github/workflows/test-external.yaml @@ -20,7 +20,6 @@ jobs: - macos ruby: - - "3.0" - "3.1" - "3.2" - "3.3" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1dca864..0769a98 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,7 +21,6 @@ jobs: - macos ruby: - - "3.0" - "3.1" - "3.2" - "3.3" diff --git a/async-container.gemspec b/async-container.gemspec index 50c9e7f..36c71fa 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -15,10 +15,14 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/socketry/async-container" + spec.metadata = { + "documentation_uri" => "https://socketry.github.io/async-container/", + "source_code_uri" => "https://github.com/socketry/async-container.git", + } + spec.files = Dir.glob(['{lib}/**/*', '*.md'], File::FNM_DOTMATCH, base: __dir__) - spec.required_ruby_version = ">= 3.0" + spec.required_ruby_version = ">= 3.1" - spec.add_dependency "async" - spec.add_dependency "async-io" + spec.add_dependency "async", "~> 2.10" end diff --git a/examples/channels/client.rb b/examples/channels/client.rb deleted file mode 100644 index 3a6424a..0000000 --- a/examples/channels/client.rb +++ /dev/null @@ -1,107 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. -# Copyright, 2020, by Olle Jonsson. - -require 'msgpack' -require 'async/io' -require 'async/io/stream' -require 'async/container' - -# class Bus -# def initialize -# -# end -# -# def << object -# :object -# end -# -# def [] key -# return -# end -# end -# -# class Proxy < BasicObject -# def initialize(bus, name) -# @bus = bus -# @name = name -# end -# -# def inspect -# "[Proxy #{method_missing(:inspect)}]" -# end -# -# def method_missing(*args, &block) -# @bus.invoke(@name, args, &block) -# end -# -# def respond_to?(*args) -# @bus.invoke(@name, ["respond_to?", *args]) -# end -# end -# -# class Wrapper < MessagePack::Factory -# def initialize(bus) -# super() -# -# self.register_type(0x00, Object, -# packer: @bus.method(:<<), -# unpacker: @bus.method(:[]) -# ) -# -# self.register_type(0x01, Symbol) -# self.register_type(0x02, Exception, -# packer: ->(exception){Marshal.dump(exception)}, -# unpacker: ->(data){Marshal.load(data)}, -# ) -# -# self.register_type(0x03, Class, -# packer: ->(klass){Marshal.dump(klass)}, -# unpacker: ->(data){Marshal.load(data)}, -# ) -# end -# end -# -# class Channel -# def self.pipe -# input, output = Async::IO.pipe -# end -# -# def initialize(input, output) -# @input = input -# @output = output -# end -# -# def read -# @input.read -# end -# -# def write -# end -# end - -container = Async::Container.new -input, output = Async::IO.pipe - -container.async do |instance| - stream = Async::IO::Stream.new(input) - output.close - - while message = stream.gets - puts "Hello World from #{instance}: #{message}" - end - - puts "exiting" -end - -stream = Async::IO::Stream.new(output) - -5.times do |i| - stream.puts "#{i}" -end - -stream.close - -container.wait diff --git a/examples/fan-out/pipe.rb b/examples/fan-out/pipe.rb new file mode 100755 index 0000000..e68876b --- /dev/null +++ b/examples/fan-out/pipe.rb @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. + +require 'async/container' + +container = Async::Container.new +input, output = IO.pipe + +container.async do |instance| + output.close + + while message = input.gets + puts "Hello World from #{instance}: #{message}" + end + + puts "exiting" +end + +5.times do |i| + output.puts "#{i}" +end + +output.close + +container.wait diff --git a/examples/grace/server.rb b/examples/grace/server.rb index 2efd5d0..0d26003 100755 --- a/examples/grace/server.rb +++ b/examples/grace/server.rb @@ -24,7 +24,7 @@ class Controller < Async::Container::Controller def initialize(...) super - @endpoint = Async::IO::Endpoint.tcp("localhost", 8080) + @endpoint = ::IO::Endpoint.tcp("localhost", 8080) @bound_endpoint = nil end diff --git a/examples/http/client.rb b/examples/http/client.rb old mode 100644 new mode 100755 index f9f5811..bf4e374 --- a/examples/http/client.rb +++ b/examples/http/client.rb @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby # frozen_string_literal: true # Released under the MIT License. diff --git a/examples/http/server.rb b/examples/http/server.rb old mode 100644 new mode 100755 index bc657a0..2298577 --- a/examples/http/server.rb +++ b/examples/http/server.rb @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby # frozen_string_literal: true # Released under the MIT License. @@ -12,28 +13,20 @@ container = Async::Container::Forked.new endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292") +bound_endpoint = Sync{endpoint.bound} -bound_endpoint = Async::Reactor.run do - Async::IO::SharedEndpoint.bound(endpoint) -end.wait - -input, output = Async::IO.pipe -input.write(".") - -Async.logger.info(endpoint) {"Bound to #{bound_endpoint.inspect}"} +Console.info(endpoint) {"Bound to #{bound_endpoint.inspect}"} GC.start GC.compact if GC.respond_to?(:compact) container.run(count: 16, restart: true) do Async do |task| - server = Async::HTTP::Server.for(bound_endpoint, endpoint.protocol, endpoint.scheme) do |request| + server = Async::HTTP::Server.for(bound_endpoint, protocol: endpoint.protocol, scheme: endpoint.scheme) do |request| Protocol::HTTP::Response[200, {}, ["Hello World"]] end - output.read(1) - Async.logger.info(server) {"Starting server..."} - output.write(".") + Console.info(server) {"Starting server..."} server.run diff --git a/examples/queue/server.rb b/examples/queue/server.rb old mode 100644 new mode 100755 index 8c8c39f..e63e7b0 --- a/examples/queue/server.rb +++ b/examples/queue/server.rb @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby # frozen_string_literal: true # Released under the MIT License. @@ -5,8 +6,8 @@ require 'async' require 'async/container' -require 'async/io/unix_endpoint' -require 'async/io/shared_endpoint' +require 'io/endpoint' +require 'io/endpoint/unix_endpoint' require 'msgpack' class Wrapper < MessagePack::Factory @@ -28,24 +29,22 @@ def initialize end end -endpoint = Async::IO::Endpoint.unix('test.ipc') +endpoint = IO::Endpoint.unix('test.ipc') +bound_endpoint = endpoint.bound + wrapper = Wrapper.new container = Async::Container.new -bound_endpoint = Sync do - Async::IO::SharedEndpoint.bound(endpoint) -end - container.spawn do |instance| Async do queue = 500_000.times.to_a - Console.logger.info(self) {"Hosting the queue..."} + Console.info(self) {"Hosting the queue..."} instance.ready! bound_endpoint.accept do |peer| - Console.logger.info(self) {"Incoming connection from #{peer}..."} + Console.info(self) {"Incoming connection from #{peer}..."} packer = wrapper.packer(peer) unpacker = wrapper.unpacker(peer) @@ -63,9 +62,9 @@ def initialize break end when :status - # Console.logger.info("Job Status") {arguments} + Console.info("Job Status") {arguments} else - Console.logger.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"} + Console.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"} end end end @@ -93,7 +92,7 @@ def initialize packer.write([:ready]) packer.flush else - Console.logger.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"} + Console.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"} end end end @@ -102,4 +101,4 @@ def initialize container.wait -Console.logger.info(self) {"Done!"} +Console.info(self) {"Done!"} diff --git a/examples/threads.rb b/examples/threads.rb deleted file mode 100755 index def31e4..0000000 --- a/examples/threads.rb +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. - -puts "Process pid: #{Process.pid}" - -threads = 10.times.collect do - Thread.new do - begin - sleep - rescue Exception - puts "Thread: #{$!}" - end - end -end - -while true - begin - threads.each(&:join) - exit(0) - rescue Exception - puts "Join: #{$!}" - end -end - -puts "Done" diff --git a/examples/title.rb b/examples/title.rb deleted file mode 100755 index d7f0be8..0000000 --- a/examples/title.rb +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2018-2022, by Samuel Williams. - -Process.setproctitle "Preparing for sleep..." - -10.times do |i| - puts "Counting sheep #{i}" - Process.setproctitle "Counting sheep #{i}" - - sleep 10 -end - -puts "Zzzzzzz" diff --git a/examples/udppipe.rb b/examples/udppipe.rb deleted file mode 100644 index 1637368..0000000 --- a/examples/udppipe.rb +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. - -require 'async/io' -require 'async/io/endpoint' -require 'async/io/unix_endpoint' - -@endpoint = Async::IO::Endpoint.unix("/tmp/notify-test.sock", Socket::SOCK_DGRAM) -# address = Async::IO::Address.udp("127.0.0.1", 6778) -# @endpoint = Async::IO::AddressEndpoint.new(address) - -def server - @endpoint.bind do |server| - puts "Receiving..." - packet, address = server.recvfrom(512) - - puts "Received: #{packet} from #{address}" - end -end - -def client(data = "Hello World!") - @endpoint.connect do |peer| - puts "Sending: #{data}" - peer.send(data) - puts "Sent!" - end -end - -Async do |task| - server_task = task.async do - server - end - - client -end diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb index 0c8b0ef..96a1bd6 100644 --- a/lib/async/container/notify/console.rb +++ b/lib/async/container/notify/console.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative 'client' diff --git a/test.rb b/test.rb new file mode 100644 index 0000000..dfb6076 --- /dev/null +++ b/test.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +Thread.handle_interrupt(RuntimeError => :never) do + Thread.current.raise(RuntimeError, "Queued error") + + puts "Pending interrupt: #{Thread.pending_interrupt?}" # true + + pid = Process.fork do + puts "Pending interrupt (child process): #{Thread.pending_interrupt?}" + Thread.handle_interrupt(RuntimeError => :immediate){} + end + + _, status = Process.waitpid2(pid) + puts "Child process status: #{status.inspect}" + + puts "Pending interrupt: #{Thread.pending_interrupt?}" # false +end + +puts "Exiting..." diff --git a/test/async/container/.bad.rb b/test/async/container/.bad.rb index d655b16..b20309e 100755 --- a/test/async/container/.bad.rb +++ b/test/async/container/.bad.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2024, by Samuel Williams. require_relative '../../../lib/async/container/controller' diff --git a/test/async/container/.dots.rb b/test/async/container/.dots.rb index 8050b0f..bafd525 100755 --- a/test/async/container/.dots.rb +++ b/test/async/container/.dots.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative '../../../lib/async/container/controller' diff --git a/test/async/container/.graceful.rb b/test/async/container/.graceful.rb index f5f1e9d..bc16a57 100755 --- a/test/async/container/.graceful.rb +++ b/test/async/container/.graceful.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2024, by Samuel Williams. require_relative '../../../lib/async/container/controller' diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index 274ed93..7900a98 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2022, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. require "async/container/controller" From 57b62f951d29169df9cfc3596ff0279ed841df03 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Apr 2024 14:20:23 +1200 Subject: [PATCH 095/166] Bump patch version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index fbd7127..9b71193 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.18.0" + VERSION = "0.18.1" end end From 3937b252145b584be6bbb0a4b753a2fdf86f8a44 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Apr 2024 15:14:55 +1200 Subject: [PATCH 096/166] Missed `async-io` in a few places... --- examples/fan-out/pipe.rb | 3 +-- examples/grace/server.rb | 2 +- examples/http/server.rb | 1 - examples/queue/server.rb | 2 +- lib/async/container/notify/server.rb | 14 +++----------- lib/async/container/notify/socket.rb | 9 ++++----- test.rb | 19 ------------------- 7 files changed, 10 insertions(+), 40 deletions(-) delete mode 100644 test.rb diff --git a/examples/fan-out/pipe.rb b/examples/fan-out/pipe.rb index e68876b..97c84fe 100755 --- a/examples/fan-out/pipe.rb +++ b/examples/fan-out/pipe.rb @@ -2,8 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. -# Copyright, 2020, by Olle Jonsson. +# Copyright, 2020-2024, by Samuel Williams. require 'async/container' diff --git a/examples/grace/server.rb b/examples/grace/server.rb index 0d26003..5a6b236 100755 --- a/examples/grace/server.rb +++ b/examples/grace/server.rb @@ -5,7 +5,7 @@ # Copyright, 2024, by Samuel Williams. require '../../lib/async/container' -require 'async/io/host_endpoint' +require 'io/endpoint/host_endpoint' Console.logger.debug! diff --git a/examples/http/server.rb b/examples/http/server.rb index 2298577..611fd29 100755 --- a/examples/http/server.rb +++ b/examples/http/server.rb @@ -8,7 +8,6 @@ require 'async/http/endpoint' require 'async/http/server' -require 'async/io/shared_endpoint' container = Async::Container::Forked.new diff --git a/examples/queue/server.rb b/examples/queue/server.rb index e63e7b0..159b205 100755 --- a/examples/queue/server.rb +++ b/examples/queue/server.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require 'async' require 'async/container' diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index 17aaa1a..5ab2c6d 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. -require 'async/io' -require 'async/io/unix_endpoint' require 'kernel/sync' require 'tmpdir' @@ -62,19 +60,13 @@ def bind class Context def initialize(path) @path = path - @endpoint = IO::Endpoint.unix(@path, ::Socket::SOCK_DGRAM) - - Sync do - @bound = @endpoint.bind - end + @bound = Addrinfo.unix(@path, ::Socket::SOCK_DGRAM).bind @state = {} end def close - Sync do - @bound.close - end + @bound.close File.unlink(@path) end diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index 6b01c39..f1bfdd1 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative 'client' -require 'async/io' -require 'async/io/unix_endpoint' +require 'io/endpoint/unix_endpoint' require 'kernel/sync' module Async @@ -32,7 +31,7 @@ def self.open!(environment = ENV) # @parameter path [String] The path to the UNIX socket used for sending messages to the process manager. def initialize(path) @path = path - @endpoint = IO::Endpoint.unix(path, ::Socket::SOCK_DGRAM) + @endpoint = ::IO::Endpoint.unix(path, ::Socket::SOCK_DGRAM) end # Dump a message in the format requied by `sd_notify`. @@ -65,7 +64,7 @@ def send(**message) Sync do @endpoint.connect do |peer| - peer.send(data) + peer.sendmsg(data) end end end diff --git a/test.rb b/test.rb deleted file mode 100644 index dfb6076..0000000 --- a/test.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -Thread.handle_interrupt(RuntimeError => :never) do - Thread.current.raise(RuntimeError, "Queued error") - - puts "Pending interrupt: #{Thread.pending_interrupt?}" # true - - pid = Process.fork do - puts "Pending interrupt (child process): #{Thread.pending_interrupt?}" - Thread.handle_interrupt(RuntimeError => :immediate){} - end - - _, status = Process.waitpid2(pid) - puts "Child process status: #{status.inspect}" - - puts "Pending interrupt: #{Thread.pending_interrupt?}" # false -end - -puts "Exiting..." From e57d83a3601364de1207701a399cacbd46f84d53 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Apr 2024 15:19:50 +1200 Subject: [PATCH 097/166] Directly connect in `Notify`. --- lib/async/container/notify/socket.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index f1bfdd1..0ac82d4 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -31,7 +31,7 @@ def self.open!(environment = ENV) # @parameter path [String] The path to the UNIX socket used for sending messages to the process manager. def initialize(path) @path = path - @endpoint = ::IO::Endpoint.unix(path, ::Socket::SOCK_DGRAM) + @address = Addrinfo.unix(path, ::Socket::SOCK_DGRAM) end # Dump a message in the format requied by `sd_notify`. @@ -62,10 +62,8 @@ def send(**message) raise ArgumentError, "Message length #{message.bytesize} exceeds #{MAXIMUM_MESSAGE_SIZE}: #{message.inspect}" end - Sync do - @endpoint.connect do |peer| - peer.sendmsg(data) - end + @address.connect do |peer| + peer.sendmsg(data) end end From 4c4aa95dc0fb473e94970150e74dd0494e27ea25 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Apr 2024 15:20:53 +1200 Subject: [PATCH 098/166] No longer need to pull in `Sync`. --- lib/async/container/notify/server.rb | 2 -- lib/async/container/notify/socket.rb | 3 --- 2 files changed, 5 deletions(-) diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index 5ab2c6d..415be55 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -4,8 +4,6 @@ # Copyright, 2020-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. -require 'kernel/sync' - require 'tmpdir' require 'securerandom' diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index 0ac82d4..34ce9cd 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -5,9 +5,6 @@ require_relative 'client' -require 'io/endpoint/unix_endpoint' -require 'kernel/sync' - module Async module Container module Notify From e7ac92247834d983d3b683af112b3ae403b9f09c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Apr 2024 15:25:26 +1200 Subject: [PATCH 099/166] Remove old tests for async v1 and async head. --- .github/workflows/async-head.yaml | 29 ----------------------------- .github/workflows/async-v1.yaml | 29 ----------------------------- 2 files changed, 58 deletions(-) delete mode 100644 .github/workflows/async-head.yaml delete mode 100644 .github/workflows/async-v1.yaml diff --git a/.github/workflows/async-head.yaml b/.github/workflows/async-head.yaml deleted file mode 100644 index d8dfd74..0000000 --- a/.github/workflows/async-head.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: Async head - -on: [push, pull_request] - -jobs: - test: - runs-on: ${{matrix.os}}-latest - - strategy: - matrix: - os: - - ubuntu - - ruby: - - head - - env: - BUNDLE_GEMFILE: gems/async-head.rb - - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{matrix.ruby}} - bundler-cache: true - - - name: Run tests - timeout-minutes: 5 - run: bundle exec sus diff --git a/.github/workflows/async-v1.yaml b/.github/workflows/async-v1.yaml deleted file mode 100644 index bacb5b8..0000000 --- a/.github/workflows/async-v1.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: Async v1 - -on: [push, pull_request] - -jobs: - test: - runs-on: ${{matrix.os}}-latest - - strategy: - matrix: - os: - - ubuntu - - ruby: - - 3.0 - - env: - BUNDLE_GEMFILE: gems/async-v1.rb - - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{matrix.ruby}} - bundler-cache: true - - - name: Run tests - timeout-minutes: 5 - run: bundle exec sus From c4e536ce0bd7c01a9fe3f38a9fba1b86cbcd2d64 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Apr 2024 15:29:02 +1200 Subject: [PATCH 100/166] Bump patch version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 9b71193..cdf80df 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.18.1" + VERSION = "0.18.2" end end From 33103f26047709a5eaa420ad694e2560b962ca0d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 13 Aug 2024 22:56:53 +1200 Subject: [PATCH 101/166] Modernize gem. --- .github/workflows/documentation-coverage.yaml | 25 ++++++++++ .github/workflows/documentation.yaml | 4 +- .github/workflows/rubocop.yaml | 24 ++++++++++ .../{coverage.yaml => test-coverage.yaml} | 4 +- .rubocop.yml | 46 +++++++++++++++++++ examples/container.rb | 10 ++-- examples/controller.rb | 46 +++++++++---------- gems.rb | 2 + lib/async/container/generic.rb | 2 - lib/async/container/process.rb | 3 +- readme.md | 6 +-- test/async/container/controller.rb | 6 +-- 12 files changed, 136 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/documentation-coverage.yaml create mode 100644 .github/workflows/rubocop.yaml rename .github/workflows/{coverage.yaml => test-coverage.yaml} (97%) create mode 100644 .rubocop.yml diff --git a/.github/workflows/documentation-coverage.yaml b/.github/workflows/documentation-coverage.yaml new file mode 100644 index 0000000..b3bac9a --- /dev/null +++ b/.github/workflows/documentation-coverage.yaml @@ -0,0 +1,25 @@ +name: Documentation Coverage + +on: [push, pull_request] + +permissions: + contents: read + +env: + CONSOLE_OUTPUT: XTerm + COVERAGE: PartialSummary + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + bundler-cache: true + + - name: Validate coverage + timeout-minutes: 5 + run: bundle exec bake decode:index:coverage lib diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 8dc5227..f5f553a 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -40,7 +40,7 @@ jobs: run: bundle exec bake utopia:project:static --force no - name: Upload documentation artifact - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: path: docs @@ -55,4 +55,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v3 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/rubocop.yaml b/.github/workflows/rubocop.yaml new file mode 100644 index 0000000..287c06d --- /dev/null +++ b/.github/workflows/rubocop.yaml @@ -0,0 +1,24 @@ +name: RuboCop + +on: [push, pull_request] + +permissions: + contents: read + +env: + CONSOLE_OUTPUT: XTerm + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby + bundler-cache: true + + - name: Run RuboCop + timeout-minutes: 10 + run: bundle exec rubocop diff --git a/.github/workflows/coverage.yaml b/.github/workflows/test-coverage.yaml similarity index 97% rename from .github/workflows/coverage.yaml rename to .github/workflows/test-coverage.yaml index 68adbf2..f9da2ff 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -1,4 +1,4 @@ -name: Coverage +name: Test Coverage on: [push, pull_request] @@ -33,7 +33,7 @@ jobs: - name: Run tests timeout-minutes: 5 run: bundle exec bake test - + - uses: actions/upload-artifact@v3 with: name: coverage-${{matrix.os}}-${{matrix.ruby}} diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..442c667 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,46 @@ +AllCops: + DisabledByDefault: true + +Layout/IndentationStyle: + Enabled: true + EnforcedStyle: tabs + +Layout/InitialIndentation: + Enabled: true + +Layout/IndentationWidth: + Enabled: true + Width: 1 + +Layout/IndentationConsistency: + Enabled: true + EnforcedStyle: normal + +Layout/EndAlignment: + Enabled: true + EnforcedStyleAlignWith: start_of_line + +Layout/BeginEndAlignment: + Enabled: true + EnforcedStyleAlignWith: start_of_line + +Layout/ElseAlignment: + Enabled: true + +Layout/DefEndAlignment: + Enabled: true + +Layout/CaseIndentation: + Enabled: true + +Layout/CommentIndentation: + Enabled: true + +Layout/EmptyLinesAroundClassBody: + Enabled: true + +Layout/EmptyLinesAroundModuleBody: + Enabled: true + +Style/FrozenStringLiteralComment: + Enabled: true diff --git a/examples/container.rb b/examples/container.rb index 301b1c0..b76e971 100755 --- a/examples/container.rb +++ b/examples/container.rb @@ -15,11 +15,11 @@ Console.logger.debug "Spawning 2 containers..." 2.times do - container.spawn do |task| - Console.logger.debug task, "Sleeping..." - sleep(2) - Console.logger.debug task, "Waking up!" - end + container.spawn do |task| + Console.logger.debug task, "Sleeping..." + sleep(2) + Console.logger.debug task, "Waking up!" + end end Console.logger.debug "Waiting for container..." diff --git a/examples/controller.rb b/examples/controller.rb index 1a8021a..0341d2e 100755 --- a/examples/controller.rb +++ b/examples/controller.rb @@ -8,29 +8,29 @@ require '../lib/async/container/controller' class Controller < Async::Container::Controller - def setup(container) - container.run(count: 1, restart: true) do |instance| - if container.statistics.failed? - Console.logger.debug(self, "Child process restarted #{container.statistics.restarts} times.") - else - Console.logger.debug(self, "Child process started.") - end - - instance.ready! - - while true - sleep 1 - - Console.logger.debug(self, "Work") - - if rand < 0.5 - Console.logger.debug(self, "Should exit...") - sleep 0.5 - exit(1) - end - end - end - end + def setup(container) + container.run(count: 1, restart: true) do |instance| + if container.statistics.failed? + Console.logger.debug(self, "Child process restarted #{container.statistics.restarts} times.") + else + Console.logger.debug(self, "Child process started.") + end + + instance.ready! + + while true + sleep 1 + + Console.logger.debug(self, "Work") + + if rand < 0.5 + Console.logger.debug(self, "Should exit...") + sleep 0.5 + exit(1) + end + end + end + end end Console.logger.debug! diff --git a/gems.rb b/gems.rb index ff6ed6e..a4e6eb9 100644 --- a/gems.rb +++ b/gems.rb @@ -17,6 +17,8 @@ group :test do gem "sus" gem "covered" + gem "decode" + gem "rubocop" gem "bake-test" gem "bake-test-external" diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index aeabe23..956bdfe 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -173,8 +173,6 @@ def spawn(name: nil, restart: false, key: nil, &block) break end end - # ensure - # Console.logger.error(self) {$!} if $! end.resume return true diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 71b274b..42370d1 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -56,8 +56,7 @@ def exec(*arguments, ready: true, **options) self.before_spawn(arguments, options) end - # TODO prefer **options... but it doesn't support redirections on < 2.7 - ::Process.exec(*arguments, options) + ::Process.exec(*arguments, **options) end end diff --git a/readme.md b/readme.md index dca5437..98ca4df 100644 --- a/readme.md +++ b/readme.md @@ -28,8 +28,8 @@ We welcome contributions to this project. ### Developer Certificate of Origin -This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted. +In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed. -### Contributor Covenant +### Community Guidelines -This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms. +This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers. diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index 7900a98..01aeb67 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -104,7 +104,7 @@ def before super end - def after + def after(error = nil) Process.kill(:TERM, @pid) Process.wait(@pid) @@ -143,7 +143,7 @@ def before super end - def after + def after(error = nil) Process.kill(:TERM, @pid) Process.wait(@pid) @@ -175,7 +175,7 @@ def before super end - def after + def after(error = nil) Process.kill(:TERM, @pid) Process.wait(@pid) From d60385cd6d09220b0c11a0063a8ab49953a9a91f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 4 Sep 2024 15:16:18 +1200 Subject: [PATCH 102/166] Tidy up `AContainer` usage. --- fixtures/a_container.rb | 95 ------------------------ fixtures/async/container/a_container.rb | 99 +++++++++++++++++++++++++ test/async/container/forked.rb | 6 +- test/async/container/hybrid.rb | 9 +-- test/async/container/threaded.rb | 5 +- 5 files changed, 107 insertions(+), 107 deletions(-) delete mode 100644 fixtures/a_container.rb create mode 100644 fixtures/async/container/a_container.rb diff --git a/fixtures/a_container.rb b/fixtures/a_container.rb deleted file mode 100644 index 854e613..0000000 --- a/fixtures/a_container.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. - -AContainer = Sus::Shared("a container") do - let(:container) {subject.new} - - it "can run concurrently" do - input, output = IO.pipe - - container.async do - output.write "Hello World" - end - - container.wait - - output.close - expect(input.read).to be == "Hello World" - end - - it "can run concurrently" do - container.async(name: "Sleepy Jerry") do |task, instance| - 3.times do |i| - instance.name = "Counting Sheep #{i}" - - sleep 0.01 - end - end - - container.wait - end - - it "should be blocking" do - skip "Fiber.blocking? is not supported!" unless Fiber.respond_to?(:blocking?) - - input, output = IO.pipe - - container.spawn do - output.write(Fiber.blocking? != false) - end - - container.wait - - output.close - expect(input.read).to be == "true" - end - - with '#sleep' do - it "can sleep for a short time" do - container.spawn do - sleep(0.01) - raise "Boom" - end - - expect(container.statistics).to have_attributes(failures: be == 0) - - container.wait - - expect(container.statistics).to have_attributes(failures: be == 1) - end - end - - with '#stop' do - it 'can stop the child process' do - container.spawn do - sleep(1) - end - - expect(container).to be(:running?) - - container.stop - - expect(container).not.to be(:running?) - end - end - - with '#ready' do - it "can notify the ready pipe in an asynchronous context" do - container.run do |instance| - Async do - instance.ready! - end - end - - expect(container).to be(:running?) - - container.wait - - container.stop - - expect(container).not.to be(:running?) - end - end -end diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb new file mode 100644 index 0000000..200ba0b --- /dev/null +++ b/fixtures/async/container/a_container.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2019-2022, by Samuel Williams. + +module Async + module Container + AContainer = Sus::Shared("a container") do + let(:container) {subject.new} + + it "can run concurrently" do + input, output = IO.pipe + + container.async do + output.write "Hello World" + end + + container.wait + + output.close + expect(input.read).to be == "Hello World" + end + + it "can run concurrently" do + container.async(name: "Sleepy Jerry") do |task, instance| + 3.times do |i| + instance.name = "Counting Sheep #{i}" + + sleep 0.01 + end + end + + container.wait + end + + it "should be blocking" do + skip "Fiber.blocking? is not supported!" unless Fiber.respond_to?(:blocking?) + + input, output = IO.pipe + + container.spawn do + output.write(Fiber.blocking? != false) + end + + container.wait + + output.close + expect(input.read).to be == "true" + end + + with '#sleep' do + it "can sleep for a short time" do + container.spawn do + sleep(0.01) + raise "Boom" + end + + expect(container.statistics).to have_attributes(failures: be == 0) + + container.wait + + expect(container.statistics).to have_attributes(failures: be == 1) + end + end + + with '#stop' do + it 'can stop the child process' do + container.spawn do + sleep(1) + end + + expect(container).to be(:running?) + + container.stop + + expect(container).not.to be(:running?) + end + end + + with '#ready' do + it "can notify the ready pipe in an asynchronous context" do + container.run do |instance| + Async do + instance.ready! + end + end + + expect(container).to be(:running?) + + container.wait + + container.stop + + expect(container).not.to be(:running?) + end + end + end + end +end diff --git a/test/async/container/forked.rb b/test/async/container/forked.rb index 6ed769d..4c93fb5 100644 --- a/test/async/container/forked.rb +++ b/test/async/container/forked.rb @@ -4,15 +4,13 @@ # Copyright, 2018-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. -require "async/container" require "async/container/forked" - -require 'a_container' +require "async/container/a_container" describe Async::Container::Forked do let(:container) {subject.new} - it_behaves_like AContainer + it_behaves_like Async::Container::AContainer it "can restart child" do trigger = IO.pipe diff --git a/test/async/container/hybrid.rb b/test/async/container/hybrid.rb index 2e3ad75..ed3b13d 100644 --- a/test/async/container/hybrid.rb +++ b/test/async/container/hybrid.rb @@ -3,13 +3,12 @@ # Released under the MIT License. # Copyright, 2019-2022, by Samuel Williams. -require 'async/container/hybrid' -require 'async/container/best' - -require 'a_container' +require "async/container/hybrid" +require "async/container/best" +require "async/container/a_container" describe Async::Container::Hybrid do - it_behaves_like AContainer + it_behaves_like Async::Container::AContainer it "should be multiprocess" do expect(subject).to be(:multiprocess?) diff --git a/test/async/container/threaded.rb b/test/async/container/threaded.rb index c2ece0d..b18dee6 100644 --- a/test/async/container/threaded.rb +++ b/test/async/container/threaded.rb @@ -4,11 +4,10 @@ # Copyright, 2018-2022, by Samuel Williams. require "async/container/threaded" - -require 'a_container' +require "async/container/a_container" describe Async::Container::Threaded do - it_behaves_like AContainer + it_behaves_like Async::Container::AContainer it "should not be multiprocess" do expect(subject).not.to be(:multiprocess?) From ce1c1b81e0ce6da658988a53d71b41e7536d6ff7 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 4 Sep 2024 15:19:19 +1200 Subject: [PATCH 103/166] Remove unnecessary trailing `if ready`. --- lib/async/container/process.rb | 2 +- lib/async/container/thread.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 42370d1..123083b 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -51,7 +51,7 @@ def name # This method replaces the child process with the new executable, thus this method never returns. def exec(*arguments, ready: true, **options) if ready - self.ready!(status: "(exec)") if ready + self.ready!(status: "(exec)") else self.before_spawn(arguments, options) end diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index bf21ba5..942c85c 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -66,7 +66,7 @@ def name # This creates the illusion that this method does not return (normally). def exec(*arguments, ready: true, **options) if ready - self.ready!(status: "(spawn)") if ready + self.ready!(status: "(spawn)") else self.before_spawn(arguments, options) end From 19a6bd8ee11248b5371b16184c209e39dd72de21 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 4 Sep 2024 15:20:36 +1200 Subject: [PATCH 104/166] Updated syntax. --- lib/async/container/thread.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index 942c85c..53fe5d3 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -72,8 +72,7 @@ def exec(*arguments, ready: true, **options) end begin - # TODO prefer **options... but it doesn't support redirections on < 2.7 - pid = ::Process.spawn(*arguments, options) + pid = ::Process.spawn(*arguments, **options) ensure _, status = ::Process.wait2(pid) From 0285752cd84c97e7e8ab79531217a39228a6cbdd Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 4 Sep 2024 15:24:46 +1200 Subject: [PATCH 105/166] Add test for setting current working directory. --- test/async/container/.cwd.rb | 23 +++++++++++++++++++++++ test/async/container/controller.rb | 15 +++++++++++++++ 2 files changed, 38 insertions(+) create mode 100755 test/async/container/.cwd.rb diff --git a/test/async/container/.cwd.rb b/test/async/container/.cwd.rb new file mode 100755 index 0000000..fd0e032 --- /dev/null +++ b/test/async/container/.cwd.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2024, by Samuel Williams. + +require_relative '../../../lib/async/container/controller' + +$stdout.sync = true + +class Pwd < Async::Container::Controller + def setup(container) + container.spawn do |instance| + instance.ready! + + instance.exec("pwd", chdir: "/") + end + end +end + +controller = Pwd.new + +controller.run diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index 01aeb67..53b1f47 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -206,4 +206,19 @@ def after(error = nil) expect(input.read).to be == 'T' end end + + with 'working directory' do + let(:controller_path) {File.expand_path(".cwd.rb", __dir__)} + + it "can change working directory" do + pipe = IO.pipe + + pid = Process.spawn("bundle", "exec", controller_path, out: pipe.last) + pipe.last.close + + expect(pipe.first.gets(chomp: true)).to be == "/" + ensure + Process.kill(:INT, pid) if pid + end + end end From cd80f9601efe995e99dd4abf3cc34cf2acf91a15 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 4 Sep 2024 15:25:12 +1200 Subject: [PATCH 106/166] Modernize gem. --- .github/workflows/test-coverage.yaml | 4 ++-- .rubocop.yml | 3 +++ examples/container.rb | 2 +- fixtures/async/container/a_container.rb | 2 +- lib/async/container/thread.rb | 2 +- test/async/container/.cwd.rb | 2 +- test/async/container/hybrid.rb | 2 +- test/async/container/threaded.rb | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index f9da2ff..ffa0927 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -34,7 +34,7 @@ jobs: timeout-minutes: 5 run: bundle exec bake test - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: coverage-${{matrix.os}}-${{matrix.ruby}} path: .covered.db @@ -50,7 +50,7 @@ jobs: ruby-version: "3.3" bundler-cache: true - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 - name: Validate coverage timeout-minutes: 5 diff --git a/.rubocop.yml b/.rubocop.yml index 442c667..a2447c2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,9 @@ Layout/IndentationConsistency: Enabled: true EnforcedStyle: normal +Layout/BlockAlignment: + Enabled: true + Layout/EndAlignment: Enabled: true EnforcedStyleAlignWith: start_of_line diff --git a/examples/container.rb b/examples/container.rb index b76e971..6d9868c 100755 --- a/examples/container.rb +++ b/examples/container.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. # Copyright, 2019, by Yuji Yaginuma. # Copyright, 2022, by Anton Sozontov. diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb index 200ba0b..462049a 100644 --- a/fixtures/async/container/a_container.rb +++ b/fixtures/async/container/a_container.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. module Async module Container diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index 53fe5d3..097cce7 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. require_relative 'channel' diff --git a/test/async/container/.cwd.rb b/test/async/container/.cwd.rb index fd0e032..63fee53 100755 --- a/test/async/container/.cwd.rb +++ b/test/async/container/.cwd.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2024, by Samuel Williams. +# Copyright, 2024, by Samuel Williams. require_relative '../../../lib/async/container/controller' diff --git a/test/async/container/hybrid.rb b/test/async/container/hybrid.rb index ed3b13d..48f50a1 100644 --- a/test/async/container/hybrid.rb +++ b/test/async/container/hybrid.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require "async/container/hybrid" require "async/container/best" diff --git a/test/async/container/threaded.rb b/test/async/container/threaded.rb index b18dee6..f6c078e 100644 --- a/test/async/container/threaded.rb +++ b/test/async/container/threaded.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2022, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. require "async/container/threaded" require "async/container/a_container" From 6fcaea457534c0dba826b08e6a97fac00b5d03a9 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 4 Sep 2024 16:51:57 +1200 Subject: [PATCH 107/166] Bump patch version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index cdf80df..3078820 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.18.2" + VERSION = "0.18.3" end end From dfd941976f75f6fa4713bf43e6058ecf710dc6dc Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 6 Dec 2024 10:56:11 +1300 Subject: [PATCH 108/166] Modernize code. --- .github/workflows/test-coverage.yaml | 2 ++ .rubocop.yml | 4 ++++ async-container.gemspec | 6 +++--- config/sus.rb | 4 ++-- examples/benchmark/scalability.rb | 6 +++--- examples/channel.rb | 2 +- examples/container.rb | 2 +- examples/controller.rb | 2 +- examples/fan-out/pipe.rb | 2 +- examples/grace/server.rb | 4 ++-- examples/http/client.rb | 6 +++--- examples/http/server.rb | 6 +++--- examples/queue/server.rb | 12 ++++++------ examples/test.rb | 6 +++--- fixtures/async/container/a_container.rb | 8 ++++---- gems.rb | 2 +- gems/async-head.rb | 4 ++-- gems/async-v1.rb | 4 ++-- lib/async/container.rb | 2 +- lib/async/container/best.rb | 6 +++--- lib/async/container/channel.rb | 2 +- lib/async/container/controller.rb | 8 ++++---- lib/async/container/error.rb | 4 ++-- lib/async/container/forked.rb | 4 ++-- lib/async/container/generic.rb | 12 ++++++------ lib/async/container/group.rb | 6 +++--- lib/async/container/hybrid.rb | 4 ++-- lib/async/container/notify.rb | 6 +++--- lib/async/container/notify/console.rb | 4 ++-- lib/async/container/notify/pipe.rb | 6 +++--- lib/async/container/notify/server.rb | 10 +++++----- lib/async/container/notify/socket.rb | 4 ++-- lib/async/container/process.rb | 6 +++--- lib/async/container/statistics.rb | 2 +- lib/async/container/thread.rb | 6 +++--- lib/async/container/threaded.rb | 4 ++-- test/async/container.rb | 8 ++++---- test/async/container/.bad.rb | 2 +- test/async/container/.cwd.rb | 2 +- test/async/container/.dots.rb | 2 +- test/async/container/.graceful.rb | 2 +- test/async/container/controller.rb | 24 ++++++++++++------------ test/async/container/notify.rb | 2 +- test/async/container/notify/.notify.rb | 2 +- 44 files changed, 114 insertions(+), 108 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index ffa0927..50e9293 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -36,6 +36,8 @@ jobs: - uses: actions/upload-artifact@v4 with: + include-hidden-files: true + if-no-files-found: error name: coverage-${{matrix.os}}-${{matrix.ruby}} path: .covered.db diff --git a/.rubocop.yml b/.rubocop.yml index a2447c2..3b8d476 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -47,3 +47,7 @@ Layout/EmptyLinesAroundModuleBody: Style/FrozenStringLiteralComment: Enabled: true + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes diff --git a/async-container.gemspec b/async-container.gemspec index 36c71fa..19ab071 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -10,8 +10,8 @@ Gem::Specification.new do |spec| spec.authors = ["Samuel Williams", "Olle Jonsson", "Anton Sozontov", "Juan Antonio Martín Lucas", "Yuji Yaginuma"] spec.license = "MIT" - spec.cert_chain = ['release.cert'] - spec.signing_key = File.expand_path('~/.gem/release.pem') + spec.cert_chain = ["release.cert"] + spec.signing_key = File.expand_path("~/.gem/release.pem") spec.homepage = "https://github.com/socketry/async-container" @@ -20,7 +20,7 @@ Gem::Specification.new do |spec| "source_code_uri" => "https://github.com/socketry/async-container.git", } - spec.files = Dir.glob(['{lib}/**/*', '*.md'], File::FNM_DOTMATCH, base: __dir__) + spec.files = Dir.glob(["{lib}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__) spec.required_ruby_version = ">= 3.1" diff --git a/config/sus.rb b/config/sus.rb index ad31566..2ddb93e 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2022, by Samuel Williams. -require 'covered/sus' +require "covered/sus" include Covered::Sus -ENV['CONSOLE_LEVEL'] ||= 'fatal' +ENV["CONSOLE_LEVEL"] ||= "fatal" diff --git a/examples/benchmark/scalability.rb b/examples/benchmark/scalability.rb index 2205140..f83a6a9 100644 --- a/examples/benchmark/scalability.rb +++ b/examples/benchmark/scalability.rb @@ -6,8 +6,8 @@ # gem install async-container gem "async-container" -require 'async/clock' -require_relative '../../lib/async/container' +require "async/clock" +require_relative "../../lib/async/container" def fibonacci(n) if n < 2 @@ -17,7 +17,7 @@ def fibonacci(n) end end -require 'sqlite3' +require "sqlite3" def work(*) 512.times do diff --git a/examples/channel.rb b/examples/channel.rb index 5592aff..fa3b3c1 100644 --- a/examples/channel.rb +++ b/examples/channel.rb @@ -4,7 +4,7 @@ # Copyright, 2020-2022, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. -require 'json' +require "json" class Channel def initialize diff --git a/examples/container.rb b/examples/container.rb index 6d9868c..50e8233 100755 --- a/examples/container.rb +++ b/examples/container.rb @@ -6,7 +6,7 @@ # Copyright, 2019, by Yuji Yaginuma. # Copyright, 2022, by Anton Sozontov. -require '../lib/async/container' +require "../lib/async/container" Console.logger.debug! diff --git a/examples/controller.rb b/examples/controller.rb index 0341d2e..992f083 100755 --- a/examples/controller.rb +++ b/examples/controller.rb @@ -5,7 +5,7 @@ # Copyright, 2022, by Anton Sozontov. # Copyright, 2024, by Samuel Williams. -require '../lib/async/container/controller' +require "../lib/async/container/controller" class Controller < Async::Container::Controller def setup(container) diff --git a/examples/fan-out/pipe.rb b/examples/fan-out/pipe.rb index 97c84fe..9fb3537 100755 --- a/examples/fan-out/pipe.rb +++ b/examples/fan-out/pipe.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. -require 'async/container' +require "async/container" container = Async::Container.new input, output = IO.pipe diff --git a/examples/grace/server.rb b/examples/grace/server.rb index 5a6b236..4a9469e 100755 --- a/examples/grace/server.rb +++ b/examples/grace/server.rb @@ -4,8 +4,8 @@ # Released under the MIT License. # Copyright, 2024, by Samuel Williams. -require '../../lib/async/container' -require 'io/endpoint/host_endpoint' +require "../../lib/async/container" +require "io/endpoint/host_endpoint" Console.logger.debug! diff --git a/examples/http/client.rb b/examples/http/client.rb index bf4e374..07511a5 100755 --- a/examples/http/client.rb +++ b/examples/http/client.rb @@ -4,9 +4,9 @@ # Released under the MIT License. # Copyright, 2022-2024, by Samuel Williams. -require 'async' -require 'async/http/endpoint' -require 'async/http/client' +require "async" +require "async/http/endpoint" +require "async/http/client" endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292") diff --git a/examples/http/server.rb b/examples/http/server.rb index 611fd29..07c785d 100755 --- a/examples/http/server.rb +++ b/examples/http/server.rb @@ -4,10 +4,10 @@ # Released under the MIT License. # Copyright, 2022-2024, by Samuel Williams. -require 'async/container' +require "async/container" -require 'async/http/endpoint' -require 'async/http/server' +require "async/http/endpoint" +require "async/http/server" container = Async::Container::Forked.new diff --git a/examples/queue/server.rb b/examples/queue/server.rb index 159b205..1074da1 100755 --- a/examples/queue/server.rb +++ b/examples/queue/server.rb @@ -4,11 +4,11 @@ # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. -require 'async' -require 'async/container' -require 'io/endpoint' -require 'io/endpoint/unix_endpoint' -require 'msgpack' +require "async" +require "async/container" +require "io/endpoint" +require "io/endpoint/unix_endpoint" +require "msgpack" class Wrapper < MessagePack::Factory def initialize @@ -29,7 +29,7 @@ def initialize end end -endpoint = IO::Endpoint.unix('test.ipc') +endpoint = IO::Endpoint.unix("test.ipc") bound_endpoint = endpoint.bound wrapper = Wrapper.new diff --git a/examples/test.rb b/examples/test.rb index 91f28e1..bbd458f 100644 --- a/examples/test.rb +++ b/examples/test.rb @@ -3,9 +3,9 @@ # Released under the MIT License. # Copyright, 2020-2022, by Samuel Williams. -require_relative 'group' -require_relative 'thread' -require_relative 'process' +require_relative "group" +require_relative "thread" +require_relative "process" group = Async::Container::Group.new diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb index 462049a..5d1c1b0 100644 --- a/fixtures/async/container/a_container.rb +++ b/fixtures/async/container/a_container.rb @@ -48,7 +48,7 @@ module Container expect(input.read).to be == "true" end - with '#sleep' do + with "#sleep" do it "can sleep for a short time" do container.spawn do sleep(0.01) @@ -63,8 +63,8 @@ module Container end end - with '#stop' do - it 'can stop the child process' do + with "#stop" do + it "can stop the child process" do container.spawn do sleep(1) end @@ -77,7 +77,7 @@ module Container end end - with '#ready' do + with "#ready" do it "can notify the ready pipe in an asynchronous context" do container.run do |instance| Async do diff --git a/gems.rb b/gems.rb index a4e6eb9..c018bcc 100644 --- a/gems.rb +++ b/gems.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2017-2024, by Samuel Williams. -source 'https://rubygems.org' +source "https://rubygems.org" gemspec diff --git a/gems/async-head.rb b/gems/async-head.rb index 07519eb..164cd80 100644 --- a/gems/async-head.rb +++ b/gems/async-head.rb @@ -3,8 +3,8 @@ # Released under the MIT License. # Copyright, 2021-2024, by Samuel Williams. -source 'https://rubygems.org' +source "https://rubygems.org" eval_gemfile "../gems.rb" -gem 'async', git: "https://github.com/socketry/async" +gem "async", git: "https://github.com/socketry/async" diff --git a/gems/async-v1.rb b/gems/async-v1.rb index 26da9af..ad2b8d8 100644 --- a/gems/async-v1.rb +++ b/gems/async-v1.rb @@ -3,8 +3,8 @@ # Released under the MIT License. # Copyright, 2021-2024, by Samuel Williams. -source 'https://rubygems.org' +source "https://rubygems.org" eval_gemfile "../gems.rb" -gem 'async', '~> 1.0' +gem "async", "~> 1.0" diff --git a/lib/async/container.rb b/lib/async/container.rb index 691afe4..a0585e3 100644 --- a/lib/async/container.rb +++ b/lib/async/container.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2017-2022, by Samuel Williams. -require_relative 'container/controller' +require_relative "container/controller" module Async module Container diff --git a/lib/async/container/best.rb b/lib/async/container/best.rb index 7ba4226..1afd378 100644 --- a/lib/async/container/best.rb +++ b/lib/async/container/best.rb @@ -3,9 +3,9 @@ # Released under the MIT License. # Copyright, 2019-2022, by Samuel Williams. -require_relative 'forked' -require_relative 'threaded' -require_relative 'hybrid' +require_relative "forked" +require_relative "threaded" +require_relative "hybrid" module Async module Container diff --git a/lib/async/container/channel.rb b/lib/async/container/channel.rb index ae61d2c..99c9591 100644 --- a/lib/async/container/channel.rb +++ b/lib/async/container/channel.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2020-2022, by Samuel Williams. -require 'json' +require "json" module Async module Container diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 294450b..05618d6 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -3,11 +3,11 @@ # Released under the MIT License. # Copyright, 2018-2024, by Samuel Williams. -require_relative 'error' -require_relative 'best' +require_relative "error" +require_relative "best" -require_relative 'statistics' -require_relative 'notify' +require_relative "statistics" +require_relative "notify" module Async module Container diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index 62dd62f..b7065f3 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -12,7 +12,7 @@ class Error < StandardError # Similar to {Interrupt}, but represents `SIGTERM`. class Terminate < SignalException - SIGTERM = Signal.list['TERM'] + SIGTERM = Signal.list["TERM"] def initialize super(SIGTERM) @@ -20,7 +20,7 @@ def initialize end class Hangup < SignalException - SIGHUP = Signal.list['HUP'] + SIGHUP = Signal.list["HUP"] def initialize super(SIGHUP) diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index c5dabd3..9bfcaa2 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -3,8 +3,8 @@ # Released under the MIT License. # Copyright, 2017-2022, by Samuel Williams. -require_relative 'generic' -require_relative 'process' +require_relative "generic" +require_relative "process" module Async module Container diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 956bdfe..881e379 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -3,18 +3,18 @@ # Released under the MIT License. # Copyright, 2019-2024, by Samuel Williams. -require 'async' +require "async" -require 'etc' +require "etc" -require_relative 'group' -require_relative 'keyed' -require_relative 'statistics' +require_relative "group" +require_relative "keyed" +require_relative "statistics" module Async module Container # An environment variable key to override {.processor_count}. - ASYNC_CONTAINER_PROCESSOR_COUNT = 'ASYNC_CONTAINER_PROCESSOR_COUNT' + ASYNC_CONTAINER_PROCESSOR_COUNT = "ASYNC_CONTAINER_PROCESSOR_COUNT" # The processor count which may be used for the default number of container threads/processes. You can override the value provided by the system by specifying the `ASYNC_CONTAINER_PROCESSOR_COUNT` environment variable. # @returns [Integer] The number of hardware processors which can run threads/processes simultaneously. diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index f3a0989..5256891 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -3,10 +3,10 @@ # Released under the MIT License. # Copyright, 2018-2024, by Samuel Williams. -require 'fiber' -require 'async/clock' +require "fiber" +require "async/clock" -require_relative 'error' +require_relative "error" module Async module Container diff --git a/lib/async/container/hybrid.rb b/lib/async/container/hybrid.rb index 1a20b1d..e5d41e0 100644 --- a/lib/async/container/hybrid.rb +++ b/lib/async/container/hybrid.rb @@ -4,8 +4,8 @@ # Copyright, 2019-2022, by Samuel Williams. # Copyright, 2022, by Anton Sozontov. -require_relative 'forked' -require_relative 'threaded' +require_relative "forked" +require_relative "threaded" module Async module Container diff --git a/lib/async/container/notify.rb b/lib/async/container/notify.rb index e9707b3..58ceb31 100644 --- a/lib/async/container/notify.rb +++ b/lib/async/container/notify.rb @@ -3,9 +3,9 @@ # Released under the MIT License. # Copyright, 2020-2022, by Samuel Williams. -require_relative 'notify/pipe' -require_relative 'notify/socket' -require_relative 'notify/console' +require_relative "notify/pipe" +require_relative "notify/socket" +require_relative "notify/console" module Async module Container diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb index 96a1bd6..d661dda 100644 --- a/lib/async/container/notify/console.rb +++ b/lib/async/container/notify/console.rb @@ -3,9 +3,9 @@ # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. -require_relative 'client' +require_relative "client" -require 'console/logger' +require "console/logger" module Async module Container diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index 398ef90..920585a 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -4,9 +4,9 @@ # Copyright, 2020-2022, by Samuel Williams. # Copyright, 2020, by Juan Antonio Martín Lucas. -require_relative 'client' +require_relative "client" -require 'json' +require "json" module Async module Container @@ -14,7 +14,7 @@ module Notify # Implements a process readiness protocol using an inherited pipe file descriptor. class Pipe < Client # The environment variable key which contains the pipe file descriptor. - NOTIFY_PIPE = 'NOTIFY_PIPE' + NOTIFY_PIPE = "NOTIFY_PIPE" # Open a notification client attached to the current {NOTIFY_PIPE} if possible. def self.open!(environment = ENV) diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index 415be55..26b55b2 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -4,14 +4,14 @@ # Copyright, 2020-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. -require 'tmpdir' -require 'securerandom' +require "tmpdir" +require "securerandom" module Async module Container module Notify class Server - NOTIFY_SOCKET = 'NOTIFY_SOCKET' + NOTIFY_SOCKET = "NOTIFY_SOCKET" MAXIMUM_MESSAGE_SIZE = 4096 def self.load(message) @@ -22,9 +22,9 @@ def self.load(message) pairs = lines.map do |line| key, value = line.split("=", 2) - if value == '0' + if value == "0" value = false - elsif value == '1' + elsif value == "1" value = true end diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index 34ce9cd..f92473e 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. -require_relative 'client' +require_relative "client" module Async module Container @@ -12,7 +12,7 @@ module Notify # See for more details of the underlying protocol. class Socket < Client # The name of the environment variable which contains the path to the notification socket. - NOTIFY_SOCKET = 'NOTIFY_SOCKET' + NOTIFY_SOCKET = "NOTIFY_SOCKET" # The maximum allowed size of the UDP message. MAXIMUM_MESSAGE_SIZE = 4096 diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 123083b..e40dfff 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -3,10 +3,10 @@ # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. -require_relative 'channel' -require_relative 'error' +require_relative "channel" +require_relative "error" -require_relative 'notify/pipe' +require_relative "notify/pipe" module Async module Container diff --git a/lib/async/container/statistics.rb b/lib/async/container/statistics.rb index 9c84cdd..3d0874f 100644 --- a/lib/async/container/statistics.rb +++ b/lib/async/container/statistics.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2019-2022, by Samuel Williams. -require 'async/reactor' +require "async/reactor" module Async module Container diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index 097cce7..a87d2f3 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -4,9 +4,9 @@ # Copyright, 2020-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. -require_relative 'channel' -require_relative 'error' -require_relative 'notify/pipe' +require_relative "channel" +require_relative "error" +require_relative "notify/pipe" module Async module Container diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index faa2804..c6f7365 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -3,8 +3,8 @@ # Released under the MIT License. # Copyright, 2017-2022, by Samuel Williams. -require_relative 'generic' -require_relative 'thread' +require_relative "generic" +require_relative "thread" module Async module Container diff --git a/test/async/container.rb b/test/async/container.rb index afec4e0..a78fc16 100644 --- a/test/async/container.rb +++ b/test/async/container.rb @@ -6,19 +6,19 @@ require "async/container" describe Async::Container do - with '.processor_count' do + with ".processor_count" do it "can get processor count" do expect(Async::Container.processor_count).to be >= 1 end it "can override the processor count" do - env = {'ASYNC_CONTAINER_PROCESSOR_COUNT' => '8'} + env = {"ASYNC_CONTAINER_PROCESSOR_COUNT" => "8"} expect(Async::Container.processor_count(env)).to be == 8 end it "fails on invalid processor count" do - env = {'ASYNC_CONTAINER_PROCESSOR_COUNT' => '-1'} + env = {"ASYNC_CONTAINER_PROCESSOR_COUNT" => "-1"} expect do Async::Container.processor_count(env) @@ -30,7 +30,7 @@ expect(Async::Container.best_container_class).not.to be_nil end - with '.new' do + with ".new" do let(:container) {Async::Container.new} it "can get best container class" do diff --git a/test/async/container/.bad.rb b/test/async/container/.bad.rb index b20309e..cf5aaa3 100755 --- a/test/async/container/.bad.rb +++ b/test/async/container/.bad.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2024, by Samuel Williams. -require_relative '../../../lib/async/container/controller' +require_relative "../../../lib/async/container/controller" $stdout.sync = true diff --git a/test/async/container/.cwd.rb b/test/async/container/.cwd.rb index 63fee53..4899bc3 100755 --- a/test/async/container/.cwd.rb +++ b/test/async/container/.cwd.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2024, by Samuel Williams. -require_relative '../../../lib/async/container/controller' +require_relative "../../../lib/async/container/controller" $stdout.sync = true diff --git a/test/async/container/.dots.rb b/test/async/container/.dots.rb index bafd525..cd4bda4 100755 --- a/test/async/container/.dots.rb +++ b/test/async/container/.dots.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. -require_relative '../../../lib/async/container/controller' +require_relative "../../../lib/async/container/controller" $stdout.sync = true diff --git a/test/async/container/.graceful.rb b/test/async/container/.graceful.rb index bc16a57..31e708d 100755 --- a/test/async/container/.graceful.rb +++ b/test/async/container/.graceful.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2024, by Samuel Williams. -require_relative '../../../lib/async/container/controller' +require_relative "../../../lib/async/container/controller" $stdout.sync = true diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index 53b1f47..faac968 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -8,7 +8,7 @@ describe Async::Container::Controller do let(:controller) {subject.new} - with '#reload' do + with "#reload" do it "can reuse keyed child" do input, output = IO.pipe @@ -46,7 +46,7 @@ def controller.setup(container) end end - with '#start' do + with "#start" do it "can start up a container" do expect(controller).to receive(:setup) @@ -88,7 +88,7 @@ def controller.setup(container) end end - with 'graceful controller' do + with "graceful controller" do let(:controller_path) {File.expand_path(".graceful.rb", __dir__)} let(:pipe) {IO.pipe} @@ -127,7 +127,7 @@ def after(error = nil) end end - with 'bad controller' do + with "bad controller" do let(:controller_path) {File.expand_path(".bad.rb", __dir__)} let(:pipe) {IO.pipe} @@ -159,7 +159,7 @@ def after(error = nil) end end - with 'signals' do + with "signals" do let(:controller_path) {File.expand_path(".dots.rb", __dir__)} let(:pipe) {IO.pipe} @@ -183,31 +183,31 @@ def after(error = nil) end it "restarts children when receiving SIGHUP" do - expect(input.read(1)).to be == '.' + expect(input.read(1)).to be == "." Process.kill(:HUP, pid) - expect(input.read(2)).to be == 'I.' + expect(input.read(2)).to be == "I." end it "exits gracefully when receiving SIGINT" do - expect(input.read(1)).to be == '.' + expect(input.read(1)).to be == "." Process.kill(:INT, pid) - expect(input.read).to be == 'I' + expect(input.read).to be == "I" end it "exits gracefully when receiving SIGTERM" do - expect(input.read(1)).to be == '.' + expect(input.read(1)).to be == "." Process.kill(:TERM, pid) - expect(input.read).to be == 'T' + expect(input.read).to be == "T" end end - with 'working directory' do + with "working directory" do let(:controller_path) {File.expand_path(".cwd.rb", __dir__)} it "can change working directory" do diff --git a/test/async/container/notify.rb b/test/async/container/notify.rb index c8cea41..264fa2f 100644 --- a/test/async/container/notify.rb +++ b/test/async/container/notify.rb @@ -11,7 +11,7 @@ let(:notify_socket) {server.path} let(:client) {subject::Socket.new(notify_socket)} - with '#ready!' do + with "#ready!" do it "should send message" do begin context = server.bind diff --git a/test/async/container/notify/.notify.rb b/test/async/container/notify/.notify.rb index ba232c4..5be848f 100755 --- a/test/async/container/notify/.notify.rb +++ b/test/async/container/notify/.notify.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2020-2022, by Samuel Williams. -require_relative '../../../../lib/async/container' +require_relative "../../../../lib/async/container" class MyController < Async::Container::Controller def setup(container) From 02f50a4af513bc40a8c57a073e2b9439328a495e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 9 Dec 2024 19:43:33 +1300 Subject: [PATCH 109/166] Update usage of `Console` logger. --- examples/container.rb | 10 +++++----- examples/controller.rb | 10 +++++----- guides/getting-started/readme.md | 10 +++++----- lib/async/container/controller.rb | 18 +++++++++--------- lib/async/container/generic.rb | 10 +++++----- lib/async/container/group.rb | 4 ++-- lib/async/container/notify/pipe.rb | 2 +- lib/async/container/process.rb | 4 ++-- lib/async/container/thread.rb | 2 +- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/examples/container.rb b/examples/container.rb index 50e8233..55e9217 100755 --- a/examples/container.rb +++ b/examples/container.rb @@ -12,16 +12,16 @@ container = Async::Container.new -Console.logger.debug "Spawning 2 containers..." +Console.debug "Spawning 2 containers..." 2.times do container.spawn do |task| - Console.logger.debug task, "Sleeping..." + Console.debug task, "Sleeping..." sleep(2) - Console.logger.debug task, "Waking up!" + Console.debug task, "Waking up!" end end -Console.logger.debug "Waiting for container..." +Console.debug "Waiting for container..." container.wait -Console.logger.debug "Finished." +Console.debug "Finished." diff --git a/examples/controller.rb b/examples/controller.rb index 992f083..afc5db3 100755 --- a/examples/controller.rb +++ b/examples/controller.rb @@ -11,9 +11,9 @@ class Controller < Async::Container::Controller def setup(container) container.run(count: 1, restart: true) do |instance| if container.statistics.failed? - Console.logger.debug(self, "Child process restarted #{container.statistics.restarts} times.") + Console.debug(self, "Child process restarted #{container.statistics.restarts} times.") else - Console.logger.debug(self, "Child process started.") + Console.debug(self, "Child process started.") end instance.ready! @@ -21,10 +21,10 @@ def setup(container) while true sleep 1 - Console.logger.debug(self, "Work") + Console.debug(self, "Work") if rand < 0.5 - Console.logger.debug(self, "Should exit...") + Console.debug(self, "Should exit...") sleep 0.5 exit(1) end @@ -35,7 +35,7 @@ def setup(container) Console.logger.debug! -Console.logger.debug(self, "Starting up...") +Console.debug(self, "Starting up...") controller = Controller.new diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index a9c6cb4..04ca895 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -29,14 +29,14 @@ Console.logger.debug! container = Async::Container.new container.spawn do |task| - Console.logger.debug task, "Sleeping..." + Console.debug task, "Sleeping..." sleep(1) - Console.logger.debug task, "Waking up!" + Console.debug task, "Waking up!" end -Console.logger.debug "Waiting for container..." +Console.debug "Waiting for container..." container.wait -Console.logger.debug "Finished." +Console.debug "Finished." ``` ## Controllers @@ -58,7 +58,7 @@ class Controller < Async::Container::Controller def setup(container) container.run count: 2, restart: true do |instance| while true - Console.logger.debug(instance, "Sleeping...") + Console.debug(instance, "Sleeping...") sleep(1) end end diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 05618d6..055f2e9 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -109,9 +109,9 @@ def restart if @container @notify&.restarting! - Console.logger.debug(self) {"Restarting container..."} + Console.debug(self) {"Restarting container..."} else - Console.logger.debug(self) {"Starting container..."} + Console.debug(self) {"Starting container..."} end container = self.create_container @@ -125,9 +125,9 @@ def restart end # Wait for all child processes to enter the ready state. - Console.logger.debug(self, "Waiting for startup...") + Console.debug(self, "Waiting for startup...") container.wait_until_ready - Console.logger.debug(self, "Finished startup.") + Console.debug(self, "Finished startup.") if container.failed? @notify&.error!("Container failed to start!") @@ -143,7 +143,7 @@ def restart container = nil if old_container - Console.logger.debug(self, "Stopping old container...") + Console.debug(self, "Stopping old container...") old_container&.stop(@graceful_stop) end @@ -157,7 +157,7 @@ def restart def reload @notify&.reloading! - Console.logger.info(self) {"Reloading container: #{@container}..."} + Console.info(self) {"Reloading container: #{@container}..."} begin self.setup(@container) @@ -166,11 +166,11 @@ def reload end # Wait for all child processes to enter the ready state. - Console.logger.debug(self, "Waiting for startup...") + Console.debug(self, "Waiting for startup...") @container.wait_until_ready - Console.logger.debug(self, "Finished startup.") + Console.debug(self, "Finished startup.") if @container.failed? @notify.error!("Container failed to reload!") @@ -210,7 +210,7 @@ def run begin handler.call rescue SetupError => error - Console.logger.error(self) {error} + Console.error(self) {error} end else raise diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 881e379..bf78d13 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -104,7 +104,7 @@ def status?(flag) # @returns [Boolean] The children all became ready. def wait_until_ready while true - Console.logger.debug(self) do |buffer| + Console.debug(self) do |buffer| buffer.puts "Waiting for ready:" @state.each do |child, state| buffer.puts "\t#{child.class}: #{state.inspect}" @@ -126,7 +126,7 @@ def stop(timeout = true) @group.stop(timeout) if @group.running? - Console.logger.warn(self) {"Group is still running after stopping it!"} + Console.warn(self) {"Group is still running after stopping it!"} end ensure @running = true @@ -140,7 +140,7 @@ def spawn(name: nil, restart: false, key: nil, &block) name ||= UNNAMED if mark?(key) - Console.logger.debug(self) {"Reusing existing child for #{key}: #{name}"} + Console.debug(self) {"Reusing existing child for #{key}: #{name}"} return false end @@ -161,10 +161,10 @@ def spawn(name: nil, restart: false, key: nil, &block) end if status.success? - Console.logger.debug(self) {"#{child} exited with #{status}"} + Console.debug(self) {"#{child} exited with #{status}"} else @statistics.failure! - Console.logger.error(self) {status} + Console.error(self) {status} end if restart diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 5256891..7d28d1a 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -65,7 +65,7 @@ def wait # Interrupt all running processes. # This resumes the controlling fiber with an instance of {Interrupt}. def interrupt - Console.logger.debug(self, "Sending interrupt to #{@running.size} running processes...") + Console.debug(self, "Sending interrupt to #{@running.size} running processes...") @running.each_value do |fiber| fiber.resume(Interrupt) end @@ -74,7 +74,7 @@ def interrupt # Terminate all running processes. # This resumes the controlling fiber with an instance of {Terminate}. def terminate - Console.logger.debug(self, "Sending terminate to #{@running.size} running processes...") + Console.debug(self, "Sending terminate to #{@running.size} running processes...") @running.each_value do |fiber| fiber.resume(Terminate) end diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index 920585a..5c0b4f2 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -22,7 +22,7 @@ def self.open!(environment = ENV) self.new(::IO.for_fd(descriptor.to_i)) end rescue Errno::EBADF => error - Console.logger.error(self) {error} + Console.error(self) {error} return nil end diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index e40dfff..d05f4c7 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -74,7 +74,7 @@ def self.fork(**options) rescue Interrupt # Graceful exit. rescue Exception => error - Console.logger.error(self) {error} + Console.error(self) {error} exit!(1) end @@ -160,7 +160,7 @@ def wait end if @status.nil? - Console.logger.warn(self) {"Process #{@pid} is blocking, has it exited?"} + Console.warn(self) {"Process #{@pid} is blocking, has it exited?"} _, @status = ::Process.wait2(@pid) end end diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index a87d2f3..d0310b2 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -188,7 +188,7 @@ def to_s # Invoked by the @waiter thread to indicate the outcome of the child thread. def finished(error = nil) if error - Console.logger.error(self) {error} + Console.error(self) {error} end @status = Status.new(error) From 9999fe11da4ca5e0970bafbf8a13f44f4094dd8e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 9 Dec 2024 19:44:37 +1300 Subject: [PATCH 110/166] Update copyrights. --- config/sus.rb | 2 +- examples/channel.rb | 2 +- examples/test.rb | 2 +- lib/async/container.rb | 2 +- lib/async/container/best.rb | 2 +- lib/async/container/channel.rb | 2 +- lib/async/container/error.rb | 2 +- lib/async/container/forked.rb | 2 +- lib/async/container/hybrid.rb | 2 +- lib/async/container/notify.rb | 2 +- lib/async/container/notify/pipe.rb | 2 +- lib/async/container/statistics.rb | 2 +- lib/async/container/threaded.rb | 2 +- test/async/container.rb | 2 +- test/async/container/notify.rb | 2 +- test/async/container/notify/.notify.rb | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/config/sus.rb b/config/sus.rb index 2ddb93e..ced7124 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2022, by Samuel Williams. +# Copyright, 2022-2024, by Samuel Williams. require "covered/sus" include Covered::Sus diff --git a/examples/channel.rb b/examples/channel.rb index fa3b3c1..8847fd7 100644 --- a/examples/channel.rb +++ b/examples/channel.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. require "json" diff --git a/examples/test.rb b/examples/test.rb index bbd458f..824eed0 100644 --- a/examples/test.rb +++ b/examples/test.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative "group" require_relative "thread" diff --git a/lib/async/container.rb b/lib/async/container.rb index a0585e3..b4269d4 100644 --- a/lib/async/container.rb +++ b/lib/async/container.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2022, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. require_relative "container/controller" diff --git a/lib/async/container/best.rb b/lib/async/container/best.rb index 1afd378..31b059f 100644 --- a/lib/async/container/best.rb +++ b/lib/async/container/best.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require_relative "forked" require_relative "threaded" diff --git a/lib/async/container/channel.rb b/lib/async/container/channel.rb index 99c9591..2313110 100644 --- a/lib/async/container/channel.rb +++ b/lib/async/container/channel.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require "json" diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index b7065f3..d798e3e 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. module Async module Container diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 9bfcaa2..243480f 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2022, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. require_relative "generic" require_relative "process" diff --git a/lib/async/container/hybrid.rb b/lib/async/container/hybrid.rb index e5d41e0..ea42f60 100644 --- a/lib/async/container/hybrid.rb +++ b/lib/async/container/hybrid.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. # Copyright, 2022, by Anton Sozontov. require_relative "forked" diff --git a/lib/async/container/notify.rb b/lib/async/container/notify.rb index 58ceb31..3079bab 100644 --- a/lib/async/container/notify.rb +++ b/lib/async/container/notify.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative "notify/pipe" require_relative "notify/socket" diff --git a/lib/async/container/notify/pipe.rb b/lib/async/container/notify/pipe.rb index 5c0b4f2..597bcf5 100644 --- a/lib/async/container/notify/pipe.rb +++ b/lib/async/container/notify/pipe.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. # Copyright, 2020, by Juan Antonio Martín Lucas. require_relative "client" diff --git a/lib/async/container/statistics.rb b/lib/async/container/statistics.rb index 3d0874f..efda3af 100644 --- a/lib/async/container/statistics.rb +++ b/lib/async/container/statistics.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2022, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require "async/reactor" diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index c6f7365..c046e80 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2022, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. require_relative "generic" require_relative "thread" diff --git a/test/async/container.rb b/test/async/container.rb index a78fc16..f3c7dfa 100644 --- a/test/async/container.rb +++ b/test/async/container.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2022, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. require "async/container" diff --git a/test/async/container/notify.rb b/test/async/container/notify.rb index 264fa2f..a7fc476 100644 --- a/test/async/container/notify.rb +++ b/test/async/container/notify.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require "async/container/controller" require "async/container/notify/server" diff --git a/test/async/container/notify/.notify.rb b/test/async/container/notify/.notify.rb index 5be848f..3fbb0c2 100755 --- a/test/async/container/notify/.notify.rb +++ b/test/async/container/notify/.notify.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative "../../../../lib/async/container" From ef9cfcf0176605a1bb30adc9bfb40dbdd315994b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 12 Dec 2024 13:58:05 +1300 Subject: [PATCH 111/166] Improved logging when child process fails. --- examples/exec-child/jobs | 30 ++++++++++++ examples/exec-child/readme.md | 69 +++++++++++++++++++++++++++ examples/exec-child/start | 24 ++++++++++ examples/exec-child/web | 30 ++++++++++++ lib/async/container/controller.rb | 16 ++++--- lib/async/container/generic.rb | 9 +++- lib/async/container/group.rb | 10 ++-- lib/async/container/notify/console.rb | 4 +- lib/async/container/process.rb | 28 +++++++---- releases.md | 5 ++ 10 files changed, 202 insertions(+), 23 deletions(-) create mode 100755 examples/exec-child/jobs create mode 100644 examples/exec-child/readme.md create mode 100755 examples/exec-child/start create mode 100755 examples/exec-child/web create mode 100644 releases.md diff --git a/examples/exec-child/jobs b/examples/exec-child/jobs new file mode 100755 index 0000000..ca988b6 --- /dev/null +++ b/examples/exec-child/jobs @@ -0,0 +1,30 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "console" +require "async/container/notify" + +# Console.logger.debug! + +class Jobs + def self.start = self.new.start + + def start + Console.info(self, "Starting jobs...") + + if notify = Async::Container::Notify.open! + Console.info(self, "Notifying container ready...") + notify.ready! + end + + loop do + Console.info(self, "Jobs running...") + + sleep 10 + end + rescue Interrupt + Console.info(self, "Exiting jobs...") + end +end + +Jobs.start diff --git a/examples/exec-child/readme.md b/examples/exec-child/readme.md new file mode 100644 index 0000000..0916b6c --- /dev/null +++ b/examples/exec-child/readme.md @@ -0,0 +1,69 @@ +# Exec Child Example + +This example demonstrates how to execute a child process using the `exec` function in a container. + +## Usage + +Start the main controller: + +``` +> bundle exec ./start + 0.0s info: AppController [oid=0x938] [ec=0x94c] [pid=96758] [2024-12-12 14:33:45 +1300] + | Controller starting... + 0.65s info: Jobs [oid=0x8e8] [ec=0x8fc] [pid=96763] [2024-12-12 14:33:45 +1300] + | Starting jobs... + 0.65s info: Jobs [oid=0x8e8] [ec=0x8fc] [pid=96763] [2024-12-12 14:33:45 +1300] + | Notifying container ready... + 0.65s info: Jobs [oid=0x8e8] [ec=0x8fc] [pid=96763] [2024-12-12 14:33:45 +1300] + | Jobs running... + 0.65s info: Web [oid=0x8e8] [ec=0x8fc] [pid=96760] [2024-12-12 14:33:45 +1300] + | Starting web... + 0.65s info: Web [oid=0x8e8] [ec=0x8fc] [pid=96760] [2024-12-12 14:33:45 +1300] + | Notifying container ready... + 0.65s info: Web [oid=0x8e8] [ec=0x8fc] [pid=96760] [2024-12-12 14:33:45 +1300] + | Web running... + 0.09s info: AppController [oid=0x938] [ec=0x94c] [pid=96758] [2024-12-12 14:33:45 +1300] + | Controller started... +``` + +In another terminal: `kill -HUP 96758` to cause a blue-green restart, which causes a new container to be started with new jobs and web processes: + +``` + 9.57s info: Jobs [oid=0x8e8] [ec=0x8fc] [pid=96836] [2024-12-12 14:33:54 +1300] + | Starting jobs... + 9.57s info: Web [oid=0x8e8] [ec=0x8fc] [pid=96833] [2024-12-12 14:33:54 +1300] + | Starting web... + 9.57s info: Jobs [oid=0x8e8] [ec=0x8fc] [pid=96836] [2024-12-12 14:33:54 +1300] + | Notifying container ready... + 9.57s info: Web [oid=0x8e8] [ec=0x8fc] [pid=96833] [2024-12-12 14:33:54 +1300] + | Notifying container ready... + 9.57s info: Jobs [oid=0x8e8] [ec=0x8fc] [pid=96836] [2024-12-12 14:33:54 +1300] + | Jobs running... + 9.57s info: Web [oid=0x8e8] [ec=0x8fc] [pid=96833] [2024-12-12 14:33:54 +1300] + | Web running... +``` + +Once the new container is running and the child processes have notified they are ready, the controller will stop the old container: + +``` + 9.01s info: Async::Container::Group [oid=0xa00] [ec=0x94c] [pid=96758] [2024-12-12 14:33:54 +1300] + | Stopping all processes... + | { + | "timeout": true + | } + 9.01s info: Async::Container::Group [oid=0xa00] [ec=0x94c] [pid=96758] [2024-12-12 14:33:54 +1300] + | Sending interrupt to 2 running processes... + 9.57s info: Web [oid=0x8e8] [ec=0x8fc] [pid=96760] [2024-12-12 14:33:54 +1300] + | Exiting web... + 9.57s info: Jobs [oid=0x8e8] [ec=0x8fc] [pid=96763] [2024-12-12 14:33:54 +1300] + | Exiting jobs... +``` + +The new container continues to run as expected: + +``` +19.57s info: Web [oid=0x8e8] [ec=0x8fc] [pid=96833] [2024-12-12 14:34:04 +1300] + | Web running... +19.57s info: Jobs [oid=0x8e8] [ec=0x8fc] [pid=96836] [2024-12-12 14:34:04 +1300] + | Jobs running... +``` diff --git a/examples/exec-child/start b/examples/exec-child/start new file mode 100755 index 0000000..2ff73fa --- /dev/null +++ b/examples/exec-child/start @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "async/container" +require "console" + +# Console.logger.debug! + +class AppController < Async::Container::Controller + def setup(container) + container.spawn(name: "Web") do |instance| + # Specify ready: false here as the child process is expected to take care of the readiness notification: + instance.exec("bundle", "exec", "web", ready: false) + end + + container.spawn(name: "Jobs") do |instance| + instance.exec("bundle", "exec", "jobs", ready: false) + end + end +end + +controller = AppController.new + +controller.run diff --git a/examples/exec-child/web b/examples/exec-child/web new file mode 100755 index 0000000..2cdf9e9 --- /dev/null +++ b/examples/exec-child/web @@ -0,0 +1,30 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "console" +require "async/container/notify" + +# Console.logger.debug! + +class Web + def self.start = self.new.start + + def start + Console.info(self, "Starting web...") + + if notify = Async::Container::Notify.open! + Console.info(self, "Notifying container ready...") + notify.ready! + end + + loop do + Console.info(self, "Web running...") + + sleep 10 + end + rescue Interrupt + Console.info(self, "Exiting web...") + end +end + +Web.start diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 055f2e9..bc6da05 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -26,13 +26,10 @@ def initialize(notify: Notify.open!, container_class: Container, graceful_stop: @container = nil @container_class = container_class - if @notify = notify - @notify.status!("Initializing...") - end - + @notify = notify @signals = {} - trap(SIGHUP) do + self.trap(SIGHUP) do self.restart end @@ -93,7 +90,12 @@ def setup(container) # Start the container unless it's already running. def start - self.restart unless @container + unless @container + Console.info(self) {"Controller starting..."} + self.restart + end + + Console.info(self) {"Controller started..."} end # Stop the container if it's running. @@ -183,6 +185,8 @@ def reload # Enter the controller run loop, trapping `SIGINT` and `SIGTERM`. def run + @notify&.status!("Initializing...") + # I thought this was the default... but it doesn't always raise an exception unless you do this explicitly. # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. interrupt_action = Signal.trap(:INT) do diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index bf78d13..185ae57 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -107,13 +107,20 @@ def wait_until_ready Console.debug(self) do |buffer| buffer.puts "Waiting for ready:" @state.each do |child, state| - buffer.puts "\t#{child.class}: #{state.inspect}" + buffer.puts "\t#{child.inspect}: #{state}" end end self.sleep if self.status?(:ready) + Console.logger.debug(self) do |buffer| + buffer.puts "All ready:" + @state.each do |child, state| + buffer.puts "\t#{child.inspect}: #{state}" + end + end + return true end end diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 7d28d1a..91d47dd 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -65,7 +65,7 @@ def wait # Interrupt all running processes. # This resumes the controlling fiber with an instance of {Interrupt}. def interrupt - Console.debug(self, "Sending interrupt to #{@running.size} running processes...") + Console.info(self, "Sending interrupt to #{@running.size} running processes...") @running.each_value do |fiber| fiber.resume(Interrupt) end @@ -74,7 +74,7 @@ def interrupt # Terminate all running processes. # This resumes the controlling fiber with an instance of {Terminate}. def terminate - Console.debug(self, "Sending terminate to #{@running.size} running processes...") + Console.info(self, "Sending terminate to #{@running.size} running processes...") @running.each_value do |fiber| fiber.resume(Terminate) end @@ -83,6 +83,7 @@ def terminate # Stop all child processes using {#terminate}. # @parameter timeout [Boolean | Numeric | Nil] If specified, invoke a graceful shutdown using {#interrupt} first. def stop(timeout = 1) + Console.info(self, "Stopping all processes...", timeout: timeout) # Use a default timeout if not specified: timeout = 1 if timeout == true @@ -105,7 +106,7 @@ def stop(timeout = 1) end # Terminate all children: - self.terminate + self.terminate if any? # Wait for all children to exit: self.wait @@ -137,7 +138,8 @@ def wait_for(channel) protected def wait_for_children(duration = nil) - Console.debug(self, "Waiting for children...", duration: duration) + Console.debug(self, "Waiting for children...", duration: duration, running: @running) + if !@running.empty? # Maybe consider using a proper event loop here: readable, _, _ = ::IO.select(@running.keys, nil, nil, duration) diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb index d661dda..fd87301 100644 --- a/lib/async/container/notify/console.rb +++ b/lib/async/container/notify/console.rb @@ -24,8 +24,8 @@ def initialize(logger) end # Send a message to the console. - def send(level: :debug, **message) - @logger.send(level, self) {message} + def send(level: :info, **message) + @logger.public_send(level, self) {message} end # Send an error message to the console. diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index d05f4c7..c14e988 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -122,10 +122,12 @@ def name= value # A human readable representation of the process. # @returns [String] - def to_s - "\#<#{self.class} #{@name}>" + def inspect + "\#<#{self.class} name=#{@name.inspect} status=#{@status.inspect} pid=#{@pid.inspect}>" end + alias to_s inspect + # Invoke {#terminate!} and then {#wait} for the child process to exit. def close self.terminate! @@ -149,22 +151,28 @@ def terminate! end # Wait for the child process to exit. + # @asynchronous This method may block. + # # @returns [::Process::Status] The process exit status. def wait if @pid && @status.nil? - _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) + Console.debug(self, "Waiting for process to exit...", pid: @pid) - if @status.nil? - sleep(0.01) + _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) + + while @status.nil? + sleep(0.1) + _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) - end - - if @status.nil? - Console.warn(self) {"Process #{@pid} is blocking, has it exited?"} - _, @status = ::Process.wait2(@pid) + + if @status.nil? + Console.warn(self) {"Process #{@pid} is blocking, has it exited?"} + end end end + Console.debug(self, "Process exited.", pid: @pid, status: @status) + return @status end end diff --git a/releases.md b/releases.md new file mode 100644 index 0000000..085fbef --- /dev/null +++ b/releases.md @@ -0,0 +1,5 @@ +# Releases + +## Unreleased + + - Improved logging when child process fails and container startup. From 61f26882580c41735a4fee47aeb4826e5ab77d5e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 22 Dec 2024 14:59:43 +1300 Subject: [PATCH 112/166] More robust signal handling. --- lib/async/container/controller.rb | 61 ++++++++++++++++++------------- lib/async/container/group.rb | 14 +++++-- lib/async/container/process.rb | 3 +- releases.md | 1 + 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index bc6da05..fb7b3a7 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -187,47 +187,56 @@ def reload def run @notify&.status!("Initializing...") + with_signal_handlers do + self.start + + while @container&.running? + begin + @container.wait + rescue SignalException => exception + if handler = @signals[exception.signo] + begin + handler.call + rescue SetupError => error + Console.error(self) {error} + end + else + raise + end + end + end + rescue Interrupt + self.stop + rescue Terminate + self.stop(false) + ensure + self.stop(false) + end + end + + private def with_signal_handlers # I thought this was the default... but it doesn't always raise an exception unless you do this explicitly. - # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. + interrupt_action = Signal.trap(:INT) do - # $stderr.puts "Received INT signal, terminating...", caller + # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. + # $stderr.puts "Received Interrupt signal, terminating...", caller ::Thread.current.raise(Interrupt) end terminate_action = Signal.trap(:TERM) do - # $stderr.puts "Received TERM signal, terminating...", caller + # $stderr.puts "Received Terminate signal, terminating...", caller ::Thread.current.raise(Terminate) end hangup_action = Signal.trap(:HUP) do - # $stderr.puts "Received HUP signal, restarting...", caller + # $stderr.puts "Received Hangup signal, restarting...", caller ::Thread.current.raise(Hangup) end - self.start - - while @container&.running? - begin - @container.wait - rescue SignalException => exception - if handler = @signals[exception.signo] - begin - handler.call - rescue SetupError => error - Console.error(self) {error} - end - else - raise - end - end + ::Thread.handle_interrupt(SignalException => :on_blocking) do + yield end - rescue Interrupt - self.stop - rescue Terminate - self.stop(false) ensure - self.stop(false) - # Restore the interrupt handler: Signal.trap(:INT, interrupt_action) Signal.trap(:TERM, terminate_action) diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 91d47dd..82a844a 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -142,11 +142,19 @@ def wait_for_children(duration = nil) if !@running.empty? # Maybe consider using a proper event loop here: + if ready = self.select(duration) + ready.each do |io| + @running[io].resume + end + end + end + end + + def select(duration) + ::Thread.handle_interrupt(SignalException => :immediate) do readable, _, _ = ::IO.select(@running.keys, nil, nil, duration) - readable&.each do |io| - @running[io].resume - end + return readable end end diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index c14e988..cb0574c 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -69,7 +69,8 @@ def self.fork(**options) Signal.trap(:INT) {::Thread.current.raise(Interrupt)} Signal.trap(:TERM) {::Thread.current.raise(Terminate)} - begin + # This could be a configuration option: + ::Thread.handle_interrupt(SignalException => :immediate) do yield Instance.for(process) rescue Interrupt # Graceful exit. diff --git a/releases.md b/releases.md index 085fbef..622e307 100644 --- a/releases.md +++ b/releases.md @@ -2,4 +2,5 @@ ## Unreleased + - Improve container signal handling reliability by using `Thread.handle_interrupt` except at known safe points. - Improved logging when child process fails and container startup. From 46646c44c4c9953dc867cca58f6f704c334ca519 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 22 Dec 2024 16:53:39 +1300 Subject: [PATCH 113/166] Add puma example. --- examples/puma/application.rb | 17 ++++++++++++++++ examples/puma/config.ru | 3 +++ examples/puma/gems.locked | 39 ++++++++++++++++++++++++++++++++++++ examples/puma/gems.rb | 6 ++++++ examples/puma/puma.rb | 6 ++++++ examples/puma/readme.md | 36 +++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+) create mode 100755 examples/puma/application.rb create mode 100644 examples/puma/config.ru create mode 100644 examples/puma/gems.locked create mode 100644 examples/puma/gems.rb create mode 100644 examples/puma/puma.rb create mode 100644 examples/puma/readme.md diff --git a/examples/puma/application.rb b/examples/puma/application.rb new file mode 100755 index 0000000..44f28b7 --- /dev/null +++ b/examples/puma/application.rb @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "async/container" +require "console" + +# Console.logger.debug! + +class Application < Async::Container::Controller + def setup(container) + container.spawn(name: "Web") do |instance| + instance.exec("bundle", "exec", "puma", "-C", "puma.rb", ready: false) + end + end +end + +Application.new.run diff --git a/examples/puma/config.ru b/examples/puma/config.ru new file mode 100644 index 0000000..8440fb6 --- /dev/null +++ b/examples/puma/config.ru @@ -0,0 +1,3 @@ +run do |env| + [200, {"content-type" => "text/plain"}, ["Hello World"]] +end diff --git a/examples/puma/gems.locked b/examples/puma/gems.locked new file mode 100644 index 0000000..f874949 --- /dev/null +++ b/examples/puma/gems.locked @@ -0,0 +1,39 @@ +PATH + remote: ../.. + specs: + async-container (0.18.3) + async (~> 2.10) + +GEM + remote: https://rubygems.org/ + specs: + async (2.21.1) + console (~> 1.29) + fiber-annotation + io-event (~> 1.6, >= 1.6.5) + console (1.29.2) + fiber-annotation + fiber-local (~> 1.1) + json + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.0) + io-event (1.7.5) + json (2.9.1) + nio4r (2.7.4) + puma (6.5.0) + nio4r (~> 2.0) + rack (3.1.8) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + async-container! + puma + rack (~> 3) + +BUNDLED WITH + 2.5.22 diff --git a/examples/puma/gems.rb b/examples/puma/gems.rb new file mode 100644 index 0000000..585bdcd --- /dev/null +++ b/examples/puma/gems.rb @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "async-container", path: "../.." + +gem "puma" +gem "rack", "~> 3" diff --git a/examples/puma/puma.rb b/examples/puma/puma.rb new file mode 100644 index 0000000..2d12df3 --- /dev/null +++ b/examples/puma/puma.rb @@ -0,0 +1,6 @@ +on_booted do + require "async/container/notify" + + notify = Async::Container::Notify.open! + notify.ready! +end diff --git a/examples/puma/readme.md b/examples/puma/readme.md new file mode 100644 index 0000000..c9e1648 --- /dev/null +++ b/examples/puma/readme.md @@ -0,0 +1,36 @@ +# Puma Example + +This example shows how to start Puma in a container, using `on_boot` for process readiness. + +## Usage + +``` +> bundle exec ./application.rb + 0.0s info: Async::Container::Notify::Console [oid=0x474] [ec=0x488] [pid=196250] [2024-12-22 16:53:08 +1300] + | {:status=>"Initializing..."} + 0.0s info: Application [oid=0x4b0] [ec=0x488] [pid=196250] [2024-12-22 16:53:08 +1300] + | Controller starting... +Puma starting in single mode... +* Puma version: 6.5.0 ("Sky's Version") +* Ruby version: ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [x86_64-linux] +* Min threads: 0 +* Max threads: 5 +* Environment: development +* PID: 196252 +* Listening on http://0.0.0.0:9292 +Use Ctrl-C to stop + 0.12s info: Async::Container::Notify::Console [oid=0x474] [ec=0x488] [pid=196250] [2024-12-22 16:53:08 +1300] + | {:ready=>true} + 0.12s info: Application [oid=0x4b0] [ec=0x488] [pid=196250] [2024-12-22 16:53:08 +1300] + | Controller started... +^C21.62s info: Async::Container::Group [oid=0x4ec] [ec=0x488] [pid=196250] [2024-12-22 16:53:30 +1300] + | Stopping all processes... + | { + | "timeout": true + | } +21.62s info: Async::Container::Group [oid=0x4ec] [ec=0x488] [pid=196250] [2024-12-22 16:53:30 +1300] + | Sending interrupt to 1 running processes... +- Gracefully stopping, waiting for requests to finish +=== puma shutdown: 2024-12-22 16:53:30 +1300 === +- Goodbye! +``` From 669e835730058b89991e14c673dfd4f5d3031ee0 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 23 Dec 2024 15:19:38 +1300 Subject: [PATCH 114/166] Improved error logging (backtrace). --- lib/async/container/controller.rb | 2 +- lib/async/container/generic.rb | 2 +- lib/async/container/process.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index fb7b3a7..6484d17 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -198,7 +198,7 @@ def run begin handler.call rescue SetupError => error - Console.error(self) {error} + Console.error(self, error) end else raise diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 185ae57..caa2185 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -171,7 +171,7 @@ def spawn(name: nil, restart: false, key: nil, &block) Console.debug(self) {"#{child} exited with #{status}"} else @statistics.failure! - Console.error(self) {status} + Console.error(self, status: status) end if restart diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index cb0574c..fda47df 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -75,7 +75,7 @@ def self.fork(**options) rescue Interrupt # Graceful exit. rescue Exception => error - Console.error(self) {error} + Console.error(self, error) exit!(1) end From 96aeb147fe2870271c2690b288c4821e3b2a42fb Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 23 Dec 2024 15:21:10 +1300 Subject: [PATCH 115/166] Add default HUP signal handling for child process. --- examples/minimal.rb | 2 +- lib/async/container/process.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/minimal.rb b/examples/minimal.rb index da6c1c6..b4d921f 100644 --- a/examples/minimal.rb +++ b/examples/minimal.rb @@ -61,7 +61,7 @@ def initialize(&block) @pid = Process.fork do Signal.trap(:INT) {::Thread.current.raise(Interrupt)} - Signal.trap(:INT) {::Thread.current.raise(Terminate)} + Signal.trap(:TERM) {::Thread.current.raise(Terminate)} @channel.in.close diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index fda47df..b3d235d 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -68,6 +68,7 @@ def self.fork(**options) # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. Signal.trap(:INT) {::Thread.current.raise(Interrupt)} Signal.trap(:TERM) {::Thread.current.raise(Terminate)} + Signal.trap(:HUP) {::Thread.current.raise(Hangup)} # This could be a configuration option: ::Thread.handle_interrupt(SignalException => :immediate) do From 1b1ff07276c0eed8f30ca642dec859aae511c8db Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 23 Dec 2024 15:22:21 +1300 Subject: [PATCH 116/166] Updated puma example. --- examples/puma/application.rb | 16 ++++++++++++++-- examples/puma/config.ru | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/puma/application.rb b/examples/puma/application.rb index 44f28b7..6de687e 100755 --- a/examples/puma/application.rb +++ b/examples/puma/application.rb @@ -8,8 +8,20 @@ class Application < Async::Container::Controller def setup(container) - container.spawn(name: "Web") do |instance| - instance.exec("bundle", "exec", "puma", "-C", "puma.rb", ready: false) + container.spawn(name: "Web", restart: true) do |instance| + pid = ::Process.spawn("puma") + + instance.ready! + + begin + status = ::Process.wait2(pid) + rescue Async::Container::Hangup + Console.warn(self, "Restarting puma...") + ::Process.kill("USR1", pid) + retry + ensure + ::Process.kill("TERM", pid) + end end end end diff --git a/examples/puma/config.ru b/examples/puma/config.ru index 8440fb6..815d433 100644 --- a/examples/puma/config.ru +++ b/examples/puma/config.ru @@ -1,3 +1,3 @@ run do |env| - [200, {"content-type" => "text/plain"}, ["Hello World"]] + [200, {"content-type" => "text/plain"}, ["Hello World #{Time.now}"]] end From 411ea4f19c7d13e72a22dd8be19ed64534ef87e5 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 23 Dec 2024 15:24:49 +1300 Subject: [PATCH 117/166] Improved signal handling. --- lib/async/container/controller.rb | 14 +++++++------- lib/async/container/group.rb | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 6484d17..df24c16 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -205,13 +205,13 @@ def run end end end - rescue Interrupt - self.stop - rescue Terminate - self.stop(false) - ensure - self.stop(false) end + rescue Interrupt + self.stop + rescue Terminate + self.stop(false) + ensure + self.stop(false) end private def with_signal_handlers @@ -233,7 +233,7 @@ def run ::Thread.current.raise(Hangup) end - ::Thread.handle_interrupt(SignalException => :on_blocking) do + ::Thread.handle_interrupt(SignalException => :never) do yield end ensure diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 82a844a..17271c5 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -150,6 +150,7 @@ def wait_for_children(duration = nil) end end + # Wait for a child process to exit OR a signal to be received. def select(duration) ::Thread.handle_interrupt(SignalException => :immediate) do readable, _, _ = ::IO.select(@running.keys, nil, nil, duration) From 2d226cbe85e747d1e3d54e6721237c955c0c13be Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 23 Dec 2024 15:24:56 +1300 Subject: [PATCH 118/166] Documentation. --- examples/puma/application.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/puma/application.rb b/examples/puma/application.rb index 6de687e..58ad39a 100755 --- a/examples/puma/application.rb +++ b/examples/puma/application.rb @@ -9,6 +9,10 @@ class Application < Async::Container::Controller def setup(container) container.spawn(name: "Web", restart: true) do |instance| + # Replace the current process with Puma: + # instance.exec("bundle", "exec", "puma", "-C", "puma.rb", ready: false) + + # Manage a child process of puma / puma workers: pid = ::Process.spawn("puma") instance.ready! From 2080bc7f3ce6a331dd673f4611c6c05593e51707 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 23 Dec 2024 15:32:19 +1300 Subject: [PATCH 119/166] Fix puma example on_boot notify ready. --- examples/puma/application.rb | 2 +- examples/puma/puma.rb | 2 +- lib/async/container/notify/console.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/puma/application.rb b/examples/puma/application.rb index 58ad39a..bfce0c0 100755 --- a/examples/puma/application.rb +++ b/examples/puma/application.rb @@ -13,7 +13,7 @@ def setup(container) # instance.exec("bundle", "exec", "puma", "-C", "puma.rb", ready: false) # Manage a child process of puma / puma workers: - pid = ::Process.spawn("puma") + pid = ::Process.spawn("puma", "-C", "puma.rb") instance.ready! diff --git a/examples/puma/puma.rb b/examples/puma/puma.rb index 2d12df3..e032973 100644 --- a/examples/puma/puma.rb +++ b/examples/puma/puma.rb @@ -2,5 +2,5 @@ require "async/container/notify" notify = Async::Container::Notify.open! - notify.ready! + notify&.ready! end diff --git a/lib/async/container/notify/console.rb b/lib/async/container/notify/console.rb index fd87301..a777f1f 100644 --- a/lib/async/container/notify/console.rb +++ b/lib/async/container/notify/console.rb @@ -5,7 +5,7 @@ require_relative "client" -require "console/logger" +require "console" module Async module Container From 687a394f7282ff2d3970e18f6b9bff99c3510f4c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 23 Dec 2024 16:54:35 +1300 Subject: [PATCH 120/166] Updated puma example using shared socket. --- examples/puma/application.rb | 42 ++++++++++++++++++++++-------------- examples/puma/gems.locked | 2 ++ examples/puma/gems.rb | 1 + 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/examples/puma/application.rb b/examples/puma/application.rb index bfce0c0..3e2b196 100755 --- a/examples/puma/application.rb +++ b/examples/puma/application.rb @@ -4,30 +4,40 @@ require "async/container" require "console" +require "io/endpoint/host_endpoint" +require "io/endpoint/bound_endpoint" + # Console.logger.debug! class Application < Async::Container::Controller + def endpoint + IO::Endpoint.tcp("0.0.0.0", 9292) + end + + def bound_socket + bound = endpoint.bound + + bound.sockets.each do |socket| + socket.listen(Socket::SOMAXCONN) + end + + return bound + end + def setup(container) + @bound = bound_socket + container.spawn(name: "Web", restart: true) do |instance| - # Replace the current process with Puma: - # instance.exec("bundle", "exec", "puma", "-C", "puma.rb", ready: false) + env = ENV.to_h - # Manage a child process of puma / puma workers: - pid = ::Process.spawn("puma", "-C", "puma.rb") - - instance.ready! - - begin - status = ::Process.wait2(pid) - rescue Async::Container::Hangup - Console.warn(self, "Restarting puma...") - ::Process.kill("USR1", pid) - retry - ensure - ::Process.kill("TERM", pid) + @bound.sockets.each_with_index do |socket, index| + env["PUMA_INHERIT_#{index}"] = "#{socket.fileno}:tcp://0.0.0.0:9292" end + + instance.exec(env, "bundle", "exec", "puma", "-C", "puma.rb", ready: false) end end end -Application.new.run +application = Application.new +application.run diff --git a/examples/puma/gems.locked b/examples/puma/gems.locked index f874949..73eedae 100644 --- a/examples/puma/gems.locked +++ b/examples/puma/gems.locked @@ -19,6 +19,7 @@ GEM fiber-local (1.1.0) fiber-storage fiber-storage (1.0.0) + io-endpoint (0.14.0) io-event (1.7.5) json (2.9.1) nio4r (2.7.4) @@ -32,6 +33,7 @@ PLATFORMS DEPENDENCIES async-container! + io-endpoint puma rack (~> 3) diff --git a/examples/puma/gems.rb b/examples/puma/gems.rb index 585bdcd..70ed123 100644 --- a/examples/puma/gems.rb +++ b/examples/puma/gems.rb @@ -1,6 +1,7 @@ source "https://rubygems.org" gem "async-container", path: "../.." +gem "io-endpoint" gem "puma" gem "rack", "~> 3" From 990f2d38ea1206f901f6415219738954bf091c4f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 23 Dec 2024 16:56:19 +1300 Subject: [PATCH 121/166] Expose `Process#spawn`. --- lib/async/container/notify/server.rb | 1 - lib/async/container/process.rb | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index 26b55b2..6da975d 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -11,7 +11,6 @@ module Async module Container module Notify class Server - NOTIFY_SOCKET = "NOTIFY_SOCKET" MAXIMUM_MESSAGE_SIZE = 4096 def self.load(message) diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index b3d235d..9ea189d 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -84,15 +84,13 @@ def self.fork(**options) end end - # def self.spawn(*arguments, name: nil, **options) - # self.new(name: name) do |process| - # unless options.key?(:out) - # options[:out] = process.out - # end - # - # ::Process.spawn(*arguments, **options) - # end - # end + def self.spawn(*arguments, name: nil, **options) + self.new(name: name) do |process| + Notify::Pipe.new(process.out).before_spawn(arguments, options) + + ::Process.spawn(*arguments, **options) + end + end # Initialize the process. # @parameter name [String] The name to use for the child process. @@ -122,6 +120,9 @@ def name= value # @attribute [String] attr :name + # @attribute [Integer] The process identifier. + attr :pid + # A human readable representation of the process. # @returns [String] def inspect From 4a3aa54a16ae64f6ae3ca6b0edd2bb0356592c5f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 10 Jan 2025 17:51:59 +1300 Subject: [PATCH 122/166] Explicit support for `Restart`. --- guides/getting-started/readme.md | 2 ++ lib/async/container/controller.rb | 8 ++++---- lib/async/container/error.rb | 2 +- lib/async/container/process.rb | 9 ++++++++- lib/async/container/thread.rb | 5 +++++ 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index 04ca895..7154ceb 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -74,6 +74,8 @@ controller.run ## Signal Handling +`SIGINT` is the reload signal. You may send this to a program to request that it reload its configuration. The default behavior is to gracefully reload the container. + `SIGINT` is the interrupt signal. The terminal sends it to the foreground process when the user presses **ctrl-c**. The default behavior is to terminate the process, but it can be caught or ignored. The intention is to provide a mechanism for an orderly, graceful shutdown. `SIGQUIT` is the dump core signal. The terminal sends it to the foreground process when the user presses **ctrl-\\**. The default behavior is to terminate the process and dump core, but it can be caught or ignored. The intention is to provide a mechanism for the user to abort the process. You can look at `SIGINT` as "user-initiated happy termination" and `SIGQUIT` as "user-initiated unhappy termination." diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index df24c16..4d3a222 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -219,18 +219,18 @@ def run interrupt_action = Signal.trap(:INT) do # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. - # $stderr.puts "Received Interrupt signal, terminating...", caller + # $stderr.puts "Received INT signal, interrupting...", caller ::Thread.current.raise(Interrupt) end terminate_action = Signal.trap(:TERM) do - # $stderr.puts "Received Terminate signal, terminating...", caller + # $stderr.puts "Received TERM signal, terminating...", caller ::Thread.current.raise(Terminate) end hangup_action = Signal.trap(:HUP) do - # $stderr.puts "Received Hangup signal, restarting...", caller - ::Thread.current.raise(Hangup) + # $stderr.puts "Received HUP signal, restarting...", caller + ::Thread.current.raise(Restart) end ::Thread.handle_interrupt(SignalException => :never) do diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index d798e3e..adfff85 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -19,7 +19,7 @@ def initialize end end - class Hangup < SignalException + class Restart < SignalException SIGHUP = Signal.list["HUP"] def initialize diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 9ea189d..aa5b8cf 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -68,7 +68,7 @@ def self.fork(**options) # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. Signal.trap(:INT) {::Thread.current.raise(Interrupt)} Signal.trap(:TERM) {::Thread.current.raise(Terminate)} - Signal.trap(:HUP) {::Thread.current.raise(Hangup)} + Signal.trap(:HUP) {::Thread.current.raise(Restart)} # This could be a configuration option: ::Thread.handle_interrupt(SignalException => :immediate) do @@ -153,6 +153,13 @@ def terminate! end end + # Send `SIGHUP` to the child process. + def restart! + unless @status + ::Process.kill(:HUP, @pid) + end + end + # Wait for the child process to exit. # @asynchronous This method may block. # diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index d0310b2..280ebdd 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -152,6 +152,11 @@ def terminate! @thread.raise(Terminate) end + # Raise {Restart} in the child thread. + def restart! + @thread.raise(Restart) + end + # Wait for the thread to exit and return he exit status. # @returns [Status] def wait From 83c00a8c1bd5b104152b81c0b1aa58a8502088f6 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 10 Jan 2025 17:52:34 +1300 Subject: [PATCH 123/166] Remove unnecessary `--keep-file-descriptors` option from notify pipe test. --- test/async/container/notify/pipe.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/async/container/notify/pipe.rb b/test/async/container/notify/pipe.rb index 73f9557..9c841ed 100644 --- a/test/async/container/notify/pipe.rb +++ b/test/async/container/notify/pipe.rb @@ -14,7 +14,7 @@ container.spawn(restart: false) do |instance| instance.exec( - "bundle", "exec", "--keep-file-descriptors", + "bundle", "exec", notify_script, ready: false ) end From d1c61e1f33ac306c6d027968333a69f4b55d5358 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 10 Jan 2025 17:52:59 +1300 Subject: [PATCH 124/166] Reduce sleep overhead in tests. --- test/async/container/.dots.rb | 2 +- test/async/container/.graceful.rb | 4 ++-- test/async/container/controller.rb | 10 +++++----- test/async/container/notify/.notify.rb | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/async/container/.dots.rb b/test/async/container/.dots.rb index cd4bda4..f510918 100755 --- a/test/async/container/.dots.rb +++ b/test/async/container/.dots.rb @@ -14,7 +14,7 @@ def setup(container) instance.ready! # This is to avoid race conditions in the controller in test conditions. - sleep 0.1 + sleep 0.001 $stdout.write "." diff --git a/test/async/container/.graceful.rb b/test/async/container/.graceful.rb index 31e708d..87ce1dd 100755 --- a/test/async/container/.graceful.rb +++ b/test/async/container/.graceful.rb @@ -14,7 +14,7 @@ def setup(container) instance.ready! # This is to avoid race conditions in the controller in test conditions. - sleep 0.1 + sleep 0.001 clock = Async::Clock.start @@ -34,7 +34,7 @@ def setup(container) end end -controller = Graceful.new(graceful_stop: 1) +controller = Graceful.new(graceful_stop: 0.01) begin controller.run diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index faac968..d9d29e3 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -18,18 +18,18 @@ def controller.setup(container) container.spawn(key: "test") do |instance| instance.ready! - sleep(0.2) + sleep(0.02) @output.write(".") @output.flush - sleep(0.4) + sleep(0.04) end container.spawn do |instance| instance.ready! - sleep(0.3) + sleep(0.03) @output.write(",") @output.flush @@ -64,7 +64,7 @@ def controller.setup(container) it "can spawn a reactor" do def controller.setup(container) container.async do |task| - task.sleep 1 + task.sleep 0.001 end end @@ -123,7 +123,7 @@ def after(error = nil) expect(input.gets).to be == "Exiting...\n" exit_time = input.gets.to_f - expect(exit_time - graceful_shutdown_time).to be >= 1.0 + expect(exit_time - graceful_shutdown_time).to be >= 0.01 end end diff --git a/test/async/container/notify/.notify.rb b/test/async/container/notify/.notify.rb index 3fbb0c2..fee8d90 100755 --- a/test/async/container/notify/.notify.rb +++ b/test/async/container/notify/.notify.rb @@ -9,11 +9,11 @@ class MyController < Async::Container::Controller def setup(container) container.run(restart: false) do |instance| - sleep(rand) + sleep(0.001) instance.ready! - sleep(rand) + sleep(0.001) end end end From d625155af5d46b5fc0c0ee1f02639691126b77a6 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 10 Jan 2025 17:55:17 +1300 Subject: [PATCH 125/166] Modernize code. --- .github/workflows/documentation-coverage.yaml | 2 +- .github/workflows/documentation.yaml | 2 +- .github/workflows/test-coverage.yaml | 4 ++-- .github/workflows/test-external.yaml | 1 + .github/workflows/test.yaml | 1 + examples/puma/application.rb | 3 +++ examples/puma/config.ru | 2 ++ examples/puma/gems.rb | 5 +++++ examples/puma/puma.rb | 5 +++++ lib/async/container/controller.rb | 2 +- lib/async/container/error.rb | 2 +- lib/async/container/process.rb | 2 +- lib/async/container/thread.rb | 2 +- license.md | 2 +- test/async/container/.dots.rb | 2 +- test/async/container/.graceful.rb | 2 +- test/async/container/controller.rb | 2 +- test/async/container/notify/.notify.rb | 2 +- test/async/container/notify/pipe.rb | 2 +- 19 files changed, 31 insertions(+), 14 deletions(-) diff --git a/.github/workflows/documentation-coverage.yaml b/.github/workflows/documentation-coverage.yaml index b3bac9a..8d801c5 100644 --- a/.github/workflows/documentation-coverage.yaml +++ b/.github/workflows/documentation-coverage.yaml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.3" + ruby-version: "3.4" bundler-cache: true - name: Validate coverage diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index f5f553a..e47c6b3 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -29,7 +29,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.3" + ruby-version: "3.4" bundler-cache: true - name: Installing packages diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 50e9293..e6dc5c3 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -21,7 +21,7 @@ jobs: - macos ruby: - - "3.3" + - "3.4" steps: - uses: actions/checkout@v4 @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.3" + ruby-version: "3.4" bundler-cache: true - uses: actions/download-artifact@v4 diff --git a/.github/workflows/test-external.yaml b/.github/workflows/test-external.yaml index 21898f5..c9cc200 100644 --- a/.github/workflows/test-external.yaml +++ b/.github/workflows/test-external.yaml @@ -23,6 +23,7 @@ jobs: - "3.1" - "3.2" - "3.3" + - "3.4" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0769a98..5d597fa 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,6 +24,7 @@ jobs: - "3.1" - "3.2" - "3.3" + - "3.4" experimental: [false] diff --git a/examples/puma/application.rb b/examples/puma/application.rb index 3e2b196..3c6e30f 100755 --- a/examples/puma/application.rb +++ b/examples/puma/application.rb @@ -1,6 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# Released under the MIT License. +# Copyright, 2024-2025, by Samuel Williams. + require "async/container" require "console" diff --git a/examples/puma/config.ru b/examples/puma/config.ru index 815d433..acb0a83 100644 --- a/examples/puma/config.ru +++ b/examples/puma/config.ru @@ -1,3 +1,5 @@ +# frozen_string_literal: true + run do |env| [200, {"content-type" => "text/plain"}, ["Hello World #{Time.now}"]] end diff --git a/examples/puma/gems.rb b/examples/puma/gems.rb index 70ed123..604ae72 100644 --- a/examples/puma/gems.rb +++ b/examples/puma/gems.rb @@ -1,3 +1,8 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2024-2025, by Samuel Williams. + source "https://rubygems.org" gem "async-container", path: "../.." diff --git a/examples/puma/puma.rb b/examples/puma/puma.rb index e032973..cf79085 100644 --- a/examples/puma/puma.rb +++ b/examples/puma/puma.rb @@ -1,3 +1,8 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2024-2025, by Samuel Williams. + on_booted do require "async/container/notify" diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index 4d3a222..fdeb031 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2024, by Samuel Williams. +# Copyright, 2018-2025, by Samuel Williams. require_relative "error" require_relative "best" diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index adfff85..802ecfa 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2024, by Samuel Williams. +# Copyright, 2019-2025, by Samuel Williams. module Async module Container diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index aa5b8cf..fa995f2 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2024, by Samuel Williams. +# Copyright, 2020-2025, by Samuel Williams. require_relative "channel" require_relative "error" diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb index 280ebdd..eff0497 100644 --- a/lib/async/container/thread.rb +++ b/lib/async/container/thread.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2024, by Samuel Williams. +# Copyright, 2020-2025, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. require_relative "channel" diff --git a/license.md b/license.md index 18e2661..7867ed1 100644 --- a/license.md +++ b/license.md @@ -1,6 +1,6 @@ # MIT License -Copyright, 2017-2024, by Samuel Williams. +Copyright, 2017-2025, by Samuel Williams. Copyright, 2019, by Yuji Yaginuma. Copyright, 2020, by Olle Jonsson. Copyright, 2020, by Juan Antonio Martín Lucas. diff --git a/test/async/container/.dots.rb b/test/async/container/.dots.rb index f510918..551b4fc 100755 --- a/test/async/container/.dots.rb +++ b/test/async/container/.dots.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2024, by Samuel Williams. +# Copyright, 2020-2025, by Samuel Williams. require_relative "../../../lib/async/container/controller" diff --git a/test/async/container/.graceful.rb b/test/async/container/.graceful.rb index 87ce1dd..af2d473 100755 --- a/test/async/container/.graceful.rb +++ b/test/async/container/.graceful.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2024, by Samuel Williams. +# Copyright, 2024-2025, by Samuel Williams. require_relative "../../../lib/async/container/controller" diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index d9d29e3..ee3a9b0 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2024, by Samuel Williams. +# Copyright, 2018-2025, by Samuel Williams. require "async/container/controller" diff --git a/test/async/container/notify/.notify.rb b/test/async/container/notify/.notify.rb index fee8d90..4a692aa 100755 --- a/test/async/container/notify/.notify.rb +++ b/test/async/container/notify/.notify.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2024, by Samuel Williams. +# Copyright, 2020-2025, by Samuel Williams. require_relative "../../../../lib/async/container" diff --git a/test/async/container/notify/pipe.rb b/test/async/container/notify/pipe.rb index 9c841ed..8bd9072 100644 --- a/test/async/container/notify/pipe.rb +++ b/test/async/container/notify/pipe.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. +# Copyright, 2020-2025, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. require "async/container/controller" From bc27b98e04ef487d9a4d3a9e9c8148b6c29791fd Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 11 Jan 2025 21:10:35 +1300 Subject: [PATCH 126/166] Move implementation of `Thread` and `Process` into `Threaded::Child` and `Process::Child` respectively. --- fixtures/async/container/a_container.rb | 7 + lib/async/container/forked.rb | 186 ++++++++++++++++++++- lib/async/container/generic.rb | 8 +- lib/async/container/process.rb | 190 ---------------------- lib/async/container/thread.rb | 204 ------------------------ lib/async/container/threaded.rb | 198 ++++++++++++++++++++++- test/async/container/forked.rb | 1 + test/async/container/notify.rb | 2 + 8 files changed, 394 insertions(+), 402 deletions(-) delete mode 100644 lib/async/container/process.rb delete mode 100644 lib/async/container/thread.rb diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb index 5d1c1b0..3597525 100644 --- a/fixtures/async/container/a_container.rb +++ b/fixtures/async/container/a_container.rb @@ -3,6 +3,8 @@ # Released under the MIT License. # Copyright, 2019-2024, by Samuel Williams. +require "async" + module Async module Container AContainer = Sus::Shared("a container") do @@ -67,10 +69,15 @@ module Container it "can stop the child process" do container.spawn do sleep(1) + rescue Interrupt + # Ignore. end expect(container).to be(:running?) + # TODO Investigate why without this, the interrupt can occur before the process is sleeping... + sleep 0.001 + container.stop expect(container).not.to be(:running?) diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 243480f..a2718f3 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -3,8 +3,11 @@ # Released under the MIT License. # Copyright, 2017-2024, by Samuel Williams. +require_relative "error" + require_relative "generic" -require_relative "process" +require_relative "channel" +require_relative "notify/pipe" module Async module Container @@ -15,11 +18,190 @@ def self.multiprocess? true end + # Represents a running child process from the point of view of the parent container. + class Child < Channel + # Represents a running child process from the point of view of the child process. + class Instance < Notify::Pipe + # Wrap an instance around the {Process} instance from within the forked child. + # @parameter process [Process] The process intance to wrap. + def self.for(process) + instance = self.new(process.out) + + # The child process won't be reading from the channel: + process.close_read + + instance.name = process.name + + return instance + end + + def initialize(io) + super + + @name = nil + end + + # Set the process title to the specified value. + # @parameter value [String] The name of the process. + def name= value + if @name = value + ::Process.setproctitle(@name) + end + end + + # The name of the process. + # @returns [String] + def name + @name + end + + # Replace the current child process with a different one. Forwards arguments and options to {::Process.exec}. + # This method replaces the child process with the new executable, thus this method never returns. + def exec(*arguments, ready: true, **options) + if ready + self.ready!(status: "(exec)") + else + self.before_spawn(arguments, options) + end + + ::Process.exec(*arguments, **options) + end + end + + # Fork a child process appropriate for a container. + # @returns [Process] + def self.fork(**options) + # $stderr.puts fork: caller + self.new(**options) do |process| + ::Process.fork do + # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. + Signal.trap(:INT) {::Thread.current.raise(Interrupt)} + Signal.trap(:TERM) {::Thread.current.raise(Terminate)} + Signal.trap(:HUP) {::Thread.current.raise(Restart)} + + # This could be a configuration option: + ::Thread.handle_interrupt(SignalException => :immediate) do + yield Instance.for(process) + rescue Interrupt + # Graceful exit. + rescue Exception => error + Console.error(self, error) + + exit!(1) + end + end + end + end + + def self.spawn(*arguments, name: nil, **options) + self.new(name: name) do |process| + Notify::Pipe.new(process.out).before_spawn(arguments, options) + + ::Process.spawn(*arguments, **options) + end + end + + # Initialize the process. + # @parameter name [String] The name to use for the child process. + def initialize(name: nil) + super() + + @name = name + @status = nil + @pid = nil + + @pid = yield(self) + + # The parent process won't be writing to the channel: + self.close_write + end + + # Set the name of the process. + # Invokes {::Process.setproctitle} if invoked in the child process. + def name= value + @name = value + + # If we are the child process: + ::Process.setproctitle(@name) if @pid.nil? + end + + # The name of the process. + # @attribute [String] + attr :name + + # @attribute [Integer] The process identifier. + attr :pid + + # A human readable representation of the process. + # @returns [String] + def inspect + "\#<#{self.class} name=#{@name.inspect} status=#{@status.inspect} pid=#{@pid.inspect}>" + end + + alias to_s inspect + + # Invoke {#terminate!} and then {#wait} for the child process to exit. + def close + self.terminate! + self.wait + ensure + super + end + + # Send `SIGINT` to the child process. + def interrupt! + unless @status + ::Process.kill(:INT, @pid) + end + end + + # Send `SIGTERM` to the child process. + def terminate! + unless @status + ::Process.kill(:TERM, @pid) + end + end + + # Send `SIGHUP` to the child process. + def restart! + unless @status + ::Process.kill(:HUP, @pid) + end + end + + # Wait for the child process to exit. + # @asynchronous This method may block. + # + # @returns [::Process::Status] The process exit status. + def wait + if @pid && @status.nil? + Console.debug(self, "Waiting for process to exit...", pid: @pid) + + _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) + + while @status.nil? + sleep(0.1) + + _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) + + if @status.nil? + Console.warn(self) {"Process #{@pid} is blocking, has it exited?"} + end + end + end + + Console.debug(self, "Process exited.", pid: @pid, status: @status) + + return @status + end + end + + # Start a named child process and execute the provided block in it. # @parameter name [String] The name (title) of the child process. # @parameter block [Proc] The block to execute in the child process. def start(name, &block) - Process.fork(name: name, &block) + Child.fork(name: name, &block) end end end diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index caa2185..d994374 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -3,8 +3,6 @@ # Released under the MIT License. # Copyright, 2019-2024, by Samuel Williams. -require "async" - require "etc" require_relative "group" @@ -197,8 +195,12 @@ def run(count: Container.processor_count, **options, &block) # @deprecated Please use {spawn} or {run} instead. def async(**options, &block) + # warn "#{self.class}##{__method__} is deprecated, please use `spawn` or `run` instead.", uplevel: 1 + + require "async" + spawn(**options) do |instance| - Async::Reactor.run(instance, &block) + Async(instance, &block) end end diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb deleted file mode 100644 index fa995f2..0000000 --- a/lib/async/container/process.rb +++ /dev/null @@ -1,190 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2025, by Samuel Williams. - -require_relative "channel" -require_relative "error" - -require_relative "notify/pipe" - -module Async - module Container - # Represents a running child process from the point of view of the parent container. - class Process < Channel - # Represents a running child process from the point of view of the child process. - class Instance < Notify::Pipe - # Wrap an instance around the {Process} instance from within the forked child. - # @parameter process [Process] The process intance to wrap. - def self.for(process) - instance = self.new(process.out) - - # The child process won't be reading from the channel: - process.close_read - - instance.name = process.name - - return instance - end - - def initialize(io) - super - - @name = nil - end - - # Set the process title to the specified value. - # @parameter value [String] The name of the process. - def name= value - if @name = value - ::Process.setproctitle(@name) - end - end - - # The name of the process. - # @returns [String] - def name - @name - end - - # Replace the current child process with a different one. Forwards arguments and options to {::Process.exec}. - # This method replaces the child process with the new executable, thus this method never returns. - def exec(*arguments, ready: true, **options) - if ready - self.ready!(status: "(exec)") - else - self.before_spawn(arguments, options) - end - - ::Process.exec(*arguments, **options) - end - end - - # Fork a child process appropriate for a container. - # @returns [Process] - def self.fork(**options) - self.new(**options) do |process| - ::Process.fork do - # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. - Signal.trap(:INT) {::Thread.current.raise(Interrupt)} - Signal.trap(:TERM) {::Thread.current.raise(Terminate)} - Signal.trap(:HUP) {::Thread.current.raise(Restart)} - - # This could be a configuration option: - ::Thread.handle_interrupt(SignalException => :immediate) do - yield Instance.for(process) - rescue Interrupt - # Graceful exit. - rescue Exception => error - Console.error(self, error) - - exit!(1) - end - end - end - end - - def self.spawn(*arguments, name: nil, **options) - self.new(name: name) do |process| - Notify::Pipe.new(process.out).before_spawn(arguments, options) - - ::Process.spawn(*arguments, **options) - end - end - - # Initialize the process. - # @parameter name [String] The name to use for the child process. - def initialize(name: nil) - super() - - @name = name - @status = nil - @pid = nil - - @pid = yield(self) - - # The parent process won't be writing to the channel: - self.close_write - end - - # Set the name of the process. - # Invokes {::Process.setproctitle} if invoked in the child process. - def name= value - @name = value - - # If we are the child process: - ::Process.setproctitle(@name) if @pid.nil? - end - - # The name of the process. - # @attribute [String] - attr :name - - # @attribute [Integer] The process identifier. - attr :pid - - # A human readable representation of the process. - # @returns [String] - def inspect - "\#<#{self.class} name=#{@name.inspect} status=#{@status.inspect} pid=#{@pid.inspect}>" - end - - alias to_s inspect - - # Invoke {#terminate!} and then {#wait} for the child process to exit. - def close - self.terminate! - self.wait - ensure - super - end - - # Send `SIGINT` to the child process. - def interrupt! - unless @status - ::Process.kill(:INT, @pid) - end - end - - # Send `SIGTERM` to the child process. - def terminate! - unless @status - ::Process.kill(:TERM, @pid) - end - end - - # Send `SIGHUP` to the child process. - def restart! - unless @status - ::Process.kill(:HUP, @pid) - end - end - - # Wait for the child process to exit. - # @asynchronous This method may block. - # - # @returns [::Process::Status] The process exit status. - def wait - if @pid && @status.nil? - Console.debug(self, "Waiting for process to exit...", pid: @pid) - - _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) - - while @status.nil? - sleep(0.1) - - _, @status = ::Process.wait2(@pid, ::Process::WNOHANG) - - if @status.nil? - Console.warn(self) {"Process #{@pid} is blocking, has it exited?"} - end - end - end - - Console.debug(self, "Process exited.", pid: @pid, status: @status) - - return @status - end - end - end -end diff --git a/lib/async/container/thread.rb b/lib/async/container/thread.rb deleted file mode 100644 index eff0497..0000000 --- a/lib/async/container/thread.rb +++ /dev/null @@ -1,204 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2025, by Samuel Williams. -# Copyright, 2020, by Olle Jonsson. - -require_relative "channel" -require_relative "error" -require_relative "notify/pipe" - -module Async - module Container - # Represents a running child thread from the point of view of the parent container. - class Thread < Channel - # Used to propagate the exit status of a child process invoked by {Instance#exec}. - class Exit < Exception - # Initialize the exit status. - # @parameter status [::Process::Status] The process exit status. - def initialize(status) - @status = status - end - - # The process exit status. - # @attribute [::Process::Status] - attr :status - - # The process exit status if it was an error. - # @returns [::Process::Status | Nil] - def error - unless status.success? - status - end - end - end - - # Represents a running child thread from the point of view of the child thread. - class Instance < Notify::Pipe - # Wrap an instance around the {Thread} instance from within the threaded child. - # @parameter thread [Thread] The thread intance to wrap. - def self.for(thread) - instance = self.new(thread.out) - - return instance - end - - def initialize(io) - @name = nil - @thread = ::Thread.current - - super - end - - # Set the name of the thread. - # @parameter value [String] The name to set. - def name= value - @thread.name = value - end - - # Get the name of the thread. - # @returns [String] - def name - @thread.name - end - - # Execute a child process using {::Process.spawn}. In order to simulate {::Process.exec}, an {Exit} instance is raised to propagage exit status. - # This creates the illusion that this method does not return (normally). - def exec(*arguments, ready: true, **options) - if ready - self.ready!(status: "(spawn)") - else - self.before_spawn(arguments, options) - end - - begin - pid = ::Process.spawn(*arguments, **options) - ensure - _, status = ::Process.wait2(pid) - - raise Exit, status - end - end - end - - def self.fork(**options) - self.new(**options) do |thread| - ::Thread.new do - yield Instance.for(thread) - end - end - end - - # Initialize the thread. - # @parameter name [String] The name to use for the child thread. - def initialize(name: nil) - super() - - @status = nil - - @thread = yield(self) - @thread.report_on_exception = false - @thread.name = name - - @waiter = ::Thread.new do - begin - @thread.join - rescue Exit => exit - finished(exit.error) - rescue Interrupt - # Graceful shutdown. - finished - rescue Exception => error - finished(error) - else - finished - end - end - end - - # Set the name of the thread. - # @parameter value [String] The name to set. - def name= value - @thread.name = value - end - - # Get the name of the thread. - # @returns [String] - def name - @thread.name - end - - # A human readable representation of the thread. - # @returns [String] - def to_s - "\#<#{self.class} #{@thread.name}>" - end - - # Invoke {#terminate!} and then {#wait} for the child thread to exit. - def close - self.terminate! - self.wait - ensure - super - end - - # Raise {Interrupt} in the child thread. - def interrupt! - @thread.raise(Interrupt) - end - - # Raise {Terminate} in the child thread. - def terminate! - @thread.raise(Terminate) - end - - # Raise {Restart} in the child thread. - def restart! - @thread.raise(Restart) - end - - # Wait for the thread to exit and return he exit status. - # @returns [Status] - def wait - if @waiter - @waiter.join - @waiter = nil - end - - return @status - end - - # A pseudo exit-status wrapper. - class Status - # Initialise the status. - # @parameter error [::Process::Status] The exit status of the child thread. - def initialize(error = nil) - @error = error - end - - # Whether the status represents a successful outcome. - # @returns [Boolean] - def success? - @error.nil? - end - - # A human readable representation of the status. - def to_s - "\#<#{self.class} #{success? ? "success" : "failure"}>" - end - end - - protected - - # Invoked by the @waiter thread to indicate the outcome of the child thread. - def finished(error = nil) - if error - Console.error(self) {error} - end - - @status = Status.new(error) - self.close_write - end - end - end -end diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index c046e80..a6b78c1 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2024, by Samuel Williams. +# Copyright, 2017-2025, by Samuel Williams. require_relative "generic" -require_relative "thread" +require_relative "channel" +require_relative "notify/pipe" module Async module Container @@ -15,11 +16,202 @@ def self.multiprocess? false end + # Represents a running child thread from the point of view of the parent container. + class Child < Channel + # Used to propagate the exit status of a child process invoked by {Instance#exec}. + class Exit < Exception + # Initialize the exit status. + # @parameter status [::Process::Status] The process exit status. + def initialize(status) + @status = status + end + + # The process exit status. + # @attribute [::Process::Status] + attr :status + + # The process exit status if it was an error. + # @returns [::Process::Status | Nil] + def error + unless status.success? + status + end + end + end + + # Represents a running child thread from the point of view of the child thread. + class Instance < Notify::Pipe + # Wrap an instance around the {Thread} instance from within the threaded child. + # @parameter thread [Thread] The thread intance to wrap. + def self.for(thread) + instance = self.new(thread.out) + + return instance + end + + def initialize(io) + @name = nil + @thread = ::Thread.current + + super + end + + # Set the name of the thread. + # @parameter value [String] The name to set. + def name= value + @thread.name = value + end + + # Get the name of the thread. + # @returns [String] + def name + @thread.name + end + + # Execute a child process using {::Process.spawn}. In order to simulate {::Process.exec}, an {Exit} instance is raised to propagage exit status. + # This creates the illusion that this method does not return (normally). + def exec(*arguments, ready: true, **options) + if ready + self.ready!(status: "(spawn)") + else + self.before_spawn(arguments, options) + end + + begin + pid = ::Process.spawn(*arguments, **options) + ensure + _, status = ::Process.wait2(pid) + + raise Exit, status + end + end + end + + def self.fork(**options) + self.new(**options) do |thread| + ::Thread.new do + yield Instance.for(thread) + end + end + end + + # Initialize the thread. + # @parameter name [String] The name to use for the child thread. + def initialize(name: nil) + super() + + @status = nil + + @thread = yield(self) + @thread.report_on_exception = false + @thread.name = name + + @waiter = ::Thread.new do + begin + @thread.join + rescue Exit => exit + finished(exit.error) + rescue Interrupt + # Graceful shutdown. + finished + rescue Exception => error + finished(error) + else + finished + end + end + end + + # Set the name of the thread. + # @parameter value [String] The name to set. + def name= value + @thread.name = value + end + + # Get the name of the thread. + # @returns [String] + def name + @thread.name + end + + # A human readable representation of the thread. + # @returns [String] + def to_s + "\#<#{self.class} #{@thread.name}>" + end + + # Invoke {#terminate!} and then {#wait} for the child thread to exit. + def close + self.terminate! + self.wait + ensure + super + end + + # Raise {Interrupt} in the child thread. + def interrupt! + @thread.raise(Interrupt) + end + + # Raise {Terminate} in the child thread. + def terminate! + @thread.raise(Terminate) + end + + # Raise {Restart} in the child thread. + def restart! + @thread.raise(Restart) + end + + # Wait for the thread to exit and return he exit status. + # @returns [Status] + def wait + if @waiter + @waiter.join + @waiter = nil + end + + return @status + end + + # A pseudo exit-status wrapper. + class Status + # Initialise the status. + # @parameter error [::Process::Status] The exit status of the child thread. + def initialize(error = nil) + @error = error + end + + # Whether the status represents a successful outcome. + # @returns [Boolean] + def success? + @error.nil? + end + + # A human readable representation of the status. + def to_s + "\#<#{self.class} #{success? ? "success" : "failure"}>" + end + end + + protected + + # Invoked by the @waiter thread to indicate the outcome of the child thread. + def finished(error = nil) + if error + Console.error(self) {error} + end + + @status = Status.new(error) + self.close_write + end + end + # Start a named child thread and execute the provided block in it. # @parameter name [String] The name (title) of the child process. # @parameter block [Proc] The block to execute in the child process. def start(name, &block) - Thread.fork(name: name, &block) + Child.fork(name: name, &block) end end end diff --git a/test/async/container/forked.rb b/test/async/container/forked.rb index 4c93fb5..b1ad306 100644 --- a/test/async/container/forked.rb +++ b/test/async/container/forked.rb @@ -4,6 +4,7 @@ # Copyright, 2018-2024, by Samuel Williams. # Copyright, 2020, by Olle Jonsson. +require "async/container/best" require "async/container/forked" require "async/container/a_container" diff --git a/test/async/container/notify.rb b/test/async/container/notify.rb index a7fc476..9e1eb29 100644 --- a/test/async/container/notify.rb +++ b/test/async/container/notify.rb @@ -6,6 +6,8 @@ require "async/container/controller" require "async/container/notify/server" +require "async" + describe Async::Container::Notify do let(:server) {subject::Server.open} let(:notify_socket) {server.path} From 06cbb914f3d9289ecde99ad4cb574cefe5aae5a7 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Feb 2025 16:06:15 +1300 Subject: [PATCH 127/166] Add code coverage for `Async::Container::Channel`. --- test/async/container/channel.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/async/container/channel.rb diff --git a/test/async/container/channel.rb b/test/async/container/channel.rb new file mode 100644 index 0000000..9af3c4b --- /dev/null +++ b/test/async/container/channel.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +require "async/container/channel" + +describe Async::Container::Channel do + let(:channel) {subject.new} + + after do + @channel&.close + end + + it "can send and receive" do + channel.out.puts "Hello, World!" + + expect(channel.in.gets).to be == "Hello, World!\n" + end + + it "can send and receive JSON" do + channel.out.puts JSON.dump({hello: "world"}) + + expect(channel.receive).to be == {hello: "world"} + end + + it "can receive invalid JSON" do + channel.out.puts "Hello, World!" + + expect(channel.receive).to be == {line: "Hello, World!\n"} + end +end From 7c5da1bebc2c4aadefb36eea697cbe17e612cd41 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Feb 2025 16:34:21 +1300 Subject: [PATCH 128/166] More coverage for notify server / client. --- lib/async/container/notify/server.rb | 8 +++++- test/async/container/notify.rb | 43 +++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index 6da975d..be02ea3 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -25,6 +25,8 @@ def self.load(message) value = false elsif value == "1" value = true + elsif key == "errno" and value =~ /\A\-?\d+\z/ + value = Integer(value) end next [key.downcase.to_sym, value] @@ -74,7 +76,11 @@ def receive message = Server.load(data) - yield message + if block_given? + yield message + else + return message + end end end end diff --git a/test/async/container/notify.rb b/test/async/container/notify.rb index 9e1eb29..2885ead 100644 --- a/test/async/container/notify.rb +++ b/test/async/container/notify.rb @@ -13,6 +13,16 @@ let(:notify_socket) {server.path} let(:client) {subject::Socket.new(notify_socket)} + it "can send and receive messages" do + context = server.bind + + client.send(true: true, false: false, hello: "world") + + message = context.receive + + expect(message).to be == {true: true, false: false, hello: "world"} + end + with "#ready!" do it "should send message" do begin @@ -31,11 +41,42 @@ end end - expect(messages.last).to have_keys(ready: be == true) + expect(messages.last).to have_keys( + ready: be == true + ) ensure context&.close Process.wait(pid) if pid end end end + + with "#stopping!" do + it "sends stopping message" do + context = server.bind + + client.stopping! + + message = context.receive + + expect(message).to have_keys( + stopping: be == true + ) + end + end + + with "#error!" do + it "sends error message" do + context = server.bind + + client.error!("Boom!") + + message = context.receive + + expect(message).to have_keys( + status: be == "Boom!", + errno: be == -1, + ) + end + end end if Async::Container.fork? From 5d8dffb8ec169ad5ae0c20d357b10fc3063477f8 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Feb 2025 16:49:48 +1300 Subject: [PATCH 129/166] Fix test. --- lib/async/container/notify/server.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index be02ea3..b75e8be 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -21,15 +21,17 @@ def self.load(message) pairs = lines.map do |line| key, value = line.split("=", 2) + key = key.downcase.to_sym + if value == "0" value = false elsif value == "1" value = true - elsif key == "errno" and value =~ /\A\-?\d+\z/ + elsif key == :errno and value =~ /\A\-?\d+\z/ value = Integer(value) end - next [key.downcase.to_sym, value] + next [key, value] end return Hash[pairs] From dda76e8b2034ace85265c1f2ae4b0e66e2dde8b6 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Feb 2025 20:04:06 +1300 Subject: [PATCH 130/166] Move test controllers into fixtures directory. --- fixtures/async/container/controllers.rb | 11 +++++++++++ .../async/container/controllers/bad.rb | 2 +- .../async/container/controllers/dots.rb | 2 +- .../async/container/controllers/graceful.rb | 2 +- .../container/controllers/working_directory.rb | 2 +- test/async/container/controller.rb | 18 ++++++++++++++---- 6 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 fixtures/async/container/controllers.rb rename test/async/container/.bad.rb => fixtures/async/container/controllers/bad.rb (88%) rename test/async/container/.dots.rb => fixtures/async/container/controllers/dots.rb (90%) rename test/async/container/.graceful.rb => fixtures/async/container/controllers/graceful.rb (94%) rename test/async/container/.cwd.rb => fixtures/async/container/controllers/working_directory.rb (85%) diff --git a/fixtures/async/container/controllers.rb b/fixtures/async/container/controllers.rb new file mode 100644 index 0000000..57c73f8 --- /dev/null +++ b/fixtures/async/container/controllers.rb @@ -0,0 +1,11 @@ +module Async + module Container + module Controllers + ROOT = File.join(__dir__, "controllers") + + def self.path_for(controller) + File.join(ROOT, "#{controller}.rb") + end + end + end +end diff --git a/test/async/container/.bad.rb b/fixtures/async/container/controllers/bad.rb similarity index 88% rename from test/async/container/.bad.rb rename to fixtures/async/container/controllers/bad.rb index cf5aaa3..60f1673 100755 --- a/test/async/container/.bad.rb +++ b/fixtures/async/container/controllers/bad.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2024, by Samuel Williams. -require_relative "../../../lib/async/container/controller" +require_relative "../../../../lib/async/container/controller" $stdout.sync = true diff --git a/test/async/container/.dots.rb b/fixtures/async/container/controllers/dots.rb similarity index 90% rename from test/async/container/.dots.rb rename to fixtures/async/container/controllers/dots.rb index 551b4fc..bc01c6d 100755 --- a/test/async/container/.dots.rb +++ b/fixtures/async/container/controllers/dots.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2020-2025, by Samuel Williams. -require_relative "../../../lib/async/container/controller" +require_relative "../../../../lib/async/container/controller" $stdout.sync = true diff --git a/test/async/container/.graceful.rb b/fixtures/async/container/controllers/graceful.rb similarity index 94% rename from test/async/container/.graceful.rb rename to fixtures/async/container/controllers/graceful.rb index af2d473..cc1ea9e 100755 --- a/test/async/container/.graceful.rb +++ b/fixtures/async/container/controllers/graceful.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2024-2025, by Samuel Williams. -require_relative "../../../lib/async/container/controller" +require_relative "../../../../lib/async/container/controller" $stdout.sync = true diff --git a/test/async/container/.cwd.rb b/fixtures/async/container/controllers/working_directory.rb similarity index 85% rename from test/async/container/.cwd.rb rename to fixtures/async/container/controllers/working_directory.rb index 4899bc3..72fcb43 100755 --- a/test/async/container/.cwd.rb +++ b/fixtures/async/container/controllers/working_directory.rb @@ -4,7 +4,7 @@ # Released under the MIT License. # Copyright, 2024, by Samuel Williams. -require_relative "../../../lib/async/container/controller" +require_relative "../../../../lib/async/container/controller" $stdout.sync = true diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index ee3a9b0..2f59a37 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -4,10 +4,17 @@ # Copyright, 2018-2025, by Samuel Williams. require "async/container/controller" +require "async/container/controllers" describe Async::Container::Controller do let(:controller) {subject.new} + with "#to_s" do + it "can generate string representation" do + expect(controller.to_s).to be == "Async::Container::Controller stopped" + end + end + with "#reload" do it "can reuse keyed child" do input, output = IO.pipe @@ -37,6 +44,9 @@ def controller.setup(container) end controller.start + + expect(controller.state_string).to be == "running" + expect(input.read(2)).to be == ".," controller.reload @@ -89,7 +99,7 @@ def controller.setup(container) end with "graceful controller" do - let(:controller_path) {File.expand_path(".graceful.rb", __dir__)} + let(:controller_path) {Async::Container::Controllers.path_for("graceful")} let(:pipe) {IO.pipe} let(:input) {pipe.first} @@ -128,7 +138,7 @@ def after(error = nil) end with "bad controller" do - let(:controller_path) {File.expand_path(".bad.rb", __dir__)} + let(:controller_path) {Async::Container::Controllers.path_for("bad")} let(:pipe) {IO.pipe} let(:input) {pipe.first} @@ -160,7 +170,7 @@ def after(error = nil) end with "signals" do - let(:controller_path) {File.expand_path(".dots.rb", __dir__)} + let(:controller_path) {Async::Container::Controllers.path_for("dots")} let(:pipe) {IO.pipe} let(:input) {pipe.first} @@ -208,7 +218,7 @@ def after(error = nil) end with "working directory" do - let(:controller_path) {File.expand_path(".cwd.rb", __dir__)} + let(:controller_path) {Async::Container::Controllers.path_for("working_directory")} it "can change working directory" do pipe = IO.pipe From 1e7b6a8d057f46b29d238d262320646e0fb89d67 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Feb 2025 20:04:25 +1300 Subject: [PATCH 131/166] Add test for `.best` returning threaded container. --- test/async/container.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/async/container.rb b/test/async/container.rb index f3c7dfa..bdefa8c 100644 --- a/test/async/container.rb +++ b/test/async/container.rb @@ -26,10 +26,6 @@ end end - it "can get best container class" do - expect(Async::Container.best_container_class).not.to be_nil - end - with ".new" do let(:container) {Async::Container.new} @@ -38,4 +34,16 @@ container.stop end end + + with ".best" do + it "can get the best container class" do + expect(Async::Container.best_container_class).not.to be_nil + end + + it "can get the best container class if fork is not available" do + expect(subject).to receive(:fork?).and_return(false) + + expect(Async::Container.best_container_class).to be == Async::Container::Threaded + end + end end From 3042fa809fd89ba24bf53948b5d89c6bd31e695a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Feb 2025 20:07:15 +1300 Subject: [PATCH 132/166] Add coverage for `Async::Container::Statistics`. --- test/async/container/statistics.rb | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 test/async/container/statistics.rb diff --git a/test/async/container/statistics.rb b/test/async/container/statistics.rb new file mode 100644 index 0000000..ccf54ef --- /dev/null +++ b/test/async/container/statistics.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +require "async/container/statistics" + +describe Async::Container::Statistics do + let(:statistics) {subject.new} + + with "#spawn!" do + it "can count spawns" do + expect(statistics.spawns).to be == 0 + + statistics.spawn! + + expect(statistics.spawns).to be == 1 + end + end + + with "#restart!" do + it "can count restarts" do + expect(statistics.restarts).to be == 0 + + statistics.restart! + + expect(statistics.restarts).to be == 1 + end + end + + with "#failure!" do + it "can count failures" do + expect(statistics.failures).to be == 0 + + statistics.failure! + + expect(statistics.failures).to be == 1 + end + end + + with "#failed?" do + it "can check for failures" do + expect(statistics).not.to be(:failed?) + + statistics.failure! + + expect(statistics).to be(:failed?) + end + end + + with "#<<" do + it "can append statistics" do + other = subject.new + + other.spawn! + other.restart! + other.failure! + + statistics << other + + expect(statistics.spawns).to be == 1 + expect(statistics.restarts).to be == 1 + expect(statistics.failures).to be == 1 + end + end +end \ No newline at end of file From 7e1e4a8b4688ea82810a4c2c20dd5d806d5dbc42 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Feb 2025 22:12:40 +1300 Subject: [PATCH 133/166] More code coverage for notify. --- .../async/container/controllers/notify.rb | 0 lib/async/container/notify/socket.rb | 6 +++++- test/async/container/notify.rb | 20 +++++++++++++++++++ test/async/container/notify/pipe.rb | 3 ++- test/async/container/notify/socket.rb | 17 ++++++++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) rename test/async/container/notify/.notify.rb => fixtures/async/container/controllers/notify.rb (100%) create mode 100644 test/async/container/notify/socket.rb diff --git a/test/async/container/notify/.notify.rb b/fixtures/async/container/controllers/notify.rb similarity index 100% rename from test/async/container/notify/.notify.rb rename to fixtures/async/container/controllers/notify.rb diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index f92473e..c56bec6 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -4,6 +4,7 @@ # Copyright, 2020-2024, by Samuel Williams. require_relative "client" +require "socket" module Async module Container @@ -31,6 +32,9 @@ def initialize(path) @address = Addrinfo.unix(path, ::Socket::SOCK_DGRAM) end + # @attribute [String] The path to the UNIX socket used for sending messages to the controller. + attr :path + # Dump a message in the format requied by `sd_notify`. # @parameter message [Hash] Keys and values should be string convertible objects. Values which are `true`/`false` are converted to `1`/`0` respectively. def dump(message) @@ -69,7 +73,7 @@ def send(**message) def error!(text, **message) message[:errno] ||= -1 - send(status: text, **message) + super end end end diff --git a/test/async/container/notify.rb b/test/async/container/notify.rb index 2885ead..68d0a63 100644 --- a/test/async/container/notify.rb +++ b/test/async/container/notify.rb @@ -51,6 +51,26 @@ end end + with "#send" do + it "sends message" do + context = server.bind + + client.send(hello: "world") + + message = context.receive + + expect(message).to be == {hello: "world"} + end + + it "fails if the message is too big" do + context = server.bind + + expect do + client.send("x" * (subject::Socket::MAXIMUM_MESSAGE_SIZE+1)) + end.to raise_exception(ArgumentError) + end + end + with "#stopping!" do it "sends stopping message" do context = server.bind diff --git a/test/async/container/notify/pipe.rb b/test/async/container/notify/pipe.rb index 8bd9072..87e9047 100644 --- a/test/async/container/notify/pipe.rb +++ b/test/async/container/notify/pipe.rb @@ -5,9 +5,10 @@ # Copyright, 2020, by Olle Jonsson. require "async/container/controller" +require "async/container/controllers" describe Async::Container::Notify::Pipe do - let(:notify_script) {File.expand_path(".notify.rb", __dir__)} + let(:notify_script) {Async::Container::Controllers.path_for("notify")} it "receives notification of child status" do container = Async::Container.new diff --git a/test/async/container/notify/socket.rb b/test/async/container/notify/socket.rb new file mode 100644 index 0000000..dc54168 --- /dev/null +++ b/test/async/container/notify/socket.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2025, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. + +require "async/container/notify/socket" + +describe Async::Container::Notify::Socket do + with ".open!" do + it "can open a socket" do + socket = subject.open!({subject::NOTIFY_SOCKET => "test"}) + + expect(socket).to have_attributes(path: be == "test") + end + end +end From 453292b21f1880f5d577dab30aeda12e3d7a2887 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Feb 2025 22:27:40 +1300 Subject: [PATCH 134/166] Fix broken size test. --- lib/async/container/notify/socket.rb | 2 +- test/async/container/notify.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/async/container/notify/socket.rb b/lib/async/container/notify/socket.rb index c56bec6..597f342 100644 --- a/lib/async/container/notify/socket.rb +++ b/lib/async/container/notify/socket.rb @@ -60,7 +60,7 @@ def send(**message) data = dump(message) if data.bytesize > MAXIMUM_MESSAGE_SIZE - raise ArgumentError, "Message length #{message.bytesize} exceeds #{MAXIMUM_MESSAGE_SIZE}: #{message.inspect}" + raise ArgumentError, "Message length #{data.bytesize} exceeds #{MAXIMUM_MESSAGE_SIZE}: #{message.inspect}" end @address.connect do |peer| diff --git a/test/async/container/notify.rb b/test/async/container/notify.rb index 68d0a63..5323726 100644 --- a/test/async/container/notify.rb +++ b/test/async/container/notify.rb @@ -66,8 +66,8 @@ context = server.bind expect do - client.send("x" * (subject::Socket::MAXIMUM_MESSAGE_SIZE+1)) - end.to raise_exception(ArgumentError) + client.send(test: "x" * (subject::Socket::MAXIMUM_MESSAGE_SIZE+1)) + end.to raise_exception(ArgumentError, message: be =~ /Message length \d+ exceeds \d+/) end end From aff73bfb8aec11409e69296ba321c1694527a897 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 5 Feb 2025 00:26:53 +1300 Subject: [PATCH 135/166] More container tests. --- examples/isolate.rb | 38 -------- fixtures/async/container/a_container.rb | 113 ++++++++++++++++++++---- 2 files changed, 94 insertions(+), 57 deletions(-) delete mode 100644 examples/isolate.rb diff --git a/examples/isolate.rb b/examples/isolate.rb deleted file mode 100644 index 7714224..0000000 --- a/examples/isolate.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. - -class Terminate < Interrupt -end - -class Isolate - def initialize(&block) - - end -end - - -parent = Isolate.new do |parent| - preload_user_code - server = bind_socket - children = 4.times.map do - Isolate.new do |worker| - app = load_user_application - worker.ready! - server.accept do |peer| - app.handle_request(peer) - end - end - end - while status = parent.wait - # Status is not just exit status of process but also can be `:ready` or something else. - end -end - -# Similar to Process.wait(pid) -status = parent.wait -# Life cycle controls -parent.interrupt! -parent.terminate! -parent.kill! diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb index 3597525..5e98148 100644 --- a/fixtures/async/container/a_container.rb +++ b/fixtures/async/container/a_container.rb @@ -10,29 +10,66 @@ module Container AContainer = Sus::Shared("a container") do let(:container) {subject.new} - it "can run concurrently" do - input, output = IO.pipe - - container.async do - output.write "Hello World" + with "#run" do + it "can run several instances concurrently" do + container.run do + sleep(1) + end + + expect(container).to be(:running?) + + container.stop(true) + + expect(container).not.to be(:running?) end - container.wait - - output.close - expect(input.read).to be == "Hello World" + it "can stop an uncooperative child process" do + container.run do + while true + begin + sleep(1) + rescue Interrupt + # Ignore. + end + end + end + + expect(container).to be(:running?) + + # TODO Investigate why without this, the interrupt can occur before the process is sleeping... + sleep 0.001 + + container.stop(true) + + expect(container).not.to be(:running?) + end end - it "can run concurrently" do - container.async(name: "Sleepy Jerry") do |task, instance| - 3.times do |i| - instance.name = "Counting Sheep #{i}" - - sleep 0.01 + with "#async" do + it "can run concurrently" do + input, output = IO.pipe + + container.async do + output.write "Hello World" end + + container.wait + + output.close + expect(input.read).to be == "Hello World" end - container.wait + it "can run concurrently" do + container.async(name: "Sleepy Jerry") do |task, instance| + 3.times do |i| + instance.name = "Counting Sheep #{i}" + + sleep 0.01 + end + end + + container.wait + end end it "should be blocking" do @@ -66,7 +103,7 @@ module Container end with "#stop" do - it "can stop the child process" do + it "can gracefully stop the child process" do container.spawn do sleep(1) rescue Interrupt @@ -75,10 +112,48 @@ module Container expect(container).to be(:running?) - # TODO Investigate why without this, the interrupt can occur before the process is sleeping... + # See above. sleep 0.001 - container.stop + container.stop(true) + + expect(container).not.to be(:running?) + end + + it "can forcefully stop the child process" do + container.spawn do + sleep(1) + rescue Interrupt + # Ignore. + end + + expect(container).to be(:running?) + + # See above. + sleep 0.001 + + container.stop(false) + + expect(container).not.to be(:running?) + end + + it "can stop an uncooperative child process" do + container.spawn do + while true + begin + sleep(1) + rescue Interrupt + # Ignore. + end + end + end + + expect(container).to be(:running?) + + # See above. + sleep 0.001 + + container.stop(true) expect(container).not.to be(:running?) end From 6d4118ce3d1b2d95dafb4437280e52fab937e691 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 5 Feb 2025 00:27:48 +1300 Subject: [PATCH 136/166] RuboCop. --- fixtures/async/container/controllers.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/fixtures/async/container/controllers.rb b/fixtures/async/container/controllers.rb index 57c73f8..170ff66 100644 --- a/fixtures/async/container/controllers.rb +++ b/fixtures/async/container/controllers.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true module Async module Container module Controllers From 1eb3aa435022980ed768aadedbbb389233ac054d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 5 Feb 2025 00:29:49 +1300 Subject: [PATCH 137/166] Add `async-service` to external tests. --- config/external.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/external.yaml b/config/external.yaml index e4771e9..ddb05f5 100644 --- a/config/external.yaml +++ b/config/external.yaml @@ -1,3 +1,6 @@ falcon: url: https://github.com/socketry/falcon.git command: bundle exec bake test +async-service: + url: https://github.com/socketry/async-service.git + command: bundle exec bake test From 16daea1b73b923e2186eb986b41066fbe17bd01a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 5 Feb 2025 00:40:33 +1300 Subject: [PATCH 138/166] Bump minor version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 3078820..86810a7 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.18.3" + VERSION = "0.19.0" end end From 21099b50371a2b1e1b7aab038e4ab813836fd680 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 7 Feb 2025 23:58:45 +1300 Subject: [PATCH 139/166] Fix signal handling in child threads. --- lib/async/container/threaded.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index a6b78c1..e44266a 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -90,7 +90,10 @@ def exec(*arguments, ready: true, **options) def self.fork(**options) self.new(**options) do |thread| ::Thread.new do - yield Instance.for(thread) + # This could be a configuration option (see forked implementation too): + ::Thread.handle_interrupt(SignalException => :immediate) do + yield Instance.for(thread) + end end end end From 91b2e51a7d3ef04d23034ee351a348448d4104ce Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 Feb 2025 00:15:38 +1300 Subject: [PATCH 140/166] Add `health_check_timeout` for detecting hung processes. --- async-container.gemspec | 2 +- examples/health_check/test.rb | 39 ++++++++++++++++++++++++ examples/test.rb | 54 --------------------------------- lib/async/container/forked.rb | 18 +++++++++++ lib/async/container/generic.rb | 21 +++++++++++-- lib/async/container/group.rb | 43 ++++++++++++++++++++++++-- lib/async/container/threaded.rb | 25 +++++++++++++++ 7 files changed, 142 insertions(+), 60 deletions(-) create mode 100755 examples/health_check/test.rb delete mode 100644 examples/test.rb diff --git a/async-container.gemspec b/async-container.gemspec index 19ab071..06368b8 100644 --- a/async-container.gemspec +++ b/async-container.gemspec @@ -24,5 +24,5 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 3.1" - spec.add_dependency "async", "~> 2.10" + spec.add_dependency "async", "~> 2.22" end diff --git a/examples/health_check/test.rb b/examples/health_check/test.rb new file mode 100755 index 0000000..9e92baa --- /dev/null +++ b/examples/health_check/test.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2022, by Anton Sozontov. +# Copyright, 2024, by Samuel Williams. + +require "../../lib/async/container/controller" + +NAMES = [ + "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb", "Ice Cream Sandwich", "Jelly Bean", "KitKat", "Lollipop", "Marshmallow", "Nougat", "Oreo", "Pie", "Apple Tart" +] + +class Controller < Async::Container::Controller + def setup(container) + container.run(count: 10, restart: true, health_check_timeout: 1) do |instance| + if container.statistics.failed? + Console.debug(self, "Child process restarted #{container.statistics.restarts} times.") + else + Console.debug(self, "Child process started.") + end + + instance.name = NAMES.sample + + instance.ready! + + while true + # Must update status more frequently than health check timeout... + sleep(rand*1.2) + + instance.ready! + end + end + end +end + +controller = Controller.new # (container_class: Async::Container::Threaded) + +controller.run diff --git a/examples/test.rb b/examples/test.rb deleted file mode 100644 index 824eed0..0000000 --- a/examples/test.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2024, by Samuel Williams. - -require_relative "group" -require_relative "thread" -require_relative "process" - -group = Async::Container::Group.new - -thread_monitor = Fiber.new do - while true - thread = Async::Container::Thread.fork do |instance| - if rand < 0.2 - raise "Random Failure!" - end - - instance.send(ready: true, status: "Started Thread") - - sleep(1) - end - - status = group.wait_for(thread) do |message| - puts "Thread message: #{message}" - end - - puts "Thread status: #{status}" - end -end.resume - -process_monitor = Fiber.new do - while true - # process = Async::Container::Process.fork do |instance| - # if rand < 0.2 - # raise "Random Failure!" - # end - # - # instance.send(ready: true, status: "Started Process") - # - # sleep(1) - # end - - process = Async::Container::Process.spawn('bash -c "sleep 1; echo foobar; sleep 1; exit -1"') - - status = group.wait_for(process) do |message| - puts "Process message: #{message}" - end - - puts "Process status: #{status}" - end -end.resume - -group.wait diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index a2718f3..5b85d2c 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -116,6 +116,24 @@ def initialize(name: nil) self.close_write end + # Convert the child process to a hash, suitable for serialization. + # + # @returns [Hash] The request as a hash. + def as_json(...) + { + name: @name, + pid: @pid, + status: @status&.to_i, + } + end + + # Convert the request to JSON. + # + # @returns [String] The request as JSON. + def to_json(...) + as_json.to_json(...) + end + # Set the name of the process. # Invokes {::Process.setproctitle} if invoked in the child process. def name= value diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index d994374..180ca7d 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -4,6 +4,7 @@ # Copyright, 2019-2024, by Samuel Williams. require "etc" +require "async/clock" require_relative "group" require_relative "keyed" @@ -141,7 +142,8 @@ def stop(timeout = true) # @parameter name [String] The name of the child instance. # @parameter restart [Boolean] Whether to restart the child instance if it fails. # @parameter key [Symbol] A key used for reloading child instances. - def spawn(name: nil, restart: false, key: nil, &block) + # @parameter health_check_timeout [Numeric | Nil] The maximum time a child instance can run without updating its state, before it is terminated as unhealthy. + def spawn(name: nil, restart: false, key: nil, health_check_timeout: nil, &block) name ||= UNNAMED if mark?(key) @@ -157,9 +159,24 @@ def spawn(name: nil, restart: false, key: nil, &block) state = insert(key, child) + # If a health check is specified, we will monitor the child process and terminate it if it does not update its state within the specified time. + if health_check_timeout + age_clock = state[:age] = Clock.start + end + begin status = @group.wait_for(child) do |message| - state.update(message) + case message + when :health_check! + if health_check_timeout&.<(age_clock.total) + Console.warn(self, "Child failed health check!", child: child, age: age_clock.total, health_check_timeout: health_check_timeout) + # If the child has failed the health check, we assume the worst and terminate it (SIGTERM). + child.terminate! + end + else + state.update(message) + age_clock&.reset! + end end ensure delete(key, child) diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 17271c5..9d8602d 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -13,7 +13,12 @@ module Container # Manages a group of running processes. class Group # Initialize an empty group. - def initialize + # + # @parameter health_check_interval [Numeric | Nil] The (biggest) interval at which health checks are performed. + def initialize(health_check_interval: 1.0) + @health_check_interval = health_check_interval + + # The running fibers, indexed by IO: @running = {} # This queue allows us to wait for processes to complete, without spawning new processes as a result. @@ -57,8 +62,36 @@ def sleep(duration) def wait self.resume - while self.running? - self.wait_for_children + with_health_checks do |duration| + self.wait_for_children(duration) + end + end + + private def with_health_checks + if @health_check_interval + health_check_clock = Clock.start + + while self.running? + duration = [@health_check_interval - health_check_clock.total, 0].max + + yield duration + + if health_check_clock.total > @health_check_interval + self.health_check! + health_check_clock.reset! + end + end + else + while self.running? + yield nil + end + end + end + + # Perform a health check on all running processes. + def health_check! + @running.each_value do |fiber| + fiber.resume(:health_check!) end end @@ -119,15 +152,19 @@ def wait_for(channel) @running[io] = Fiber.current while @running.key?(io) + # Wait for some event on the channel: result = Fiber.yield if result == Interrupt channel.interrupt! elsif result == Terminate channel.terminate! + elsif result + yield result elsif message = channel.receive yield message else + # Wait for the channel to exit: return channel.wait end end diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index e44266a..ba5d72d 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -125,6 +125,23 @@ def initialize(name: nil) end end + # Convert the child process to a hash, suitable for serialization. + # + # @returns [Hash] The request as a hash. + def as_json(...) + { + name: @thread.name, + status: @status&.as_json, + } + end + + # Convert the request to JSON. + # + # @returns [String] The request as JSON. + def to_json(...) + as_json.to_json(...) + end + # Set the name of the thread. # @parameter value [String] The name to set. def name= value @@ -191,6 +208,14 @@ def success? @error.nil? end + def as_json(...) + if @error + @error.inspect + else + true + end + end + # A human readable representation of the status. def to_s "\#<#{self.class} #{success? ? "success" : "failure"}>" From 1ca1dccffde232ed79c965f3992c817468c247e7 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 8 Feb 2025 00:19:26 +1300 Subject: [PATCH 141/166] Bump minor version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 86810a7..ae2261e 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.19.0" + VERSION = "0.20.0" end end From 2f9b70685f67dc7171eba1672ad46715adcaeb32 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 11:50:38 +1300 Subject: [PATCH 142/166] Pass through options to `Group`. --- lib/async/container/generic.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 180ca7d..0e57d70 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -39,7 +39,7 @@ def self.run(*arguments, **options, &block) UNNAMED = "Unnamed" def initialize(**options) - @group = Group.new + @group = Group.new(**options) @running = true @state = {} @@ -48,8 +48,10 @@ def initialize(**options) @keyed = {} end + # @attribute [Group] The group of running children instances. attr :group + # @attribute [Hash(Child, Hash)] The state of each child instance. attr :state # A human readable representation of the container. From 9f316b982c14dfb4ae5941d28b9c72492a8b0bba Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 11:49:50 +1300 Subject: [PATCH 143/166] Fix Hybrid health check handling. --- fixtures/async/container/a_container.rb | 33 +++++++++++++++++++++++++ lib/async/container/hybrid.rb | 6 +++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb index 5e98148..4ff911c 100644 --- a/fixtures/async/container/a_container.rb +++ b/fixtures/async/container/a_container.rb @@ -176,6 +176,39 @@ module Container expect(container).not.to be(:running?) end end + + with "health_check_timeout:" do + let(:container) {subject.new(health_check_interval: 0.01)} + + it "should not terminate a child process if it updates its state within the specified time" do + # We use #run here to hit the Hybrid container code path: + container.run(count: 1, health_check_timeout: 0.02) do |instance| + instance.ready! + + 10.times do + instance.ready! + sleep(0.01) + end + end + + container.wait + + expect(container.statistics).to have_attributes(failures: be == 0) + end + + it "can terminate a child process if it does not update its state within the specified time" do + container.spawn(health_check_timeout: 0.01) do |instance| + instance.ready! + + # This should trigger the health check - since restart is false, the process will be terminated: + sleep(1) + end + + container.wait + + expect(container.statistics).to have_attributes(failures: be > 0) + end + end end end end diff --git a/lib/async/container/hybrid.rb b/lib/async/container/hybrid.rb index ea42f60..537eaf4 100644 --- a/lib/async/container/hybrid.rb +++ b/lib/async/container/hybrid.rb @@ -15,7 +15,8 @@ class Hybrid < Forked # @parameter count [Integer] The number of instances to start. # @parameter forks [Integer] The number of processes to fork. # @parameter threads [Integer] the number of threads to start. - def run(count: nil, forks: nil, threads: nil, **options, &block) + # @parameter health_check_timeout [Numeric] The timeout for health checks, in seconds. Passed into the child {Threaded} containers. + def run(count: nil, forks: nil, threads: nil, health_check_timeout: nil, **options, &block) processor_count = Container.processor_count count ||= processor_count ** 2 forks ||= [processor_count, count].min @@ -25,7 +26,7 @@ def run(count: nil, forks: nil, threads: nil, **options, &block) self.spawn(**options) do |instance| container = Threaded.new - container.run(count: threads, **options, &block) + container.run(count: threads, health_check_timeout: health_check_timeout, **options, &block) container.wait_until_ready instance.ready! @@ -34,6 +35,7 @@ def run(count: nil, forks: nil, threads: nil, **options, &block) rescue Async::Container::Terminate # Stop it immediately: container.stop(false) + raise ensure # Stop it gracefully (also code path for Interrupt): container.stop From 316bc9e6ed08110dd6e500af2e5e0870334e8f54 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 12:31:57 +1300 Subject: [PATCH 144/166] Relax timing. --- fixtures/async/container/a_container.rb | 10 +++++----- test/async/container/controller.rb | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb index 4ff911c..d28a339 100644 --- a/fixtures/async/container/a_container.rb +++ b/fixtures/async/container/a_container.rb @@ -178,16 +178,16 @@ module Container end with "health_check_timeout:" do - let(:container) {subject.new(health_check_interval: 0.01)} + let(:container) {subject.new(health_check_interval: 1.0)} it "should not terminate a child process if it updates its state within the specified time" do # We use #run here to hit the Hybrid container code path: - container.run(count: 1, health_check_timeout: 0.02) do |instance| + container.run(count: 1, health_check_timeout: 1.0) do |instance| instance.ready! 10.times do instance.ready! - sleep(0.01) + sleep(0.5) end end @@ -197,11 +197,11 @@ module Container end it "can terminate a child process if it does not update its state within the specified time" do - container.spawn(health_check_timeout: 0.01) do |instance| + container.spawn(health_check_timeout: 1.0) do |instance| instance.ready! # This should trigger the health check - since restart is false, the process will be terminated: - sleep(1) + sleep(2.0) end container.wait diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index 2f59a37..c5a0353 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -25,18 +25,18 @@ def controller.setup(container) container.spawn(key: "test") do |instance| instance.ready! - sleep(0.02) + sleep(0.2) @output.write(".") @output.flush - sleep(0.04) + sleep(0.4) end container.spawn do |instance| instance.ready! - sleep(0.03) + sleep(0.3) @output.write(",") @output.flush From 8165303865142a014be47d2da2b453603d10713d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 13:15:20 +1300 Subject: [PATCH 145/166] Remove gems.locked from example. --- examples/puma/gems.locked | 41 --------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 examples/puma/gems.locked diff --git a/examples/puma/gems.locked b/examples/puma/gems.locked deleted file mode 100644 index 73eedae..0000000 --- a/examples/puma/gems.locked +++ /dev/null @@ -1,41 +0,0 @@ -PATH - remote: ../.. - specs: - async-container (0.18.3) - async (~> 2.10) - -GEM - remote: https://rubygems.org/ - specs: - async (2.21.1) - console (~> 1.29) - fiber-annotation - io-event (~> 1.6, >= 1.6.5) - console (1.29.2) - fiber-annotation - fiber-local (~> 1.1) - json - fiber-annotation (0.2.0) - fiber-local (1.1.0) - fiber-storage - fiber-storage (1.0.0) - io-endpoint (0.14.0) - io-event (1.7.5) - json (2.9.1) - nio4r (2.7.4) - puma (6.5.0) - nio4r (~> 2.0) - rack (3.1.8) - -PLATFORMS - ruby - x86_64-linux - -DEPENDENCIES - async-container! - io-endpoint - puma - rack (~> 3) - -BUNDLED WITH - 2.5.22 From fd32d6fc6910d469ac3367f46c037ea2e617e474 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 13:31:33 +1300 Subject: [PATCH 146/166] Add release notes. --- bake.rb | 12 ++++++++++++ gems.rb | 1 + readme.md | 2 ++ releases.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 bake.rb diff --git a/bake.rb b/bake.rb new file mode 100644 index 0000000..d3bc6c1 --- /dev/null +++ b/bake.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +# Update the project documentation with the new version number. +# +# @parameter version [String] The new version number. +def after_gem_release_version_increment(version) + context["releases:update"].call(version) + context["utopia:project:readme:update"].call +end diff --git a/gems.rb b/gems.rb index c018bcc..725e802 100644 --- a/gems.rb +++ b/gems.rb @@ -10,6 +10,7 @@ group :maintenance, optional: true do gem "bake-gem" gem "bake-modernize" + gem "bake-releases" gem "utopia-project" end diff --git a/readme.md b/readme.md index 98ca4df..89c4d41 100644 --- a/readme.md +++ b/readme.md @@ -16,6 +16,8 @@ Provides containers which implement parallelism for clients and servers. Please see the [project documentation](https://socketry.github.io/async-container/). +## Releases + ## Contributing We welcome contributions to this project. diff --git a/releases.md b/releases.md index 622e307..a035d8a 100644 --- a/releases.md +++ b/releases.md @@ -2,5 +2,47 @@ ## Unreleased + - Fix compatibility between {ruby Async::Container::Hybrid} and the health check. + - {ruby Async::Container::Generic#initialize} passes unused arguments through to {ruby Async::Container::Group}. + +## v0.20.0 + + - Improve container signal handling reliability by using `Thread.handle_interrupt` except at known safe points. - Improved logging when child process fails and container startup. + +### Add `health_check_timeout` for detecting hung processes. + +In order to detect hung processes, a `health_check_timeout` can be specified when spawning children workers. If the health check does not complete within the specified timeout, the child process is killed. + +```ruby +require "async/container" + +container = Async::Container.new + +container.run(count: 1, restart: true, health_check_timeout: 1) do |instance| + while true + # This example will fail sometimes: + sleep(0.5 + rand) + instance.ready! + end +end + +container.wait +``` + +If the health check does not complete within the specified timeout, the child process is killed: + +``` + 3.01s warn: Async::Container::Forked [oid=0x1340] [ec=0x1348] [pid=27100] [2025-02-20 13:24:55 +1300] + | Child failed health check! + | { + | "child": { + | "name": "Unnamed", + | "pid": 27101, + | "status": null + | }, + | "age": 1.0612829999881797, + | "health_check_timeout": 1 + | } +``` From d9f8c33bfd3a6b89060f82b4723a261bbc5d384a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 13:44:38 +1300 Subject: [PATCH 147/166] Bump patch version. --- lib/async/container/version.rb | 2 +- readme.md | 17 ++++++++++++++++- releases.md | 9 ++++----- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index ae2261e..3e62776 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.20.0" + VERSION = "0.20.1" end end diff --git a/readme.md b/readme.md index 89c4d41..285fcc7 100644 --- a/readme.md +++ b/readme.md @@ -14,10 +14,25 @@ Provides containers which implement parallelism for clients and servers. ## Usage -Please see the [project documentation](https://socketry.github.io/async-container/). +Please see the [project documentation](https://socketry.github.io/async-container/) for more details. + + - [Getting Started](https://socketry.github.io/async-container/guides/getting-started/index) - This guide explains how to use `async-container` to build basic scalable systems. ## Releases +Please see the [project releases](https://socketry.github.io/async-container/releases/index) for all releases. + +### v0.20.1 + + - Fix compatibility between Async::Container::Hybrid and the health check. + - Async::Container::Generic\#initialize passes unused arguments through to Async::Container::Group. + +### v0.20.0 + + - Improve container signal handling reliability by using `Thread.handle_interrupt` except at known safe points. + - Improved logging when child process fails and container startup. + - [Add `health_check_timeout` for detecting hung processes.](https://socketry.github.io/async-container/releases/index#add-health_check_timeout-for-detecting-hung-processes.) + ## Contributing We welcome contributions to this project. diff --git a/releases.md b/releases.md index a035d8a..12814f0 100644 --- a/releases.md +++ b/releases.md @@ -1,13 +1,12 @@ # Releases -## Unreleased +## v0.20.1 - Fix compatibility between {ruby Async::Container::Hybrid} and the health check. - - {ruby Async::Container::Generic#initialize} passes unused arguments through to {ruby Async::Container::Group}. + - {ruby Async::Container::Generic\#initialize} passes unused arguments through to {ruby Async::Container::Group}. ## v0.20.0 - - Improve container signal handling reliability by using `Thread.handle_interrupt` except at known safe points. - Improved logging when child process fails and container startup. @@ -15,7 +14,7 @@ In order to detect hung processes, a `health_check_timeout` can be specified when spawning children workers. If the health check does not complete within the specified timeout, the child process is killed. -```ruby +``` ruby require "async/container" container = Async::Container.new @@ -33,7 +32,7 @@ container.wait If the health check does not complete within the specified timeout, the child process is killed: -``` +``` 3.01s warn: Async::Container::Forked [oid=0x1340] [ec=0x1348] [pid=27100] [2025-02-20 13:24:55 +1300] | Child failed health check! | { From c67867ff8053a926ed2cc3b816940cb436cf558c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 14:24:36 +1300 Subject: [PATCH 148/166] Use `SIGKILL`/`Thread#kill` when the health check fails. --- fixtures/async/container/a_container.rb | 18 +++++++++++++++++- lib/async/container/forked.rb | 7 +++++++ lib/async/container/generic.rb | 4 ++-- lib/async/container/threaded.rb | 13 ++++++++++++- releases.md | 4 ++++ 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb index d28a339..21891e8 100644 --- a/fixtures/async/container/a_container.rb +++ b/fixtures/async/container/a_container.rb @@ -201,7 +201,23 @@ module Container instance.ready! # This should trigger the health check - since restart is false, the process will be terminated: - sleep(2.0) + sleep + end + + container.wait + + expect(container.statistics).to have_attributes(failures: be > 0) + end + + it "can kill a child process even if it ignores exceptions/signals" do + container.spawn(health_check_timeout: 1.0) do |instance| + while true + begin + sleep 1 + rescue Exception => error + # Ignore. + end + end end container.wait diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 5b85d2c..6f5187f 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -180,6 +180,13 @@ def terminate! end end + # Send `SIGKILL` to the child process. + def kill! + unless @status + ::Process.kill(:KILL, @pid) + end + end + # Send `SIGHUP` to the child process. def restart! unless @status diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 0e57d70..a430352 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -172,8 +172,8 @@ def spawn(name: nil, restart: false, key: nil, health_check_timeout: nil, &block when :health_check! if health_check_timeout&.<(age_clock.total) Console.warn(self, "Child failed health check!", child: child, age: age_clock.total, health_check_timeout: health_check_timeout) - # If the child has failed the health check, we assume the worst and terminate it (SIGTERM). - child.terminate! + # If the child has failed the health check, we assume the worst and kill it immediately: + child.kill! end else state.update(message) diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index ba5d72d..3bb1bdf 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -11,6 +11,9 @@ module Async module Container # A multi-thread container which uses {Thread.fork}. class Threaded < Generic + class Kill < Exception + end + # Indicates that this is not a multi-process container. def self.multiprocess? false @@ -178,6 +181,14 @@ def terminate! @thread.raise(Terminate) end + # Invoke {Thread#kill} on the child thread. + def kill! + # Killing a thread does not raise an exception in the thread, so we need to handle the status here: + @status = Status.new(:killed) + + @thread.kill + end + # Raise {Restart} in the child thread. def restart! @thread.raise(Restart) @@ -230,7 +241,7 @@ def finished(error = nil) Console.error(self) {error} end - @status = Status.new(error) + @status ||= Status.new(error) self.close_write end end diff --git a/releases.md b/releases.md index 12814f0..f93f2f9 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Use `SIGKILL`/`Thread#kill` when the health check fails. In some cases, `SIGTERM` may not be sufficient to terminate a process because the signal can be ignored or the process may be in an uninterruptible state. + ## v0.20.1 - Fix compatibility between {ruby Async::Container::Hybrid} and the health check. From b531a92136a36f98c15e99bcca98425d2c285e09 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 14:56:50 +1300 Subject: [PATCH 149/166] Improve reliability of keyed child test. --- test/async/container/controller.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/async/container/controller.rb b/test/async/container/controller.rb index c5a0353..f57aa8f 100644 --- a/test/async/container/controller.rb +++ b/test/async/container/controller.rb @@ -25,18 +25,16 @@ def controller.setup(container) container.spawn(key: "test") do |instance| instance.ready! - sleep(0.2) - @output.write(".") @output.flush - sleep(0.4) + sleep(0.2) end container.spawn do |instance| instance.ready! - sleep(0.3) + sleep(0.1) @output.write(",") @output.flush @@ -52,6 +50,7 @@ def controller.setup(container) controller.reload expect(input.read(1)).to be == "," + controller.wait end end From dd7f1efb1437105f0844439462d96fde3d3b2c5b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Feb 2025 15:04:25 +1300 Subject: [PATCH 150/166] Bump minor version. --- lib/async/container/version.rb | 2 +- readme.md | 4 ++++ releases.md | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 3e62776..268e846 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.20.1" + VERSION = "0.21.0" end end diff --git a/readme.md b/readme.md index 285fcc7..04a7bfe 100644 --- a/readme.md +++ b/readme.md @@ -22,6 +22,10 @@ Please see the [project documentation](https://socketry.github.io/async-containe Please see the [project releases](https://socketry.github.io/async-container/releases/index) for all releases. +### v0.21.0 + + - Use `SIGKILL`/`Thread#kill` when the health check fails. In some cases, `SIGTERM` may not be sufficient to terminate a process because the signal can be ignored or the process may be in an uninterruptible state. + ### v0.20.1 - Fix compatibility between Async::Container::Hybrid and the health check. diff --git a/releases.md b/releases.md index f93f2f9..c9424d3 100644 --- a/releases.md +++ b/releases.md @@ -1,6 +1,6 @@ # Releases -## Unreleased +## v0.21.0 - Use `SIGKILL`/`Thread#kill` when the health check fails. In some cases, `SIGTERM` may not be sufficient to terminate a process because the signal can be ignored or the process may be in an uninterruptible state. From 3977136cd9b02e5ab047de035a521ba71a2367b1 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 26 Feb 2025 15:47:15 +1300 Subject: [PATCH 151/166] Add instance `#to_json` and `#as_json` for serialization. --- fixtures/async/container/a_container.rb | 21 +++++++++++++++++++++ lib/async/container/forked.rb | 11 +++++++++++ lib/async/container/threaded.rb | 12 ++++++++++++ 3 files changed, 44 insertions(+) diff --git a/fixtures/async/container/a_container.rb b/fixtures/async/container/a_container.rb index 21891e8..461ab8e 100644 --- a/fixtures/async/container/a_container.rb +++ b/fixtures/async/container/a_container.rb @@ -87,6 +87,27 @@ module Container expect(input.read).to be == "true" end + with "instance" do + it "can generate JSON representation" do + IO.pipe do |input, output| + container.spawn do |instance| + output.write(instance.to_json) + end + + container.wait + + expect(container.statistics).to have_attributes(failures: be == 0) + + output.close + instance = JSON.parse(input.read, symbolize_names: true) + expect(instance).to have_keys( + process_id: be_a(Integer), + name: be_a(String), + ) + end + end + end + with "#sleep" do it "can sleep for a short time" do container.spawn do diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 6f5187f..292c171 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -41,6 +41,17 @@ def initialize(io) @name = nil end + def as_json(...) + { + process_id: ::Process.pid, + name: @name, + } + end + + def to_json(...) + as_json.to_json(...) + end + # Set the process title to the specified value. # @parameter value [String] The name of the process. def name= value diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index 3bb1bdf..9a0628f 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -59,6 +59,18 @@ def initialize(io) super end + def as_json(...) + { + process_id: ::Process.pid, + thread_id: @thread.object_id, + name: @thread.name, + } + end + + def to_json(...) + as_json.to_json(...) + end + # Set the name of the thread. # @parameter value [String] The name to set. def name= value From fef06302dc7ee4ffd5e4f978a7713c1ebf1647ec Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 26 Feb 2025 15:49:50 +1300 Subject: [PATCH 152/166] Tidy up instance name handling. --- lib/async/container/forked.rb | 7 ++++--- lib/async/container/threaded.rb | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 292c171..8370286 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -55,9 +55,10 @@ def to_json(...) # Set the process title to the specified value. # @parameter value [String] The name of the process. def name= value - if @name = value - ::Process.setproctitle(@name) - end + @name = value + + # This sets the process title to an empty string if the name is nil: + ::Process.setproctitle(@name.to_s) end # The name of the process. diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index 9a0628f..f979927 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -53,7 +53,6 @@ def self.for(thread) end def initialize(io) - @name = nil @thread = ::Thread.current super From a960cc5522a17d6ed94812de419d7b97a1829f19 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 26 Feb 2025 15:57:40 +1300 Subject: [PATCH 153/166] Bump minor version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 268e846..d39e2be 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.21.0" + VERSION = "0.22.0" end end From b1be8ea0a073b9c57cbc8dc9c62bc07fb34a8552 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 27 Feb 2025 19:20:44 +1300 Subject: [PATCH 154/166] Whitespace. --- examples/controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/controller.rb b/examples/controller.rb index afc5db3..427b3c7 100755 --- a/examples/controller.rb +++ b/examples/controller.rb @@ -15,14 +15,14 @@ def setup(container) else Console.debug(self, "Child process started.") end - + instance.ready! - + while true sleep 1 - + Console.debug(self, "Work") - + if rand < 0.5 Console.debug(self, "Should exit...") sleep 0.5 From 8612957c6fb1aac7e7a39b9689fd19a4b9b43d2a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 27 Feb 2025 19:46:00 +1300 Subject: [PATCH 155/166] `NOTIFY_LOG` readiness notification for Kubernetes probes. (#39) --- lib/async/container/notify.rb | 2 ++ lib/async/container/notify/log.rb | 52 ++++++++++++++++++++++++++++ lib/async/container/notify/server.rb | 1 + test/async/container/notify/log.rb | 23 ++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 lib/async/container/notify/log.rb create mode 100644 test/async/container/notify/log.rb diff --git a/lib/async/container/notify.rb b/lib/async/container/notify.rb index 3079bab..5dfb155 100644 --- a/lib/async/container/notify.rb +++ b/lib/async/container/notify.rb @@ -6,6 +6,7 @@ require_relative "notify/pipe" require_relative "notify/socket" require_relative "notify/console" +require_relative "notify/log" module Async module Container @@ -18,6 +19,7 @@ def self.open! @client ||= ( Pipe.open! || Socket.open! || + Log.open! || Console.open! ) end diff --git a/lib/async/container/notify/log.rb b/lib/async/container/notify/log.rb new file mode 100644 index 0000000..4992c88 --- /dev/null +++ b/lib/async/container/notify/log.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2024, by Samuel Williams. + +require_relative "client" +require "socket" + +module Async + module Container + module Notify + class Log < Client + # The name of the environment variable which contains the path to the notification socket. + NOTIFY_LOG = "NOTIFY_LOG" + + # Open a notification client attached to the current {NOTIFY_LOG} if possible. + def self.open!(environment = ENV) + if path = environment.delete(NOTIFY_LOG) + self.new(path) + end + end + + # Initialize the notification client. + # @parameter path [String] The path to the UNIX socket used for sending messages to the process manager. + def initialize(path) + @path = path + end + + # @attribute [String] The path to the UNIX socket used for sending messages to the controller. + attr :path + + # Send the given message. + # @parameter message [Hash] + def send(**message) + data = JSON.dump(message) + + File.open(@path, "a") do |file| + file.puts(data) + end + end + + # Send the specified error. + # `sd_notify` requires an `errno` key, which defaults to `-1` to indicate a generic error. + def error!(text, **message) + message[:errno] ||= -1 + + super + end + end + end + end +end diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index b75e8be..ca1e25d 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -5,6 +5,7 @@ # Copyright, 2020, by Olle Jonsson. require "tmpdir" +require "socket" require "securerandom" module Async diff --git a/test/async/container/notify/log.rb b/test/async/container/notify/log.rb new file mode 100644 index 0000000..9f02df4 --- /dev/null +++ b/test/async/container/notify/log.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2025, by Samuel Williams. +# Copyright, 2020, by Olle Jonsson. + +require "async/container/controller" +require "async/container/controllers" + +require "tmpdir" + +describe Async::Container::Notify::Pipe do + let(:notify_script) {Async::Container::Controllers.path_for("notify")} + let(:notify_log) {File.expand_path("notify-#{::Process.pid}-#{SecureRandom.hex(8)}.log", Dir.tmpdir)} + + it "receives notification of child status" do + system({"NOTIFY_LOG" => notify_log}, "bundle", "exec", notify_script) + + lines = File.readlines(notify_log).map{|line| JSON.parse(line)} + + expect(lines.last).to be == {"ready" => true} + end +end From b4c3627ffa1f39824969fc41dec05bf68b4cf50f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 27 Feb 2025 19:47:34 +1300 Subject: [PATCH 156/166] Bump minor version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index d39e2be..fcbd75e 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.22.0" + VERSION = "0.23.0" end end From 3dccefdb36ec6c3438b5bd90dbe52c211a0f1105 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 27 Feb 2025 19:59:41 +1300 Subject: [PATCH 157/166] Release notes. --- readme.md | 4 ++++ releases.md | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/readme.md b/readme.md index 04a7bfe..0c2463e 100644 --- a/readme.md +++ b/readme.md @@ -22,6 +22,10 @@ Please see the [project documentation](https://socketry.github.io/async-containe Please see the [project releases](https://socketry.github.io/async-container/releases/index) for all releases. +### v0.23.0 + + - [Add support for `NOTIFY_LOG` for Kubernetes readiness probes.](https://socketry.github.io/async-container/releases/index#add-support-for-notify_log-for-kubernetes-readiness-probes.) + ### v0.21.0 - Use `SIGKILL`/`Thread#kill` when the health check fails. In some cases, `SIGTERM` may not be sufficient to terminate a process because the signal can be ignored or the process may be in an uninterruptible state. diff --git a/releases.md b/releases.md index c9424d3..4176b2f 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,26 @@ # Releases +## v0.23.0 + +### Add support for `NOTIFY_LOG` for Kubernetes readiness probes. + +You may specify a `NOTIFY_LOG` environment variable to enable readiness logging to a log file. This can be used for Kubernetes readiness probes, e.g. + +```yaml +containers: + - name: falcon + env: + - name: NOTIFY_LOG + value: "/tmp/notify.log" + command: ["falcon", "host"] + readinessProbe: + exec: + command: ["sh", "-c", "grep -q '\"ready\":true' /tmp/notify.log"] + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 12 +``` + ## v0.21.0 - Use `SIGKILL`/`Thread#kill` when the health check fails. In some cases, `SIGTERM` may not be sufficient to terminate a process because the signal can be ignored or the process may be in an uninterruptible state. From 80e9efe1ea425b04f4801a564bb79099db2ebd62 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 28 Feb 2025 08:12:07 +1300 Subject: [PATCH 158/166] Add `size` (running children) to controller `ready!` notification. --- lib/async/container/controller.rb | 4 ++-- lib/async/container/generic.rb | 5 +++++ lib/async/container/group.rb | 5 +++++ test/async/container/notify/log.rb | 5 ++++- test/async/container/notify/pipe.rb | 2 +- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index fdeb031..c95f9f8 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -149,7 +149,7 @@ def restart old_container&.stop(@graceful_stop) end - @notify&.ready! + @notify&.ready!(size: @container.size) ensure # If we are leaving this function with an exception, try to kill the container: container&.stop(false) @@ -185,7 +185,7 @@ def reload # Enter the controller run loop, trapping `SIGINT` and `SIGTERM`. def run - @notify&.status!("Initializing...") + @notify&.status!("Initializing controller...") with_signal_handlers do self.start diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index a430352..b6bcaf2 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -51,6 +51,11 @@ def initialize(**options) # @attribute [Group] The group of running children instances. attr :group + # @returns [Integer] The number of running children instances. + def size + @group.size + end + # @attribute [Hash(Child, Hash)] The state of each child instance. attr :state diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 9d8602d..d29aaea 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -32,6 +32,11 @@ def inspect # @attribute [Hash(IO, Fiber)] the running tasks, indexed by IO. attr :running + # @returns [Integer] The number of running processes. + def size + @running.size + end + # Whether the group contains any running processes. # @returns [Boolean] def running? diff --git a/test/async/container/notify/log.rb b/test/async/container/notify/log.rb index 9f02df4..033cf4e 100644 --- a/test/async/container/notify/log.rb +++ b/test/async/container/notify/log.rb @@ -18,6 +18,9 @@ lines = File.readlines(notify_log).map{|line| JSON.parse(line)} - expect(lines.last).to be == {"ready" => true} + expect(lines.last).to have_keys( + "ready" => be == true, + "size" => be > 0, + ) end end diff --git a/test/async/container/notify/pipe.rb b/test/async/container/notify/pipe.rb index 87e9047..035c424 100644 --- a/test/async/container/notify/pipe.rb +++ b/test/async/container/notify/pipe.rb @@ -24,7 +24,7 @@ container.sleep _child, state = container.state.first - expect(state).to be == {status: "Initializing..."} + expect(state).to be == {status: "Initializing controller..."} container.wait From b5d6786d3f9ff166c93e19185e8e1c1124c3754f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 28 Feb 2025 08:12:19 +1300 Subject: [PATCH 159/166] Remove "Waiting for children..." debug log as it's quite noisy. --- lib/async/container/group.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index d29aaea..63b1747 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -180,7 +180,8 @@ def wait_for(channel) protected def wait_for_children(duration = nil) - Console.debug(self, "Waiting for children...", duration: duration, running: @running) + # This log is a big noisy and doesn't really provide a lot of useful information. + # Console.debug(self, "Waiting for children...", duration: duration, running: @running) if !@running.empty? # Maybe consider using a proper event loop here: From 2fb6f6b39aa4517159241ece66d91cbd62680106 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 28 Feb 2025 08:37:32 +1300 Subject: [PATCH 160/166] Bump patch version. --- lib/async/container/version.rb | 2 +- releases.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index fcbd75e..301b9b6 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.23.0" + VERSION = "0.23.1" end end diff --git a/releases.md b/releases.md index 4176b2f..a003c48 100644 --- a/releases.md +++ b/releases.md @@ -6,7 +6,7 @@ You may specify a `NOTIFY_LOG` environment variable to enable readiness logging to a log file. This can be used for Kubernetes readiness probes, e.g. -```yaml +``` yaml containers: - name: falcon env: From 63e94563cbdaa663e88d4704fe023a8b015ee584 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 1 Mar 2025 11:39:54 +1300 Subject: [PATCH 161/166] Make `#stop` less noisy. --- lib/async/container/group.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 63b1747..3b026e6 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -121,7 +121,7 @@ def terminate # Stop all child processes using {#terminate}. # @parameter timeout [Boolean | Numeric | Nil] If specified, invoke a graceful shutdown using {#interrupt} first. def stop(timeout = 1) - Console.info(self, "Stopping all processes...", timeout: timeout) + Console.debug(self, "Stopping all processes...", timeout: timeout) # Use a default timeout if not specified: timeout = 1 if timeout == true From 4cc4dc8e185f99ffe74c8e90da1b0fe721c83929 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 1 Mar 2025 11:41:15 +1300 Subject: [PATCH 162/166] Bump patch version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 301b9b6..4b23748 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.23.1" + VERSION = "0.23.2" end end From e8f39e57d1f8a173cfe30fb5f736bd4a30c27518 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 7 Mar 2025 20:29:44 +1300 Subject: [PATCH 163/166] Initial metrics for health check failure. (#41) --- config/metrics.rb | 8 ++++++++ config/sus.rb | 11 +++++++++++ examples/health_check/test.rb | 3 ++- gems.rb | 2 ++ lib/async/container/generic.rb | 11 ++++++++--- lib/metrics/provider/async/container.rb | 6 ++++++ lib/metrics/provider/async/container/generic.rb | 17 +++++++++++++++++ 7 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 config/metrics.rb create mode 100644 lib/metrics/provider/async/container.rb create mode 100644 lib/metrics/provider/async/container/generic.rb diff --git a/config/metrics.rb b/config/metrics.rb new file mode 100644 index 0000000..d5670e0 --- /dev/null +++ b/config/metrics.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +def prepare + require "metrics/provider/async/container" +end diff --git a/config/sus.rb b/config/sus.rb index ced7124..e5fc866 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -7,3 +7,14 @@ include Covered::Sus ENV["CONSOLE_LEVEL"] ||= "fatal" +ENV["METRICS_BACKEND"] ||= "metrics/backend/test" + +def prepare_instrumentation! + require "metrics" +end + +def before_tests(...) + prepare_instrumentation! + + super +end diff --git a/examples/health_check/test.rb b/examples/health_check/test.rb index 9e92baa..70c6bbe 100755 --- a/examples/health_check/test.rb +++ b/examples/health_check/test.rb @@ -5,7 +5,8 @@ # Copyright, 2022, by Anton Sozontov. # Copyright, 2024, by Samuel Williams. -require "../../lib/async/container/controller" +require "metrics" +require_relative "../../lib/async/container/controller" NAMES = [ "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb", "Ice Cream Sandwich", "Jelly Bean", "KitKat", "Lollipop", "Marshmallow", "Nougat", "Oreo", "Pie", "Apple Tart" diff --git a/gems.rb b/gems.rb index 725e802..086b282 100644 --- a/gems.rb +++ b/gems.rb @@ -21,6 +21,8 @@ gem "decode" gem "rubocop" + gem "metrics" + gem "bake-test" gem "bake-test-external" end diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index b6bcaf2..4e67774 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -145,6 +145,13 @@ def stop(timeout = true) @running = true end + protected def health_check_failed!(child, age_clock, health_check_timeout) + Console.warn(self, "Child failed health check!", child: child, age: age_clock.total, health_check_timeout: health_check_timeout) + + # If the child has failed the health check, we assume the worst and kill it immediately: + child.kill! + end + # Spawn a child instance into the container. # @parameter name [String] The name of the child instance. # @parameter restart [Boolean] Whether to restart the child instance if it fails. @@ -176,9 +183,7 @@ def spawn(name: nil, restart: false, key: nil, health_check_timeout: nil, &block case message when :health_check! if health_check_timeout&.<(age_clock.total) - Console.warn(self, "Child failed health check!", child: child, age: age_clock.total, health_check_timeout: health_check_timeout) - # If the child has failed the health check, we assume the worst and kill it immediately: - child.kill! + health_check_failed!(child, age_clock, health_check_timeout) end else state.update(message) diff --git a/lib/metrics/provider/async/container.rb b/lib/metrics/provider/async/container.rb new file mode 100644 index 0000000..80d07c2 --- /dev/null +++ b/lib/metrics/provider/async/container.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +require_relative "container/generic" diff --git a/lib/metrics/provider/async/container/generic.rb b/lib/metrics/provider/async/container/generic.rb new file mode 100644 index 0000000..aba27dd --- /dev/null +++ b/lib/metrics/provider/async/container/generic.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +require_relative "../../../../async/container/generic" +require "metrics/provider" + +Metrics::Provider(Async::Container::Generic) do + ASYNC_CONTAINER_GENERIC_HEALTH_CHECK_FAILED = Metrics.metric("async.container.generic.health_check_failed", :counter, description: "The number of health checks that failed.") + + protected def health_check_failed!(child, age_clock, health_check_timeout) + ASYNC_CONTAINER_GENERIC_HEALTH_CHECK_FAILED.emit(1) + + super + end +end From 2ea38e4e55c2e60b5667062acc97617a99eea088 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 7 Mar 2025 21:16:26 +1300 Subject: [PATCH 164/166] 100% documentation coverage. --- lib/async/container.rb | 2 ++ lib/async/container/error.rb | 11 +++++++++-- lib/async/container/forked.rb | 25 +++++++++++++++++++++++-- lib/async/container/generic.rb | 8 ++++++-- lib/async/container/group.rb | 1 + lib/async/container/keyed.rb | 15 +++++++++------ lib/async/container/notify/client.rb | 3 +++ lib/async/container/notify/log.rb | 1 + lib/async/container/notify/server.rb | 24 ++++++++++++++++++++++++ lib/async/container/statistics.rb | 1 + lib/async/container/threaded.rb | 19 ++++++++++++++++--- 11 files changed, 95 insertions(+), 15 deletions(-) diff --git a/lib/async/container.rb b/lib/async/container.rb index b4269d4..d95c64d 100644 --- a/lib/async/container.rb +++ b/lib/async/container.rb @@ -5,7 +5,9 @@ require_relative "container/controller" +# @namespace module Async + # @namespace module Container end end diff --git a/lib/async/container/error.rb b/lib/async/container/error.rb index 802ecfa..0758cae 100644 --- a/lib/async/container/error.rb +++ b/lib/async/container/error.rb @@ -5,6 +5,7 @@ module Async module Container + # Represents an error that occured during container execution. class Error < StandardError end @@ -13,15 +14,18 @@ class Error < StandardError # Similar to {Interrupt}, but represents `SIGTERM`. class Terminate < SignalException SIGTERM = Signal.list["TERM"] - + + # Create a new terminate error. def initialize super(SIGTERM) end end + # Similar to {Interrupt}, but represents `SIGHUP`. class Restart < SignalException SIGHUP = Signal.list["HUP"] + # Create a new restart error. def initialize super(SIGHUP) end @@ -29,13 +33,16 @@ def initialize # Represents the error which occured when a container failed to start up correctly. class SetupError < Error + # Create a new setup error. + # + # @parameter container [Generic] The container that failed. def initialize(container) super("Could not create container!") @container = container end - # The container that failed. + # @attribute [Generic] The container that failed. attr :container end end diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 8370286..bc21551 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -35,12 +35,18 @@ def self.for(process) return instance end + # Initialize the child process instance. + # + # @parameter io [IO] The IO object to use for communication. def initialize(io) super @name = nil end + # Generate a hash representation of the process. + # + # @returns [Hash] The process as a hash, including `process_id` and `name`. def as_json(...) { process_id: ::Process.pid, @@ -48,11 +54,15 @@ def as_json(...) } end + # Generate a JSON representation of the process. + # + # @returns [String] The process as JSON. def to_json(...) as_json.to_json(...) end # Set the process title to the specified value. + # # @parameter value [String] The name of the process. def name= value @name = value @@ -61,14 +71,17 @@ def name= value ::Process.setproctitle(@name.to_s) end - # The name of the process. - # @returns [String] + # @returns [String] The name of the process. def name @name end # Replace the current child process with a different one. Forwards arguments and options to {::Process.exec}. # This method replaces the child process with the new executable, thus this method never returns. + # + # @parameter arguments [Array] The arguments to pass to the new process. + # @parameter ready [Boolean] If true, informs the parent process that the child is ready. Otherwise, the child process will need to use a notification protocol to inform the parent process that it is ready. + # @parameter options [Hash] Additional options to pass to {::Process.exec}. def exec(*arguments, ready: true, **options) if ready self.ready!(status: "(exec)") @@ -81,6 +94,7 @@ def exec(*arguments, ready: true, **options) end # Fork a child process appropriate for a container. + # # @returns [Process] def self.fork(**options) # $stderr.puts fork: caller @@ -105,6 +119,13 @@ def self.fork(**options) end end + # Spawn a child process using {::Process.spawn}. + # + # The child process will need to inform the parent process that it is ready using a notification protocol. + # + # @parameter arguments [Array] The arguments to pass to the new process. + # @parameter name [String] The name of the process. + # @parameter options [Hash] Additional options to pass to {::Process.spawn}. def self.spawn(*arguments, name: nil, **options) self.new(name: name) do |process| Notify::Pipe.new(process.out).before_spawn(arguments, options) diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 4e67774..312dfce 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -32,12 +32,16 @@ def self.processor_count(env = ENV) # A base class for implementing containers. class Generic - def self.run(*arguments, **options, &block) - self.new.run(*arguments, **options, &block) + # Run a new container. + def self.run(...) + self.new.run(...) end UNNAMED = "Unnamed" + # Initialize the container. + # + # @parameter options [Hash] Options passed to the {Group} instance. def initialize(**options) @group = Group.new(**options) @running = true diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 3b026e6..f1f761c 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -25,6 +25,7 @@ def initialize(health_check_interval: 1.0) @queue = nil end + # @returns [String] A human-readable representation of the group. def inspect "#<#{self.class} running=#{@running.size}>" end diff --git a/lib/async/container/keyed.rb b/lib/async/container/keyed.rb index 6e2b753..f8d286e 100644 --- a/lib/async/container/keyed.rb +++ b/lib/async/container/keyed.rb @@ -8,22 +8,23 @@ module Container # Tracks a key/value pair such that unmarked keys can be identified and cleaned up. # This helps implement persistent processes that start up child processes per directory or configuration file. If those directories and/or configuration files are removed, the child process can then be cleaned up automatically, because those key/value pairs will not be marked when reloading the container. class Keyed + # Initialize the keyed instance + # + # @parameter key [Object] The key. + # @parameter value [Object] The value. def initialize(key, value) @key = key @value = value @marked = true end - # The key. Normally a symbol or a file-system path. - # @attribute [Object] + # @attribute [Object] The key value, normally a symbol or a file-system path. attr :key - # The value. Normally a child instance of some sort. - # @attribute [Object] + # @attribute [Object] The value, normally a child instance. attr :value - # Has the instance been marked? - # @returns [Boolean] + # @returns [Boolean] True if the instance has been marked, during reloading the container. def marked? @marked end @@ -39,6 +40,8 @@ def clear! end # Stop the instance if it was not marked. + # + # @returns [Boolean] True if the instance was stopped. def stop? unless @marked @value.stop diff --git a/lib/async/container/notify/client.rb b/lib/async/container/notify/client.rb index 63eca29..dbd3ac6 100644 --- a/lib/async/container/notify/client.rb +++ b/lib/async/container/notify/client.rb @@ -7,6 +7,9 @@ module Async module Container # Handles the details of several process readiness protocols. module Notify + # Represents a client that can send messages to the parent controller in order to notify it of readiness, status changes, etc. + # + # A process readiness protocol (e.g. `sd_notify`) is a simple protocol for a child process to notify the parent process that it is ready (e.g. to accept connections, to process requests, etc). This can help dependency-based startup systems to start services in the correct order, and to handle failures gracefully. class Client # Notify the parent controller that the child has become ready, with a brief status message. # @parameters message [Hash] Additional details to send with the message. diff --git a/lib/async/container/notify/log.rb b/lib/async/container/notify/log.rb index 4992c88..010211d 100644 --- a/lib/async/container/notify/log.rb +++ b/lib/async/container/notify/log.rb @@ -9,6 +9,7 @@ module Async module Container module Notify + # Represents a client that uses a local log file to communicate readiness, status changes, etc. class Log < Client # The name of the environment variable which contains the path to the notification socket. NOTIFY_LOG = "NOTIFY_LOG" diff --git a/lib/async/container/notify/server.rb b/lib/async/container/notify/server.rb index ca1e25d..a9d74fc 100644 --- a/lib/async/container/notify/server.rb +++ b/lib/async/container/notify/server.rb @@ -11,9 +11,14 @@ module Async module Container module Notify + # A simple UDP server that can be used to receive messages from a child process, tracking readiness, status changes, etc. class Server MAXIMUM_MESSAGE_SIZE = 4096 + # Parse a message, according to the `sd_notify` protocol. + # + # @parameter message [String] The message to parse. + # @returns [Hash] The parsed message. def self.load(message) lines = message.split("\n") @@ -38,6 +43,9 @@ def self.load(message) return Hash[pairs] end + # Generate a new unique path for the UNIX socket. + # + # @returns [String] The path for the UNIX socket. def self.generate_path File.expand_path( "async-container-#{::Process.pid}-#{SecureRandom.hex(8)}.ipc", @@ -45,21 +53,33 @@ def self.generate_path ) end + # Open a new server instance with a temporary and unique path. def self.open(path = self.generate_path) self.new(path) end + # Initialize the server with the given path. + # + # @parameter path [String] The path to the UNIX socket. def initialize(path) @path = path end + # @attribute [String] The path to the UNIX socket. attr :path + # Generate a bound context for receiving messages. + # + # @returns [Context] The bound context. def bind Context.new(@path) end + # A bound context for receiving messages. class Context + # Initialize the context with the given path. + # + # @parameter path [String] The path to the UNIX socket. def initialize(path) @path = path @bound = Addrinfo.unix(@path, ::Socket::SOCK_DGRAM).bind @@ -67,12 +87,16 @@ def initialize(path) @state = {} end + # Close the bound context. def close @bound.close File.unlink(@path) end + # Receive a message from the bound context. + # + # @returns [Hash] The parsed message. def receive while true data, _address, _flags, *_controls = @bound.recvmsg(MAXIMUM_MESSAGE_SIZE) diff --git a/lib/async/container/statistics.rb b/lib/async/container/statistics.rb index efda3af..ea38b0e 100644 --- a/lib/async/container/statistics.rb +++ b/lib/async/container/statistics.rb @@ -9,6 +9,7 @@ module Async module Container # Tracks various statistics relating to child instances in a container. class Statistics + # Initialize the statistics all to 0. def initialize @spawns = 0 @restarts = 0 diff --git a/lib/async/container/threaded.rb b/lib/async/container/threaded.rb index f979927..3b9595b 100644 --- a/lib/async/container/threaded.rb +++ b/lib/async/container/threaded.rb @@ -11,9 +11,6 @@ module Async module Container # A multi-thread container which uses {Thread.fork}. class Threaded < Generic - class Kill < Exception - end - # Indicates that this is not a multi-process container. def self.multiprocess? false @@ -52,12 +49,18 @@ def self.for(thread) return instance end + # Initialize the child thread instance. + # + # @parameter io [IO] The IO object to use for communication with the parent. def initialize(io) @thread = ::Thread.current super end + # Generate a hash representation of the thread. + # + # @returns [Hash] The thread as a hash, including `process_id`, `thread_id`, and `name`. def as_json(...) { process_id: ::Process.pid, @@ -66,6 +69,9 @@ def as_json(...) } end + # Generate a JSON representation of the thread. + # + # @returns [String] The thread as JSON. def to_json(...) as_json.to_json(...) end @@ -101,6 +107,9 @@ def exec(*arguments, ready: true, **options) end end + # Start a new child thread and execute the provided block in it. + # + # @parameter options [Hash] Additional options to to the new child instance. def self.fork(**options) self.new(**options) do |thread| ::Thread.new do @@ -113,6 +122,7 @@ def self.fork(**options) end # Initialize the thread. + # # @parameter name [String] The name to use for the child thread. def initialize(name: nil) super() @@ -230,6 +240,9 @@ def success? @error.nil? end + # Convert the status to a hash, suitable for serialization. + # + # @returns [Boolean | String] If the status is an error, the error message is returned, otherwise `true`. def as_json(...) if @error @error.inspect From 40f40c56179dd21d8296b5acdf3dbf20db73b6c9 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 7 Mar 2025 21:18:03 +1300 Subject: [PATCH 165/166] Bump minor version. --- lib/async/container/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/container/version.rb b/lib/async/container/version.rb index 4b23748..d2dd80e 100644 --- a/lib/async/container/version.rb +++ b/lib/async/container/version.rb @@ -5,6 +5,6 @@ module Async module Container - VERSION = "0.23.2" + VERSION = "0.24.0" end end From 6805b6b0469aa2b4e81256f324445c767407220e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 7 Mar 2025 21:28:51 +1300 Subject: [PATCH 166/166] Update release notes. --- readme.md | 4 ++++ releases.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/readme.md b/readme.md index 0c2463e..b7d147a 100644 --- a/readme.md +++ b/readme.md @@ -22,6 +22,10 @@ Please see the [project documentation](https://socketry.github.io/async-containe Please see the [project releases](https://socketry.github.io/async-container/releases/index) for all releases. +### v0.24.0 + + - Add support for health check failure metrics. + ### v0.23.0 - [Add support for `NOTIFY_LOG` for Kubernetes readiness probes.](https://socketry.github.io/async-container/releases/index#add-support-for-notify_log-for-kubernetes-readiness-probes.) diff --git a/releases.md b/releases.md index a003c48..ff8995e 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## v0.24.0 + + - Add support for health check failure metrics. + ## v0.23.0 ### Add support for `NOTIFY_LOG` for Kubernetes readiness probes.