hookapp 2.0.5 → 2.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS.md +4 -0
  3. data/CHANGELOG.md +9 -0
  4. data/Gemfile.lock +35 -40
  5. data/LICENSE +21 -0
  6. data/README.md +52 -19
  7. data/Rakefile +7 -3
  8. data/bin/hook +102 -65
  9. data/buildnotes.md +30 -0
  10. data/hook.rdoc +35 -11
  11. data/hookapp.gemspec +1 -0
  12. data/html/App.html +1 -1
  13. data/html/GLI/Commands/Doc.html +1 -1
  14. data/html/GLI/Commands/MarkdownDocumentListener.html +27 -17
  15. data/html/GLI/Commands.html +1 -1
  16. data/html/GLI.html +1 -1
  17. data/html/Hook.html +1 -1
  18. data/html/HookApp.html +106 -44
  19. data/html/Hooker.html +2 -2
  20. data/html/README_rdoc.html +47 -18
  21. data/html/String.html +23 -1
  22. data/html/created.rid +9 -8
  23. data/html/index.html +39 -12
  24. data/html/js/navigation.js.gz +0 -0
  25. data/html/js/search_index.js +1 -1
  26. data/html/js/search_index.js.gz +0 -0
  27. data/html/js/searcher.js.gz +0 -0
  28. data/html/table_of_contents.html +71 -7
  29. data/lib/hook/hookapp.rb +40 -22
  30. data/lib/hook/hooker.rb +1 -0
  31. data/lib/hook/markdown_document_listener.rb +12 -2
  32. data/lib/hook/prompt.rb +113 -0
  33. data/lib/hook/string.rb +4 -0
  34. data/lib/hook/version.rb +1 -1
  35. data/lib/hook.rb +2 -0
  36. data/test/helpers/hook-helpers.rb +76 -0
  37. data/test/hook_clip_test.rb +24 -0
  38. data/test/hook_clone_test.rb +30 -0
  39. data/test/hook_encode_test.rb +30 -0
  40. data/test/hook_link_test.rb +40 -0
  41. data/test/hook_list_test.rb +25 -0
  42. data/test/hook_remove_test.rb +34 -0
  43. data/test/hook_scripts_test.rb +21 -0
  44. metadata +33 -16
  45. data/lib/helpers/fuzzyfilefinder +0 -0
  46. data/test/default_test.rb +0 -14
  47. data/test/hookfiles/01.test +0 -0
  48. data/test/hookfiles/02.test +0 -0
  49. data/test/hookfiles/03.test +0 -0
  50. data/test/hookfiles/04.test +0 -0
  51. data/test/hookfiles/05.test +0 -0
  52. data/test/hookfiles/06.test +0 -0
  53. data/test/hookfiles/07.test +0 -0
  54. data/test/hookfiles/08.test +0 -0
  55. data/test/hookfiles/09.test +0 -0
  56. data/test/hookfiles/10.test +0 -0
  57. data/test/hookfiles/11.test +0 -0
  58. data/test/hookfiles/12.test +0 -0
@@ -15,6 +15,7 @@ module GLI
15
15
  @io = File.new('README.md', 'w')
16
16
  @nest = '#'
17
17
  @arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new
18
+ @parent_command = []
18
19
  end
19
20
 
20
21
  def beginning
@@ -26,6 +27,12 @@ module GLI
26
27
  @io.puts IO.read('CREDITS.md')
27
28
  @io.puts
28
29
  end
30
+
31
+ if File.exist?('AUTHORS.md')
32
+ @io.puts IO.read('AUTHORS.md')
33
+ @io.puts
34
+ end
35
+
29
36
  if File.exist?('LICENSE.md')
30
37
  @io.puts IO.read('LICENSE.md')
31
38
  @io.puts
@@ -89,7 +96,7 @@ module GLI
89
96
  name = "[no-]#{name}" if name.to_s.length > 1
90
97
  aliases = aliases.map { |_| _.to_s.length > 1 ? "[no-]#{_}" : _ }
91
98
  end
92
- invocations = ([name] + aliases).map { |_| "`" + add_dashes(_) + "`" }.join('|')
99
+ invocations = ([name] + aliases).map { |_| "`" + add_dashes(_).strip + "`" }.join('|')
93
100
  @io.puts header("#{invocations}", 2)
94
101
  @io.puts
95
102
  @io.puts String(desc).strip
@@ -109,8 +116,10 @@ module GLI
109
116
 
110
117
  # Gives you a command in the current context and creates a new context of this command
111
118
  def command(name, aliases, desc, long_desc, arg_name, arg_options)
119
+ @parent_command.push ([name] + aliases).join('|')
112
120
  arg_name_fmt = @arg_name_formatter.format(arg_name, arg_options, [])
113
- @io.puts header("`$ #{@exe}` <mark>`#{([name] + aliases).join('|')}`</mark> `#{arg_name_fmt}`", 1)
121
+ arg_name_fmt = " `#{arg_name_fmt.strip}`" if arg_name_fmt
122
+ @io.puts header("`$ #{@exe}` <mark>`#{@parent_command.join(' ')}`</mark>#{arg_name_fmt}", 1)
114
123
  @io.puts
115
124
  @io.puts "*#{String(desc).strip}*"
116
125
  @io.puts
@@ -121,6 +130,7 @@ module GLI
121
130
 
122
131
  # Ends a command, and "pops" you back up one context
123
132
  def end_command(_name)
133
+ @parent_command.pop
124
134
  decrement_nest
125
135
  @io.puts "* * * * * *\n\n" unless @nest.size > 2
126
136
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hook
4
+ module PromptSTD
5
+ ##
6
+ ## Clear the terminal screen
7
+ ##
8
+ def clear_screen(msg = nil)
9
+ puts "\e[H\e[2J" if $stdout.tty?
10
+ puts msg if msg.good?
11
+ end
12
+
13
+ ##
14
+ ## Redirect STDOUT and STDERR to /dev/null or file
15
+ ##
16
+ ## @param file [String] a file path to redirect to
17
+ ##
18
+ def silence_std(file = '/dev/null')
19
+ $stdout = File.new(file, 'w')
20
+ $stderr = File.new(file, 'w')
21
+ end
22
+
23
+ ##
24
+ ## Restore silenced STDOUT and STDERR
25
+ ##
26
+ def restore_std
27
+ $stdout = STDOUT
28
+ $stderr = STDERR
29
+ end
30
+ end
31
+
32
+ # Methods for working installing/using FuzzyFileFinder
33
+ module PromptFZF
34
+ ##
35
+ ## Get path to fzf binary, installing if needed
36
+ ##
37
+ ## @return [String] Path to fzf binary
38
+ ##
39
+ def fzf
40
+ @fzf ||= install_fzf
41
+ end
42
+
43
+ ##
44
+ ## Remove fzf binary
45
+ ##
46
+ def uninstall_fzf
47
+ fzf_bin = File.join(File.dirname(__FILE__), '../helpers/fzf/bin/fzf')
48
+ FileUtils.rm_f(fzf_bin) if File.exist?(fzf_bin)
49
+ warn 'fzf: removed #{fzf_bin}'
50
+ end
51
+
52
+ ##
53
+ ## Return the path to the fzf binary
54
+ ##
55
+ ## @return [String] Path to fzf
56
+ ##
57
+ def which_fzf
58
+ fzf_dir = File.join(File.dirname(__FILE__), '../helpers/fzf')
59
+ fzf_bin = File.join(fzf_dir, 'bin/fzf')
60
+ return fzf_bin if File.exist?(fzf_bin)
61
+
62
+ TTY::Which.which('fzf')
63
+ end
64
+
65
+ ##
66
+ ## Install fzf on the current system. Installs to a
67
+ ## subdirectory of the gem
68
+ ##
69
+ ## @param force [Boolean] If true, reinstall if
70
+ ## needed
71
+ ##
72
+ ## @return [String] Path to fzf binary
73
+ ##
74
+ def install_fzf(force: false)
75
+ if force
76
+ uninstall_fzf
77
+ elsif which_fzf
78
+ return which_fzf
79
+ end
80
+
81
+ fzf_dir = File.join(File.dirname(__FILE__), '../helpers/fzf')
82
+ FileUtils.mkdir_p(fzf_dir) unless File.directory?(fzf_dir)
83
+ fzf_bin = File.join(fzf_dir, 'bin/fzf')
84
+ return fzf_bin if File.exist?(fzf_bin)
85
+
86
+ warn 'fzf: Compiling and installing fzf -- this will only happen once'
87
+ warn 'fzf: fzf is copyright Junegunn Choi, MIT License <https://github.com/junegunn/fzf/blob/master/LICENSE>'
88
+
89
+ silence_std
90
+ `'#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null`
91
+ unless File.exist?(fzf_bin)
92
+ restore_std
93
+ warn 'Error installing, trying again as root'
94
+ silence_std
95
+ `sudo '#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null`
96
+ end
97
+ restore_std
98
+ unless File.exist?(fzf_bin)
99
+ puts 'fzf: unable to install fzf. You can install manually and Hook CLI will use the system version.'
100
+ puts 'fzf: see https://github.com/junegunn/fzf#installation'
101
+ raise RuntimeError.new('Error installing fzf, please report at https://github.com/ttscoff/hookapp/issues')
102
+ end
103
+
104
+ warn "fzf: installed to #{fzf}"
105
+ fzf_bin
106
+ end
107
+ end
108
+
109
+ module Prompt
110
+ include PromptSTD
111
+ include PromptFZF
112
+ end
113
+ end
data/lib/hook/string.rb CHANGED
@@ -57,4 +57,8 @@ class String
57
57
 
58
58
  true
59
59
  end
60
+
61
+ def escape_quotes
62
+ gsub(/"/, '\\"')
63
+ end
60
64
  end
data/lib/hook/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hook
4
- VERSION = '2.0.5'
4
+ VERSION = '2.0.9'
5
5
  end
data/lib/hook.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'hook/version'
4
+ require 'tty-which'
4
5
  require 'shellwords'
5
6
  require 'cgi'
6
7
  require 'gli'
8
+ require 'hook/prompt'
7
9
  require 'hook/string'
8
10
  require 'hook/hookapp'
9
11
  require 'hook/hooker'
@@ -0,0 +1,76 @@
1
+ require 'fileutils'
2
+ require 'open3'
3
+
4
+ class ::Numeric
5
+ def pad_to(x)
6
+ "%0#{x.to_i}d" % self
7
+ end
8
+ end
9
+
10
+ module HookHelpers
11
+ HOOK_EXEC = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'hook')
12
+ HOOK_FILES_DIR = File.join(File.dirname(__FILE__), '..', 'hookfiles')
13
+ HOOK_COMPLETIONS_DIR = File.join(File.dirname(__FILE__), '..', '..', 'lib', 'completion')
14
+
15
+ def create_temp_files
16
+ FileUtils.mkdir_p HOOK_FILES_DIR
17
+ 10.times.with_index do |i|
18
+ new_file = File.join(HOOK_FILES_DIR, i.pad_to(2) + '.md')
19
+ File.open(new_file, 'w') do |f|
20
+ f.puts ("Hook Test File ##{i}")
21
+ end
22
+ hook('rm', '-a', '-f', new_file)
23
+ end
24
+ end
25
+
26
+ def clean_temp_files
27
+ FileUtils.rm_r HOOK_FILES_DIR, force: true
28
+ end
29
+
30
+ def assert_count_links(file, count, msg)
31
+ res = hook('ls', file).strip
32
+
33
+ links = res == 'No bookmarks' ? 0 : res.split(/\n/).size
34
+
35
+ assert_equal(count, links, msg)
36
+ end
37
+
38
+ def assert_links_include(file, pattern, msg)
39
+ result = hook('ls', file).strip
40
+ assert_match(/#{pattern}/, result, msg)
41
+ end
42
+
43
+ def hook(*args)
44
+ hook_with_env({}, *args)
45
+ end
46
+
47
+ def hook_with_stdin(input, *args)
48
+ pread_stdin({}, input, HOOK_EXEC, *args)
49
+ end
50
+
51
+ def hook_with_env(env, *args)
52
+ pread(env, HOOK_EXEC, *args)
53
+ end
54
+
55
+ def pread_stdin(env, input, *cmd)
56
+ out, err, status = Open3.capture3(env, 'bundle', 'exec', *cmd, :stdin_data => input)
57
+ unless status.success?
58
+ raise [
59
+ "Error (#{status}): #{cmd.inspect} failed", "STDOUT:", out.inspect, "STDERR:", err.inspect
60
+ ].join("\n")
61
+ end
62
+
63
+ out
64
+ end
65
+
66
+ def pread(env, *cmd)
67
+ out, err, status = Open3.capture3(env, 'bundle', 'exec', *cmd)
68
+ unless status.success?
69
+ raise [
70
+ "Error (#{status}): #{cmd.inspect} failed", "STDOUT:", out.inspect, "STDERR:", err.inspect
71
+ ].join("\n")
72
+ end
73
+
74
+ out
75
+ end
76
+ end
@@ -0,0 +1,24 @@
1
+ require 'hook-helpers'
2
+ require 'test_helper'
3
+
4
+ class ClipTest < Test::Unit::TestCase
5
+ include HookHelpers
6
+
7
+ def setup
8
+ @basedir = HOOK_FILES_DIR
9
+ create_temp_files
10
+ end
11
+
12
+ def teardown
13
+ clean_temp_files
14
+ end
15
+
16
+ def test_clip
17
+ file = Dir.glob(File.join(HOOK_FILES_DIR, '*.md'))[0]
18
+ # Clear clipboard
19
+ `echo -n | pbcopy`
20
+ hook('clip', file)
21
+ clipboard = `pbpaste`.strip
22
+ assert_match(/^hook:.*?#{File.basename(file).sub(/\./, '(%2E|\.)')}$/, clipboard, 'Clipboard should contain link to first file')
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ require 'hook-helpers'
2
+ require 'test_helper'
3
+
4
+ class CloneTest < Test::Unit::TestCase
5
+ include HookHelpers
6
+
7
+ def setup
8
+ @basedir = HOOK_FILES_DIR
9
+ create_temp_files
10
+ end
11
+
12
+ def teardown
13
+ clean_temp_files
14
+ end
15
+
16
+ def test_clone
17
+ count = 3
18
+
19
+ files = Dir.glob(File.join(HOOK_FILES_DIR, '*.md'))
20
+
21
+ # Link all files to last file
22
+ hook('link', *files[0..count - 1])
23
+ links = hook('ls', files[count - 1]).strip
24
+
25
+ hook('clone', files[count-1], files[count])
26
+ cloned_links = hook('ls', files[count]).strip
27
+
28
+ assert_match(links, cloned_links, "#{files[count - 1]} links should match #{files[count]} links")
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'hook-helpers'
2
+ require 'test_helper'
3
+
4
+ class EncodeTest < Test::Unit::TestCase
5
+ include HookHelpers
6
+
7
+ def setup
8
+ end
9
+
10
+ def teardown
11
+ end
12
+
13
+ # # FIXME: I don't know why this isn't getting output
14
+ # def test_encode_args
15
+ # result = hook('percent', 'encode', %(here's a "string?"))
16
+ # assert_match(/here%27s%20a%20%22string%3F%22/, result, 'URL encoded string should match')
17
+ # end
18
+
19
+ def test_encode_stdin
20
+ result = pread_stdin({}, %(here's a "string?"), HOOK_EXEC, 'percent', 'encode')
21
+ assert_match(/here%27s%20a%20%22string%3F%22/, result, 'URL encoded string should match')
22
+ end
23
+
24
+ def test_encode_decode_stdin
25
+ original_string = %(here's a "string?")
26
+ encoded = pread_stdin({}, original_string, HOOK_EXEC, 'percent', 'encode')
27
+ decoded = pread_stdin({}, encoded, HOOK_EXEC, 'percent', 'decode')
28
+ assert_match(decoded, original_string, 'URL encoded string should match')
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ require 'hook-helpers'
2
+ require 'test_helper'
3
+
4
+ class LinkTest < Test::Unit::TestCase
5
+ include HookHelpers
6
+
7
+ def setup
8
+ @basedir = HOOK_FILES_DIR
9
+ create_temp_files
10
+ end
11
+
12
+ def teardown
13
+ clean_temp_files
14
+ end
15
+
16
+ def test_link
17
+ count = 3
18
+
19
+ files = Dir.glob(File.join(HOOK_FILES_DIR, '*.md'))[0..(count - 1)]
20
+
21
+ # Link all files to last file
22
+ hook('link', *files)
23
+
24
+ assert_count_links(files[-1], count - 1, "Last file should have #{count - 1} links")
25
+ # I think hook behavior with multi-file linking has changed...
26
+ # assert_count_links(files[0], 1, 'First file should have 1 link')
27
+ end
28
+
29
+ def test_bi_link
30
+ count = 3
31
+
32
+ files = Dir.glob(File.join(HOOK_FILES_DIR, '*.md'))[0..(count - 1)]
33
+
34
+ # Link all files bi-directionally
35
+ hook('link', '-a', *files)
36
+
37
+ assert_count_links(files[-1], count - 1, "Last file should have #{count - 1} links")
38
+ assert_count_links(files[0], count - 1, "First file should have #{count - 1} links")
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ require 'hook-helpers'
2
+ require 'test_helper'
3
+
4
+ class ListTest < Test::Unit::TestCase
5
+ include HookHelpers
6
+
7
+ def setup
8
+ @basedir = HOOK_FILES_DIR
9
+ create_temp_files
10
+ end
11
+
12
+ def teardown
13
+ clean_temp_files
14
+ end
15
+
16
+ def test_list
17
+ count = 2
18
+ files = Dir.glob(File.join(HOOK_FILES_DIR, '*.md'))[0..(count - 1)]
19
+ # Link all files to last file
20
+ hook('link', *files)
21
+
22
+ assert_count_links(files[-1], count - 1, "Last file should have #{count - 1} links")
23
+ assert_links_include(files[-1], File.basename(files[0]), 'Links on last file should include first file')
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ require 'hook-helpers'
2
+ require 'test_helper'
3
+
4
+ class RemoveTest < Test::Unit::TestCase
5
+ include HookHelpers
6
+
7
+ def setup
8
+ @basedir = HOOK_FILES_DIR
9
+ create_temp_files
10
+ end
11
+
12
+ def teardown
13
+ clean_temp_files
14
+ end
15
+
16
+ def test_remove
17
+ count = 5
18
+
19
+ files = Dir.glob(File.join(HOOK_FILES_DIR, '*.md'))[0..(count - 1)]
20
+
21
+ # Link all files to last file
22
+ hook('link', *files)
23
+
24
+ assert_count_links(files[-1], count - 1, "Last file should start with #{count - 1} links")
25
+
26
+ hook('remove', files[-1], files[0])
27
+
28
+ assert_count_links(files[-1], count - 2, "Last file should have #{count - 2} links")
29
+
30
+ hook('remove', '--all', '--force', files[-1])
31
+
32
+ assert_count_links(files[-1], 0, "Last file should end with 0 links")
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ require 'hook-helpers'
2
+ require 'test_helper'
3
+
4
+ class ScriptsTest < Test::Unit::TestCase
5
+ include HookHelpers
6
+
7
+ def setup
8
+ end
9
+
10
+ def teardown
11
+ end
12
+
13
+ def test_shell_scripts
14
+ %w[bash fish zsh].each do |sh|
15
+ source = IO.read(File.join(HOOK_COMPLETIONS_DIR, 'hook_completion.' + sh))
16
+ result = hook('scripts', sh)
17
+
18
+ assert_match(source, result, 'Script output should match')
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hookapp
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-18 00:00:00.000000000 Z
11
+ date: 2022-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aruba
@@ -66,6 +66,26 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 2.20.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-which
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.5'
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 0.5.0
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '0.5'
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 0.5.0
69
89
  description:
70
90
  email: me@brettterpstra.com
71
91
  executables:
@@ -76,9 +96,11 @@ extra_rdoc_files:
76
96
  - hook.rdoc
77
97
  files:
78
98
  - ".gitignore"
99
+ - AUTHORS.md
79
100
  - CHANGELOG.md
80
101
  - Gemfile
81
102
  - Gemfile.lock
103
+ - LICENSE
82
104
  - LICENSE.md
83
105
  - OVERVIEW.md
84
106
  - README.md
@@ -148,26 +170,21 @@ files:
148
170
  - lib/completion/hook_completion.bash
149
171
  - lib/completion/hook_completion.fish
150
172
  - lib/completion/hook_completion.zsh
151
- - lib/helpers/fuzzyfilefinder
152
173
  - lib/hook.rb
153
174
  - lib/hook/hookapp.rb
154
175
  - lib/hook/hooker.rb
155
176
  - lib/hook/markdown_document_listener.rb
177
+ - lib/hook/prompt.rb
156
178
  - lib/hook/string.rb
157
179
  - lib/hook/version.rb
158
- - test/default_test.rb
159
- - test/hookfiles/01.test
160
- - test/hookfiles/02.test
161
- - test/hookfiles/03.test
162
- - test/hookfiles/04.test
163
- - test/hookfiles/05.test
164
- - test/hookfiles/06.test
165
- - test/hookfiles/07.test
166
- - test/hookfiles/08.test
167
- - test/hookfiles/09.test
168
- - test/hookfiles/10.test
169
- - test/hookfiles/11.test
170
- - test/hookfiles/12.test
180
+ - test/helpers/hook-helpers.rb
181
+ - test/hook_clip_test.rb
182
+ - test/hook_clone_test.rb
183
+ - test/hook_encode_test.rb
184
+ - test/hook_link_test.rb
185
+ - test/hook_list_test.rb
186
+ - test/hook_remove_test.rb
187
+ - test/hook_scripts_test.rb
171
188
  - test/test_helper.rb
172
189
  homepage: https://brettterpstra.com
173
190
  licenses: []
Binary file
data/test/default_test.rb DELETED
@@ -1,14 +0,0 @@
1
- require 'test_helper'
2
-
3
- class DefaultTest < Test::Unit::TestCase
4
-
5
- def setup
6
- end
7
-
8
- def teardown
9
- end
10
-
11
- def test_the_truth
12
- assert true
13
- end
14
- end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes