arli 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,3 @@
1
- require 'colored2'
2
1
  require 'arli/cli/app'
3
2
  require 'pp'
4
3
  module Arli
@@ -4,12 +4,12 @@ require 'open3'
4
4
  require 'arli'
5
5
  require 'arli/version'
6
6
  require 'arli/errors'
7
- require 'arli/output'
7
+ require 'arli/helpers/output'
8
8
 
9
9
  module Arli
10
10
  module Commands
11
11
  class Base
12
- include Arli::Output
12
+ include Arli::Helpers::Output
13
13
 
14
14
  attr_accessor :config, :name
15
15
 
@@ -6,38 +6,65 @@ require 'arli'
6
6
  require 'arduino/library'
7
7
  require_relative 'base'
8
8
  require_relative 'bundle'
9
+ require 'arli/library'
9
10
 
10
11
  module Arli
11
12
  module Commands
12
- class Install < Bundle
13
+ class Install < Base
13
14
  require 'arduino/library/include'
14
15
 
15
- attr_accessor :library
16
+ attr_accessor :library,
17
+ :arlifile,
18
+ :install_argument,
19
+ :install_method
20
+
21
+ include ::Arli::Library
16
22
 
17
23
  def setup
18
- validate_argument
24
+ super
25
+
26
+ self.install_argument = runtime.argv.first
27
+ raise InvalidInstallSyntaxError,
28
+ "Missing installation argument: a name, a file or a URL." unless install_argument
19
29
 
20
- self.library = identify_library(runtime.argv.first)
21
- validate_library
30
+ self.library = identify_library(install_argument)
31
+ raise Arli::Errors::LibraryNotFound,
32
+ "Library #{cfg.to_hash} was not found" unless library
22
33
 
23
- self.arlifile = Arli::ArliFile.new(config: config, libraries: [library]) if library
34
+ self.arlifile = Arli::ArliFile.new(config: config, libraries: [ library ])
35
+ if config.trace
36
+ info("found library using #{install_method}:\n#{library.inspect}")
37
+ end
38
+ end
39
+
40
+ def run
41
+ arlifile.install
24
42
  end
25
43
 
26
44
  # arg can be 'Adafruit GFX Library'
27
45
  def identify_library(arg)
28
- if File.exist?(arg)
29
- begin
30
- Arduino::Library::Model.from(arg)
31
- rescue
32
- nil
33
- end
34
- elsif arg =~ %r[https?://]
35
- Arduino::Library::Model.from_hash(url: arg, name: File.basename(arg))
36
- else
37
- results = search(name: /^#{arg}$/i)
38
- validate_search(arg, results)
39
- results.sort.last if results && !results.empty?
40
- end
46
+ results = if arg =~ %r[https?://]i
47
+ self.install_method = :url
48
+ r = search(url: /^#{arg}$/i)
49
+ if r.empty?
50
+ self.install_method = :website
51
+ r = search(website: /^#{arg}$/i)
52
+ end
53
+ if r.empty?
54
+ self.install_method = :custom
55
+ r = [ Arduino::Library::Model.from_hash(url: arg, name: File.basename(arg)) ]
56
+ end
57
+ r
58
+ elsif File.exist?(arg) || arg =~ /\.zip$/
59
+ self.install_method = :archiveFileName
60
+ search(archiveFileName: "#{File.basename(arg)}")
61
+ else
62
+ self.install_method = :name
63
+ search(name: /^#{arg}$/)
64
+ end
65
+
66
+ validate_search(arg, results)
67
+ results.sort.last if results && !results.empty?
41
68
  end
42
69
 
43
70
  def params
@@ -52,19 +79,10 @@ module Arli
52
79
 
53
80
  def validate_search(arg, results)
54
81
  raise Arli::Errors::LibraryNotFound,
55
- "Can't find library by argument #{arg.bold.yellow}" if results.nil? || results.empty?
82
+ "Can't find library by argument #{arg.bold.yellow}, searching by #{install_method}" if results.nil? || results.empty?
83
+ dupes = results.map(&:name).uniq.size
56
84
  raise Arli::Errors::TooManyMatchesError,
57
- "More than one match found for #{arg.bold.yellow}" if results.map(&:name).uniq.size > 1
58
- end
59
-
60
- def validate_library
61
- raise Arli::Errors::LibraryNotFound,
62
- "Library #{cfg.to_hash} was not found" unless library
63
- end
64
-
65
- def validate_argument
66
- raise InvalidInstallSyntaxError,
67
- "Missing installation argument: a name, a file or a URL." unless runtime.argv.first
85
+ "More than one match found for #{arg.bold.yellow} — #{dupes} libraries matched" if dupes > 1
68
86
  end
69
87
 
70
88
  def cfg
@@ -4,53 +4,42 @@ require 'open3'
4
4
  require 'arli'
5
5
  require 'arli/commands/base'
6
6
  require 'arli/errors'
7
+ require 'arli/library/multi_version'
7
8
  require 'arduino/library'
8
9
 
10
+
9
11
  module Arli
10
12
  module Commands
11
13
  class Search < Base
12
14
 
13
- LibraryWithVersion = Struct.new(:name, :versions)
14
-
15
15
  require 'arduino/library/include'
16
16
 
17
17
  attr_accessor :search_string,
18
18
  :search_opts,
19
+ :search_method,
19
20
  :results,
20
21
  :limit,
21
22
  :database,
22
23
  :format,
23
- :hash,
24
- :custom_print
24
+ :unique_libraries
25
25
 
26
26
  def initialize(*args)
27
27
  super(*args)
28
- self.format = :short
29
- self.hash = Hash.new
30
- self.custom_print = true
28
+ self.format = config.search.results.output_format
29
+ valid_methods = Arli::Library::MultiVersion.format_methods
30
+ raise Arli::Errors::InvalidSearchSyntaxError,
31
+ "invalid format #{format}" unless valid_methods.include?(format)
31
32
  end
32
33
 
33
34
  def run
34
- self.search_opts = process_search_options!
35
- self.results = search(database, **search_opts).sort
36
-
37
- results.map do |lib|
38
- hash[lib.name] ||= LibraryWithVersion.new(lib.name, [])
39
- hash[lib.name].versions << lib.version
40
-
41
- method = "to_s_#{format}".to_sym
42
- if lib.respond_to?(method)
43
- self.custom_print = false
44
- lib.send(method)
45
- end
46
- end
35
+ self.search_opts = process_search_options!
36
+ self.results = search(database, **search_opts).sort
37
+ self.unique_libraries = Set.new
47
38
 
48
- if custom_print
49
- previous = nil
50
- results.each do |lib|
51
- print_lib(hash[lib.name]) unless previous == lib.name
52
- previous = lib.name
53
- end
39
+ results.map { |lib| add_lib_or_version(lib) }
40
+
41
+ unique_libraries.each do |multi_version|
42
+ puts multi_version.send("to_s_#{format}".to_sym)
54
43
  end
55
44
  print_total_with_help
56
45
  rescue Exception => e
@@ -58,10 +47,13 @@ module Arli
58
47
  puts e.backtrace.join("\n") if ENV['DEBUG']
59
48
  end
60
49
 
61
- def print_lib(lib)
62
- $stdout.puts "#{lib.name.bold.magenta} " +
63
- (lib.versions ?
64
- "(#{lib.versions.size} versions: #{lib.versions.reverse[0..5].join(', ').blue})": '')
50
+ def add_lib_or_version(lib)
51
+ a_version = Arli::Library::MultiVersion.new(lib)
52
+ if unique_libraries.include?(a_version)
53
+ unique_libraries.find { |l| l.name == a_version.name }&.add_version(library: lib)
54
+ else
55
+ unique_libraries << a_version
56
+ end
65
57
  end
66
58
 
67
59
  def process_search_options!
@@ -71,9 +63,12 @@ module Arli
71
63
  self.limit = config.search.results.limit
72
64
  search_opts = {}
73
65
  begin
74
- search_opts = eval("{ #{search_string} }")
66
+ params_code = "{ #{search_string} }"
67
+ puts "Evaluating: [#{params_code.blue}]\nSearch Method: [#{search_method.to_s.green}]" if config.trace
68
+ search_opts = eval(params_code)
69
+
75
70
  rescue => e
76
- handle_error(e)
71
+ handle_and_raise_error(e)
77
72
  end
78
73
 
79
74
  unless search_opts.is_a?(::Hash) && search_opts.size > 0
@@ -91,9 +86,19 @@ module Arli
91
86
  def extract_search_argument!
92
87
  search = runtime.argv.first
93
88
  if search =~ /:/
89
+ self.search_method = :ruby
94
90
  search
91
+ elsif search.start_with?('/')
92
+ self.search_method = :regex_name_and_url
93
+ # exact match
94
+ "#{config.search.default_field}: #{search}, archiveFileName: #{search}"
95
+ elsif search.start_with?('=')
96
+ self.search_method = :equals
97
+ # exact match
98
+ "#{config.search.default_field}: '#{search[1..-1]}'"
95
99
  elsif search
96
- "#{config.search.default_field}: /^#{search}$/"
100
+ self.search_method = :regex
101
+ "#{config.search.default_field}: /#{search.downcase}/i"
97
102
  end
98
103
  end
99
104
 
@@ -118,10 +123,10 @@ module Arli
118
123
  def print_total_with_help
119
124
  puts "———————————————————————"
120
125
  puts " Total Versions : #{results.size.to_s.bold.magenta}\n"
121
- puts "Unique Libraries : #{hash.keys.size.to_s.bold.magenta}\n"
126
+ puts "Unique Libraries : #{unique_libraries.size.to_s.bold.magenta}\n"
122
127
  puts "———————————————————————"
123
128
  if results.size == Arli::Configuration::DEFAULT_RESULTS_LIMIT
124
- puts "Hint: use #{'-m 0'.bold.green} to disable the limit, or set it to another value."
129
+ puts "Hint: use #{'-m 5'.bold.green} to limit the result set."
125
130
  end
126
131
  end
127
132
 
@@ -9,7 +9,7 @@ module Arli
9
9
  DEFAULT_LOCK_FILENAME = (DEFAULT_FILENAME + '.lock').freeze
10
10
  ACTIONS_WHEN_EXISTS = %i(backup overwrite abort)
11
11
  ARLI_COMMAND = 'arli'.freeze
12
- DEFAULT_RESULTS_LIMIT = 100
12
+ DEFAULT_RESULTS_LIMIT = 0
13
13
 
14
14
  extend Dry::Configurable
15
15
 
@@ -43,6 +43,7 @@ module Arli
43
43
  # Global flags
44
44
  setting :debug, ENV['ARLI_DEBUG'] || false
45
45
  setting :trace, false
46
+ setting :no_color, false
46
47
  setting :dry_run, false
47
48
  setting :verbose, false
48
49
  setting :help, false
@@ -55,7 +56,7 @@ module Arli
55
56
  setting :results do
56
57
  setting :attrs
57
58
  setting :limit, DEFAULT_RESULTS_LIMIT
58
- setting :format, :inspect
59
+ setting :output_format, :short
59
60
  end
60
61
  end
61
62
 
@@ -64,7 +65,7 @@ module Arli
64
65
  setting :path, ::Dir.pwd
65
66
  setting :name, ::Arli::Configuration::DEFAULT_FILENAME
66
67
  setting :lock_name, ::Arli::Configuration::DEFAULT_LOCK_FILENAME
67
- setting :lock_format, :json
68
+ setting :lock_format, :text
68
69
  end
69
70
 
70
71
  setting :bundle do
@@ -6,4 +6,10 @@ class String
6
6
  tr('-', '_').
7
7
  downcase
8
8
  end
9
+
10
+ def reformat_wrapped(width = 70, indent_with = 8)
11
+ ind = (' ' * indent_with)
12
+ (ind + self.gsub(/\s+/, ' ').gsub(/(.{1,#{width}})( |\Z)/, "\\1\n" + ind))
13
+ end
14
+
9
15
  end
@@ -0,0 +1,48 @@
1
+ module Arli
2
+ module Helpers
3
+ module Inherited
4
+
5
+ module ClassMethods
6
+ def set_or_get(var_name, val = nil, set_nil = false)
7
+ var = "@#{var_name}".to_sym
8
+ self.instance_variable_set(var, val) if val
9
+ self.instance_variable_set(var, nil) if set_nil
10
+ self.instance_variable_get(var)
11
+ end
12
+
13
+ def short_name
14
+ name.gsub(/.*::/, '').underscore.to_sym
15
+ end
16
+
17
+ def attr_assignable(*attrs)
18
+ self.class.instance_eval do
19
+ attrs.each do |attribute|
20
+ send(:define_method, attribute) do |val = nil, **opts|
21
+ set_or_get(attribute, val, opts && opts[:nil])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ module Subclassing
29
+ def included(klass)
30
+ klass.instance_eval do
31
+ class << self
32
+ include ::Arli::Helpers::Inherited
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.included(base)
39
+ base.instance_eval do
40
+ class << self
41
+ include(::Arli::Helpers::Inherited::ClassMethods)
42
+ end
43
+ end
44
+ base.extend(Subclassing) if base.is_a?(Class)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,187 @@
1
+ require 'colored2'
2
+ require 'tty-cursor'
3
+
4
+ module Arli
5
+ module Helpers
6
+ module Output
7
+ CHAR_FAILURE = '✖'.red
8
+ CHAR_SUCCESS = '✔'.green
9
+
10
+ class << self
11
+ attr_accessor :enabled, :cursor
12
+
13
+ def enable!
14
+ self.enabled = true
15
+ end
16
+
17
+ def enabled?
18
+ self.enabled
19
+ end
20
+
21
+ def disable!
22
+ self.enabled = false
23
+ end
24
+ end
25
+
26
+ self.enable!
27
+ self.cursor = TTY::Cursor
28
+
29
+ def cursor
30
+ Arli::Helpers::Output.cursor
31
+ end
32
+
33
+ def info(msg, header = nil)
34
+ __pf('%-20s', header.blue) if header
35
+ __pf((header ? ' : ' : '') + msg + "\n") if msg
36
+ end
37
+
38
+ def debug(msg)
39
+ __pf('%-20s', header.blue) if header
40
+ __pf((header ? ' : ' : '') + msg + "\n") if msg
41
+ end
42
+
43
+ def error(msg, exception = nil)
44
+ __pf "#{msg.to_s.red}\n" if msg
45
+ __pf "#{exception.inspect.red}\n\n" if exception
46
+ end
47
+
48
+ def report_exception(e, header = nil)
49
+ if header
50
+ __pf header.bold.yellow + ': '
51
+ else
52
+ __pf 'Error: '.bold.red
53
+ end
54
+ error e.message if (e && e.respond_to?(:message))
55
+ if e && Arli.config.trace
56
+ __pf "\n"
57
+ __pf 'Top 10 stack trace'.bold.yellow + "\n"
58
+ __pf e.backtrace.reverse[-10..-1].join("\n").red + "\n"
59
+ elsif e
60
+ __pf "\nUse -t (--trace) for detailed exception\n" +
61
+ "or -D (--debug) to print Arli config\n"
62
+ end
63
+ end
64
+
65
+ def raise_invalid_arli_command!(cmd, e = nil)
66
+ raise Arli::Errors::InvalidCommandError.new(cmd)
67
+ end
68
+
69
+ # Shortcuts disabled in tests
70
+ def ___(msg = nil, newline = false)
71
+ return unless Arli::Helpers::Output.enabled?
72
+ __pf msg if msg
73
+ __pt if newline
74
+ end
75
+
76
+ def __pt(*args)
77
+ puts(*args) if Arli::Helpers::Output.enabled?
78
+ end
79
+
80
+ def __p(*args)
81
+ print(*args) if Arli::Helpers::Output.enabled?
82
+ end
83
+
84
+ def __pf(*args)
85
+ printf(*args) if Arli::Helpers::Output.enabled?
86
+ end
87
+
88
+
89
+ def print_target_dir(d, verb = 'installed')
90
+ print_action_success(d.green, "#{verb} #{d.green} ")
91
+ end
92
+
93
+ def print_action_starting(action_name)
94
+ if verbose?
95
+ indent_cursor
96
+ ___ "⇨ #{action_name.yellow} ... "
97
+ end
98
+ if block_given?
99
+ yield
100
+ ok if verbose?
101
+ end
102
+ end
103
+
104
+ def print_action_success(short, verbose = nil)
105
+ if verbose? && !quiet?
106
+ indent_cursor
107
+ ___ "⇨ #{verbose || short} #{CHAR_SUCCESS}"
108
+ elsif !quiet?
109
+ ___ "#{short} #{CHAR_SUCCESS} "
110
+ end
111
+ end
112
+
113
+ def print_action_failure(short, verbose = nil)
114
+ if verbose? && !quiet?
115
+ indent_cursor
116
+ ___ "⇨ #{verbose || short} #{CHAR_FAILURE}\n"
117
+ elsif !quiet?
118
+ ___ "#{short} #{CHAR_FAILURE} "
119
+ end
120
+ end
121
+
122
+ def action_fail(action, exception)
123
+ print_action_failure(action.class.short_name,
124
+ "#{action.class.short_name} failed with #{exception.message.red}\n" +
125
+ "Action Description: #{action.class.description}")
126
+ raise(exception)
127
+ end
128
+
129
+ def action_ok(action)
130
+ print_action_success(action.action_name)
131
+ end
132
+
133
+ def ok
134
+ ___ " #{CHAR_SUCCESS} "
135
+ end
136
+
137
+ def fuck
138
+ ___ " #{CHAR_FAILURE} "
139
+ end
140
+
141
+ def header(command: nil)
142
+ out = "\n#{hr}\n"
143
+ out << "Arli (#{::Arli::VERSION.yellow})"
144
+ out << ", Command: #{command.name.to_s.magenta.bold}" if command
145
+ if command && command.params && Arli.config.verbose
146
+ out << "\n#{command.params.to_s.blue}\n"
147
+ end
148
+ out << "\nLibrary Path: #{Arli.default_library_path.green}\n"
149
+ out << "#{hr}\n"
150
+ info out
151
+ end
152
+
153
+ def hr
154
+ ('-' * (ENV['COLUMNS'] || 80)).red.dark
155
+ end
156
+
157
+ # Some shortcuts
158
+ def verbose?
159
+ config.verbose && !quiet?
160
+ end
161
+
162
+ def quiet?
163
+ config.quiet
164
+ end
165
+
166
+ def overwrite?
167
+ config.if_exists.overwrite
168
+ end
169
+
170
+ def backup?
171
+ config.if_exists.backup
172
+ end
173
+
174
+ def abort?
175
+ config.if_exists.abort
176
+ end
177
+
178
+ def debug?
179
+ config.debug
180
+ end
181
+
182
+ def indent_cursor(value = 40)
183
+ ___ cursor.column(value)
184
+ end
185
+ end
186
+ end
187
+ end