codeclimate-fede 0.85.21
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/bin/check +18 -0
- data/bin/codeclimate +21 -0
- data/bin/prep-release +45 -0
- data/bin/publish +47 -0
- data/bin/release +41 -0
- data/bin/validate-release +18 -0
- data/config/engines.yml +322 -0
- data/lib/cc/analyzer/bridge.rb +106 -0
- data/lib/cc/analyzer/composite_container_listener.rb +21 -0
- data/lib/cc/analyzer/container/result.rb +74 -0
- data/lib/cc/analyzer/container.rb +208 -0
- data/lib/cc/analyzer/container_listener.rb +9 -0
- data/lib/cc/analyzer/engine.rb +125 -0
- data/lib/cc/analyzer/engine_output.rb +74 -0
- data/lib/cc/analyzer/engine_output_filter.rb +36 -0
- data/lib/cc/analyzer/engine_output_overrider.rb +31 -0
- data/lib/cc/analyzer/filesystem.rb +50 -0
- data/lib/cc/analyzer/formatters/formatter.rb +53 -0
- data/lib/cc/analyzer/formatters/html_formatter.rb +415 -0
- data/lib/cc/analyzer/formatters/json_formatter.rb +38 -0
- data/lib/cc/analyzer/formatters/plain_text_formatter.rb +101 -0
- data/lib/cc/analyzer/formatters/spinner.rb +35 -0
- data/lib/cc/analyzer/formatters.rb +21 -0
- data/lib/cc/analyzer/issue.rb +69 -0
- data/lib/cc/analyzer/issue_sorter.rb +30 -0
- data/lib/cc/analyzer/issue_validations/category_validation.rb +32 -0
- data/lib/cc/analyzer/issue_validations/check_name_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/content_validation.rb +21 -0
- data/lib/cc/analyzer/issue_validations/description_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/location_format_validation.rb +72 -0
- data/lib/cc/analyzer/issue_validations/other_locations_format_validation.rb +41 -0
- data/lib/cc/analyzer/issue_validations/path_existence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/path_is_file_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/path_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/relative_path_validation.rb +32 -0
- data/lib/cc/analyzer/issue_validations/remediation_points_validation.rb +25 -0
- data/lib/cc/analyzer/issue_validations/severity_validation.rb +39 -0
- data/lib/cc/analyzer/issue_validations/type_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/validation.rb +35 -0
- data/lib/cc/analyzer/issue_validations.rb +26 -0
- data/lib/cc/analyzer/issue_validator.rb +11 -0
- data/lib/cc/analyzer/location_description.rb +45 -0
- data/lib/cc/analyzer/logging_container_listener.rb +24 -0
- data/lib/cc/analyzer/measurement.rb +22 -0
- data/lib/cc/analyzer/measurement_validations/name_validation.rb +23 -0
- data/lib/cc/analyzer/measurement_validations/type_validation.rb +15 -0
- data/lib/cc/analyzer/measurement_validations/validation.rb +27 -0
- data/lib/cc/analyzer/measurement_validations/value_validation.rb +21 -0
- data/lib/cc/analyzer/measurement_validations.rb +16 -0
- data/lib/cc/analyzer/measurement_validator.rb +11 -0
- data/lib/cc/analyzer/mounted_path.rb +80 -0
- data/lib/cc/analyzer/raising_container_listener.rb +32 -0
- data/lib/cc/analyzer/source_buffer.rb +47 -0
- data/lib/cc/analyzer/source_extractor.rb +79 -0
- data/lib/cc/analyzer/source_fingerprint.rb +40 -0
- data/lib/cc/analyzer/statsd_container_listener.rb +51 -0
- data/lib/cc/analyzer/validator.rb +38 -0
- data/lib/cc/analyzer.rb +50 -0
- data/lib/cc/cli/analyze/engine_failure.rb +11 -0
- data/lib/cc/cli/analyze.rb +90 -0
- data/lib/cc/cli/command.rb +85 -0
- data/lib/cc/cli/console.rb +12 -0
- data/lib/cc/cli/engines/engine_command.rb +15 -0
- data/lib/cc/cli/engines/install.rb +35 -0
- data/lib/cc/cli/engines/list.rb +18 -0
- data/lib/cc/cli/engines.rb +5 -0
- data/lib/cc/cli/file_store.rb +42 -0
- data/lib/cc/cli/global_cache.rb +47 -0
- data/lib/cc/cli/global_config.rb +35 -0
- data/lib/cc/cli/help.rb +51 -0
- data/lib/cc/cli/output.rb +34 -0
- data/lib/cc/cli/prepare.rb +98 -0
- data/lib/cc/cli/runner.rb +75 -0
- data/lib/cc/cli/validate_config.rb +84 -0
- data/lib/cc/cli/version.rb +16 -0
- data/lib/cc/cli/version_checker.rb +107 -0
- data/lib/cc/cli.rb +39 -0
- data/lib/cc/config/checks_adapter.rb +40 -0
- data/lib/cc/config/default_adapter.rb +54 -0
- data/lib/cc/config/engine.rb +41 -0
- data/lib/cc/config/engine_set.rb +47 -0
- data/lib/cc/config/json_adapter.rb +17 -0
- data/lib/cc/config/prepare.rb +92 -0
- data/lib/cc/config/validation/check_validator.rb +34 -0
- data/lib/cc/config/validation/engine_validator.rb +93 -0
- data/lib/cc/config/validation/fetch_validator.rb +78 -0
- data/lib/cc/config/validation/file_validator.rb +112 -0
- data/lib/cc/config/validation/hash_validations.rb +52 -0
- data/lib/cc/config/validation/json.rb +31 -0
- data/lib/cc/config/validation/prepare_validator.rb +40 -0
- data/lib/cc/config/validation/yaml.rb +66 -0
- data/lib/cc/config/yaml_adapter.rb +73 -0
- data/lib/cc/config.rb +70 -0
- data/lib/cc/engine_registry.rb +74 -0
- data/lib/cc/resolv.rb +39 -0
- data/lib/cc/workspace/exclusion.rb +34 -0
- data/lib/cc/workspace/path_tree/dir_node.rb +67 -0
- data/lib/cc/workspace/path_tree/file_node.rb +31 -0
- data/lib/cc/workspace/path_tree.rb +49 -0
- data/lib/cc/workspace.rb +39 -0
- metadata +279 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
class Config
|
|
3
|
+
module Validation
|
|
4
|
+
class YAML < FileValidator
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def validate
|
|
8
|
+
@data = ::YAML.safe_load(File.read(path))
|
|
9
|
+
|
|
10
|
+
return unless validate_hash_data
|
|
11
|
+
|
|
12
|
+
validate_version
|
|
13
|
+
validate_prepare
|
|
14
|
+
|
|
15
|
+
validate_one_of(%w[engines plugins])
|
|
16
|
+
validate_one_of(%w[exclude_paths exclude_patterns])
|
|
17
|
+
|
|
18
|
+
validate_engines("engines", legacy: true)
|
|
19
|
+
validate_engines("plugins")
|
|
20
|
+
|
|
21
|
+
validate_checks
|
|
22
|
+
|
|
23
|
+
validate_exclude_pattern("exclude_patterns")
|
|
24
|
+
validate_exclude_pattern("exclude_paths", legacy: true)
|
|
25
|
+
|
|
26
|
+
deprecated_key_warnings
|
|
27
|
+
warn_unrecognized_keys(%w[checks prepare engines plugins ratings languages exclude_paths exclude_patterns version])
|
|
28
|
+
rescue Psych::SyntaxError => ex
|
|
29
|
+
errors << "Unable to parse: #{ex.message}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate_version
|
|
33
|
+
if !data.key?("version") && (data.key?("plugins") || data.key?("exclude_patterns"))
|
|
34
|
+
warnings << %(missing 'version' key. Please add `version: "2"`)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def validate_one_of(keys)
|
|
39
|
+
num = keys.map { |k| data.key?(k) }.select(&:present?).count
|
|
40
|
+
if num > 1
|
|
41
|
+
wrapped_keys = keys.map { |k| "'#{k}'" }
|
|
42
|
+
errors << "only use one of #{wrapped_keys.join(", ")}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def deprecated_key_warnings
|
|
47
|
+
deprecate_key("engines", "plugins")
|
|
48
|
+
deprecate_key("exclude_paths", "exclude_patterns")
|
|
49
|
+
deprecate_key("languages")
|
|
50
|
+
deprecate_key("ratings")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def deprecate_key(key, new_key = nil)
|
|
54
|
+
if data.key?(key)
|
|
55
|
+
warnings <<
|
|
56
|
+
if new_key.nil?
|
|
57
|
+
"'#{key}' has been deprecated, and will not be used"
|
|
58
|
+
else
|
|
59
|
+
"'#{key}' has been deprecated, please use '#{new_key}' instead"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
class Config
|
|
3
|
+
class YAMLAdapter
|
|
4
|
+
DEFAULT_PATH = ".codeclimate.yml".freeze
|
|
5
|
+
|
|
6
|
+
attr_reader :config
|
|
7
|
+
|
|
8
|
+
def self.load(path = DEFAULT_PATH)
|
|
9
|
+
new(::YAML.safe_load(File.read(path)))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(yaml = {})
|
|
13
|
+
@config = yaml || {}
|
|
14
|
+
|
|
15
|
+
upconvert_legacy_yaml!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def coerce_engine(data)
|
|
21
|
+
if [true, false].include?(data)
|
|
22
|
+
{ "enabled" => data }
|
|
23
|
+
elsif data.is_a?(Hash)
|
|
24
|
+
data
|
|
25
|
+
else
|
|
26
|
+
{}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Many of our plugins still expect:
|
|
31
|
+
#
|
|
32
|
+
# { config: PATH }
|
|
33
|
+
#
|
|
34
|
+
# But we document, and hope to eventually move to:
|
|
35
|
+
#
|
|
36
|
+
# { config: { file: PATH } }
|
|
37
|
+
#
|
|
38
|
+
# We need to munge from the latter to the former when/if we encounter it
|
|
39
|
+
def convert_to_legacy_file_config(config)
|
|
40
|
+
if config.is_a?(Hash) && config.keys.one? && config.key?("file")
|
|
41
|
+
config["file"]
|
|
42
|
+
else
|
|
43
|
+
config
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def upconvert_legacy_yaml!
|
|
48
|
+
config.delete("ratings")
|
|
49
|
+
|
|
50
|
+
if config.key?("engines")
|
|
51
|
+
config["plugins"] ||= config.delete("engines")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
plugins = config.fetch("plugins", {})
|
|
55
|
+
plugins.each do |engine, data|
|
|
56
|
+
plugins[engine] = coerce_engine(data)
|
|
57
|
+
if plugins.fetch(engine)["exclude_paths"]
|
|
58
|
+
plugins.fetch(engine)["exclude_patterns"] ||= Array(plugins.fetch(engine).delete("exclude_paths"))
|
|
59
|
+
end
|
|
60
|
+
if plugins.fetch(engine)["config"]
|
|
61
|
+
plugins.fetch(engine)["config"] = convert_to_legacy_file_config(
|
|
62
|
+
plugins.fetch(engine).fetch("config"),
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if config.key?("exclude_paths")
|
|
68
|
+
config["exclude_patterns"] ||= Array(config.delete("exclude_paths"))
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/cc/config.rb
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require "cc/config/checks_adapter"
|
|
2
|
+
require "cc/config/default_adapter"
|
|
3
|
+
require "cc/config/engine"
|
|
4
|
+
require "cc/config/engine_set"
|
|
5
|
+
require "cc/config/json_adapter"
|
|
6
|
+
require "cc/config/prepare"
|
|
7
|
+
require "cc/config/validation/hash_validations"
|
|
8
|
+
require "cc/config/validation/check_validator"
|
|
9
|
+
require "cc/config/validation/engine_validator"
|
|
10
|
+
require "cc/config/validation/fetch_validator"
|
|
11
|
+
require "cc/config/validation/file_validator"
|
|
12
|
+
require "cc/config/validation/json"
|
|
13
|
+
require "cc/config/validation/prepare_validator"
|
|
14
|
+
require "cc/config/validation/yaml"
|
|
15
|
+
require "cc/config/yaml_adapter"
|
|
16
|
+
|
|
17
|
+
module CC
|
|
18
|
+
class Config
|
|
19
|
+
attr_reader \
|
|
20
|
+
:analysis_paths,
|
|
21
|
+
:engines,
|
|
22
|
+
:exclude_patterns,
|
|
23
|
+
:prepare
|
|
24
|
+
|
|
25
|
+
attr_writer \
|
|
26
|
+
:development
|
|
27
|
+
|
|
28
|
+
def self.load
|
|
29
|
+
config =
|
|
30
|
+
if File.exist?(JSONAdapter::DEFAULT_PATH)
|
|
31
|
+
JSONAdapter.load.config
|
|
32
|
+
elsif File.exist?(YAMLAdapter::DEFAULT_PATH)
|
|
33
|
+
YAMLAdapter.load.config
|
|
34
|
+
else
|
|
35
|
+
{}
|
|
36
|
+
end
|
|
37
|
+
config = DefaultAdapter.new(config).config
|
|
38
|
+
config = ChecksAdapter.new(config).config
|
|
39
|
+
build(config)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.build(data)
|
|
43
|
+
new(
|
|
44
|
+
engines: EngineSet.new(data.fetch("plugins", {})).engines,
|
|
45
|
+
exclude_patterns: data.fetch("exclude_patterns", DefaultAdapter::EXCLUDE_PATTERNS),
|
|
46
|
+
prepare: Prepare.from_data(data["prepare"]),
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(analysis_paths: [], development: false, engines: [], exclude_patterns: [], prepare: Prepare.new)
|
|
51
|
+
@analysis_paths = analysis_paths
|
|
52
|
+
@development = development
|
|
53
|
+
@engines = engines
|
|
54
|
+
@exclude_patterns = exclude_patterns
|
|
55
|
+
@prepare = prepare
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def merge(other)
|
|
59
|
+
Merge.new(self, other).run
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def development?
|
|
63
|
+
@development
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def disable_plugins!
|
|
67
|
+
@engines.delete_if(&:plugin?)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
class EngineRegistry
|
|
3
|
+
include Enumerable
|
|
4
|
+
|
|
5
|
+
DEFAULT_MEMORY_LIMIT = 1_024_000_000
|
|
6
|
+
DEFAULT_COMMAND = nil
|
|
7
|
+
DEFAULT_MANIFEST_PATH = File.expand_path("../../../config/engines.yml", __FILE__)
|
|
8
|
+
|
|
9
|
+
EngineDetails = Struct.new(:image, :command, :description, :memory)
|
|
10
|
+
EngineDetailsNotFoundError = Class.new(StandardError)
|
|
11
|
+
|
|
12
|
+
def initialize(path = DEFAULT_MANIFEST_PATH, prefix = nil)
|
|
13
|
+
@yaml = YAML.safe_load(File.read(path))
|
|
14
|
+
@prefix = prefix || ENV["CODECLIMATE_PREFIX"] || ""
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def each
|
|
18
|
+
yaml.each do |name, metadata|
|
|
19
|
+
engine = Config::Engine.new(
|
|
20
|
+
name,
|
|
21
|
+
channel: metadata.fetch("channels").keys.first,
|
|
22
|
+
)
|
|
23
|
+
engine_details = fetch_engine_details(engine)
|
|
24
|
+
|
|
25
|
+
yield(engine, engine_details)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fetch_engine_details(engine, development: false)
|
|
30
|
+
if development
|
|
31
|
+
EngineDetails.new("codeclimate/codeclimate-#{engine.name}", nil, "")
|
|
32
|
+
else
|
|
33
|
+
metadata = yaml.fetch(engine.name)
|
|
34
|
+
channels = metadata.fetch("channels")
|
|
35
|
+
|
|
36
|
+
EngineDetails.new(
|
|
37
|
+
[prefix, channels.fetch(engine.channel)].join,
|
|
38
|
+
metadata.fetch("command", DEFAULT_COMMAND),
|
|
39
|
+
metadata.fetch("description", "(No description available)"),
|
|
40
|
+
memory_limit(metadata["minimum_memory_limit"]),
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
rescue KeyError
|
|
44
|
+
raise EngineDetailsNotFoundError, not_found_message(engine, channels)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
attr_reader :yaml, :prefix
|
|
50
|
+
|
|
51
|
+
def memory_limit(minimum_memory_limit)
|
|
52
|
+
[
|
|
53
|
+
minimum_memory_limit.to_i,
|
|
54
|
+
default_memory_limit.to_i,
|
|
55
|
+
].max
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def default_memory_limit
|
|
59
|
+
ENV["ENGINE_MEMORY_LIMIT_BYTES"] || DEFAULT_MEMORY_LIMIT
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def not_found_message(engine, available_channels)
|
|
63
|
+
if available_channels
|
|
64
|
+
# Known engine, unknown channel
|
|
65
|
+
"Channel #{engine.channel} not found" \
|
|
66
|
+
" for #{engine.name}," \
|
|
67
|
+
" available channels: #{available_channels.keys.inspect}"
|
|
68
|
+
else
|
|
69
|
+
# Unknown engine
|
|
70
|
+
"No engine named #{engine.name} found"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/cc/resolv.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "resolv-replace"
|
|
2
|
+
|
|
3
|
+
module CC
|
|
4
|
+
class Resolv
|
|
5
|
+
def self.with_fixed_dns(dns = ::Resolv::DNS.new)
|
|
6
|
+
::Resolv::DefaultResolver.replace_resolvers([Fixed.new(dns)])
|
|
7
|
+
|
|
8
|
+
yield if block_given?
|
|
9
|
+
ensure
|
|
10
|
+
# There's no way to ask what the current values are before we override
|
|
11
|
+
# them; hopefully going by the source is good enough.
|
|
12
|
+
# https://docs.ruby-lang.org/en/2.0.0/Resolv.html#method-c-new
|
|
13
|
+
default_resolvers = [::Resolv::Hosts.new, ::Resolv::DNS.new]
|
|
14
|
+
::Resolv::DefaultResolver.replace_resolvers(default_resolvers)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Fixed
|
|
18
|
+
def initialize(fallback)
|
|
19
|
+
@addresses = {}
|
|
20
|
+
@fallback = fallback
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def each_address(name)
|
|
24
|
+
if addresses.key?(name)
|
|
25
|
+
yield addresses.fetch(name)
|
|
26
|
+
else
|
|
27
|
+
fallback.each_address(name) do |address|
|
|
28
|
+
addresses[name] ||= address
|
|
29
|
+
yield address
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :addresses, :fallback
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
class Workspace
|
|
3
|
+
class Exclusion
|
|
4
|
+
def initialize(pattern)
|
|
5
|
+
@negated = pattern.starts_with?("!")
|
|
6
|
+
@pattern = simplify(pattern)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def expand
|
|
10
|
+
if glob?
|
|
11
|
+
Dir.glob(pattern)
|
|
12
|
+
else
|
|
13
|
+
[pattern]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def glob?
|
|
18
|
+
pattern.include?("*")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def negated?
|
|
22
|
+
negated
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :negated, :pattern
|
|
28
|
+
|
|
29
|
+
def simplify(pattern)
|
|
30
|
+
pattern.to_s.sub(%r{(/\*\*)?(/\*)?$}, "").sub(/^\!/, "")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
class Workspace
|
|
3
|
+
class PathTree
|
|
4
|
+
class DirNode
|
|
5
|
+
def initialize(root_path, children = {})
|
|
6
|
+
@root_path = root_path.dup.freeze
|
|
7
|
+
@children = children.each_with_object({}) do |(k, v), memo|
|
|
8
|
+
memo[k.clone] = v.clone
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def clone
|
|
13
|
+
self.class.new(root_path, children)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def all_paths
|
|
17
|
+
if populated?
|
|
18
|
+
children.values.flat_map(&:all_paths)
|
|
19
|
+
else
|
|
20
|
+
[File.join(root_path, File::SEPARATOR)]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def populated?
|
|
25
|
+
children.present?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def remove(head = nil, *tail)
|
|
29
|
+
return if head.nil? && tail.empty?
|
|
30
|
+
populate_direct_children
|
|
31
|
+
|
|
32
|
+
if (child = children[head])
|
|
33
|
+
child.remove(*tail)
|
|
34
|
+
children.delete(head) if !child.populated? || tail.empty?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add(head = nil, *tail)
|
|
39
|
+
return if head.nil? && tail.empty?
|
|
40
|
+
|
|
41
|
+
if (entry = find_direct_child(head))
|
|
42
|
+
children[entry.basename.to_s.dup.freeze] ||= PathTree.node_for_pathname(entry)
|
|
43
|
+
children[entry.basename.to_s.dup.freeze].add(*tail)
|
|
44
|
+
else
|
|
45
|
+
Analyzer.logger.debug("Couldn't include because part of path doesn't exist: #{File.join(root_path, head)}")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
attr_reader :children, :root_path
|
|
52
|
+
|
|
53
|
+
def populate_direct_children
|
|
54
|
+
return if populated?
|
|
55
|
+
|
|
56
|
+
Pathname.new(root_path).each_child do |child_path|
|
|
57
|
+
children[child_path.basename.to_s] = PathTree.node_for_pathname(child_path)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def find_direct_child(name)
|
|
62
|
+
Pathname.new(root_path).children.detect { |c| c.basename.to_s == name }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
class Workspace
|
|
3
|
+
class PathTree
|
|
4
|
+
class FileNode
|
|
5
|
+
def initialize(root_path)
|
|
6
|
+
@root_path = root_path.dup.freeze
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def all_paths
|
|
10
|
+
[root_path]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def populated?
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def remove(*)
|
|
18
|
+
# this space intentionally left blank
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add(*)
|
|
22
|
+
# this space intentionally left blank
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :root_path
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
require "set"
|
|
3
|
+
|
|
4
|
+
module CC
|
|
5
|
+
class Workspace
|
|
6
|
+
class PathTree
|
|
7
|
+
autoload :DirNode, "cc/workspace/path_tree/dir_node"
|
|
8
|
+
autoload :FileNode, "cc/workspace/path_tree/file_node"
|
|
9
|
+
|
|
10
|
+
def self.node_for_pathname(pathname)
|
|
11
|
+
if pathname.directory?
|
|
12
|
+
DirNode.new(pathname.to_s)
|
|
13
|
+
else
|
|
14
|
+
FileNode.new(pathname.to_s)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.for_path(path)
|
|
19
|
+
new(node_for_pathname(Pathname.new(path)))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(root_node)
|
|
23
|
+
@root_node = root_node
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clone
|
|
27
|
+
self.class.new(root_node.clone)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def exclude_paths(paths)
|
|
31
|
+
paths.each { |path| root_node.remove(*normalized_path_pieces(path)) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def include_paths(paths)
|
|
35
|
+
paths.each { |path| root_node.add(*normalized_path_pieces(path)) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
delegate :all_paths, to: :root_node
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :root_node
|
|
43
|
+
|
|
44
|
+
def normalized_path_pieces(path)
|
|
45
|
+
Pathname.new(path).cleanpath.to_s.split(File::SEPARATOR).reject(&:blank?)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/cc/workspace.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
class Workspace
|
|
3
|
+
autoload :Exclusion, "cc/workspace/exclusion"
|
|
4
|
+
autoload :PathTree, "cc/workspace/path_tree"
|
|
5
|
+
|
|
6
|
+
def initialize(path_tree = PathTree.for_path("."))
|
|
7
|
+
@path_tree = path_tree
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def clone
|
|
11
|
+
self.class.new(path_tree.clone)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def paths
|
|
15
|
+
path_tree.all_paths
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add(paths)
|
|
19
|
+
if paths.present?
|
|
20
|
+
path_tree.include_paths(paths)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def remove(patterns)
|
|
25
|
+
Array(patterns).each do |pattern|
|
|
26
|
+
exclusion = Exclusion.new(pattern)
|
|
27
|
+
if exclusion.negated?
|
|
28
|
+
path_tree.include_paths(exclusion.expand)
|
|
29
|
+
else
|
|
30
|
+
path_tree.exclude_paths(exclusion.expand)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
attr_reader :path_tree
|
|
38
|
+
end
|
|
39
|
+
end
|