sigterm_extensions 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE.md +0 -0
  5. data/README.md +0 -0
  6. data/bin/ctxirb +156 -0
  7. data/lib/git.rb +166 -0
  8. data/lib/git/LICENSE +21 -0
  9. data/lib/git/author.rb +14 -0
  10. data/lib/git/base.rb +551 -0
  11. data/lib/git/base/factory.rb +75 -0
  12. data/lib/git/branch.rb +126 -0
  13. data/lib/git/branches.rb +71 -0
  14. data/lib/git/config.rb +22 -0
  15. data/lib/git/diff.rb +159 -0
  16. data/lib/git/index.rb +5 -0
  17. data/lib/git/lib.rb +1041 -0
  18. data/lib/git/log.rb +128 -0
  19. data/lib/git/object.rb +312 -0
  20. data/lib/git/path.rb +31 -0
  21. data/lib/git/remote.rb +36 -0
  22. data/lib/git/repository.rb +6 -0
  23. data/lib/git/stash.rb +27 -0
  24. data/lib/git/stashes.rb +55 -0
  25. data/lib/git/status.rb +199 -0
  26. data/lib/git/version.rb +5 -0
  27. data/lib/git/working_directory.rb +4 -0
  28. data/lib/sigterm_extensions.rb +75 -0
  29. data/lib/sigterm_extensions/all.rb +12 -0
  30. data/lib/sigterm_extensions/backtrace_cleaner.rb +129 -0
  31. data/lib/sigterm_extensions/callbacks.rb +847 -0
  32. data/lib/sigterm_extensions/concern.rb +169 -0
  33. data/lib/sigterm_extensions/configurable.rb +38 -0
  34. data/lib/sigterm_extensions/core_ext.rb +4 -0
  35. data/lib/sigterm_extensions/core_ext/array.rb +3 -0
  36. data/lib/sigterm_extensions/core_ext/array/extract.rb +19 -0
  37. data/lib/sigterm_extensions/core_ext/array/extract_options.rb +29 -0
  38. data/lib/sigterm_extensions/core_ext/class.rb +3 -0
  39. data/lib/sigterm_extensions/core_ext/class/attribute.rb +139 -0
  40. data/lib/sigterm_extensions/core_ext/class/attribute_accessors.rb +4 -0
  41. data/lib/sigterm_extensions/core_ext/class/subclasses.rb +52 -0
  42. data/lib/sigterm_extensions/core_ext/custom.rb +12 -0
  43. data/lib/sigterm_extensions/core_ext/digest.rb +3 -0
  44. data/lib/sigterm_extensions/core_ext/digest/uuid.rb +51 -0
  45. data/lib/sigterm_extensions/core_ext/enumerable.rb +232 -0
  46. data/lib/sigterm_extensions/core_ext/file.rb +3 -0
  47. data/lib/sigterm_extensions/core_ext/file/atomic.rb +68 -0
  48. data/lib/sigterm_extensions/core_ext/hash.rb +3 -0
  49. data/lib/sigterm_extensions/core_ext/hash/deep_merge.rb +41 -0
  50. data/lib/sigterm_extensions/core_ext/hash/deep_transform_values.rb +44 -0
  51. data/lib/sigterm_extensions/core_ext/hash/except.rb +22 -0
  52. data/lib/sigterm_extensions/core_ext/hash/keys.rb +141 -0
  53. data/lib/sigterm_extensions/core_ext/hash/reverse_merge.rb +23 -0
  54. data/lib/sigterm_extensions/core_ext/hash/slice.rb +24 -0
  55. data/lib/sigterm_extensions/core_ext/kernel.rb +3 -0
  56. data/lib/sigterm_extensions/core_ext/kernel/concern.rb +12 -0
  57. data/lib/sigterm_extensions/core_ext/kernel/reporting.rb +43 -0
  58. data/lib/sigterm_extensions/core_ext/kernel/singleton_class.rb +6 -0
  59. data/lib/sigterm_extensions/core_ext/load_error.rb +7 -0
  60. data/lib/sigterm_extensions/core_ext/module.rb +3 -0
  61. data/lib/sigterm_extensions/core_ext/module/aliasing.rb +29 -0
  62. data/lib/sigterm_extensions/core_ext/module/anonymous.rb +28 -0
  63. data/lib/sigterm_extensions/core_ext/module/attr_internal.rb +36 -0
  64. data/lib/sigterm_extensions/core_ext/module/attribute_accessors.rb +208 -0
  65. data/lib/sigterm_extensions/core_ext/module/attribute_accessors_per_thread.rb +146 -0
  66. data/lib/sigterm_extensions/core_ext/module/concerning.rb +132 -0
  67. data/lib/sigterm_extensions/core_ext/module/delegation.rb +319 -0
  68. data/lib/sigterm_extensions/core_ext/module/redefine_method.rb +38 -0
  69. data/lib/sigterm_extensions/core_ext/module/remove_method.rb +15 -0
  70. data/lib/sigterm_extensions/core_ext/name_error.rb +36 -0
  71. data/lib/sigterm_extensions/core_ext/object.rb +3 -0
  72. data/lib/sigterm_extensions/core_ext/object/blank.rb +153 -0
  73. data/lib/sigterm_extensions/core_ext/object/colors.rb +39 -0
  74. data/lib/sigterm_extensions/core_ext/object/duplicable.rb +47 -0
  75. data/lib/sigterm_extensions/core_ext/object/inclusion.rb +27 -0
  76. data/lib/sigterm_extensions/core_ext/object/instance_variables.rb +28 -0
  77. data/lib/sigterm_extensions/core_ext/object/methods.rb +61 -0
  78. data/lib/sigterm_extensions/core_ext/object/with_options.rb +80 -0
  79. data/lib/sigterm_extensions/core_ext/range.rb +3 -0
  80. data/lib/sigterm_extensions/core_ext/range/compare_range.rb +74 -0
  81. data/lib/sigterm_extensions/core_ext/range/conversions.rb +39 -0
  82. data/lib/sigterm_extensions/core_ext/range/overlaps.rb +8 -0
  83. data/lib/sigterm_extensions/core_ext/securerandom.rb +43 -0
  84. data/lib/sigterm_extensions/core_ext/string.rb +3 -0
  85. data/lib/sigterm_extensions/core_ext/string/access.rb +93 -0
  86. data/lib/sigterm_extensions/core_ext/string/filters.rb +143 -0
  87. data/lib/sigterm_extensions/core_ext/string/starts_ends_with.rb +4 -0
  88. data/lib/sigterm_extensions/core_ext/string/strip.rb +25 -0
  89. data/lib/sigterm_extensions/core_ext/tryable.rb +132 -0
  90. data/lib/sigterm_extensions/descendants_tracker.rb +108 -0
  91. data/lib/sigterm_extensions/gem_methods.rb +47 -0
  92. data/lib/sigterm_extensions/hash_binding.rb +16 -0
  93. data/lib/sigterm_extensions/inflector.rb +339 -0
  94. data/lib/sigterm_extensions/inflector/acronyms.rb +42 -0
  95. data/lib/sigterm_extensions/inflector/inflections.rb +249 -0
  96. data/lib/sigterm_extensions/inflector/inflections/defaults.rb +117 -0
  97. data/lib/sigterm_extensions/inflector/rules.rb +37 -0
  98. data/lib/sigterm_extensions/inflector/version.rb +8 -0
  99. data/lib/sigterm_extensions/interactive_editor.rb +120 -0
  100. data/lib/sigterm_extensions/lazy.rb +34 -0
  101. data/lib/sigterm_extensions/lazy_load_hooks.rb +79 -0
  102. data/lib/sigterm_extensions/option_merger.rb +32 -0
  103. data/lib/sigterm_extensions/ordered_hash.rb +48 -0
  104. data/lib/sigterm_extensions/ordered_options.rb +83 -0
  105. data/lib/sigterm_extensions/paths.rb +235 -0
  106. data/lib/sigterm_extensions/per_thread_registry.rb +58 -0
  107. data/lib/sigterm_extensions/proxy_object.rb +14 -0
  108. data/lib/sigterm_extensions/staging/boot.rb +31 -0
  109. data/lib/sigterm_extensions/staging/boot/bundler_patch.rb +24 -0
  110. data/lib/sigterm_extensions/staging/boot/command.rb +26 -0
  111. data/lib/sigterm_extensions/staging/boot/gemfile_next_auto_sync.rb +79 -0
  112. data/lib/sigterm_extensions/version.rb +4 -0
  113. data/lib/sigterm_extensions/wrappable.rb +16 -0
  114. data/sigterm_extensions.gemspec +42 -0
  115. data/templates/dotpryrc.rb.erb +124 -0
  116. 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