baltix 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +44 -0
  3. data/.gitignore +10 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +8 -0
  6. data/README.md +60 -0
  7. data/Rakefile +8 -0
  8. data/TODO +84 -0
  9. data/baltix.gemspec +39 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/exe/baltix +4 -0
  13. data/lib/baltix/actor/copy.rb +19 -0
  14. data/lib/baltix/actor/link.rb +20 -0
  15. data/lib/baltix/actor/spec.rb +25 -0
  16. data/lib/baltix/actor/touch.rb +17 -0
  17. data/lib/baltix/actor.rb +75 -0
  18. data/lib/baltix/cli.rb +173 -0
  19. data/lib/baltix/deps.rb +280 -0
  20. data/lib/baltix/dsl.rb +311 -0
  21. data/lib/baltix/extensions.rb +536 -0
  22. data/lib/baltix/i18n.rb +64 -0
  23. data/lib/baltix/loader/cmake.rb +11 -0
  24. data/lib/baltix/loader/git-version-gen.rb +36 -0
  25. data/lib/baltix/loader/mast.rb +139 -0
  26. data/lib/baltix/loader/pom.rb +27 -0
  27. data/lib/baltix/loader/rookbook.rb +26 -0
  28. data/lib/baltix/loader/yaml.rb +18 -0
  29. data/lib/baltix/loader.rb +192 -0
  30. data/lib/baltix/log.rb +73 -0
  31. data/lib/baltix/rake.rb +57 -0
  32. data/lib/baltix/scheme.erb.yaml +20 -0
  33. data/lib/baltix/source/base.rb +438 -0
  34. data/lib/baltix/source/fake.rb +17 -0
  35. data/lib/baltix/source/gem.rb +407 -0
  36. data/lib/baltix/source/gemfile.rb +35 -0
  37. data/lib/baltix/source/rakefile.rb +24 -0
  38. data/lib/baltix/source.rb +57 -0
  39. data/lib/baltix/space/spec.rb +11 -0
  40. data/lib/baltix/space.rb +424 -0
  41. data/lib/baltix/spec/rpm/name.rb +155 -0
  42. data/lib/baltix/spec/rpm/parser.rb +412 -0
  43. data/lib/baltix/spec/rpm/secondary.rb +170 -0
  44. data/lib/baltix/spec/rpm/spec_core.rb +580 -0
  45. data/lib/baltix/spec/rpm.erb +188 -0
  46. data/lib/baltix/spec/rpm.rb +822 -0
  47. data/lib/baltix/spec.rb +48 -0
  48. data/lib/baltix/version.rb +3 -0
  49. data/lib/baltix.rb +19 -0
  50. data/locale/en_US.UTF-8.yaml +27 -0
  51. data/locale/ru_RU.UTF-8.yaml +23 -0
  52. metadata +216 -0
@@ -0,0 +1,424 @@
1
+ require 'rubygems'
2
+
3
+ require 'baltix/version'
4
+ require 'baltix/log'
5
+ require 'baltix/source'
6
+ require 'baltix/spec'
7
+ require 'baltix/cli'
8
+
9
+ class Baltix::Space
10
+ include Baltix::Log
11
+
12
+ class InvalidSpaceFileError < StandardError; end
13
+
14
+ TYPES = {
15
+ sources: Baltix::Source
16
+ }
17
+
18
+ STATES = {
19
+ invalid: ->(_, source, _) { !source.valid? },
20
+ disabled: ->(space, source, _) { space.is_disabled?(source) },
21
+ duplicated: ->(_, _, dup) { dup },
22
+ }
23
+
24
+ TYPE_CHARS = {
25
+ fake: '·',
26
+ gem: '*',
27
+ gemfile: '&',
28
+ rakefile: '^',
29
+ }
30
+
31
+ STATUS_CHARS = {
32
+ invalid: 'X',
33
+ disabled: '-',
34
+ duplicated: '=',
35
+ valid: 'V',
36
+ }
37
+
38
+
39
+ Gem.load_yaml
40
+
41
+ @@space = {}
42
+ @@options = {}
43
+
44
+ # +options+ property returns the hash of the loaded options if any
45
+ #
46
+ # options #=> {...}
47
+ #
48
+ attr_reader :options, :state
49
+
50
+ # +name+ returns a default name of the space with a prefix if any. It returns name of a source when
51
+ # its root is the same as the space's root, or returns name defined in the spec if any.
52
+ # If no spec defined returns name of last folder in rootdir or "root" as a default main source name.
53
+ #
54
+ # space.name # => space-name
55
+ #
56
+ def name
57
+ return @name if @name
58
+
59
+ @name = spec&.name || main_source&.name
60
+ end
61
+
62
+ # +version+ returns a default version for the space. Returns version of a source when
63
+ # its root is the same as the space's root, or returns version defined in the spec if any,
64
+ # or returns default one.
65
+ #
66
+ # space.version # => 2.1.1
67
+ #
68
+ def version
69
+ return @version if @version
70
+
71
+ @version ||= main_source&.version || spec&.version
72
+ end
73
+
74
+ attr_writer :rootdir
75
+
76
+ # +rootdir+ returns the root dir for the space got from the options,
77
+ # defaulting to the current folder.
78
+ #
79
+ # rootdir #=> /root/dir/for/the/space
80
+ #
81
+ def rootdir
82
+ @rootdir ||= read_attribute(:rootdir) || Dir.pwd
83
+ end
84
+
85
+ # +main_source+ selects main source from the list of sources when source root dir is the space's one and
86
+ # source's name contains minimum name size among the matching sources
87
+ #
88
+ def main_source
89
+ return @main_source if @main_source
90
+
91
+ reals = valid_sources.select { |source| !source.is_a?(Baltix::Source::Fake) }
92
+ if spec && !spec.state.blank?
93
+ specen_source = reals.find { |real| spec.state["name"] === real.name }
94
+ end
95
+
96
+ root_source ||= valid_sources.sort {|x, y| x.name.size <=> y.name.size }.find { |source| source.rootdir == rootdir }
97
+ @main_source =
98
+ specen_source || root_source.is_a?(Baltix::Source::Fake) && reals.size == 1 && reals.first || root_source
99
+ end
100
+
101
+ def time_stamp
102
+ Time.now.strftime("%Y%m%d")
103
+ end
104
+
105
+ # +changes+ returns a list of open-struct formatted changes in the space or
106
+ # spec defined if any, otherwise returns blank array.
107
+ #
108
+ # space.changes # => []
109
+ #
110
+ def changes
111
+ @changes ||= spec&.changes || main_source&.respond_to?(:changes) && main_source.changes || []
112
+ end
113
+
114
+ # +summaries+ returns an open-struct formatted summaries with locales as keys
115
+ # in the space or spec defined if any, otherwise returns blank open struct.
116
+ #
117
+ # space.summaries # => #<OpenStruct en_US.UTF-8: ...>
118
+ #
119
+ def summaries
120
+ return @summaries if @summaries
121
+
122
+ if summaries = spec&.summaries || state.summaries
123
+ summaries
124
+ elsif summary = main_source&.summary
125
+ { Baltix::I18n.default_locale => summary }.to_os
126
+ end
127
+ end
128
+
129
+ # +licenses+ returns license list defined in all the valid sources found in the space.
130
+ #
131
+ # space.licenses => # ["MIT"]
132
+ #
133
+ def licenses
134
+ return @licenses if @licenses
135
+
136
+ licenses = valid_sources.map { |source| source.licenses rescue [] }.flatten.uniq
137
+
138
+ @licenses = !licenses.blank? && licenses || spec&.licenses || []
139
+ end
140
+
141
+ # +dependencies+ returns all the valid source dependencies list as an array of Gem::Dependency
142
+ # objects, otherwise returning blank array.
143
+ def dependencies
144
+ @dependencies ||= valid_sources.map do |source|
145
+ source.respond_to?(:dependencies) && source.dependencies || []
146
+ end.flatten.reject do |dep|
147
+ match_platform?(dep) || sources.any? do |s|
148
+ dep.name == s.name &&
149
+ dep.requirement.satisfied_by?(Gem::Version.new(s.version))
150
+ end
151
+ end
152
+ end
153
+
154
+ def match_platform? dep
155
+ if dep.respond_to?(:platforms)
156
+ (dep.platforms & options.skip_platforms).any?
157
+ end
158
+ end
159
+
160
+ def files
161
+ @files ||= valid_sources.map { |s| s.files rescue [] }.flatten.uniq
162
+ end
163
+
164
+ def executables
165
+ @executables ||= valid_sources.map { |s| s.executables rescue [] }.flatten.uniq
166
+ end
167
+
168
+ def docs
169
+ @docs ||= valid_sources.map { |s| s.docs rescue [] }.flatten.uniq
170
+ end
171
+
172
+ def compilables
173
+ @compilables ||= valid_sources.map { |s| s.extensions rescue [] }.flatten.uniq
174
+ end
175
+
176
+ # +sources+ returns all the sources in the space. It will load from the space sources,
177
+ # or by default will search sources in the provided folder or the current one.
178
+ #
179
+ # space.sources => # [#<Baltix::Source:...>, #<...>]
180
+ #
181
+ def sources
182
+ @sources ||= stat_sources.map { |x| x.first }
183
+ end
184
+
185
+ # +valid_sources+ returns all the valid sources based on the current source list.
186
+ #
187
+ # space.valid_sources => # [#<Baltix::Source:...>, #<...>]
188
+ #
189
+ # def valid_sources
190
+ # @valid_sources ||= sources.select do |source|
191
+ # source.valid? && is_regarded?(source)
192
+ # end
193
+ # end
194
+ def valid_sources
195
+ @valid_sources = stat_sources.map {|(source, status)| status == :valid && source || nil }.compact
196
+ end
197
+
198
+ # def is_regarded? source
199
+ # regarded_names.any? {|i| i === source.name } ||
200
+ # !ignored_names.any? {|i| i === source.name } &&
201
+ # !ignored_path_tokens.any? {|t| /\/#{t}\// =~ source.source_file }
202
+ # end
203
+
204
+ def ignored_names
205
+ @ignored_names ||= (read_attribute(:ignored_names) || [])
206
+ end
207
+
208
+ def regarded_names
209
+ @regarded_names ||= read_attribute(:regarded_names) || []
210
+ end
211
+
212
+ def ignored_path_tokens
213
+ @ignored_path_tokens ||= (read_attribute(:ignored_path_tokens) || [])
214
+ end
215
+
216
+ def spec_type
217
+ @spec_type ||= read_attribute(:spec_type) || spec && spec.class.to_s.split("::").last.downcase
218
+ end
219
+
220
+ def read_attribute attr
221
+ options.send(attr) || state.send(attr)
222
+ end
223
+
224
+ def options_for type
225
+ @@options[type] = type::OPTIONS.map do |option|
226
+ value = self.options[option] || self.respond_to?(option) && self.send(option) || nil
227
+
228
+ [ option, value ]
229
+ end.compact.to_os
230
+ end
231
+
232
+ # +spec+ property returns the hash of the loaded spec if any, it can be freely
233
+ # reassigned.
234
+ #
235
+ # spec #=> {...}
236
+ #
237
+ def spec
238
+ @spec ||= gen_spec
239
+ end
240
+
241
+ def spec= value
242
+ gen_spec(value)
243
+ end
244
+
245
+ def is_disabled? source
246
+ options.ignored_path_tokens.any? { |t| /\/#{t}\// =~ source.source_file } ||
247
+ options.regarded_names.all? { |i| !i.match?(source.name) } &&
248
+ options.ignored_names.any? { |i| i === source.name }
249
+ end
250
+
251
+ protected
252
+
253
+ def gen_spec spec_in = nil
254
+ spec_pre = spec_in || state.spec
255
+
256
+ @spec =
257
+ if spec_pre.is_a?(Baltix::Spec::Rpm)
258
+ spec_pre
259
+ elsif spec_pre.is_a?(String)
260
+ self.class.load(spec_pre)
261
+ elsif options&.spec_file
262
+ Baltix::Spec.load_from(source: IO.read(options.spec_file), options: options, space: self)
263
+ elsif @spec_type || options&.spec_type
264
+ Baltix::Spec.find(@spec_type || options.spec_type).new(options: options, space: self)
265
+ end
266
+
267
+ @spec.assign_space(self) if @spec
268
+
269
+ @spec
270
+ end
271
+
272
+ def initialize state_in = {}, options = {}
273
+ @options = Baltix::CLI::DEFAULT_OPTIONS.merge(options || {})
274
+ @state = (state_in || {}).to_os
275
+
276
+ baltix_log
277
+ end
278
+
279
+ # init log
280
+ def baltix_log
281
+ ios = DEFAULT_IO_NAMES.merge(%i(error warn info debug).map {|k| [k, options["#{k}_io"]]}.to_h)
282
+ Baltix::Log.setup(options.log_level.to_sym, ios)
283
+ end
284
+
285
+ def show_tree
286
+ info("Sources:")
287
+ stat_source_tree.each do |(path_in, stated_sources)|
288
+ stated_sources.each do |(source, status)|
289
+ path = source.source_path_from(rootdir) if source.source_file
290
+ stat = [STATUS_CHARS[status], TYPE_CHARS[source.type.to_sym]].join(" ")
291
+ namever = [source.name, source.version].compact.join(":")
292
+ info_in = "#{stat}#{namever} [#{path}]"
293
+
294
+ info(info_in)
295
+ end
296
+ end
297
+ end
298
+
299
+ # returns all the sources with their statuses, and sorted by a its rootdir value
300
+ #
301
+ def stat_sources &block
302
+ return @stat_sources if @stat_sources
303
+
304
+ @stat_sources =
305
+ read_attribute(:stat_sources) || Baltix::Source.search_in(rootdir, options).group_by do |x|
306
+ [x.name, x.version].compact.join(":")
307
+ end.map do |(full_name, v)|
308
+ sorten =
309
+ v.sort do |x,y|
310
+ c0 = ::Baltix::Source.loaders.index(x.loader) <=> ::Baltix::Source.loaders.index(y.loader)
311
+ c1 = c0 == 0 && x.name <=> y.name || c0
312
+ c2 = c1 == 0 && y.version <=> x.version || c1
313
+ c3 = c2 == 0 && y.platform <=> x.platform || c2
314
+ c4 = c3 == 0 && y.source_names.grep(/gemspec/).count <=> x.source_names.grep(/gemspec/).count
315
+
316
+ c2 == 0 && c3 == 0 && c4 == 0 && x.rootdir.size <=> y.rootdir.size || c4 != 0 && c4 || c3 != 0 && c3 || c2
317
+ end.reduce([[], 0]) do |(res, index), source|
318
+ dup = source.valid? && index > 0
319
+ dup_index = source.valid? && index + 1 || index
320
+
321
+ [res | [[source, source_status(source, dup)]], dup_index]
322
+ end.first
323
+
324
+ sorten[1..-1].each { |x| sorten.first.first.alias_to(x.first) }
325
+
326
+ sorten
327
+ end.flatten(1).sort_by {|(x, _)| x.rootdir.size }.each do |(source, status)|
328
+ block[source, status] if block_given?
329
+ end
330
+
331
+ show_tree
332
+
333
+ @stat_sources
334
+ end
335
+
336
+ # returns source tree, and sorted, and then grouped by a its rootdir value
337
+ #
338
+ def stat_source_tree
339
+ @stat_source_tree ||=
340
+ stat_sources.group_by {|(x, _)| x.rootdir }.map do |(path, sources)|
341
+ [File.join('.', path[rootdir.size..-1] || ''), sources]
342
+ end.to_h
343
+ end
344
+
345
+ # returns status for the source for the project
346
+ #
347
+ def source_status source, dup
348
+ %i(valid duplicated disabled invalid).reduce() do |res, status|
349
+ STATES[status][self, source, dup] && status || res
350
+ end
351
+ end
352
+
353
+ def context
354
+ @context ||= options[:context] || spec&.context || {}
355
+ end
356
+
357
+ def method_missing method, *args
358
+ value =
359
+ instance_variable_get(:"@#{method}") ||
360
+ (spec.send(method) rescue nil) ||
361
+ options&.[](method) ||
362
+ spec&.options&.[](method.to_s) ||
363
+ state[method]
364
+
365
+ instance_variable_set(:"@#{method}", value || super)
366
+
367
+ value
368
+ end
369
+
370
+ class << self
371
+ def load string
372
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("4.0.0")
373
+ YAML.load(string,
374
+ aliases: true,
375
+ permitted_classes: [
376
+ Baltix::Source::Fake,
377
+ Baltix::Source::Rakefile,
378
+ Baltix::Source::Gemfile,
379
+ Baltix::Source::Gem,
380
+ Baltix::Spec::Rpm,
381
+ Baltix::Spec::Rpm::Name,
382
+ Baltix::Spec::Rpm::Secondary,
383
+ Gem::Specification,
384
+ Gem::Version,
385
+ Gem::Dependency,
386
+ Gem::Requirement,
387
+ OpenStruct,
388
+ Symbol,
389
+ Time,
390
+ Date
391
+ ])
392
+ else
393
+ YAML.load(string)
394
+ end
395
+ end
396
+
397
+ def load_from! state: Dir[".space"].first, options: {}
398
+ # system_path_check # TODO required to generate spec rubocop
399
+
400
+ state_tmp =
401
+ case state
402
+ when IO, StringIO
403
+ load(state.readlines.join(""))
404
+ when String
405
+ raise InvalidSpaceFileError.new(state: state) if !File.file?(state)
406
+
407
+ load(IO.read(state))
408
+ when NilClass
409
+ else
410
+ raise InvalidSpaceFileError
411
+ end.to_os
412
+
413
+ @@space[state_tmp.name] = self.new(state_tmp, options)
414
+ end
415
+
416
+ def load_from state: Dir[".space"].first, options: {}
417
+ load_from!(state: state, options: options)
418
+ rescue InvalidSpaceFileError
419
+ @@space[nil] = new(nil, options)
420
+ end
421
+ end
422
+ end
423
+
424
+ require 'baltix/space/spec'
@@ -0,0 +1,155 @@
1
+ class Baltix::Spec::Rpm::Name
2
+ class InvalidAdoptedNameError < StandardError; end
3
+ class UnsupportedMatchError < StandardError; end
4
+
5
+ PREFICES = %w(gem ruby rubygem)
6
+ RULE = /^(?<full_name>(?:(?<prefix>#{PREFICES.join('|')})-)?(?<name>.*?))(?:-(?<suffix>doc|devel))?$/
7
+
8
+ attr_reader :name, :kind, :suffix
9
+ attr_accessor :support_name
10
+
11
+ def aliases
12
+ @aliases ||= []
13
+ end
14
+
15
+ def prefix
16
+ @prefix ||= kind == "lib" && self.class.default_prefix || nil
17
+ end
18
+
19
+ def self.default_prefix
20
+ "gem"
21
+ end
22
+
23
+ def support_name= value
24
+ case @support_name = value
25
+ when NilClass
26
+ @kind = kind == "exec" && "app" || @kind
27
+ else
28
+ @kind = kind == "app" && "exec" || @kind
29
+ end
30
+ end
31
+
32
+ def eql? other, deep = false
33
+ case other
34
+ when self.class
35
+ self.eql_by?(:kind, other) && self.eql_by?(:name, other)
36
+ when String, Symbol
37
+ (([ autoname, fullname ] | [ aliases ].flatten) & self.class.parse(other).aliases).any?
38
+ else
39
+ other.to_s == self.fullname
40
+ end || deep && self.eql_by?(:support_name, other)
41
+ end
42
+
43
+ def == other
44
+ eql?(other)
45
+ end
46
+
47
+ def === other
48
+ eql?(other)
49
+ end
50
+
51
+ def =~ re
52
+ to_s =~ re
53
+ end
54
+
55
+ def to_s
56
+ fullname
57
+ end
58
+
59
+ def merge other
60
+ options =
61
+ %w(prefix suffix name support_name kind).map do |prop|
62
+ [ prop.to_sym, other.send(prop) || self.send(prop) ]
63
+ end.to_h
64
+
65
+ self.class.new(options.merge(aliases: self.aliases | other.aliases))
66
+ end
67
+
68
+ def eql_by? value, other
69
+ case value
70
+ when :name
71
+ ([ self.name, self.aliases ].flatten & [ other.name, other.aliases ].flatten).any?
72
+ when :kind
73
+ self.kind == other.kind
74
+ when :support_name
75
+ self.support_name === (other.is_a?(self.class) && other.support_name || other)
76
+ else
77
+ raise(UnsupportedMatchError.new)
78
+ end
79
+ end
80
+
81
+ # +fullname+ returns newly reconstructed adopted full name based on the storen data.
82
+ # All the "." and "_" is replaced with "-", and "ruby" prefix with "gem".
83
+ #
84
+ # name.fullname #=> "gem-foo-bar-baz-doc"
85
+ #
86
+ def fullname
87
+ [ autoprefix, autoname, autosuffix ].compact.join("-")
88
+ end
89
+
90
+ def original_fullname
91
+ [ prefix, name, suffix ].compact.join("-")
92
+ end
93
+
94
+ def autoprefix
95
+ %w(exec app).include?(kind) || %w(app).include?(support_name&.kind) ? nil : self.class.default_prefix
96
+ end
97
+
98
+ def autoname
99
+ name&.downcase&.gsub(/[\._]/, "-")
100
+ end
101
+
102
+ def autosuffix
103
+ %w(doc devel).include?(kind) && kind || nil
104
+ end
105
+
106
+ protected
107
+
108
+ def initialize options = {}
109
+ @aliases = options.fetch(:aliases, []) | options.fetch(:name, "").gsub(/[\.\_]+/, "-").split(",")
110
+ @prefix = options[:prefix]
111
+ @suffix = options[:suffix]
112
+ @name = options[:name]
113
+ @support_name = options[:support_name]
114
+ @kind = options[:kind] && options[:kind].to_s ||
115
+ @suffix ||
116
+ @prefix && "lib" ||
117
+ @support_name && "exec" || "app"
118
+ end
119
+
120
+ class << self
121
+ def parse name_in, options_in = {}
122
+ m, kind =
123
+ if name_in.is_a?(self)
124
+ [name_in.original_fullname.match(RULE), name_in.kind]
125
+ else
126
+ [name_in.match(RULE)]
127
+ end
128
+
129
+ aliases_in = (options_in[:aliases] || []).flatten.uniq
130
+ subaliases = aliases_in - [ m["full_name"] ]
131
+ #aliases = subaliases | [ m["full_name"] ]
132
+
133
+ raise(InvalidAdoptedNameError) if !m
134
+
135
+ prefixed = subaliases.size >= aliases_in.size
136
+ options = {
137
+ prefix: prefixed && m["prefix"] || nil,
138
+ #prefix: subaliases.blank? && m["prefix"] || nil,
139
+ #prefix: m["prefix"],
140
+ suffix: m["suffix"],
141
+ kind: kind,
142
+ #name: m["name"],
143
+ name: prefixed && m["name"] || m["full_name"],
144
+ #name: subaliases.blank? && m["name"] || m["full_name"],
145
+ }.merge(options_in).merge({
146
+ aliases: subaliases | [ m["full_name"] ]
147
+ })
148
+
149
+ options[:name] = options[:name].blank? && options[:aliases].first || options[:name]
150
+ #binding.pry if name_in =~ /ruby/
151
+
152
+ new(options)
153
+ end
154
+ end
155
+ end