rubish-gem 0.0.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +23 -0
  3. data/Dockerfile +54 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +39 -0
  6. data/Rakefile +12 -0
  7. data/lib/rubish/arithmetic.rb +140 -0
  8. data/lib/rubish/ast.rb +168 -0
  9. data/lib/rubish/builtins/arithmetic.rb +129 -0
  10. data/lib/rubish/builtins/bind_readline.rb +834 -0
  11. data/lib/rubish/builtins/directory_stack.rb +182 -0
  12. data/lib/rubish/builtins/echo_printf.rb +510 -0
  13. data/lib/rubish/builtins/hash_directories.rb +260 -0
  14. data/lib/rubish/builtins/read.rb +299 -0
  15. data/lib/rubish/builtins/trap.rb +324 -0
  16. data/lib/rubish/codegen.rb +1273 -0
  17. data/lib/rubish/completion.rb +840 -0
  18. data/lib/rubish/completions/bash_helpers.rb +530 -0
  19. data/lib/rubish/completions/git.rb +431 -0
  20. data/lib/rubish/completions/help_parser.rb +453 -0
  21. data/lib/rubish/completions/ssh.rb +114 -0
  22. data/lib/rubish/config.rb +267 -0
  23. data/lib/rubish/data/builtin_help.rb +716 -0
  24. data/lib/rubish/data/completion_data.rb +53 -0
  25. data/lib/rubish/data/readline_config.rb +47 -0
  26. data/lib/rubish/data/shell_options.rb +251 -0
  27. data/lib/rubish/data_define.rb +65 -0
  28. data/lib/rubish/execution_context.rb +1124 -0
  29. data/lib/rubish/expansion.rb +988 -0
  30. data/lib/rubish/history.rb +663 -0
  31. data/lib/rubish/lazy_loader.rb +127 -0
  32. data/lib/rubish/lexer.rb +1194 -0
  33. data/lib/rubish/parser.rb +1167 -0
  34. data/lib/rubish/prompt.rb +766 -0
  35. data/lib/rubish/repl.rb +2267 -0
  36. data/lib/rubish/runtime/builtins.rb +7222 -0
  37. data/lib/rubish/runtime/command.rb +1153 -0
  38. data/lib/rubish/runtime/job.rb +153 -0
  39. data/lib/rubish/runtime.rb +1169 -0
  40. data/lib/rubish/shell_state.rb +241 -0
  41. data/lib/rubish/startup_profiler.rb +67 -0
  42. data/lib/rubish/version.rb +5 -0
  43. data/lib/rubish.rb +60 -0
  44. data/sig/rubish.rbs +4 -0
  45. metadata +85 -0
@@ -0,0 +1,431 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubish
4
+ module Builtins
5
+ # ==========================================================================
6
+ # Git completion function
7
+ # ==========================================================================
8
+
9
+ def _git_completion(cmd, cur, prev)
10
+ words = @comp_words
11
+ cword = @comp_cword
12
+
13
+ # Determine git subcommand
14
+ subcommand = nil
15
+ subcommand_idx = nil
16
+ words.each_with_index do |word, idx|
17
+ next if idx == 0 # Skip 'git'
18
+ next if word.start_with?('-') # Skip options
19
+
20
+ # Found subcommand
21
+ subcommand = word
22
+ subcommand_idx = idx
23
+ break
24
+ end
25
+
26
+ if subcommand.nil? || cword <= (subcommand_idx || 1)
27
+ # Complete git subcommands or top-level options
28
+ if cur.start_with?('-')
29
+ @compreply = GIT_COMMON_OPTIONS.select { |opt| opt.start_with?(cur) }
30
+ else
31
+ @compreply = GIT_COMMANDS.select { |c| c.start_with?(cur) }
32
+ end
33
+ return
34
+ end
35
+
36
+ # Complete based on subcommand
37
+ case subcommand
38
+ when 'add'
39
+ _git_complete_add(cur, prev)
40
+ when 'branch'
41
+ _git_complete_branch(cur, prev)
42
+ when 'checkout', 'switch'
43
+ _git_complete_checkout(cur, prev)
44
+ when 'commit'
45
+ _git_complete_commit(cur, prev)
46
+ when 'diff'
47
+ _git_complete_diff(cur, prev)
48
+ when 'fetch', 'pull', 'push'
49
+ _git_complete_remote_branch(cur, prev, subcommand)
50
+ when 'log', 'show'
51
+ _git_complete_log(cur, prev)
52
+ when 'merge', 'rebase'
53
+ _git_complete_refs(cur)
54
+ when 'remote'
55
+ _git_complete_remote(cur, prev, words, cword, subcommand_idx)
56
+ when 'reset'
57
+ _git_complete_reset(cur, prev)
58
+ when 'revert'
59
+ _git_complete_refs(cur)
60
+ when 'stash'
61
+ _git_complete_stash(cur, prev, words, cword, subcommand_idx)
62
+ when 'tag'
63
+ _git_complete_tag(cur, prev)
64
+ else
65
+ # Default: complete files and refs
66
+ _git_complete_refs(cur)
67
+ _git_complete_files(cur)
68
+ end
69
+ end
70
+
71
+ def _git_complete_add(cur, prev)
72
+ case prev
73
+ when '-p', '--patch'
74
+ _git_complete_files(cur)
75
+ else
76
+ if cur.start_with?('-')
77
+ opts = %w[-n --dry-run -v --verbose -i --interactive -p --patch
78
+ -e --edit -f --force -u --update -A --all --no-ignore-removal
79
+ --no-all --ignore-removal -N --intent-to-add --refresh
80
+ --ignore-errors --ignore-missing --sparse --pathspec-from-file=
81
+ --pathspec-file-nul --renormalize --chmod=+x --chmod=-x]
82
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
83
+ else
84
+ _git_complete_files(cur)
85
+ end
86
+ end
87
+ end
88
+
89
+ def _git_complete_branch(cur, prev)
90
+ case prev
91
+ when '-d', '-D', '--delete', '-m', '-M', '--move', '-c', '-C', '--copy'
92
+ _git_complete_local_branches(cur)
93
+ when '-u', '--set-upstream-to'
94
+ _git_complete_remote_refs(cur)
95
+ when '-t', '--track'
96
+ _git_complete_remote_refs(cur)
97
+ else
98
+ if cur.start_with?('-')
99
+ opts = %w[-a --all -d -D --delete -f --force -i --ignore-case -l --list
100
+ -m -M --move -c -C --copy -r --remotes --show-current -v --verbose
101
+ -q --quiet --track -t --no-track --set-upstream-to -u --unset-upstream
102
+ --edit-description --contains --no-contains --merged --no-merged
103
+ --column --no-column --sort= --points-at --format= --color --no-color]
104
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
105
+ else
106
+ _git_complete_local_branches(cur)
107
+ end
108
+ end
109
+ end
110
+
111
+ def _git_complete_checkout(cur, prev)
112
+ case prev
113
+ when '-b', '-B', '--orphan'
114
+ # New branch name - don't complete
115
+ @compreply = []
116
+ when '--'
117
+ _git_complete_files(cur)
118
+ else
119
+ if cur.start_with?('-')
120
+ opts = %w[-q --quiet -f --force -b -B --detach --ours --theirs
121
+ -m --merge -l --track -t --no-track --orphan --ignore-other-worktrees
122
+ --recurse-submodules --no-recurse-submodules --progress --no-progress
123
+ --overlay --no-overlay --pathspec-from-file= --pathspec-file-nul]
124
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
125
+ else
126
+ _git_complete_refs(cur)
127
+ end
128
+ end
129
+ end
130
+
131
+ def _git_complete_commit(cur, prev)
132
+ case prev
133
+ when '-C', '-c', '--reuse-message', '--reedit-message', '--fixup', '--squash'
134
+ _git_complete_refs(cur)
135
+ when '-m', '--message'
136
+ @compreply = [] # Message string
137
+ when '-F', '--file', '--pathspec-from-file'
138
+ _git_complete_files(cur)
139
+ when '--author'
140
+ @compreply = [] # Author string
141
+ when '--date'
142
+ @compreply = [] # Date string
143
+ when '--cleanup'
144
+ @compreply = %w[strip whitespace verbatim scissors default].select { |opt| opt.start_with?(cur) }
145
+ else
146
+ if cur.start_with?('-')
147
+ opts = %w[-a --all -p --patch --reset-author -s --signoff -n --no-verify
148
+ -v --verbose -u --untracked-files -q --quiet --dry-run
149
+ --short --branch --porcelain --long -z --null --status
150
+ --no-status -F --file -m --message --author --date
151
+ -C --reuse-message -c --reedit-message --fixup --squash
152
+ --amend --no-edit -e --edit --cleanup= --trailer
153
+ --only -i --include --allow-empty --allow-empty-message]
154
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
155
+ else
156
+ _git_complete_files(cur)
157
+ end
158
+ end
159
+ end
160
+
161
+ def _git_complete_diff(cur, prev)
162
+ if cur.start_with?('-')
163
+ opts = %w[-p -u --patch -U --unified= --raw --patch-with-raw --stat
164
+ --numstat --shortstat --dirstat --summary --patch-with-stat
165
+ -z --name-only --name-status --color --no-color --color-moved
166
+ --word-diff --color-words --no-renames --check --full-index
167
+ --binary -a --text -R --ignore-space-change -w
168
+ --ignore-all-space -b --ignore-blank-lines --inter-hunk-context=
169
+ --patience --histogram --diff-algorithm= --anchored=
170
+ --minimal --no-index --cached --staged -S -G --pickaxe-regex]
171
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
172
+ else
173
+ _git_complete_refs(cur)
174
+ _git_complete_files(cur)
175
+ end
176
+ end
177
+
178
+ def _git_complete_remote_branch(cur, prev, subcommand)
179
+ if cur.start_with?('-')
180
+ case subcommand
181
+ when 'fetch'
182
+ opts = %w[-q --quiet -v --verbose --all -a --append --depth= --deepen=
183
+ --shallow-since= --shallow-exclude= --unshallow --update-shallow
184
+ --dry-run -f --force -k --keep -p --prune -n --no-tags -t --tags
185
+ --refmap= -u --update-head-ok --progress --no-progress -j --jobs=
186
+ --prefetch --set-upstream -o --server-option= --upload-pack]
187
+ when 'pull'
188
+ opts = %w[-q --quiet -v --verbose --rebase -r --no-rebase --ff-only
189
+ --no-ff --ff --no-commit --commit --no-stat --stat
190
+ --no-signoff --signoff --no-log --log --squash --no-squash
191
+ --strategy= -X --strategy-option= --depth= -s --strategy=
192
+ --allow-unrelated-histories --autostash --no-autostash]
193
+ when 'push'
194
+ opts = %w[-q --quiet -v --verbose --all --mirror --tags --follow-tags
195
+ -n --dry-run --porcelain --delete --prune -u --set-upstream
196
+ --thin --no-thin --force -f --force-with-lease --repo
197
+ --no-verify --progress --signed= --push-option= --atomic
198
+ -d --delete --receive-pack= --exec= -o --push-option=]
199
+ else
200
+ opts = []
201
+ end
202
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
203
+ else
204
+ _git_complete_remotes(cur)
205
+ _git_complete_refs(cur)
206
+ end
207
+ end
208
+
209
+ def _git_complete_log(cur, prev)
210
+ if cur.start_with?('-')
211
+ opts = %w[--follow -p --patch --stat --shortstat --numstat --summary
212
+ --name-only --name-status --pretty= --format= --abbrev-commit
213
+ --oneline --graph --decorate --no-decorate --all --branches
214
+ --remotes --tags --source --merges --no-merges --first-parent
215
+ --author= --committer= --grep= --all-match --invert-grep
216
+ --regexp-ignore-case --since= --after= --until= --before=
217
+ --ancestry-path --cherry-pick --left-right --reverse
218
+ -n --max-count= --skip= -S -G --pickaxe-regex --walk-reflogs
219
+ --merge --boundary --simplify-merges --date= --date-order
220
+ --author-date-order --topo-order --full-history]
221
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
222
+ else
223
+ _git_complete_refs(cur)
224
+ _git_complete_files(cur)
225
+ end
226
+ end
227
+
228
+ def _git_complete_remote(cur, prev, words, cword, subcommand_idx)
229
+ # Determine remote subcommand
230
+ remote_subcmd = nil
231
+ words[(subcommand_idx + 1)..].each do |word|
232
+ next if word.start_with?('-')
233
+ next if word.empty? # Skip empty words
234
+ remote_subcmd = word
235
+ break
236
+ end
237
+
238
+ if remote_subcmd.nil? || remote_subcmd.empty?
239
+ # Complete remote subcommands
240
+ remote_cmds = %w[add rename remove rm show prune update get-url set-url set-head set-branches]
241
+ @compreply = remote_cmds.select { |c| c.start_with?(cur) }
242
+ return
243
+ end
244
+
245
+ case remote_subcmd
246
+ when 'add'
247
+ if cur.start_with?('-')
248
+ opts = %w[-t --track -m --master -f --fetch --tags --no-tags --mirror=]
249
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
250
+ end
251
+ when 'rename', 'remove', 'rm', 'show', 'prune', 'get-url', 'set-url', 'set-head', 'set-branches'
252
+ _git_complete_remotes(cur)
253
+ when 'update'
254
+ if cur.start_with?('-')
255
+ @compreply = %w[-p --prune].select { |opt| opt.start_with?(cur) }
256
+ else
257
+ # Complete remote groups or remotes
258
+ _git_complete_remotes(cur)
259
+ end
260
+ end
261
+ end
262
+
263
+ def _git_complete_reset(cur, prev)
264
+ if cur.start_with?('-')
265
+ opts = %w[-q --quiet --soft --mixed --hard --merge --keep -p --patch -N
266
+ --intent-to-add --pathspec-from-file= --pathspec-file-nul]
267
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
268
+ else
269
+ _git_complete_refs(cur)
270
+ _git_complete_files(cur)
271
+ end
272
+ end
273
+
274
+ def _git_complete_stash(cur, prev, words, cword, subcommand_idx)
275
+ # Determine stash subcommand
276
+ stash_subcmd = nil
277
+ words[(subcommand_idx + 1)..].each do |word|
278
+ next if word.start_with?('-')
279
+ next if word.empty? # Skip empty words
280
+ stash_subcmd = word
281
+ break
282
+ end
283
+
284
+ if stash_subcmd.nil? || stash_subcmd.empty?
285
+ stash_cmds = %w[list show drop pop apply branch push save clear create store]
286
+ @compreply = stash_cmds.select { |c| c.start_with?(cur) }
287
+ return
288
+ end
289
+
290
+ case stash_subcmd
291
+ when 'show', 'drop', 'pop', 'apply', 'branch'
292
+ _git_complete_stash_refs(cur)
293
+ when 'push', 'save'
294
+ if cur.start_with?('-')
295
+ opts = %w[-p --patch -k --keep-index --no-keep-index -q --quiet
296
+ -u --include-untracked -a --all -m --message --pathspec-from-file=]
297
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
298
+ else
299
+ _git_complete_files(cur)
300
+ end
301
+ end
302
+ end
303
+
304
+ def _git_complete_tag(cur, prev)
305
+ case prev
306
+ when '-m', '--message', '-F', '--file'
307
+ @compreply = [] # Message or file
308
+ when '-u', '--local-user'
309
+ @compreply = [] # GPG key
310
+ else
311
+ if cur.start_with?('-')
312
+ opts = %w[-a --annotate -s --sign -u --local-user -f --force -d --delete
313
+ -v --verify -n --n= -l --list --sort= --contains --no-contains
314
+ --merged --no-merged --points-at --format= --color --no-color
315
+ -i --ignore-case -m --message -F --file --cleanup=]
316
+ @compreply = opts.select { |opt| opt.start_with?(cur) }
317
+ else
318
+ _git_complete_tags(cur)
319
+ end
320
+ end
321
+ end
322
+
323
+ # Helper methods for git completion
324
+
325
+ def _git_complete_refs(cur)
326
+ # Complete git refs (branches, tags, commits)
327
+ @compreply ||= []
328
+ return unless git_repo?
329
+
330
+ begin
331
+ # Get all refs
332
+ refs = `git for-each-ref --format='%(refname:short)' 2>/dev/null`.split("\n")
333
+ refs.concat(`git rev-parse --symbolic --branches --tags --remotes 2>/dev/null`.split("\n"))
334
+ refs.uniq!
335
+ @compreply.concat(refs.select { |r| r.start_with?(cur) })
336
+ rescue
337
+ # Git command failed
338
+ end
339
+ end
340
+
341
+ def _git_complete_local_branches(cur)
342
+ @compreply = []
343
+ return unless git_repo?
344
+
345
+ begin
346
+ branches = `git for-each-ref --format='%(refname:short)' refs/heads/ 2>/dev/null`.split("\n")
347
+ @compreply = branches.select { |b| b.start_with?(cur) }
348
+ rescue
349
+ # Git command failed
350
+ end
351
+ end
352
+
353
+ def _git_complete_remote_refs(cur)
354
+ @compreply = []
355
+ return unless git_repo?
356
+
357
+ begin
358
+ refs = `git for-each-ref --format='%(refname:short)' refs/remotes/ 2>/dev/null`.split("\n")
359
+ @compreply = refs.select { |r| r.start_with?(cur) }
360
+ rescue
361
+ # Git command failed
362
+ end
363
+ end
364
+
365
+ def _git_complete_remotes(cur)
366
+ @compreply ||= []
367
+ return unless git_repo?
368
+
369
+ begin
370
+ remotes = `git remote 2>/dev/null`.split("\n")
371
+ @compreply.concat(remotes.select { |r| r.start_with?(cur) })
372
+ rescue
373
+ # Git command failed
374
+ end
375
+ end
376
+
377
+ def _git_complete_tags(cur)
378
+ @compreply ||= []
379
+ return unless git_repo?
380
+
381
+ begin
382
+ tags = `git tag -l 2>/dev/null`.split("\n")
383
+ @compreply.concat(tags.select { |t| t.start_with?(cur) })
384
+ rescue
385
+ # Git command failed
386
+ end
387
+ end
388
+
389
+ def _git_complete_stash_refs(cur)
390
+ @compreply = []
391
+ return unless git_repo?
392
+
393
+ begin
394
+ stashes = `git stash list 2>/dev/null`.each_line.map { |l| l.split(':').first }.compact
395
+ @compreply = stashes.select { |s| s.start_with?(cur) }
396
+ rescue
397
+ # Git command failed
398
+ end
399
+ end
400
+
401
+ def _git_complete_files(cur)
402
+ @compreply ||= []
403
+
404
+ # Get modified/untracked files for git commands
405
+ if git_repo?
406
+ begin
407
+ # Modified files
408
+ modified = `git diff --name-only 2>/dev/null`.split("\n")
409
+ # Staged files
410
+ staged = `git diff --cached --name-only 2>/dev/null`.split("\n")
411
+ # Untracked files
412
+ untracked = `git ls-files --others --exclude-standard 2>/dev/null`.split("\n")
413
+
414
+ files = (modified + staged + untracked).uniq
415
+ @compreply.concat(files.select { |f| f.start_with?(cur) })
416
+ rescue
417
+ # Git command failed, fall back to regular file completion
418
+ end
419
+ end
420
+
421
+ # Also complete regular files
422
+ pattern = cur.empty? ? '*' : "#{cur}*"
423
+ @compreply.concat(Dir.glob(pattern).select { |f| File.file?(f) || File.directory?(f) })
424
+ @compreply.uniq!
425
+ end
426
+
427
+ def git_repo?
428
+ system('git rev-parse --git-dir >/dev/null 2>&1')
429
+ end
430
+ end
431
+ end