grouik 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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