Class: FormatStaged

Inherits:
Object
  • Object
show all
Defined in:
lib/format-staged.rb,
lib/format-staged/io.rb,
lib/format-staged/entry.rb,
lib/format-staged/version.rb

Defined Under Namespace

Classes: Entry

Constant Summary collapse

VERSION =
"0.0.1"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(formatter:, patterns:, update: true, write: true, verbose: true) ⇒ FormatStaged

Returns a new instance of FormatStaged.



9
10
11
12
13
14
15
# File 'lib/format-staged.rb', line 9

def initialize(formatter:, patterns:, update: true, write: true, verbose: true) 
  @formatter = formatter
  @patterns = patterns
  @update = update
  @write = write
  @verbose = verbose
end

Instance Attribute Details

#formatterObject (readonly)

Returns the value of attribute formatter.



7
8
9
# File 'lib/format-staged.rb', line 7

def formatter
  @formatter
end

#patternsObject (readonly)

Returns the value of attribute patterns.



7
8
9
# File 'lib/format-staged.rb', line 7

def patterns
  @patterns
end

#updateObject (readonly)

Returns the value of attribute update.



7
8
9
# File 'lib/format-staged.rb', line 7

def update
  @update
end

#verboseObject (readonly)

Returns the value of attribute verbose.



7
8
9
# File 'lib/format-staged.rb', line 7

def verbose
  @verbose
end

#writeObject (readonly)

Returns the value of attribute write.



7
8
9
# File 'lib/format-staged.rb', line 7

def write
  @write
end

Instance Method Details

#format_file(file) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/format-staged.rb', line 30

def format_file(file) 
  new_hash = format_object file
  
  return true if not write 

  if new_hash == file.dst_hash
    puts "Unchanged #{file.src_path}"
    return false
  end
  
  if object_is_empty new_hash
    puts "Skipping #{file.src_path}, formatted file is empty"
    return false
  end

  replace_file_in_index file, new_hash
  
  if update
    begin
      patch_working_file file, new_hash
    rescue => error
      puts "Warning: failed updating #{file.src_path} in working copy: #{error}"
    end
  end
  
  true
end

#format_object(file) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/format-staged.rb', line 58

def format_object(file)
  puts "Formatting #{file.src_path}"
  
  format_command = formatter.sub("{}", file.src_path.shellescape)
  
  pid1, r = pipe_command "git", "cat-file", "-p", file.dst_hash
  pid2, r = pipe_command format_command, source: r
  pid3, r = pipe_command "git", "hash-object", "-w", "--stdin", source: r
  
  result = r.readlines.map { |it| it.chomp }
  if @verbose
    result.each do |line|
      puts "< #{line}"
    end
  end

  Process.wait pid1
  raise "Cannot read #{file.dst_hash} from object database" unless $?.success?
  
  Process.wait pid2
  raise "Error formatting #{file.src_path}" unless $?.success?
  
  Process.wait pid3
  raise "Error writing formatted file back to object database" unless $?.success? && !result.empty?
  
  result.first
end

#get_output(*args, lines: true) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/format-staged/io.rb', line 2

def get_output(*args, lines: true)
  puts '> ' + args.join(' ') if @verbose
  
  output = IO.popen(args, err: :err) do |io|
    if lines
      io.readlines.map { |l| l.chomp }
    else
      io.read
    end
  end
  
  if @verbose and lines
    output.each do |line|
      puts "< #{line}"
    end
  end
  
  raise "Failed to run command" unless $?.success?
  
  output
end

#object_is_empty(hash) ⇒ Object



86
87
88
89
# File 'lib/format-staged.rb', line 86

def object_is_empty(hash)
  size = get_output("git", "cat-file", "-s", hash).first.to_i
  size == 0
end

#patch_working_file(file, new_hash) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/format-staged.rb', line 91

def patch_working_file(file, new_hash)
  patch = get_output "git", "diff", file.dst_hash, new_hash, lines: false
  patch.gsub! "a/#{file.dst_hash}", "a/#{file.src_path}"
  patch.gsub! "b/#{new_hash}", "b/#{file.src_path}"

  input, patch_out = IO.pipe
  pid, r = pipe_command "git", "apply", "-", source: input
  
  patch_out.write patch
  patch_out.close
  
  Process.wait pid
  raise "Error applying patch" unless $?.success?
end

#pipe_command(*args, source: nil) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/format-staged/io.rb', line 24

def pipe_command(*args, source: nil)
  puts (source.nil? ? '> ' : '| ') + args.join(' ')  if @verbose
  r, w = IO.pipe
  
  opts = {}
  opts[:in] = source unless source.nil?
  opts[:out] = w
  opts[:err] = :err
  
  pid = spawn(*args, **opts)
  
  w.close
  source &.close
  
  return [pid, r]
end

#replace_file_in_index(file, new_hash) ⇒ Object



106
107
108
# File 'lib/format-staged.rb', line 106

def replace_file_in_index(file, new_hash)
  get_output "git", "update-index", "--cacheinfo", "#{file.dst_mode},#{new_hash},#{file.src_path}"
end

#runObject



17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/format-staged.rb', line 17

def run()
root = get_output('git', 'rev-parse', '--show-toplevel').first

files = get_output('git', 'diff-index', '--cached', '--diff-filter=AM', '--no-renames', 'HEAD')
  .map { |line| Entry.new(line, root: root) }
  .reject { |entry| entry.symlink? }
  .filter { |entry| entry.matches?(@patterns) }
  
  files.each do |file|
    format_file(file)
  end
end