arli 0.3.2 → 0.5.1

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