grouik 1.0.0

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.
@@ -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