greeenboii 0.1.5 → 0.1.7

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.idea/dataSources.local.xml +1 -1
  3. data/.idea/greeenboii.iml +0 -2
  4. data/.idea/vcs.xml +1 -0
  5. data/.idea/workspace.xml +154 -123
  6. data/.rubocop.sorbet.yml +5 -0
  7. data/.rubocop.yml +36 -0
  8. data/README.md +5 -0
  9. data/exe/greeenboii +1 -1
  10. data/lib/greeenboii/version.rb +5 -5
  11. data/lib/greeenboii.rb +447 -234
  12. data/sig/greeenboii/todo_list.rbs +30 -0
  13. data/sorbet/rbi/gems/.gitattributes +1 -0
  14. data/sorbet/rbi/gems/ast@2.4.2.rbi +585 -0
  15. data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  16. data/sorbet/rbi/gems/bigdecimal@3.1.9.rbi +9 -0
  17. data/sorbet/rbi/gems/cli-ui@2.3.0.rbi +3181 -0
  18. data/sorbet/rbi/gems/console_table@0.3.1.rbi +78 -0
  19. data/sorbet/rbi/gems/csv@3.3.2.rbi +9 -0
  20. data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
  21. data/sorbet/rbi/gems/httparty@0.22.0.rbi +2115 -0
  22. data/sorbet/rbi/gems/json@2.10.1.rbi +2120 -0
  23. data/sorbet/rbi/gems/language_server-protocol@3.17.0.4.rbi +9 -0
  24. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +86 -0
  25. data/sorbet/rbi/gems/mini_mime@1.1.5.rbi +9 -0
  26. data/sorbet/rbi/gems/minitest@5.25.4.rbi +1547 -0
  27. data/sorbet/rbi/gems/multi_xml@0.7.1.rbi +9 -0
  28. data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
  29. data/sorbet/rbi/gems/nokogiri@1.18.3.rbi +8205 -0
  30. data/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
  31. data/sorbet/rbi/gems/parser@3.3.7.1.rbi +5525 -0
  32. data/sorbet/rbi/gems/prism@1.3.0.rbi +41403 -0
  33. data/sorbet/rbi/gems/racc@1.8.1.rbi +164 -0
  34. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
  35. data/sorbet/rbi/gems/rake-compiler@1.2.9.rbi +9 -0
  36. data/sorbet/rbi/gems/rake@13.2.1.rbi +3028 -0
  37. data/sorbet/rbi/gems/rbi@0.2.4.rbi +4542 -0
  38. data/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +3795 -0
  39. data/sorbet/rbi/gems/rubocop-ast@1.38.0.rbi +7654 -0
  40. data/sorbet/rbi/gems/rubocop@1.72.2.rbi +61026 -0
  41. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
  42. data/sorbet/rbi/gems/spoom@1.5.4.rbi +5026 -0
  43. data/sorbet/rbi/gems/sqlite3@2.6.0.rbi +1895 -0
  44. data/sorbet/rbi/gems/tapioca@0.16.11.rbi +3656 -0
  45. data/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
  46. data/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
  47. data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +251 -0
  48. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  49. data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
  50. data/sorbet/tapioca/require.rb +6 -0
  51. metadata +47 -8
data/lib/greeenboii.rb CHANGED
@@ -1,234 +1,447 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "greeenboii/version"
4
- require "cli/ui"
5
- require "date"
6
- require "console_table"
7
-
8
- require "nokogiri"
9
- require "httparty"
10
-
11
- require "sqlite3"
12
-
13
- module Greeenboii
14
- class Error < StandardError; end
15
-
16
- class Search
17
- SEARCH_ENGINES = {
18
- "Google" => "https://www.google.com/search?client=opera-gx&q= ",
19
- "Bing" => "https://www.bing.com/search?q=",
20
- "DuckDuckGo" => "https://duckduckgo.com/?q="
21
- }.freeze
22
-
23
- def self.perform_search
24
- query = CLI::UI.ask("Enter your search query:", default: "")
25
- return if query.empty?
26
-
27
- results = []
28
- CLI::UI::SpinGroup.new do |spin_group|
29
- SEARCH_ENGINES.each do |engine, base_url|
30
- spin_group.add("Searching #{engine}...") do |spinner|
31
- suffix = case engine
32
- when "Google"
33
- "&sourceid=opera&ie=UTF-8&oe=UTF-8"
34
- when "Bing"
35
- "&sp=-1&pq=test&sc=6-4&qs=n&sk=&cvid=#{SecureRandom.hex(16)}"
36
- when "DuckDuckGo"
37
- "&t=h_&ia=web"
38
- end
39
- search_url = "#{base_url}#{URI.encode_www_form_component(query)}#{suffix}"
40
- links = scrape_links(engine, search_url)
41
- results << { engine: engine, links: links }
42
- sleep 1.0 # Simulating search delay
43
- spinner.update_title("#{engine} search complete!")
44
- end
45
- end
46
- end
47
-
48
- display_results(results)
49
- end
50
-
51
- def self.scrape_links(engine, url)
52
- headers = {
53
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36",
54
- "Accept-Language" => "en-US,en;q=0.5"
55
- }
56
-
57
- response = HTTParty.get(url, headers: headers)
58
- puts "Debug: HTTP Status: #{response.code}"
59
-
60
- doc = Nokogiri::HTML(response.body)
61
- puts "Debug: Page title: #{doc.title}"
62
-
63
- results = []
64
-
65
- case engine
66
- when "Google"
67
- doc.css("div.g").each do |result|
68
- link = result.css(".yuRUbf > a").first
69
- next unless link
70
-
71
- title = result.css("h3").text.strip
72
- url = link["href"]
73
- snippet = result.css(".VwiC3b").text.strip
74
-
75
- puts "Debug: Found Google result - Title: #{title}"
76
- results << url if url.start_with?("http")
77
- end
78
-
79
- when "Bing"
80
- doc.css("#b_results li.b_algo").each do |result|
81
- link = result.css("h2 a").first
82
- next unless link
83
-
84
- url = link["href"]
85
- puts "Debug: Found Bing result - URL: #{url}"
86
- results << url if url.start_with?("http")
87
- end
88
-
89
- else # DuckDuckGo
90
- doc.css(".result__body").each do |result|
91
- link = result.css(".result__title a").first
92
- next unless link
93
-
94
- url = link["href"]
95
- puts "Debug: Found DuckDuckGo result - URL: #{url}"
96
- results << url if url.start_with?("http")
97
- end
98
- end
99
-
100
- puts "Debug: Total results found: #{results.length}"
101
- results.take(8)
102
- end
103
- def self.display_results(results)
104
- CLI::UI.frame_style = :bracket
105
- CLI::UI::Frame.open(CLI::UI.fmt("{{green:Search Results}}")) do
106
- results.each do |result|
107
- puts CLI::UI.fmt("{{v}} {{cyan:#{result[:engine]}}}")
108
- result[:links].each_with_index do |link, idx|
109
- puts CLI::UI.fmt(" {{*}} ##{idx + 1}: #{link}")
110
- end
111
- end
112
- end
113
- end
114
- end
115
-
116
- class TodoList
117
- def initialize
118
- @db = setup_database
119
- end
120
-
121
- private def setup_database
122
- db = SQLite3::Database.new("greeenboii_todo.db")
123
- db.execute <<~SQL
124
- CREATE TABLE IF NOT EXISTS todos (
125
- id INTEGER PRIMARY KEY,
126
- title TEXT NOT NULL,
127
- completed BOOLEAN DEFAULT 0,
128
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
129
- )
130
- SQL
131
- db
132
- end
133
-
134
- def add_task
135
- title = CLI::UI.ask("Enter task title:")
136
- return if title.empty?
137
-
138
- @db.execute("INSERT INTO todos (title) VALUES (?)", [title])
139
- puts CLI::UI.fmt "{{green:✓}} Task added successfully!"
140
- end
141
-
142
- def list_tasks
143
- tasks = @db.execute("SELECT id, title, completed, created_at FROM todos ORDER BY created_at DESC")
144
- if tasks.empty?
145
- puts CLI::UI.fmt "{{yellow:⚠}} No tasks found"
146
- return
147
- end
148
-
149
- ConsoleTable.define(%w[ID Title Status Created]) do |table|
150
- tasks.each do |id, title, completed, created_at|
151
- status = completed == 1 ? "{{green:✓}} Done" : "{{red:✗}} Pending"
152
- table << [id, title, CLI::UI.fmt(status), created_at]
153
- end
154
- end
155
- end
156
-
157
- def mark_done
158
- list_tasks
159
- id = CLI::UI.ask("Enter task ID to mark as done:")
160
- return if id.empty?
161
-
162
- @db.execute("UPDATE todos SET completed = 1 WHERE id = ?", [id])
163
- puts CLI::UI.fmt "{{green:✓}} Task marked as done!"
164
- end
165
-
166
- def delete_task
167
- list_tasks
168
- id = CLI::UI.ask("Enter task ID to delete:")
169
- return if id.empty?
170
-
171
- @db.execute("DELETE FROM todos WHERE id = ?", [id])
172
- puts CLI::UI.fmt "{{green:✓}} Task deleted!"
173
- end
174
-
175
- def update_task
176
- list_tasks
177
- id = CLI::UI.ask("Enter task ID to update:")
178
- return if id.empty?
179
-
180
- title = CLI::UI.ask("Enter new title:")
181
- return if title.empty?
182
-
183
- @db.execute("UPDATE todos SET title = ? WHERE id = ?", [title, id])
184
- puts CLI::UI.fmt "{{green:✓}} Task updated!"
185
- end
186
-
187
- def show_menu
188
- CLI::UI::Frame.divider('{{v}} Todo List')
189
- loop do
190
- CLI::UI::Prompt.ask("Todo List Options:") do |handler|
191
- handler.option("List Tasks") { list_tasks }
192
- handler.option("Add Task") { add_task }
193
- handler.option("Mark Done") { mark_done }
194
- handler.option("Update Task") { update_task }
195
- handler.option("Delete Task") { delete_task }
196
- handler.option("Exit") { return }
197
- end
198
- end
199
- end
200
- end
201
-
202
- class Options
203
- def self.show_options
204
- CLI::UI::Prompt.instructions_color = CLI::UI::Color::GRAY
205
-
206
- CLI::UI::Prompt.ask("Choose an option:") do |handler|
207
- handler.option("{{gray:Search Files}}") { |selection| puts "Placeholder, Replaced soon. #{selection}" }
208
- handler.option("{{gray:Search Directory}}") { |selection| puts "Placeholder, Replaced soon. #{selection}" }
209
- handler.option("{{gray:Search Content}}") { |selection| puts "Placeholder, Replaced soon. #{selection}" }
210
- handler.option("{{yellow:Todo List}}") { |_selection| TodoList.new.show_menu }
211
- handler.option("{{cyan:Search Engine}}") { |_selection| Search.perform_search }
212
- handler.option("{{red:Exit}}") { |_selection| exit }
213
- end
214
- end
215
- end
216
-
217
- class Main
218
- CLI::UI::StdoutRouter.enable
219
- current_time = DateTime.now.strftime("%d-%m-%Y %H:%M:%S")
220
- CLI::UI::Frame.open("{{v}} Greeenboi : #{current_time}") do
221
- puts "Welcome to Greeenboii"
222
- puts "Lets do some magic!"
223
- CLI::UI.frame_style = :bracket
224
- CLI::UI::Frame.open(CLI::UI.fmt("{{green:Welcome to Greeenboii CLI}}")) do
225
- puts CLI::UI.fmt("{{cyan:Version}}: #{Greeenboii::VERSION}")
226
- # ConsoleTable.define(%w[Name Version]) do |table|
227
- # table << ["Greeenboii", Greeenboii::VERSION]
228
- # end
229
- puts CLI::UI.fmt("{{yellow:Type 'help' to see available commands}}")
230
- Options.show_options
231
- end
232
- end
233
- end
234
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "greeenboii/version"
4
+ require "cli/ui"
5
+ require "date"
6
+ require "console_table"
7
+
8
+ require "nokogiri"
9
+ require "httparty"
10
+
11
+ require "sqlite3"
12
+
13
+ module Greeenboii
14
+ class Error < StandardError; end
15
+
16
+ class WebsiteBuilder
17
+ def self.build_website
18
+ CLI::UI::Frame.open("{{v}} Website Builder") do
19
+ CLI::UI::Prompt.ask("Choose a template:") do |handler|
20
+ handler.option("{{green:NextJs-CoolStack}}") { |selection| create_nextjs(selection) }
21
+ handler.option("{{yellow:Hono-API}}") { |selection| create_hono(selection) }
22
+ # handler.option("{{blue:Rails}}") { |selection| create_rails(selection) }
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.create_nextjs(_selection)
28
+ # Ask for a relative path instead
29
+ location = CLI::UI.ask("Where to install? (relative path)", default: "nextjs-project")
30
+ CLI::UI.puts("Checking prerequisites...")
31
+
32
+ # Check prerequisites
33
+ node_version = begin
34
+ `node -v`.strip
35
+ rescue StandardError
36
+ nil
37
+ end
38
+ unless node_version
39
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Node.js not found. Please install Node.js first"))
40
+ return
41
+ end
42
+
43
+ git_version = begin
44
+ `git --version`.strip
45
+ rescue StandardError
46
+ nil
47
+ end
48
+ unless git_version
49
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Git not found. Please install Git first"))
50
+ return
51
+ end
52
+
53
+ # Create a path relative to current directory
54
+ full_path = File.expand_path(location, Dir.pwd)
55
+
56
+ # Check if directory already exists
57
+ if Dir.exist?(full_path)
58
+ # If directory exists, check if it's empty
59
+ unless Dir.empty?(full_path) # . and .. entries
60
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Directory not empty: #{full_path}"))
61
+ return
62
+ end
63
+ else
64
+ # Try to create the directory
65
+ begin
66
+ FileUtils.mkdir_p(full_path)
67
+ rescue StandardError => e
68
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Cannot create directory: #{e.message}"))
69
+ return
70
+ end
71
+ end
72
+
73
+ success = false
74
+ CLI::UI::Spinner.spin("Setting up NextJS + Supabase template") do |spinner|
75
+ spinner.update_title("Cloning repository...")
76
+
77
+ # Clone directly into the target directory
78
+ clone_cmd = "git clone https://github.com/greeenboi/nextjs-supabase-template.git \"#{full_path}\""
79
+ result = system(clone_cmd)
80
+
81
+ unless result
82
+ # Try alternative approach if direct cloning fails
83
+ spinner.update_title("Direct clone failed, trying alternative...")
84
+
85
+ # Clone to a temporary directory first
86
+ temp_dir = "#{Dir.pwd}/temp_clone_#{Time.now.to_i}"
87
+ FileUtils.mkdir_p(temp_dir)
88
+ result = system("git clone https://github.com/greeenboi/nextjs-supabase-template.git \"#{temp_dir}\"")
89
+
90
+ if result
91
+ # Copy files to destination
92
+ FileUtils.cp_r(Dir.glob("#{temp_dir}/*"), full_path)
93
+ FileUtils.cp_r(Dir.glob("#{temp_dir}/.*").reject { |f| f =~ /\/\.\.?$/ }, full_path)
94
+ FileUtils.rm_rf(temp_dir)
95
+ else
96
+ spinner.update_title("Clone failed")
97
+ next
98
+ end
99
+ end
100
+
101
+ spinner.update_title("Installing dependencies...")
102
+ Dir.chdir(full_path) do
103
+ package_manager = if system("bun -v > /dev/null 2>&1")
104
+ "bun"
105
+ elsif system("pnpm -v > /dev/null 2>&1")
106
+ "pnpm"
107
+ else
108
+ "npm"
109
+ end
110
+ spinner.update_title("Installing dependencies with #{package_manager}...")
111
+ system("#{package_manager} install")
112
+ end
113
+
114
+ success = true
115
+ spinner.update_title("Setup complete!")
116
+ rescue StandardError => e
117
+ spinner.update_title("Error: #{e.message}")
118
+ end
119
+
120
+ if success
121
+ CLI::UI.puts(CLI::UI.fmt("{{v}} NextJS template installed successfully in #{full_path}"))
122
+ CLI::UI.puts(CLI::UI.fmt("{{*}} To start: cd \"#{location}\" && #{system} run dev"))
123
+ else
124
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Installation failed"))
125
+ end
126
+ end
127
+
128
+ def self.create_hono(_selection)
129
+ # Ask for a relative path instead
130
+ location = CLI::UI.ask("Where to install? (relative path)", default: "hono-api")
131
+ CLI::UI.puts("Checking prerequisites...")
132
+
133
+ # Check prerequisites
134
+ node_version = begin
135
+ `node -v`.strip
136
+ rescue StandardError
137
+ nil
138
+ end
139
+ unless node_version
140
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Node.js not found. Please install Node.js first"))
141
+ return
142
+ end
143
+
144
+ git_version = begin
145
+ `git --version`.strip
146
+ rescue StandardError
147
+ nil
148
+ end
149
+ unless git_version
150
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Git not found. Please install Git first"))
151
+ return
152
+ end
153
+
154
+ # Check for Deno
155
+ deno_version = begin
156
+ `deno --version`.strip
157
+ rescue StandardError
158
+ nil
159
+ end
160
+ unless deno_version
161
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Deno not found. Please install Deno first"))
162
+ return
163
+ end
164
+
165
+ # Create a path relative to current directory
166
+ full_path = File.expand_path(location, Dir.pwd)
167
+
168
+ # Check if directory already exists
169
+ if Dir.exist?(full_path)
170
+ # If directory exists, check if it's empty
171
+ unless Dir.empty?(full_path) # . and .. entries
172
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Directory not empty: #{full_path}"))
173
+ return
174
+ end
175
+ else
176
+ # Try to create the directory
177
+ begin
178
+ FileUtils.mkdir_p(full_path)
179
+ rescue StandardError => e
180
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Cannot create directory: #{e.message}"))
181
+ return
182
+ end
183
+ end
184
+
185
+ success = false
186
+ CLI::UI::Spinner.spin("Setting up Hono Deno backend template") do |spinner|
187
+ spinner.update_title("Cloning repository...")
188
+
189
+ # Clone directly into the target directory
190
+ clone_cmd = "git clone https://github.com/greeenboi/hono-deno-backend-template.git \"#{full_path}\""
191
+ result = system(clone_cmd)
192
+
193
+ unless result
194
+ # Try alternative approach if direct cloning fails
195
+ spinner.update_title("Direct clone failed, trying alternative...")
196
+
197
+ # Clone to a temporary directory first
198
+ temp_dir = "#{Dir.pwd}/temp_clone_#{Time.now.to_i}"
199
+ FileUtils.mkdir_p(temp_dir)
200
+ result = system("git clone https://github.com/greeenboi/hono-deno-backend-template.git \"#{temp_dir}\"")
201
+
202
+ if result
203
+ # Copy files to destination
204
+ FileUtils.cp_r(Dir.glob("#{temp_dir}/*"), full_path)
205
+ FileUtils.cp_r(Dir.glob("#{temp_dir}/.*").reject { |f| f =~ /\/\.\.?$/ }, full_path)
206
+ FileUtils.rm_rf(temp_dir)
207
+ else
208
+ spinner.update_title("Clone failed")
209
+ next
210
+ end
211
+ end
212
+
213
+ success = true
214
+ spinner.update_title("Setup complete!")
215
+ rescue StandardError => e
216
+ spinner.update_title("Error: #{e.message}")
217
+ end
218
+
219
+ if success
220
+ CLI::UI.puts(CLI::UI.fmt("{{v}} Hono API template installed successfully in #{full_path}"))
221
+ CLI::UI.puts(CLI::UI.fmt("{{*}} To start: cd \"#{location}\" && deno task start"))
222
+ else
223
+ CLI::UI.puts(CLI::UI.fmt("{{x}} Installation failed"))
224
+ end
225
+ end
226
+ end
227
+
228
+ class Search
229
+ SEARCH_ENGINES = {
230
+ "Google" => "https://www.google.com/search?client=opera-gx&q= ",
231
+ "Bing" => "https://www.bing.com/search?q=",
232
+ "DuckDuckGo" => "https://duckduckgo.com/?q="
233
+ }.freeze
234
+
235
+ def self.perform_search
236
+ query = CLI::UI.ask("Enter your search query:", default: "")
237
+ return if query.empty?
238
+
239
+ results = []
240
+ CLI::UI::SpinGroup.new do |spin_group|
241
+ SEARCH_ENGINES.each do |engine, base_url|
242
+ spin_group.add("Searching #{engine}...") do |spinner|
243
+ suffix = case engine
244
+ when "Google"
245
+ "&sourceid=opera&ie=UTF-8&oe=UTF-8"
246
+ when "Bing"
247
+ "&sp=-1&pq=test&sc=6-4&qs=n&sk=&cvid=#{SecureRandom.hex(16)}"
248
+ when "DuckDuckGo"
249
+ "&t=h_&ia=web"
250
+ end
251
+ search_url = "#{base_url}#{URI.encode_www_form_component(query)}#{suffix}"
252
+ links = scrape_links(engine, search_url)
253
+ results << { engine: engine, links: links }
254
+ sleep 1.0 # Simulating search delay
255
+ spinner.update_title("#{engine} search complete!")
256
+ end
257
+ end
258
+ end
259
+
260
+ display_results(results)
261
+ end
262
+
263
+ def self.scrape_links(engine, url)
264
+ headers = {
265
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36",
266
+ "Accept-Language" => "en-US,en;q=0.5"
267
+ }
268
+
269
+ response = HTTParty.get(url, headers: headers)
270
+ puts "Debug: HTTP Status: #{response.code}"
271
+
272
+ doc = Nokogiri::HTML(response.body)
273
+ puts "Debug: Page title: #{doc.title}"
274
+
275
+ results = []
276
+
277
+ case engine
278
+ when "Google"
279
+ doc.css("div.g").each do |result|
280
+ link = result.css(".yuRUbf > a").first
281
+ next unless link
282
+
283
+ title = result.css("h3").text.strip
284
+ url = link["href"]
285
+ result.css(".VwiC3b").text.strip
286
+
287
+ puts "Debug: Found Google result - Title: #{title}"
288
+ results << url if url.start_with?("http")
289
+ end
290
+
291
+ when "Bing"
292
+ doc.css("#b_results li.b_algo").each do |result|
293
+ link = result.css("h2 a").first
294
+ next unless link
295
+
296
+ url = link["href"]
297
+ puts "Debug: Found Bing result - URL: #{url}"
298
+ results << url if url.start_with?("http")
299
+ end
300
+
301
+ else # DuckDuckGo
302
+ doc.css(".result__body").each do |result|
303
+ link = result.css(".result__title a").first
304
+ next unless link
305
+
306
+ url = link["href"]
307
+ puts "Debug: Found DuckDuckGo result - URL: #{url}"
308
+ results << url if url.start_with?("http")
309
+ end
310
+ end
311
+
312
+ puts "Debug: Total results found: #{results.length}"
313
+ results.take(8)
314
+ end
315
+
316
+ def self.display_results(results)
317
+ CLI::UI.frame_style = :bracket
318
+ CLI::UI::Frame.open(CLI::UI.fmt("{{green:Search Results}}")) do
319
+ results.each do |result|
320
+ puts CLI::UI.fmt("{{v}} {{cyan:#{result[:engine]}}}")
321
+ result[:links].each_with_index do |link, idx|
322
+ puts CLI::UI.fmt(" {{*}} ##{idx + 1}: #{link}")
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
328
+
329
+ class TodoList
330
+ def initialize
331
+ @db = setup_database
332
+ end
333
+
334
+ private def setup_database
335
+ db = SQLite3::Database.new("greeenboii_todo.db")
336
+ db.execute <<~SQL
337
+ CREATE TABLE IF NOT EXISTS todos (
338
+ id INTEGER PRIMARY KEY,
339
+ title TEXT NOT NULL,
340
+ completed BOOLEAN DEFAULT 0,
341
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
342
+ )
343
+ SQL
344
+ db
345
+ end
346
+
347
+ def add_task
348
+ title = CLI::UI.ask("Enter task title:")
349
+ return if title.empty?
350
+
351
+ @db.execute("INSERT INTO todos (title) VALUES (?)", [title])
352
+ puts CLI::UI.fmt "{{green:✓}} Task added successfully!"
353
+ end
354
+
355
+ def list_tasks
356
+ tasks = @db.execute("SELECT id, title, completed, created_at FROM todos ORDER BY created_at DESC")
357
+ if tasks.empty?
358
+ puts CLI::UI.fmt "{{yellow:⚠}} No tasks found"
359
+ return
360
+ end
361
+
362
+ ConsoleTable.define(%w[ID Title Status Created]) do |table|
363
+ tasks.each do |id, title, completed, created_at|
364
+ status = completed == 1 ? "{{green:✓}} Done" : "{{red:✗}} Pending"
365
+ table << [id, title, CLI::UI.fmt(status), created_at]
366
+ end
367
+ end
368
+ end
369
+
370
+ def mark_done
371
+ list_tasks
372
+ id = CLI::UI.ask("Enter task ID to mark as done:")
373
+ return if id.empty?
374
+
375
+ @db.execute("UPDATE todos SET completed = 1 WHERE id = ?", [id])
376
+ puts CLI::UI.fmt "{{green:✓}} Task marked as done!"
377
+ end
378
+
379
+ def delete_task
380
+ list_tasks
381
+ id = CLI::UI.ask("Enter task ID to delete:")
382
+ return if id.empty?
383
+
384
+ @db.execute("DELETE FROM todos WHERE id = ?", [id])
385
+ puts CLI::UI.fmt "{{green:✓}} Task deleted!"
386
+ end
387
+
388
+ def update_task
389
+ list_tasks
390
+ id = CLI::UI.ask("Enter task ID to update:")
391
+ return if id.empty?
392
+
393
+ title = CLI::UI.ask("Enter new title:")
394
+ return if title.empty?
395
+
396
+ @db.execute("UPDATE todos SET title = ? WHERE id = ?", [title, id])
397
+ puts CLI::UI.fmt "{{green:✓}} Task updated!"
398
+ end
399
+
400
+ def show_menu
401
+ CLI::UI::Frame.divider("{{v}} Todo List")
402
+ loop do
403
+ CLI::UI::Prompt.ask("Todo List Options:") do |handler|
404
+ handler.option("List Tasks") { list_tasks }
405
+ handler.option("Add Task") { add_task }
406
+ handler.option("Mark Done") { mark_done }
407
+ handler.option("Update Task") { update_task }
408
+ handler.option("Delete Task") { delete_task }
409
+ handler.option("Exit") { return }
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ class Options
416
+ def self.show_options
417
+ CLI::UI::Prompt.instructions_color = CLI::UI::Color::GRAY
418
+
419
+ CLI::UI::Prompt.ask("Choose an option:") do |handler|
420
+ handler.option("{{gray:Search Files}}") { |selection| puts "Placeholder, Replaced soon. #{selection}" }
421
+ handler.option("{{gray:Search Directory}}") { |selection| puts "Placeholder, Replaced soon. #{selection}" }
422
+ handler.option("{{green:Website Builder}}") { |_selection| WebsiteBuilder.build_website }
423
+ handler.option("{{yellow:Todo List}}") { |_selection| TodoList.new.show_menu }
424
+ handler.option("{{cyan:Search Engine}}") { |_selection| Search.perform_search }
425
+ handler.option("{{red:Exit}}") { |_selection| exit }
426
+ end
427
+ end
428
+ end
429
+
430
+ class Main
431
+ CLI::UI::StdoutRouter.enable
432
+ current_time = DateTime.now.strftime("%d-%m-%Y %H:%M:%S")
433
+ CLI::UI::Frame.open("{{v}} Greeenboi : #{current_time}") do
434
+ puts "Welcome to Greeenboii"
435
+ puts "Lets do some magic!"
436
+ CLI::UI.frame_style = :bracket
437
+ CLI::UI::Frame.open(CLI::UI.fmt("{{green:Welcome to Greeenboii CLI}}")) do
438
+ puts CLI::UI.fmt("{{cyan:Version}}: #{Greeenboii::VERSION}")
439
+ # ConsoleTable.define(%w[Name Version]) do |table|
440
+ # table << ["Greeenboii", Greeenboii::VERSION]
441
+ # end
442
+ puts CLI::UI.fmt("{{yellow:Type 'help' to see available commands}}")
443
+ Options.show_options
444
+ end
445
+ end
446
+ end
447
+ end