Module: Yard::Timekeeper

Defined in:
lib/yard/timekeeper.rb,
lib/yard/timekeeper/version.rb,
sig/yard/timekeeper.rbs,
sig/yard/timekeeper/version.rbs

Defined Under Namespace

Modules: Version Classes: Error

Constant Summary collapse

RAKE_INTEGRATIONS =
{}
RAKE_INTEGRATIONS_MUTEX =
Mutex.new
TITLE_GENERATOR_DIFF_LINE_RE =
/\A[+-].*Documentation by YARD \d+(?:\.\d+)+(?:[.\w-][.\w-]*)?\s*\z/
TIMESTAMP_DIFF_LINE_RE =
/\A[+-]\s{2}Generated on .+ by\s*\z/
/
  \A[+-]\s{2}
  \d+(?:\.\d+)+(?:[.\w-][.\w-]*)?
  \s\(ruby-[^)]+\)\.\s*
  \z
/x
TITLE_GENERATOR_LINE_RE =
/Documentation by YARD \d+(?:\.\d+)+(?:[.\w-][.\w-]*)?/
TIMESTAMP_LINE_RE =
/\A\s{2}Generated on .+ by\s*\z/
/
  \A\s{2}
  \d+(?:\.\d+)+(?:[.\w-][.\w-]*)?
  \s\(ruby-[^)]+\)\.\s*
  \z
/x
DIFF_METADATA_PREFIXES =
[
  "diff --git ",
  "index ",
  "--- ",
  "+++ ",
  "@@ "
].freeze
VERSION =

Traditional Constant Location

Returns:

  • (String)
Version::VERSION

Class Method Summary collapse

Class Method Details

.__reset_rake_integrations__Object



82
83
84
85
# File 'lib/yard/timekeeper.rb', line 82

def __reset_rake_integrations__
  RAKE_INTEGRATIONS_MUTEX.synchronize { RAKE_INTEGRATIONS.clear }
  nil
end

Returns:

  • (Boolean)


146
147
148
149
150
151
152
153
154
# File 'lib/yard/timekeeper.rb', line 146

def balanced_footer_churn?(change_lines)
  removed_lines = change_lines.select { |line| line.start_with?("-") }
  added_lines = change_lines.select { |line| line.start_with?("+") }

  removed_lines.size == added_lines.size &&
    removed_lines.size.between?(1, 2) &&
    removed_lines.map { |line| footer_churn_kind(line) }.sort ==
      added_lines.map { |line| footer_churn_kind(line) }.sort
end

.changed_docs_files(root) ⇒ Object



100
101
102
103
104
105
106
107
# File 'lib/yard/timekeeper.rb', line 100

def changed_docs_files(root)
  stdout, status = Open3.capture2("git", "diff", "--name-only", "--relative", "--", "docs", chdir: root)
  return [] unless status.success?

  stdout.lines.map(&:strip).reject(&:empty?)
rescue Errno::ENOENT
  []
end

.enabled?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/yard/timekeeper.rb', line 87

def enabled?
  !ENV.fetch("YARD_TIMEKEEPER_DISABLE", "false").casecmp?("true")
end


156
157
158
159
160
161
162
# File 'lib/yard/timekeeper.rb', line 156

def footer_churn_kind(line)
  return :title_generator if line.match?(TITLE_GENERATOR_DIFF_LINE_RE)
  return :timestamp if line.match?(TIMESTAMP_DIFF_LINE_RE)
  return :footer_generator_version if line.match?(FOOTER_GENERATOR_VERSION_DIFF_LINE_RE)

  :unknown
end

Returns:

  • (Boolean)


140
141
142
143
144
# File 'lib/yard/timekeeper.rb', line 140

def footer_churn_line?(line)
  line.match?(TITLE_GENERATOR_DIFF_LINE_RE) ||
    line.match?(TIMESTAMP_DIFF_LINE_RE) ||
    line.match?(FOOTER_GENERATOR_VERSION_DIFF_LINE_RE)
end

.generated_metadata_kind(line) ⇒ Object



209
210
211
212
213
214
215
216
# File 'lib/yard/timekeeper.rb', line 209

def (line)
  stripped_line = line.strip
  return :title_generator if stripped_line.match?(TITLE_GENERATOR_LINE_RE)
  return :timestamp if line.match?(TIMESTAMP_LINE_RE)
  return :footer_generator_version if line.match?(FOOTER_GENERATOR_VERSION_LINE_RE)

  nil
end

.generated_metadata_lines(content) ⇒ Object



202
203
204
205
206
207
# File 'lib/yard/timekeeper.rb', line 202

def (content)
  content.lines.each_with_object({}) do |line, replacements|
    kind = (line)
    replacements[kind] ||= line if kind
  end
end

.git_diff(path, root) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/yard/timekeeper.rb', line 109

def git_diff(path, root)
  stdout, _status = Open3.capture2(
    "git",
    "diff",
    "--no-ext-diff",
    "--no-color",
    "--unified=0",
    "--",
    path,
    chdir: root
  )
  stdout
end

.git_rootObject



91
92
93
94
95
96
97
98
# File 'lib/yard/timekeeper.rb', line 91

def git_root
  stdout, status = Open3.capture2("git", "rev-parse", "--show-toplevel", chdir: Dir.pwd)
  return unless status.success?

  stdout.strip
rescue Errno::ENOENT
  nil
end

.install_rake_tasks!(yard_task_name = :yard) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/yard/timekeeper.rb', line 68

def install_rake_tasks!(yard_task_name = :yard)
  return false unless defined?(::Rake::Task) && ::Rake::Task.task_defined?(yard_task_name)

  RAKE_INTEGRATIONS_MUTEX.synchronize do
    key = yard_task_name.to_s
    unless RAKE_INTEGRATIONS[key]
      ::Rake::Task[yard_task_name].enhance { ::Yard::Timekeeper.postprocess_html_docs }
      RAKE_INTEGRATIONS[key] = true
    end
  end

  true
end

.normalize_generated_metadata(path, root) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/yard/timekeeper.rb', line 164

def (path, root)
  original = tracked_file_content(path, root)
  return false unless original

  absolute_path = File.join(root, path)
  return false unless File.file?(absolute_path)

  replacement_lines = (original)
  return false if replacement_lines.empty?

  current = File.read(absolute_path)
  changed = false
  normalized_lines = current.lines.map do |line|
    kind = (line)
    replacement = replacement_lines[kind]
    if replacement && line != replacement
      changed = true
      replacement
    else
      line
    end
  end

  return false unless changed

  File.write(absolute_path, normalized_lines.join)
  true
end

.postprocess_html_docsObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/yard/timekeeper.rb', line 42

def postprocess_html_docs
  return unless enabled?

  docs_dir = File.join(Dir.pwd, "docs")
  return unless Dir.exist?(docs_dir)

  root = git_root
  return unless root

  changed_docs_files(root).each do |relative_path|
    next unless relative_path.end_with?(".html")
    diff = git_diff(relative_path, root)
    if timestamp_only_diff?(diff)
      restore_file(relative_path, root)
    else
      (relative_path, root)
    end
  end
rescue => e
  warn("Yard::Timekeeper.postprocess_html_docs failed: #{e.class}: #{e.message}")
end

.restore_file(path, root) ⇒ Object



218
219
220
221
222
223
# File 'lib/yard/timekeeper.rb', line 218

def restore_file(path, root)
  _stdout, _stderr, status = Open3.capture3("git", "checkout", "--", path, chdir: root)
  status.success?
rescue Errno::ENOENT
  false
end

.run_at_exitObject



64
65
66
# File 'lib/yard/timekeeper.rb', line 64

def run_at_exit
  postprocess_html_docs
end

.timestamp_only_diff?(diff_text) ⇒ Boolean

Returns:

  • (Boolean)


123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/yard/timekeeper.rb', line 123

def timestamp_only_diff?(diff_text)
  return false if diff_text.to_s.strip.empty?

  change_lines = []

  diff_text.each_line do |line|
    next if DIFF_METADATA_PREFIXES.any? { |prefix| line.start_with?(prefix) }
    next if line.strip.empty?

    return false unless footer_churn_line?(line)

    change_lines << line
  end

  balanced_footer_churn?(change_lines)
end

.tracked_file_content(path, root) ⇒ Object



193
194
195
196
197
198
199
200
# File 'lib/yard/timekeeper.rb', line 193

def tracked_file_content(path, root)
  stdout, status = Open3.capture2("git", "show", "HEAD:#{path}", chdir: root)
  return unless status.success?

  stdout
rescue Errno::ENOENT
  nil
end