tty-file 0.1.0

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.
@@ -0,0 +1,104 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ module File
5
+ class CreateFile
6
+ attr_reader :relative_path, :content, :options, :prompt
7
+
8
+ def initialize(relative_path, content, options = {})
9
+ @content = content
10
+ @options = options
11
+ @relative_path = convert_encoded_path(relative_path)
12
+ @prompt = TTY::Prompt.new
13
+ end
14
+
15
+ def exist?
16
+ ::File.exist?(relative_path)
17
+ end
18
+
19
+ def identical?
20
+ ::File.binread(relative_path) == content
21
+ end
22
+
23
+ def log_status(*args)
24
+ TTY::File.log_status(*args)
25
+ end
26
+
27
+ # Create a file
28
+ #
29
+ # @api public
30
+ def call
31
+ detect_collision do
32
+ FileUtils.mkdir_p(::File.dirname(relative_path))
33
+ ::File.open(relative_path, 'wb') { |f| f.write(content) }
34
+ end
35
+ relative_path
36
+ end
37
+
38
+ protected
39
+
40
+ def context
41
+ options[:context]
42
+ end
43
+
44
+ def convert_encoded_path(filename)
45
+ filename.gsub(/%(.*?)%/) do |match|
46
+ method = $1.strip
47
+ if context.respond_to?(method, true)
48
+ context.public_send(method)
49
+ else
50
+ match
51
+ end
52
+ end
53
+ end
54
+
55
+ # Check if file already exists and ask for user input on collision
56
+ #
57
+ # @api private
58
+ def detect_collision
59
+ if exist?
60
+ if identical?
61
+ log_status(:identical, relative_path, options.fetch(:verbose, true), :blue)
62
+ elsif options[:force]
63
+ log_status(:force, relative_path, options.fetch(:verbose, true), :yellow)
64
+ elsif options[:skip]
65
+ log_status(:skip, relative_path, options.fetch(:verbose, true), :yellow)
66
+ else
67
+ log_status(:collision, relative_path, options.fetch(:verbose, true), :red)
68
+ yield if file_collision(relative_path, content)
69
+ end
70
+ else
71
+ log_status(:create, relative_path, options.fetch(:verbose, true), :green)
72
+ return if options[:noop]
73
+ yield
74
+ end
75
+ end
76
+
77
+ # Display conflict resolution menu and gather answer
78
+ #
79
+ # @api private
80
+ def file_collision(relative_path, content)
81
+ choices = [
82
+ { key: 'y', name: 'yes, overwrite', value: :yes },
83
+ { key: 'n', name: 'no, do not overwrite', value: :no },
84
+ { key: 'q', name: 'quit, abort', value: :quit },
85
+ { key: 'd', name: 'diff, compare files line by line', value: :diff }
86
+ ]
87
+ answer = prompt.expand("Overwrite #{relative_path}?", choices)
88
+ interpret_answer(answer)
89
+ end
90
+
91
+ # @api private
92
+ def interpret_answer(answer)
93
+ case answer
94
+ when :yes
95
+ true
96
+ when :no
97
+ false
98
+ when :quit
99
+ abort
100
+ end
101
+ end
102
+ end # CreateFile
103
+ end # File
104
+ end # TTY
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ require 'diff/lcs'
4
+ require 'diff/lcs/hunk'
5
+ require 'enumerator'
6
+
7
+ module TTY
8
+ module File
9
+ class Differ
10
+ # Create a Differ
11
+ #
12
+ # @api public
13
+ def initialize(string_a, string_b, options = {})
14
+ @string_a = string_a
15
+ @string_b = string_b
16
+ @format = options.fetch(:format, :unified)
17
+ @context_lines = options.fetch(:context_lines, 3)
18
+ end
19
+
20
+ # Find character difference between two strings
21
+ #
22
+ # @return [String]
23
+ # the difference between content or empty if no
24
+ # difference found
25
+ #
26
+ # @api public
27
+ def call
28
+ diffs = Diff::LCS.diff(string_a_lines, string_b_lines)
29
+ return '' if diffs.empty?
30
+ hunks = extract_hunks(diffs)
31
+ format_hunks(hunks)
32
+ end
33
+
34
+ private
35
+
36
+ def convert_to_lines(string)
37
+ string.split(/\n/).map(&:chomp)
38
+ end
39
+
40
+ def string_a_lines
41
+ convert_to_lines(@string_a)
42
+ end
43
+
44
+ def string_b_lines
45
+ convert_to_lines(@string_b)
46
+ end
47
+
48
+ # @api public
49
+ def extract_hunks(diffs)
50
+ file_length_difference = 0
51
+
52
+ diffs.map do |piece|
53
+ hunk = Diff::LCS::Hunk.new(string_a_lines, string_b_lines, piece,
54
+ @context_lines, file_length_difference)
55
+ file_length_difference = hunk.file_length_difference
56
+ hunk
57
+ end
58
+ end
59
+
60
+ # @api public
61
+ def format_hunks(hunks)
62
+ output = ""
63
+ hunks.each_cons(2) do |prev_hunk, current_hunk|
64
+ begin
65
+ if current_hunk.overlaps?(prev_hunk)
66
+ current_hunk.unshift(prev_hunk)
67
+ else
68
+ output << prev_hunk.diff(@format).to_s
69
+ end
70
+ ensure
71
+ output << "\n"
72
+ end
73
+ end
74
+ output << hunks.last.diff(@format) << "\n" if hunks.last
75
+ end
76
+ end # Differ
77
+ end # File
78
+ end # TTY
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+
6
+ module TTY
7
+ module File
8
+ DownloadError = Class.new(StandardError)
9
+
10
+ class DownloadFile
11
+ attr_reader :uri, :dest_path, :limit
12
+
13
+ DEFAULT_REDIRECTS = 3
14
+
15
+ # @options
16
+ #
17
+ def initialize(url, dest_path, options = {})
18
+ @uri = URI.parse(url)
19
+ @dest_path = dest_path
20
+ @limit = options.fetch(:limit) { DEFAULT_REDIRECTS }
21
+ end
22
+
23
+ # Download a file
24
+ #
25
+ # @api public
26
+ def call
27
+ download(uri, dest_path, limit)
28
+ end
29
+
30
+ private
31
+
32
+ # @api private
33
+ def download(uri, path, limit)
34
+ raise DownloadError, 'Redirect limit reached!' if limit.zero?
35
+ content = ''
36
+
37
+ Net::HTTP.start(uri.host, uri.port,
38
+ use_ssl: uri.scheme == 'https') do |http|
39
+ http.request_get(uri.path) do |response|
40
+ case response
41
+ when Net::HTTPSuccess
42
+ response.read_body do |seg|
43
+ content << seg
44
+ end
45
+ when Net::HTTPRedirection
46
+ download(URI.parse(response['location']), path, limit - 1)
47
+ else
48
+ response.error!
49
+ end
50
+ end
51
+ end
52
+ content
53
+ end
54
+ end # DownloadFile
55
+ end # File
56
+ end # TTY
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ module File
5
+ VERSION = "0.1.0"
6
+ end # File
7
+ end # TTY
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ desc 'Load gem inside irb console'
4
+ task :console do
5
+ require 'irb'
6
+ require 'irb/completion'
7
+ require File.join(__FILE__, '../../lib/tty-file')
8
+ ARGV.clear
9
+ IRB.start
10
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ desc 'Measure code coverage'
4
+ task :coverage do
5
+ begin
6
+ original, ENV['COVERAGE'] = ENV['COVERAGE'], 'true'
7
+ Rake::Task['spec'].invoke
8
+ ensure
9
+ ENV['COVERAGE'] = original
10
+ end
11
+ end
data/tasks/spec.rake ADDED
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc 'Run all specs'
7
+ RSpec::Core::RakeTask.new(:spec) do |task|
8
+ task.pattern = 'spec/{unit,integration}{,/*/**}/*_spec.rb'
9
+ end
10
+
11
+ namespace :spec do
12
+ desc 'Run unit specs'
13
+ RSpec::Core::RakeTask.new(:unit) do |task|
14
+ task.pattern = 'spec/unit{,/*/**}/*_spec.rb'
15
+ end
16
+
17
+ desc 'Run integration specs'
18
+ RSpec::Core::RakeTask.new(:integration) do |task|
19
+ task.pattern = 'spec/integration{,/*/**}/*_spec.rb'
20
+ end
21
+ end
22
+
23
+ rescue LoadError
24
+ %w[spec spec:unit spec:integration].each do |name|
25
+ task name do
26
+ $stderr.puts "In order to run #{name}, do `gem install rspec`"
27
+ end
28
+ end
29
+ end
data/tty-file.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tty/file/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tty-file"
8
+ spec.version = TTY::File::VERSION
9
+ spec.authors = ["Piotr Murach"]
10
+ spec.email = [""]
11
+
12
+ spec.summary = %q{File manipulation utility methods}
13
+ spec.description = %q{File manipulation utility methods}
14
+ spec.homepage = "https://piotrmurach.github.io/tty"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'pastel', '~> 0.6.0'
23
+ spec.add_dependency 'tty-prompt', '~> 0.7.0'
24
+ spec.add_dependency 'diff-lcs', '~> 1.2.5'
25
+
26
+ spec.add_development_dependency 'bundler', '>= 1.5.0', '< 2.0'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tty-file
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Murach
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pastel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-prompt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.7.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.7.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: diff-lcs
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.2.5
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.2.5
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.5.0
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: '2.0'
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 1.5.0
72
+ - - "<"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rake
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '10.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '10.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rspec
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '3.0'
103
+ description: File manipulation utility methods
104
+ email:
105
+ - ''
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - ".gitignore"
111
+ - ".rspec"
112
+ - ".travis.yml"
113
+ - CHANGELOG.md
114
+ - CODE_OF_CONDUCT.md
115
+ - Gemfile
116
+ - LICENSE.txt
117
+ - README.md
118
+ - Rakefile
119
+ - bin/console
120
+ - bin/setup
121
+ - lib/tty-file.rb
122
+ - lib/tty/file.rb
123
+ - lib/tty/file/create_file.rb
124
+ - lib/tty/file/differ.rb
125
+ - lib/tty/file/download_file.rb
126
+ - lib/tty/file/version.rb
127
+ - tasks/console.rake
128
+ - tasks/coverage.rake
129
+ - tasks/spec.rake
130
+ - tty-file.gemspec
131
+ homepage: https://piotrmurach.github.io/tty
132
+ licenses:
133
+ - MIT
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.5.1
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: File manipulation utility methods
155
+ test_files: []