k_starter 0.1.0 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.builders/generators/01-bootstrap.rb +16 -16
  3. data/.rubocop.yml +14 -0
  4. data/CHANGELOG.md +23 -0
  5. data/Gemfile +22 -0
  6. data/README.md +1 -1
  7. data/exe/k_starter +15 -0
  8. data/ext/k_starter/extconf.rb +5 -0
  9. data/ext/k_starter/k_starter.c +9 -0
  10. data/ext/k_starter/k_starter.h +6 -0
  11. data/lib/k_starter/app.rb +16 -0
  12. data/lib/k_starter/app_generator.rb +198 -0
  13. data/lib/k_starter/cli.rb +63 -0
  14. data/lib/k_starter/commands/.keep +1 -0
  15. data/lib/k_starter/commands/command.rb +54 -0
  16. data/lib/k_starter/commands/configuration/configuration_menu.rb +46 -0
  17. data/lib/k_starter/commands/project/bootstrap_project.rb +28 -0
  18. data/lib/k_starter/commands/project/edit_project.rb +34 -0
  19. data/lib/k_starter/commands/project/new_project.rb +28 -0
  20. data/lib/k_starter/commands/project/project_menu.rb +51 -0
  21. data/lib/k_starter/commands/project/select_new_project_type.rb +44 -0
  22. data/lib/k_starter/database/base_model.rb +21 -0
  23. data/lib/k_starter/database/config_model.rb +99 -0
  24. data/lib/k_starter/database/project_model.rb +94 -0
  25. data/lib/k_starter/map.rb +46 -0
  26. data/lib/k_starter/questions/ask_questions.rb +20 -0
  27. data/lib/k_starter/questions/base_question.rb +208 -0
  28. data/lib/k_starter/questions/gem.rb +132 -0
  29. data/lib/k_starter/questions/rails.rb +162 -0
  30. data/lib/k_starter/questions/svelte.rb +52 -0
  31. data/lib/k_starter/r7_hotwire.rb +98 -0
  32. data/lib/k_starter/schema.rb +89 -0
  33. data/lib/k_starter/starters/base.rb +80 -0
  34. data/lib/k_starter/starters/gem.rb +71 -0
  35. data/lib/k_starter/starters/nuxt.rb +29 -0
  36. data/lib/k_starter/starters/rails.rb +29 -0
  37. data/lib/k_starter/starters/svelte.rb +70 -0
  38. data/lib/k_starter/tty_helpers.rb +118 -0
  39. data/lib/k_starter/version.rb +1 -1
  40. data/lib/k_starter.rb +43 -1
  41. data/package-lock.json +2 -2
  42. data/package.json +1 -1
  43. metadata +159 -2
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KStarter
4
+ module Commands
5
+ # Submenu for project
6
+ class ProjectMenu < KStarter::Commands::Command
7
+ def initialize(subcommand, options)
8
+ @subcommand = (subcommand || '').to_sym
9
+
10
+ @options = options
11
+ super()
12
+ end
13
+
14
+ def execute(input: $stdin, output: $stdout)
15
+ command = nil
16
+
17
+ loop do
18
+ case @subcommand
19
+ when :new
20
+ command = KStarter::Commands::SelectNewProjectType.new(:menu)
21
+ when :edit
22
+ command = KStarter::Commands::EditProject.new
23
+ when :bootstrap
24
+ command = KStarter::Commands::BootstrapProject.new
25
+ end
26
+ command&.execute(input: input, output: output) if command
27
+ break if menu.nil?
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def menu
34
+ display_exiting_projects
35
+
36
+ choices = [
37
+ { name: 'New project settings', value: :new },
38
+ { name: 'Edit project settings', value: :edit },
39
+ { name: 'Bootstrap a project', value: :bootstrap }
40
+ ]
41
+
42
+ subcommand = prompt.select('Select your subcommand?', choices, per_page: 15, cycle: true)
43
+
44
+ command = KStarter::Commands::ProjectMenu.new(subcommand, {})
45
+ command.execute(input: @input, output: @output)
46
+ rescue KStarter::EscapePressed
47
+ nil
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KStarter
4
+ module Commands
5
+ # Submenu for forms
6
+ class SelectNewProjectType < KStarter::Commands::Command
7
+ def initialize(subcommand, **options)
8
+ @subcommand = (subcommand || '').to_sym
9
+
10
+ @options = options
11
+ super()
12
+ end
13
+
14
+ def execute(input: $stdin, output: $stdout)
15
+ command = nil
16
+ case @subcommand
17
+ when :menu
18
+ command = select_new_project_type
19
+ end
20
+ command&.execute(input: input, output: output)
21
+ end
22
+
23
+ private
24
+
25
+ def select_new_project_type
26
+ prompt.warn('Forms for project settings')
27
+
28
+ choices = [
29
+ { name: 'Rails' , value: { type: :rails, variant: nil } },
30
+ { name: 'Ruby library (GEM)' , value: { type: :gem, variant: :library } },
31
+ { name: 'Ruby Command line Ruby (GEM)', value: { type: :gem, variant: :cli } },
32
+ { name: 'Svelte' , value: { type: :svelte, variant: nil } }
33
+ ]
34
+
35
+ prompt.warn('What type of project are you creating?')
36
+ form = prompt.select('Select project type?', choices, per_page: 15, cycle: true)
37
+
38
+ KStarter::Commands::NewProject.new(form_type: form[:type], form_variant: form[:variant])
39
+ rescue KStarter::EscapePressed
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KStarter
4
+ module Database
5
+ # Handle access to project data and configuration JSON files
6
+ class BaseModel
7
+ attr_reader :folder
8
+ attr_reader :backup_folder
9
+ attr_reader :filename
10
+ attr_reader :data
11
+
12
+ # Split Config and Data into two classes
13
+ def initialize(**args)
14
+ @folder = args[:folder] || '~/.config'
15
+ @folder = File.expand_path(folder)
16
+ @backup_folder = File.join(folder, 'klue_backup')
17
+ @filename = File.join(@folder, args[:file])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KStarter
4
+ module Database
5
+ # Handle access to configuration data files
6
+ class ConfigModel < KStarter::Database::BaseModel
7
+ DEFAULT_FILE_NAME = 'k_starter.config.json'
8
+
9
+ def initialize(**args)
10
+ args = { file: DEFAULT_FILE_NAME }.merge(args)
11
+ super(**args)
12
+
13
+ initialize_model
14
+ end
15
+
16
+ # rubocop:disable Naming/AccessorMethodName
17
+ def set_project_type(project_type)
18
+ project_type = project_type.to_h unless project_type.is_a?(Hash)
19
+
20
+ index = project_type_index(project_type[:type], project_type[:variant])
21
+
22
+ if index.nil?
23
+ data[:project_types] << project_type
24
+ else
25
+ data[:project_types][index] = project_type
26
+ end
27
+
28
+ write
29
+ end
30
+ # rubocop:enable Naming/AccessorMethodName
31
+
32
+ def get_project_type(type, variant = nil)
33
+ index = project_type_index(type, variant)
34
+
35
+ return nil if index.nil?
36
+
37
+ @data[:project_types][index]
38
+ end
39
+
40
+ def project_variants(type)
41
+ type = as_sym(type)
42
+
43
+ # project variants without nil or empty
44
+ data[:project_types]
45
+ .select { |t| t[:type]&.to_sym == type }
46
+ .map { |t| t[:variant]&.to_sym }
47
+ .compact
48
+ end
49
+
50
+ def github_user
51
+ data[:github][:user]
52
+ end
53
+
54
+ def github_organizations
55
+ data[:github][:organizations]
56
+ end
57
+
58
+ private
59
+
60
+ def initialize_model
61
+ if File.exist?(filename)
62
+ read
63
+ return
64
+ end
65
+
66
+ @data = {
67
+ github: {
68
+ user: 'not_set',
69
+ organizations: []
70
+ },
71
+ project_types: []
72
+ }
73
+
74
+ write
75
+ end
76
+
77
+ def read
78
+ @data = JSON.parse(File.read(filename), symbolize_names: true)
79
+ end
80
+
81
+ def write
82
+ FileUtils.mkdir_p(File.dirname(filename))
83
+ File.write(filename, JSON.pretty_generate(data))
84
+ end
85
+
86
+ def project_type_index(type, variant = nil)
87
+ type = as_sym(type)
88
+ variant = as_sym(variant)
89
+
90
+ @data[:project_types].find_index { |t| t[:type]&.to_sym == type && (variant.nil? || t[:variant]&.to_sym == variant) }
91
+ end
92
+
93
+ def as_sym(value)
94
+ value.to_sym if value.is_a?(String)
95
+ value
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KStarter
4
+ module Database
5
+ # Handle access to project data files
6
+ class ProjectModel < KStarter::Database::BaseModel
7
+ DEFAULT_FILE_NAME = 'k_starter.projects.json'
8
+
9
+ # def reset_data
10
+ # require 'fileutils'
11
+
12
+ # source_file = App.data.find_file
13
+
14
+ # return unless File.exist?(source_file)
15
+
16
+ # backup_folder = App.backup_folder
17
+ # time = Time.now.strftime('%Y%m%d%H%M%S')
18
+
19
+ # target_file = File.join(backup_folder, "#{time}_#{App::DATA_FILE}")
20
+
21
+ # FileUtils.mkdir_p backup_folder
22
+ # FileUtils.cp(source_file, target_file)
23
+ # File.delete(source_file)
24
+
25
+ # @data = nil
26
+ # end
27
+
28
+ def initialize(**args)
29
+ args = { file: DEFAULT_FILE_NAME }.merge(args)
30
+ super(**args)
31
+
32
+ initialize_model
33
+ end
34
+
35
+ def project_list
36
+ @project_list ||= begin
37
+ projects = data[:projects]
38
+ projects.map { |project| KStarter::Map.project(project) }
39
+ end
40
+ end
41
+
42
+ def find_project(name, variant = nil)
43
+ index = find_project_index(name, variant)
44
+
45
+ return project_list[index] if index
46
+ end
47
+
48
+ def find_project_index(name, variant = nil)
49
+ project_list.find_index { |p| p.name == name && (variant.nil? || p.variant == variant) }
50
+ end
51
+
52
+ def save_project(project, project_index = nil)
53
+ project = project.to_h unless project.is_a?(Hash)
54
+
55
+ index = project_index || find_project_index(project[:name])
56
+
57
+ if index.nil?
58
+ data[:projects] << project
59
+ else
60
+ data[:projects][index] = project
61
+ end
62
+
63
+ write
64
+ clear_memoization
65
+ end
66
+
67
+ private
68
+
69
+ def initialize_model
70
+ if File.exist?(filename)
71
+ read
72
+ return
73
+ end
74
+
75
+ @data = { projects: [] }
76
+
77
+ write
78
+ end
79
+
80
+ def clear_memoization
81
+ @project_list = nil
82
+ end
83
+
84
+ def read
85
+ @data = JSON.parse(File.read(filename), symbolize_names: true)
86
+ end
87
+
88
+ def write
89
+ FileUtils.mkdir_p(File.dirname(filename))
90
+ File.write(filename, JSON.pretty_generate(data))
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry'
4
+ require 'date'
5
+ require 'thor'
6
+
7
+ module KStarter
8
+ # Handle the global access such as configuration
9
+ class Map
10
+ class << self
11
+ def project(project_hash)
12
+ case project_hash[:type].to_sym
13
+ when :gem
14
+ KStarter::Schema::GemProject.new(project_hash)
15
+ when :rails
16
+ KStarter::Schema::RailsProject.new(project_hash)
17
+ when :svelte
18
+ KStarter::Schema::SvelteProject.new(project_hash)
19
+ when :nuxt
20
+ KStarter::Schema::NuxtProject.new(project_hash)
21
+ else
22
+ raise "Unknown project type: #{project_hash[:type]}"
23
+ end
24
+ rescue StandardError => e
25
+ puts e.message
26
+ end
27
+
28
+ def starter(project_data)
29
+ project = project_data.is_a?(Hash) ? project(project_data) : project_data
30
+
31
+ raise "Unknown project type: #{project_data[:type]}" if project.nil?
32
+
33
+ case project.type
34
+ when :gem
35
+ KStarter::Starters::Gem.new(project)
36
+ when :rails
37
+ KStarter::Starters::Rails.new(project)
38
+ when :svelte
39
+ KStarter::Starters::Svelte.new(project)
40
+ when :nuxt
41
+ KStarter::Starters::Nuxt.new(project)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KStarter
4
+ module Questions
5
+ # Select a form with questions and load it up as a New form or
6
+ # an Edit form by optionally providing data
7
+ class AskQuestions
8
+ class << self
9
+ def for(form_type, form_variant = nil, **data)
10
+ return KStarter::Questions::Rails.new(**data) if form_type == :rails
11
+ return KStarter::Questions::LibraryGem.new(**data) if form_type == :gem && form_variant == :library
12
+ return KStarter::Questions::CliGem.new(**data) if form_type == :gem && form_variant == :cli
13
+ return KStarter::Questions::Svelte.new(**data) if form_type == :svelte
14
+
15
+ raise "Unknown form type: #{form_type}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ # see: bundle --help
4
+ # example: bundle gem --coc --test=rspec --mit k_project
5
+ # km2 new k_project -t ruby_gem \
6
+ # --description "k_project is a command line GEM for building new ruby, rails and other language projects" \
7
+ # --user-story "As a Developer, I want a simple path to creating new projects in different languages, so that I be productive quickly"
8
+
9
+ module KStarter
10
+ module Questions
11
+ # Ask code generations for a new projects.
12
+ class BaseQuestion
13
+ include TtyHelpers
14
+
15
+ attr_reader :name # project name
16
+ attr_reader :type # project type
17
+ attr_reader :variant # project variant
18
+ attr_reader :root_path # project root path
19
+ attr_reader :sub_path # project sub path, useful for folders under the root such as klueless projects
20
+ attr_reader :github_key # github user_name or organization name
21
+ attr_reader :description # project description
22
+ attr_reader :story_active # is story required?
23
+ attr_reader :actor # who is the actor
24
+ attr_reader :problem # what problem do they have
25
+ attr_reader :solution # what is the intended solution
26
+ attr_reader :user_story # what is the primary user story
27
+ attr_reader :klue_template_active # is klue_template used?
28
+ attr_reader :klue_template_name # name of the klue template
29
+
30
+ def initialize(**args)
31
+ initialize_attributes(**args)
32
+ apply_attribute_defaults
33
+ clean_attributes
34
+ end
35
+
36
+ def default_questions; end
37
+
38
+ def question
39
+ @question ||= prompt
40
+ end
41
+
42
+ def ask_root_path
43
+ project_type_config = App.config.get_project_type(type, variant)
44
+ configured_path = project_type_config.nil? ? '~/unknown_project' : project_type_config[:path]
45
+
46
+ @root_path = question.ask('Root project path') do |q|
47
+ q.default(configured_path)
48
+ q.required(true)
49
+ end
50
+ end
51
+
52
+ def ask_sub_path
53
+ project_type_config = App.config.get_project_type(type, variant)
54
+ configured_path = project_type_config.nil? ? '~/unknown_project' : project_type_config[:sub_path]
55
+
56
+ @sub_path = question.ask('Sub path within the project') do |q|
57
+ q.default(configured_path)
58
+ q.required(true)
59
+ end
60
+ end
61
+
62
+ def ask_github_key
63
+ choices = [App.config.github_user] + App.config.github_organizations
64
+ @github_key = prompt.select('Select Github user or organization', choices, cycle: true, filter: true, default: @github_key)
65
+ end
66
+
67
+ def ask_description
68
+ @description = question.ask('Description', default: @description)
69
+ end
70
+
71
+ def ask_story_active?
72
+ @story_active = question.yes?('Is a user story required?', default: @story_active)
73
+ show_story(actor: @actor, problem: @problem, solution: @solution)
74
+ @story_active
75
+ end
76
+
77
+ def ask_actor
78
+ @actor = prompt.ask('Who is the primary actor for this project?', default: @actor || 'Developer')
79
+ # show_story(actor: @actor)
80
+ end
81
+
82
+ def ask_problem
83
+ @problem = question.ask('What problem are you solving?', default: @problem)
84
+ # show_story(actor: @actor, problem: @problem)
85
+ end
86
+
87
+ def ask_solution
88
+ @solution = question.ask('What is the intended solution?', default: @solution)
89
+ # show_story(actor: @actor, problem: @problem, solution: @solution)
90
+ end
91
+
92
+ def show_story(**args)
93
+ prompt.warn("As a #{args[:actor] || 'ACTOR'}, I want to #{args[:problem] || 'SOLVE PROBLEM'} so that I can #{args[:solution] || 'HAVE BENEFIT'}")
94
+ end
95
+
96
+ def ask_user_story
97
+ @user_story = question.ask('User Story', default: "As a #{actor}, I want to #{problem} so that I can #{solution}")
98
+ end
99
+
100
+ def story_questions
101
+ return unless ask_story_active?
102
+
103
+ ask_actor
104
+ ask_problem
105
+ ask_solution
106
+ ask_user_story
107
+ end
108
+
109
+ # ask_user_story
110
+
111
+ def ask_klue_template_active?
112
+ @klue_template_active = question.yes?('Does this project use a Klue Templates?', default: @klue_template_active)
113
+ @klue_template_active
114
+ end
115
+
116
+ def ask_klue_template_name
117
+ prompt.warn('Location: ~/dev/kgems/k_templates/definitions/starter')
118
+ @klue_template_name = prompt.ask('Klue starter template name', default: infer_klue_template_name(@klue_template_name)) do |q|
119
+ q.required(true)
120
+ end
121
+ end
122
+
123
+ def klue_template_questions
124
+ return unless ask_klue_template_active?
125
+
126
+ ask_klue_template_name
127
+ end
128
+
129
+ def infer_klue_template_name(klue_template_name)
130
+ return klue_template_name unless klue_template_name.nil?
131
+
132
+ case type
133
+ when :rails, :svelte, :nuxt3
134
+ return type.to_s
135
+ when :gem
136
+ # return variant == :library ? 'ruby_gem' : 'ruby_gem_cli'
137
+ return 'ruby_gem'
138
+ end
139
+ nil
140
+ end
141
+
142
+ def default_story_active
143
+ @story_active = true if @story_active.nil?
144
+ end
145
+
146
+ def default_klue_template_active
147
+ @klue_template_active = true if @klue_template_active.nil?
148
+ end
149
+
150
+ def to_h
151
+ {
152
+ name: name,
153
+ type: type,
154
+ variant: variant,
155
+ root_path: root_path,
156
+ description: description,
157
+ github_key: github_key,
158
+ story: {
159
+ active: story_active,
160
+ actor: actor,
161
+ problem: problem,
162
+ solution: solution,
163
+ user_story: user_story
164
+ },
165
+ klue_template: {
166
+ active: klue_template_active,
167
+ name: klue_template_name
168
+ }
169
+ }
170
+ end
171
+
172
+ private
173
+
174
+ def initialize_attributes(**args)
175
+ @name = args[:name]
176
+ @type = args[:type]
177
+ @variant = args[:variant]
178
+ @root_path = args[:root_path]
179
+ @sub_path = args[:sub_path]
180
+ @github_key = args[:github_key]
181
+ @description = args[:description]
182
+ @story_active = args.dig(:story, :active)
183
+ @actor = args.dig(:story, :actor)
184
+ @problem = args.dig(:story, :problem)
185
+ @solution = args.dig(:story, :solution)
186
+ @user_story = args.dig(:story, :user_story)
187
+ @klue_template_active = args.dig(:klue_template, :active)
188
+ @klue_template_name = args.dig(:klue_template, :name)
189
+ end
190
+
191
+ def apply_attribute_defaults; end
192
+
193
+ def clean_attributes
194
+ @story_active = handle_boolean(@story_active)
195
+ @klue_template_active = handle_boolean(@klue_template_active)
196
+ end
197
+
198
+ def handle_boolean(value)
199
+ case value
200
+ when true, 'true', 't', 'yes', 'y', '1'
201
+ true
202
+ else
203
+ false
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end