arli 0.7.0 → 0.8.3

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.
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