arli 0.7.0 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Gemfile +0 -2
  4. data/README.md +149 -48
  5. data/arli.gemspec +1 -3
  6. data/docs/arli-in-action.png +0 -0
  7. data/docs/arlifile.png +0 -0
  8. data/exe/arli +5 -1
  9. data/lib/arli.rb +2 -2
  10. data/lib/arli/actions.rb +6 -1
  11. data/lib/arli/actions/action.rb +57 -37
  12. data/lib/arli/actions/dir_name.rb +18 -16
  13. data/lib/arli/actions/git_repo.rb +8 -8
  14. data/lib/arli/actions/move_to_library_path.rb +54 -0
  15. data/lib/arli/actions/{zip_file.rb → unzip_file.rb} +14 -10
  16. data/lib/arli/arli_file.rb +52 -32
  17. data/lib/arli/cli/app.rb +11 -4
  18. data/lib/arli/cli/command_finder.rb +14 -11
  19. data/lib/arli/cli/parser.rb +70 -29
  20. data/lib/arli/cli/parser_factory.rb +105 -50
  21. data/lib/arli/cli/runner.rb +8 -4
  22. data/lib/arli/commands.rb +1 -0
  23. data/lib/arli/commands/base.rb +11 -6
  24. data/lib/arli/commands/bundle.rb +43 -0
  25. data/lib/arli/commands/install.rb +56 -13
  26. data/lib/arli/commands/search.rb +82 -27
  27. data/lib/arli/configuration.rb +37 -18
  28. data/lib/arli/errors.rb +6 -0
  29. data/lib/arli/library.rb +4 -46
  30. data/lib/arli/library/installer.rb +71 -0
  31. data/lib/arli/library/proxy.rb +79 -0
  32. data/lib/arli/lock/file.rb +65 -0
  33. data/lib/arli/lock/formats.rb +4 -0
  34. data/lib/arli/lock/formats/base.rb +26 -0
  35. data/lib/arli/lock/formats/cmake.rb +24 -0
  36. data/lib/arli/lock/formats/json.rb +25 -0
  37. data/lib/arli/lock/formats/text.rb +13 -0
  38. data/lib/arli/lock/formats/yaml.rb +14 -0
  39. data/lib/arli/output.rb +94 -11
  40. data/lib/arli/version.rb +1 -1
  41. metadata +17 -35
  42. data/docs/arli-full.png +0 -0
  43. data/lib/arli/installer.rb +0 -52
@@ -10,9 +10,13 @@ module Arli
10
10
  # This action renames invalid library folders based on the
11
11
  # source files found inside.
12
12
  class DirName < Action
13
+
14
+ description 'Auto-detects the canonical library folder name'
15
+
13
16
  attr_accessor :sources, :headers
14
17
 
15
- def act
18
+ def execute
19
+
16
20
  find_source_files
17
21
 
18
22
  # so "dir" is the 'Adafruit_Unified_Sensor'
@@ -20,29 +24,27 @@ module Arli
20
24
  # rename the folder
21
25
 
22
26
  if headers.include?(dir) || sources.include?(dir)
23
- print_target_dir(dir)
27
+ set_canonical_dir!(dir)
24
28
  else
25
- # if we end up setting this, we'll also move the folder.
26
- canonical_dir =
29
+ candidate =
27
30
  if_only_one(headers) ||
28
31
  if_only_one(sources) ||
29
32
  if_header_a_substring(headers)
30
33
 
31
- if canonical_dir
32
- library.canonical_dir = canonical_dir
33
- mv(dir, library.canonical_dir)
34
- print_target_dir(canonical_dir)
35
- else
36
- library.canonical_dir = dir
37
- print_target_dir(dir)
38
- end
34
+ set_canonical_dir!(candidate)
39
35
  end
40
-
41
-
42
36
  end
43
37
 
44
- def print_target_dir(d)
45
- ___ " installed to #{d.green} #{'✔'.green}" unless Arli.config.quiet
38
+ private
39
+
40
+ def set_canonical_dir!(canonical_dir)
41
+ if canonical_dir && canonical_dir != dir
42
+ mv(dir, canonical_dir)
43
+ library.canonical_dir = canonical_dir
44
+ else
45
+ library.canonical_dir = dir
46
+ end
47
+ print_target_dir(library.canonical_dir, 'installed to')
46
48
  end
47
49
 
48
50
  def if_header_a_substring(files)
@@ -2,13 +2,13 @@ require_relative 'action'
2
2
  module Arli
3
3
  module Actions
4
4
  class GitRepo < Action
5
+ description 'Fetches or updates remote git repositories'
6
+ check_command 'git --version'
7
+ check_pattern 'git version'
5
8
 
6
- def act
7
- c = library.exists? ? git_update_command : git_clone_command
8
- execute(c)
9
- rescue Exception => e
10
- fuck
11
- raise e
9
+ def execute
10
+ run_system_command(git_clone_command)
11
+ print_action_success('cloned', "cloned from #{library.url}")
12
12
  end
13
13
 
14
14
  def git_update_command
@@ -22,7 +22,7 @@ module Arli
22
22
  protected
23
23
 
24
24
  # @param <String> *args — list of arguments or a single string
25
- def execute(*args)
25
+ def run_system_command(*args)
26
26
  cmd = args.join(' ')
27
27
  raise 'No command to run was given' unless cmd
28
28
  info("\n" + cmd.green) if Arli.debug?
@@ -31,7 +31,7 @@ module Arli
31
31
  info("\n" + e.red) if e && Arli.debug?
32
32
  rescue Exception => e
33
33
  error "Error running [#{args.join(' ')}]\n" +
34
- "Current folder is [#{Dir.pwd.yellow}]", e
34
+ "Current folder is [#{Dir.pwd.yellow}]", e
35
35
  raise e
36
36
  end
37
37
  end
@@ -0,0 +1,54 @@
1
+ require_relative 'action'
2
+
3
+ module Arli
4
+ module Actions
5
+ class MoveToLibraryPath < Action
6
+ description 'Moves the downloaded library to the proper path, optionally creating a backup'
7
+
8
+ def execute
9
+ Dir.chdir(config.runtime.pwd) do
10
+
11
+ handle_preexisting_folder(path) if exists?
12
+
13
+ if Dir.exist?(temp_path) && !Dir.exist?(path)
14
+ FileUtils.mkdir_p(File.dirname(path))
15
+ ___ "current: #{Dir.pwd.yellow}\ntemp_path: #{temp_path.yellow}\nlibrary_path: #{path.yellow}\n" if debug?
16
+ mv(temp_path, path)
17
+ elsif Dir.exist?(path)
18
+ raise ::Arli::Errors::InstallerError,
19
+ "Directory #{path} was not expected to still be there!"
20
+ elsif !Dir.exist?(temp_path)
21
+ raise ::Arli::Errors::InstallerError,
22
+ "Directory #{temp_path} was expected to exist!"
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def handle_preexisting_folder(to)
30
+ if Dir.exist?(to)
31
+ if abort?
32
+ raise ::Arli::Errors::LibraryAlreadyExists, "Directory #{to} already exists"
33
+ elsif backup?
34
+ backup!(to)
35
+ elsif overwrite?
36
+ FileUtils.rm_rf(to)
37
+ end
38
+ end
39
+ end
40
+
41
+ def backup!(p)
42
+ if Dir.exist?(p)
43
+ backup_path = "#{p}.arli-backup-#{Time.now.strftime('%Y%m%d%H%M%S')}"
44
+ FileUtils.mv(p, backup_path)
45
+ print_target_dir(backup_path, 'backed up')
46
+ if verbose?
47
+ ___ "\nNOTE: path #{p.blue} has been backed up to #{backup_path.bold.green}\n"
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -1,24 +1,28 @@
1
- require 'archive/zip'
2
1
  require_relative 'action'
3
2
 
4
3
  module Arli
5
4
  module Actions
6
- class ZipFile < Action
7
- def act
8
- # TODO: verify that this works if backup option is provided
9
- library.rm_rf!
5
+ class UnzipFile < Action
6
+ description 'Downloads and unzip remote ZIP archives'
7
+ check_command 'unzip -h'
8
+ check_pattern 'extract files to pipe'
9
+
10
+ def execute
11
+ return if library.url.nil?
12
+ return if library.url !~ /\.zip$/i
13
+
10
14
  download!
15
+
11
16
  if File.exist?(zip_archive)
12
- FileUtils.rm_rf(zip_folder) if zip_folder
17
+ FileUtils.rm_rf(top_dir_inside_zip) if top_dir_inside_zip
13
18
  unzip(zip_archive, '.')
14
- FileUtils.move(zip_folder, dir) if Dir.exist?(zip_folder)
19
+ FileUtils.move(top_dir_inside_zip, dir) if Dir.exist?(top_dir_inside_zip)
15
20
  end
16
21
  rescue Exception => e
17
22
  fuck
18
- puts
19
23
  raise(e)
20
24
  ensure
21
- delete_zip!
25
+ delete_zip! rescue nil
22
26
  end
23
27
 
24
28
  private
@@ -36,7 +40,7 @@ module Arli
36
40
  end
37
41
 
38
42
  # list the contents of the archive and grab the top level folder
39
- def zip_folder
43
+ def top_dir_inside_zip
40
44
  @zip_folder ||= `unzip -Z1 #{zip_archive} | awk 'BEGIN{FS="/"}{print $1}' | uniq | tail -1`.chomp
41
45
  end
42
46
 
@@ -2,6 +2,7 @@ require 'arli'
2
2
  require 'yaml'
3
3
  require 'forwardable'
4
4
  require 'arduino/library'
5
+ require 'hashie/mash'
5
6
 
6
7
  module Arli
7
8
  class ArliFile
@@ -13,58 +14,77 @@ module Arli
13
14
  def_delegators :@dependencies, *(Array.new.methods - Object.methods)
14
15
 
15
16
  attr_accessor :dependencies,
16
- :file_hash,
17
- :file,
18
- :library_path
17
+ :parsed_data,
18
+ :arlifile_path
19
19
 
20
20
  def initialize(config: Arli.config, libraries: [])
21
- self.library_path = config.libraries.path
22
- FileUtils.mkpath(library_path) unless Dir.exist?(library_path)
23
-
24
- if libraries && !libraries.empty?
25
- self.dependencies = libraries.map{ |lib| make_lib(lib) }
26
- else
27
- self.file = "#{config.arlifile.path}/#{config.arlifile.name}"
28
- unless file && File.exist?(file)
29
- raise(Arli::Errors::ArliFileNotFound,
30
- "Arlifile could not be found at\n#{file.bold.yellow}")
31
- end
32
- self.dependencies = parse_file
33
- end
21
+ self.arlifile_path = "#{config.arlifile.path}/#{config.arlifile.name}"
22
+ self.dependencies = read_dependencies(libraries)
23
+ Arli.config.libraries.temp_dir ||= Dir.mktmpdir
34
24
  end
35
25
 
36
- def within_library_path
37
- Dir.chdir(library_path) do
38
- yield if block_given?
39
- end
40
- end
26
+ alias libraries dependencies
41
27
 
42
- def each_dependency(&block)
43
- within_library_path { dependencies.each(&block) }
28
+ def install
29
+ each_in_temp_path do |lib|
30
+ lib.install
31
+ end
44
32
  end
45
33
 
46
34
  private
47
35
 
48
- def parse_file
49
- self.file_hash = ::YAML.load(::File.read(self.file))
50
- file_hash['dependencies'].map { |lib| make_lib(lib) }
36
+ def within_temp_path(&block)
37
+ within_path(Arli.config.libraries.temp_dir, &block)
51
38
  end
52
39
 
53
- def make_lib(lib)
54
- ::Arli::Library.new(library_model(lib))
40
+ def each_in_temp_path(&block)
41
+ within_temp_path do
42
+ dependencies.each(&block)
43
+ end
55
44
  end
56
45
 
46
+
57
47
  def library_model(lib)
48
+ return lib if lib.is_a?(::Arduino::Library::Model)
49
+
58
50
  ::Arduino::Library::Model.from(lib).tap do |model|
59
51
  if model.nil?
60
52
  lib_output = (lib && lib['name']) ? lib['name'] : lib.inspect
61
53
  raise Arli::Errors::LibraryNotFound, 'Error: '.bold.red +
62
- "Library #{lib_output.yellow} ".red + "was not found.\n\n".red +
63
- %Q[ HINT: run #{"arli search 'name: /#{lib_output}/'".green}\n] +
64
- %Q[ to find the exact name of the library you are trying\n] +
65
- %Q[ to install. Alternatively, provide a url: field.\n]
54
+ "Library #{lib_output.yellow} ".red + "was not found.\n\n".red +
55
+ %Q[ HINT: run #{"arli search 'name: /#{lib_output}/'".green}\n] +
56
+ %Q[ to find the exact name of the library you are trying\n] +
57
+ %Q[ to install. Alternatively, provide a url: field.\n]
58
+ end
59
+ end
60
+ end
61
+
62
+ def make_lib(lib)
63
+ ::Arli::Library::Proxy.new(library_model(lib))
64
+ end
65
+
66
+ def within_path(p, &_block)
67
+ FileUtils.mkpath(p) unless Dir.exist?(p)
68
+ Dir.chdir(p) do
69
+ yield if block_given?
70
+ end
71
+ end
72
+
73
+ def read_dependencies(libraries)
74
+ if libraries && !libraries.empty?
75
+ libraries.map { |lib| make_lib(lib) }
76
+ else
77
+ unless arlifile_path && File.exist?(arlifile_path)
78
+ raise(Arli::Errors::ArliFileNotFound,
79
+ "Arlifile could not be found at\n#{arlifile_path.bold.yellow}")
66
80
  end
81
+ parse_yaml_file
67
82
  end
68
83
  end
84
+
85
+ def parse_yaml_file
86
+ self.parsed_data = Hashie::Mash.new(::YAML.load(::File.read(self.arlifile_path)))
87
+ parsed_data.dependencies.map { |lib| make_lib(lib) }
88
+ end
69
89
  end
70
90
  end
@@ -1,7 +1,7 @@
1
1
  require 'forwardable'
2
2
  require 'optparse'
3
3
  require 'colored2'
4
-
4
+ require 'tmpdir'
5
5
  require_relative 'parser'
6
6
  require_relative 'command_finder'
7
7
  require_relative 'parser_factory'
@@ -17,9 +17,11 @@ module Arli
17
17
  attr_accessor :argv, :config, :command
18
18
 
19
19
  def initialize(argv, config: Arli.config)
20
- self.argv = argv
21
- self.config = config
22
- self.config.runtime.argv = argv
20
+ self.argv = argv
21
+ self.config = config
22
+ self.config.runtime.argv = argv
23
+ self.config.runtime.pwd = ::Dir.pwd
24
+ Arli.config.libraries.temp_dir = ::Dir.mktmpdir
23
25
  end
24
26
 
25
27
  def start
@@ -47,8 +49,13 @@ module Arli
47
49
  report_exception(e, 'Invalid flags or options')
48
50
  rescue Arli::Errors::InvalidCommandError => e
49
51
  report_exception(e, 'Unknown command')
52
+ rescue Arli::Errors::InvalidSyntaxError => e
53
+ report_exception(e, 'Incorrect command usage')
50
54
  rescue Exception => e
51
55
  report_exception(e)
56
+ ensure
57
+ d = Arli.config.libraries.temp_dir
58
+ FileUtils.rm_rf(d) if Dir.exist?(d) rescue nil
52
59
  end
53
60
 
54
61
  def parse_global_flags
@@ -36,11 +36,19 @@ module Arli
36
36
  self
37
37
  end
38
38
 
39
+ def detect_command
40
+ return nil unless non_flag_argument?
41
+ cmd = argv.shift.to_sym
42
+ if factory.valid_command?(cmd)
43
+ cmd
44
+ else
45
+ raise_invalid_arli_command!(cmd)
46
+ end
47
+ end
48
+
39
49
  def parse_command_arguments!(cmd)
40
50
  parser = factory.command_parser(cmd)
41
- if parser
42
- factory.parse_argv(parser, argv)
43
- end
51
+ factory.parse_argv(parser, argv) if parser
44
52
  end
45
53
 
46
54
  def instantiate_command
@@ -53,14 +61,9 @@ module Arli
53
61
  end
54
62
  end
55
63
 
56
- def detect_command
57
- return nil unless argv.first && argv.first !~ /^-.*$/
58
- cmd = argv.shift.to_sym
59
- if factory.valid_command?(cmd)
60
- cmd
61
- else
62
- raise_invalid_arli_command!(cmd)
63
- end
64
+
65
+ def non_flag_argument?
66
+ argv.first && argv.first !~ /^-.*$/
64
67
  end
65
68
 
66
69
  def factory
@@ -1,8 +1,10 @@
1
1
  require 'arli/configuration'
2
+ require 'colored2'
3
+ require 'optionparser'
2
4
 
3
5
  module Arli
4
6
  module CLI
5
- class Parser < OptionParser
7
+ class Parser < ::OptionParser
6
8
 
7
9
  attr_accessor :output_lines,
8
10
  :command,
@@ -21,38 +23,51 @@ module Arli
21
23
  end
22
24
 
23
25
  def option_install
24
- option_library_name
25
26
  option_lib_home
26
- option_dependency_file
27
27
  option_if_exists
28
28
  end
29
29
 
30
- def option_dependency_file
30
+ def option_bundle
31
+ option_lib_home
32
+ option_arlifile_path
33
+ option_arlifile_lock_format
34
+ option_if_exists
35
+ end
36
+
37
+ def option_arlifile_path
31
38
  on('-a', '--arli-path PATH',
32
- 'Folder where ' + 'Arlifile'.green + ' is located,',
39
+ 'An alternate folder with the ' + 'Arlifile'.green + ' file.',
33
40
  "Defaults to the current directory.\n\n") do |v|
34
41
  config.arlifile.path = v
35
42
  end
36
43
  end
37
44
 
38
- def option_library_name
39
- on('-n', '--name NAME',
40
- 'If provided a library name is searched and, if found',
41
- 'installed. In this mode Arlifile not used.' + "\n\n") do |v|
42
- config.install.library_names << { name: v }
45
+ SUPPORTED_FORMATS = %w[cmake text json yaml]
46
+
47
+ def option_arlifile_lock_format
48
+ on('-f', '--format FMT',
49
+ "Arli writes an #{'Arlifile.lock'.green} with resolved info.",
50
+ "The default format is #{'text'.bold.yellow}. Use -f to set it",
51
+ "to one of: #{SUPPORTED_FORMATS.join(', ').bold.yellow}\n\n") do |v|
52
+ if SUPPORTED_FORMATS.include?(v.downcase)
53
+ config.arlifile.lock_format = v.downcase.to_sym
54
+ else
55
+ raise ::OptionParser::InvalidOption,
56
+ "#{v.yellow} is not a supported lock file format"
57
+ end
43
58
  end
44
59
  end
45
60
 
46
61
  def option_lib_home
47
- on('-l', '--libraries PATH',
48
- 'Local folder where custom Arduino libraries are installed',
49
- "Defaults to #{Arli.default_library_path}\n\n") do |v|
62
+ on('-l', '--lib-path PATH',
63
+ 'Destination: typically your Arduino libraries folder',
64
+ "Defaults to #{'~/Documents/Arduino/Libraries'.green}\n\n") do |v|
50
65
  config.libraries.path = v
51
66
  end
52
67
  end
53
68
 
54
69
  def option_search
55
- on('-d', '--database FILE/URL',
70
+ on('-d', '--database URL',
56
71
  'a JSON(.gz) file path or a URL of the library database.',
57
72
  'Defaults to the Arduino-maintained database.') do |v|
58
73
  config.database.path = v
@@ -72,10 +87,10 @@ module Arli
72
87
  'Alternatively you can either ' + 'abort'.bold.blue + ' or ' + 'backup'.bold.blue
73
88
  ) do |v|
74
89
  if v == 'abort'
75
- config.install.if_exists.abort = true
76
- config.install.if_exists.overwrite = false
90
+ config.if_exists.abort = true
91
+ config.if_exists.overwrite = false
77
92
  elsif v == 'backup'
78
- config.install.if_exists.backup = true
93
+ config.if_exists.backup = true
79
94
  end
80
95
  end
81
96
  sep ' '
@@ -87,13 +102,7 @@ module Arli
87
102
  on('-h', '--help', 'prints this help') do
88
103
  ::Arli.config.help = true
89
104
 
90
- command_hash = factory.command_parsers[command_name]
91
-
92
- if command_hash && command_hash[:description]
93
- output 'Description:'
94
- output ' ' + command_hash[:description]
95
- output ''
96
- end
105
+ command_hash = output_command_description(command_name)
97
106
 
98
107
  output_help
99
108
  output_command_help if commands
@@ -106,9 +115,14 @@ module Arli
106
115
  end
107
116
  end
108
117
 
118
+ def header(string)
119
+ output "#{string.bold.magenta}:"
120
+ output
121
+ end
122
+
109
123
  def output_examples(examples)
110
- output 'Examples:'
111
- indent = ' '
124
+ header 'Examples'
125
+ indent = ' '
112
126
  examples.each do |example|
113
127
  output
114
128
  output indent + '# ' + example[:desc]
@@ -131,9 +145,10 @@ module Arli
131
145
 
132
146
  def command_help
133
147
 
134
- subtext = "Available Commands:\n"
148
+ header 'Available Commands'
149
+ subtext = ''
135
150
  factory.command_parsers.each_pair do |command, config|
136
- subtext << %Q/#{sprintf(' %-12s', command.to_s).green} — #{sprintf('%s', config[:description]).blue}\n/
151
+ subtext << %Q/#{sprintf(' %-12s', command.to_s).green} — #{sprintf('%s', config[:sentence]).blue}\n/
137
152
  end
138
153
  subtext << <<-EOS
139
154
 
@@ -156,6 +171,10 @@ See #{Arli::Configuration::ARLI_COMMAND.blue + ' command '.green + '--help'.yell
156
171
  end
157
172
 
158
173
  def common_help_options
174
+ on('-C', '--no-color',
175
+ 'Disable any color output.') do |*|
176
+ Colored2.disable! # if $stdout.tty?
177
+ end
159
178
  on('-D', '--debug',
160
179
  'Print debugging info.') do |v|
161
180
  config.debug = true
@@ -164,6 +183,10 @@ See #{Arli::Configuration::ARLI_COMMAND.blue + ' command '.green + '--help'.yell
164
183
  'Print exception stack traces.') do |v|
165
184
  config.trace = v
166
185
  end
186
+ # on('-n', '--dry-run',
187
+ # 'Only print actions, but do not do them.') do |v|
188
+ # config.trace = v
189
+ # end
167
190
  on('-v', '--verbose',
168
191
  'Print more information.') do |v|
169
192
  config.verbose = true
@@ -181,7 +204,25 @@ See #{Arli::Configuration::ARLI_COMMAND.blue + ' command '.green + '--help'.yell
181
204
 
182
205
  def print_version_copyright
183
206
  output << Arli::Configuration::ARLI_COMMAND.bold.yellow + ' (' + Arli::VERSION.bold.green + ')' +
184
- " © 2017 Konstantin Gredeskoul, MIT License."
207
+ ' © 2017 Konstantin Gredeskoul, MIT License.'.dark unless Arli.config.quiet
208
+ end
209
+
210
+ def output_command_description(command_name)
211
+ command_hash = factory.command_parsers[command_name]
212
+ indent = ' '
213
+
214
+ if command_hash
215
+ if command_hash.description
216
+ header 'Description'
217
+ if command_hash.sentence
218
+ output indent + command_hash.sentence.bold
219
+ output ''
220
+ end
221
+ output indent + Array(command_hash[:description]).map(&:dark).join('').gsub(/\n/, "\n#{indent}")
222
+ end
223
+ end
224
+
225
+ command_hash
185
226
  end
186
227
  end
187
228
  end