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.
data/lib/atk/console.rb CHANGED
@@ -1,14 +1,11 @@
1
1
  require "tty-prompt"
2
2
  require_relative "./os.rb"
3
- require "colorize"
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 shows stderr and returns a success value
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 single_quote_escape(string)
62
- string.gsub(/'/, "'\"'\"'")
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
- " '"+Console.single_quote_escape(each)+"'"
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
@@ -1,4 +1,4 @@
1
- (using_atk_version): 1.0
1
+ (using_atk_version): 1.1.0
2
2
  (project):
3
3
  name: Your Project Name Here
4
4
  description: Describe your project here
@@ -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
- # TODO
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(from:nil, to:nil, force: true)
160
- # if the directories are different, then throw an error
161
- if not File.identical?(File.dirname(from), File.dirname(to))
162
- raise "\n\nFileSystem.rename() requires that the the file stay in the same place and only change names.\nIf you want to move a file, use FileSystem.move()"
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 copy
169
- File.rename(from, to)
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
- # TODO: eventually make this work for any drive, not just the current drive
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(input=nil, from:nil, url:nil, to:nil)
390
+ def self.download(the_url, to:nil)
381
391
  require 'open-uri'
382
- # if only one argument, either input or url
383
- if ((input!=nil) != (url!=nil)) && (from==nil) && (to==nil)
384
- # this covers:
385
- # download 'site.com/file'
386
- the_url = url || input
387
- file_name = the_url.match /(?<=\/)[^\/]+\z/
388
- file_name = file_name[0]
389
- elsif (to != nil) && ((input!=nil)!=(url!=nil))
390
- # this covers:
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.directory?(target_dir)
8
- if Console.verbose
9
- git_repo = Git.open(target_dir, :log => Logger.new(STDOUT))
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
- git_repo = Git.open(target_dir)
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
- # pull from origin master
18
- # TODO: make this a force pull
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 './file_sys'
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
- # TODO: improve this error message
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
- IO.write(temp_file, @value)
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
- -"#{@value}"
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
- self.parser_version1(@data)
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
- # TODO: in the future do an online check to see if the latest ATK could handle this
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.red
219
+ raise <<-HEREDOC.remove_indent
207
220
 
208
- #{"Couldn't find an info.yaml in the current directory or any parent directory".red}
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 parser_version1(data)
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
  #