mnenv 0.1.0 → 0.1.1

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 (303) 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 +42 -0
  22. data/lib/mnenv/commands/version_command.rb +213 -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 +17 -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/repository.rb +50 -35
  44. data/lib/mnenv/resolver +72 -0
  45. data/lib/mnenv/shells/base.rb +32 -0
  46. data/lib/mnenv/shells/bash.rb +72 -0
  47. data/lib/mnenv/shells/cmd.rb +108 -0
  48. data/lib/mnenv/shells/factory.rb +82 -0
  49. data/lib/mnenv/shells/power_shell.rb +110 -0
  50. data/lib/mnenv/shim_manager.rb +121 -0
  51. data/lib/mnenv/snap.rb +7 -0
  52. data/lib/mnenv/snap_repository.rb +2 -19
  53. data/lib/mnenv/source_registry.rb +69 -0
  54. data/lib/mnenv/version.rb +1 -1
  55. data/lib/mnenv/versions_manager.rb +92 -0
  56. data/lib/mnenv.rb +3 -0
  57. data/mnenv.gemspec +4 -1
  58. data/scripts/cross-source-switching-test.sh +214 -0
  59. data/scripts/integration-test.sh +89 -0
  60. data/scripts/version-switching-test.sh +151 -0
  61. metadata +82 -247
  62. data/data/chocolatey/versions.yaml +0 -812
  63. data/data/gemfile/v1.1.6/Gemfile +0 -4
  64. data/data/gemfile/v1.1.6/Gemfile.lock.archived +0 -232
  65. data/data/gemfile/v1.1.7/Gemfile +0 -4
  66. data/data/gemfile/v1.1.7/Gemfile.lock.archived +0 -235
  67. data/data/gemfile/v1.1.8/Gemfile +0 -4
  68. data/data/gemfile/v1.1.8/Gemfile.lock.archived +0 -238
  69. data/data/gemfile/v1.10.0/Gemfile +0 -5
  70. data/data/gemfile/v1.10.0/Gemfile.lock.archived +0 -930
  71. data/data/gemfile/v1.10.1/Gemfile +0 -5
  72. data/data/gemfile/v1.10.1/Gemfile.lock.archived +0 -929
  73. data/data/gemfile/v1.10.10/Gemfile +0 -5
  74. data/data/gemfile/v1.10.10/Gemfile.lock.archived +0 -973
  75. data/data/gemfile/v1.10.11/Gemfile +0 -5
  76. data/data/gemfile/v1.10.11/Gemfile.lock.archived +0 -975
  77. data/data/gemfile/v1.10.2/Gemfile +0 -5
  78. data/data/gemfile/v1.10.2/Gemfile.lock.archived +0 -939
  79. data/data/gemfile/v1.10.3/Gemfile +0 -5
  80. data/data/gemfile/v1.10.3/Gemfile.lock.archived +0 -946
  81. data/data/gemfile/v1.10.5/Gemfile +0 -5
  82. data/data/gemfile/v1.10.5/Gemfile.lock.archived +0 -958
  83. data/data/gemfile/v1.10.6/Gemfile +0 -5
  84. data/data/gemfile/v1.10.6/Gemfile.lock.archived +0 -969
  85. data/data/gemfile/v1.10.7/Gemfile +0 -5
  86. data/data/gemfile/v1.10.7/Gemfile.lock.archived +0 -969
  87. data/data/gemfile/v1.10.8/Gemfile +0 -5
  88. data/data/gemfile/v1.10.8/Gemfile.lock.archived +0 -968
  89. data/data/gemfile/v1.10.9/Gemfile +0 -5
  90. data/data/gemfile/v1.10.9/Gemfile.lock.archived +0 -972
  91. data/data/gemfile/v1.11.0/Gemfile +0 -5
  92. data/data/gemfile/v1.11.0/Gemfile.lock.archived +0 -971
  93. data/data/gemfile/v1.11.1/Gemfile +0 -5
  94. data/data/gemfile/v1.11.1/Gemfile.lock.archived +0 -975
  95. data/data/gemfile/v1.11.4/Gemfile +0 -5
  96. data/data/gemfile/v1.11.4/Gemfile.lock.archived +0 -1046
  97. data/data/gemfile/v1.11.5/Gemfile +0 -5
  98. data/data/gemfile/v1.11.5/Gemfile.lock.archived +0 -1047
  99. data/data/gemfile/v1.12.10/Gemfile +0 -3
  100. data/data/gemfile/v1.12.10/Gemfile.lock.archived +0 -1073
  101. data/data/gemfile/v1.12.3/Gemfile +0 -3
  102. data/data/gemfile/v1.12.3/Gemfile.lock.archived +0 -1050
  103. data/data/gemfile/v1.12.4/Gemfile +0 -3
  104. data/data/gemfile/v1.12.4/Gemfile.lock.archived +0 -1056
  105. data/data/gemfile/v1.12.5/Gemfile +0 -3
  106. data/data/gemfile/v1.12.5/Gemfile.lock.archived +0 -1054
  107. data/data/gemfile/v1.12.6/Gemfile +0 -3
  108. data/data/gemfile/v1.12.6/Gemfile.lock.archived +0 -1056
  109. data/data/gemfile/v1.12.8/Gemfile +0 -3
  110. data/data/gemfile/v1.12.8/Gemfile.lock.archived +0 -1063
  111. data/data/gemfile/v1.13.0/Gemfile +0 -3
  112. data/data/gemfile/v1.13.0/Gemfile.lock.archived +0 -1074
  113. data/data/gemfile/v1.13.2/Gemfile +0 -3
  114. data/data/gemfile/v1.13.2/Gemfile.lock.archived +0 -899
  115. data/data/gemfile/v1.13.3/Gemfile +0 -3
  116. data/data/gemfile/v1.13.3/Gemfile.lock.archived +0 -938
  117. data/data/gemfile/v1.13.4/Gemfile +0 -3
  118. data/data/gemfile/v1.13.4/Gemfile.lock.archived +0 -938
  119. data/data/gemfile/v1.13.5/Gemfile +0 -3
  120. data/data/gemfile/v1.13.5/Gemfile.lock.archived +0 -944
  121. data/data/gemfile/v1.13.7/Gemfile +0 -3
  122. data/data/gemfile/v1.13.7/Gemfile.lock.archived +0 -944
  123. data/data/gemfile/v1.13.8/Gemfile +0 -3
  124. data/data/gemfile/v1.13.8/Gemfile.lock.archived +0 -944
  125. data/data/gemfile/v1.13.9/Gemfile +0 -3
  126. data/data/gemfile/v1.13.9/Gemfile.lock.archived +0 -956
  127. data/data/gemfile/v1.14.3/Gemfile +0 -3
  128. data/data/gemfile/v1.14.3/Gemfile.lock.archived +0 -950
  129. data/data/gemfile/v1.2.12/Gemfile +0 -3
  130. data/data/gemfile/v1.2.12/Gemfile.lock.archived +0 -283
  131. data/data/gemfile/v1.2.2/Gemfile +0 -4
  132. data/data/gemfile/v1.2.2/Gemfile.lock.archived +0 -224
  133. data/data/gemfile/v1.2.3/Gemfile +0 -4
  134. data/data/gemfile/v1.2.3/Gemfile.lock.archived +0 -231
  135. data/data/gemfile/v1.2.6/Gemfile +0 -4
  136. data/data/gemfile/v1.2.6/Gemfile.lock.archived +0 -239
  137. data/data/gemfile/v1.2.8/Gemfile +0 -4
  138. data/data/gemfile/v1.2.8/Gemfile.lock.archived +0 -233
  139. data/data/gemfile/v1.2.9/Gemfile +0 -4
  140. data/data/gemfile/v1.2.9/Gemfile.lock.archived +0 -245
  141. data/data/gemfile/v1.3.1/Gemfile +0 -3
  142. data/data/gemfile/v1.3.1/Gemfile.lock.archived +0 -296
  143. data/data/gemfile/v1.3.2/Gemfile +0 -3
  144. data/data/gemfile/v1.3.2/Gemfile.lock.archived +0 -296
  145. data/data/gemfile/v1.3.4/Gemfile +0 -3
  146. data/data/gemfile/v1.3.4/Gemfile.lock.archived +0 -284
  147. data/data/gemfile/v1.3.5/Gemfile +0 -3
  148. data/data/gemfile/v1.3.5/Gemfile.lock.archived +0 -284
  149. data/data/gemfile/v1.3.6/Gemfile +0 -3
  150. data/data/gemfile/v1.3.6/Gemfile.lock.archived +0 -286
  151. data/data/gemfile/v1.3.9/Gemfile +0 -3
  152. data/data/gemfile/v1.3.9/Gemfile.lock.archived +0 -334
  153. data/data/gemfile/v1.4.0/Gemfile +0 -3
  154. data/data/gemfile/v1.4.0/Gemfile.lock.archived +0 -330
  155. data/data/gemfile/v1.4.10/Gemfile +0 -4
  156. data/data/gemfile/v1.4.10/Gemfile.lock.archived +0 -461
  157. data/data/gemfile/v1.4.11/Gemfile +0 -4
  158. data/data/gemfile/v1.4.11/Gemfile.lock.archived +0 -452
  159. data/data/gemfile/v1.4.12/Gemfile +0 -4
  160. data/data/gemfile/v1.4.12/Gemfile.lock.archived +0 -452
  161. data/data/gemfile/v1.4.13/Gemfile +0 -4
  162. data/data/gemfile/v1.4.13/Gemfile.lock.archived +0 -455
  163. data/data/gemfile/v1.4.14/Gemfile +0 -4
  164. data/data/gemfile/v1.4.14/Gemfile.lock.archived +0 -456
  165. data/data/gemfile/v1.4.18/Gemfile +0 -3
  166. data/data/gemfile/v1.4.18/Gemfile.lock.archived +0 -486
  167. data/data/gemfile/v1.4.3/Gemfile +0 -3
  168. data/data/gemfile/v1.4.3/Gemfile.lock.archived +0 -339
  169. data/data/gemfile/v1.4.4/Gemfile +0 -3
  170. data/data/gemfile/v1.4.4/Gemfile.lock.archived +0 -339
  171. data/data/gemfile/v1.4.5/Gemfile +0 -3
  172. data/data/gemfile/v1.4.5/Gemfile.lock.archived +0 -348
  173. data/data/gemfile/v1.4.6/Gemfile +0 -3
  174. data/data/gemfile/v1.4.6/Gemfile.lock.archived +0 -357
  175. data/data/gemfile/v1.4.7/Gemfile +0 -3
  176. data/data/gemfile/v1.4.7/Gemfile.lock.archived +0 -391
  177. data/data/gemfile/v1.4.8/Gemfile +0 -3
  178. data/data/gemfile/v1.4.8/Gemfile.lock.archived +0 -445
  179. data/data/gemfile/v1.4.9/Gemfile +0 -3
  180. data/data/gemfile/v1.4.9/Gemfile.lock.archived +0 -448
  181. data/data/gemfile/v1.5.0/Gemfile +0 -3
  182. data/data/gemfile/v1.5.0/Gemfile.lock.archived +0 -478
  183. data/data/gemfile/v1.5.10/Gemfile +0 -3
  184. data/data/gemfile/v1.5.10/Gemfile.lock.archived +0 -668
  185. data/data/gemfile/v1.5.11/Gemfile +0 -3
  186. data/data/gemfile/v1.5.11/Gemfile.lock.archived +0 -668
  187. data/data/gemfile/v1.5.15/Gemfile +0 -3
  188. data/data/gemfile/v1.5.15/Gemfile.lock.archived +0 -686
  189. data/data/gemfile/v1.5.16/Gemfile +0 -3
  190. data/data/gemfile/v1.5.16/Gemfile.lock.archived +0 -684
  191. data/data/gemfile/v1.5.17/Gemfile +0 -3
  192. data/data/gemfile/v1.5.17/Gemfile.lock.archived +0 -684
  193. data/data/gemfile/v1.5.18/Gemfile +0 -5
  194. data/data/gemfile/v1.5.18/Gemfile.lock.archived +0 -691
  195. data/data/gemfile/v1.5.19/Gemfile +0 -5
  196. data/data/gemfile/v1.5.19/Gemfile.lock.archived +0 -703
  197. data/data/gemfile/v1.5.20/Gemfile +0 -5
  198. data/data/gemfile/v1.5.20/Gemfile.lock.archived +0 -703
  199. data/data/gemfile/v1.5.21/Gemfile +0 -5
  200. data/data/gemfile/v1.5.21/Gemfile.lock.archived +0 -707
  201. data/data/gemfile/v1.5.22/Gemfile +0 -5
  202. data/data/gemfile/v1.5.22/Gemfile.lock.archived +0 -707
  203. data/data/gemfile/v1.5.23/Gemfile +0 -5
  204. data/data/gemfile/v1.5.23/Gemfile.lock.archived +0 -711
  205. data/data/gemfile/v1.5.24/Gemfile +0 -5
  206. data/data/gemfile/v1.5.24/Gemfile.lock.archived +0 -711
  207. data/data/gemfile/v1.5.3/Gemfile +0 -3
  208. data/data/gemfile/v1.5.3/Gemfile.lock.archived +0 -651
  209. data/data/gemfile/v1.5.4/Gemfile +0 -3
  210. data/data/gemfile/v1.5.4/Gemfile.lock.archived +0 -657
  211. data/data/gemfile/v1.5.5/Gemfile +0 -3
  212. data/data/gemfile/v1.5.5/Gemfile.lock.archived +0 -657
  213. data/data/gemfile/v1.5.6/Gemfile +0 -3
  214. data/data/gemfile/v1.5.6/Gemfile.lock.archived +0 -657
  215. data/data/gemfile/v1.5.7/Gemfile +0 -3
  216. data/data/gemfile/v1.5.7/Gemfile.lock.archived +0 -657
  217. data/data/gemfile/v1.5.8/Gemfile +0 -3
  218. data/data/gemfile/v1.5.8/Gemfile.lock.archived +0 -655
  219. data/data/gemfile/v1.5.9/Gemfile +0 -3
  220. data/data/gemfile/v1.5.9/Gemfile.lock.archived +0 -656
  221. data/data/gemfile/v1.6.1/Gemfile +0 -5
  222. data/data/gemfile/v1.6.1/Gemfile.lock.archived +0 -721
  223. data/data/gemfile/v1.6.10/Gemfile +0 -5
  224. data/data/gemfile/v1.6.10/Gemfile.lock.archived +0 -744
  225. data/data/gemfile/v1.6.11/Gemfile +0 -5
  226. data/data/gemfile/v1.6.11/Gemfile.lock.archived +0 -744
  227. data/data/gemfile/v1.6.12/Gemfile +0 -5
  228. data/data/gemfile/v1.6.12/Gemfile.lock.archived +0 -745
  229. data/data/gemfile/v1.6.13/Gemfile +0 -5
  230. data/data/gemfile/v1.6.13/Gemfile.lock.archived +0 -745
  231. data/data/gemfile/v1.6.14/Gemfile +0 -5
  232. data/data/gemfile/v1.6.14/Gemfile.lock.archived +0 -754
  233. data/data/gemfile/v1.6.15/Gemfile +0 -5
  234. data/data/gemfile/v1.6.15/Gemfile.lock.archived +0 -757
  235. data/data/gemfile/v1.6.2/Gemfile +0 -5
  236. data/data/gemfile/v1.6.2/Gemfile.lock.archived +0 -718
  237. data/data/gemfile/v1.6.3/Gemfile +0 -5
  238. data/data/gemfile/v1.6.3/Gemfile.lock.archived +0 -728
  239. data/data/gemfile/v1.6.4/Gemfile +0 -5
  240. data/data/gemfile/v1.6.4/Gemfile.lock.archived +0 -730
  241. data/data/gemfile/v1.6.5/Gemfile +0 -5
  242. data/data/gemfile/v1.6.5/Gemfile.lock.archived +0 -733
  243. data/data/gemfile/v1.6.6/Gemfile +0 -5
  244. data/data/gemfile/v1.6.6/Gemfile.lock.archived +0 -733
  245. data/data/gemfile/v1.6.7/Gemfile +0 -5
  246. data/data/gemfile/v1.6.7/Gemfile.lock.archived +0 -733
  247. data/data/gemfile/v1.6.9/Gemfile +0 -5
  248. data/data/gemfile/v1.6.9/Gemfile.lock.archived +0 -744
  249. data/data/gemfile/v1.7.0/Gemfile +0 -5
  250. data/data/gemfile/v1.7.0/Gemfile.lock.archived +0 -750
  251. data/data/gemfile/v1.7.1/Gemfile +0 -5
  252. data/data/gemfile/v1.7.1/Gemfile.lock.archived +0 -750
  253. data/data/gemfile/v1.7.2/Gemfile +0 -5
  254. data/data/gemfile/v1.7.2/Gemfile.lock.archived +0 -747
  255. data/data/gemfile/v1.7.3/Gemfile +0 -5
  256. data/data/gemfile/v1.7.3/Gemfile.lock.archived +0 -755
  257. data/data/gemfile/v1.7.4/Gemfile +0 -5
  258. data/data/gemfile/v1.7.4/Gemfile.lock.archived +0 -756
  259. data/data/gemfile/v1.7.5/Gemfile +0 -5
  260. data/data/gemfile/v1.7.5/Gemfile.lock.archived +0 -759
  261. data/data/gemfile/v1.7.6/Gemfile +0 -5
  262. data/data/gemfile/v1.7.6/Gemfile.lock.archived +0 -768
  263. data/data/gemfile/v1.8.10/Gemfile +0 -5
  264. data/data/gemfile/v1.8.10/Gemfile.lock.archived +0 -792
  265. data/data/gemfile/v1.8.11/Gemfile +0 -5
  266. data/data/gemfile/v1.8.11/Gemfile.lock.archived +0 -862
  267. data/data/gemfile/v1.8.3/Gemfile +0 -5
  268. data/data/gemfile/v1.8.3/Gemfile.lock.archived +0 -773
  269. data/data/gemfile/v1.8.4/Gemfile +0 -5
  270. data/data/gemfile/v1.8.4/Gemfile.lock.archived +0 -768
  271. data/data/gemfile/v1.8.5/Gemfile +0 -5
  272. data/data/gemfile/v1.8.5/Gemfile.lock.archived +0 -768
  273. data/data/gemfile/v1.8.6/Gemfile +0 -5
  274. data/data/gemfile/v1.8.6/Gemfile.lock.archived +0 -777
  275. data/data/gemfile/v1.8.7/Gemfile +0 -5
  276. data/data/gemfile/v1.8.7/Gemfile.lock.archived +0 -777
  277. data/data/gemfile/v1.8.8/Gemfile +0 -5
  278. data/data/gemfile/v1.8.8/Gemfile.lock.archived +0 -778
  279. data/data/gemfile/v1.8.9/Gemfile +0 -5
  280. data/data/gemfile/v1.8.9/Gemfile.lock.archived +0 -775
  281. data/data/gemfile/v1.9.0/Gemfile +0 -5
  282. data/data/gemfile/v1.9.0/Gemfile.lock.archived +0 -871
  283. data/data/gemfile/v1.9.1/Gemfile +0 -5
  284. data/data/gemfile/v1.9.1/Gemfile.lock.archived +0 -906
  285. data/data/gemfile/v1.9.2/Gemfile +0 -5
  286. data/data/gemfile/v1.9.2/Gemfile.lock.archived +0 -898
  287. data/data/gemfile/v1.9.3/Gemfile +0 -5
  288. data/data/gemfile/v1.9.3/Gemfile.lock.archived +0 -898
  289. data/data/gemfile/v1.9.4/Gemfile +0 -5
  290. data/data/gemfile/v1.9.4/Gemfile.lock.archived +0 -901
  291. data/data/gemfile/v1.9.5/Gemfile +0 -5
  292. data/data/gemfile/v1.9.5/Gemfile.lock.archived +0 -903
  293. data/data/gemfile/v1.9.6/Gemfile +0 -5
  294. data/data/gemfile/v1.9.6/Gemfile.lock.archived +0 -900
  295. data/data/gemfile/v1.9.7/Gemfile +0 -5
  296. data/data/gemfile/v1.9.7/Gemfile.lock.archived +0 -922
  297. data/data/gemfile/v1.9.8/Gemfile +0 -5
  298. data/data/gemfile/v1.9.8/Gemfile.lock.archived +0 -933
  299. data/data/gemfile/versions.yaml +0 -751
  300. data/data/homebrew/versions.yaml +0 -567
  301. data/data/snap/github_tags.json +0 -42
  302. data/data/snap/versions.yaml +0 -589
  303. data/snapcraft-list-copied-from-site.md +0 -101
data/lib/mnenv/cli.rb CHANGED
@@ -2,18 +2,49 @@
2
2
 
3
3
  require 'thor'
4
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
5
 
13
6
  module Mnenv
7
+ # Autoload constants for lazy loading
8
+ autoload :VERSION, 'mnenv/version'
9
+ autoload :GemfileRepository, 'mnenv/gemfile_repository'
10
+ autoload :SnapRepository, 'mnenv/snap_repository'
11
+ autoload :HomebrewRepository, 'mnenv/homebrew_repository'
12
+ autoload :ChocolateyRepository, 'mnenv/chocolatey_repository'
13
+ autoload :GemfileCommand, 'mnenv/commands/gemfile_command'
14
+ autoload :SnapCommand, 'mnenv/commands/snap_command'
15
+ autoload :HomebrewCommand, 'mnenv/commands/homebrew_command'
16
+ autoload :ChocolateyCommand, 'mnenv/commands/chocolatey_command'
17
+ autoload :InstallCommand, 'mnenv/commands/install_command'
18
+ autoload :VersionCommand, 'mnenv/commands/version_command'
19
+ autoload :UninstallCommand, 'mnenv/commands/uninstall_command'
20
+ autoload :JsonFormatter, 'mnenv/json_formatter'
21
+ autoload :ShimManager, 'mnenv/shim_manager'
22
+ autoload :BinaryRepository, 'mnenv/binary_repository'
23
+
14
24
  class Cli < Thor
15
25
  package_name 'mnenv'
16
26
 
27
+ # Global options
28
+ class_option :verbose, type: :boolean, default: false, desc: 'Enable verbose output'
29
+ class_option :'data-dir', type: :string, desc: 'Path to data directory (default: uses ~/.mnenv/versions/data)'
30
+
31
+ # Track verbose mode and data_dir for use in other classes
32
+ class << self
33
+ def verbose?
34
+ @verbose ||= false
35
+ end
36
+
37
+ attr_accessor :data_dir
38
+
39
+ attr_writer :verbose
40
+ end
41
+
42
+ def initialize(*args)
43
+ super
44
+ self.class.verbose = options[:verbose]
45
+ self.class.data_dir = options[:'data-dir']
46
+ end
47
+
17
48
  # Platform subcommands
18
49
  desc 'gemfile SUBCOMMAND', 'Manage Ruby (Gemfile) versions'
19
50
  subcommand 'gemfile', GemfileCommand
@@ -27,12 +58,75 @@ module Mnenv
27
58
  desc 'chocolatey SUBCOMMAND', 'Manage Chocolatey versions'
28
59
  subcommand 'chocolatey', ChocolateyCommand
29
60
 
61
+ # Interactive installation commands
62
+ desc 'install [VERSION]', 'Install a Metanorma version'
63
+ method_option :source, type: :string, enum: %w[gemfile binary], default: 'gemfile',
64
+ desc: 'Installation source (gemfile or binary)'
65
+ method_option :interactive, type: :boolean, aliases: '-i', default: false,
66
+ desc: 'Interactive mode for version selection'
67
+ method_option :list, type: :boolean, aliases: '-l', default: false,
68
+ desc: 'List all available Metanorma versions'
69
+ def install(version = nil)
70
+ cmd = InstallCommand.new
71
+ cmd.options = Thor::CoreExt::HashWithIndifferentAccess.new(options.merge(version: version))
72
+ cmd.install(version)
73
+ end
74
+
75
+ desc 'use [VERSION]', 'Set Metanorma version for current shell session'
76
+ method_option :source, type: :string, enum: %w[gemfile binary],
77
+ desc: 'Source type (gemfile or binary)'
78
+ method_option :interactive, type: :boolean, aliases: '-i', default: false,
79
+ desc: 'Interactive mode for version selection'
80
+ def use(version = nil)
81
+ cmd = VersionCommand.new
82
+ cmd.options = options
83
+ cmd.use(version)
84
+ end
85
+
86
+ desc 'global [VERSION]', 'Set default Metanorma version globally'
87
+ method_option :source, type: :string, enum: %w[gemfile binary],
88
+ desc: 'Source type (gemfile or binary)'
89
+ method_option :interactive, type: :boolean, aliases: '-i', default: false,
90
+ desc: 'Interactive mode for version selection'
91
+ def global(version = nil)
92
+ cmd = VersionCommand.new
93
+ cmd.options = options
94
+ cmd.global(version)
95
+ end
96
+
97
+ desc 'local [VERSION]', 'Set Metanorma version for current directory'
98
+ method_option :source, type: :string, enum: %w[gemfile binary],
99
+ desc: 'Source type (gemfile or binary)'
100
+ method_option :interactive, type: :boolean, aliases: '-i', default: false,
101
+ desc: 'Interactive mode for version selection'
102
+ def local(version = nil)
103
+ cmd = VersionCommand.new
104
+ cmd.options = options
105
+ cmd.local(version)
106
+ end
107
+
108
+ desc 'versions', 'List all installed Metanorma versions'
109
+ def versions
110
+ cmd = VersionCommand.new
111
+ cmd.versions
112
+ end
113
+
114
+ desc 'uninstall VERSION', 'Uninstall a Metanorma version'
115
+ method_option :force, type: :boolean, aliases: '-f', default: false,
116
+ desc: 'Force uninstallation without confirmation'
117
+ def uninstall(version)
118
+ cmd = UninstallCommand.new
119
+ cmd.options = options
120
+ cmd.uninstall(version)
121
+ end
122
+
30
123
  # General commands
31
124
  PLATFORM_REPOSITORIES = {
32
125
  'gemfile' => GemfileRepository,
33
126
  'snap' => SnapRepository,
34
127
  'homebrew' => HomebrewRepository,
35
- 'chocolatey' => ChocolateyRepository
128
+ 'chocolatey' => ChocolateyRepository,
129
+ 'binary' => BinaryRepository
36
130
  }.freeze
37
131
 
38
132
  desc 'info PLATFORM VERSION', 'Get details for a specific version'
@@ -54,8 +148,6 @@ module Mnenv
54
148
 
55
149
  case options[:format]
56
150
  when 'json'
57
- require_relative 'json_formatter'
58
- require 'json'
59
151
  output = JsonFormatter.format_version(version_obj)
60
152
  output['platform'] = platform
61
153
  puts JSON.pretty_generate(output)
@@ -66,6 +158,7 @@ module Mnenv
66
158
  puts " Parsed: #{version_obj.parsed_at || 'N/A'}"
67
159
  version_obj.to_h.each do |k, v|
68
160
  next if %w[version published_at parsed_at].include?(k)
161
+
69
162
  puts " #{k}: #{v}"
70
163
  end
71
164
  end
@@ -120,5 +213,12 @@ module Mnenv
120
213
  def version
121
214
  puts "mnenv #{Mnenv::VERSION}"
122
215
  end
216
+
217
+ desc 'update', 'Update version data from metanorma/versions repository'
218
+ def update
219
+ puts 'Updating version data...'
220
+ Repository.update_versions
221
+ puts 'Version data updated successfully.'
222
+ end
123
223
  end
124
224
  end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative '../version'
5
+ require_relative '../binary_repository'
6
+
7
+ module Mnenv
8
+ class AvailableCommand < Thor
9
+ namespace :available
10
+
11
+ class_option :source, type: :string, enum: %w[gemfile binary all], default: 'all',
12
+ desc: 'Source type to show (gemfile, binary, or all)'
13
+ class_option :format, type: :string, aliases: '-f', default: 'text',
14
+ desc: 'Output format (text or json)'
15
+
16
+ desc 'gemfile', 'List available Metanorma versions from RubyGems (gemfile method)'
17
+ method_option :format, type: :string, aliases: '-f', default: 'text'
18
+ def gemfile
19
+ repo = GemfileRepository.new
20
+ versions = repo.all.sort.reverse
21
+
22
+ show_available_versions('gemfile', versions)
23
+ end
24
+
25
+ desc 'binary', 'List available Metanorma versions from GitHub releases (binary method)'
26
+ method_option :format, type: :string, aliases: '-f', default: 'text'
27
+ def binary
28
+ repo = BinaryRepository.new
29
+ versions = repo.all
30
+
31
+ show_available_versions('binary', versions)
32
+ end
33
+
34
+ desc 'all', 'List all available Metanorma versions from all sources'
35
+ method_option :format, type: :string, aliases: '-f', default: 'text'
36
+ def all
37
+ case options[:format]
38
+ when 'json'
39
+ output = {}
40
+ output['gemfile'] = format_versions_json(GemfileRepository.new.all.sort.reverse)
41
+ output['binary'] = format_versions_json(BinaryRepository.new.all)
42
+ puts JSON.pretty_generate(output)
43
+ else
44
+ puts "\n=== Gemfile (RubyGems) ==="
45
+ gemfile
46
+
47
+ puts "\n=== Binary (packed-mn releases) ==="
48
+ binary
49
+ end
50
+ end
51
+
52
+ default_task :all
53
+
54
+ private
55
+
56
+ def show_available_versions(source, versions)
57
+ current_version, current_source = resolve_current
58
+
59
+ case options[:format]
60
+ when 'json'
61
+ output = {
62
+ 'source' => source,
63
+ 'versions' => format_versions_json(versions)
64
+ }
65
+ puts JSON.pretty_generate(output)
66
+ else
67
+ puts "Available Metanorma versions (source: #{source}):"
68
+
69
+ versions.each do |v|
70
+ # Check if installed
71
+ version_dir = File.expand_path("~/.mnenv/versions/#{v.version}")
72
+ installed = Dir.exist?(version_dir)
73
+
74
+ # Check installed source
75
+ installed_source = if installed
76
+ source_file = File.join(version_dir, 'source')
77
+ File.exist?(source_file) ? File.read(source_file).strip : 'unknown'
78
+ end
79
+
80
+ # Check if currently active
81
+ is_current = installed && v.version == current_version && installed_source == current_source
82
+
83
+ # Build status indicator
84
+ status_parts = []
85
+ status_parts << 'current' if is_current
86
+ status_parts << 'installed' if installed && !is_current
87
+ status = status_parts.empty? ? '' : " [#{status_parts.join(', ')}]"
88
+
89
+ # Show installed source if different
90
+ source_info = if installed && installed_source && installed_source != source
91
+ " (as #{installed_source})"
92
+ elsif installed && installed_source == source
93
+ ''
94
+ end
95
+
96
+ # Version display with padding
97
+ version_display = v.display_name
98
+ marker = is_current ? '*' : ' '
99
+ padding = ' ' * (15 - version_display.length - status.length)
100
+
101
+ puts " #{marker} #{version_display}#{padding}#{status}#{source_info}"
102
+ end
103
+ end
104
+ end
105
+
106
+ def format_versions_json(versions)
107
+ versions.map do |v|
108
+ version_dir = File.expand_path("~/.mnenv/versions/#{v.version}")
109
+ installed = Dir.exist?(version_dir)
110
+ installed_source = if installed
111
+ source_file = File.join(version_dir, 'source')
112
+ File.exist?(source_file) ? File.read(source_file).strip : nil
113
+ end
114
+
115
+ {
116
+ 'version' => v.version,
117
+ 'display_name' => v.display_name,
118
+ 'published_at' => v.published_at&.iso8601,
119
+ 'installed' => installed,
120
+ 'installed_source' => installed_source
121
+ }
122
+ end
123
+ end
124
+
125
+ def resolve_current
126
+ # Get current version
127
+ version = ENV['METANORMA_VERSION']
128
+ unless version
129
+ # Check .metanorma-version file
130
+ dir = Dir.pwd
131
+ while dir && dir != '/'
132
+ if File.exist?(File.join(dir, '.metanorma-version'))
133
+ version = File.read(File.join(dir, '.metanorma-version')).strip
134
+ break
135
+ end
136
+ dir = File.dirname(dir)
137
+ end
138
+
139
+ # Check global version
140
+ version ||= begin
141
+ global_version_file = File.expand_path('~/.mnenv/version')
142
+ File.read(global_version_file).strip if File.exist?(global_version_file)
143
+ end
144
+ end
145
+
146
+ # Get current source
147
+ source = ENV['METANORMA_SOURCE']
148
+ unless source
149
+ # Check .metanorma-source file
150
+ dir = Dir.pwd
151
+ while dir && dir != '/'
152
+ if File.exist?(File.join(dir, '.metanorma-source'))
153
+ source = File.read(File.join(dir, '.metanorma-source')).strip
154
+ break
155
+ end
156
+ dir = File.dirname(dir)
157
+ end
158
+
159
+ # Check global source
160
+ source ||= begin
161
+ global_source_file = File.expand_path('~/.mnenv/source')
162
+ File.read(global_source_file).strip if File.exist?(global_source_file)
163
+ end
164
+ end
165
+
166
+ [version, source]
167
+ end
168
+ end
169
+ end
@@ -4,12 +4,14 @@ require 'thor'
4
4
  require 'json'
5
5
 
6
6
  module Mnenv
7
+ autoload :ChocolateyRepository, 'mnenv/chocolatey_repository'
8
+ autoload :JsonFormatter, 'mnenv/json_formatter'
9
+ autoload :Chocolatey, 'mnenv/chocolatey'
10
+
7
11
  class ChocolateyCommand < Thor
8
12
  desc 'list', 'List all Chocolatey versions'
9
13
  method_option :format, type: :string, aliases: '-f', default: 'text'
10
14
  def list
11
- require_relative '../chocolatey_repository'
12
- require_relative '../json_formatter'
13
15
  repo = ChocolateyRepository.new
14
16
  versions = repo.all
15
17
 
@@ -25,7 +27,6 @@ module Mnenv
25
27
 
26
28
  desc 'refresh', 'Fetch and add new Chocolatey versions'
27
29
  def refresh
28
- require_relative '../chocolatey/fetcher'
29
30
  fetcher = Chocolatey::Fetcher.new
30
31
  existing = fetcher.repository.all.map(&:version)
31
32
  remote_versions = fetcher.fetch_all
@@ -41,7 +42,6 @@ module Mnenv
41
42
 
42
43
  desc 'revamp', 'Re-fetch all Chocolatey versions'
43
44
  def revamp
44
- require_relative '../chocolatey/fetcher'
45
45
  fetcher = Chocolatey::Fetcher.new
46
46
  versions = fetcher.fetch_and_save
47
47
  puts "Revamped #{versions.size} Chocolatey versions"
@@ -49,7 +49,6 @@ module Mnenv
49
49
 
50
50
  desc 'update VERSION', 'Update a specific Chocolatey version'
51
51
  def update(version)
52
- require_relative '../chocolatey/fetcher'
53
52
  fetcher = Chocolatey::Fetcher.new
54
53
  versions = fetcher.fetch_all
55
54
  target = versions.find { |v| v.version == version }
@@ -4,12 +4,14 @@ require 'thor'
4
4
  require 'json'
5
5
 
6
6
  module Mnenv
7
+ autoload :GemfileRepository, 'mnenv/gemfile_repository'
8
+ autoload :JsonFormatter, 'mnenv/json_formatter'
9
+ autoload :Gemfile, 'mnenv/gemfile'
10
+
7
11
  class GemfileCommand < Thor
8
12
  desc 'list', 'List all Gemfile (Ruby) versions'
9
13
  method_option :format, type: :string, aliases: '-f', default: 'text'
10
14
  def list
11
- require_relative '../gemfile_repository'
12
- require_relative '../json_formatter'
13
15
  repo = GemfileRepository.new
14
16
  versions = repo.all
15
17
 
@@ -25,21 +27,18 @@ module Mnenv
25
27
 
26
28
  desc 'refresh', 'Extract new Gemfiles (incremental mode)'
27
29
  def refresh
28
- require_relative '../gemfile/extractor'
29
30
  extractor = Gemfile::Extractor.new(mode: :incremental)
30
31
  extractor.run
31
32
  end
32
33
 
33
34
  desc 'revamp', 'Re-extract all Gemfiles'
34
35
  def revamp
35
- require_relative '../gemfile/extractor'
36
36
  extractor = Gemfile::Extractor.new(mode: :revamp)
37
37
  extractor.run
38
38
  end
39
39
 
40
40
  desc 'update VERSION', 'Re-extract specific version'
41
41
  def update(version)
42
- require_relative '../gemfile/extractor'
43
42
  extractor = Gemfile::Extractor.new(mode: :replace, target_version: version)
44
43
  extractor.run
45
44
  end
@@ -4,12 +4,14 @@ require 'thor'
4
4
  require 'json'
5
5
 
6
6
  module Mnenv
7
+ autoload :HomebrewRepository, 'mnenv/homebrew_repository'
8
+ autoload :JsonFormatter, 'mnenv/json_formatter'
9
+ autoload :Homebrew, 'mnenv/homebrew'
10
+
7
11
  class HomebrewCommand < Thor
8
12
  desc 'list', 'List all Homebrew versions'
9
13
  method_option :format, type: :string, aliases: '-f', default: 'text'
10
14
  def list
11
- require_relative '../homebrew_repository'
12
- require_relative '../json_formatter'
13
15
  repo = HomebrewRepository.new
14
16
  versions = repo.all
15
17
 
@@ -25,7 +27,6 @@ module Mnenv
25
27
 
26
28
  desc 'refresh', 'Fetch and add new Homebrew versions'
27
29
  def refresh
28
- require_relative '../homebrew/fetcher'
29
30
  fetcher = Homebrew::Fetcher.new
30
31
  existing = fetcher.repository.all.map(&:version)
31
32
  remote_versions = fetcher.fetch_all
@@ -41,7 +42,6 @@ module Mnenv
41
42
 
42
43
  desc 'revamp', 'Re-fetch all Homebrew versions'
43
44
  def revamp
44
- require_relative '../homebrew/fetcher'
45
45
  fetcher = Homebrew::Fetcher.new
46
46
  versions = fetcher.fetch_and_save
47
47
  puts "Revamped #{versions.size} Homebrew versions"
@@ -49,7 +49,6 @@ module Mnenv
49
49
 
50
50
  desc 'update VERSION', 'Update a specific Homebrew version'
51
51
  def update(version)
52
- require_relative '../homebrew/fetcher'
53
52
  fetcher = Homebrew::Fetcher.new
54
53
  versions = fetcher.fetch_all
55
54
  target = versions.find { |v| v.version == version }
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty/prompt'
4
+ require_relative '../installer'
5
+ require_relative '../binary_repository'
6
+
7
+ module Mnenv
8
+ class InstallCommand < Thor
9
+ namespace :install
10
+
11
+ class_option :source, type: :string, enum: %w[gemfile binary], default: 'gemfile',
12
+ desc: 'Installation source (gemfile or binary)'
13
+ class_option :interactive, type: :boolean, aliases: '-i', default: false,
14
+ desc: 'Interactive mode for version selection'
15
+
16
+ desc 'VERSION', 'Install a specific Metanorma version'
17
+ method_option :source, type: :string, enum: %w[gemfile binary], default: 'gemfile',
18
+ desc: 'Installation source (gemfile or binary)'
19
+ method_option :interactive, type: :boolean, aliases: '-i', default: false,
20
+ desc: 'Interactive mode for version selection'
21
+ method_option :list, type: :boolean, aliases: '-l', default: false,
22
+ desc: 'List all available Metanorma versions'
23
+ def install(version = nil)
24
+ # If --list flag is provided, show available versions and exit
25
+ return list_available_versions if options[:list]
26
+
27
+ # If no version provided or interactive mode, prompt for selection
28
+ if options[:interactive] || version.nil?
29
+ version, source = select_installation_interactive
30
+ else
31
+ source = options[:source]
32
+ end
33
+
34
+ installer = InstallerFactory.create(version, source: source)
35
+
36
+ if installer.installed?
37
+ prompt = TTY::Prompt.new
38
+ unless prompt.yes?("Version #{version} (source: #{source}) is already installed. Reinstall?")
39
+ puts 'Installation cancelled.'
40
+ return
41
+ end
42
+ end
43
+
44
+ puts "Installing Metanorma #{version} (source: #{source})..."
45
+ installer.install
46
+ puts "Successfully installed Metanorma #{version} (source: #{source})!"
47
+ rescue Installer::InstallationError => e
48
+ warn "Error: #{e.message}"
49
+ exit 1
50
+ end
51
+
52
+ default_task :install
53
+
54
+ private
55
+
56
+ def list_available_versions
57
+ current_version, current_source = resolve_current
58
+ current_platform = detect_platform
59
+
60
+ puts "\nAvailable Metanorma versions:"
61
+ puts "(gemfile = source build, binary = prebuilt for specific platforms)\n"
62
+
63
+ # Get all unique versions from both sources
64
+ gemfile_repo = GemfileRepository.new
65
+ binary_repo = BinaryRepository.new
66
+
67
+ gemfile_versions = gemfile_repo.all.sort.reverse
68
+ binary_versions = binary_repo.all
69
+
70
+ # Combine all versions
71
+ all_versions = Hash.new { |h, k| h[k] = { gemfile: false, binary: false } }
72
+
73
+ gemfile_versions.each do |v|
74
+ all_versions[v.version][:gemfile] = true
75
+ all_versions[v.version][:gemfile_obj] = v
76
+ end
77
+
78
+ binary_versions.each do |v|
79
+ all_versions[v.version][:binary] = true
80
+ all_versions[v.version][:binary_obj] = v
81
+ all_versions[v.version][:binary_platforms] = extract_platforms(v.assets)
82
+ end
83
+
84
+ # Check what's installed (from installed directory, not versions data)
85
+ if Dir.exist?(Paths::INSTALLED_DIR)
86
+ Dir.glob("#{Paths::INSTALLED_DIR}/*/").each do |dir|
87
+ version = File.basename(dir)
88
+ source_file = File.join(dir, 'source')
89
+ source = File.exist?(source_file) ? File.read(source_file).strip : nil
90
+ all_versions[version][:"installed_#{source}"] = true if source && all_versions[version]
91
+ end
92
+ end
93
+
94
+ # Display sorted versions (newest first)
95
+ all_versions.sort.reverse.each do |version, info|
96
+ # Build sources string with platform info for binary
97
+ sources = []
98
+ sources << 'gemfile' if info[:gemfile]
99
+ if info[:binary]
100
+ platforms = info[:binary_platforms] || []
101
+ if platforms.any?
102
+ # Highlight current platform
103
+ platform_display = platforms.map do |p|
104
+ p == current_platform ? "#{p}*" : p
105
+ end.join(', ')
106
+ sources << "binary [#{platform_display}]"
107
+ else
108
+ sources << 'binary'
109
+ end
110
+ end
111
+
112
+ # Check if current
113
+ is_current = version == current_version && sources.any? { |s| s.include?(current_source.to_s) }
114
+ marker = is_current ? '* ' : ' '
115
+
116
+ # Version display
117
+ version_display = version.ljust(15)
118
+
119
+ # Sources display
120
+ sources_display = sources.join(', ')
121
+
122
+ # Installed status
123
+ installed = []
124
+ installed << 'gemfile' if info[:installed_gemfile]
125
+ installed << 'binary' if info[:installed_binary]
126
+ installed_display = installed.empty? ? '' : " [installed: #{installed.join(', ')}]"
127
+
128
+ puts " #{marker}#{version_display}(#{sources_display})#{installed_display}"
129
+ end
130
+
131
+ # Legend
132
+ puts "\nLegend:"
133
+ puts ' * Current version / platform'
134
+ puts ' [installed: ...] = Already installed locally'
135
+ puts " Detected platform: #{current_platform}"
136
+ puts "\nInstall with: mnenv install VERSION --source SOURCE"
137
+ puts 'Examples:'
138
+ puts ' mnenv install 1.14.4 --source gemfile'
139
+ puts ' mnenv install 1.14.4 --source binary'
140
+ end
141
+
142
+ def extract_platforms(assets)
143
+ return [] unless assets
144
+
145
+ assets.filter_map do |asset|
146
+ # Match patterns like metanorma-darwin-arm64.tgz or metanorma-linux-x86_64.tgz
147
+ case asset
148
+ when /darwin/ then 'macos'
149
+ when /linux/ then 'linux'
150
+ when /windows/ then 'windows'
151
+ end
152
+ end.uniq.sort
153
+ end
154
+
155
+ def detect_platform
156
+ case RbConfig::CONFIG['host_os']
157
+ when /linux/ then 'linux'
158
+ when /darwin/ then 'macos'
159
+ when /mswin|mingw|cygwin/ then 'windows'
160
+ else 'unknown'
161
+ end
162
+ end
163
+
164
+ def resolve_current
165
+ # Get current version
166
+ version = ENV['METANORMA_VERSION']
167
+ unless version
168
+ # Check .metanorma-version file
169
+ dir = Dir.pwd
170
+ while dir && dir != '/'
171
+ if File.exist?(File.join(dir, '.metanorma-version'))
172
+ version = File.read(File.join(dir, '.metanorma-version')).strip
173
+ break
174
+ end
175
+ dir = File.dirname(dir)
176
+ end
177
+
178
+ # Check global version
179
+ version ||= (File.read(Paths::VERSION_FILE).strip if File.exist?(Paths::VERSION_FILE))
180
+
181
+ end
182
+
183
+ # Get current source
184
+ source = ENV['METANORMA_SOURCE']
185
+ unless source
186
+ # Check .metanorma-source file
187
+ dir = Dir.pwd
188
+ while dir && dir != '/'
189
+ if File.exist?(File.join(dir, '.metanorma-source'))
190
+ source = File.read(File.join(dir, '.metanorma-source')).strip
191
+ break
192
+ end
193
+ dir = File.dirname(dir)
194
+ end
195
+
196
+ # Check global source
197
+ source ||= (File.read(Paths::SOURCE_FILE).strip if File.exist?(Paths::SOURCE_FILE))
198
+
199
+ end
200
+
201
+ [version, source]
202
+ end
203
+
204
+ def select_installation_interactive
205
+ repo = GemfileRepository.new
206
+ prompt = TTY::Prompt.new
207
+
208
+ # First, select source
209
+ source = prompt.select('Select installation source:', [
210
+ { name: 'Gemfile (faster, on-demand loading, requires dev tools, upgradable)',
211
+ value: 'gemfile' },
212
+ { name: 'Binary (slower, full-stack memory load, no dev tools, fixed)',
213
+ value: 'binary' }
214
+ ])
215
+
216
+ # Then, select version
217
+ choices = repo.all.sort.map do |v|
218
+ version_dir = Paths.version_install_dir(v.version)
219
+ installed = Dir.exist?(version_dir) && File.exist?(File.join(version_dir, 'source'))
220
+ installed_source = if installed
221
+ src = File.read(File.join(version_dir, 'source')).strip
222
+ src.empty? ? '' : "(#{src})"
223
+ else
224
+ ''
225
+ end
226
+ { name: "#{v.display_name} #{installed ? "[installed: #{installed_source}]" : ''}", value: v.version }
227
+ end
228
+
229
+ version = prompt.select('Select a version to install:', choices)
230
+
231
+ [version, source]
232
+ end
233
+ end
234
+ end