stats.cr 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. require "option_parser"
  2. require "./stats_lib"
  3. require "./expected_output"
  4. module Stats
  5. @@configs = [
  6. Config.common_mark_config,
  7. Config.gfm_config,
  8. ]
  9. # Set this to `true` an rerun `--update-files` to ease finding easy strict
  10. # fixes.
  11. @@improve_strict = false
  12. @@scores = Hash(String, Hash(Int32, CompareLevel)).new
  13. @@section_name_replace = Regex.new("[ \\)\\(]+")
  14. def self.process_config(test_prefix : String,
  15. raw : Bool,
  16. update_files : Bool,
  17. verbose : Bool,
  18. specified_section : String?,
  19. verbose_loose : Bool)
  20. config = @@configs.find! { |c| c.prefix == test_prefix }
  21. sections = load_common_mark_sections(test_prefix)
  22. scores = Hash(String, Hash(Int32, CompareLevel)).new
  23. sections.each do |key, value|
  24. next if !specified_section.nil? && key != specified_section
  25. units = [] of DataCase
  26. value.each do |e|
  27. result = compare_result(
  28. config,
  29. e,
  30. verbose_fail: verbose,
  31. verbose_loose_match: verbose_loose,
  32. extensions: e.extensions
  33. )
  34. units << DataCase.new(
  35. front_matter: result.test_case.to_s,
  36. input: result.test_case.markdown,
  37. expected_output: (@@improve_strict && result.compare_level == CompareLevel::Loose) ? result.test_case.html : result.result.not_nil!
  38. )
  39. if scores[key]?
  40. nested_map = scores[key]
  41. else
  42. scores[key] = Hash(Int32, CompareLevel).new
  43. nested_map = scores[key]
  44. end
  45. nested_map[e.example] = result.compare_level
  46. end
  47. if update_files && units.empty? == false
  48. file_name = key.downcase.gsub(@@section_name_replace, "_")
  49. file_name = file_name.rstrip('_')
  50. file_name = "#{file_name}.unit"
  51. Dir.mkdir_p Path["spec", test_prefix]
  52. File.write(Path["spec", test_prefix, file_name], unit_output(units))
  53. end
  54. end
  55. if raw || update_files
  56. print_raw(test_prefix, scores, update_files)
  57. end
  58. if !raw || update_files
  59. print_friendly(test_prefix, scores, update_files)
  60. end
  61. end
  62. def self.unit_output(cases : Array(DataCase)) : String
  63. _cases = cases.map do |data_case|
  64. ">>> #{data_case.front_matter}
  65. #{data_case.input}<<<
  66. #{data_case.expected_output}"
  67. end
  68. _cases.join
  69. end
  70. def self.pct(value : Int, total : Int, section : String) : String
  71. "#{value.to_s.rjust(4)} " +
  72. "of #{total.to_s.rjust(4)} " +
  73. "- #{(100 * value / total).format('.', "", 1).rjust(5)}% #{section}"
  74. end
  75. def self.print_raw(test_prefix : String, scores : Hash(String, Hash(Int32, CompareLevel)), update_files : Bool) : Nil
  76. sink : IO::FileDescriptor
  77. if update_files
  78. file = get_stats_file(test_prefix)
  79. puts "Updating #{file.path}"
  80. sink = file
  81. else
  82. sink = STDOUT
  83. end
  84. val = JSON.build(" ") do |json|
  85. json.object do
  86. scores.each do |section, map|
  87. json.string section
  88. json.object do
  89. map.each do |example, level|
  90. json.field example, level
  91. end
  92. end
  93. end
  94. end
  95. end
  96. sink.puts val
  97. sink.flush
  98. sink.close
  99. end
  100. def self.print_friendly(test_prefix : String, scores : Hash(String, Hash(Int32, CompareLevel)), update_files : Bool) : Nil
  101. total_valid = 0
  102. total_strict = 0
  103. total_examples = 0
  104. sink : IO::FileDescriptor
  105. if update_files
  106. path = Path[tool_dir, "#{test_prefix}_stats.txt"]
  107. puts "Updating #{path}"
  108. sink = File.open(path, "w+")
  109. else
  110. sink = STDOUT
  111. end
  112. scores.each do |section, map|
  113. total = map.values.size
  114. total_examples += total
  115. section_strict_count = map.values.count { |val| val == CompareLevel::Strict }
  116. section_loose_count = map.values.count { |val| val == CompareLevel::Loose }
  117. section_valid_count = section_strict_count + section_loose_count
  118. total_strict += section_strict_count
  119. total_valid += section_valid_count
  120. sink.puts pct(section_valid_count, total, section)
  121. end
  122. sink.puts pct(total_valid, total_examples, "TOTAL")
  123. sink.puts pct(total_strict, total_valid, "TOTAL Strict")
  124. sink.flush
  125. sink.close
  126. end
  127. def self.main
  128. section : String? = nil
  129. raw = false
  130. update_files = false
  131. verbose = false
  132. verbose_loose = false
  133. flavor : String? = nil
  134. cli = OptionParser.new do |parser|
  135. parser.on("--section SECTION", "Restrict tests to one section") { |val| section = val }
  136. parser.on("--raw", "raw JSON format") { raw = true }
  137. parser.on("--update-files", "Update stats files in #{tool_dir}") { update_files = true }
  138. parser.on("--verbose", "Print details for failures and errors") { verbose = true }
  139. parser.on("--verbose-loose", "Print details for \"loose\" matches") { verbose_loose = true }
  140. parser.on("--flavor FLAVOR", "") do |_flavor|
  141. match = @@configs.find { |c| c.prefix == _flavor }
  142. if match.nil?
  143. STDERR.puts "FLAVOR must be one of: " + @@configs.map(&.prefix).join(", ")
  144. exit 64
  145. end
  146. flavor = _flavor
  147. end
  148. parser.on("-h", "--help", "Show this help message") do
  149. puts parser
  150. exit
  151. end
  152. parser.missing_option do |op|
  153. STDERR.puts "Mission value for option #{op}"
  154. puts parser
  155. exit 64
  156. end
  157. end
  158. cli.parse
  159. if update_files && (raw || verbose || (section != nil))
  160. STDERR.puts "The `update-files` flag must be used by itself"
  161. puts cli
  162. exit 64
  163. end
  164. test_prefix = flavor
  165. unless update_files
  166. test_prefix = @@configs[0].prefix
  167. end
  168. test_prefixes = if test_prefix.nil?
  169. @@configs.map(&.prefix)
  170. else
  171. [test_prefix]
  172. end
  173. test_prefixes.each do |prefix|
  174. process_config(prefix, raw, update_files, verbose,
  175. section, verbose_loose)
  176. end
  177. end
  178. end
  179. Stats.main