grouik 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require 'benchmark'
10
+ require 'pathname'
11
+ require 'ostruct'
12
+
13
+ require 'grouik/concerns'
14
+
15
+ # Main class loader
16
+ #
17
+ # loads files during ``Grouik::Loader#load_all``
18
+ class Grouik::Loader
19
+ attr_accessor :basedir
20
+ attr_accessor :ignores
21
+ attr_reader :paths
22
+
23
+ attr_reader :attempts
24
+ attr_reader :errors
25
+ attr_reader :stats
26
+
27
+ include Grouik::Concerns::Helpable
28
+
29
+ def initialize
30
+ self.basedir = '.'
31
+ self.ignores = []
32
+
33
+ @loadeds = []
34
+ @errors = {}
35
+ @loadables = []
36
+ @attempts = 0
37
+ @stats = nil
38
+
39
+ return self unless block_given?
40
+
41
+ yield self
42
+ register
43
+ end
44
+
45
+ # @param [Array<String|Regexp>] ignores
46
+ def ignores=(ignores)
47
+ @ignores = helpers.get(:loader).make_ignores(ignores)
48
+ end
49
+
50
+ # @param [Array<String>] paths
51
+ def paths=(paths)
52
+ @paths = paths.to_a.map { |path| Pathname.new(path.to_s) }
53
+ end
54
+
55
+ # @return [Array<Pathname>]
56
+ def paths
57
+ (@paths || (@paths.empty? ? ['.'] : @paths)).clone
58
+ end
59
+
60
+ # Register paths
61
+ #
62
+ # @return [self]
63
+ def register
64
+ helpers.get(:loader).register_paths(basedir, @paths)
65
+
66
+ self
67
+ end
68
+
69
+ # Get loadables
70
+ #
71
+ # @return [Array<Grouik::Loadable>]
72
+ def loadables
73
+ if @loadables.empty?
74
+ self.basedir do
75
+ @loadables = helpers.get(:loader)
76
+ .collect_loadables(paths)
77
+ .ignores(ignores)
78
+ end
79
+ end
80
+
81
+ @loadables.clone
82
+ end
83
+
84
+ # @return [Pathname]
85
+ def basedir
86
+ Dir.chdir(helpers.get(:loader).pwd.join(basedir)) { yield } if block_given?
87
+
88
+ Pathname.new(@basedir)
89
+ end
90
+
91
+ # @return [self]
92
+ def load_all
93
+ return @loadeds.clone unless @loadeds.empty?
94
+
95
+ loadables = self.loadables
96
+ @loadeds = []
97
+ @errors = {}
98
+
99
+ @stats = Benchmark.measure do
100
+ loop do
101
+ loadables = process_loadables(loadables)
102
+ break if loadables.nil? or (loadables and loadables.empty?)
103
+ end
104
+ end
105
+ @loadeds.compact
106
+ self
107
+ end
108
+
109
+ # Format using a formatter
110
+ #
111
+ # @param [Hash] options
112
+ # @return [String]
113
+ def format(options = {})
114
+ Grouik.get(:formatter).format(load_all.loadables, options).to_s
115
+ end
116
+
117
+ def loaded?
118
+ self.loadables.size == @loadeds.size
119
+ end
120
+
121
+ # @return [Fixnum]
122
+ def attempts_maxcount
123
+ (self.loadables.size**2) + 1
124
+ end
125
+
126
+ protected
127
+
128
+ def process_loadables(processables)
129
+ processables.each_with_index do |loadable, index|
130
+ return [] if attempts >= attempts_maxcount or processables.empty?
131
+
132
+ @attempts += 1
133
+ loaded = nil
134
+ begin
135
+ loaded = loadable.load(helpers.get(:loader).pwd.join(basedir))
136
+ rescue StandardError => e
137
+ unless @errors[loadable.to_s]
138
+ @errors[loadable.to_s] = OpenStruct.new(
139
+ source: loadable.to_s,
140
+ message: e.message.lines[0].strip.freeze,
141
+ line: e.backtrace[0].split(':')[1],
142
+ error: e
143
+ ).freeze
144
+ end
145
+ next
146
+ end
147
+
148
+ next if loaded.nil?
149
+
150
+ @loadeds.push(processables.delete_at(index))
151
+ # when loadable is loaded, then error is removed
152
+ @errors.delete(loadable.to_s)
153
+ end
154
+ processables
155
+ end
156
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ # Describe a message (sent on a IO as STDOUT/STDERR)
10
+ class Grouik::Output::Message
11
+ attr_accessor :content
12
+ attr_accessor :type
13
+ attr_accessor :stream
14
+
15
+ def initialize
16
+ yield self if block_given?
17
+ end
18
+
19
+ # @param content [String]
20
+ def content=(content)
21
+ @content = content.to_s.empty? ? nil : content.to_s
22
+ end
23
+
24
+ # @param stream [IO]
25
+ def stream=(stream)
26
+ @stream = stream.clone
27
+ end
28
+
29
+ # @return [IO]
30
+ def stream
31
+ @stream || STDOUT.clone
32
+ end
33
+
34
+ # @raise [RuntimeError]
35
+ # @return [self]
36
+ def send
37
+ attrs = [:stream, :content, :type]
38
+ attrs.each do |attr|
39
+ raise 'attributes %s must be set' % attrs if public_send(attr).nil?
40
+ end
41
+
42
+ messager_class.new(stream, content.to_s).output(type)
43
+ self
44
+ end
45
+
46
+ protected
47
+
48
+ # @return [Grouik::Output::Messager]
49
+ def messager_class
50
+ require '%s/messager' % __dir__
51
+
52
+ Grouik::Output::Messager
53
+ end
54
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ # Provide a wrapper over ``Rainbow``
10
+ class Grouik::Output::Messager
11
+ attr_accessor :stream
12
+ attr_accessor :content
13
+
14
+ def initialize(stream = STDOUT, content = nil)
15
+ @stream = stream
16
+ self.content = content
17
+ end
18
+
19
+ def content=(content)
20
+ @content = content.to_s
21
+ end
22
+
23
+ # @todo Catch ``NoMethodError`` or similar error
24
+ # @return [self]
25
+ def output(type)
26
+ stream.puts self.format_as(type)
27
+
28
+ self
29
+ end
30
+
31
+ class << self
32
+ # @return [self]
33
+ def output(stream, type)
34
+ self.new(stream, content).output(type)
35
+ end
36
+
37
+ # Defines available formats
38
+ #
39
+ # @return [Hash]
40
+ def formats
41
+ {
42
+ status_success: {
43
+ background: :green,
44
+ color: :black,
45
+ },
46
+ status_failure: {
47
+ background: :red,
48
+ color: :black,
49
+ },
50
+ error: {
51
+ color: :red,
52
+ }
53
+ }
54
+ end
55
+ end
56
+
57
+ # @param type [Symbol|String]
58
+ # @return [Rainbow::Presenter, Rainbow::NullPresenter]
59
+ def format_as(type)
60
+ colorize(self.class.formats[type.to_sym])
61
+ end
62
+
63
+ # Denote output is colorizable (``tty?``)
64
+ def colorizable?
65
+ stream.tty?
66
+ end
67
+
68
+ protected
69
+
70
+ # Get a colorizable (almost a wrapper over ``Rainbow``) content instance
71
+ #
72
+ # @return [Rainbow::Presenter, Rainbow::NullPresenter]
73
+ def colorizable
74
+ require 'rainbow'
75
+
76
+ Rainbow.enabled = colorizable?
77
+ Rainbow::Wrapper.new(colorizable?).wrap(content)
78
+ end
79
+
80
+ def colorize(format = {})
81
+ colorizable = self.colorizable.clone
82
+ format.to_h.each { |k, v| colorizable = colorizable.public_send(k, v) }
83
+
84
+ colorizable
85
+ end
86
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ module Grouik::Output
10
+ [:message].each do |req|
11
+ require '%s/output/%s' % [__dir__, req]
12
+ end
13
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require 'pathname'
10
+
11
+ require 'grouik/concerns'
12
+
13
+ # Intended to wrap ``Grouik::Loader``
14
+ #
15
+ # Easify reusability through ``Grouik::Cli``
16
+ # or as standalone (for example in a Rake task)
17
+ #
18
+ # Sample of use:
19
+ #
20
+ # ~~~~
21
+ # task 'src/ceres.rb' do
22
+ # require 'grouik'
23
+ #
24
+ # Grouik.process do |process|
25
+ # process.verbose = false
26
+ # process.paths = ['lib']
27
+ # process.basedir = 'src'
28
+ # process.output = 'src/ceres.rb'
29
+ # process.template = 'src/ceres.tpl'
30
+ # end.on_failure { exit Errno::ECANCELED::Errno }
31
+ # end
32
+ # ~~~~
33
+ class Grouik::Process
34
+ attr_accessor :template
35
+ attr_accessor :bootstrap
36
+ attr_accessor :verbose
37
+ attr_reader :output
38
+
39
+ include Grouik::Concerns::Helpable
40
+
41
+ def initialize
42
+ @output = nil
43
+ @template = nil
44
+ @bootstrap = nil
45
+ @verbose = false
46
+
47
+ @loader = Grouik::Loader.new
48
+ yield self if block_given?
49
+ @loader.register
50
+ end
51
+
52
+ def verbose?
53
+ !!@verbose
54
+ end
55
+
56
+ # @param [Pathname|String] output
57
+ # @return [Object]
58
+ def output=(output)
59
+ @output = output.is_a?(String) ? Pathname.new(output) : output
60
+ end
61
+
62
+ # @return [self]
63
+ # @todo Do not suppress exceptions
64
+ def process
65
+ @output.write('') if @output.respond_to?(:file?) and !@output.exist?
66
+ if bootstrap
67
+ begin
68
+ require bootstrap if bootstrap
69
+ rescue NameError
70
+ rescue LoadError
71
+ end
72
+ end
73
+
74
+ output = @loader.format(template: @template)
75
+ display_errors
76
+ # avoid to break previouly written file in case of failure
77
+ @output.write(output) if success?
78
+
79
+ self
80
+ end
81
+
82
+ # @param [Hash] options
83
+ def format(options = {})
84
+ @loader.format(options)
85
+ end
86
+
87
+ # Errors encountered during process
88
+ #
89
+ # @return [Hash]
90
+ def errors
91
+ loader.errors
92
+ end
93
+
94
+ # Denote errors encountered
95
+ #
96
+ # @return [Boolean]
97
+ def errors?
98
+ errors.empty? ? false : true
99
+ end
100
+
101
+ # Display encountered errors
102
+ #
103
+ # @return [self]
104
+ def display_errors
105
+ helpers.get(:process).display_errors(self)
106
+
107
+ self
108
+ end
109
+
110
+ # Display status
111
+ #
112
+ # @return [self]
113
+ def display_status
114
+ helpers.get(:process).display_status(self)
115
+
116
+ self
117
+ end
118
+
119
+ # Denote process is a success
120
+ #
121
+ # @return [Boolean]
122
+ def success?
123
+ loader.loaded? and !errors?
124
+ end
125
+
126
+ # Denote process is a failure
127
+ #
128
+ # @return [Boolean]
129
+ def failure?
130
+ !success?
131
+ end
132
+
133
+ # Block executed on failure
134
+ #
135
+ # @yield [self] Block executed when errors have been encountered
136
+ # @return [self]
137
+ def on_failure
138
+ yield(self) if failure?
139
+
140
+ self
141
+ end
142
+
143
+ # Block executed on success
144
+ #
145
+ # @yield [self] Block executed when process is a success
146
+ # @return [self]
147
+ def on_success
148
+ yield(self) if success?
149
+
150
+ self
151
+ end
152
+
153
+ # Provides access to public accessors
154
+ def method_missing(method, *args, &block)
155
+ if respond_to_missing?(method)
156
+ @loader.public_send(method, *args, &block)
157
+ else
158
+ super
159
+ end
160
+ end
161
+
162
+ def respond_to_missing?(method, include_private = false)
163
+ result = loader_accessors.include?(method.to_sym)
164
+ unless result
165
+ return super if include_private
166
+ end
167
+
168
+ result
169
+ end
170
+
171
+ # Get loader
172
+ #
173
+ # @return [Grouik::Loader]
174
+ def loader
175
+ @loader.clone.freeze
176
+ end
177
+
178
+ protected
179
+
180
+ # Get loader public attributes
181
+ #
182
+ # @return [Array]
183
+ def loader_attributes
184
+ loader.public_methods
185
+ .grep(/^\w+=$/)
186
+ .map { |m| m.to_s.gsub(/=$/, '').to_sym }
187
+ end
188
+
189
+ # Get loader public accessors
190
+ #
191
+ # @return [Array]
192
+ def loader_accessors
193
+ loader_attributes + loader_attributes.map { |m| ('%s=' % m).to_sym }
194
+ end
195
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ # Special types in use
10
+ module Grouik::Types
11
+ end
12
+
13
+ # Get filtered loadables, using ignores regexp
14
+ #
15
+ # @return [Array<Grouik::Loadable>]
16
+ class Grouik::Types::Loadables < Array
17
+ # Removes ignored patterns (regexps)
18
+ #
19
+ # @param [Array<Regexp>] regexps
20
+ # @return [self]
21
+ def ignores(regexps)
22
+ filter = lambda do |loadable, regexps|
23
+ regexps.each do |regexp|
24
+ return true if loadable and regexp.match(loadable.to_s)
25
+ end
26
+
27
+ false
28
+ end
29
+
30
+ self.clone.delete_if do |loadable|
31
+ filter.call(loadable, regexps)
32
+ end
33
+ end
34
+
35
+ # @return [self]
36
+ def add_file(file, basedir = nil)
37
+ self.push(make_loadable(basedir, file))
38
+
39
+ self
40
+ end
41
+
42
+ # @return [Grouik::Loadable]
43
+ def make_loadable(*args)
44
+ Grouik.get(:loadable_factory).call(*args)
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ ---
2
+ major: 1
3
+ minor: 0
4
+ patch: 0
5
+ authors: ['Dimitri Arrigoni']
6
+ email: 'dimitri@arrigoni.me'
7
+ date: '2017-03-28'
8
+ summary: 'Grouik!'
9
+ description: 'Simple require file generator'
10
+ licenses: ['GPL-3.0']
11
+ license: 'Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
12
+ License GPLv3+: GNU GPL version 3 or later
13
+ <http://www.gnu.org/licenses/gpl.html>.
14
+ This is free software: you are free to change and redistribute it.
15
+ There is NO WARRANTY, to the extent permitted by law.'
16
+ homepage: 'https://github.com/SwagDevOps/ruby-grouik'
data/src/lib/grouik.rb ADDED
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require 'pathname'
10
+ require 'active_support/inflector'
11
+ require 'version_info'
12
+
13
+ $LOAD_PATH.unshift Pathname.new(__dir__)
14
+
15
+ # Produce a ``require`` file, resolving classes dependencies
16
+ #
17
+ # Sample of use:
18
+ #
19
+ # ~~~~
20
+ # Grouik.process do |process|
21
+ # process.basedir = './path/to/my/project'
22
+ # process.paths = ['lib']
23
+ # process.ignores = []
24
+ # process.output = '/dev/stdout'
25
+ # process.template = 'lib/main'
26
+ # process.bootstrap = nil
27
+ # process.verbose = true
28
+ # end.success? ? 0 : 1
29
+ # ~~~~
30
+ #
31
+ # or using command line.
32
+ module Grouik
33
+ require 'grouik/helpers'
34
+
35
+ class << self
36
+ # @return [Hash]
37
+ def version_info
38
+ unless self.const_defined?(:VERSION)
39
+ include VersionInfo
40
+
41
+ VersionInfo.file_format = :yaml
42
+ VERSION.file_name = self.version_filepath
43
+ VERSION.load
44
+ end
45
+
46
+ VERSION.to_hash.freeze
47
+ end
48
+
49
+ protected
50
+
51
+ # Get path to the ``version`` file
52
+ #
53
+ # @return [Pathname]
54
+ def version_filepath
55
+ name = ActiveSupport::Inflector.underscore(self.name)
56
+
57
+ Pathname.new(__dir__).join(name, 'version_info.yml')
58
+ end
59
+ end
60
+
61
+ # registers version_info
62
+ self.version_info
63
+ # loads sub(modules|classes)
64
+ [:loader, :loadable, :formatter, :process, :output].each do |r|
65
+ require '%s/%s' % [ActiveSupport::Inflector.underscore(name), r]
66
+ end
67
+
68
+ @components = {
69
+ process_class: Process,
70
+ formatter: Formatter,
71
+ helpers: Grouik::Helpers,
72
+ inflector: ActiveSupport::Inflector,
73
+ messager_factory: ->(&block) { Output::Message.new(&block) },
74
+ loadable_factory: ->(base, path) { Loadable.new(base, path) },
75
+ }
76
+
77
+ class << self
78
+ attr_accessor :components
79
+
80
+ # Access to components
81
+ #
82
+ # @param name [String, Symbol] name of component
83
+ # @return [Object]
84
+ # @see Grouik.components
85
+ def get(name)
86
+ components.fetch(name.to_sym)
87
+ end
88
+
89
+ # Initialize a Process and process it
90
+ #
91
+ # @return [Grouik::Process]
92
+ def process(&block)
93
+ self.get(:process_class).new(&block).process
94
+ end
95
+
96
+ def message(&block)
97
+ self.get(:messager_factory).call(&block).send
98
+ self
99
+ end
100
+ end
101
+ end