format-staged 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8fd81e745a1b6710724e4834252786c1775928cb3ca8281b4acb94f87bf0946
4
- data.tar.gz: 05f8dfa70f6405d390bf19e6c9c515ebed6647b714ecc83122e28b420f5934bf
3
+ metadata.gz: 80bea79a03480ca702efd9bec176a0a2e180e82e854c7300773b064bf4c46b1e
4
+ data.tar.gz: 06356777c82f354560e700aef495929f1dc7e4d0d72c08456271a3b08fd3d8cc
5
5
  SHA512:
6
- metadata.gz: 6d04e7ef3312f160d22796fa6c0b9f923405b7c65540cd107641c2c668d312f66859d9c661b00a3e43fbddec56fc4b888d8823057bd64d652147d8aa90ff57bf
7
- data.tar.gz: 61873bda257b0fd6d89abb4bb191f32694ea4dfb6e7f36b138d84f45b77091ba64b458195debdf498f3503b55800e3f685f170a8e0b3054b5895c324ebf8f4ec
6
+ metadata.gz: bd2a7a026faf4ff75b34b69a26aba8df9ff9d18d15be75341c365a8c5bae010044010d2fe3976cd5b60ccc9e7f0d68c7a62955bc620c56b7ad3955779713ec95
7
+ data.tar.gz: 126334604bfac8205438d64d597638490d560db058476f4a53b673f89f2b80cbc055a3f76863d5f6489b89f132140947794cb4a0cece89ea89ea287fe6c0c5f8
@@ -1,40 +1,59 @@
1
1
  #!/usr/bin/env ruby
2
- require 'format-staged'
2
+ # frozen_string_literal: true
3
+
4
+ require 'format_staged'
3
5
  require 'optparse'
4
6
 
5
7
  parameters = {
6
- :update => true,
7
- :write => true,
8
- :verbose => false,
8
+ update: true,
9
+ write: true,
10
+ verbose: false
9
11
  }
10
12
 
11
13
  parser = OptionParser.new do |opt|
12
14
  opt.banner = "Usage: #{opt.program_name} [options] [patterns]"
13
- opt.separator ""
14
- opt.on('-f', '--formatter COMMAND', 'Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will be replaced with a path to the file being formatted. (Example: "prettier --stdin-filepath \'{}\'")') do |o|
15
+ opt.separator ''
16
+ opt.on('-f', '--formatter COMMAND',
17
+ <<~DOC
18
+ Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will \
19
+ be replaced with a path to the file being formatted. \
20
+ (Example: "prettier --stdin-filepath \'{}\'")
21
+ DOC
22
+ ) do |o|
15
23
  parameters[:formatter] = o
16
24
  end
17
-
18
- opt.on('--[no-]update-working-tree', 'By default formatting changes made to staged file content will also be applied to working tree files via a patch. This option disables that behavior, leaving working tree files untouched.') do |value|
25
+
26
+ opt.on('--[no-]update-working-tree',
27
+ <<~DOC
28
+ By default formatting changes made to staged file content will also be applied to working tree \
29
+ files via a patch. This option disables that behavior, leaving working tree files untouched.#{' '}
30
+ DOC
31
+ ) do |value|
19
32
  parameters[:update] = value
20
33
  end
21
-
22
- opt.on('--[no-]write', "Prevents #{opt.program_name} from modifying staged or working tree files. You can use this option to check staged changes with a linter instead of formatting. With this option stdout from the formatter command is ignored.") do |value|
34
+
35
+ opt.on('--[no-]write',
36
+ <<~DOC
37
+ "Prevents #{opt.program_name} from modifying staged or working tree files. You can use this option \
38
+ to check staged changes with a linter instead of formatting. With this option stdout from the \
39
+ formatter command is ignored."
40
+ DOC
41
+ ) do |value|
23
42
  parameters[:write] = value
24
43
  end
25
-
26
- opt.on("-v", "--[no-]verbose", 'Shows commands being run') do |value|
44
+
45
+ opt.on('-v', '--[no-]verbose', 'Shows commands being run') do |value|
27
46
  parameters[:verbose] = value
28
47
  end
29
48
 
30
- opt.separator ""
31
-
49
+ opt.separator ''
50
+
32
51
  opt.on_tail('-h', '--help', 'Prints this help') do
33
52
  puts opt
34
53
  exit
35
54
  end
36
-
37
- opt.on_tail('--version', "Prints the version number and exits") do
55
+
56
+ opt.on_tail('--version', 'Prints the version number and exits') do
38
57
  puts FormatStaged::VERSION
39
58
  exit
40
59
  end
@@ -43,9 +62,9 @@ end
43
62
  parser.parse!
44
63
  parameters[:patterns] = ARGV
45
64
 
46
- if !parameters[:formatter] or parameters[:patterns].empty?
47
- puts "Missing formatter or file patterns!"
48
-
65
+ if !parameters[:formatter] || parameters[:patterns].empty?
66
+ puts 'Missing formatter or file patterns!'
67
+
49
68
  puts parser
50
69
  exit
51
70
  end
@@ -1,9 +1,19 @@
1
- class FormatStaged
1
+ # frozen_string_literal: true
2
+
3
+ class FormatStaged
2
4
  class Entry
3
- PATTERN = /^:(?<src_mode>\d+) (?<dst_mode>\d+) (?<src_hash>[a-f0-9]+) (?<dst_hash>[a-f0-9]+) (?<status>[A-Z])(?<score>\d+)?\t(?<src_path>[^\t]+)(?:\t(?<dst_path>[^\t]+))?$/
5
+ PATTERN = /^
6
+ :(?<src_mode>\d+)\s
7
+ (?<dst_mode>\d+)\s
8
+ (?<src_hash>[a-f0-9]+)\s
9
+ (?<dst_hash>[a-f0-9]+)\s
10
+ (?<status>[A-Z])(?<score>\d+)?\t
11
+ (?<src_path>[^\t]+)
12
+ (?:\t(?<dst_path>[^\t]+))?
13
+ $/x.freeze
4
14
 
5
15
  attr_reader :src_mode, :dst_mode, :src_hash, :dst_hash, :status, :score, :src_path, :dst_path, :path, :root
6
-
16
+
7
17
  def initialize(line, root:)
8
18
  matches = line.match(PATTERN) or raise "Cannot parse output #{line}"
9
19
  @src_mode = matches[:src_mode]
@@ -17,17 +27,15 @@ class FormatStaged
17
27
  @path = File.expand_path(@src_path, root)
18
28
  @root = root
19
29
  end
20
-
30
+
21
31
  def symlink?
22
32
  @dst_mode == '120000'
23
33
  end
24
-
34
+
25
35
  def matches?(patterns)
26
36
  result = false
27
37
  patterns.each do |pattern|
28
- if File.fnmatch? pattern, path, File::FNM_EXTGLOB
29
- result = true
30
- end
38
+ result = true if File.fnmatch? pattern, path, File::FNM_EXTGLOB
31
39
  end
32
40
  result
33
41
  end
@@ -1,40 +1,43 @@
1
- class FormatStaged
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ class FormatStaged
2
5
  def get_output(*args, lines: true)
3
- puts '> ' + args.join(' ') if @verbose
4
-
6
+ puts "> #{args.join(' ')}" if @verbose
7
+
5
8
  output = IO.popen(args, err: :err) do |io|
6
9
  if lines
7
- io.readlines.map { |l| l.chomp }
10
+ io.readlines.map(&:chomp)
8
11
  else
9
12
  io.read
10
13
  end
11
14
  end
12
-
13
- if @verbose and lines
15
+
16
+ if @verbose && lines
14
17
  output.each do |line|
15
18
  puts "< #{line}"
16
19
  end
17
20
  end
18
-
19
- raise "Failed to run command" unless $?.success?
20
-
21
+
22
+ raise 'Failed to run command' unless $CHILD_STATUS.success?
23
+
21
24
  output
22
25
  end
23
-
26
+
24
27
  def pipe_command(*args, source: nil)
25
- puts (source.nil? ? '> ' : '| ') + args.join(' ') if @verbose
28
+ puts (source.nil? ? '> ' : '| ') + args.join(' ') if @verbose
26
29
  r, w = IO.pipe
27
-
30
+
28
31
  opts = {}
29
32
  opts[:in] = source unless source.nil?
30
33
  opts[:out] = w
31
34
  opts[:err] = :err
32
-
35
+
33
36
  pid = spawn(*args, **opts)
34
-
37
+
35
38
  w.close
36
- source &.close
37
-
38
- return [pid, r]
39
- end
39
+ source&.close
40
+
41
+ [pid, r]
42
+ end
40
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class FormatStaged
2
- VERSION = "0.0.1"
3
- end
4
+ VERSION = '0.0.2'
5
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'format-staged/version'
5
+ require 'format-staged/entry'
6
+ require 'format-staged/io'
7
+ require 'shellwords'
8
+
9
+ class FormatStaged
10
+ attr_reader :formatter, :patterns, :update, :write, :verbose
11
+
12
+ def initialize(formatter:, patterns:, update: true, write: true, verbose: true)
13
+ @formatter = formatter
14
+ @patterns = patterns
15
+ @update = update
16
+ @write = write
17
+ @verbose = verbose
18
+ end
19
+
20
+ def run
21
+ root = get_output('git', 'rev-parse', '--show-toplevel').first
22
+
23
+ files = get_output('git', 'diff-index', '--cached', '--diff-filter=AM', '--no-renames', 'HEAD')
24
+ .map { |line| Entry.new(line, root: root) }
25
+ .reject(&:symlink?)
26
+ .filter { |entry| entry.matches?(@patterns) }
27
+
28
+ files.each do |file|
29
+ format_file(file)
30
+ end
31
+ end
32
+
33
+ def format_file(file)
34
+ new_hash = format_object file
35
+
36
+ return true unless write
37
+
38
+ if new_hash == file.dst_hash
39
+ puts "Unchanged #{file.src_path}"
40
+ return false
41
+ end
42
+
43
+ if object_is_empty new_hash
44
+ puts "Skipping #{file.src_path}, formatted file is empty"
45
+ return false
46
+ end
47
+
48
+ replace_file_in_index file, new_hash
49
+
50
+ if update
51
+ begin
52
+ patch_working_file file, new_hash
53
+ rescue StandardError => e
54
+ puts "Warning: failed updating #{file.src_path} in working copy: #{e}"
55
+ end
56
+ end
57
+
58
+ true
59
+ end
60
+
61
+ def format_object(file)
62
+ puts "Formatting #{file.src_path}"
63
+
64
+ format_command = formatter.sub('{}', file.src_path.shellescape)
65
+
66
+ pid1, r = pipe_command 'git', 'cat-file', '-p', file.dst_hash
67
+ pid2, r = pipe_command format_command, source: r
68
+ pid3, r = pipe_command 'git', 'hash-object', '-w', '--stdin', source: r
69
+
70
+ result = r.readlines.map(&:chomp)
71
+ if @verbose
72
+ result.each do |line|
73
+ puts "< #{line}"
74
+ end
75
+ end
76
+
77
+ Process.wait pid1
78
+ raise "Cannot read #{file.dst_hash} from object database" unless $CHILD_STATUS.success?
79
+
80
+ Process.wait pid2
81
+ raise "Error formatting #{file.src_path}" unless $CHILD_STATUS.success?
82
+
83
+ Process.wait pid3
84
+ raise 'Error writing formatted file back to object database' unless $CHILD_STATUS.success? && !result.empty?
85
+
86
+ result.first
87
+ end
88
+
89
+ def object_is_empty(hash)
90
+ size = get_output('git', 'cat-file', '-s', hash).first.to_i
91
+ size.zero?
92
+ end
93
+
94
+ def patch_working_file(file, new_hash)
95
+ patch = get_output 'git', 'diff', file.dst_hash, new_hash, lines: false
96
+ patch.gsub! "a/#{file.dst_hash}", "a/#{file.src_path}"
97
+ patch.gsub! "b/#{new_hash}", "b/#{file.src_path}"
98
+
99
+ input, patch_out = IO.pipe
100
+ pid, r = pipe_command 'git', 'apply', '-', source: input
101
+
102
+ patch_out.write patch
103
+ patch_out.close
104
+
105
+ Process.wait pid
106
+ raise 'Error applying patch' unless $CHILD_STATUS.success?
107
+ end
108
+
109
+ def replace_file_in_index(file, new_hash)
110
+ get_output 'git', 'update-index', '--cacheinfo', "#{file.dst_mode},#{new_hash},#{file.src_path}"
111
+ end
112
+ end
metadata CHANGED
@@ -1,15 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: format-staged
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Weidauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-22 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-05-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.29'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.29'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop-rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.6'
13
55
  description: git format staged
14
56
  email: sven@5sw.de
15
57
  executables:
@@ -18,23 +60,24 @@ extensions: []
18
60
  extra_rdoc_files: []
19
61
  files:
20
62
  - bin/git-format-staged
21
- - lib/format-staged.rb
22
63
  - lib/format-staged/entry.rb
23
64
  - lib/format-staged/io.rb
24
65
  - lib/format-staged/version.rb
66
+ - lib/format_staged.rb
25
67
  homepage: https://github.com/5sw/format-staged
26
68
  licenses:
27
69
  - MIT
28
- metadata: {}
70
+ metadata:
71
+ rubygems_mfa_required: 'true'
29
72
  post_install_message:
30
73
  rdoc_options: []
31
74
  require_paths:
32
75
  - lib
33
76
  required_ruby_version: !ruby/object:Gem::Requirement
34
77
  requirements:
35
- - - ">="
78
+ - - "~>"
36
79
  - !ruby/object:Gem::Version
37
- version: '0'
80
+ version: '2.7'
38
81
  required_rubygems_version: !ruby/object:Gem::Requirement
39
82
  requirements:
40
83
  - - ">="
data/lib/format-staged.rb DELETED
@@ -1,109 +0,0 @@
1
- require 'format-staged/version'
2
- require 'format-staged/entry'
3
- require 'format-staged/io'
4
- require 'shellwords'
5
-
6
- class FormatStaged
7
- attr_reader :formatter, :patterns, :update, :write, :verbose
8
-
9
- def initialize(formatter:, patterns:, update: true, write: true, verbose: true)
10
- @formatter = formatter
11
- @patterns = patterns
12
- @update = update
13
- @write = write
14
- @verbose = verbose
15
- end
16
-
17
- def run()
18
- root = get_output('git', 'rev-parse', '--show-toplevel').first
19
-
20
- files = get_output('git', 'diff-index', '--cached', '--diff-filter=AM', '--no-renames', 'HEAD')
21
- .map { |line| Entry.new(line, root: root) }
22
- .reject { |entry| entry.symlink? }
23
- .filter { |entry| entry.matches?(@patterns) }
24
-
25
- files.each do |file|
26
- format_file(file)
27
- end
28
- end
29
-
30
- def format_file(file)
31
- new_hash = format_object file
32
-
33
- return true if not write
34
-
35
- if new_hash == file.dst_hash
36
- puts "Unchanged #{file.src_path}"
37
- return false
38
- end
39
-
40
- if object_is_empty new_hash
41
- puts "Skipping #{file.src_path}, formatted file is empty"
42
- return false
43
- end
44
-
45
- replace_file_in_index file, new_hash
46
-
47
- if update
48
- begin
49
- patch_working_file file, new_hash
50
- rescue => error
51
- puts "Warning: failed updating #{file.src_path} in working copy: #{error}"
52
- end
53
- end
54
-
55
- true
56
- end
57
-
58
- def format_object(file)
59
- puts "Formatting #{file.src_path}"
60
-
61
- format_command = formatter.sub("{}", file.src_path.shellescape)
62
-
63
- pid1, r = pipe_command "git", "cat-file", "-p", file.dst_hash
64
- pid2, r = pipe_command format_command, source: r
65
- pid3, r = pipe_command "git", "hash-object", "-w", "--stdin", source: r
66
-
67
- result = r.readlines.map { |it| it.chomp }
68
- if @verbose
69
- result.each do |line|
70
- puts "< #{line}"
71
- end
72
- end
73
-
74
- Process.wait pid1
75
- raise "Cannot read #{file.dst_hash} from object database" unless $?.success?
76
-
77
- Process.wait pid2
78
- raise "Error formatting #{file.src_path}" unless $?.success?
79
-
80
- Process.wait pid3
81
- raise "Error writing formatted file back to object database" unless $?.success? && !result.empty?
82
-
83
- result.first
84
- end
85
-
86
- def object_is_empty(hash)
87
- size = get_output("git", "cat-file", "-s", hash).first.to_i
88
- size == 0
89
- end
90
-
91
- def patch_working_file(file, new_hash)
92
- patch = get_output "git", "diff", file.dst_hash, new_hash, lines: false
93
- patch.gsub! "a/#{file.dst_hash}", "a/#{file.src_path}"
94
- patch.gsub! "b/#{new_hash}", "b/#{file.src_path}"
95
-
96
- input, patch_out = IO.pipe
97
- pid, r = pipe_command "git", "apply", "-", source: input
98
-
99
- patch_out.write patch
100
- patch_out.close
101
-
102
- Process.wait pid
103
- raise "Error applying patch" unless $?.success?
104
- end
105
-
106
- def replace_file_in_index(file, new_hash)
107
- get_output "git", "update-index", "--cacheinfo", "#{file.dst_mode},#{new_hash},#{file.src_path}"
108
- end
109
- end