arli 0.3.2 → 0.5.1

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.
@@ -3,89 +3,40 @@ require 'fileutils'
3
3
  require 'open3'
4
4
  require 'arli'
5
5
  require 'arli/version'
6
+ require 'arli/errors'
6
7
 
7
8
  module Arli
8
9
  module Commands
9
10
  class Base
10
- attr_accessor :lib_path, :arli_file, :abort_if_exists, :command
11
+ attr_accessor :lib_path,
12
+ :arlifile,
13
+ :abort_if_exists,
14
+ :create_backup,
15
+ :command,
16
+ :debug,
17
+ :trace
11
18
 
12
19
  def initialize(options)
13
20
  self.lib_path = options[:lib_home]
14
21
  self.abort_if_exists = options[:abort_if_exists]
15
- self.command = self.class.name.gsub(/.*::/, '').downcase.to_sym
22
+ self.create_backup = options[:create_backup]
23
+ self.debug = options[:debug]
24
+ self.trace = options[:trace]
25
+
26
+ self.command = self.class.name.gsub(/.*::/, '').downcase.to_sym
16
27
  setup
17
28
  end
18
29
 
19
- def header
20
- out = "——————————————————————————————————————————————————————————\n"
21
- out << "Arli : Version #{::Arli::VERSION.bold.yellow}\n"
22
- out << "Command : #{command.to_s.bold.blue}\n" if command
23
- out << "Library Path : #{lib_path.bold.green}\n" if lib_path
24
- out << "ArliFile : #{arli_file.file.bold.magenta}\n" if
25
- arli_file && arli_file.file
26
- out << '——————————————————————————————————————————————————————————'
27
-
28
- info out
29
-
30
- self
31
- end
32
-
33
- # Commands implement #run method that uses helpers below:
34
- protected
35
-
36
- def all_dependencies(cmd, *args)
37
- for_each_dependency do |dep|
38
- begin
39
- method_name = "#{cmd}_dependency".to_sym
40
- argv = args.map { |key| dep[key] }
41
- if self.respond_to?(method_name)
42
- info("dependency #{dep.inspect}: calling #{method_name} with args #{argv.inspect}") if Arli::DEBUG
43
- self.send(method_name, *argv) do |system_command|
44
- execute(system_command)
45
- end
46
- else
47
- raise ArgumentError,
48
- "Method #{method_name.to_s.bold.blue} is not implemented on #{self.class.name.bold.red}"
49
- end
50
- end
51
- end
52
- rescue Exception => e
53
- error "Error while running command #{cmd}:\n\n#{e.message.bold.red}"
54
- if Arli::DEBUG
55
- error e.backtrace.join("\n")
56
- end
30
+ def name
31
+ self.class.name.gsub(/.*::/, '').downcase
57
32
  end
58
33
 
59
34
  def setup
60
35
  FileUtils.mkdir_p(lib_path)
61
36
  end
62
37
 
63
- # @param <String> *args — list of arguments or a single string
64
- def execute(*args)
65
- cmd = args.join(' ')
66
- info cmd.bold.green
67
- o, e, s = Open3.capture3(cmd)
68
- puts o if o
69
- puts e.red if e
70
- s
71
- rescue Exception => e
72
- error "Error running [#{cmd.bold.yellow}]\n" +
73
- "Current folder is [#{Dir.pwd.bold.yellow}]", e
74
- raise e
75
- end
76
-
77
- def for_each_dependency(&_block)
78
- raise 'Library Path is nil!' unless lib_path
79
- FileUtils.mkpath(lib_path) unless Dir.exist?(lib_path)
80
- arli_file.each do |dependency|
81
- Dir.chdir(lib_path) do
82
- yield(dependency)
83
- end
84
- end
85
- end
86
-
87
38
  def error(msg, exception = nil)
88
- printf 'Runtime Error: '.bold.red + "\n#{msg}\n" if msg
39
+ printf 'Runtime Error: '.red + "\n#{msg}\n" if msg
89
40
  if exception
90
41
  puts
91
42
  printf 'Exception: '.red + "\n#{exception.inspect.red}\n\n"
@@ -94,7 +45,7 @@ module Arli
94
45
  end
95
46
 
96
47
  def info(msg, header = nil)
97
- printf('%-20s', header.bold.blue) if header
48
+ printf('%-20s', header.blue) if header
98
49
  printf((header ? ' : ' : '') + msg + "\n") if msg
99
50
  end
100
51
 
@@ -1,37 +1,25 @@
1
1
  require 'json'
2
- require 'fileutils'
3
- require 'open3'
4
2
  require 'arli'
5
- require 'arli/commands/update'
3
+ require 'net/http'
4
+ require_relative 'base'
5
+ require_relative '../installers/zip_file'
6
6
 
7
7
  module Arli
8
8
  module Commands
9
- class Install < Update
9
+ class Install < Base
10
10
 
11
- def run
12
- all_dependencies(command, 'name', 'url')
11
+ def initialize(options)
12
+ super(options)
13
+ self.arlifile = Arli::ArliFile.new(lib_path: lib_path,
14
+ arlifile_path: options[:arli_dir])
13
15
  end
14
16
 
15
- def install_dependency(name, url)
16
- cmd = if Dir.exist?(name)
17
- if abort_if_exists
18
- raise <<-EOF
19
- Existing folder found for library #{name.red}.
20
- Please use -u switch with 'install' command,
21
- or invoke the 'update' command directly."
22
- EOF
23
- .gsub(/^\s+/, '')
24
-
25
- else
26
- update_dependency(name)
27
- end
28
- else
29
- "git clone -v #{url} #{name} 2>&1"
30
- end
31
- yield(cmd) if block_given?
32
- cmd
17
+ def run
18
+ arlifile.each_dependency do |lib|
19
+ Arli::Installer.new(lib: lib, command: self).install
20
+ end
33
21
  end
34
- end
35
22
 
23
+ end
36
24
  end
37
25
  end
@@ -3,6 +3,7 @@ require 'fileutils'
3
3
  require 'open3'
4
4
  require 'arli'
5
5
  require 'arli/commands/base'
6
+ require 'arli/errors'
6
7
  require 'arduino/library'
7
8
  require 'awesome_print'
8
9
  module Arli
@@ -15,30 +16,30 @@ module Arli
15
16
  :limit,
16
17
  :database
17
18
 
18
- class InvalidOptionError < ArgumentError; end
19
-
20
19
  def initialize(options)
21
20
  super(options)
22
- self.search_string = options[:search]
23
- self.limit = options[:limit] || 100
21
+ self.search_string = options[:argv].first
22
+
23
+ puts "using search string [#{search_string}]" if search_string && Arli.debug?
24
+ self.limit = options[:limit] || 100
24
25
 
25
- raise InvalidOptionError, 'Please provide search string with --search' \
26
+ raise Arli::Errors::InvalidSyntaxError, 'Please provide search string after the "search" command' \
26
27
  unless search_string
27
28
 
28
29
  begin
29
30
  self.search_opts = eval("{ #{search_string} }")
30
31
  rescue => e
31
- raise InvalidOptionError "Search string '#{search_string}' is invalid.\n" +
32
+ raise Arli::Errors::InvalidSyntaxError, "Search string '#{search_string}' is invalid.\n" +
32
33
  e.message.red
33
34
  end
34
35
 
35
36
  unless search_opts.is_a?(::Hash) && search_opts.size > 0
36
- raise InvalidOptionError, "Search string '#{search_string}' did not eval to Hash.\n"
37
+ raise Arli::Errors::InvalidSyntaxError, "Search string '#{search_string}' did not eval to Hash.\n"
37
38
  end
38
39
 
39
40
  self.database = options[:database] ? db_from(option[:database]) : db_default
40
41
 
41
- search_opts.merge!(limit: limit) if limit
42
+ search_opts.merge!(limit: limit) if limit && limit > 0
42
43
  end
43
44
 
44
45
  def run
@@ -0,0 +1,16 @@
1
+ module Arli
2
+ class Config
3
+ DEFAULT_FILENAME = 'Arlifile'.freeze
4
+ PARAMS = %i(
5
+ library_path
6
+ library_index_path
7
+ library_index_url
8
+ logger
9
+ debug
10
+ )
11
+
12
+ class << self
13
+ attr_accessor *PARAMS
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module Arli
2
+ module Errors
3
+ class ArliError < StandardError; end
4
+
5
+ class InvalidCommandError < ArliError; end
6
+
7
+ class InvalidSyntaxError < ArliError; end
8
+
9
+ class AbstractMethodCalled < ArliError; end
10
+
11
+ class ArliFileNotFound < ArliError; end
12
+
13
+ class LibraryAlreadyExists < ArliError; end
14
+
15
+ class InstallerError < ArliError; end
16
+
17
+ class ZipFileError < InstallerError; end
18
+ end
19
+ end
@@ -0,0 +1,100 @@
1
+ require 'arli'
2
+ require 'forwardable'
3
+ module Arli
4
+ class Installer
5
+ extend Forwardable
6
+ attr_accessor :lib, :lib_dir, :lib_path, :command
7
+
8
+ def_delegators :@command, :info, :error, :debug
9
+
10
+ def initialize(lib:, command:, lib_path: Arli.library_path)
11
+ self.lib = lib
12
+ self.command = command
13
+ self.lib_dir = lib.name.gsub(/ /, '_')
14
+ self.lib_path = lib_path
15
+ end
16
+
17
+ def exists?
18
+ Dir.exist?(target_lib_path)
19
+ end
20
+
21
+ def install
22
+ action = self.exists? ? 'replacing' : 'installing'
23
+ backup! if command.create_backup
24
+ abort! if exists? && command.abort_if_exists
25
+
26
+ s "#{action} #{lib.name.blue}"
27
+ library = ::Arduino::Library::Resolver.resolve(lib)
28
+
29
+ if library.nil?
30
+ s ' ❌ ' + "\n"
31
+ elsif library.url.nil?
32
+ s ' ❌ ' + "\n"
33
+ else
34
+ s "(#{library.version.blue})" if library.version
35
+ s '✔ '.bold.green
36
+ if library.url =~ /\.zip$/i
37
+ s 'unpacking zip...'
38
+ Arli::Installers::ZipFile.new(lib: library).install
39
+ s '✔ '.bold.green
40
+ else
41
+ c = exists? ? git_update_command : git_clone_command
42
+ s 'running git...'
43
+ execute(c)
44
+ s '✔ '.bold.green
45
+ end
46
+ end
47
+ s nil, true
48
+ end
49
+
50
+
51
+ def git_update_command
52
+ "cd #{lib_dir} && git pull --rebase 2>&1"
53
+ end
54
+
55
+ def git_clone_command
56
+ "git clone -v #{lib.url} #{lib_dir} 2>&1"
57
+ end
58
+
59
+ def backup_lib_path
60
+ target_lib_path + ".#{Time.now.strftime('%Y%m%d%H%M%S')}"
61
+ end
62
+
63
+ def target_lib_path
64
+ "#{lib_path}/#{lib_dir}"
65
+ end
66
+
67
+ def backup!
68
+ FileUtils.cp_r(target_lib_path, backup_lib_path) if exists?
69
+ end
70
+
71
+ def abort!
72
+ raise Arli::Errors::LibraryAlreadyExists,
73
+ "Library #{target_lib_path} already exists!"
74
+ end
75
+
76
+ def s(msg, newline = false)
77
+ printf msg + ' ' if msg
78
+ puts if newline
79
+ end
80
+
81
+ protected
82
+
83
+ # @param <String> *args — list of arguments or a single string
84
+ def execute(*args)
85
+ cmd = args.join(' ')
86
+ raise 'No command to run was given' unless cmd
87
+ info("\n" + cmd.green) if self.debug
88
+ o, e, s = Open3.capture3(cmd)
89
+ info("\n" + o) if o if self.debug
90
+ info("\n" + e.red) if e && self.debug
91
+ s
92
+ rescue Exception => e
93
+ error "Error running [#{args.join(' ')}]\n" +
94
+ "Current folder is [#{Dir.pwd.yellow}]", e
95
+ raise e
96
+ end
97
+
98
+ end
99
+ end
100
+
@@ -0,0 +1,61 @@
1
+ require 'archive/zip'
2
+
3
+ module Arli
4
+ module Installers
5
+ class ZipFile
6
+ attr_accessor :lib, :lib_dir
7
+
8
+ def initialize(lib:)
9
+ self.lib = lib
10
+ self.lib_dir = lib.name.gsub(/ /, '_')
11
+ raise 'Invalid URL for this installer: ' + lib.url unless lib.url =~ /\.zip$/i
12
+ end
13
+
14
+ def install
15
+ download!
16
+
17
+ remove_library!
18
+ remove_library_versions!
19
+
20
+ unzip(zip_archive, '.')
21
+
22
+ dir = dirs_matching(lib_dir).first
23
+
24
+ FileUtils.move(dir, lib_dir) if dir
25
+
26
+ FileUtils.rm_f(zip_archive) if File.exist?(zip_archive)
27
+ end
28
+
29
+ def remove_library!
30
+ FileUtils.rm_rf(lib_dir)
31
+ end
32
+
33
+ def remove_library_versions!
34
+ dirs = dirs_matching(lib_dir)
35
+ dirs.delete(lib_dir) # we don't want to delete the actual library
36
+ dirs.each { |d| FileUtils.rm_f(d) }
37
+ end
38
+
39
+ private
40
+
41
+ def dirs_matching(name)
42
+ Dir.glob("#{name}*").select { |d| File.directory?(d) }
43
+ end
44
+
45
+ def zip_archive
46
+ @zip_archive ||= File.basename(lib.url)
47
+ end
48
+
49
+ def download!
50
+ File.write(zip_archive, Net::HTTP.get(URI.parse(lib.url)))
51
+ end
52
+
53
+ # def unzip(file, destination)
54
+ # Archive::Zip.extract(file, destination, on_error: :skip)
55
+ # end
56
+ def unzip(file, destination)
57
+ `unzip -o #{file} -d #{destination}`
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ module Arli
2
+ module Logger
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ %i(debug info error warn fatal).each do |level|
6
+ define_method "_#{level}" do |*args|
7
+ Arli.logger.send(level, *args) if Arli.logger
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,14 +1,21 @@
1
1
  require 'arduino/library'
2
+ require 'arli/config'
3
+ require 'arli/version'
4
+
2
5
  module Arli
3
6
  class CLI
4
7
  class Parser < OptionParser
5
- attr_accessor :output_lines, :command, :options
8
+ attr_accessor :output_lines, :command, :options, :arlifile
6
9
 
7
10
  def initialize(command = nil)
8
11
  super(nil, 22)
9
- self.output_lines = Array.new
10
- self.command = command
11
- self.options = Hashie::Mash.new
12
+ self.output_lines = ::Array.new
13
+ self.command = command
14
+ self.options = ::Hashie::Mash.new
15
+ self.arlifile = options[:arli_dir] ?
16
+ options[:arli_dir] + '/' + Arli::Config::DEFAULT_FILENAME :
17
+ Arli::Config::DEFAULT_FILENAME
18
+ self.options[:arlifile] = arlifile
12
19
  end
13
20
 
14
21
  def sep(text = nil)
@@ -16,66 +23,83 @@ module Arli
16
23
  end
17
24
 
18
25
  def option_dependency_file
19
- on('-a', '--arli-file FILE',
20
- 'ArliFile.yml'.bold.green + ' is the file listing the dependencies',
21
- "Default filename is #{DEFAULT_ARLI_FILE.bold.magenta})\n\n") do |v|
22
- options[:arli_file] = v
26
+ on('-p', '--arli-path PATH',
27
+ 'Folder where ' + 'Arlifile'.green + ' is located,',
28
+ "Defaults to the current directory.\n\n") do |v|
29
+ options[:arli_dir] = v
23
30
  end
24
31
  end
25
32
 
26
33
  def option_lib_home
27
- on('-l', '--lib-home HOME', 'Local folder where libraries are installed',
28
- "Default: #{default_library_path}\n\n") do |v|
34
+ on('-l', '--libs PATH', 'Local folder where libraries are installed',
35
+ "Defaults to #{default_library_path}\n\n") do |v|
29
36
  options[:lib_home] = v
30
37
  end
31
38
  end
32
39
 
33
-
34
40
  def option_search
35
- on('-s', '--search TERMS', 'ruby-style hash arguments to search for',
36
- %Q(eg: -s "name: 'AudioZero', version: /^1.0/")) do |v|
37
- options[:search] = v
38
- end
39
- on('-d', '--database SOURCE',
41
+ on('-d', '--database FILE/URL',
40
42
  'a JSON file name, or a URL that contains the index',
41
- 'By default, the Arduino-maintained list is searched') do |v|
43
+ 'Defaults to the Arduino-maintained list') do |v|
42
44
  options[:database] = v
43
45
  end
44
- on('-m', '--max LIMIT',
46
+ on('-m', '--max NUMBER',
45
47
  'if provided, limits the result set to this number',
46
- 'Default value is 100') do |v|
47
- options[:limit] = v.to_i
48
+ 'Defaults to 100') do |v|
49
+ options[:limit] = v.to_i if v
48
50
  end
49
51
  end
50
52
 
51
53
  def option_abort_if_exists
52
- on('-e', '--abort-on-exiting',
53
- 'Abort if a library folder already exists',
54
- 'instead of updating it.') do |v|
55
- options[:abort_if_exists] = true
54
+ on('-e', '--if-exists ACTION',
55
+ 'If a library folder already exists, by default',
56
+ 'it will be overwritten or updated if possible.',
57
+ 'Alternatively you can either ' + 'abort'.bold.blue + ' or ' + 'backup'.bold.blue
58
+ ) do |v|
59
+ if v =~ /abort/i
60
+ options[:abort_if_exists] = true
61
+ elsif v =~ /backup/
62
+ options[:create_backup] = true
63
+ elsif v =~ /replace/
64
+ end
56
65
  end
66
+ sep ' '
57
67
  end
58
68
 
59
- def option_help(commands: false, command: nil)
69
+ def option_help(commands: false, command_name: nil)
70
+ on('-D', '--debug',
71
+ 'Print debugging info.') do |v|
72
+ options[:debug] = true
73
+ end
74
+ on('-t', '--trace',
75
+ 'Print exception stack traces.') do |v|
76
+ options[:trace] = v
77
+ end
78
+ on('-v', '--verbose',
79
+ 'Print more information.') do |v|
80
+ options[:verbose] = true
81
+ end
82
+ on('-V', '--version',
83
+ 'Print current version and exit') do |v|
84
+ output 'Version: ' + Arli::VERSION
85
+ options[:help] = true
86
+ end
60
87
  on('-h', '--help', 'prints this help') do
61
- puts 'Description:'.bold if command
62
- output ' ' * 4 + command[:description].bold.green if command
88
+ output 'Description:' if command_name
89
+ output ' ' * 4 + command_name[:description].green if command_name
63
90
  output ''
64
91
  output_help
65
92
  output_command_help if commands
93
+
94
+ if command_name && command_name[:example]
95
+ output 'Example:'
96
+ output ' ' * 4 + command_name[:example]
97
+ end
98
+
66
99
  options[:help] = true
67
100
  end
68
101
  end
69
102
 
70
- def option_library
71
- on('-n', '--lib-name LIBRARY', 'Library Name') { |v| options[:library_name] = v }
72
- on('-f', '--from FROM', 'A git or https URL') { |v| options[:library_from] = v }
73
- on('-v', '--version VERSION', 'Library Version, i.e. git tag') { |v| options[:library_version] = v }
74
- on('-i', '--install', 'Install a library') { |v| options[:library_action] = :install }
75
- on('-r', '--remove', 'Remove a library') { |v| options[:library_action] = :remove }
76
- on('-u', '--update', 'Update a local library') { |v| options[:library_action] = :update }
77
- end
78
-
79
103
  def option_help_with_subtext
80
104
  option_help
81
105
  end
@@ -89,14 +113,14 @@ module Arli
89
113
  end
90
114
 
91
115
  def command_help
92
- subtext = "Available Commands:\n".bold
116
+ subtext = "Available Commands:\n"
93
117
 
94
118
  ::Arli::CLI.commands.each_pair do |command, config|
95
119
  subtext << %Q/#{sprintf(' %-12s', command.to_s).green} : #{sprintf('%s', config[:description]).yellow}\n/
96
120
  end
97
121
  subtext << <<-EOS
98
122
 
99
- See #{COMMAND.bold.blue + ' <command> '.bold.green + '--help'.bold.yellow} for more information on a specific command.
123
+ See #{COMMAND.blue + ' <command> '.green + '--help'.yellow} for more information on a specific command.
100
124
 
101
125
  EOS
102
126
  subtext
@@ -112,9 +136,8 @@ See #{COMMAND.bold.blue + ' <command> '.bold.green + '--help'.bold.yellow} for m
112
136
  end
113
137
 
114
138
  def default_library_path
115
- Arduino::Library::DEFAULT_ARDUINO_LIBRARY_PATH.gsub(%r(#{ENV['HOME']}), '~').blue
139
+ ::Arli.config.library_path.gsub(%r(#{ENV['HOME']}), '~').blue
116
140
  end
117
-
118
141
  end
119
142
  end
120
143
  end