mnenv 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +17 -0
  3. data/README.adoc +605 -0
  4. data/Rakefile +8 -0
  5. data/data/chocolatey/versions.yaml +812 -0
  6. data/data/gemfile/v1.1.6/Gemfile +4 -0
  7. data/data/gemfile/v1.1.6/Gemfile.lock.archived +232 -0
  8. data/data/gemfile/v1.1.7/Gemfile +4 -0
  9. data/data/gemfile/v1.1.7/Gemfile.lock.archived +235 -0
  10. data/data/gemfile/v1.1.8/Gemfile +4 -0
  11. data/data/gemfile/v1.1.8/Gemfile.lock.archived +238 -0
  12. data/data/gemfile/v1.10.0/Gemfile +5 -0
  13. data/data/gemfile/v1.10.0/Gemfile.lock.archived +930 -0
  14. data/data/gemfile/v1.10.1/Gemfile +5 -0
  15. data/data/gemfile/v1.10.1/Gemfile.lock.archived +929 -0
  16. data/data/gemfile/v1.10.10/Gemfile +5 -0
  17. data/data/gemfile/v1.10.10/Gemfile.lock.archived +973 -0
  18. data/data/gemfile/v1.10.11/Gemfile +5 -0
  19. data/data/gemfile/v1.10.11/Gemfile.lock.archived +975 -0
  20. data/data/gemfile/v1.10.2/Gemfile +5 -0
  21. data/data/gemfile/v1.10.2/Gemfile.lock.archived +939 -0
  22. data/data/gemfile/v1.10.3/Gemfile +5 -0
  23. data/data/gemfile/v1.10.3/Gemfile.lock.archived +946 -0
  24. data/data/gemfile/v1.10.5/Gemfile +5 -0
  25. data/data/gemfile/v1.10.5/Gemfile.lock.archived +958 -0
  26. data/data/gemfile/v1.10.6/Gemfile +5 -0
  27. data/data/gemfile/v1.10.6/Gemfile.lock.archived +969 -0
  28. data/data/gemfile/v1.10.7/Gemfile +5 -0
  29. data/data/gemfile/v1.10.7/Gemfile.lock.archived +969 -0
  30. data/data/gemfile/v1.10.8/Gemfile +5 -0
  31. data/data/gemfile/v1.10.8/Gemfile.lock.archived +968 -0
  32. data/data/gemfile/v1.10.9/Gemfile +5 -0
  33. data/data/gemfile/v1.10.9/Gemfile.lock.archived +972 -0
  34. data/data/gemfile/v1.11.0/Gemfile +5 -0
  35. data/data/gemfile/v1.11.0/Gemfile.lock.archived +971 -0
  36. data/data/gemfile/v1.11.1/Gemfile +5 -0
  37. data/data/gemfile/v1.11.1/Gemfile.lock.archived +975 -0
  38. data/data/gemfile/v1.11.4/Gemfile +5 -0
  39. data/data/gemfile/v1.11.4/Gemfile.lock.archived +1046 -0
  40. data/data/gemfile/v1.11.5/Gemfile +5 -0
  41. data/data/gemfile/v1.11.5/Gemfile.lock.archived +1047 -0
  42. data/data/gemfile/v1.12.10/Gemfile +3 -0
  43. data/data/gemfile/v1.12.10/Gemfile.lock.archived +1073 -0
  44. data/data/gemfile/v1.12.3/Gemfile +3 -0
  45. data/data/gemfile/v1.12.3/Gemfile.lock.archived +1050 -0
  46. data/data/gemfile/v1.12.4/Gemfile +3 -0
  47. data/data/gemfile/v1.12.4/Gemfile.lock.archived +1056 -0
  48. data/data/gemfile/v1.12.5/Gemfile +3 -0
  49. data/data/gemfile/v1.12.5/Gemfile.lock.archived +1054 -0
  50. data/data/gemfile/v1.12.6/Gemfile +3 -0
  51. data/data/gemfile/v1.12.6/Gemfile.lock.archived +1056 -0
  52. data/data/gemfile/v1.12.8/Gemfile +3 -0
  53. data/data/gemfile/v1.12.8/Gemfile.lock.archived +1063 -0
  54. data/data/gemfile/v1.13.0/Gemfile +3 -0
  55. data/data/gemfile/v1.13.0/Gemfile.lock.archived +1074 -0
  56. data/data/gemfile/v1.13.2/Gemfile +3 -0
  57. data/data/gemfile/v1.13.2/Gemfile.lock.archived +899 -0
  58. data/data/gemfile/v1.13.3/Gemfile +3 -0
  59. data/data/gemfile/v1.13.3/Gemfile.lock.archived +938 -0
  60. data/data/gemfile/v1.13.4/Gemfile +3 -0
  61. data/data/gemfile/v1.13.4/Gemfile.lock.archived +938 -0
  62. data/data/gemfile/v1.13.5/Gemfile +3 -0
  63. data/data/gemfile/v1.13.5/Gemfile.lock.archived +944 -0
  64. data/data/gemfile/v1.13.7/Gemfile +3 -0
  65. data/data/gemfile/v1.13.7/Gemfile.lock.archived +944 -0
  66. data/data/gemfile/v1.13.8/Gemfile +3 -0
  67. data/data/gemfile/v1.13.8/Gemfile.lock.archived +944 -0
  68. data/data/gemfile/v1.13.9/Gemfile +3 -0
  69. data/data/gemfile/v1.13.9/Gemfile.lock.archived +956 -0
  70. data/data/gemfile/v1.14.3/Gemfile +3 -0
  71. data/data/gemfile/v1.14.3/Gemfile.lock.archived +950 -0
  72. data/data/gemfile/v1.2.12/Gemfile +3 -0
  73. data/data/gemfile/v1.2.12/Gemfile.lock.archived +283 -0
  74. data/data/gemfile/v1.2.2/Gemfile +4 -0
  75. data/data/gemfile/v1.2.2/Gemfile.lock.archived +224 -0
  76. data/data/gemfile/v1.2.3/Gemfile +4 -0
  77. data/data/gemfile/v1.2.3/Gemfile.lock.archived +231 -0
  78. data/data/gemfile/v1.2.6/Gemfile +4 -0
  79. data/data/gemfile/v1.2.6/Gemfile.lock.archived +239 -0
  80. data/data/gemfile/v1.2.8/Gemfile +4 -0
  81. data/data/gemfile/v1.2.8/Gemfile.lock.archived +233 -0
  82. data/data/gemfile/v1.2.9/Gemfile +4 -0
  83. data/data/gemfile/v1.2.9/Gemfile.lock.archived +245 -0
  84. data/data/gemfile/v1.3.1/Gemfile +3 -0
  85. data/data/gemfile/v1.3.1/Gemfile.lock.archived +296 -0
  86. data/data/gemfile/v1.3.2/Gemfile +3 -0
  87. data/data/gemfile/v1.3.2/Gemfile.lock.archived +296 -0
  88. data/data/gemfile/v1.3.4/Gemfile +3 -0
  89. data/data/gemfile/v1.3.4/Gemfile.lock.archived +284 -0
  90. data/data/gemfile/v1.3.5/Gemfile +3 -0
  91. data/data/gemfile/v1.3.5/Gemfile.lock.archived +284 -0
  92. data/data/gemfile/v1.3.6/Gemfile +3 -0
  93. data/data/gemfile/v1.3.6/Gemfile.lock.archived +286 -0
  94. data/data/gemfile/v1.3.9/Gemfile +3 -0
  95. data/data/gemfile/v1.3.9/Gemfile.lock.archived +334 -0
  96. data/data/gemfile/v1.4.0/Gemfile +3 -0
  97. data/data/gemfile/v1.4.0/Gemfile.lock.archived +330 -0
  98. data/data/gemfile/v1.4.10/Gemfile +4 -0
  99. data/data/gemfile/v1.4.10/Gemfile.lock.archived +461 -0
  100. data/data/gemfile/v1.4.11/Gemfile +4 -0
  101. data/data/gemfile/v1.4.11/Gemfile.lock.archived +452 -0
  102. data/data/gemfile/v1.4.12/Gemfile +4 -0
  103. data/data/gemfile/v1.4.12/Gemfile.lock.archived +452 -0
  104. data/data/gemfile/v1.4.13/Gemfile +4 -0
  105. data/data/gemfile/v1.4.13/Gemfile.lock.archived +455 -0
  106. data/data/gemfile/v1.4.14/Gemfile +4 -0
  107. data/data/gemfile/v1.4.14/Gemfile.lock.archived +456 -0
  108. data/data/gemfile/v1.4.18/Gemfile +3 -0
  109. data/data/gemfile/v1.4.18/Gemfile.lock.archived +486 -0
  110. data/data/gemfile/v1.4.3/Gemfile +3 -0
  111. data/data/gemfile/v1.4.3/Gemfile.lock.archived +339 -0
  112. data/data/gemfile/v1.4.4/Gemfile +3 -0
  113. data/data/gemfile/v1.4.4/Gemfile.lock.archived +339 -0
  114. data/data/gemfile/v1.4.5/Gemfile +3 -0
  115. data/data/gemfile/v1.4.5/Gemfile.lock.archived +348 -0
  116. data/data/gemfile/v1.4.6/Gemfile +3 -0
  117. data/data/gemfile/v1.4.6/Gemfile.lock.archived +357 -0
  118. data/data/gemfile/v1.4.7/Gemfile +3 -0
  119. data/data/gemfile/v1.4.7/Gemfile.lock.archived +391 -0
  120. data/data/gemfile/v1.4.8/Gemfile +3 -0
  121. data/data/gemfile/v1.4.8/Gemfile.lock.archived +445 -0
  122. data/data/gemfile/v1.4.9/Gemfile +3 -0
  123. data/data/gemfile/v1.4.9/Gemfile.lock.archived +448 -0
  124. data/data/gemfile/v1.5.0/Gemfile +3 -0
  125. data/data/gemfile/v1.5.0/Gemfile.lock.archived +478 -0
  126. data/data/gemfile/v1.5.10/Gemfile +3 -0
  127. data/data/gemfile/v1.5.10/Gemfile.lock.archived +668 -0
  128. data/data/gemfile/v1.5.11/Gemfile +3 -0
  129. data/data/gemfile/v1.5.11/Gemfile.lock.archived +668 -0
  130. data/data/gemfile/v1.5.15/Gemfile +3 -0
  131. data/data/gemfile/v1.5.15/Gemfile.lock.archived +686 -0
  132. data/data/gemfile/v1.5.16/Gemfile +3 -0
  133. data/data/gemfile/v1.5.16/Gemfile.lock.archived +684 -0
  134. data/data/gemfile/v1.5.17/Gemfile +3 -0
  135. data/data/gemfile/v1.5.17/Gemfile.lock.archived +684 -0
  136. data/data/gemfile/v1.5.18/Gemfile +5 -0
  137. data/data/gemfile/v1.5.18/Gemfile.lock.archived +691 -0
  138. data/data/gemfile/v1.5.19/Gemfile +5 -0
  139. data/data/gemfile/v1.5.19/Gemfile.lock.archived +703 -0
  140. data/data/gemfile/v1.5.20/Gemfile +5 -0
  141. data/data/gemfile/v1.5.20/Gemfile.lock.archived +703 -0
  142. data/data/gemfile/v1.5.21/Gemfile +5 -0
  143. data/data/gemfile/v1.5.21/Gemfile.lock.archived +707 -0
  144. data/data/gemfile/v1.5.22/Gemfile +5 -0
  145. data/data/gemfile/v1.5.22/Gemfile.lock.archived +707 -0
  146. data/data/gemfile/v1.5.23/Gemfile +5 -0
  147. data/data/gemfile/v1.5.23/Gemfile.lock.archived +711 -0
  148. data/data/gemfile/v1.5.24/Gemfile +5 -0
  149. data/data/gemfile/v1.5.24/Gemfile.lock.archived +711 -0
  150. data/data/gemfile/v1.5.3/Gemfile +3 -0
  151. data/data/gemfile/v1.5.3/Gemfile.lock.archived +651 -0
  152. data/data/gemfile/v1.5.4/Gemfile +3 -0
  153. data/data/gemfile/v1.5.4/Gemfile.lock.archived +657 -0
  154. data/data/gemfile/v1.5.5/Gemfile +3 -0
  155. data/data/gemfile/v1.5.5/Gemfile.lock.archived +657 -0
  156. data/data/gemfile/v1.5.6/Gemfile +3 -0
  157. data/data/gemfile/v1.5.6/Gemfile.lock.archived +657 -0
  158. data/data/gemfile/v1.5.7/Gemfile +3 -0
  159. data/data/gemfile/v1.5.7/Gemfile.lock.archived +657 -0
  160. data/data/gemfile/v1.5.8/Gemfile +3 -0
  161. data/data/gemfile/v1.5.8/Gemfile.lock.archived +655 -0
  162. data/data/gemfile/v1.5.9/Gemfile +3 -0
  163. data/data/gemfile/v1.5.9/Gemfile.lock.archived +656 -0
  164. data/data/gemfile/v1.6.1/Gemfile +5 -0
  165. data/data/gemfile/v1.6.1/Gemfile.lock.archived +721 -0
  166. data/data/gemfile/v1.6.10/Gemfile +5 -0
  167. data/data/gemfile/v1.6.10/Gemfile.lock.archived +744 -0
  168. data/data/gemfile/v1.6.11/Gemfile +5 -0
  169. data/data/gemfile/v1.6.11/Gemfile.lock.archived +744 -0
  170. data/data/gemfile/v1.6.12/Gemfile +5 -0
  171. data/data/gemfile/v1.6.12/Gemfile.lock.archived +745 -0
  172. data/data/gemfile/v1.6.13/Gemfile +5 -0
  173. data/data/gemfile/v1.6.13/Gemfile.lock.archived +745 -0
  174. data/data/gemfile/v1.6.14/Gemfile +5 -0
  175. data/data/gemfile/v1.6.14/Gemfile.lock.archived +754 -0
  176. data/data/gemfile/v1.6.15/Gemfile +5 -0
  177. data/data/gemfile/v1.6.15/Gemfile.lock.archived +757 -0
  178. data/data/gemfile/v1.6.2/Gemfile +5 -0
  179. data/data/gemfile/v1.6.2/Gemfile.lock.archived +718 -0
  180. data/data/gemfile/v1.6.3/Gemfile +5 -0
  181. data/data/gemfile/v1.6.3/Gemfile.lock.archived +728 -0
  182. data/data/gemfile/v1.6.4/Gemfile +5 -0
  183. data/data/gemfile/v1.6.4/Gemfile.lock.archived +730 -0
  184. data/data/gemfile/v1.6.5/Gemfile +5 -0
  185. data/data/gemfile/v1.6.5/Gemfile.lock.archived +733 -0
  186. data/data/gemfile/v1.6.6/Gemfile +5 -0
  187. data/data/gemfile/v1.6.6/Gemfile.lock.archived +733 -0
  188. data/data/gemfile/v1.6.7/Gemfile +5 -0
  189. data/data/gemfile/v1.6.7/Gemfile.lock.archived +733 -0
  190. data/data/gemfile/v1.6.9/Gemfile +5 -0
  191. data/data/gemfile/v1.6.9/Gemfile.lock.archived +744 -0
  192. data/data/gemfile/v1.7.0/Gemfile +5 -0
  193. data/data/gemfile/v1.7.0/Gemfile.lock.archived +750 -0
  194. data/data/gemfile/v1.7.1/Gemfile +5 -0
  195. data/data/gemfile/v1.7.1/Gemfile.lock.archived +750 -0
  196. data/data/gemfile/v1.7.2/Gemfile +5 -0
  197. data/data/gemfile/v1.7.2/Gemfile.lock.archived +747 -0
  198. data/data/gemfile/v1.7.3/Gemfile +5 -0
  199. data/data/gemfile/v1.7.3/Gemfile.lock.archived +755 -0
  200. data/data/gemfile/v1.7.4/Gemfile +5 -0
  201. data/data/gemfile/v1.7.4/Gemfile.lock.archived +756 -0
  202. data/data/gemfile/v1.7.5/Gemfile +5 -0
  203. data/data/gemfile/v1.7.5/Gemfile.lock.archived +759 -0
  204. data/data/gemfile/v1.7.6/Gemfile +5 -0
  205. data/data/gemfile/v1.7.6/Gemfile.lock.archived +768 -0
  206. data/data/gemfile/v1.8.10/Gemfile +5 -0
  207. data/data/gemfile/v1.8.10/Gemfile.lock.archived +792 -0
  208. data/data/gemfile/v1.8.11/Gemfile +5 -0
  209. data/data/gemfile/v1.8.11/Gemfile.lock.archived +862 -0
  210. data/data/gemfile/v1.8.3/Gemfile +5 -0
  211. data/data/gemfile/v1.8.3/Gemfile.lock.archived +773 -0
  212. data/data/gemfile/v1.8.4/Gemfile +5 -0
  213. data/data/gemfile/v1.8.4/Gemfile.lock.archived +768 -0
  214. data/data/gemfile/v1.8.5/Gemfile +5 -0
  215. data/data/gemfile/v1.8.5/Gemfile.lock.archived +768 -0
  216. data/data/gemfile/v1.8.6/Gemfile +5 -0
  217. data/data/gemfile/v1.8.6/Gemfile.lock.archived +777 -0
  218. data/data/gemfile/v1.8.7/Gemfile +5 -0
  219. data/data/gemfile/v1.8.7/Gemfile.lock.archived +777 -0
  220. data/data/gemfile/v1.8.8/Gemfile +5 -0
  221. data/data/gemfile/v1.8.8/Gemfile.lock.archived +778 -0
  222. data/data/gemfile/v1.8.9/Gemfile +5 -0
  223. data/data/gemfile/v1.8.9/Gemfile.lock.archived +775 -0
  224. data/data/gemfile/v1.9.0/Gemfile +5 -0
  225. data/data/gemfile/v1.9.0/Gemfile.lock.archived +871 -0
  226. data/data/gemfile/v1.9.1/Gemfile +5 -0
  227. data/data/gemfile/v1.9.1/Gemfile.lock.archived +906 -0
  228. data/data/gemfile/v1.9.2/Gemfile +5 -0
  229. data/data/gemfile/v1.9.2/Gemfile.lock.archived +898 -0
  230. data/data/gemfile/v1.9.3/Gemfile +5 -0
  231. data/data/gemfile/v1.9.3/Gemfile.lock.archived +898 -0
  232. data/data/gemfile/v1.9.4/Gemfile +5 -0
  233. data/data/gemfile/v1.9.4/Gemfile.lock.archived +901 -0
  234. data/data/gemfile/v1.9.5/Gemfile +5 -0
  235. data/data/gemfile/v1.9.5/Gemfile.lock.archived +903 -0
  236. data/data/gemfile/v1.9.6/Gemfile +5 -0
  237. data/data/gemfile/v1.9.6/Gemfile.lock.archived +900 -0
  238. data/data/gemfile/v1.9.7/Gemfile +5 -0
  239. data/data/gemfile/v1.9.7/Gemfile.lock.archived +922 -0
  240. data/data/gemfile/v1.9.8/Gemfile +5 -0
  241. data/data/gemfile/v1.9.8/Gemfile.lock.archived +933 -0
  242. data/data/gemfile/versions.yaml +751 -0
  243. data/data/homebrew/versions.yaml +567 -0
  244. data/data/snap/github_tags.json +42 -0
  245. data/data/snap/versions.yaml +589 -0
  246. data/exe/mnenv +10 -0
  247. data/lib/mnenv/chocolatey/fetcher.rb +69 -0
  248. data/lib/mnenv/chocolatey_repository.rb +11 -0
  249. data/lib/mnenv/cli.rb +124 -0
  250. data/lib/mnenv/commands/chocolatey_command.rb +76 -0
  251. data/lib/mnenv/commands/gemfile_command.rb +57 -0
  252. data/lib/mnenv/commands/homebrew_command.rb +76 -0
  253. data/lib/mnenv/commands/snap_command.rb +89 -0
  254. data/lib/mnenv/commands.rb +6 -0
  255. data/lib/mnenv/fetcher.rb +30 -0
  256. data/lib/mnenv/gemfile/extractor.rb +140 -0
  257. data/lib/mnenv/gemfile/fetcher.rb +43 -0
  258. data/lib/mnenv/gemfile_repository.rb +13 -0
  259. data/lib/mnenv/homebrew/fetcher.rb +44 -0
  260. data/lib/mnenv/homebrew_repository.rb +11 -0
  261. data/lib/mnenv/json_formatter.rb +43 -0
  262. data/lib/mnenv/logger.rb +63 -0
  263. data/lib/mnenv/models/chocolatey_version.rb +18 -0
  264. data/lib/mnenv/models/gemfile_version.rb +40 -0
  265. data/lib/mnenv/models/homebrew_version.rb +23 -0
  266. data/lib/mnenv/models/snap_version.rb +22 -0
  267. data/lib/mnenv/models/version.rb +23 -0
  268. data/lib/mnenv/models.rb +9 -0
  269. data/lib/mnenv/repository.rb +105 -0
  270. data/lib/mnenv/snap/fetcher.rb +109 -0
  271. data/lib/mnenv/snap_repository.rb +70 -0
  272. data/lib/mnenv/version.rb +5 -0
  273. data/lib/mnenv.rb +8 -0
  274. data/mnenv.gemspec +43 -0
  275. data/snapcraft-list-copied-from-site.md +101 -0
  276. metadata +421 -0
data/lib/mnenv/cli.rb ADDED
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'json'
5
+ require_relative 'models'
6
+ require_relative 'commands'
7
+ require_relative 'json_formatter'
8
+ require_relative 'gemfile_repository'
9
+ require_relative 'snap_repository'
10
+ require_relative 'homebrew_repository'
11
+ require_relative 'chocolatey_repository'
12
+
13
+ module Mnenv
14
+ class Cli < Thor
15
+ package_name 'mnenv'
16
+
17
+ # Platform subcommands
18
+ desc 'gemfile SUBCOMMAND', 'Manage Ruby (Gemfile) versions'
19
+ subcommand 'gemfile', GemfileCommand
20
+
21
+ desc 'snap SUBCOMMAND', 'Manage Snap versions'
22
+ subcommand 'snap', SnapCommand
23
+
24
+ desc 'homebrew SUBCOMMAND', 'Manage Homebrew versions'
25
+ subcommand 'homebrew', HomebrewCommand
26
+
27
+ desc 'chocolatey SUBCOMMAND', 'Manage Chocolatey versions'
28
+ subcommand 'chocolatey', ChocolateyCommand
29
+
30
+ # General commands
31
+ PLATFORM_REPOSITORIES = {
32
+ 'gemfile' => GemfileRepository,
33
+ 'snap' => SnapRepository,
34
+ 'homebrew' => HomebrewRepository,
35
+ 'chocolatey' => ChocolateyRepository
36
+ }.freeze
37
+
38
+ desc 'info PLATFORM VERSION', 'Get details for a specific version'
39
+ method_option :format, type: :string, aliases: '-f', default: 'json'
40
+ def info(platform, version_number)
41
+ repo_class = PLATFORM_REPOSITORIES[platform]
42
+ unless repo_class
43
+ puts "Error: Unknown platform '#{platform}'. Available: #{PLATFORM_REPOSITORIES.keys.join(', ')}"
44
+ exit 1
45
+ end
46
+
47
+ repo = repo_class.new
48
+ version_obj = repo.find(version_number)
49
+
50
+ unless version_obj
51
+ puts "Error: Version '#{version_number}' not found for platform '#{platform}'"
52
+ exit 1
53
+ end
54
+
55
+ case options[:format]
56
+ when 'json'
57
+ require_relative 'json_formatter'
58
+ require 'json'
59
+ output = JsonFormatter.format_version(version_obj)
60
+ output['platform'] = platform
61
+ puts JSON.pretty_generate(output)
62
+ else
63
+ puts "#{platform.capitalize} version #{version_obj.display_name}:"
64
+ puts " Version: #{version_obj.version}"
65
+ puts " Published: #{version_obj.published_at || 'N/A'}"
66
+ puts " Parsed: #{version_obj.parsed_at || 'N/A'}"
67
+ version_obj.to_h.each do |k, v|
68
+ next if %w[version published_at parsed_at].include?(k)
69
+ puts " #{k}: #{v}"
70
+ end
71
+ end
72
+ end
73
+
74
+ desc 'list PLATFORM', 'List versions for a platform'
75
+ method_option :format, type: :string, aliases: '-f', default: 'json'
76
+ def list(platform)
77
+ repo_class = PLATFORM_REPOSITORIES[platform]
78
+ unless repo_class
79
+ puts "Error: Unknown platform '#{platform}'. Available: #{PLATFORM_REPOSITORIES.keys.join(', ')}"
80
+ exit 1
81
+ end
82
+
83
+ repo = repo_class.new
84
+ versions = repo.all
85
+
86
+ case options[:format]
87
+ when 'json'
88
+ output = JsonFormatter.format_versions(versions)
89
+ output['platform'] = platform
90
+ puts JSON.pretty_generate(output)
91
+ else
92
+ puts "#{platform.capitalize} versions (#{versions.size}):"
93
+ versions.each do |v|
94
+ published = v.published_at ? " (#{v.published_at.strftime('%Y-%m-%d')})" : ''
95
+ puts " #{v.display_name}#{published}"
96
+ end
97
+ end
98
+ end
99
+
100
+ desc 'list-all', 'List all versions from all platforms'
101
+ method_option :format, type: :string, aliases: '-f', default: 'json'
102
+ def list_all
103
+ case options[:format]
104
+ when 'json'
105
+ output = {}
106
+ PLATFORM_REPOSITORIES.each do |name, repo_class|
107
+ repo = repo_class.new
108
+ output[name] = JsonFormatter.format_versions(repo.all)
109
+ end
110
+
111
+ puts JSON.pretty_generate(output)
112
+ else
113
+ PLATFORM_REPOSITORIES.each_key do |platform|
114
+ list(platform)
115
+ end
116
+ end
117
+ end
118
+
119
+ desc 'version', 'Show mnenv version'
120
+ def version
121
+ puts "mnenv #{Mnenv::VERSION}"
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'json'
5
+
6
+ module Mnenv
7
+ class ChocolateyCommand < Thor
8
+ desc 'list', 'List all Chocolatey versions'
9
+ method_option :format, type: :string, aliases: '-f', default: 'text'
10
+ def list
11
+ require_relative '../chocolatey_repository'
12
+ require_relative '../json_formatter'
13
+ repo = ChocolateyRepository.new
14
+ versions = repo.all
15
+
16
+ case options[:format]
17
+ when 'json'
18
+ output = JsonFormatter.format_versions(versions)
19
+ output['platform'] = 'chocolatey'
20
+ puts JSON.pretty_generate(output)
21
+ else
22
+ list_versions_text(versions, 'Chocolatey')
23
+ end
24
+ end
25
+
26
+ desc 'refresh', 'Fetch and add new Chocolatey versions'
27
+ def refresh
28
+ require_relative '../chocolatey/fetcher'
29
+ fetcher = Chocolatey::Fetcher.new
30
+ existing = fetcher.repository.all.map(&:version)
31
+ remote_versions = fetcher.fetch_all
32
+ new_versions = remote_versions.reject { |v| existing.include?(v.version) }
33
+
34
+ if new_versions.empty?
35
+ puts 'No new Chocolatey versions found'
36
+ else
37
+ fetcher.repository.save_all(new_versions)
38
+ puts "Added #{new_versions.size} new Chocolatey versions"
39
+ end
40
+ end
41
+
42
+ desc 'revamp', 'Re-fetch all Chocolatey versions'
43
+ def revamp
44
+ require_relative '../chocolatey/fetcher'
45
+ fetcher = Chocolatey::Fetcher.new
46
+ versions = fetcher.fetch_and_save
47
+ puts "Revamped #{versions.size} Chocolatey versions"
48
+ end
49
+
50
+ desc 'update VERSION', 'Update a specific Chocolatey version'
51
+ def update(version)
52
+ require_relative '../chocolatey/fetcher'
53
+ fetcher = Chocolatey::Fetcher.new
54
+ versions = fetcher.fetch_all
55
+ target = versions.find { |v| v.version == version }
56
+
57
+ if target
58
+ fetcher.repository.save(target)
59
+ puts "Updated Chocolatey version #{version}"
60
+ else
61
+ puts "Chocolatey version #{version} not found"
62
+ exit 1
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def list_versions_text(versions, name)
69
+ puts "#{name} versions (#{versions.size}):"
70
+ versions.each do |v|
71
+ published = v.published_at ? " (#{v.published_at.strftime('%Y-%m-%d')})" : ''
72
+ puts " #{v.display_name}#{published}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'json'
5
+
6
+ module Mnenv
7
+ class GemfileCommand < Thor
8
+ desc 'list', 'List all Gemfile (Ruby) versions'
9
+ method_option :format, type: :string, aliases: '-f', default: 'text'
10
+ def list
11
+ require_relative '../gemfile_repository'
12
+ require_relative '../json_formatter'
13
+ repo = GemfileRepository.new
14
+ versions = repo.all
15
+
16
+ case options[:format]
17
+ when 'json'
18
+ output = JsonFormatter.format_versions(versions)
19
+ output['platform'] = 'ruby'
20
+ puts JSON.pretty_generate(output)
21
+ else
22
+ list_versions_text(versions, 'Ruby (Gemfile)')
23
+ end
24
+ end
25
+
26
+ desc 'refresh', 'Extract new Gemfiles (incremental mode)'
27
+ def refresh
28
+ require_relative '../gemfile/extractor'
29
+ extractor = Gemfile::Extractor.new(mode: :incremental)
30
+ extractor.run
31
+ end
32
+
33
+ desc 'revamp', 'Re-extract all Gemfiles'
34
+ def revamp
35
+ require_relative '../gemfile/extractor'
36
+ extractor = Gemfile::Extractor.new(mode: :revamp)
37
+ extractor.run
38
+ end
39
+
40
+ desc 'update VERSION', 'Re-extract specific version'
41
+ def update(version)
42
+ require_relative '../gemfile/extractor'
43
+ extractor = Gemfile::Extractor.new(mode: :replace, target_version: version)
44
+ extractor.run
45
+ end
46
+
47
+ private
48
+
49
+ def list_versions_text(versions, name)
50
+ puts "#{name} versions (#{versions.size}):"
51
+ versions.each do |v|
52
+ published = v.published_at ? " (#{v.published_at.strftime('%Y-%m-%d')})" : ''
53
+ puts " #{v.display_name}#{published}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'json'
5
+
6
+ module Mnenv
7
+ class HomebrewCommand < Thor
8
+ desc 'list', 'List all Homebrew versions'
9
+ method_option :format, type: :string, aliases: '-f', default: 'text'
10
+ def list
11
+ require_relative '../homebrew_repository'
12
+ require_relative '../json_formatter'
13
+ repo = HomebrewRepository.new
14
+ versions = repo.all
15
+
16
+ case options[:format]
17
+ when 'json'
18
+ output = JsonFormatter.format_versions(versions)
19
+ output['platform'] = 'homebrew'
20
+ puts JSON.pretty_generate(output)
21
+ else
22
+ list_versions_text(versions, 'Homebrew')
23
+ end
24
+ end
25
+
26
+ desc 'refresh', 'Fetch and add new Homebrew versions'
27
+ def refresh
28
+ require_relative '../homebrew/fetcher'
29
+ fetcher = Homebrew::Fetcher.new
30
+ existing = fetcher.repository.all.map(&:version)
31
+ remote_versions = fetcher.fetch_all
32
+ new_versions = remote_versions.reject { |v| existing.include?(v.version) }
33
+
34
+ if new_versions.empty?
35
+ puts 'No new Homebrew versions found'
36
+ else
37
+ fetcher.repository.save_all(new_versions)
38
+ puts "Added #{new_versions.size} new Homebrew versions"
39
+ end
40
+ end
41
+
42
+ desc 'revamp', 'Re-fetch all Homebrew versions'
43
+ def revamp
44
+ require_relative '../homebrew/fetcher'
45
+ fetcher = Homebrew::Fetcher.new
46
+ versions = fetcher.fetch_and_save
47
+ puts "Revamped #{versions.size} Homebrew versions"
48
+ end
49
+
50
+ desc 'update VERSION', 'Update a specific Homebrew version'
51
+ def update(version)
52
+ require_relative '../homebrew/fetcher'
53
+ fetcher = Homebrew::Fetcher.new
54
+ versions = fetcher.fetch_all
55
+ target = versions.find { |v| v.version == version }
56
+
57
+ if target
58
+ fetcher.repository.save(target)
59
+ puts "Updated Homebrew version #{version}"
60
+ else
61
+ puts "Homebrew version #{version} not found"
62
+ exit 1
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def list_versions_text(versions, name)
69
+ puts "#{name} versions (#{versions.size}):"
70
+ versions.each do |v|
71
+ published = v.published_at ? " (#{v.published_at.strftime('%Y-%m-%d')})" : ''
72
+ puts " #{v.display_name}#{published}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'json'
5
+
6
+ module Mnenv
7
+ class SnapCommand < Thor
8
+ desc 'list', 'List all Snap versions'
9
+ method_option :format, type: :string, aliases: '-f', default: 'text'
10
+ def list
11
+ require_relative '../snap_repository'
12
+ require_relative '../json_formatter'
13
+ repo = SnapRepository.new
14
+ versions = repo.all
15
+
16
+ case options[:format]
17
+ when 'json'
18
+ output = JsonFormatter.format_versions(versions)
19
+ output['platform'] = 'snap'
20
+ puts JSON.pretty_generate(output)
21
+ else
22
+ list_versions_text(versions, 'Snap')
23
+ end
24
+ end
25
+
26
+ desc 'refresh', 'Fetch and add new Snap versions (incremental)'
27
+ def refresh
28
+ require_relative '../snap/fetcher'
29
+ require_relative '../snap_repository'
30
+ fetcher = Snap::Fetcher.new
31
+ repo = fetcher.repository
32
+
33
+ # Build map of existing composite keys
34
+ existing_keys = repo.all.map { |v| "#{v.version}-#{v.revision}-#{v.arch}-#{v.channel}" }
35
+ remote_versions = fetcher.fetch_all
36
+
37
+ # Find truly new versions (composite key doesn't exist)
38
+ new_versions = remote_versions.reject do |v|
39
+ key = "#{v.version}-#{v.revision}-#{v.arch}-#{v.channel}"
40
+ existing_keys.include?(key)
41
+ end
42
+
43
+ if new_versions.empty?
44
+ puts 'No new Snap versions found'
45
+ else
46
+ repo.save_all(new_versions)
47
+ puts "Added #{new_versions.size} new Snap versions"
48
+ end
49
+ end
50
+
51
+ desc 'revamp', 'Re-fetch all Snap versions'
52
+ def revamp
53
+ require_relative '../snap/fetcher'
54
+ fetcher = Snap::Fetcher.new
55
+ versions = fetcher.fetch_and_save
56
+ puts "Revamped #{versions.size} Snap versions"
57
+ end
58
+
59
+ desc 'update VERSION', 'Update a specific Snap version (all arch/channel combinations)'
60
+ def update(version)
61
+ require_relative '../snap/fetcher'
62
+ fetcher = Snap::Fetcher.new
63
+ versions = fetcher.fetch_all
64
+ targets = versions.select { |v| v.version == version }
65
+
66
+ if targets.empty?
67
+ puts "Snap version #{version} not found in current channel-map"
68
+ puts "Note: Historical versions no longer in Snap API cannot be updated"
69
+ exit 1
70
+ end
71
+
72
+ fetcher.repository.save_all(targets)
73
+ puts "Updated #{targets.size} Snap entries for version #{version}:"
74
+ targets.each do |v|
75
+ puts " - #{v.arch}/#{v.channel}: revision #{v.revision}"
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def list_versions_text(versions, name)
82
+ puts "#{name} versions (#{versions.size}):"
83
+ versions.each do |v|
84
+ published = v.published_at ? " (#{v.published_at.strftime('%Y-%m-%d')})" : ''
85
+ puts " #{v.display_name}#{published}"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'commands/gemfile_command'
4
+ require_relative 'commands/snap_command'
5
+ require_relative 'commands/homebrew_command'
6
+ require_relative 'commands/chocolatey_command'
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open-uri'
5
+
6
+ module Mnenv
7
+ class Fetcher
8
+ attr_reader :repository
9
+
10
+ def initialize(repository: nil)
11
+ @repository = repository || default_repository
12
+ end
13
+
14
+ def fetch_all
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def fetch_and_save
19
+ versions = fetch_all
20
+ repository.save_all(versions)
21
+ versions
22
+ end
23
+
24
+ protected
25
+
26
+ def fetch_json(uri) = JSON.parse(URI(uri).open.read)
27
+
28
+ def default_repository = raise NotImplementedError
29
+ end
30
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../logger'
4
+ require_relative '../gemfile_repository'
5
+ require_relative './fetcher'
6
+ require_relative '../models/gemfile_version'
7
+ require 'fileutils'
8
+
9
+ module Mnenv
10
+ module Gemfile
11
+ class Extractor
12
+ ExtractionMode = %i[incremental replace revamp].freeze
13
+
14
+ attr_reader :mode, :repository, :fetcher, :target_version
15
+
16
+ def initialize(mode: :incremental, repository: nil, fetcher: nil, target_version: nil)
17
+ @mode = mode || :incremental
18
+ @repository = repository || GemfileRepository.new
19
+ @fetcher = fetcher || Fetcher.new(repository: @repository)
20
+ @target_version = target_version
21
+ end
22
+
23
+ def run
24
+ Logger.header "Starting Gemfile extraction (mode: #{mode})"
25
+
26
+ case mode
27
+ when :incremental then extract_incremental
28
+ when :replace then extract_replace
29
+ when :revamp then extract_revamp
30
+ end
31
+
32
+ Logger.success 'Extraction complete'
33
+ end
34
+
35
+ private
36
+
37
+ def extract_incremental
38
+ remote_versions = fetcher.fetch_all
39
+ existing_versions = repository.all.select { |v| v.exists_locally? }.map(&:version)
40
+
41
+ missing = remote_versions.reject { |v| existing_versions.include?(v.version) }
42
+
43
+ Logger.info "Found #{missing.size} new versions"
44
+
45
+ missing.each { |v| extract_version(v) }
46
+ end
47
+
48
+ def extract_replace
49
+ raise 'target_version required for replace mode' unless target_version
50
+
51
+ remote_version = fetcher.fetch_all.find { |v| v.version == target_version }
52
+ raise "Version #{target_version} not found remotely" unless remote_version
53
+
54
+ local_version = repository.find(target_version)
55
+ remove_gemfiles(local_version) if local_version
56
+
57
+ extract_version(remote_version)
58
+ end
59
+
60
+ def extract_revamp
61
+ remote_versions = fetcher.fetch_all
62
+
63
+ Logger.info "Re-extracting #{remote_versions.size} versions"
64
+
65
+ remote_versions.each { |v| extract_version(v) }
66
+ end
67
+
68
+ def extract_version(version)
69
+ Logger.pulling version.version
70
+ pull_docker_image(version.version)
71
+
72
+ Logger.section "Extracting Gemfiles from #{version.version}"
73
+ extract_gemfiles(version)
74
+
75
+ cleanup_docker_image(version.version)
76
+
77
+ repository.save(version)
78
+ end
79
+
80
+ def pull_docker_image(version_number)
81
+ system('docker', 'pull', "#{Fetcher::DOCKER_IMAGE}:#{version_number}", out: File::NULL)
82
+ raise 'Failed to pull Docker image' unless $?.success?
83
+ end
84
+
85
+ def extract_gemfiles(version)
86
+ FileUtils.mkdir_p(version.directory_path)
87
+
88
+ script = gemfile_extraction_script
89
+ cmd = "docker run --rm --entrypoint sh #{Fetcher::DOCKER_IMAGE}:#{version.version} -c '#{script}'"
90
+ output = `#{cmd}`
91
+
92
+ raise "Extraction failed for #{version.version}" unless $?.success?
93
+
94
+ gemfile, gemfile_lock = parse_gemfile_output(output)
95
+
96
+ File.write(version.gemfile_path_calc, gemfile)
97
+ File.write(version.gemfile_lock_path_calc, gemfile_lock)
98
+
99
+ Logger.extracted version.version
100
+ end
101
+
102
+ def gemfile_extraction_script
103
+ <<~SCRIPT
104
+ for path in /metanorma/Gemfile /setup/Gemfile /Gemfile /root/Gemfile; do
105
+ if [ -f "$path" ]; then
106
+ gemfile_dir=$(dirname "$path")
107
+ echo "GEMFILE_DIR=$gemfile_dir"
108
+ cat "$path"
109
+ echo "===GEMFILE.EOF==="
110
+ cat "$gemfile_dir/Gemfile.lock"
111
+ exit 0
112
+ fi
113
+ done
114
+ echo "ERROR: No Gemfile found"
115
+ exit 1
116
+ SCRIPT
117
+ end
118
+
119
+ def parse_gemfile_output(output)
120
+ parts = output.split('===GEMFILE.EOF===')
121
+ raise 'Failed to parse output' if parts.size < 2
122
+
123
+ gemfile = parts[0].sub(/GEMFILE_DIR=.+\n/, '')
124
+ gemfile_lock = parts[1]
125
+
126
+ [gemfile.strip, gemfile_lock.strip]
127
+ end
128
+
129
+ def remove_gemfiles(version)
130
+ FileUtils.rm_f([version.gemfile_path_calc, version.gemfile_lock_path_calc])
131
+ Logger.info "Removed existing Gemfiles for #{version.version}"
132
+ end
133
+
134
+ def cleanup_docker_image(version_number)
135
+ system('docker', 'rmi', '-f', "#{Fetcher::DOCKER_IMAGE}:#{version_number}",
136
+ out: File::NULL, err: File::NULL)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../fetcher'
4
+ require_relative '../gemfile_repository'
5
+ require_relative '../models/gemfile_version'
6
+
7
+ module Mnenv
8
+ module Gemfile
9
+ class Fetcher < Mnenv::Fetcher
10
+ DOCKER_IMAGE = 'metanorma/metanorma'
11
+ API_BASE = "https://registry.hub.docker.com/v2/repositories/#{DOCKER_IMAGE}/tags".freeze
12
+
13
+ def fetch_all
14
+ uri = URI("#{API_BASE}?page_size=100")
15
+ versions = []
16
+
17
+ loop do
18
+ data = fetch_json(uri)
19
+ data['results'].each do |result|
20
+ name = result['name']
21
+ next unless name.match?(/^\d+\.\d+\.\d+$/)
22
+
23
+ versions << GemfileVersion.new(
24
+ version: name,
25
+ published_at: parse_timestamp(result['tag_last_pushed'])
26
+ )
27
+ end
28
+ break unless data['next']
29
+
30
+ uri = URI(data['next'])
31
+ end
32
+
33
+ versions.sort
34
+ end
35
+
36
+ private
37
+
38
+ def parse_timestamp(value) = value&.then { |v| Time.parse(v) }
39
+
40
+ def default_repository = @default_repository ||= GemfileRepository.new
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'repository'
4
+ require_relative 'models/gemfile_version'
5
+
6
+ module Mnenv
7
+ class GemfileRepository < Repository
8
+ def version_class = GemfileVersion
9
+ def source_name = :gemfile
10
+
11
+ def default_data_dir = File.join(__dir__, '..', '..', 'data', 'gemfile')
12
+ end
13
+ end