sigterm_extensions 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +6 -0
- data/LICENSE.md +0 -0
- data/README.md +0 -0
- data/bin/ctxirb +156 -0
- data/lib/git.rb +166 -0
- data/lib/git/LICENSE +21 -0
- data/lib/git/author.rb +14 -0
- data/lib/git/base.rb +551 -0
- data/lib/git/base/factory.rb +75 -0
- data/lib/git/branch.rb +126 -0
- data/lib/git/branches.rb +71 -0
- data/lib/git/config.rb +22 -0
- data/lib/git/diff.rb +159 -0
- data/lib/git/index.rb +5 -0
- data/lib/git/lib.rb +1041 -0
- data/lib/git/log.rb +128 -0
- data/lib/git/object.rb +312 -0
- data/lib/git/path.rb +31 -0
- data/lib/git/remote.rb +36 -0
- data/lib/git/repository.rb +6 -0
- data/lib/git/stash.rb +27 -0
- data/lib/git/stashes.rb +55 -0
- data/lib/git/status.rb +199 -0
- data/lib/git/version.rb +5 -0
- data/lib/git/working_directory.rb +4 -0
- data/lib/sigterm_extensions.rb +75 -0
- data/lib/sigterm_extensions/all.rb +12 -0
- data/lib/sigterm_extensions/backtrace_cleaner.rb +129 -0
- data/lib/sigterm_extensions/callbacks.rb +847 -0
- data/lib/sigterm_extensions/concern.rb +169 -0
- data/lib/sigterm_extensions/configurable.rb +38 -0
- data/lib/sigterm_extensions/core_ext.rb +4 -0
- data/lib/sigterm_extensions/core_ext/array.rb +3 -0
- data/lib/sigterm_extensions/core_ext/array/extract.rb +19 -0
- data/lib/sigterm_extensions/core_ext/array/extract_options.rb +29 -0
- data/lib/sigterm_extensions/core_ext/class.rb +3 -0
- data/lib/sigterm_extensions/core_ext/class/attribute.rb +139 -0
- data/lib/sigterm_extensions/core_ext/class/attribute_accessors.rb +4 -0
- data/lib/sigterm_extensions/core_ext/class/subclasses.rb +52 -0
- data/lib/sigterm_extensions/core_ext/custom.rb +12 -0
- data/lib/sigterm_extensions/core_ext/digest.rb +3 -0
- data/lib/sigterm_extensions/core_ext/digest/uuid.rb +51 -0
- data/lib/sigterm_extensions/core_ext/enumerable.rb +232 -0
- data/lib/sigterm_extensions/core_ext/file.rb +3 -0
- data/lib/sigterm_extensions/core_ext/file/atomic.rb +68 -0
- data/lib/sigterm_extensions/core_ext/hash.rb +3 -0
- data/lib/sigterm_extensions/core_ext/hash/deep_merge.rb +41 -0
- data/lib/sigterm_extensions/core_ext/hash/deep_transform_values.rb +44 -0
- data/lib/sigterm_extensions/core_ext/hash/except.rb +22 -0
- data/lib/sigterm_extensions/core_ext/hash/keys.rb +141 -0
- data/lib/sigterm_extensions/core_ext/hash/reverse_merge.rb +23 -0
- data/lib/sigterm_extensions/core_ext/hash/slice.rb +24 -0
- data/lib/sigterm_extensions/core_ext/kernel.rb +3 -0
- data/lib/sigterm_extensions/core_ext/kernel/concern.rb +12 -0
- data/lib/sigterm_extensions/core_ext/kernel/reporting.rb +43 -0
- data/lib/sigterm_extensions/core_ext/kernel/singleton_class.rb +6 -0
- data/lib/sigterm_extensions/core_ext/load_error.rb +7 -0
- data/lib/sigterm_extensions/core_ext/module.rb +3 -0
- data/lib/sigterm_extensions/core_ext/module/aliasing.rb +29 -0
- data/lib/sigterm_extensions/core_ext/module/anonymous.rb +28 -0
- data/lib/sigterm_extensions/core_ext/module/attr_internal.rb +36 -0
- data/lib/sigterm_extensions/core_ext/module/attribute_accessors.rb +208 -0
- data/lib/sigterm_extensions/core_ext/module/attribute_accessors_per_thread.rb +146 -0
- data/lib/sigterm_extensions/core_ext/module/concerning.rb +132 -0
- data/lib/sigterm_extensions/core_ext/module/delegation.rb +319 -0
- data/lib/sigterm_extensions/core_ext/module/redefine_method.rb +38 -0
- data/lib/sigterm_extensions/core_ext/module/remove_method.rb +15 -0
- data/lib/sigterm_extensions/core_ext/name_error.rb +36 -0
- data/lib/sigterm_extensions/core_ext/object.rb +3 -0
- data/lib/sigterm_extensions/core_ext/object/blank.rb +153 -0
- data/lib/sigterm_extensions/core_ext/object/colors.rb +39 -0
- data/lib/sigterm_extensions/core_ext/object/duplicable.rb +47 -0
- data/lib/sigterm_extensions/core_ext/object/inclusion.rb +27 -0
- data/lib/sigterm_extensions/core_ext/object/instance_variables.rb +28 -0
- data/lib/sigterm_extensions/core_ext/object/methods.rb +61 -0
- data/lib/sigterm_extensions/core_ext/object/with_options.rb +80 -0
- data/lib/sigterm_extensions/core_ext/range.rb +3 -0
- data/lib/sigterm_extensions/core_ext/range/compare_range.rb +74 -0
- data/lib/sigterm_extensions/core_ext/range/conversions.rb +39 -0
- data/lib/sigterm_extensions/core_ext/range/overlaps.rb +8 -0
- data/lib/sigterm_extensions/core_ext/securerandom.rb +43 -0
- data/lib/sigterm_extensions/core_ext/string.rb +3 -0
- data/lib/sigterm_extensions/core_ext/string/access.rb +93 -0
- data/lib/sigterm_extensions/core_ext/string/filters.rb +143 -0
- data/lib/sigterm_extensions/core_ext/string/starts_ends_with.rb +4 -0
- data/lib/sigterm_extensions/core_ext/string/strip.rb +25 -0
- data/lib/sigterm_extensions/core_ext/tryable.rb +132 -0
- data/lib/sigterm_extensions/descendants_tracker.rb +108 -0
- data/lib/sigterm_extensions/gem_methods.rb +47 -0
- data/lib/sigterm_extensions/hash_binding.rb +16 -0
- data/lib/sigterm_extensions/inflector.rb +339 -0
- data/lib/sigterm_extensions/inflector/acronyms.rb +42 -0
- data/lib/sigterm_extensions/inflector/inflections.rb +249 -0
- data/lib/sigterm_extensions/inflector/inflections/defaults.rb +117 -0
- data/lib/sigterm_extensions/inflector/rules.rb +37 -0
- data/lib/sigterm_extensions/inflector/version.rb +8 -0
- data/lib/sigterm_extensions/interactive_editor.rb +120 -0
- data/lib/sigterm_extensions/lazy.rb +34 -0
- data/lib/sigterm_extensions/lazy_load_hooks.rb +79 -0
- data/lib/sigterm_extensions/option_merger.rb +32 -0
- data/lib/sigterm_extensions/ordered_hash.rb +48 -0
- data/lib/sigterm_extensions/ordered_options.rb +83 -0
- data/lib/sigterm_extensions/paths.rb +235 -0
- data/lib/sigterm_extensions/per_thread_registry.rb +58 -0
- data/lib/sigterm_extensions/proxy_object.rb +14 -0
- data/lib/sigterm_extensions/staging/boot.rb +31 -0
- data/lib/sigterm_extensions/staging/boot/bundler_patch.rb +24 -0
- data/lib/sigterm_extensions/staging/boot/command.rb +26 -0
- data/lib/sigterm_extensions/staging/boot/gemfile_next_auto_sync.rb +79 -0
- data/lib/sigterm_extensions/version.rb +4 -0
- data/lib/sigterm_extensions/wrappable.rb +16 -0
- data/sigterm_extensions.gemspec +42 -0
- data/templates/dotpryrc.rb.erb +124 -0
- metadata +315 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# vim: set syntax=ruby :
|
|
2
|
+
# Giles Bowkett, Greg Brown, and several audience members from Giles' Ruby East presentation.
|
|
3
|
+
# http://gilesbowkett.blogspot.com/2007/10/use-vi-or-any-text-editor-from-within.html
|
|
4
|
+
|
|
5
|
+
require 'irb'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'tempfile'
|
|
8
|
+
require 'shellwords'
|
|
9
|
+
require 'yaml'
|
|
10
|
+
|
|
11
|
+
class InteractiveEditor
|
|
12
|
+
VERSION = '0.0.11'
|
|
13
|
+
EDITORS = Hash.new { |h,k| h[k] = InteractiveEditor.new(k) }
|
|
14
|
+
|
|
15
|
+
attr_accessor :editor
|
|
16
|
+
|
|
17
|
+
def initialize(editor)
|
|
18
|
+
@editor = editor.to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def edit(object, file=nil)
|
|
22
|
+
object = object == TOPLEVEL_BINDING.eval('self') ? nil : object
|
|
23
|
+
|
|
24
|
+
current_file = if file
|
|
25
|
+
FileUtils.touch(file) unless File.exist?(file)
|
|
26
|
+
File.new(file)
|
|
27
|
+
else
|
|
28
|
+
if @file && File.exist?(@file.path) && !object
|
|
29
|
+
@file
|
|
30
|
+
else
|
|
31
|
+
Tempfile.new( object ? ["yobj_tempfile", ".yml"] : ["irb_tempfile", ".rb"] )
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if object
|
|
36
|
+
File.open( current_file.path, 'w' ) { |f| f << object.to_yaml }
|
|
37
|
+
else
|
|
38
|
+
@file = current_file
|
|
39
|
+
mtime = File.stat(@file.path).mtime
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
args = Shellwords.shellwords(@editor) #parse @editor as arguments could be complex
|
|
43
|
+
args << current_file.path
|
|
44
|
+
current_file.close rescue nil
|
|
45
|
+
Exec.system(*args)
|
|
46
|
+
|
|
47
|
+
if object
|
|
48
|
+
File.exists?(current_file.path) ? YAML.load_file(current_file.path) : object
|
|
49
|
+
elsif mtime < File.stat(@file.path).mtime
|
|
50
|
+
execute
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def execute
|
|
55
|
+
eval(IO.read(@file.path), TOPLEVEL_BINDING)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.edit(editor, self_, file=nil)
|
|
59
|
+
find_editor[editor].edit(self_, file)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.find_editor
|
|
63
|
+
#maybe serialise last file to disk, for recovery
|
|
64
|
+
if defined?(Pry) and IRB == Pry
|
|
65
|
+
IRB.config.interactive_editors ||= EDITORS
|
|
66
|
+
else
|
|
67
|
+
IRB.conf[:interactive_editors] ||= EDITORS
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module Exec
|
|
72
|
+
module Java
|
|
73
|
+
def system(file, *args)
|
|
74
|
+
require 'spoon'
|
|
75
|
+
Process.waitpid(Spoon.spawnp(file, *args))
|
|
76
|
+
rescue Errno::ECHILD => e
|
|
77
|
+
raise "error exec'ing #{file}: #{e}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
module MRI
|
|
82
|
+
def system(file, *args)
|
|
83
|
+
Kernel::system(file, *args) #or raise "error exec'ing #{file}: #{$?}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
extend RUBY_PLATFORM =~ /java/ ? Java : MRI
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
module Editors
|
|
91
|
+
{
|
|
92
|
+
:vi => nil,
|
|
93
|
+
:vim => nil,
|
|
94
|
+
:nvim => nil,
|
|
95
|
+
:emacs => nil,
|
|
96
|
+
:nano => nil,
|
|
97
|
+
:mate => 'mate -w',
|
|
98
|
+
:subl => 'subl -wn',
|
|
99
|
+
:mvim => 'mvim -g -f' + case ENV['TERM_PROGRAM']
|
|
100
|
+
when 'iTerm.app'; ' -c "au VimLeave * !open -a iTerm"'
|
|
101
|
+
when 'Apple_Terminal'; ' -c "au VimLeave * !open -a Terminal"'
|
|
102
|
+
else '' #don't do tricky things if we don't know the Term
|
|
103
|
+
end
|
|
104
|
+
}.each do |k,v|
|
|
105
|
+
define_method(k) do |*args|
|
|
106
|
+
InteractiveEditor.edit(v || k, self, *args)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def ed(*args)
|
|
111
|
+
if ENV['EDITOR'].to_s.size > 0
|
|
112
|
+
InteractiveEditor.edit(ENV['EDITOR'], self, *args)
|
|
113
|
+
else
|
|
114
|
+
raise "You need to set the EDITOR environment variable first"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
include InteractiveEditor::Editors
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module SigtermExtensions
|
|
2
|
+
# A class that can be wrapped around an expensive method call so it's only
|
|
3
|
+
# executed when actually needed.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
#
|
|
7
|
+
# object = SigtermExtensions::Lazy.new { some_expensive_work_here }
|
|
8
|
+
#
|
|
9
|
+
# object['foo']
|
|
10
|
+
# object.bar
|
|
11
|
+
class Lazy < BasicObject
|
|
12
|
+
def initialize(&block)
|
|
13
|
+
@block = block
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def method_missing(name, *args, &block)
|
|
17
|
+
__evaluate__
|
|
18
|
+
|
|
19
|
+
@result.__send__(name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def respond_to_missing?(name, include_private = false)
|
|
23
|
+
__evaluate__
|
|
24
|
+
|
|
25
|
+
@result.respond_to?(name, include_private) || super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def __evaluate__
|
|
31
|
+
@result = @block.call unless defined?(@result)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module SigtermExtensions
|
|
2
|
+
# lazy_load_hooks allows Rails to lazily load a lot of components and thus
|
|
3
|
+
# making the app boot faster. Because of this feature now there is no need to
|
|
4
|
+
# require <tt>ActiveRecord::Base</tt> at boot time purely to apply
|
|
5
|
+
# configuration. Instead a hook is registered that applies configuration once
|
|
6
|
+
# <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is
|
|
7
|
+
# used as example but this feature can be applied elsewhere too.
|
|
8
|
+
#
|
|
9
|
+
# Here is an example where +on_load+ method is called to register a hook.
|
|
10
|
+
#
|
|
11
|
+
# initializer 'active_record.initialize_timezone' do
|
|
12
|
+
# ActiveSupport.on_load(:active_record) do
|
|
13
|
+
# self.time_zone_aware_attributes = true
|
|
14
|
+
# self.default_timezone = :utc
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# When the entirety of +ActiveRecord::Base+ has been
|
|
19
|
+
# evaluated then +run_load_hooks+ is invoked. The very last line of
|
|
20
|
+
# +ActiveRecord::Base+ is:
|
|
21
|
+
#
|
|
22
|
+
# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
|
|
23
|
+
module LazyLoadHooks
|
|
24
|
+
def self.extended(base) # :nodoc:
|
|
25
|
+
base.class_eval do
|
|
26
|
+
@load_hooks = Hash.new { |h, k| h[k] = [] }
|
|
27
|
+
@loaded = Hash.new { |h, k| h[k] = [] }
|
|
28
|
+
@run_once = Hash.new { |h, k| h[k] = [] }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Declares a block that will be executed when a Rails component is fully
|
|
33
|
+
# loaded.
|
|
34
|
+
#
|
|
35
|
+
# Options:
|
|
36
|
+
#
|
|
37
|
+
# * <tt>:yield</tt> - Yields the object that run_load_hooks to +block+.
|
|
38
|
+
# * <tt>:run_once</tt> - Given +block+ will run only once.
|
|
39
|
+
def on_load(name, options = {}, &block)
|
|
40
|
+
@loaded[name].each do |base|
|
|
41
|
+
execute_hook(name, base, options, block)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@load_hooks[name] << [block, options]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def run_load_hooks(name, base = Object)
|
|
48
|
+
@loaded[name] << base
|
|
49
|
+
@load_hooks[name].each do |hook, options|
|
|
50
|
+
execute_hook(name, base, options, hook)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
def with_execution_control(name, block, once)
|
|
56
|
+
unless @run_once[name].include?(block)
|
|
57
|
+
@run_once[name] << block if once
|
|
58
|
+
|
|
59
|
+
yield
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def execute_hook(name, base, options, block)
|
|
64
|
+
with_execution_control(name, block, options[:run_once]) do
|
|
65
|
+
if options[:yield]
|
|
66
|
+
block.call(base)
|
|
67
|
+
else
|
|
68
|
+
if base.is_a?(Module)
|
|
69
|
+
base.class_eval(&block)
|
|
70
|
+
else
|
|
71
|
+
base.instance_eval(&block)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
extend LazyLoadHooks
|
|
79
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "sigterm_extensions/core_ext/hash/deep_merge"
|
|
2
|
+
|
|
3
|
+
module SigtermExtensions
|
|
4
|
+
class OptionMerger #:nodoc:
|
|
5
|
+
instance_methods.each do |method|
|
|
6
|
+
undef_method(method) unless method.match?(/^(__|instance_eval|class|object_id)/)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(context, options)
|
|
10
|
+
@context, @options = context, options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
def method_missing(method, *arguments, &block)
|
|
15
|
+
options = nil
|
|
16
|
+
if arguments.first.is_a?(Proc)
|
|
17
|
+
proc = arguments.pop
|
|
18
|
+
arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
|
|
19
|
+
elsif arguments.last.respond_to?(:to_hash)
|
|
20
|
+
options = @options.deep_merge(arguments.pop)
|
|
21
|
+
else
|
|
22
|
+
options = @options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if options
|
|
26
|
+
@context.__send__(method, *arguments, **options, &block)
|
|
27
|
+
else
|
|
28
|
+
@context.__send__(method, *arguments, &block)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
YAML.add_builtin_type("omap") do |type, val|
|
|
4
|
+
SigtermExtensions::OrderedHash[val.map { |v| v.to_a.first }]
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module SigtermExtensions
|
|
8
|
+
# DEPRECATED: <tt>SigtermExtensions::OrderedHash</tt> implements a hash that preserves
|
|
9
|
+
# insertion order.
|
|
10
|
+
#
|
|
11
|
+
# oh = SigtermExtensions::OrderedHash.new
|
|
12
|
+
# oh[:a] = 1
|
|
13
|
+
# oh[:b] = 2
|
|
14
|
+
# oh.keys # => [:a, :b], this order is guaranteed
|
|
15
|
+
#
|
|
16
|
+
# Also, maps the +omap+ feature for YAML files
|
|
17
|
+
# (See https://yaml.org/type/omap.html) to support ordered items
|
|
18
|
+
# when loading from yaml.
|
|
19
|
+
#
|
|
20
|
+
# <tt>SigtermExtensions::OrderedHash</tt> is namespaced to prevent conflicts
|
|
21
|
+
# with other implementations.
|
|
22
|
+
class OrderedHash < ::Hash
|
|
23
|
+
def to_yaml_type
|
|
24
|
+
"!tag:yaml.org,2002:omap"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def encode_with(coder)
|
|
28
|
+
coder.represent_seq "!omap", map { |k, v| { k => v } }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def select(*args, &block)
|
|
32
|
+
dup.tap { |hash| hash.select!(*args, &block) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def reject(*args, &block)
|
|
36
|
+
dup.tap { |hash| hash.reject!(*args, &block) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def nested_under_indifferent_access
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns true to make sure that this hash is extractable via <tt>Array#extract_options!</tt>
|
|
44
|
+
def extractable_options?
|
|
45
|
+
true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "sigterm_extensions/core_ext/object/blank"
|
|
2
|
+
|
|
3
|
+
module SigtermExtensions
|
|
4
|
+
# Usually key value pairs are handled something like this:
|
|
5
|
+
#
|
|
6
|
+
# h = {}
|
|
7
|
+
# h[:boy] = 'John'
|
|
8
|
+
# h[:girl] = 'Mary'
|
|
9
|
+
# h[:boy] # => 'John'
|
|
10
|
+
# h[:girl] # => 'Mary'
|
|
11
|
+
# h[:dog] # => nil
|
|
12
|
+
#
|
|
13
|
+
# Using +OrderedOptions+, the above code could be reduced to:
|
|
14
|
+
#
|
|
15
|
+
# h = SigtermExtensions::OrderedOptions.new
|
|
16
|
+
# h.boy = 'John'
|
|
17
|
+
# h.girl = 'Mary'
|
|
18
|
+
# h.boy # => 'John'
|
|
19
|
+
# h.girl # => 'Mary'
|
|
20
|
+
# h.dog # => nil
|
|
21
|
+
#
|
|
22
|
+
# To raise an exception when the value is blank, append a
|
|
23
|
+
# bang to the key name, like:
|
|
24
|
+
#
|
|
25
|
+
# h.dog! # => raises KeyError: :dog is blank
|
|
26
|
+
#
|
|
27
|
+
class OrderedOptions < Hash
|
|
28
|
+
alias_method :_get, :[] # preserve the original #[] method
|
|
29
|
+
protected :_get # make it protected
|
|
30
|
+
|
|
31
|
+
def []=(key, value)
|
|
32
|
+
super(key.to_sym, value)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def [](key)
|
|
36
|
+
super(key.to_sym)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def method_missing(name, *args)
|
|
40
|
+
name_string = +name.to_s
|
|
41
|
+
if name_string.chomp!("=")
|
|
42
|
+
self[name_string] = args.first
|
|
43
|
+
else
|
|
44
|
+
bangs = name_string.chomp!("!")
|
|
45
|
+
|
|
46
|
+
if bangs
|
|
47
|
+
self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
|
|
48
|
+
else
|
|
49
|
+
self[name_string]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def respond_to_missing?(name, include_private)
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# +InheritableOptions+ provides a constructor to build an +OrderedOptions+
|
|
60
|
+
# hash inherited from another hash.
|
|
61
|
+
#
|
|
62
|
+
# Use this if you already have some hash and you want to create a new one based on it.
|
|
63
|
+
#
|
|
64
|
+
# h = ActiveSupport::InheritableOptions.new({ girl: 'Mary', boy: 'John' })
|
|
65
|
+
# h.girl # => 'Mary'
|
|
66
|
+
# h.boy # => 'John'
|
|
67
|
+
class InheritableOptions < OrderedOptions
|
|
68
|
+
def initialize(parent = nil)
|
|
69
|
+
if parent.kind_of?(OrderedOptions)
|
|
70
|
+
# use the faster _get when dealing with OrderedOptions
|
|
71
|
+
super() { |h, k| parent._get(k) }
|
|
72
|
+
elsif parent
|
|
73
|
+
super() { |h, k| parent[k] }
|
|
74
|
+
else
|
|
75
|
+
super()
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def inheritable_copy
|
|
80
|
+
self.class.new(self)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
module SigtermExtensions
|
|
2
|
+
module Paths
|
|
3
|
+
# This object is an extended hash that behaves as root of the <tt>SigtermExtensions::Paths</tt> system.
|
|
4
|
+
# It allows you to collect information about how you want to structure your application
|
|
5
|
+
# paths through a Hash-like API. It requires you to give a physical path on initialization.
|
|
6
|
+
#
|
|
7
|
+
# root = Root.new "/SigtermExtensions"
|
|
8
|
+
# root.add "app/controllers", eager_load: true
|
|
9
|
+
#
|
|
10
|
+
# The above command creates a new root object and adds "app/controllers" as a path.
|
|
11
|
+
# This means we can get a <tt>SigtermExtensions::Paths::Path</tt> object back like below:
|
|
12
|
+
#
|
|
13
|
+
# path = root["app/controllers"]
|
|
14
|
+
# path.eager_load? # => true
|
|
15
|
+
# path.is_a?(SigtermExtensions::Paths::Path) # => true
|
|
16
|
+
#
|
|
17
|
+
# The +Path+ object is simply an enumerable and allows you to easily add extra paths:
|
|
18
|
+
#
|
|
19
|
+
# path.is_a?(Enumerable) # => true
|
|
20
|
+
# path.to_ary.inspect # => ["app/controllers"]
|
|
21
|
+
#
|
|
22
|
+
# path << "lib/controllers"
|
|
23
|
+
# path.to_ary.inspect # => ["app/controllers", "lib/controllers"]
|
|
24
|
+
#
|
|
25
|
+
# Notice that when you add a path using +add+, the path object created already
|
|
26
|
+
# contains the path with the same path value given to +add+. In some situations,
|
|
27
|
+
# you may not want this behavior, so you can give <tt>:with</tt> as option.
|
|
28
|
+
#
|
|
29
|
+
# root.add "config/routes", with: "config/routes.rb"
|
|
30
|
+
# root["config/routes"].inspect # => ["config/routes.rb"]
|
|
31
|
+
#
|
|
32
|
+
# The +add+ method accepts the following options as arguments:
|
|
33
|
+
# eager_load, autoload, autoload_once, and glob.
|
|
34
|
+
#
|
|
35
|
+
# Finally, the +Path+ object also provides a few helpers:
|
|
36
|
+
#
|
|
37
|
+
# root = Root.new "/SigtermExtensions"
|
|
38
|
+
# root.add "app/controllers"
|
|
39
|
+
#
|
|
40
|
+
# root["app/controllers"].expanded # => ["/SigtermExtensions/app/controllers"]
|
|
41
|
+
# root["app/controllers"].existent # => ["/SigtermExtensions/app/controllers"]
|
|
42
|
+
#
|
|
43
|
+
# Check the <tt>SigtermExtensions::Paths::Path</tt> documentation for more information.
|
|
44
|
+
class Root
|
|
45
|
+
attr_accessor :path
|
|
46
|
+
|
|
47
|
+
def initialize(path)
|
|
48
|
+
@path = path
|
|
49
|
+
@root = {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def []=(path, value)
|
|
53
|
+
glob = self[path] ? self[path].glob : nil
|
|
54
|
+
add(path, with: value, glob: glob)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add(path, options = {})
|
|
58
|
+
with = Array(options.fetch(:with, path))
|
|
59
|
+
@root[path] = Path.new(self, path, with, options)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def [](path)
|
|
63
|
+
@root[path]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def values
|
|
67
|
+
@root.values
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def keys
|
|
71
|
+
@root.keys
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def values_at(*list)
|
|
75
|
+
@root.values_at(*list)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def all_paths
|
|
79
|
+
values.tap(&:uniq!)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def autoload_once
|
|
83
|
+
filter_by(&:autoload_once?)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def eager_load
|
|
87
|
+
filter_by(&:eager_load?)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def autoload_paths
|
|
91
|
+
filter_by(&:autoload?)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def load_paths
|
|
95
|
+
filter_by(&:load_path?)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
def filter_by(&block)
|
|
100
|
+
all_paths.find_all(&block).flat_map { |path|
|
|
101
|
+
paths = path.existent
|
|
102
|
+
paths - path.children.flat_map { |p| yield(p) ? [] : p.existent }
|
|
103
|
+
}.uniq
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def new_root(r)
|
|
108
|
+
Root.new(r)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class Path
|
|
112
|
+
include Enumerable
|
|
113
|
+
|
|
114
|
+
attr_accessor :glob
|
|
115
|
+
|
|
116
|
+
def initialize(root, current, paths, options = {})
|
|
117
|
+
@paths = paths
|
|
118
|
+
@current = current
|
|
119
|
+
@root = root
|
|
120
|
+
@glob = options[:glob]
|
|
121
|
+
@exclude = options[:exclude]
|
|
122
|
+
|
|
123
|
+
options[:autoload_once] ? autoload_once! : skip_autoload_once!
|
|
124
|
+
options[:eager_load] ? eager_load! : skip_eager_load!
|
|
125
|
+
options[:autoload] ? autoload! : skip_autoload!
|
|
126
|
+
options[:load_path] ? load_path! : skip_load_path!
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def absolute_current # :nodoc:
|
|
130
|
+
File.expand_path(@current, @root.path)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def children
|
|
134
|
+
keys = @root.keys.find_all { |k|
|
|
135
|
+
k.start_with?(@current) && k != @current
|
|
136
|
+
}
|
|
137
|
+
@root.values_at(*keys.sort)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def first
|
|
141
|
+
expanded.first
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def last
|
|
145
|
+
expanded.last
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
%w(autoload_once eager_load autoload load_path).each do |m|
|
|
149
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
150
|
+
def #{m}! # def eager_load!
|
|
151
|
+
@#{m} = true # @eager_load = true
|
|
152
|
+
end # end
|
|
153
|
+
#
|
|
154
|
+
def skip_#{m}! # def skip_eager_load!
|
|
155
|
+
@#{m} = false # @eager_load = false
|
|
156
|
+
end # end
|
|
157
|
+
#
|
|
158
|
+
def #{m}? # def eager_load?
|
|
159
|
+
@#{m} # @eager_load
|
|
160
|
+
end # end
|
|
161
|
+
RUBY
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def each(&block)
|
|
165
|
+
@paths.each(&block)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def <<(path)
|
|
169
|
+
@paths << path
|
|
170
|
+
end
|
|
171
|
+
alias :push :<<
|
|
172
|
+
|
|
173
|
+
def concat(paths)
|
|
174
|
+
@paths.concat paths
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def unshift(*paths)
|
|
178
|
+
@paths.unshift(*paths)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def to_ary
|
|
182
|
+
@paths
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def extensions # :nodoc:
|
|
186
|
+
$1.split(",") if @glob =~ /\{([\S]+)\}/
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Expands all paths against the root and return all unique values.
|
|
190
|
+
def expanded
|
|
191
|
+
raise "You need to set a path root" unless @root.path
|
|
192
|
+
result = []
|
|
193
|
+
|
|
194
|
+
each do |path|
|
|
195
|
+
path = File.expand_path(path, @root.path)
|
|
196
|
+
|
|
197
|
+
if @glob && File.directory?(path)
|
|
198
|
+
result.concat files_in(path)
|
|
199
|
+
else
|
|
200
|
+
result << path
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
result.uniq!
|
|
205
|
+
result
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Returns all expanded paths but only if they exist in the filesystem.
|
|
209
|
+
def existent
|
|
210
|
+
expanded.select do |f|
|
|
211
|
+
does_exist = File.exist?(f)
|
|
212
|
+
|
|
213
|
+
if !does_exist && File.symlink?(f)
|
|
214
|
+
raise "File #{f.inspect} is a symlink that does not point to a valid file"
|
|
215
|
+
end
|
|
216
|
+
does_exist
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def existent_directories
|
|
221
|
+
expanded.select { |d| File.directory?(d) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
alias to_a expanded
|
|
225
|
+
|
|
226
|
+
private
|
|
227
|
+
def files_in(path)
|
|
228
|
+
files = Dir.glob(@glob, base: path)
|
|
229
|
+
files -= @exclude if @exclude
|
|
230
|
+
files.map! { |file| File.join(path, file) }
|
|
231
|
+
files.sort
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|