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