1
0

docs.thor 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. class DocsCLI < Thor
  2. include Thor::Actions
  3. def self.to_s
  4. 'Docs'
  5. end
  6. def initialize(*args)
  7. require 'docs'
  8. trap('INT') { puts; exit! } # hide backtrace on ^C
  9. super
  10. end
  11. desc 'list', 'List available documentations'
  12. def list
  13. max_length = 0
  14. Docs.all.
  15. map { |doc| [doc.to_s.demodulize.underscore, doc] }.
  16. each { |pair| max_length = pair.first.length if pair.first.length > max_length }.
  17. each { |pair| puts "#{pair.first.rjust max_length + 1}: #{pair.second.base_url.remove %r{\Ahttps?://}}" }
  18. end
  19. desc 'page <doc> [path] [--verbose] [--debug]', 'Generate a page (no indexing)'
  20. option :verbose, type: :boolean
  21. option :debug, type: :boolean
  22. def page(name, path = '')
  23. unless path.empty? || path.start_with?('/')
  24. return puts 'ERROR: [path] must be an absolute path.'
  25. end
  26. Docs.install_report :store if options[:verbose]
  27. if options[:debug]
  28. GC.disable
  29. Docs.install_report :filter, :request
  30. end
  31. if Docs.generate_page(name, path)
  32. puts 'Done'
  33. else
  34. puts "Failed!#{' (try running with --debug for more information)' unless options[:debug]}"
  35. end
  36. rescue Docs::DocNotFound
  37. invalid_doc(name)
  38. end
  39. desc 'generate <doc> [--verbose] [--debug] [--force] [--package]', 'Generate a documentation'
  40. option :verbose, type: :boolean
  41. option :debug, type: :boolean
  42. option :force, type: :boolean
  43. option :package, type: :boolean
  44. def generate(name)
  45. Docs.install_report :store if options[:verbose]
  46. Docs.install_report :scraper if options[:debug]
  47. Docs.install_report :progress_bar, :doc if $stdout.tty?
  48. unless options[:force]
  49. puts <<-TEXT.strip_heredoc
  50. Note: this command will scrape the documentation from the source.
  51. Some scrapers require a local setup. Others will send thousands of
  52. HTTP requests, potentially slowing down the source site.
  53. Please don't use it unless you are modifying the code.
  54. To download the latest tested version of a documentation, use:
  55. thor docs:download #{name}\n
  56. TEXT
  57. return unless yes? 'Proceed? (y/n)'
  58. end
  59. if Docs.generate(name)
  60. generate_manifest
  61. if options[:package]
  62. require 'unix_utils'
  63. package_doc(Docs.find(name))
  64. end
  65. puts 'Done'
  66. else
  67. puts "Failed!#{' (try running with --debug for more information)' unless options[:debug]}"
  68. end
  69. rescue Docs::DocNotFound
  70. invalid_doc(name)
  71. end
  72. desc 'manifest', 'Create the manifest'
  73. def manifest
  74. generate_manifest
  75. puts 'Done'
  76. end
  77. desc 'download (<doc> <doc>... | --all)', 'Download documentations'
  78. option :all, type: :boolean
  79. def download(*names)
  80. require 'unix_utils'
  81. docs = options[:all] ? Docs.all : find_docs(names)
  82. assert_docs(docs)
  83. download_docs(docs)
  84. generate_manifest
  85. puts 'Done'
  86. rescue Docs::DocNotFound => error
  87. invalid_doc(error.name)
  88. end
  89. desc 'package (<doc> <doc>... | --all)', 'Package documentations'
  90. option :all, type: :boolean
  91. def package(*names)
  92. require 'unix_utils'
  93. docs = options[:all] ? Docs.all : find_docs(names)
  94. assert_docs(docs)
  95. docs.each(&method(:package_doc))
  96. puts 'Done'
  97. rescue Docs::DocNotFound => error
  98. invalid_doc(error.name)
  99. end
  100. desc 'clean', 'Delete documentation packages'
  101. def clean
  102. File.delete(*Dir[File.join Docs.store_path, '*.tar.gz'])
  103. puts 'Done'
  104. end
  105. private
  106. def find_docs(names)
  107. names.map do |name|
  108. Docs.find(name)
  109. end
  110. end
  111. def assert_docs(docs)
  112. if docs.empty?
  113. puts 'ERROR: called with no arguments.'
  114. puts 'Run "thor docs:list" for usage patterns.'
  115. exit
  116. end
  117. end
  118. def invalid_doc(name)
  119. puts %(ERROR: invalid doc "#{name}".)
  120. puts 'Run "thor docs:list" to see the list of docs.'
  121. end
  122. def download_docs(docs)
  123. # Don't allow downloaded files to be created as StringIO
  124. require 'open-uri'
  125. OpenURI::Buffer.send :remove_const, 'StringMax' if OpenURI::Buffer.const_defined?('StringMax')
  126. OpenURI::Buffer.const_set 'StringMax', 0
  127. require 'thread'
  128. length = docs.length
  129. i = 0
  130. (1..4).map do
  131. Thread.new do
  132. while doc = docs.shift
  133. status = begin
  134. download_doc(doc)
  135. 'OK'
  136. rescue => e
  137. "FAILED (#{e.class}: #{e.message})"
  138. end
  139. puts "(#{i += 1}/#{length}) #{doc.name} #{status}"
  140. end
  141. end
  142. end.map(&:join)
  143. end
  144. def download_doc(doc)
  145. target = File.join(Docs.store_path, "#{doc.path}.tar.gz")
  146. open "http://dl.devdocs.io/#{doc.path}.tar.gz" do |file|
  147. FileUtils.mkpath(Docs.store_path)
  148. FileUtils.mv(file, target)
  149. unpackage_doc(doc)
  150. end
  151. end
  152. def unpackage_doc(doc)
  153. path = File.join(Docs.store_path, doc.path)
  154. FileUtils.mkpath(path)
  155. tar = UnixUtils.gunzip("#{path}.tar.gz")
  156. dir = UnixUtils.untar(tar)
  157. FileUtils.rm_rf(path)
  158. FileUtils.mv(dir, path)
  159. FileUtils.rm(tar)
  160. FileUtils.rm("#{path}.tar.gz")
  161. end
  162. def package_doc(doc)
  163. path = File.join Docs.store_path, doc.path
  164. if File.exist?(path)
  165. tar = UnixUtils.tar(path)
  166. gzip = UnixUtils.gzip(tar)
  167. FileUtils.mv(gzip, "#{path}.tar.gz")
  168. FileUtils.rm(tar)
  169. else
  170. puts %(ERROR: can't find "#{doc.name}" documentation files.)
  171. end
  172. end
  173. def generate_manifest
  174. Docs.generate_manifest
  175. end
  176. end