mnenv 0.1.0 → 0.1.2

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 (306) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/PROPOSAL.md +197 -0
  4. data/README.adoc +168 -461
  5. data/Rakefile +7 -4
  6. data/bin/Install-Mnenv.ps1 +145 -0
  7. data/bin/mnenv-installer +72 -0
  8. data/completions/bash +47 -0
  9. data/completions/fish +29 -0
  10. data/completions/powershell.ps1 +94 -0
  11. data/completions/zsh +43 -0
  12. data/lib/mnenv/binary_repository.rb +189 -0
  13. data/lib/mnenv/chocolatey.rb +7 -0
  14. data/lib/mnenv/cli.rb +110 -10
  15. data/lib/mnenv/commands/available_command.rb +169 -0
  16. data/lib/mnenv/commands/chocolatey_command.rb +4 -5
  17. data/lib/mnenv/commands/gemfile_command.rb +4 -5
  18. data/lib/mnenv/commands/homebrew_command.rb +4 -5
  19. data/lib/mnenv/commands/install_command.rb +234 -0
  20. data/lib/mnenv/commands/snap_command.rb +5 -7
  21. data/lib/mnenv/commands/uninstall_command.rb +111 -0
  22. data/lib/mnenv/commands/version_command.rb +167 -0
  23. data/lib/mnenv/commands.rb +9 -4
  24. data/lib/mnenv/gemfile/extractor.rb +10 -3
  25. data/lib/mnenv/gemfile.rb +8 -0
  26. data/lib/mnenv/gemfile_repository.rb +0 -2
  27. data/lib/mnenv/homebrew.rb +7 -0
  28. data/lib/mnenv/installer/base.rb +62 -0
  29. data/lib/mnenv/installer/factory.rb +46 -0
  30. data/lib/mnenv/installer.rb +12 -0
  31. data/lib/mnenv/installers/binary_installer.rb +242 -0
  32. data/lib/mnenv/installers/gemfile_installer.rb +76 -0
  33. data/lib/mnenv/json_formatter.rb +3 -13
  34. data/lib/mnenv/logger.rb +9 -1
  35. data/lib/mnenv/models/binary_version.rb +78 -0
  36. data/lib/mnenv/models/chocolatey_version.rb +7 -0
  37. data/lib/mnenv/models/gemfile_version.rb +19 -5
  38. data/lib/mnenv/models/homebrew_version.rb +7 -0
  39. data/lib/mnenv/models/snap_version.rb +8 -0
  40. data/lib/mnenv/models/version.rb +16 -0
  41. data/lib/mnenv/models.rb +7 -5
  42. data/lib/mnenv/paths.rb +69 -0
  43. data/lib/mnenv/platform_detector.rb +109 -0
  44. data/lib/mnenv/repository.rb +50 -35
  45. data/lib/mnenv/resolver +72 -0
  46. data/lib/mnenv/shells/base.rb +32 -0
  47. data/lib/mnenv/shells/bash.rb +72 -0
  48. data/lib/mnenv/shells/cmd.rb +108 -0
  49. data/lib/mnenv/shells/factory.rb +82 -0
  50. data/lib/mnenv/shells/power_shell.rb +110 -0
  51. data/lib/mnenv/shim_manager.rb +121 -0
  52. data/lib/mnenv/snap.rb +7 -0
  53. data/lib/mnenv/snap_repository.rb +2 -19
  54. data/lib/mnenv/source_registry.rb +69 -0
  55. data/lib/mnenv/sources.rb +46 -0
  56. data/lib/mnenv/version.rb +1 -1
  57. data/lib/mnenv/version_resolver.rb +108 -0
  58. data/lib/mnenv/versions_manager.rb +92 -0
  59. data/lib/mnenv.rb +6 -0
  60. data/mnenv.gemspec +4 -1
  61. data/scripts/cross-source-switching-test.sh +214 -0
  62. data/scripts/integration-test.sh +89 -0
  63. data/scripts/version-switching-test.sh +151 -0
  64. metadata +85 -247
  65. data/data/chocolatey/versions.yaml +0 -812
  66. data/data/gemfile/v1.1.6/Gemfile +0 -4
  67. data/data/gemfile/v1.1.6/Gemfile.lock.archived +0 -232
  68. data/data/gemfile/v1.1.7/Gemfile +0 -4
  69. data/data/gemfile/v1.1.7/Gemfile.lock.archived +0 -235
  70. data/data/gemfile/v1.1.8/Gemfile +0 -4
  71. data/data/gemfile/v1.1.8/Gemfile.lock.archived +0 -238
  72. data/data/gemfile/v1.10.0/Gemfile +0 -5
  73. data/data/gemfile/v1.10.0/Gemfile.lock.archived +0 -930
  74. data/data/gemfile/v1.10.1/Gemfile +0 -5
  75. data/data/gemfile/v1.10.1/Gemfile.lock.archived +0 -929
  76. data/data/gemfile/v1.10.10/Gemfile +0 -5
  77. data/data/gemfile/v1.10.10/Gemfile.lock.archived +0 -973
  78. data/data/gemfile/v1.10.11/Gemfile +0 -5
  79. data/data/gemfile/v1.10.11/Gemfile.lock.archived +0 -975
  80. data/data/gemfile/v1.10.2/Gemfile +0 -5
  81. data/data/gemfile/v1.10.2/Gemfile.lock.archived +0 -939
  82. data/data/gemfile/v1.10.3/Gemfile +0 -5
  83. data/data/gemfile/v1.10.3/Gemfile.lock.archived +0 -946
  84. data/data/gemfile/v1.10.5/Gemfile +0 -5
  85. data/data/gemfile/v1.10.5/Gemfile.lock.archived +0 -958
  86. data/data/gemfile/v1.10.6/Gemfile +0 -5
  87. data/data/gemfile/v1.10.6/Gemfile.lock.archived +0 -969
  88. data/data/gemfile/v1.10.7/Gemfile +0 -5
  89. data/data/gemfile/v1.10.7/Gemfile.lock.archived +0 -969
  90. data/data/gemfile/v1.10.8/Gemfile +0 -5
  91. data/data/gemfile/v1.10.8/Gemfile.lock.archived +0 -968
  92. data/data/gemfile/v1.10.9/Gemfile +0 -5
  93. data/data/gemfile/v1.10.9/Gemfile.lock.archived +0 -972
  94. data/data/gemfile/v1.11.0/Gemfile +0 -5
  95. data/data/gemfile/v1.11.0/Gemfile.lock.archived +0 -971
  96. data/data/gemfile/v1.11.1/Gemfile +0 -5
  97. data/data/gemfile/v1.11.1/Gemfile.lock.archived +0 -975
  98. data/data/gemfile/v1.11.4/Gemfile +0 -5
  99. data/data/gemfile/v1.11.4/Gemfile.lock.archived +0 -1046
  100. data/data/gemfile/v1.11.5/Gemfile +0 -5
  101. data/data/gemfile/v1.11.5/Gemfile.lock.archived +0 -1047
  102. data/data/gemfile/v1.12.10/Gemfile +0 -3
  103. data/data/gemfile/v1.12.10/Gemfile.lock.archived +0 -1073
  104. data/data/gemfile/v1.12.3/Gemfile +0 -3
  105. data/data/gemfile/v1.12.3/Gemfile.lock.archived +0 -1050
  106. data/data/gemfile/v1.12.4/Gemfile +0 -3
  107. data/data/gemfile/v1.12.4/Gemfile.lock.archived +0 -1056
  108. data/data/gemfile/v1.12.5/Gemfile +0 -3
  109. data/data/gemfile/v1.12.5/Gemfile.lock.archived +0 -1054
  110. data/data/gemfile/v1.12.6/Gemfile +0 -3
  111. data/data/gemfile/v1.12.6/Gemfile.lock.archived +0 -1056
  112. data/data/gemfile/v1.12.8/Gemfile +0 -3
  113. data/data/gemfile/v1.12.8/Gemfile.lock.archived +0 -1063
  114. data/data/gemfile/v1.13.0/Gemfile +0 -3
  115. data/data/gemfile/v1.13.0/Gemfile.lock.archived +0 -1074
  116. data/data/gemfile/v1.13.2/Gemfile +0 -3
  117. data/data/gemfile/v1.13.2/Gemfile.lock.archived +0 -899
  118. data/data/gemfile/v1.13.3/Gemfile +0 -3
  119. data/data/gemfile/v1.13.3/Gemfile.lock.archived +0 -938
  120. data/data/gemfile/v1.13.4/Gemfile +0 -3
  121. data/data/gemfile/v1.13.4/Gemfile.lock.archived +0 -938
  122. data/data/gemfile/v1.13.5/Gemfile +0 -3
  123. data/data/gemfile/v1.13.5/Gemfile.lock.archived +0 -944
  124. data/data/gemfile/v1.13.7/Gemfile +0 -3
  125. data/data/gemfile/v1.13.7/Gemfile.lock.archived +0 -944
  126. data/data/gemfile/v1.13.8/Gemfile +0 -3
  127. data/data/gemfile/v1.13.8/Gemfile.lock.archived +0 -944
  128. data/data/gemfile/v1.13.9/Gemfile +0 -3
  129. data/data/gemfile/v1.13.9/Gemfile.lock.archived +0 -956
  130. data/data/gemfile/v1.14.3/Gemfile +0 -3
  131. data/data/gemfile/v1.14.3/Gemfile.lock.archived +0 -950
  132. data/data/gemfile/v1.2.12/Gemfile +0 -3
  133. data/data/gemfile/v1.2.12/Gemfile.lock.archived +0 -283
  134. data/data/gemfile/v1.2.2/Gemfile +0 -4
  135. data/data/gemfile/v1.2.2/Gemfile.lock.archived +0 -224
  136. data/data/gemfile/v1.2.3/Gemfile +0 -4
  137. data/data/gemfile/v1.2.3/Gemfile.lock.archived +0 -231
  138. data/data/gemfile/v1.2.6/Gemfile +0 -4
  139. data/data/gemfile/v1.2.6/Gemfile.lock.archived +0 -239
  140. data/data/gemfile/v1.2.8/Gemfile +0 -4
  141. data/data/gemfile/v1.2.8/Gemfile.lock.archived +0 -233
  142. data/data/gemfile/v1.2.9/Gemfile +0 -4
  143. data/data/gemfile/v1.2.9/Gemfile.lock.archived +0 -245
  144. data/data/gemfile/v1.3.1/Gemfile +0 -3
  145. data/data/gemfile/v1.3.1/Gemfile.lock.archived +0 -296
  146. data/data/gemfile/v1.3.2/Gemfile +0 -3
  147. data/data/gemfile/v1.3.2/Gemfile.lock.archived +0 -296
  148. data/data/gemfile/v1.3.4/Gemfile +0 -3
  149. data/data/gemfile/v1.3.4/Gemfile.lock.archived +0 -284
  150. data/data/gemfile/v1.3.5/Gemfile +0 -3
  151. data/data/gemfile/v1.3.5/Gemfile.lock.archived +0 -284
  152. data/data/gemfile/v1.3.6/Gemfile +0 -3
  153. data/data/gemfile/v1.3.6/Gemfile.lock.archived +0 -286
  154. data/data/gemfile/v1.3.9/Gemfile +0 -3
  155. data/data/gemfile/v1.3.9/Gemfile.lock.archived +0 -334
  156. data/data/gemfile/v1.4.0/Gemfile +0 -3
  157. data/data/gemfile/v1.4.0/Gemfile.lock.archived +0 -330
  158. data/data/gemfile/v1.4.10/Gemfile +0 -4
  159. data/data/gemfile/v1.4.10/Gemfile.lock.archived +0 -461
  160. data/data/gemfile/v1.4.11/Gemfile +0 -4
  161. data/data/gemfile/v1.4.11/Gemfile.lock.archived +0 -452
  162. data/data/gemfile/v1.4.12/Gemfile +0 -4
  163. data/data/gemfile/v1.4.12/Gemfile.lock.archived +0 -452
  164. data/data/gemfile/v1.4.13/Gemfile +0 -4
  165. data/data/gemfile/v1.4.13/Gemfile.lock.archived +0 -455
  166. data/data/gemfile/v1.4.14/Gemfile +0 -4
  167. data/data/gemfile/v1.4.14/Gemfile.lock.archived +0 -456
  168. data/data/gemfile/v1.4.18/Gemfile +0 -3
  169. data/data/gemfile/v1.4.18/Gemfile.lock.archived +0 -486
  170. data/data/gemfile/v1.4.3/Gemfile +0 -3
  171. data/data/gemfile/v1.4.3/Gemfile.lock.archived +0 -339
  172. data/data/gemfile/v1.4.4/Gemfile +0 -3
  173. data/data/gemfile/v1.4.4/Gemfile.lock.archived +0 -339
  174. data/data/gemfile/v1.4.5/Gemfile +0 -3
  175. data/data/gemfile/v1.4.5/Gemfile.lock.archived +0 -348
  176. data/data/gemfile/v1.4.6/Gemfile +0 -3
  177. data/data/gemfile/v1.4.6/Gemfile.lock.archived +0 -357
  178. data/data/gemfile/v1.4.7/Gemfile +0 -3
  179. data/data/gemfile/v1.4.7/Gemfile.lock.archived +0 -391
  180. data/data/gemfile/v1.4.8/Gemfile +0 -3
  181. data/data/gemfile/v1.4.8/Gemfile.lock.archived +0 -445
  182. data/data/gemfile/v1.4.9/Gemfile +0 -3
  183. data/data/gemfile/v1.4.9/Gemfile.lock.archived +0 -448
  184. data/data/gemfile/v1.5.0/Gemfile +0 -3
  185. data/data/gemfile/v1.5.0/Gemfile.lock.archived +0 -478
  186. data/data/gemfile/v1.5.10/Gemfile +0 -3
  187. data/data/gemfile/v1.5.10/Gemfile.lock.archived +0 -668
  188. data/data/gemfile/v1.5.11/Gemfile +0 -3
  189. data/data/gemfile/v1.5.11/Gemfile.lock.archived +0 -668
  190. data/data/gemfile/v1.5.15/Gemfile +0 -3
  191. data/data/gemfile/v1.5.15/Gemfile.lock.archived +0 -686
  192. data/data/gemfile/v1.5.16/Gemfile +0 -3
  193. data/data/gemfile/v1.5.16/Gemfile.lock.archived +0 -684
  194. data/data/gemfile/v1.5.17/Gemfile +0 -3
  195. data/data/gemfile/v1.5.17/Gemfile.lock.archived +0 -684
  196. data/data/gemfile/v1.5.18/Gemfile +0 -5
  197. data/data/gemfile/v1.5.18/Gemfile.lock.archived +0 -691
  198. data/data/gemfile/v1.5.19/Gemfile +0 -5
  199. data/data/gemfile/v1.5.19/Gemfile.lock.archived +0 -703
  200. data/data/gemfile/v1.5.20/Gemfile +0 -5
  201. data/data/gemfile/v1.5.20/Gemfile.lock.archived +0 -703
  202. data/data/gemfile/v1.5.21/Gemfile +0 -5
  203. data/data/gemfile/v1.5.21/Gemfile.lock.archived +0 -707
  204. data/data/gemfile/v1.5.22/Gemfile +0 -5
  205. data/data/gemfile/v1.5.22/Gemfile.lock.archived +0 -707
  206. data/data/gemfile/v1.5.23/Gemfile +0 -5
  207. data/data/gemfile/v1.5.23/Gemfile.lock.archived +0 -711
  208. data/data/gemfile/v1.5.24/Gemfile +0 -5
  209. data/data/gemfile/v1.5.24/Gemfile.lock.archived +0 -711
  210. data/data/gemfile/v1.5.3/Gemfile +0 -3
  211. data/data/gemfile/v1.5.3/Gemfile.lock.archived +0 -651
  212. data/data/gemfile/v1.5.4/Gemfile +0 -3
  213. data/data/gemfile/v1.5.4/Gemfile.lock.archived +0 -657
  214. data/data/gemfile/v1.5.5/Gemfile +0 -3
  215. data/data/gemfile/v1.5.5/Gemfile.lock.archived +0 -657
  216. data/data/gemfile/v1.5.6/Gemfile +0 -3
  217. data/data/gemfile/v1.5.6/Gemfile.lock.archived +0 -657
  218. data/data/gemfile/v1.5.7/Gemfile +0 -3
  219. data/data/gemfile/v1.5.7/Gemfile.lock.archived +0 -657
  220. data/data/gemfile/v1.5.8/Gemfile +0 -3
  221. data/data/gemfile/v1.5.8/Gemfile.lock.archived +0 -655
  222. data/data/gemfile/v1.5.9/Gemfile +0 -3
  223. data/data/gemfile/v1.5.9/Gemfile.lock.archived +0 -656
  224. data/data/gemfile/v1.6.1/Gemfile +0 -5
  225. data/data/gemfile/v1.6.1/Gemfile.lock.archived +0 -721
  226. data/data/gemfile/v1.6.10/Gemfile +0 -5
  227. data/data/gemfile/v1.6.10/Gemfile.lock.archived +0 -744
  228. data/data/gemfile/v1.6.11/Gemfile +0 -5
  229. data/data/gemfile/v1.6.11/Gemfile.lock.archived +0 -744
  230. data/data/gemfile/v1.6.12/Gemfile +0 -5
  231. data/data/gemfile/v1.6.12/Gemfile.lock.archived +0 -745
  232. data/data/gemfile/v1.6.13/Gemfile +0 -5
  233. data/data/gemfile/v1.6.13/Gemfile.lock.archived +0 -745
  234. data/data/gemfile/v1.6.14/Gemfile +0 -5
  235. data/data/gemfile/v1.6.14/Gemfile.lock.archived +0 -754
  236. data/data/gemfile/v1.6.15/Gemfile +0 -5
  237. data/data/gemfile/v1.6.15/Gemfile.lock.archived +0 -757
  238. data/data/gemfile/v1.6.2/Gemfile +0 -5
  239. data/data/gemfile/v1.6.2/Gemfile.lock.archived +0 -718
  240. data/data/gemfile/v1.6.3/Gemfile +0 -5
  241. data/data/gemfile/v1.6.3/Gemfile.lock.archived +0 -728
  242. data/data/gemfile/v1.6.4/Gemfile +0 -5
  243. data/data/gemfile/v1.6.4/Gemfile.lock.archived +0 -730
  244. data/data/gemfile/v1.6.5/Gemfile +0 -5
  245. data/data/gemfile/v1.6.5/Gemfile.lock.archived +0 -733
  246. data/data/gemfile/v1.6.6/Gemfile +0 -5
  247. data/data/gemfile/v1.6.6/Gemfile.lock.archived +0 -733
  248. data/data/gemfile/v1.6.7/Gemfile +0 -5
  249. data/data/gemfile/v1.6.7/Gemfile.lock.archived +0 -733
  250. data/data/gemfile/v1.6.9/Gemfile +0 -5
  251. data/data/gemfile/v1.6.9/Gemfile.lock.archived +0 -744
  252. data/data/gemfile/v1.7.0/Gemfile +0 -5
  253. data/data/gemfile/v1.7.0/Gemfile.lock.archived +0 -750
  254. data/data/gemfile/v1.7.1/Gemfile +0 -5
  255. data/data/gemfile/v1.7.1/Gemfile.lock.archived +0 -750
  256. data/data/gemfile/v1.7.2/Gemfile +0 -5
  257. data/data/gemfile/v1.7.2/Gemfile.lock.archived +0 -747
  258. data/data/gemfile/v1.7.3/Gemfile +0 -5
  259. data/data/gemfile/v1.7.3/Gemfile.lock.archived +0 -755
  260. data/data/gemfile/v1.7.4/Gemfile +0 -5
  261. data/data/gemfile/v1.7.4/Gemfile.lock.archived +0 -756
  262. data/data/gemfile/v1.7.5/Gemfile +0 -5
  263. data/data/gemfile/v1.7.5/Gemfile.lock.archived +0 -759
  264. data/data/gemfile/v1.7.6/Gemfile +0 -5
  265. data/data/gemfile/v1.7.6/Gemfile.lock.archived +0 -768
  266. data/data/gemfile/v1.8.10/Gemfile +0 -5
  267. data/data/gemfile/v1.8.10/Gemfile.lock.archived +0 -792
  268. data/data/gemfile/v1.8.11/Gemfile +0 -5
  269. data/data/gemfile/v1.8.11/Gemfile.lock.archived +0 -862
  270. data/data/gemfile/v1.8.3/Gemfile +0 -5
  271. data/data/gemfile/v1.8.3/Gemfile.lock.archived +0 -773
  272. data/data/gemfile/v1.8.4/Gemfile +0 -5
  273. data/data/gemfile/v1.8.4/Gemfile.lock.archived +0 -768
  274. data/data/gemfile/v1.8.5/Gemfile +0 -5
  275. data/data/gemfile/v1.8.5/Gemfile.lock.archived +0 -768
  276. data/data/gemfile/v1.8.6/Gemfile +0 -5
  277. data/data/gemfile/v1.8.6/Gemfile.lock.archived +0 -777
  278. data/data/gemfile/v1.8.7/Gemfile +0 -5
  279. data/data/gemfile/v1.8.7/Gemfile.lock.archived +0 -777
  280. data/data/gemfile/v1.8.8/Gemfile +0 -5
  281. data/data/gemfile/v1.8.8/Gemfile.lock.archived +0 -778
  282. data/data/gemfile/v1.8.9/Gemfile +0 -5
  283. data/data/gemfile/v1.8.9/Gemfile.lock.archived +0 -775
  284. data/data/gemfile/v1.9.0/Gemfile +0 -5
  285. data/data/gemfile/v1.9.0/Gemfile.lock.archived +0 -871
  286. data/data/gemfile/v1.9.1/Gemfile +0 -5
  287. data/data/gemfile/v1.9.1/Gemfile.lock.archived +0 -906
  288. data/data/gemfile/v1.9.2/Gemfile +0 -5
  289. data/data/gemfile/v1.9.2/Gemfile.lock.archived +0 -898
  290. data/data/gemfile/v1.9.3/Gemfile +0 -5
  291. data/data/gemfile/v1.9.3/Gemfile.lock.archived +0 -898
  292. data/data/gemfile/v1.9.4/Gemfile +0 -5
  293. data/data/gemfile/v1.9.4/Gemfile.lock.archived +0 -901
  294. data/data/gemfile/v1.9.5/Gemfile +0 -5
  295. data/data/gemfile/v1.9.5/Gemfile.lock.archived +0 -903
  296. data/data/gemfile/v1.9.6/Gemfile +0 -5
  297. data/data/gemfile/v1.9.6/Gemfile.lock.archived +0 -900
  298. data/data/gemfile/v1.9.7/Gemfile +0 -5
  299. data/data/gemfile/v1.9.7/Gemfile.lock.archived +0 -922
  300. data/data/gemfile/v1.9.8/Gemfile +0 -5
  301. data/data/gemfile/v1.9.8/Gemfile.lock.archived +0 -933
  302. data/data/gemfile/versions.yaml +0 -751
  303. data/data/homebrew/versions.yaml +0 -567
  304. data/data/snap/github_tags.json +0 -42
  305. data/data/snap/versions.yaml +0 -589
  306. data/snapcraft-list-copied-from-site.md +0 -101
@@ -4,12 +4,14 @@ require 'thor'
4
4
  require 'json'
5
5
 
6
6
  module Mnenv
7
+ autoload :SnapRepository, 'mnenv/snap_repository'
8
+ autoload :JsonFormatter, 'mnenv/json_formatter'
9
+ autoload :Snap, 'mnenv/snap'
10
+
7
11
  class SnapCommand < Thor
8
12
  desc 'list', 'List all Snap versions'
9
13
  method_option :format, type: :string, aliases: '-f', default: 'text'
10
14
  def list
11
- require_relative '../snap_repository'
12
- require_relative '../json_formatter'
13
15
  repo = SnapRepository.new
14
16
  versions = repo.all
15
17
 
@@ -25,8 +27,6 @@ module Mnenv
25
27
 
26
28
  desc 'refresh', 'Fetch and add new Snap versions (incremental)'
27
29
  def refresh
28
- require_relative '../snap/fetcher'
29
- require_relative '../snap_repository'
30
30
  fetcher = Snap::Fetcher.new
31
31
  repo = fetcher.repository
32
32
 
@@ -50,7 +50,6 @@ module Mnenv
50
50
 
51
51
  desc 'revamp', 'Re-fetch all Snap versions'
52
52
  def revamp
53
- require_relative '../snap/fetcher'
54
53
  fetcher = Snap::Fetcher.new
55
54
  versions = fetcher.fetch_and_save
56
55
  puts "Revamped #{versions.size} Snap versions"
@@ -58,14 +57,13 @@ module Mnenv
58
57
 
59
58
  desc 'update VERSION', 'Update a specific Snap version (all arch/channel combinations)'
60
59
  def update(version)
61
- require_relative '../snap/fetcher'
62
60
  fetcher = Snap::Fetcher.new
63
61
  versions = fetcher.fetch_all
64
62
  targets = versions.select { |v| v.version == version }
65
63
 
66
64
  if targets.empty?
67
65
  puts "Snap version #{version} not found in current channel-map"
68
- puts "Note: Historical versions no longer in Snap API cannot be updated"
66
+ puts 'Note: Historical versions no longer in Snap API cannot be updated'
69
67
  exit 1
70
68
  end
71
69
 
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty/prompt'
4
+ require_relative '../installer'
5
+
6
+ module Mnenv
7
+ class UninstallCommand < Thor
8
+ namespace :uninstall
9
+
10
+ class_option :source, type: :string, enum: %w[gemfile binary],
11
+ desc: 'Source type to uninstall (gemfile or binary). If not specified, uninstalls all sources.'
12
+ class_option :force, type: :boolean, aliases: '-f', default: false,
13
+ desc: 'Force uninstallation without confirmation'
14
+
15
+ desc 'VERSION', 'Uninstall a specific Metanorma version'
16
+ method_option :source, type: :string, enum: %w[gemfile binary]
17
+ method_option :force, type: :boolean, aliases: '-f', default: false
18
+ def uninstall(version)
19
+ source = options[:source]
20
+
21
+ if source
22
+ # Uninstall specific source
23
+ uninstall_source(version, source)
24
+ else
25
+ # Uninstall all sources for this version
26
+ uninstall_all_sources(version)
27
+ end
28
+
29
+ # Regenerate shims after uninstallation
30
+ ShimManager.new.regenerate_all
31
+ rescue StandardError => e
32
+ warn "Error: #{e.message}"
33
+ exit 1
34
+ end
35
+
36
+ private
37
+
38
+ def uninstall_source(version, source)
39
+ version_dir = Paths.version_install_dir(version, source)
40
+
41
+ unless Dir.exist?(version_dir)
42
+ puts "Version #{version} (source: #{source}) is not installed."
43
+ return
44
+ end
45
+
46
+ confirm_and_remove(version, source, version_dir)
47
+ end
48
+
49
+ def uninstall_all_sources(version)
50
+ # Find all installed sources for this version
51
+ installed_sources = find_installed_sources(version)
52
+
53
+ if installed_sources.empty?
54
+ puts "Version #{version} is not installed."
55
+ return
56
+ end
57
+
58
+ if installed_sources.length == 1
59
+ # Only one source, uninstall directly
60
+ source = installed_sources.first
61
+ version_dir = Paths.version_install_dir(version, source)
62
+ confirm_and_remove(version, source, version_dir)
63
+ else
64
+ # Multiple sources, ask which to uninstall
65
+ puts "Version #{version} has multiple sources installed:"
66
+ installed_sources.each do |src|
67
+ puts " - #{src}"
68
+ end
69
+ puts ''
70
+
71
+ prompt = TTY::Prompt.new
72
+ choices = installed_sources.map { |s| { name: s, value: s } }
73
+ choices << { name: 'All sources', value: 'all' }
74
+
75
+ selected = prompt.select('Which source(s) to uninstall?', choices)
76
+
77
+ if selected == 'all'
78
+ installed_sources.each do |src|
79
+ version_dir = Paths.version_install_dir(version, src)
80
+ confirm_and_remove(version, src, version_dir, skip_confirm: options[:force])
81
+ end
82
+ else
83
+ version_dir = Paths.version_install_dir(version, selected)
84
+ confirm_and_remove(version, selected, version_dir)
85
+ end
86
+ end
87
+ end
88
+
89
+ def find_installed_sources(version)
90
+ sources = []
91
+ %w[gemfile binary].each do |source|
92
+ version_dir = Paths.version_install_dir(version, source)
93
+ sources << source if Dir.exist?(version_dir)
94
+ end
95
+ sources
96
+ end
97
+
98
+ def confirm_and_remove(version, source, version_dir, skip_confirm: false)
99
+ unless skip_confirm || options[:force]
100
+ prompt = TTY::Prompt.new
101
+ unless prompt.yes?("Uninstall Metanorma #{version} (#{source})? This cannot be undone.")
102
+ puts 'Uninstallation cancelled.'
103
+ return
104
+ end
105
+ end
106
+
107
+ FileUtils.rm_rf(version_dir)
108
+ puts "Uninstalled Metanorma #{version} (source: #{source})"
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty/prompt'
4
+ require 'json'
5
+ require_relative '../installer'
6
+ require_relative '../version_resolver'
7
+
8
+ module Mnenv
9
+ class VersionCommand < Thor
10
+ namespace :version
11
+
12
+ class_option :source, type: :string, enum: %w[gemfile binary],
13
+ desc: 'Source type (gemfile or binary)'
14
+ class_option :interactive, type: :boolean, aliases: '-i', default: false,
15
+ desc: 'Interactive mode for version selection'
16
+
17
+ desc 'use VERSION', 'Set Metanorma version for current shell session'
18
+ method_option :source, type: :string, enum: %w[gemfile binary]
19
+ method_option :interactive, type: :boolean, aliases: '-i', default: false
20
+ def use(version = nil)
21
+ version, source = resolve_version_and_source(version, options[:source], options[:interactive])
22
+
23
+ puts "export METANORMA_VERSION=#{version}"
24
+ puts "export METANORMA_SOURCE=#{source}" if source
25
+ puts "# Run this in your shell, or use: eval \"$(mnenv use #{version}#{" --source #{source}" if source})\""
26
+ end
27
+
28
+ desc 'global VERSION', 'Set default Metanorma version globally'
29
+ method_option :source, type: :string, enum: %w[gemfile binary]
30
+ method_option :interactive, type: :boolean, aliases: '-i', default: false
31
+ def global(version = nil)
32
+ version, source = resolve_version_and_source(version, options[:source], options[:interactive])
33
+ verify_installed!(version, source)
34
+
35
+ File.write(Paths::VERSION_FILE, version)
36
+ File.write(Paths::SOURCE_FILE, source) if source
37
+ puts "Global Metanorma version set to #{version}#{source ? " (source: #{source})" : ''}"
38
+ rescue StandardError => e
39
+ warn "Error: #{e.message}"
40
+ exit 1
41
+ end
42
+
43
+ desc 'local VERSION', 'Set Metanorma version for current directory'
44
+ method_option :source, type: :string, enum: %w[gemfile binary]
45
+ method_option :interactive, type: :boolean, aliases: '-i', default: false
46
+ def local(version = nil)
47
+ version, source = resolve_version_and_source(version, options[:source], options[:interactive])
48
+ verify_installed!(version, source)
49
+
50
+ File.write('.metanorma-version', version)
51
+ File.write('.metanorma-source', source) if source
52
+ puts "Local Metanorma version set to #{version}#{source ? " (source: #{source})" : ''}"
53
+ puts "Created .metanorma-version#{source ? ' and .metanorma-source' : ''}"
54
+ rescue StandardError => e
55
+ warn "Error: #{e.message}"
56
+ exit 1
57
+ end
58
+
59
+ desc 'versions', 'List all installed Metanorma versions'
60
+ method_option :format, type: :string, aliases: '-f', default: 'text',
61
+ desc: 'Output format (text or json)'
62
+ def versions
63
+ installed = list_installed_versions
64
+
65
+ if installed.empty?
66
+ puts 'No versions installed.'
67
+ puts "\nRun: mnenv install --list"
68
+ return
69
+ end
70
+
71
+ current_version, current_source = resolver.resolve
72
+
73
+ case options[:format]
74
+ when 'json'
75
+ output = {
76
+ 'current_version' => current_version,
77
+ 'current_source' => current_source,
78
+ 'installed' => installed.map do |version, sources|
79
+ sources.map do |source|
80
+ {
81
+ 'version' => version,
82
+ 'source' => source,
83
+ 'current' => version == current_version && source == current_source
84
+ }
85
+ end
86
+ end.flatten
87
+ }
88
+ puts JSON.pretty_generate(output)
89
+ else
90
+ puts 'Installed Metanorma versions:'
91
+
92
+ installed.sort.reverse.each do |version, sources|
93
+ sources.sort.each do |source|
94
+ is_current = version == current_version && source == current_source
95
+ marker = is_current ? '* ' : ' '
96
+ puts " #{marker}#{version} (source: #{source})"
97
+ end
98
+ end
99
+
100
+ puts "\nCurrent version: #{current_version || 'none'}"
101
+ puts "Current source: #{current_source || 'none'}"
102
+ puts "\nTo see available versions, run: mnenv install --list"
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Get the shared resolver instance
109
+ def resolver
110
+ @resolver ||= VersionResolver.new
111
+ end
112
+
113
+ # List installed versions with their sources
114
+ # Uses Paths::INSTALLED_DIR with new naming convention: <version>-<source>
115
+ def list_installed_versions
116
+ return {} unless Dir.exist?(Paths::INSTALLED_DIR)
117
+
118
+ versions = Hash.new { |h, k| h[k] = [] }
119
+
120
+ Dir.glob(File.join(Paths::INSTALLED_DIR, '*')).each do |dir|
121
+ next unless File.directory?(dir)
122
+
123
+ dir_name = File.basename(dir)
124
+ version, source = Paths.parse_version_dir(dir_name)
125
+
126
+ # Only include valid version-source directories
127
+ next unless version && source
128
+
129
+ versions[version] << source
130
+ end
131
+
132
+ versions
133
+ end
134
+
135
+ def resolve_version_and_source(version, source, interactive)
136
+ version, source = select_version_interactive if interactive || version.nil?
137
+ source ||= resolver.resolve_source
138
+ [version, source]
139
+ end
140
+
141
+ def select_version_interactive
142
+ prompt = TTY::Prompt.new
143
+ installed = list_installed_versions
144
+
145
+ raise 'No versions installed. Run: mnenv install --list' if installed.empty?
146
+
147
+ choices = installed.flat_map do |version, sources|
148
+ sources.map do |src|
149
+ { name: "#{version} (#{src})", value: [version, src] }
150
+ end
151
+ end
152
+
153
+ prompt.select('Select a version:', choices)
154
+ end
155
+
156
+ def verify_installed!(version, source)
157
+ version_dir = Paths.version_install_dir(version, source)
158
+ return if Dir.exist?(version_dir)
159
+
160
+ raise "Version #{version} (source: #{source}) is not installed. Run: mnenv install #{version} --source #{source}"
161
+ end
162
+
163
+ def installed_versions
164
+ list_installed_versions.keys.sort
165
+ end
166
+ end
167
+ end
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
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'
3
+ module Mnenv
4
+ autoload :GemfileCommand, 'mnenv/commands/gemfile_command'
5
+ autoload :SnapCommand, 'mnenv/commands/snap_command'
6
+ autoload :HomebrewCommand, 'mnenv/commands/homebrew_command'
7
+ autoload :ChocolateyCommand, 'mnenv/commands/chocolatey_command'
8
+ autoload :InstallCommand, 'mnenv/commands/install_command'
9
+ autoload :VersionCommand, 'mnenv/commands/version_command'
10
+ autoload :UninstallCommand, 'mnenv/commands/uninstall_command'
11
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'English'
3
4
  require_relative '../logger'
4
5
  require_relative '../gemfile_repository'
5
6
  require_relative './fetcher'
@@ -36,7 +37,7 @@ module Mnenv
36
37
 
37
38
  def extract_incremental
38
39
  remote_versions = fetcher.fetch_all
39
- existing_versions = repository.all.select { |v| v.exists_locally? }.map(&:version)
40
+ existing_versions = repository.all.select(&:exists_locally?).map(&:version)
40
41
 
41
42
  missing = remote_versions.reject { |v| existing_versions.include?(v.version) }
42
43
 
@@ -74,12 +75,18 @@ module Mnenv
74
75
 
75
76
  cleanup_docker_image(version.version)
76
77
 
78
+ # Update version metadata after successful extraction
79
+ version.gemfile_exists = true
80
+ # Store relative paths (relative to data/gemfile directory)
81
+ version.gemfile_path = "v#{version.version}/Gemfile"
82
+ version.gemfile_lock_path = "v#{version.version}/Gemfile.lock.archived"
83
+
77
84
  repository.save(version)
78
85
  end
79
86
 
80
87
  def pull_docker_image(version_number)
81
88
  system('docker', 'pull', "#{Fetcher::DOCKER_IMAGE}:#{version_number}", out: File::NULL)
82
- raise 'Failed to pull Docker image' unless $?.success?
89
+ raise 'Failed to pull Docker image' unless $CHILD_STATUS.success?
83
90
  end
84
91
 
85
92
  def extract_gemfiles(version)
@@ -89,7 +96,7 @@ module Mnenv
89
96
  cmd = "docker run --rm --entrypoint sh #{Fetcher::DOCKER_IMAGE}:#{version.version} -c '#{script}'"
90
97
  output = `#{cmd}`
91
98
 
92
- raise "Extraction failed for #{version.version}" unless $?.success?
99
+ raise "Extraction failed for #{version.version}" unless $CHILD_STATUS.success?
93
100
 
94
101
  gemfile, gemfile_lock = parse_gemfile_output(output)
95
102
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ module Gemfile
5
+ autoload :Extractor, 'mnenv/gemfile/extractor'
6
+ autoload :Fetcher, 'mnenv/gemfile/fetcher'
7
+ end
8
+ end
@@ -7,7 +7,5 @@ module Mnenv
7
7
  class GemfileRepository < Repository
8
8
  def version_class = GemfileVersion
9
9
  def source_name = :gemfile
10
-
11
- def default_data_dir = File.join(__dir__, '..', '..', 'data', 'gemfile')
12
10
  end
13
11
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ module Homebrew
5
+ autoload :Fetcher, 'mnenv/homebrew/fetcher'
6
+ end
7
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ class Installer
5
+ class InstallationError < StandardError; end
6
+ class DevelopmentToolsMissing < InstallationError; end
7
+
8
+ attr_reader :version, :source
9
+
10
+ def initialize(version, source: nil, target_dir: nil)
11
+ @version = version
12
+ @source = source || default_source
13
+ @target_dir = target_dir || default_target_dir
14
+ end
15
+
16
+ def install
17
+ verify_prerequisites!
18
+ create_install_directory
19
+ perform_installation
20
+ regenerate_shims
21
+ end
22
+
23
+ def installed?
24
+ Dir.exist?(version_dir)
25
+ end
26
+
27
+ private
28
+
29
+ def default_target_dir
30
+ @default_target_dir ||= Paths.version_install_dir(version, source)
31
+ end
32
+
33
+ def version_dir
34
+ @target_dir
35
+ end
36
+
37
+ def verify_prerequisites!
38
+ raise NotImplementedError, "#{self.class} must implement verify_prerequisites!"
39
+ end
40
+
41
+ def perform_installation
42
+ raise NotImplementedError, "#{self.class} must implement perform_installation!"
43
+ end
44
+
45
+ def create_install_directory
46
+ FileUtils.mkdir_p(version_dir)
47
+ end
48
+
49
+ def regenerate_shims
50
+ ShimManager.new.regenerate_all
51
+ end
52
+
53
+ def default_source
54
+ # Try to read from ~/.mnenv/source, else default to gemfile
55
+ if File.exist?(Paths::SOURCE_FILE)
56
+ File.read(Paths::SOURCE_FILE).strip
57
+ else
58
+ 'gemfile' # Default: faster for devs
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../source_registry'
4
+
5
+ module Mnenv
6
+ # Factory for creating installer instances based on source type
7
+ # Uses SourceRegistry for extensibility - new sources just need to register
8
+ class InstallerFactory
9
+ class UnknownSourceError < StandardError; end
10
+
11
+ # Create an installer for the given version and source
12
+ # @param version [String] The version to install
13
+ # @param source [String] The source type (e.g., 'gemfile', 'binary')
14
+ # @param target_dir [String, nil] Optional custom target directory
15
+ # @return [Installer] An installer instance
16
+ # @raise [UnknownSourceError] If source is not registered
17
+ def self.create(version, source:, target_dir: nil)
18
+ installer_class = SourceRegistry.installer(source.to_s)
19
+
20
+ unless installer_class
21
+ available = SourceRegistry.all_names.join(', ')
22
+ raise Installer::InstallationError,
23
+ "Unknown source: #{source}. Available sources: #{available}"
24
+ end
25
+
26
+ installer_class.new(version, source: source, target_dir: target_dir)
27
+ rescue SourceRegistry::UnknownSourceError
28
+ available = SourceRegistry.all_names.join(', ')
29
+ raise Installer::InstallationError,
30
+ "Unknown source: #{source}. Available sources: #{available}"
31
+ end
32
+
33
+ # Check if a source is supported
34
+ # @param source [String] The source type
35
+ # @return [Boolean]
36
+ def self.supported?(source)
37
+ SourceRegistry.registered?(source.to_s)
38
+ end
39
+
40
+ # Get list of supported sources
41
+ # @return [Array<String>]
42
+ def self.supported_sources
43
+ SourceRegistry.all_names
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ autoload :Installer, 'mnenv/installer/base'
5
+ autoload :InstallerFactory, 'mnenv/installer/factory'
6
+ autoload :ShimManager, 'mnenv/shim_manager'
7
+
8
+ module Installers
9
+ autoload :GemfileInstaller, 'mnenv/installers/gemfile_installer'
10
+ autoload :BinaryInstaller, 'mnenv/installers/binary_installer'
11
+ end
12
+ end