11# frozen_string_literal: true
22
33require "rails/generators"
4+ require "fileutils"
5+ require_relative "../../../memory_bank_rails/config"
46
57module MemoryBank
68 module Generators
79 class InstallGenerator < Rails ::Generators ::Base
810 source_root File . expand_path ( "templates" , __dir__ )
911
10- class_option :guide , type : :string , default : "rails_web " , desc : "Which guide to install"
11- class_option :with_rules , type : :boolean , default : false , desc : "Copy .cursorrules if available"
12+ class_option :guide , type : :string , default : "select " , desc : "Which guide to install (or 'select' to choose interactively) "
13+ class_option :with_rules , type : :boolean , default : false , desc : "Copy editor rules ( .cursorrules, RuboCop, Solargraph) if available"
1214
1315 def create_config
1416 template "config/memory_bank.yml.erb" , "config/memory_bank.yml"
@@ -20,18 +22,112 @@ def ensure_directories
2022 end
2123
2224 def copy_guide
23- guide = options [ "guide" ]
24- guide_dir = File . join ( self . class . source_root , "guides" , guide )
25- unless Dir . exist? ( guide_dir )
25+ config = MemoryBankRails ::Config . load ( destination_root )
26+ guides_path = config . dig ( "memory_bank" , "guides_path" )
27+ guide = options [ "guide" ] . to_s
28+
29+ if guide == "select"
30+ guide , source_kind , external_full_path = select_guide_interactively ( guides_path )
31+ else
32+ source_kind , external_full_path = resolve_guide_source ( guide , guides_path )
33+ end
34+
35+ case source_kind
36+ when :builtin
37+ copy_file File . join ( "guides" , guide , "developmentGuide.md" ) , ".memory_bank/developmentGuide.md"
38+ if options [ "with_rules" ] && File . exist? ( File . join ( self . class . source_root , "guides" , guide , ".cursorrules" ) )
39+ copy_file File . join ( "guides" , guide , ".cursorrules" ) , ".cursorrules"
40+ end
41+ when :external
42+ copy_external_guide_files ( external_full_path )
43+ else
2644 say "Unknown guide: #{ guide } " , :red
2745 return
2846 end
2947
30- copy_file File . join ( "guides" , guide , "developmentGuide.md" ) , ".memory_bank/developmentGuide.md"
31- if options [ "with_rules" ] && File . exist? ( File . join ( guide_dir , ".cursorrules" ) )
32- copy_file File . join ( "guides" , guide , ".cursorrules" ) , ".cursorrules"
48+ install_editor_rules if options [ "with_rules" ]
49+ end
50+
51+ private
52+
53+ def resolve_guide_source ( guide , guides_path )
54+ builtin_dir = File . join ( self . class . source_root , "guides" , guide )
55+ return [ :builtin , nil ] if Dir . exist? ( builtin_dir )
56+
57+ if guides_path
58+ expanded = File . expand_path ( guides_path )
59+ external_dir = File . join ( expanded , guide )
60+ return [ :external , external_dir ] if Dir . exist? ( external_dir )
61+ end
62+
63+ [ :unknown , nil ]
64+ end
65+
66+ def select_guide_interactively ( guides_path )
67+ builtin = available_builtin_guides
68+ external = available_external_guides ( guides_path )
69+ options = [ ]
70+ builtin . each { |g | options << [ "📦 #{ g } (builtin)" , :builtin , g , nil ] }
71+ external . each { |g , path | options << [ "🔧 #{ g } (external)" , :external , g , path ] }
72+
73+ if options . empty?
74+ say "No guides available. Falling back to builtin 'rails_web'" , :yellow
75+ return [ "rails_web" , :builtin , nil ]
76+ end
77+
78+ say "\n 🚀 Memory Bank Initializer (Rails)" , :green
79+ say "=================================\n "
80+ options . each_with_index do |( label , _kind , _slug , _path ) , idx |
81+ say format ( "%2d) %s" , idx + 1 , label )
82+ end
83+ choice = ask ( "\n ? What type of memory bank would you like to install? (1-#{ options . size } )" ) . to_i
84+ choice = 1 if choice < 1 || choice > options . size
85+ label , kind , slug , full_path = options [ choice - 1 ]
86+ [ slug , kind , full_path ]
87+ end
88+
89+ def available_builtin_guides
90+ Dir . children ( File . join ( self . class . source_root , "guides" ) ) . select do |entry |
91+ File . exist? ( File . join ( self . class . source_root , "guides" , entry , "developmentGuide.md" ) )
92+ end . sort
93+ end
94+
95+ def available_external_guides ( guides_path )
96+ return [ ] unless guides_path
97+ expanded = File . expand_path ( guides_path )
98+ return [ ] unless Dir . exist? ( expanded )
99+ Dir . children ( expanded ) . filter_map do |entry |
100+ full = File . join ( expanded , entry )
101+ guide_md = File . join ( full , "developmentGuide.md" )
102+ File . directory? ( full ) && File . exist? ( guide_md ) ? [ entry , full ] : nil
103+ end . sort_by ( &:first )
104+ end
105+
106+ def copy_external_guide_files ( external_dir )
107+ source_md = File . join ( external_dir , "developmentGuide.md" )
108+ unless File . exist? ( source_md )
109+ say "External guide is missing developmentGuide.md: #{ external_dir } " , :red
110+ return
111+ end
112+ FileUtils . cp ( source_md , File . join ( destination_root , ".memory_bank" , "developmentGuide.md" ) )
113+
114+ rules = File . join ( external_dir , ".cursorrules" )
115+ if options [ "with_rules" ] && File . exist? ( rules )
116+ FileUtils . cp ( rules , File . join ( destination_root , ".cursorrules" ) )
33117 end
34118 end
119+
120+ def install_editor_rules
121+ # Copy base RuboCop and Solargraph configs if they don't exist
122+ rubocop_template = File . join ( "editor" , ".rubocop.yml" )
123+ solargraph_template = File . join ( "editor" , ".solargraph.yml" )
124+
125+ rubocop_target = File . join ( destination_root , ".rubocop.yml" )
126+ solargraph_target = File . join ( destination_root , ".solargraph.yml" )
127+
128+ copy_file ( rubocop_template , ".rubocop.yml" ) unless File . exist? ( rubocop_target )
129+ copy_file ( solargraph_template , ".solargraph.yml" ) unless File . exist? ( solargraph_target )
130+ end
35131 end
36132 end
37133end
0 commit comments