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.3'

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.



12
13
14
15
16
17
18
# File 'lib/format_staged.rb', line 12

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.



10
11
12
# File 'lib/format_staged.rb', line 10

def formatter
  @formatter
end

#patternsObject (readonly)

Returns the value of attribute patterns.



10
11
12
# File 'lib/format_staged.rb', line 10

def patterns
  @patterns
end

#updateObject (readonly)

Returns the value of attribute update.



10
11
12
# File 'lib/format_staged.rb', line 10

def update
  @update
end

#verboseObject (readonly)

Returns the value of attribute verbose.



10
11
12
# File 'lib/format_staged.rb', line 10

def verbose
  @verbose
end

#writeObject (readonly)

Returns the value of attribute write.



10
11
12
# File 'lib/format_staged.rb', line 10

def write
  @write
end

Instance Method Details

#format_file(file) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/format_staged.rb', line 33

def format_file(file)
  new_hash = format_object file

  return true unless 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 StandardError => e
      puts "Warning: failed updating #{file.src_path} in working copy: #{e}"
    end
  end

  true
end

#format_object(file) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/format_staged.rb', line 61

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 = read_output(r, lines: false).chomp

  Process.wait pid1
  raise "Cannot read #{file.dst_hash} from object database" unless $CHILD_STATUS.success?

  Process.wait pid2
  raise "Error formatting #{file.src_path}" unless $CHILD_STATUS.success?

  Process.wait pid3
  raise 'Error writing formatted file back to object database' unless $CHILD_STATUS.success? && !result.empty?

  result
end

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



5
6
7
8
9
10
11
12
13
14
# File 'lib/format-staged/io.rb', line 5

def get_output(*args, lines: true, silent: false)
  puts "> #{args.join(' ')}" if @verbose

  r = IO.popen(args, err: :err)
  output = read_output(r, lines: lines, silent: silent)

  raise 'Failed to run command' unless $CHILD_STATUS.success?

  output
end

#object_is_empty(hash) ⇒ Object



84
85
86
87
# File 'lib/format_staged.rb', line 84

def object_is_empty(hash)
  size = get_output('git', 'cat-file', '-s', hash).first.to_i
  size.zero?
end

#patch_working_file(file, new_hash) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/format_staged.rb', line 89

def patch_working_file(file, new_hash)
  patch = get_output 'git', 'diff', file.dst_hash, new_hash, lines: false, silent: true
  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

  read_output r

  Process.wait pid
  raise 'Error applying patch' unless $CHILD_STATUS.success?
end

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



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

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

  [pid, r]
end

#read_output(r, lines: true, silent: false) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/format-staged/io.rb', line 33

def read_output(r, lines: true, silent: false)
  result = r.read
  splits = result.split("\n")
  if @verbose && !silent
    splits.each do |line|
      puts "< #{line}"
    end
  end
  r.close

  lines ? splits : result
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



20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/format_staged.rb', line 20

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(&:symlink?)
          .filter { |entry| entry.matches?(@patterns) }

  files.each do |file|
    format_file(file)
  end
end