atk_toolbox 0.0.126 → 0.0.127
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.
- 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
|
#
|