forked from github/licensed
-
Notifications
You must be signed in to change notification settings - Fork 0
/
source.rb
111 lines (93 loc) · 3.88 KB
/
source.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# frozen_string_literal: true
module Licensed
module Sources
class Source
class DependencyEnumerationNotImplementedError < StandardError
def initialize(message = "Source classes must implemented `enumerate_dependencies`")
super
end
end
class Error < StandardError; end
class << self
attr_reader :sources
def inherited(klass)
# register the inherited class as a source on the Licensed::Sources::Source class
Licensed::Sources::Source.register_source(klass)
end
def register_source(klass)
# add the source class to the known sources list
return unless klass < Licensed::Sources::Source
(@sources ||= []) << klass
end
# Returns the source name as the first snake cased class or module name
# following "Licensed::Sources::". This is the type that is included
# in metadata files and cache paths.
# e.g. for `Licensed::Sources::Yarn::V1`, this returns "yarn"
def type
type_and_version[0]
end
# Returns the source name as a "/" delimited string of all the module and
# class names following "Licensed::Sources::". This is the type that is
# used to distinguish multiple versions of a sources from each other.
# e.g. for `Licensed::Sources::Yarn::V1`, this returns `yarn/v1`
def full_type
type_and_version.join("/")
end
# Returns an array that includes the source's type name at the first index, and
# optionally a version string for the source as the second index.
# Callers should override this function and not `type` or `full_type` when
# needing to adjust the default type and version parsing logic
def type_and_version
self.name.gsub("#{Licensed::Sources.name}::", "")
.gsub(/([A-Z\d]+)([A-Z][a-z])/, "\\1_\\2".freeze)
.gsub(/([a-z\d])([A-Z])/, "\\1_\\2".freeze)
.downcase
.split("::")
end
# Returns true if the source requires matching reviewed and ignored dependencies'
# versions as well as their name
def require_matched_dependency_version
false
end
end
# all sources have a configuration
attr_accessor :config
def initialize(configuration)
@config = configuration
end
# Returns whether a source is enabled based on the environment in which licensed is run
# Defaults to false.
def enabled?
false
end
# Returns all dependencies that should be evaluated.
# Excludes ignored dependencies.
def dependencies
cached_dependencies
.reject { |d| ignored?(d) }
.each { |d| add_additional_terms_from_configuration(d) }
end
# Enumerate all source dependencies. Must be implemented by each source class.
def enumerate_dependencies
raise DependencyEnumerationNotImplementedError
end
# Returns whether a dependency is ignored in the configuration.
def ignored?(dependency)
config.ignored?(dependency.metadata, require_version: self.class.require_matched_dependency_version)
end
# Returns configuration options set for the current source
def source_config
@source_config ||= config[self.class.type].is_a?(Hash) ? config[self.class.type] : {}
end
private
# Returns a cached list of dependencies
def cached_dependencies
@dependencies ||= enumerate_dependencies.compact
end
# Add any additional_terms for this dependency that have been added to the configuration
def add_additional_terms_from_configuration(dependency)
dependency.additional_terms.concat config.additional_terms_for_dependency("type" => self.class.type, "name" => dependency.name)
end
end
end
end