k_starter 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 (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 +7 -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 +45 -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 +44 -1
  41. data/package-lock.json +2 -2
  42. data/package.json +1 -1
  43. metadata +172 -1
@@ -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