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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 83f25d1bc75415d52ac220b352808fc496eedbf1
4
+ data.tar.gz: 8315a3d896b9556d0b716a27855c8d2fd87c0ab8
5
+ SHA512:
6
+ metadata.gz: b775cb7d82ec784f43d903ad6a36a98daa7de4db8ff343a83833cda9baff530c72d2a73639e51dd95dafa864f40e0033d603da89b10566ac60e1a95df7a60b2c
7
+ data.tar.gz: cc9ceee6b5db5d7c98083ccfe7fb954f61f70a73a4a81d1e706402c9a9caa44ec5df9d3b5d4f292c84e5a821d7e16c6840d8f4a55e789ed5acc9dcb16d6b03fe
data/src/bin/grouik ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright (C) 2017 Dimitri Arrigoni <dimitri@arrigoni.me>
5
+ # License GPLv3+: GNU GPL version 3 or later
6
+ # <http://www.gnu.org/licenses/gpl.html>.
7
+ # This is free software: you are free to change and redistribute it.
8
+ # There is NO WARRANTY, to the extent permitted by law.
9
+
10
+ require 'pathname'
11
+ $0 = Pathname.new(__FILE__).basename.to_s
12
+
13
+ # run bundled in a development context -------------------------------
14
+ if Pathname.new(__dir__).join('..', '..', '%s.gemspec' % $PROGRAM_NAME).file?
15
+ require 'rubygems'
16
+ require 'bundler'
17
+ Bundler.setup(:default)
18
+ end
19
+
20
+ # require lib requirements -------------------------------------------
21
+ [nil, :cli].each do |r|
22
+ req = r ? "#{$PROGRAM_NAME}/#{r}" : $PROGRAM_NAME
23
+ begin
24
+ require Pathname.new(__dir__).join('..', 'lib', '%s' % req)
25
+ rescue LoadError
26
+ begin
27
+ require req
28
+ rescue LoadError
29
+ require 'rubygems'
30
+ require req
31
+ end
32
+ end
33
+ end
34
+
35
+ # process command line options and run -------------------------------
36
+ exit Grouik::Cli.run
@@ -0,0 +1,172 @@
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 'optparse'
10
+ require 'ostruct'
11
+ require 'pathname'
12
+ require 'yaml'
13
+
14
+ require 'grouik' unless defined?(Grouik)
15
+ require 'grouik/concerns'
16
+
17
+ # Grouik command line interface
18
+ #
19
+ # Provides a ready to use program, based on ``Grouik`` library
20
+ class Grouik::Cli
21
+ attr_reader :argv
22
+ attr_reader :options
23
+ attr_reader :arguments
24
+
25
+ include Grouik::Concerns::Helpable
26
+
27
+ class << self
28
+ # Program name
29
+ #
30
+ # @return [String]
31
+ def program_name
32
+ Pathname.new($PROGRAM_NAME).basename('.rb').to_s
33
+ end
34
+
35
+ # Run
36
+ #
37
+ # @param [Array] argv
38
+ def run(argv = ARGV)
39
+ self.new(argv).run
40
+ end
41
+
42
+ # default options
43
+ def defaults
44
+ {
45
+ stats: true,
46
+ paths: ['.'],
47
+ basedir: '.',
48
+ output: STDOUT,
49
+ ignores: [],
50
+ require: nil,
51
+ template: nil,
52
+ }
53
+ end
54
+ end
55
+
56
+ # @return [String]
57
+ def program_name
58
+ self.class.program_name
59
+ end
60
+
61
+ # Constructor
62
+ #
63
+ # @param [Array] argv
64
+ def initialize(argv = ARGV)
65
+ @argv = argv.clone
66
+ @options = self.class.defaults
67
+ # @options = config unless config.empty?
68
+ @arguments = []
69
+ end
70
+
71
+ # Provide an ``OptionParser``
72
+ #
73
+ # @return [OptionParser]
74
+ def parser
75
+ parser = helpers.get(:cli).make_parser(@options)
76
+ parser.banner = 'Usage: %s [OPTION]... [FILE]...' % program_name
77
+
78
+ parser
79
+ end
80
+
81
+ # Parse command line options
82
+ #
83
+ # Abort process (error code SHOULD BE ``22``) on invalid option
84
+ #
85
+ # @return [self]
86
+ def parse!
87
+ argv = self.argv.clone
88
+ begin
89
+ parser.parse!(argv)
90
+ rescue OptionParser::InvalidOption
91
+ STDERR.puts(parser)
92
+ exit(Errno::EINVAL::Errno)
93
+ end
94
+ @arguments = argv
95
+ # @options = prepare_options(@options)
96
+ self
97
+ end
98
+
99
+ # Get processable items (based on command arguments), used during execution
100
+ #
101
+ # @return [Array<OpenStruct>]
102
+ def processables
103
+ processables = []
104
+ if arguments.empty?
105
+ processables[0] = OpenStruct.new(
106
+ path: Pathname.new(Dir.pwd),
107
+ options: options,
108
+ 'file?' => false
109
+ )
110
+ else
111
+ arguments.each do |filepath|
112
+ config = helpers.get(:cli).read_config(filepath)
113
+
114
+ processables << OpenStruct.new(
115
+ path: Pathname.new(filepath).dirname,
116
+ file: Pathname.new(filepath),
117
+ options: config,
118
+ 'file?' => true
119
+ )
120
+ end
121
+ end
122
+
123
+ processables.map(&:freeze)
124
+ end
125
+
126
+ # Execute CLI and return exit code
127
+ #
128
+ # @return [Fixnum]
129
+ def run
130
+ parse!
131
+
132
+ if options[:version]
133
+ STDOUT.puts helpers.get(:cli).version_chapter
134
+ return 0
135
+ end
136
+
137
+ processables.each do |processable|
138
+ Dir.chdir(processable.path) do
139
+ process(processable.options)
140
+ end
141
+ end
142
+ 0
143
+ end
144
+
145
+ protected
146
+
147
+ # Initiate and run a new ``Process`` from options
148
+ #
149
+ # @param [Hash] options
150
+ # @return [Grouik::Process]
151
+ def process(options)
152
+ options = helpers.get(:cli).prepare_options(options)
153
+
154
+ process = Grouik.process do |process|
155
+ process.basedir = options.fetch(:basedir)
156
+ process.paths = options.fetch(:paths)
157
+ process.ignores = options[:ignores]
158
+ process.output = options.fetch(:output)
159
+ process.template = options[:template]
160
+ process.bootstrap = options[:require]
161
+ end
162
+
163
+ process.on_success do |process|
164
+ process.display_status if options[:stats]
165
+ end.on_failure do |process|
166
+ process.display_status if options[:stats]
167
+ exit Errno::ECANCELED::Errno
168
+ end
169
+
170
+ process
171
+ end
172
+ end
@@ -0,0 +1,19 @@
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 ``helpers`` method
10
+ module Grouik::Concerns::Helpable
11
+ extend ActiveSupport::Concern
12
+
13
+ # Loads helper
14
+ #
15
+ # @return [Grouik::Helpers]
16
+ def helpers
17
+ Grouik.get(:helpers)
18
+ end
19
+ end
@@ -0,0 +1,16 @@
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
+ # Concerns in use
10
+ module Grouik::Concerns
11
+ require 'active_support/concern'
12
+
13
+ [:helpable].each do |concern|
14
+ require 'grouik/concerns/%s' % concern
15
+ end
16
+ end
@@ -0,0 +1,67 @@
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 'tenjin'
11
+
12
+ # Formatter used to render loadables
13
+ class Grouik::Formatter
14
+ attr_reader :options
15
+ attr_reader :loadables
16
+ attr_reader :template
17
+
18
+ def initialize(loadables, options = {})
19
+ @loadables = loadables
20
+ @options = options
21
+ @formatted = nil
22
+ @engine = Tenjin::Engine.new(cache: false)
23
+ @template = options[:template]
24
+ end
25
+
26
+ # @return [Pathname|nil]
27
+ def template
28
+ @template ? Pathname.new(@template).realpath : nil
29
+ end
30
+
31
+ def to_s
32
+ formatted
33
+ end
34
+
35
+ def format
36
+ @formatted = @formatted.nil? ? output : @formatted
37
+
38
+ return self
39
+ end
40
+
41
+ def formatted
42
+ format
43
+ @formatted.clone
44
+ end
45
+
46
+ class << self
47
+ def format(loadables, options = {})
48
+ self.new(loadables, options).format
49
+ end
50
+ end
51
+
52
+ protected
53
+
54
+ def output
55
+ items = loadables.to_a.map { |i| "require '#{i}'" }
56
+
57
+ return items.join("\n") + "\n" unless template
58
+
59
+ context = {
60
+ requirement: lambda do |indent = nil|
61
+ items.map { |i| '%s%s' % [indent, i] }.join("\n")
62
+ end
63
+ }
64
+
65
+ @engine.render(template.to_s, context)
66
+ end
67
+ end
@@ -0,0 +1,102 @@
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 'optparse'
10
+
11
+ # Cli helper, see ``Grouik::Cli``
12
+ class Grouik::Helpers::Cli
13
+ # Provide an ``OptionParser``
14
+ #
15
+ # @param [Hash] options
16
+ # @return [OptionParser]
17
+ def make_parser(options = {})
18
+ parser = OptionParser.new
19
+
20
+ {
21
+ basedir: ['--basedir=BASEDIR', 'Basedir [%s]' % options[:basedir]],
22
+ output: ['-o=OUTPUT', '--output=OUTPUT', 'Output [/dev/stdout]'],
23
+ require: ['-r=REQUIRE', '--require=REQUIRE',
24
+ 'Required file on startup'],
25
+ ignores: ['--ignores x,y,z', Array, 'Ignores'],
26
+ paths: ['--paths x,y,z', Array, 'Paths'],
27
+ stats: ['--[no-]stats', 'Display some stats'],
28
+ version: ['--version', 'Display the version and exit']
29
+ }.each do |k, v|
30
+ parser.on(*v) { |o| options[k] = o }
31
+ end
32
+
33
+ parser
34
+ end
35
+
36
+ # Prepare options
37
+ #
38
+ # Process values in order to easify their use
39
+ #
40
+ # @param [Hash] options
41
+ # @return [Hash]
42
+ def prepare_options(options)
43
+ [:require, :output].each do |k|
44
+ next unless options[k]
45
+ begin
46
+ options[k] = Pathname.new(options[k])
47
+ rescue TypeError
48
+ next
49
+ end
50
+ unless options[k].absolute?
51
+ options[k] = Pathname.new(Dir.pwd).join(options[k])
52
+ end
53
+ end
54
+
55
+ [:ignores, :paths].each do |k|
56
+ next unless options[k]
57
+ options[k] = [options[k]] if options[k].is_a? String
58
+
59
+ options[k] = options[k].to_a.map { |s| /#{s}/ } if :ignores == k
60
+ end
61
+
62
+ options
63
+ end
64
+
65
+ # Get the license
66
+ #
67
+ # @return [String]
68
+ def license
69
+ Grouik.version_info[:license].to_s.gsub(/\n{3}/mi, "\n\n")
70
+ end
71
+
72
+ # Get a displayable version
73
+ #
74
+ # Some inspiration taken from ``wget --version``
75
+ #
76
+ # @return [String]
77
+ def version_chapter
78
+ ['%s %s on %s' % [Grouik.name, Grouik::VERSION, host_os],
79
+ nil,
80
+ license].join("\n")
81
+ end
82
+
83
+ # @return [String]
84
+ def host_os
85
+ RbConfig::CONFIG['host_os']
86
+ end
87
+
88
+ # Read a config file
89
+ #
90
+ # @param [String] path
91
+ # @return [Hash]
92
+ def read_config(path)
93
+ file = Pathname.new(path.to_s)
94
+ config = YAML.safe_load(file.read)
95
+
96
+ h = config.each_with_object({}) do |(k, v), n|
97
+ n[k.intern] = v
98
+ end
99
+
100
+ h
101
+ end
102
+ end
@@ -0,0 +1,72 @@
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 'grouik/types'
10
+
11
+ # Loader helper, see ``Grouik::Loader``
12
+ class Grouik::Helpers::Loader
13
+ # Make loadables
14
+ #
15
+ # @@return [Grouik::Types::Loadables]
16
+ def make_loadables
17
+ Grouik::Types::Loadables.new
18
+ end
19
+
20
+ # Make ignores
21
+ #
22
+ # @param [Array<String|Regexp>] input
23
+ # @return [Array<Regexp>]
24
+ def make_ignores(input)
25
+ input.to_a.map { |s| s.is_a?(Regexp) ? s : /^#{s}$/ }
26
+ end
27
+
28
+ # Register paths
29
+ #
30
+ # @param [String] basedir
31
+ # @param [Array<String|Pathname>] paths
32
+ def register_paths(basedir, paths)
33
+ basedir = Pathname.new(basedir)
34
+
35
+ paths.reverse.each do |path|
36
+ $LOAD_PATH.unshift basedir.realpath.join(path).to_s
37
+ end
38
+ end
39
+
40
+ # @return [Pathname]
41
+ def pwd
42
+ Pathname.new(Dir.pwd)
43
+ end
44
+
45
+ # @param [String] path
46
+ # @return [Array<Pathname>]
47
+ def files_in_path(path)
48
+ loaddir = path.to_s
49
+ basereg = %r{^#{Regexp.quote(loaddir)}\/}
50
+
51
+ Dir.glob(path.join('**/*.rb'))
52
+ .sort
53
+ .map { |file| file.gsub(basereg, '') }
54
+ .map { |file| Pathname.new(file) }
55
+ end
56
+
57
+ # Collect loadables by paths
58
+ #
59
+ # @param [Array<String>] paths
60
+ # @return [Grouik::Types::Loadables]
61
+ def collect_loadables(paths)
62
+ loadables = self.make_loadables
63
+
64
+ paths.each do |path|
65
+ self.files_in_path(path).each do |file|
66
+ loadables = loadables.add_file(file, path.to_s)
67
+ end
68
+ end
69
+
70
+ loadables
71
+ end
72
+ end
@@ -0,0 +1,82 @@
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
+ # Helper providing outputs (mostly useful in a CLI/Rake context)
10
+ #
11
+ # Main methods are ``display_errors`` and ``display_status``
12
+ class Grouik::Helpers::Process
13
+ # Display loading errors
14
+ #
15
+ # @param [Grouik::Process] process
16
+ # @return [self]
17
+ def display_errors(process)
18
+ process.errors.each do |_k, struct|
19
+ Grouik.message do |m|
20
+ m.stream = STDERR
21
+ m.type = 'error'
22
+ m.content = ('%s:%s: %s' % [
23
+ struct.source,
24
+ struct.line,
25
+ struct.message,
26
+ ])
27
+ end
28
+ end
29
+ end
30
+
31
+ # Display (on ``STDERR``) loader related statistics
32
+ #
33
+ # @param [Grouik::Process] process
34
+ # @return [self]
35
+ def display_status(process)
36
+ message = '%s: %s files; %s iterations; %s errors (%.4f) [%s]'
37
+ loader = process.loader
38
+
39
+ Grouik.message do |m|
40
+ m.stream = STDERR
41
+ m.type = 'status_%s' % status(process)
42
+ m.content = (message %
43
+ [
44
+ status(process).to_s.capitalize,
45
+ loader.loadables.size,
46
+ loader.attempts,
47
+ loader.errors.size,
48
+ loader.stats ? loader.stats.real : 0,
49
+ format_filepath(process.output)
50
+ ])
51
+ end
52
+ self
53
+ end
54
+
55
+ # Denote status from process
56
+ #
57
+ # @return [Symbol]
58
+ def status(process)
59
+ statuses = { true => :success, false => :failure }
60
+
61
+ statuses.fetch(process.success?)
62
+ end
63
+
64
+ protected
65
+
66
+ # Format a filepath with a ``require`` format
67
+ #
68
+ # @param [Pathname|String|Object] filepath
69
+ def format_filepath(filepath)
70
+ filepath = filepath.to_s
71
+
72
+ $LOAD_PATH.each do |path|
73
+ regexp = %r{^#{Regexp.quote(path.to_s)}\/}
74
+ if regexp.match(filepath)
75
+ filepath.gsub!(regexp, '').gsub!(/\.rb$/, '')
76
+ break
77
+ end
78
+ end
79
+
80
+ filepath
81
+ end
82
+ end
@@ -0,0 +1,50 @@
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
+
12
+ # Helpers (loader)
13
+ #
14
+ # Provide easy access to helpers
15
+ module Grouik::Helpers
16
+ class << self
17
+ # Retrieve helper by name
18
+ #
19
+ # @param [String|Symbol] target
20
+ # @return [Object]
21
+ def get(target)
22
+ class_name = self.classify(target)
23
+
24
+ require load_dir.join(target.to_s) unless const_defined?(class_name)
25
+
26
+ inflector.constantize(class_name).new
27
+ end
28
+
29
+ # Directory where helpers stand
30
+ #
31
+ # @return [Pathname]
32
+ def load_dir
33
+ Pathname.new(__FILE__.gsub(/\.rb$/, ''))
34
+ end
35
+
36
+ protected
37
+
38
+ # @return ActiveSupport::Inflector
39
+ def inflector
40
+ ActiveSupport::Inflector
41
+ end
42
+
43
+ # Transform string
44
+ #
45
+ # return [String]
46
+ def classify(target)
47
+ '%s::%s' % [name, inflector.classify(target.to_s.gsub('/', '::'))]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,51 @@
1
+ require 'pathname'
2
+
3
+ # Describe a loable item (Ruby file)
4
+ class Grouik::Loadable
5
+ attr_reader :base
6
+ attr_reader :path
7
+ attr_reader :basedir
8
+
9
+ # @param [String] base
10
+ # @param [String] path
11
+ # @param [String] basedir
12
+ def initialize(base, path, basedir = '.')
13
+ @base = Pathname.new(base)
14
+ @path = path
15
+ @basedir = Pathname.new(basedir).realpath
16
+ end
17
+
18
+ # @param [Boolean] format format as loadable from ``$LOAD_PATH``
19
+ # @return [String]
20
+ def path(format = false)
21
+ path = @path.to_s
22
+
23
+ {
24
+ true => path,
25
+ false => basedir.join(base, path).to_s
26
+ }[(format and loadable?)].gsub(/\.rb$/, '')
27
+ end
28
+
29
+ # @return [String]
30
+ def to_s
31
+ path(true)
32
+ end
33
+
34
+ # @return [Boolean]
35
+ def load(from = nil)
36
+ path = from ? Pathname.new(from).join(self.path) : self.path
37
+
38
+ return require path
39
+ end
40
+
41
+ def loadable?
42
+ self.class.paths.include?(basedir.join(base).to_s)
43
+ end
44
+
45
+ class << self
46
+ # @return [Array<String>]
47
+ def paths
48
+ $LOAD_PATH.map(&:to_s)
49
+ end
50
+ end
51
+ end