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
@@ -9,13 +9,13 @@ module Arli
9
9
  def default_help
10
10
  gp = global_parser
11
11
  gp.parse!(%w(--help))
12
- gp.print
12
+ print_parser_help(gp)
13
13
  end
14
14
 
15
15
  def parse_argv(parser, argv)
16
16
  if parser
17
17
  parser.parse!(argv)
18
- parser.print
18
+ print_parser_help(parser)
19
19
  end
20
20
  end
21
21
 
@@ -34,47 +34,95 @@ module Arli
34
34
 
35
35
  def command_parsers
36
36
  @command_parsers ||= {
37
- install: {
38
- description: 'installs libraries defined in Arlifile or by -n flag',
39
- examples: [
40
- { desc: 'Install all libs defined in the ./Arlifile file',
41
- cmd: 'arli install' },
42
- { desc: 'Install a single library matched by the --name flag',
43
- cmd: 'arli install -n "Adafruit GFX Library"' }
44
- ],
45
-
46
- parser: -> (command_name) {
47
- make_parser(command_name) do |parser|
48
- parser.banner = usage_line 'install'
49
- parser.option_install
50
- parser.option_help(command_name: command_name)
51
- end
52
- } },
53
-
54
- search: {
55
- description: 'Flexible Search of the Arduino Library Database',
56
- examples: [
57
- { desc: 'Search using the regular expression containing the name:',
58
- cmd: 'arli search AudioZero' },
59
-
60
- { desc: 'Same exact search as above, but using advanced ruby hash arguments syntax:',
61
- cmd: %Q{arli search 'name: /AudioZero/'} },
62
-
63
- { desc: 'Search using case insensitive name search:',
64
- cmd: %Q{arli search 'name: /adafruit/i'} },
65
-
66
- { desc: 'Finally, search for the exact name match:',
67
- cmd: %Q{arli search '^Time$'} },
68
- ],
69
-
70
- parser: -> (command_name) {
71
- make_parser(command_name) do |parser|
72
- parser.banner = usage_line 'search ' + '[ name | search-expression ]'.magenta
73
- parser.option_search
74
- parser.option_help(command_name: command_name)
75
- end
76
- }
77
- }
37
+ search: Hashie::Mash.new(
38
+ {
39
+ sentence: 'Search standard Arduino Library Database with over 4K entries',
40
+ description: ["This command provides both the simple name-based search interface,\n",
41
+ "and the most sophisticated field-by-field search using a downloaded, \n",
42
+ "and locally cached Public Arduino Database JSON file, maintained\n",
43
+ "by Arduino and the Community. If you know of another database,\n",
44
+ "that's what the --database flag is for."],
45
+ examples: [
46
+ { desc: 'Search using the regular expression containing the name:',
47
+ cmd: 'arli search AudioZero' },
48
+
49
+ { desc: 'Same exact search as above, but using ruby hash syntax:',
50
+ cmd: %Q{arli search 'name: /AudioZero/'} },
51
+
52
+ { desc: 'Lets get a particular version of the library',
53
+ cmd: %Q{arli search 'name: "AudioZero", version: "1.0,2"'} },
54
+
55
+ { desc: 'Search using case insensitive name search, and :',
56
+ cmd: %Q{arli search 'name: /adafruit/i'} },
57
+
58
+ { desc: 'Finally, search for the exact name match:',
59
+ cmd: %Q{arli search '^Time$'} },
60
+ ],
61
+
62
+ parser: -> (command_name) {
63
+ make_parser(command_name) do |parser|
64
+ parser.banner = usage_line 'search ' + '[ name | search-expression ]'.magenta
65
+ parser.option_search
66
+ parser.option_help(command_name: command_name)
67
+ end
68
+ }
69
+ }),
70
+
71
+ bundle: Hashie::Mash.new(
72
+ {
73
+ sentence: 'Installs all libraries specified in Arlifile',
74
+ description:
75
+ [
76
+ "This command reads ", "Arlifile".bold.green, " (from the current folder, by default),\n",
77
+ "and then it installs all dependent libraries specified there, checking if \n",
78
+ "each already exists, and if not — downloading them, and installing them into\n",
79
+ "your Arduino Library folder. Both the folder with the Arlifile, as well as the\n",
80
+ "destination library path folder can be changed with the command line flags.\n",
81
+ ],
82
+ examples: [
83
+ { desc: 'Install all libs defined in Arlifile:',
84
+ cmd: 'arli bundle ' },
85
+
86
+ { desc: 'Custom Arlifile location, and destination path:',
87
+ cmd: 'arli bundle -a ./src -l ./libraries' }
88
+ ],
89
+
90
+ parser: -> (command_name) {
91
+ make_parser(command_name) do |parser|
92
+ parser.banner = usage_line 'bundle'
93
+ parser.option_bundle
94
+ parser.option_help(command_name: command_name)
95
+ end
96
+ } }),
97
+
98
+ install: Hashie::Mash.new(
99
+ {
100
+ sentence: 'Installs a single library either by searching, or url or local ZIP',
101
+ description: [
102
+ "This command installs a single library into your library path\n",
103
+ "using the third argument to the command #{'arli install'.bold.white}\n".dark ,
104
+ "which can be a library name, local ZIP file, or a remote URL \n",
105
+ "(either ZIP or Git Repo)\n"
106
+
107
+ ],
108
+ examples: [
109
+ { desc: 'Install the latest version of this library',
110
+ cmd: 'arli install "Adafruit GFX Library"' },
111
+
112
+ { desc: 'Install the library from a Github URL',
113
+ cmd: 'arli install https://github.com/jfturcot/SimpleTimer' },
114
+
115
+ { desc: 'Install a local ZIP file',
116
+ cmd: 'arli install ~/Downloads/DHT-Library.zip' },
117
+ ],
118
+
119
+ parser: -> (command_name) {
120
+ make_parser(command_name) do |parser|
121
+ parser.banner = usage_line 'install' + ' [ "library name" | url | local-zip ] '.magenta
122
+ parser.option_install
123
+ parser.option_help(command_name: command_name)
124
+ end
125
+ } }),
78
126
  }
79
127
  end
80
128
 
@@ -93,21 +141,28 @@ module Arli
93
141
  end
94
142
 
95
143
  def global_usage(command)
96
- "Usage:\n " + Arli::Configuration::ARLI_COMMAND.blue +
97
- ' [ options ] '.yellow + '[ ' + (command || 'command').green +
98
- ' [ options ] '.yellow + ' ]' + "\n"
144
+ 'Usage:'.magenta.bold +
145
+ "\n " + arli_command + ' options '.yellow +
146
+ "\n " + arli_command + ' ' + ((command || 'command')).green + ' [ options ] '.yellow + "\n"
147
+ end
148
+
149
+ def arli_command
150
+ @arli_command ||= Arli::Configuration::ARLI_COMMAND.blue.bold
99
151
  end
100
152
 
101
153
  def command_usage(command)
102
- "Usage:\n " + Arli::Configuration::ARLI_COMMAND.blue + ' ' +
103
- command.green +
104
- ' [options]'.yellow + "\n\n" +
105
- 'Command Options'
154
+ 'Usage:'.magenta.bold +
155
+ "\n " + arli_command + ' ' + command.green + ' [options]'.yellow + "\n\n" +
156
+ 'Options'.magenta.bold
106
157
  end
107
158
 
108
159
  def usage_line(command = nil)
109
160
  command ? command_usage(command) : global_usage(command)
110
161
  end
162
+
163
+ def print_parser_help(parser)
164
+ parser.print
165
+ end
111
166
  end
112
167
 
113
168
 
@@ -17,12 +17,13 @@ module Arli
17
17
  $stdout = @stdout
18
18
 
19
19
  Arli::CLI::App.new(@argv).start
20
+ print_debug_info
20
21
  0
21
22
  rescue StandardError => e
22
23
  b = e.backtrace
23
- @stderr.puts("#{b.shift.bold}:\n")
24
+ @stderr.puts("#{b.shift.bold}:\n") if Arli.config.debug
24
25
  @stderr.puts("#{e.message.bold.red} (#{e.class.name.yellow})")
25
- @stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n")).red
26
+ @stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n").red)
26
27
  1
27
28
  rescue SystemExit => e
28
29
  e.status
@@ -30,11 +31,14 @@ module Arli
30
31
  $stderr = STDERR
31
32
  $stdin = STDIN
32
33
  $stdout = STDOUT
33
- ap(Arli.config.to_hash, indent: 6, index: false) if Arli.config.debug
34
34
  end
35
35
  @kernel.exit(exit_code)
36
36
  end
37
+
38
+ def print_debug_info
39
+ $stdout.puts JSON.pretty_generate(Arli.config.to_hash).gsub(/("\w+":)/, '\1'.bold.blue) \
40
+ if Arli.config.debug
41
+ end
37
42
  end
38
43
  end
39
-
40
44
  end
@@ -5,4 +5,5 @@ end
5
5
 
6
6
  require_relative 'commands/base'
7
7
  require_relative 'commands/search'
8
+ require_relative 'commands/bundle'
8
9
  require_relative 'commands/install'
@@ -15,14 +15,9 @@ module Arli
15
15
 
16
16
  def initialize(config: Arli.config)
17
17
  self.config = config
18
- FileUtils.mkdir_p(library_path) unless Dir.exist?(library_path)
19
18
  setup
20
19
  end
21
20
 
22
- def run(*args)
23
- raise ArgumentError, 'This method must be implemented in subclasses'
24
- end
25
-
26
21
  def runtime
27
22
  config.runtime
28
23
  end
@@ -35,12 +30,22 @@ module Arli
35
30
  config.libraries.path
36
31
  end
37
32
 
33
+ def temp_path
34
+ config.libraries.temp_dir
35
+ end
36
+
38
37
  def setup
38
+ FileUtils.mkdir_p(library_path) unless Dir.exist?(library_path)
39
+ FileUtils.mkdir_p(temp_path) unless Dir.exist?(temp_path)
40
+ end
39
41
 
42
+ def run(*args)
43
+ raise Arli::Errors::AbstractMethodCalled,
44
+ 'This method must be implemented in subclasses'
40
45
  end
41
46
 
42
47
  def params
43
-
48
+ ''
44
49
  end
45
50
  end
46
51
  end
@@ -0,0 +1,43 @@
1
+ require 'json'
2
+ require 'arli'
3
+ require 'net/http'
4
+ require_relative 'base'
5
+ require_relative '../arli_file'
6
+ require_relative '../lock/formats'
7
+ require_relative '../lock/file'
8
+
9
+ module Arli
10
+ module Commands
11
+ class Bundle < Base
12
+
13
+ attr_accessor :arlifile, :lock_file
14
+
15
+ def setup
16
+ super
17
+ self.arlifile = Arli::ArliFile.new(config: config)
18
+ self.lock_file = Arli::Lock::File.new(config: config)
19
+ end
20
+
21
+ def params
22
+ if arlifile&.libraries
23
+ "libraries: \n • " + arlifile.libraries.map(&:name).join("\n • ")
24
+ end
25
+ end
26
+
27
+ def run
28
+ install_with_arli_file
29
+ end
30
+
31
+ protected
32
+
33
+ def install_with_arli_file
34
+ arlifile.install
35
+ post_install
36
+ end
37
+
38
+ def post_install
39
+ lock_file.lock!(*arlifile.libraries)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,31 +1,74 @@
1
+ require 'hashie/mash'
2
+ require 'net/http'
1
3
  require 'json'
2
4
  require 'arli'
3
- require 'net/http'
5
+
6
+ require 'arduino/library'
4
7
  require_relative 'base'
5
- require_relative '../installer'
8
+ require_relative 'bundle'
6
9
 
7
10
  module Arli
8
11
  module Commands
9
- class Install < Base
12
+ class Install < Bundle
13
+ require 'arduino/library/include'
14
+
15
+ attr_accessor :library
10
16
 
11
- attr_accessor :arlifile
17
+ def setup
18
+ validate_argument
19
+
20
+ self.library = identify_library(runtime.argv.first)
21
+ validate_library
12
22
 
13
- def initialize(*args)
14
- super(*args)
23
+ self.arlifile = Arli::ArliFile.new(config: config, libraries: [library]) if library
15
24
  end
16
25
 
17
- def setup
18
- self.arlifile = Arli::ArliFile.new(
19
- config: config,
20
- libraries: Arli.config.install.library_names)
26
+ # arg can be 'Adafruit GFX Library'
27
+ 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
21
41
  end
22
42
 
23
43
  def params
24
- "libraries: \n " + arlifile.dependencies.map(&:name).join("\n • ")
44
+ " • #{library.to_s}"
45
+ end
46
+
47
+ def post_install
48
+ #
49
+ end
50
+
51
+ private
52
+
53
+ def validate_search(arg, results)
54
+ raise Arli::Errors::LibraryNotFound,
55
+ "Can't find library by argument #{arg.bold.yellow}" if results.nil? || results.empty?
56
+ 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
25
68
  end
26
69
 
27
- def run
28
- arlifile.each_dependency { |lib| lib.install }
70
+ def cfg
71
+ config.install
29
72
  end
30
73
  end
31
74
  end
@@ -5,69 +5,124 @@ require 'arli'
5
5
  require 'arli/commands/base'
6
6
  require 'arli/errors'
7
7
  require 'arduino/library'
8
- require 'awesome_print'
9
8
 
10
9
  module Arli
11
10
  module Commands
12
11
  class Search < Base
12
+
13
+ LibraryWithVersion = Struct.new(:name, :versions)
14
+
13
15
  require 'arduino/library/include'
14
16
 
15
17
  attr_accessor :search_string,
16
18
  :search_opts,
17
19
  :results,
18
20
  :limit,
19
- :database
21
+ :database,
22
+ :format,
23
+ :hash,
24
+ :custom_print
20
25
 
21
26
  def initialize(*args)
22
27
  super(*args)
28
+ self.format = :short
29
+ self.hash = Hash.new
30
+ self.custom_print = true
23
31
  end
24
32
 
25
- def setup
26
- search = runtime.argv.first
33
+ def run
34
+ self.search_opts = process_search_options!
35
+ self.results = search(database, **search_opts).sort
27
36
 
28
- self.search_string = if search =~ /:/
29
- search
30
- elsif search
31
- "name: /#{search}/"
32
- end
37
+ results.map do |lib|
38
+ hash[lib.name] ||= LibraryWithVersion.new(lib.name, [])
39
+ hash[lib.name].versions << lib.version
33
40
 
34
- self.limit = config.search.results.limit
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
47
 
36
- unless search_string
37
- raise Arli::Errors::InvalidSyntaxError,
38
- 'Please provide search string after the "search" command'
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
54
  end
55
+ print_total_with_help
56
+ rescue Exception => e
57
+ error e
58
+ puts e.backtrace.join("\n") if ENV['DEBUG']
59
+ end
40
60
 
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})": '')
65
+ end
66
+
67
+ def process_search_options!
68
+ self.search_string = extract_search_argument!
69
+ raise_error_unless_search_string!
70
+
71
+ self.limit = config.search.results.limit
72
+ search_opts = {}
41
73
  begin
42
- self.search_opts = eval("{ #{search_string} }")
74
+ search_opts = eval("{ #{search_string} }")
43
75
  rescue => e
44
- raise Arli::Errors::InvalidSyntaxError, "Search string '#{search_string}' is invalid.\n" +
45
- e.message.red
76
+ handle_error(e)
46
77
  end
47
78
 
48
79
  unless search_opts.is_a?(::Hash) && search_opts.size > 0
49
- raise Arli::Errors::InvalidSyntaxError, "Search string '#{search_string}' did not eval to Hash.\n"
80
+ raise Arli::Errors::InvalidSearchSyntaxError,
81
+ "Search string '#{search_string}' did not eval to Hash.\n"
50
82
  end
51
83
 
52
84
  self.database = db_default
53
85
 
54
86
  search_opts.merge!(limit: limit) if limit && limit > 0
55
87
  search_opts.delete(:limit) if limit == 0
88
+ search_opts
56
89
  end
57
90
 
58
- def run
59
- self.results = search(database, **search_opts)
60
- results.sort.map do |lib|
61
- puts pretty_library(lib)
91
+ def extract_search_argument!
92
+ search = runtime.argv.first
93
+ if search =~ /:/
94
+ search
95
+ elsif search
96
+ "#{config.search.default_field}: /^#{search}$/"
62
97
  end
63
- puts "\nTotal matches: #{results.size.to_s.bold.magenta}"
64
- rescue Exception => e
65
- error e
66
- puts e.backtrace.join("\n") if ENV['DEBUG']
67
98
  end
68
99
 
69
- def pretty_library(lib, **options)
70
- "#{lib.name.bold.blue} (#{lib.version.yellow}), by #{lib.author.magenta}"
100
+ def raise_error_unless_search_string!
101
+ unless search_string
102
+ raise Arli::Errors::InvalidSyntaxError,
103
+ 'Expected an argument or a flag to follow the command ' + 'search'.bold.green
104
+ end
105
+ end
106
+
107
+ def handle_and_raise_error(e)
108
+ message = e.message
109
+ if message =~ /undefined method.*Arduino::Library::Model/
110
+ message = "Invalid attributed search. Possible values are:" +
111
+ "\n#{Arduino::Library::Types::LIBRARY_PROPERTIES.keys}"
112
+ end
113
+ raise Arli::Errors::InvalidSearchSyntaxError,
114
+ "Search string '#{search_string}' is invalid.\n" +
115
+ message.red
116
+ end
117
+
118
+ def print_total_with_help
119
+ puts "———————————————————————"
120
+ puts " Total Versions : #{results.size.to_s.bold.magenta}\n"
121
+ puts "Unique Libraries : #{hash.keys.size.to_s.bold.magenta}\n"
122
+ puts "———————————————————————"
123
+ 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."
125
+ end
71
126
  end
72
127
 
73
128
  def params