tty-file 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []