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.
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
  #