atk_toolbox 0.0.126 → 0.0.127
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/atk/atk_info.rb +230 -60
- data/lib/atk/autocomplete.rb +1 -1
- data/lib/atk/commands/project.rb +69 -64
- data/lib/atk/console.rb +92 -11
- data/lib/atk/default_info.yaml +1 -1
- data/lib/atk/{file_sys.rb → file_system.rb} +89 -40
- data/lib/atk/git.rb +15 -14
- data/lib/atk/{yaml_info_parser.rb → info.rb} +41 -24
- data/lib/atk/os.rb +29 -11
- data/lib/atk/set_command.rb +11 -6
- data/lib/atk_toolbox/version.rb +1 -1
- data/lib/console_colors.rb +104 -0
- data/lib/rubygems_plugin.rb +15 -0
- metadata +5 -68
- data/lib/atk/extra_yaml.rb +0 -147
- data/lib/atk/online.rb +0 -8
- data/lib/atk/package_managers.rb +0 -111
- data/lib/atk/zip.rb +0 -54
data/lib/atk/console.rb
CHANGED
@@ -1,14 +1,11 @@
|
|
1
1
|
require "tty-prompt"
|
2
2
|
require_relative "./os.rb"
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# TODO: switch to using https://github.com/piotrmurach/tty-command#2-interface
|
3
|
+
require_relative "../console_colors.rb"
|
7
4
|
|
8
5
|
# easy access to the commandline
|
9
6
|
class String
|
10
7
|
# add a - operator to strings that makes it behave like a system() call
|
11
|
-
# but it
|
8
|
+
# but it returns a success value for chaining commands with || or &&
|
12
9
|
def -@
|
13
10
|
Process.wait(Process.spawn(self))
|
14
11
|
return $?.success?
|
@@ -20,8 +17,6 @@ end
|
|
20
17
|
# Console
|
21
18
|
#
|
22
19
|
# see https://github.com/piotrmurach/tty-prompt
|
23
|
-
# TODO: look into https://piotrmurach.github.io/tty/ to animate the terminal
|
24
|
-
# TODO: look at https://github.com/pazdera/catpix to add an ATK logo in the terminal
|
25
20
|
class TTY::Prompt
|
26
21
|
attr_accessor :verbose
|
27
22
|
def _save_args
|
@@ -57,18 +52,104 @@ class TTY::Prompt
|
|
57
52
|
def has_command(name_of_executable)
|
58
53
|
return OS.has_command(name_of_executable)
|
59
54
|
end
|
55
|
+
alias :has_command? :has_command
|
60
56
|
|
61
|
-
def
|
62
|
-
|
57
|
+
def as_shell_argument(argument)
|
58
|
+
argument = argument.to_s
|
59
|
+
if OS.is?(:unix)
|
60
|
+
# use single quotes to perfectly escape any string
|
61
|
+
return " '"+argument.gsub(/'/, "'\"'\"'")+"'"
|
62
|
+
else
|
63
|
+
# *sigh* Windows
|
64
|
+
# this problem is unsovleable
|
65
|
+
# see: https://superuser.com/questions/182454/using-backslash-to-escape-characters-in-cmd-exe-runas-command-as-example
|
66
|
+
# "The fact is, there's nothing that will escape " within quotes for argument passing.
|
67
|
+
# You can brood over this for a couple of years and arrive at no solution.
|
68
|
+
# This is just some of the inherent limitations of cmd scripting.
|
69
|
+
# However, the good news is that you'll most likely never come across a situation whereby you need to do so.
|
70
|
+
# Sure, there's no way to get echo """ & echo 1 to work, but that's not such a big deal because it's simply
|
71
|
+
# a contrived problem which you'll likely never encounter.
|
72
|
+
# For example, consider runas. It works fine without needing to escape " within quotes
|
73
|
+
# because runas knew that there's no way to do so and made internal adjustments to work around it.
|
74
|
+
# runas invented its own parsing rules (runas /flag "anything even including quotes") and does not
|
75
|
+
# interpret cmd arguments the usual way.
|
76
|
+
# Official documentation for these special syntax is pretty sparse (or non-existent).
|
77
|
+
# Aside from /? and help, it's mostly trial-and-error."
|
78
|
+
#
|
79
|
+
|
80
|
+
|
81
|
+
# according to Microsoft see: https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
82
|
+
# the best possible (but still broken) implementation is to quote things
|
83
|
+
# in accordance with their default C++ argument parser
|
84
|
+
# so thats what this function does
|
85
|
+
|
86
|
+
# users are going to have to manually escape things like ^, !, % etc depending on the context they're used in
|
87
|
+
|
88
|
+
simple_char = "[a-zA-Z0-9_.,;`=-*?\\/\\[\\]]"
|
89
|
+
|
90
|
+
# if its a simple argument just pass it on
|
91
|
+
if argument =~ /\A(#{simple_char})*\z/
|
92
|
+
return " #{argument}"
|
93
|
+
# if it is complicated, then quote it and escape quotes
|
94
|
+
else
|
95
|
+
# find any backslashes that come before a double quote or the ending of the argument
|
96
|
+
# then double the number of slashes
|
97
|
+
escaped = argument.gsub(/(?<slashes>\/+)(?="|\z)/) do |each_match|
|
98
|
+
"\/" * (each_match['slashes'].size * 2)
|
99
|
+
end
|
100
|
+
|
101
|
+
# then find all the double quotes and escape them
|
102
|
+
escaped.gsub!(/"/, '\\"')
|
103
|
+
|
104
|
+
# all of the remaining escapes are up to Windows user's/devs
|
105
|
+
|
106
|
+
return " \"#{escaped}\""
|
107
|
+
end
|
108
|
+
end
|
63
109
|
end
|
64
110
|
|
65
111
|
def make_arguments_appendable(arguments)
|
66
|
-
# TODO: make sure this works on windows
|
67
112
|
safe_arguments = arguments.map do |each|
|
68
|
-
|
113
|
+
Console.as_shell_argument(each)
|
69
114
|
end
|
70
115
|
return safe_arguments.join('')
|
71
116
|
end
|
117
|
+
|
118
|
+
# returns the locations where commands are stored from highest to lowest priority
|
119
|
+
def command_sources()
|
120
|
+
if OS.is?('unix')
|
121
|
+
return ENV['PATH'].split(':')
|
122
|
+
else
|
123
|
+
return ENV['PATH'].split(';')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def require_superuser()
|
128
|
+
if OS.is?('unix')
|
129
|
+
system("sudo echo 'permissions acquired'")
|
130
|
+
else
|
131
|
+
# check if already admin
|
132
|
+
# $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
133
|
+
# $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
134
|
+
# FUTURE: add a check here and raise an error if not admin
|
135
|
+
puts "(in the future this will be an automatic check)"
|
136
|
+
puts "(if you're unsure, then the answer is probably no)"
|
137
|
+
if Console.yes?("Are you running this \"as an Administrator\"?\n(caution: incorrectly saying 'yes' can cause broken systems)")
|
138
|
+
puts "assuming permissions are acquired"
|
139
|
+
else
|
140
|
+
puts <<-HEREDOC.remove_indent
|
141
|
+
|
142
|
+
You'll need to
|
143
|
+
- close the current program
|
144
|
+
- reopen it "as Administrator"
|
145
|
+
- redo whatever steps you did to get here
|
146
|
+
|
147
|
+
HEREDOC
|
148
|
+
Console.keypress("Press enter to end the current process", keys: [:return])
|
149
|
+
exit
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
72
153
|
end
|
73
154
|
|
74
155
|
Console = TTY::Prompt.new
|
data/lib/atk/default_info.yaml
CHANGED
@@ -31,12 +31,14 @@ module FileSystem
|
|
31
31
|
# It is by-default forceful (dangerous/overwriting)
|
32
32
|
# it is made to get things done in a no-nonsense error-free way and to have every pratical tool in one place
|
33
33
|
|
34
|
-
#
|
34
|
+
# FUTURE add
|
35
35
|
# change_owner
|
36
36
|
# set_permissions
|
37
37
|
# relative_path_between
|
38
38
|
# relative_path_to
|
39
39
|
# add a force: true option to most of the commands
|
40
|
+
# zip
|
41
|
+
# unzip
|
40
42
|
|
41
43
|
def self.write(data, to:nil)
|
42
44
|
# make sure the containing folder exists
|
@@ -156,17 +158,25 @@ module FileSystem
|
|
156
158
|
FileUtils.move(from, to/new_name, force: force, noop: noop, verbose: verbose, secure: secure)
|
157
159
|
end
|
158
160
|
|
159
|
-
def self.rename(
|
160
|
-
|
161
|
-
|
162
|
-
|
161
|
+
def self.rename(path, new_name:nil, force: true)
|
162
|
+
if File.dirname(new_name) != "."
|
163
|
+
raise <<-HEREDOC.remove_indent
|
164
|
+
|
165
|
+
|
166
|
+
When using FileSystem.rename(path, new_name)
|
167
|
+
The new_name needs to be a filename, not a file path
|
168
|
+
e.g. "foo.txt" not "a_folder/foo.txt"
|
169
|
+
|
170
|
+
If you want to move the file, use FileSystem.move(from:nil, to:nil, new_name:"")
|
171
|
+
HEREDOC
|
163
172
|
end
|
173
|
+
to = path/new_name
|
164
174
|
# make sure the path is clear
|
165
175
|
if force
|
166
176
|
FileSystem.delete(to)
|
167
177
|
end
|
168
|
-
# perform the
|
169
|
-
File.rename(
|
178
|
+
# perform the rename
|
179
|
+
File.rename(path, to)
|
170
180
|
end
|
171
181
|
|
172
182
|
def self.touch(*args)
|
@@ -208,7 +218,7 @@ module FileSystem
|
|
208
218
|
if not OS.is?("windows")
|
209
219
|
pieces.unshift('/')
|
210
220
|
else
|
211
|
-
#
|
221
|
+
# FUTURE: eventually make this work for any drive, not just the current drive
|
212
222
|
pieces.unshift('\\')
|
213
223
|
end
|
214
224
|
end
|
@@ -377,40 +387,79 @@ module FileSystem
|
|
377
387
|
File.stat(*args)
|
378
388
|
end
|
379
389
|
|
380
|
-
def self.download(
|
390
|
+
def self.download(the_url, to:nil)
|
381
391
|
require 'open-uri'
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
# download 'site.com/file' to:'file'
|
392
|
-
# download url:'site.com/file' to:'file'
|
393
|
-
the_url = url || input
|
394
|
-
file_name = to
|
395
|
-
elsif ((from!=nil) != (url!=nil)) && input!=nil
|
396
|
-
# this covers:
|
397
|
-
# download 'file' from:'site.com/file'
|
398
|
-
# download 'file' url:'site.com/file'
|
399
|
-
the_url = from || url
|
400
|
-
file_name = input
|
401
|
-
else
|
402
|
-
raise <<-HEREDOC.remove_indent
|
403
|
-
I'm not sure how you're using the download function.
|
404
|
-
Please use one of the following methods:
|
405
|
-
download 'site.com/file'
|
406
|
-
download 'site.com/file', to:'file'
|
407
|
-
download url:'site.com/file', to:'file'
|
408
|
-
download 'file', from:'site.com/file'
|
409
|
-
download 'file', url:'site.com/file'
|
410
|
-
HEREDOC
|
392
|
+
FileSystem.write(open(URI.encode(the_url)).read, to: to)
|
393
|
+
end
|
394
|
+
|
395
|
+
def self.online?
|
396
|
+
require 'open-uri'
|
397
|
+
begin
|
398
|
+
true if open("http://www.google.com/")
|
399
|
+
rescue
|
400
|
+
false
|
411
401
|
end
|
412
|
-
FileSystem.write(open(URI.encode(the_url)).read, to: file_name)
|
413
402
|
end
|
414
403
|
end
|
415
404
|
# create an FS singleton_class.send(:alias_method, :FS = :FileSystem)
|
416
|
-
FS = FileSystem
|
405
|
+
FS = FileSystem
|
406
|
+
|
407
|
+
|
408
|
+
|
409
|
+
|
410
|
+
# TODO: add zip/unzip functionality
|
411
|
+
# spec.add_runtime_dependency 'zip', '~> 2.0', '>= 2.0.2'
|
412
|
+
# require 'zip'
|
413
|
+
|
414
|
+
# class ZipFileGenerator
|
415
|
+
# # Initialize with the directory to zip and the location of the output archive.
|
416
|
+
# def initialize(input_dir, output_file)
|
417
|
+
# @input_dir = input_dir
|
418
|
+
# @output_file = output_file
|
419
|
+
# end
|
420
|
+
|
421
|
+
# # Zip the input directory.
|
422
|
+
# def write
|
423
|
+
# entries = Dir.entries(@input_dir) - %w[. ..]
|
424
|
+
|
425
|
+
# Zip::ZipFile.open(@output_file, Zip::ZipFile::CREATE) do |zipfile|
|
426
|
+
# write_entries entries, '', zipfile
|
427
|
+
# end
|
428
|
+
# end
|
429
|
+
|
430
|
+
# private
|
431
|
+
|
432
|
+
# # A helper method to make the recursion work.
|
433
|
+
# def write_entries(entries, path, zipfile)
|
434
|
+
# entries.each do |e|
|
435
|
+
# zipfile_path = path == '' ? e : File.join(path, e)
|
436
|
+
# disk_file_path = File.join(@input_dir, zipfile_path)
|
437
|
+
|
438
|
+
# if File.directory? disk_file_path
|
439
|
+
# recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
440
|
+
# else
|
441
|
+
# put_into_archive(disk_file_path, zipfile, zipfile_path)
|
442
|
+
# end
|
443
|
+
# end
|
444
|
+
# end
|
445
|
+
|
446
|
+
# def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
447
|
+
# zipfile.mkdir zipfile_path
|
448
|
+
# subdir = Dir.entries(disk_file_path) - %w[. ..]
|
449
|
+
# write_entries subdir, zipfile_path, zipfile
|
450
|
+
# end
|
451
|
+
|
452
|
+
# def put_into_archive(disk_file_path, zipfile, zipfile_path)
|
453
|
+
# zipfile.add(zipfile_path, disk_file_path)
|
454
|
+
# end
|
455
|
+
# end
|
456
|
+
|
457
|
+
|
458
|
+
# def zip(source, destination=nil)
|
459
|
+
# if destination == nil
|
460
|
+
# destination = source + ".zip"
|
461
|
+
# end
|
462
|
+
|
463
|
+
# zip_helper = ZipFileGenerator.new(source, destination)
|
464
|
+
# zip_helper.write()
|
465
|
+
# end
|
data/lib/atk/git.rb
CHANGED
@@ -1,26 +1,27 @@
|
|
1
|
-
require 'git'
|
2
|
-
require 'logger'
|
3
|
-
|
4
1
|
module Git
|
5
2
|
def self.ensure_cloned_and_up_to_date(target_dir, git_repo_url)
|
6
3
|
# check if it exists
|
7
|
-
if FS.
|
8
|
-
if
|
9
|
-
|
4
|
+
if FS.folder?(target_dir)
|
5
|
+
# check if its a git repo
|
6
|
+
if FS.folder?(target_dir/".git")
|
7
|
+
# fetch master
|
8
|
+
system("git fetch origin master")
|
9
|
+
if $?.success?
|
10
|
+
# force pull
|
11
|
+
system("git reset --hard origin/master")
|
12
|
+
end
|
13
|
+
return
|
10
14
|
else
|
11
|
-
|
15
|
+
# clear the path
|
16
|
+
FS.delete(target_dir)
|
12
17
|
end
|
13
|
-
# if it doesn't exist, then clone it
|
14
|
-
else
|
15
|
-
git_repo = Git.clone(git_repo_url, target_dir)
|
16
18
|
end
|
17
|
-
#
|
18
|
-
|
19
|
-
git_repo.pull
|
19
|
+
# clone the directory if pulling didn't occur
|
20
|
+
system("git", "clone", git_repo_url, target_dir)
|
20
21
|
end
|
21
22
|
|
22
23
|
def self.repo_name(url)
|
23
|
-
require_relative './
|
24
|
+
require_relative './file_system'
|
24
25
|
*folders, name, extension = FS.path_pieces(url)
|
25
26
|
return name
|
26
27
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "yaml"
|
2
|
-
require "colorize"
|
3
2
|
require 'fileutils'
|
4
3
|
require_relative './os'
|
4
|
+
require_relative "../console_colors.rb"
|
5
5
|
require_relative './remove_indent.rb'
|
6
6
|
require_relative './version.rb'
|
7
7
|
|
@@ -32,8 +32,6 @@ module FileSystem
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
# TODO: for efficiency, have the parser generate a parsed object, instead of only handling everything dynamically (allow for both)
|
36
|
-
|
37
35
|
#
|
38
36
|
# Create loaders for ruby code literal and console code literal
|
39
37
|
#
|
@@ -52,8 +50,7 @@ end
|
|
52
50
|
end
|
53
51
|
|
54
52
|
def run
|
55
|
-
#
|
56
|
-
raise "This needs to be overloaded"
|
53
|
+
raise "#{self.class}.run needs to be overloaded"
|
57
54
|
end
|
58
55
|
|
59
56
|
def to_s
|
@@ -66,10 +63,12 @@ end
|
|
66
63
|
#
|
67
64
|
class RubyCode < Code
|
68
65
|
def run(*args)
|
66
|
+
# FUTURE: generate this name to ensure there are never name conflicts
|
69
67
|
temp_file = ".info_language_runner_cache.rb"
|
70
|
-
|
68
|
+
# a file needs to be created in the root dir so that things like __dir__ work properly
|
69
|
+
# the first line of the temp file deletes itself so that it appears as if no file ever existed
|
70
|
+
IO.write(temp_file, "File.delete('./.info_language_runner_cache.rb')\n#{@value}")
|
71
71
|
Process.wait(Process.spawn("ruby", temp_file, *args))
|
72
|
-
File.delete(temp_file)
|
73
72
|
end
|
74
73
|
end
|
75
74
|
register_tag('!language/ruby', RubyCode)
|
@@ -84,13 +83,13 @@ end
|
|
84
83
|
end
|
85
84
|
end
|
86
85
|
|
87
|
-
#
|
86
|
+
#
|
88
87
|
# Console Code
|
89
|
-
#
|
90
|
-
# TODO: add support for console code
|
88
|
+
#
|
91
89
|
class ConsoleCode < Code
|
92
90
|
def run
|
93
|
-
|
91
|
+
system(@value)
|
92
|
+
return $?
|
94
93
|
end
|
95
94
|
end
|
96
95
|
register_tag('!language/console', ConsoleCode)
|
@@ -110,6 +109,9 @@ class Info
|
|
110
109
|
class ReRaiseException < Exception
|
111
110
|
end
|
112
111
|
|
112
|
+
class YamlFileDoesntExist < Exception
|
113
|
+
end
|
114
|
+
|
113
115
|
def initialize()
|
114
116
|
#
|
115
117
|
# find the yaml file
|
@@ -117,7 +119,7 @@ class Info
|
|
117
119
|
begin
|
118
120
|
@path = Info.path
|
119
121
|
rescue
|
120
|
-
raise <<-HEREDOC.remove_indent
|
122
|
+
raise YamlFileDoesntExist, <<-HEREDOC.remove_indent
|
121
123
|
|
122
124
|
|
123
125
|
When calling Info.new
|
@@ -143,24 +145,35 @@ class Info
|
|
143
145
|
#
|
144
146
|
# check the version, and parse accordingly
|
145
147
|
#
|
146
|
-
version = nil
|
148
|
+
@version = nil
|
147
149
|
begin
|
148
|
-
version = Version.new(@data['(using_atk_version)'].to_s)
|
150
|
+
@version = Version.new(@data['(using_atk_version)'].to_s)
|
149
151
|
rescue => exception
|
150
152
|
# if no version, then don't worry about parsing
|
151
153
|
end
|
152
|
-
if version.is_a?(Version)
|
154
|
+
if @version.is_a?(Version)
|
153
155
|
begin
|
154
|
-
if version <= Version.new("1.0.0")
|
155
|
-
|
156
|
+
if @version <= Version.new("1.0.0")
|
157
|
+
raise ReRaiseException, <<-HEREDOC.remove_indent
|
158
|
+
|
159
|
+
So it looks like you're info.yaml file version is 1.0
|
160
|
+
That was the alpha version of ATK, which is the only version that will not be supported
|
161
|
+
|
162
|
+
#{"This is likely just an accident and the only thing that needs to be done is change:".color_as :message}
|
163
|
+
(using_atk_version): 1.0
|
164
|
+
#{"to:".color_as :message}
|
165
|
+
(using_atk_version): 1.1.0
|
166
|
+
HEREDOC
|
167
|
+
elsif @version <= Version.new("1.1.0")
|
168
|
+
self.parser_version_1_1(@data)
|
156
169
|
else
|
157
|
-
#
|
158
|
-
raise <<-HEREDOC.remove_indent
|
170
|
+
# FUTURE: in the future do an online check to see if the latest ATK could handle this
|
171
|
+
raise ReRaiseException, <<-HEREDOC.remove_indent
|
159
172
|
|
160
173
|
Hey I think you need to update atk:
|
161
174
|
`atk update`
|
162
175
|
Why?
|
163
|
-
The (using_atk_version) in the info.yaml is: #{version}
|
176
|
+
The (using_atk_version) in the info.yaml is: #{@version}
|
164
177
|
However, I (your current version of ATK) don't know
|
165
178
|
how to handle that version.
|
166
179
|
HEREDOC
|
@@ -183,7 +196,7 @@ class Info
|
|
183
196
|
please go here:
|
184
197
|
https://github.com/aggie-tool-kit/atk-toolbox/issues/new
|
185
198
|
and tell us what happened before getting this message
|
186
|
-
and also paste the original error message
|
199
|
+
and also paste the original error message
|
187
200
|
|
188
201
|
Sorry for the bug!
|
189
202
|
HEREDOC
|
@@ -203,9 +216,9 @@ class Info
|
|
203
216
|
|
204
217
|
# if all folders exhausted
|
205
218
|
if next_location == folder
|
206
|
-
raise <<-HEREDOC.remove_indent
|
219
|
+
raise <<-HEREDOC.remove_indent
|
207
220
|
|
208
|
-
#{"Couldn't find an info.yaml in the current directory or any parent directory".
|
221
|
+
#{"Couldn't find an info.yaml in the current directory or any parent directory".color_as(:bad)}
|
209
222
|
#{Dir.pwd}
|
210
223
|
Are you sure you're running the command from the correct directory?
|
211
224
|
HEREDOC
|
@@ -219,7 +232,11 @@ class Info
|
|
219
232
|
return FileSystem.join( self.folder(), "info.yaml")
|
220
233
|
end
|
221
234
|
|
222
|
-
def
|
235
|
+
def version()
|
236
|
+
return @version
|
237
|
+
end
|
238
|
+
|
239
|
+
def parser_version_1_1(data)
|
223
240
|
#
|
224
241
|
# parse the commands
|
225
242
|
#
|