arli 0.5.1 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rake_tasks~ +8 -0
- data/README.md +122 -30
- data/Rakefile +24 -2
- data/arli.gemspec +4 -1
- data/exe/arli +2 -4
- data/lib/arli.rb +19 -21
- data/lib/arli/actions.rb +19 -0
- data/lib/arli/actions/action.rb +32 -0
- data/lib/arli/actions/backup.rb +30 -0
- data/lib/arli/actions/dir_name.rb +66 -0
- data/lib/arli/actions/git_repo.rb +43 -0
- data/lib/arli/actions/zip_file.rb +54 -0
- data/lib/arli/arli_file.rb +22 -37
- data/lib/arli/cli.rb +4 -190
- data/lib/arli/cli/app.rb +73 -0
- data/lib/arli/cli/command_finder.rb +72 -0
- data/lib/arli/{parser.rb → cli/parser.rb} +66 -56
- data/lib/arli/cli/parser_factory.rb +97 -0
- data/lib/arli/cli/runner.rb +40 -0
- data/lib/arli/commands.rb +8 -0
- data/lib/arli/commands/base.rb +23 -30
- data/lib/arli/commands/install.rb +14 -9
- data/lib/arli/commands/search.rb +35 -10
- data/lib/arli/configuration.rb +62 -0
- data/lib/arli/extensions.rb +9 -0
- data/lib/arli/installer.rb +30 -78
- data/lib/arli/library.rb +49 -0
- data/lib/arli/output.rb +94 -0
- data/lib/arli/version.rb +1 -1
- metadata +63 -8
- data/lib/arli/config.rb +0 -16
- data/lib/arli/installers/zip_file.rb +0 -61
- data/lib/arli/logger.rb +0 -13
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative 'action'
|
2
|
+
|
3
|
+
module Arli
|
4
|
+
module Actions
|
5
|
+
# The purpose of this action is to fix the directory
|
6
|
+
# name of the library, that's possibly incorrect.
|
7
|
+
# For example, library "Adafruit Unified Sensor" installs
|
8
|
+
# into the folder 'Adafruit_Unified_Sensor'
|
9
|
+
# while the source files inside are 'Adafruit_Sensor.h'
|
10
|
+
# This action renames invalid library folders based on the
|
11
|
+
# source files found inside.
|
12
|
+
class DirName < Action
|
13
|
+
attr_accessor :sources, :headers
|
14
|
+
|
15
|
+
def act
|
16
|
+
find_source_files
|
17
|
+
|
18
|
+
# so "dir" is the 'Adafruit_Unified_Sensor'
|
19
|
+
# but we found header Adafruit_Sensor we should
|
20
|
+
# rename the folder
|
21
|
+
|
22
|
+
return if headers.include?(dir)
|
23
|
+
return if sources.include?(dir)
|
24
|
+
|
25
|
+
___
|
26
|
+
|
27
|
+
# if we end up setting this, we'll also move the folder.
|
28
|
+
canonical_dir =
|
29
|
+
if_only_one(headers) ||
|
30
|
+
if_only_one(sources) ||
|
31
|
+
if_header_a_substring(headers)
|
32
|
+
|
33
|
+
if canonical_dir
|
34
|
+
library.canonical_dir = canonical_dir
|
35
|
+
FileUtils.rm_rf(canonical_dir) if Dir.exist?(canonical_dir)
|
36
|
+
___ " (#{canonical_dir.bold.green}) "
|
37
|
+
FileUtils.mv(dir, library.canonical_dir)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def if_header_a_substring(files)
|
42
|
+
files.find { |file| dir.start_with?(file) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def if_only_one(file_names)
|
46
|
+
if file_names.size == 1 && file_names.first != dir
|
47
|
+
file_names.first
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_source_files
|
52
|
+
Dir.chdir(dir) do
|
53
|
+
self.sources = files_with_extension('**.{cpp,c}')
|
54
|
+
self.headers = files_with_extension('**.{h}')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
EXT_REGEX = /\.(cpp|h|c)$/
|
59
|
+
EXT_CHOMP = ->(f) { f.gsub(EXT_REGEX, '') }
|
60
|
+
|
61
|
+
def files_with_extension(pattern)
|
62
|
+
Dir.glob(pattern).map(&EXT_CHOMP)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'action'
|
2
|
+
module Arli
|
3
|
+
module Actions
|
4
|
+
class GitRepo < Action
|
5
|
+
|
6
|
+
def act
|
7
|
+
c = library.exists? ? git_update_command : git_clone_command
|
8
|
+
___ 'running ' + c.blue + ' '
|
9
|
+
execute(c)
|
10
|
+
ok
|
11
|
+
rescue Exception => e
|
12
|
+
fuck
|
13
|
+
raise e
|
14
|
+
end
|
15
|
+
|
16
|
+
def git_update_command
|
17
|
+
"cd #{path} && git pull --rebase 2>&1"
|
18
|
+
end
|
19
|
+
|
20
|
+
def git_clone_command
|
21
|
+
"git clone -v #{library.url} #{path} 2>&1"
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
# @param <String> *args — list of arguments or a single string
|
27
|
+
def execute(*args)
|
28
|
+
cmd = args.join(' ')
|
29
|
+
raise 'No command to run was given' unless cmd
|
30
|
+
info("\n" + cmd.green) if Arli.debug?
|
31
|
+
o, e, s = Open3.capture3(cmd)
|
32
|
+
info("\n" + o) if o if Arli.debug?
|
33
|
+
info("\n" + e.red) if e && Arli.debug?
|
34
|
+
rescue Exception => e
|
35
|
+
error "Error running [#{args.join(' ')}]\n" +
|
36
|
+
"Current folder is [#{Dir.pwd.yellow}]", e
|
37
|
+
raise e
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'archive/zip'
|
2
|
+
require_relative 'action'
|
3
|
+
|
4
|
+
module Arli
|
5
|
+
module Actions
|
6
|
+
class ZipFile < Action
|
7
|
+
|
8
|
+
def act
|
9
|
+
___
|
10
|
+
library.rm_rf!
|
11
|
+
download!
|
12
|
+
if File.exist?(zip_archive)
|
13
|
+
ok; ___
|
14
|
+
FileUtils.rm_rf(zip_folder) if zip_folder
|
15
|
+
unzip(zip_archive, '.')
|
16
|
+
if Dir.exist?(zip_folder)
|
17
|
+
ok; ___
|
18
|
+
FileUtils.move(zip_folder, dir)
|
19
|
+
ok
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue Exception => e
|
23
|
+
fuck
|
24
|
+
puts
|
25
|
+
raise(e)
|
26
|
+
ensure
|
27
|
+
delete_zip!
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def delete_zip!
|
33
|
+
FileUtils.rm_f(zip_archive) if File.exist?(zip_archive)
|
34
|
+
end
|
35
|
+
|
36
|
+
def download!
|
37
|
+
File.write(zip_archive, Net::HTTP.get(URI.parse(library.url)))
|
38
|
+
end
|
39
|
+
|
40
|
+
def zip_archive
|
41
|
+
@zip_archive ||= File.basename(library.url)
|
42
|
+
end
|
43
|
+
|
44
|
+
# list the contents of the archive and grab the top level folder
|
45
|
+
def zip_folder
|
46
|
+
@zip_folder ||= `unzip -Z1 #{zip_archive} | awk 'BEGIN{FS="/"}{print $1}' | uniq | tail -1`.chomp
|
47
|
+
end
|
48
|
+
|
49
|
+
def unzip(file, destination)
|
50
|
+
`unzip -o #{file} -d #{destination}`
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/arli/arli_file.rb
CHANGED
@@ -8,60 +8,45 @@ module Arli
|
|
8
8
|
require 'arduino/library/include'
|
9
9
|
|
10
10
|
include Enumerable
|
11
|
-
|
12
11
|
extend Forwardable
|
13
|
-
def_delegators :@dependencies, *(Array.new.methods - Object.methods)
|
14
|
-
|
15
|
-
attr_accessor :dependencies, :file_hash, :file, :lib_path, :resolved
|
16
12
|
|
17
|
-
|
18
|
-
arlifile_path: nil)
|
13
|
+
def_delegators :@dependencies, *(Array.new.methods - Object.methods)
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
self.dependencies = []
|
15
|
+
attr_accessor :dependencies,
|
16
|
+
:file_hash,
|
17
|
+
:file,
|
18
|
+
:library_path
|
25
19
|
|
26
|
-
|
27
|
-
|
20
|
+
def initialize(config: Arli.config)
|
21
|
+
self.library_path = config.libraries.path
|
22
|
+
self.file = "#{config.arlifile.path}/#{config.arlifile.name}"
|
28
23
|
|
24
|
+
unless file && File.exist?(file)
|
25
|
+
raise(Arli::Errors::ArliFileNotFound,
|
26
|
+
"Arlifile could not be found at\n#{file.bold.yellow}")
|
27
|
+
end
|
29
28
|
|
30
|
-
|
31
|
-
self.dependencies
|
29
|
+
FileUtils.mkpath(library_path) unless Dir.exist?(library_path)
|
30
|
+
self.dependencies = parse_file
|
32
31
|
end
|
33
32
|
|
34
|
-
def
|
35
|
-
|
36
|
-
Dir.chdir(lib_path) do
|
33
|
+
def within_library_path
|
34
|
+
Dir.chdir(library_path) do
|
37
35
|
yield if block_given?
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
def each_dependency(&
|
42
|
-
|
43
|
-
dependencies.each do |dependency|
|
44
|
-
yield(dependency)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def error(*args)
|
50
|
-
STDERR.puts *args.join("\n")
|
51
|
-
end
|
52
|
-
|
53
|
-
def info(*args)
|
54
|
-
STDOUT.puts *args.join("\n") if Arli.debug?
|
39
|
+
def each_dependency(&block)
|
40
|
+
within_library_path { dependencies.each(&block) }
|
55
41
|
end
|
56
42
|
|
57
43
|
private
|
58
44
|
|
59
|
-
def
|
60
|
-
self.file_hash
|
61
|
-
|
62
|
-
::Arduino::Library::Model.
|
45
|
+
def parse_file
|
46
|
+
self.file_hash = ::YAML.load(::File.read(self.file))
|
47
|
+
file_hash['dependencies'].map do |lib|
|
48
|
+
::Arli::Library.new(::Arduino::Library::Model.from(lib))
|
63
49
|
end
|
64
50
|
end
|
65
|
-
|
66
51
|
end
|
67
52
|
end
|
data/lib/arli/cli.rb
CHANGED
@@ -1,194 +1,8 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'hashie/mash'
|
3
|
-
require 'hashie/extensions/symbolize_keys'
|
4
|
-
require 'colored2'
|
5
|
-
require 'arli'
|
6
|
-
require 'arli/parser'
|
7
|
-
require 'arli/commands/install'
|
8
|
-
require 'arli/commands/search'
|
9
|
-
|
10
1
|
module Arli
|
11
|
-
|
12
|
-
|
13
|
-
COMMAND = 'arli'.freeze
|
14
|
-
PARSER = ::Arli::CLI::Parser
|
15
|
-
|
16
|
-
attr_accessor :argv, :parser, :command_name, :command
|
17
|
-
attr_accessor :options
|
18
|
-
|
19
|
-
def initialize(argv = ARGV.dup)
|
20
|
-
self.argv = argv
|
21
|
-
self.options = Hashie::Mash.new
|
22
|
-
end
|
23
|
-
|
24
|
-
def parse
|
25
|
-
if argv.first
|
26
|
-
if argv.first =~ /^-.*$/
|
27
|
-
self.parser = self.class.global
|
28
|
-
elsif command_detected?
|
29
|
-
self.parser = parse_command_options!
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
if parser
|
34
|
-
parser.parse!(argv)
|
35
|
-
parser.print
|
36
|
-
options.merge!(parser.options)
|
37
|
-
end
|
38
|
-
|
39
|
-
self.options = Hashie::Extensions::SymbolizeKeys.symbolize_keys!(options.to_h)
|
40
|
-
|
41
|
-
unless options[:help]
|
42
|
-
self.command = create_command if command_name
|
43
|
-
execute
|
44
|
-
end
|
45
|
-
|
46
|
-
rescue OptionParser::InvalidOption => e
|
47
|
-
report_exception(e, 'Command line usage error!')
|
48
|
-
|
49
|
-
rescue Arli::Errors::InvalidCommandError
|
50
|
-
report_exception(e, 'This command does not exist')
|
51
|
-
|
52
|
-
rescue Exception => e
|
53
|
-
report_exception(e)
|
54
|
-
raise e if options[:trace]
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def report_exception(e, header = nil)
|
60
|
-
error header.bold.yellow if header
|
61
|
-
printf ' ☠ '
|
62
|
-
error e.message if e && e.respond_to?(:message)
|
63
|
-
end
|
64
|
-
|
65
|
-
def execute
|
66
|
-
if command
|
67
|
-
header
|
68
|
-
command.run
|
69
|
-
else
|
70
|
-
gp = self.class.global
|
71
|
-
gp.parse!(%w(--help))
|
72
|
-
gp.print
|
73
|
-
nil
|
74
|
-
end
|
75
|
-
rescue NameError => e
|
76
|
-
error e.inspect
|
77
|
-
error e.backtrace.join("\n") if options[:trace]
|
78
|
-
end
|
79
|
-
|
80
|
-
def create_command
|
81
|
-
command_class = ::Arli::Commands.const_get(command_name.to_s.capitalize)
|
82
|
-
|
83
|
-
options[:lib_home] ||= ::Arli.config.library_path
|
84
|
-
options[:argv] = argv
|
2
|
+
module CLi
|
85
3
|
|
86
|
-
info "created command #{command_name.to_s.green},\noptions: #{options.inspect.blue}" if Arli.debug?
|
87
|
-
|
88
|
-
command_class.new(options)
|
89
|
-
end
|
90
|
-
|
91
|
-
def header
|
92
|
-
out = ''
|
93
|
-
out << "Arli (#{::Arli::VERSION.yellow})"
|
94
|
-
out << " running #{command.name.to_s.blue}" if command
|
95
|
-
out << "\n"
|
96
|
-
out << "Library Folder: #{options[:lib_home].gsub(/#{ENV['HOME']}/, '~').green}\n" if options[:lib_home]
|
97
|
-
out << '——————————————————————————————————————————————————————————'
|
98
|
-
info out
|
99
|
-
end
|
100
|
-
|
101
|
-
def info(*args)
|
102
|
-
self.class.output(*args)
|
103
|
-
end
|
104
|
-
|
105
|
-
def error(*args)
|
106
|
-
self.class.output(*(args.compact.map { |a| a.to_s.red }))
|
107
|
-
end
|
108
|
-
|
109
|
-
def parse_command_options!
|
110
|
-
self.class.parser_for(command_name)
|
111
|
-
end
|
112
|
-
|
113
|
-
def command_detected?
|
114
|
-
self.command_name = argv.shift if argv.first && argv.first !~ /^-.*$/
|
115
|
-
if self.command_name
|
116
|
-
self.command_name = command_name.to_sym
|
117
|
-
unless self.class.commands.key?(command_name)
|
118
|
-
raise Arli::Errors::InvalidCommandError, "Error: #{command_name ? command_name.to_s.red : 'nil'} is not a valid arli command_name!"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
self.command_name
|
122
|
-
end
|
123
|
-
|
124
|
-
class << self
|
125
|
-
|
126
|
-
def output(*args)
|
127
|
-
puts args.join("\n") unless args.empty?
|
128
|
-
end
|
129
|
-
|
130
|
-
def global
|
131
|
-
@global ||= PARSER.new do |parser|
|
132
|
-
parser.banner = usage_line
|
133
|
-
parser.sep
|
134
|
-
parser.option_help(commands: true)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def usage_line(command = nil)
|
139
|
-
command ? command_usage(command) : global_usage(command)
|
140
|
-
end
|
141
|
-
|
142
|
-
def global_usage(command)
|
143
|
-
"Usage:\n " + COMMAND.blue +
|
144
|
-
' [options] '.yellow + '[ ' + (command || 'command').green +
|
145
|
-
' [options] '.yellow + ' ]' + "\n"
|
146
|
-
end
|
147
|
-
|
148
|
-
def command_usage(command)
|
149
|
-
"Usage:\n " + COMMAND.blue + ' ' +
|
150
|
-
command.green +
|
151
|
-
' [options]'.yellow + "\n\n" +
|
152
|
-
'Command Options'
|
153
|
-
end
|
154
|
-
|
155
|
-
def commands
|
156
|
-
@commands ||= {
|
157
|
-
install: {
|
158
|
-
description: 'installs libraries defined in Arlifile',
|
159
|
-
parser: -> (command_name) {
|
160
|
-
PARSER.new do |parser|
|
161
|
-
parser.banner = usage_line 'install'
|
162
|
-
parser.option_lib_home
|
163
|
-
parser.option_dependency_file
|
164
|
-
parser.option_abort_if_exists
|
165
|
-
parser.option_help(command_name: command_name)
|
166
|
-
end
|
167
|
-
} },
|
168
|
-
|
169
|
-
search: {
|
170
|
-
description: 'Flexible Search of the Arduino Library Database',
|
171
|
-
example: 'arli search '.green + %Q['name: /AudioZero/, version: "1.0.1"'].green,
|
172
|
-
parser: -> (command_name) {
|
173
|
-
PARSER.new do |parser|
|
174
|
-
parser.banner = usage_line 'search ' + '<query>'.magenta
|
175
|
-
parser.option_search
|
176
|
-
parser.option_help(command_name: command_name)
|
177
|
-
end
|
178
|
-
}
|
179
|
-
}
|
180
|
-
}
|
181
|
-
end
|
182
|
-
|
183
|
-
def parser_for(cmd)
|
184
|
-
if commands[cmd]
|
185
|
-
cmd_hash = commands[cmd]
|
186
|
-
commands[cmd][:parser][cmd_hash]
|
187
|
-
else
|
188
|
-
raise(Arli::Errors::InvalidCommandError,
|
189
|
-
"'#{cmd}' is not a valid command_name.\nSupported commands are:\n\t#{commands.keys.join("\n\t")}")
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
4
|
end
|
194
5
|
end
|
6
|
+
|
7
|
+
require_relative 'cli/parser_factory'
|
8
|
+
require_relative 'cli/runner'
|
data/lib/arli/cli/app.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'optparse'
|
3
|
+
require 'colored2'
|
4
|
+
|
5
|
+
require_relative 'parser'
|
6
|
+
require_relative 'command_finder'
|
7
|
+
require_relative 'parser_factory'
|
8
|
+
require_relative '../commands'
|
9
|
+
require_relative '../output'
|
10
|
+
|
11
|
+
|
12
|
+
module Arli
|
13
|
+
module CLI
|
14
|
+
class App
|
15
|
+
include Arli::Output
|
16
|
+
|
17
|
+
attr_accessor :argv, :config, :command
|
18
|
+
|
19
|
+
def initialize(argv, config: Arli.config)
|
20
|
+
self.argv = argv
|
21
|
+
self.config = config
|
22
|
+
self.config.runtime.argv = argv
|
23
|
+
end
|
24
|
+
|
25
|
+
def start
|
26
|
+
if argv.empty?
|
27
|
+
factory.default_help
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
parse_global_flags
|
32
|
+
return if Arli.config.help
|
33
|
+
|
34
|
+
finder = CommandFinder.new(argv, config: config)
|
35
|
+
finder.parse!
|
36
|
+
return if Arli.config.help
|
37
|
+
|
38
|
+
if finder.command
|
39
|
+
self.command = finder.command
|
40
|
+
execute!
|
41
|
+
else
|
42
|
+
factory.default_help
|
43
|
+
end
|
44
|
+
rescue OptionParser::InvalidOption => e
|
45
|
+
report_exception(e, 'Invalid flags or options')
|
46
|
+
rescue Arli::Errors::InvalidCommandError => e
|
47
|
+
report_exception(e, 'Unknown command')
|
48
|
+
rescue Exception => e
|
49
|
+
report_exception(e)
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_global_flags
|
53
|
+
if argv.first && argv.first.start_with?('-')
|
54
|
+
parser = factory.global_parser
|
55
|
+
factory.parse_argv(parser, argv)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def execute!
|
60
|
+
if command
|
61
|
+
header(command: command) if config.verbose
|
62
|
+
command.run
|
63
|
+
else
|
64
|
+
factory.default_help
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def factory
|
69
|
+
Arli::CLI::ParserFactory
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|