Skip to content

Commit

Permalink
script
Browse files Browse the repository at this point in the history
  • Loading branch information
BostX committed Dec 10, 2023
1 parent 4d39c8e commit 23c3458
Show file tree
Hide file tree
Showing 5 changed files with 461 additions and 196 deletions.
39 changes: 24 additions & 15 deletions script/script.fish → script/cleanup-checkout-cache.fish
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

# From
# https://guix.gnu.org/manual/devel/en/guix.html#index-Git_002c-using-the-latest-commit
# "Checkouts are kept in a cache under ~/.cache/guix/checkouts to speed up
# consecutive accesses to the same repository. You may want to clean it up once
# in a while to save disk space."
#
# Checkouts are kept in a cache under ~/.cache/guix/checkouts to speed up
# consecutive accesses to the same repository. You may want to clean it up once
# in a while to save disk space.
# This script prints out a list of shell script commands (active of commented
# out), and remarks which can be executed of further refined.

# The path must not be surrounded by double quotes, i.e. it must NOT be a
# string.
set checkoutCache ~/.cache/guix/checkouts

# regular expression for matching an URL
Expand All @@ -21,11 +25,13 @@ set cacheDirs (find $checkoutCache -mindepth 1 -maxdepth 1 -type d)
set cacheNonDirs (find $checkoutCache -mindepth 1 -maxdepth 1 -not -type d)
# printf "[DEBUG] cacheNonDirs:\n$cacheNonDirs\n"

set allChannelRepos (guix system describe \
| grep repository | grep --only-matching $regexp)
set allChannelRepos (guix system describe | \\
grep repository | grep --only-matching "$regexp")
# printf "[DEBUG] allChannelRepos:\n$allChannelRepos\n"

# define empty variables which will serve as arrays
# Define empty variables which will serve as arrays. Can't use:
# set <variable> "" # some comment
# since that creates a variable containing an empty string.
set keepCheckouts # local clones of git repositories actively used & cached
set keepURLs # fetch-URLs of these clones
set removeCheckouts # inactive clones ...
Expand All @@ -36,8 +42,8 @@ for checkout in $cacheDirs
# printf "[DEBUG] checkout: %s\n" $checkout
set checkoutGit $checkout/.git
if test -d $checkoutGit # is ... a directory?
set checkoutURL (git --git-dir=$checkoutGit remote --verbose \
| grep fetch | grep --only-matching $regexp)
set checkoutURL (git --git-dir=$checkoutGit remote --verbose | \
grep fetch | grep --only-matching "$regexp")
# printf "[DEBUG] checkoutURL: %s\n" $checkoutURL
if contains $checkoutURL $allChannelRepos
set --append keepCheckouts $checkout
Expand All @@ -51,10 +57,13 @@ for checkout in $cacheDirs
end
end

# `test -n "$foo"`: is foo a string with non-zero length, i.e. is it defined?
# test -n "$<variable>":
# $<variable> surrounded by double quotes gets converted to a string. By
# testing if such a string has a non-zero length we ask if the $<variable> is
# a void value?

if test -n "$removeCheckouts"
printf "\n# Cached checkouts to delete:\n"
printf "\n### Cached checkouts to delete:\n"
# The array size of $removeCheckouts and $removeURLs are the same
for i in (seq (count $removeCheckouts))
# printf "%s # %s\n" $removeCheckouts[$i] $removeURLs[$i]
Expand All @@ -66,19 +75,19 @@ if test -n "$removeCheckouts"
end

if test -n "$keepCheckouts"
printf "\n# Cached checkouts to keep:\n"
printf "\n### Cached checkouts to keep:\n"
# The array size of $keepCheckouts and $keepURLs are the same
for i in (seq (count $keepCheckouts))
printf "%s # %s\n" $keepCheckouts[$i] $keepURLs[$i]
printf "# %s # %s\n" $keepCheckouts[$i] $keepURLs[$i]
end
end

if test -n "$nonGitRepos" || test -n "$cacheNonDirs"
printf "\n# Other items in the checkout cache directory:\n"
printf "\n### Other items in the checkout cache directory:\n"
for checkout in $nonGitRepos
printf "%s\n" $checkout
printf "# %s\n" $checkout
end
for ndir in $cacheNonDirs
printf "%s\n" $ndir
printf "# %s\n" $ndir
end
end
27 changes: 15 additions & 12 deletions script/cleanup-checkout-cache.scm
Original file line number Diff line number Diff line change
Expand Up @@ -95,30 +95,33 @@ containing its output."
(set! nonGitRepos (append nonGitRepos (list checkout))))))
cacheDirs)

(for-each
(lambda (i)
(let ((removeCheckoutsI (list-ref removeCheckouts i))
(removeURLsI (list-ref removeURLs i)))
(format #t "mv ~a{,.deleteMe} # ~a\n" removeCheckoutsI removeURLsI)))
;; `(iota N)` returns a list of numbers (0 ... N-1)
(iota (length removeCheckouts)))
(if (> (length removeCheckouts) 0)
(begin
(format #t "\n### Cached checkouts to delete:\n")
(for-each
(lambda (i)
(let ((removeCheckoutsI (list-ref removeCheckouts i))
(removeURLsI (list-ref removeURLs i)))
(format #t "mv ~a{,.deleteMe} # ~a\n" removeCheckoutsI removeURLsI)))
;; `(iota N)` returns a list of numbers (0 ... N-1)
(iota (length removeCheckouts)))))

(if (> (length keepCheckouts) 0)
(begin
(format #t "\n# Cached checkouts to keep:\n")
(format #t "\n### Cached checkouts to keep:\n")
(for-each
(lambda (i)
(let ((keepCheckoutsI (list-ref keepCheckouts i))
(keepURLsI (list-ref keepURLs i)))
(format #t "~a # ~a\n" keepCheckoutsI keepURLsI)))
(format #t "# ~a # ~a\n" keepCheckoutsI keepURLsI)))
;; `(iota N)` returns a list of numbers (0 ... N-1)
(iota (length keepCheckouts)))))

(if (or (> (length nonGitRepos) 0)
(> (length cacheNonDirs) 0))
(begin
(format #t "\n# Other items in the checkout cache directory:\n")
(for-each (lambda (checkout) (format #t "~a\n" checkout))
(format #t "\n### Other items in the checkout cache directory:\n")
(for-each (lambda (checkout) (format #t "# ~a\n" checkout))
nonGitRepos)
(for-each (lambda (ndir) (format #t "~a\n" ndir))
(for-each (lambda (ndir) (format #t "# ~a\n" ndir))
cacheNonDirs)))
221 changes: 221 additions & 0 deletions script/part-2.markdown.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
#+TITLE: Iterative, real-world script development, Part 2.
#+AUTHOR: Rostislav Svoboda
#+DATE: {{{time(%B %d\, %Y)}}}
#+OPTIONS: toc:nil
# #+LaTeX_CLASS: article
# #+LaTeX_CLASS_OPTIONS: [a4paper, 11pt]

#+BEGIN_EXPORT html
<style>
blockquote {
font-style: italic;
}
</style>
#+END_EXPORT

Let's convert the script from Fish shell to Guile Scheme in a straight manner,
as if we were writing basically putting parenthesis around the fish-script
statements and expressions.

final script is below results are below

Also create a download link for the python code used for conversion.

Is it possible to edit the python code used for code generation?

Plugins

Fire up your ChatGPT (Model Plugins Beta gpt4, CoderPad plugin) alter-ego and...

* Upload the `cleanup-checkout-cache.fish` and enter the prompt:
#+BEGIN_SRC markdown :exports code
Please analyze the Fish-shell script I'm sending you in the attachment and
convert it to Guile Scheme according to the rules and guidelines I'm going to
send you in the next step.

We will proceed with the conversion step-by-step.

After the step-by-step conversion is done, please combine all the steps
together, convert the whole Fish script and provide me a download link to the
resulting Guile Scheme script file.
#+END_SRC

... Make sure at this point that your alter-ego ChatGPT understands the
uploaded Fish-script.

* Enter the prompt:
#+BEGIN_SRC markdown :exports code
The conversion rules and guidelines are:

- The conversion result consists of the following blocks:
```
[Guile Scheme hashbang]
[Docstring]
[Some Guile Scheme snippet or snippets]
[Converted code]
```

- The generated Guile code must follow the same logic and have the same
structure as the origin.

- Keep the generated Guile code simple and well readable. Avoid nesting
s-expressions if nesting is not present in the origin. Don't do any
"smart" conversion.

- Make sure all strings are properly surrounded with double quotes.

- Make sure all generated s-expressions are syntactically valid!

- Conversion of regular expressions:
+ Escape only the backslashes by doubling them (`\` → `\\`). Do not
escape other characters.
+ Keep the structure of the regular expression unchanged.

- Convert Fish-shell hashbang:
```
#!/usr/bin/env fish
```
to Guile Scheme hashbang:
```
#!/usr/bin/env guile
!#
```

- After the docstring append this Guile Scheme snippet:
```scheme
(use-modules
;; provides read-line
(ice-9 rdelim)
;; provides open-input-pipe
(ice-9 popen))

(define (read-all-strings port)
"Return a list of all lines of text from the PORT.
Returns a list of strings."
(let loop ((res '())
(str (read-line port))) ; read-line from (ice-9 rdelim)
(if (and str (not (eof-object? str)))
(loop (append res (list str))
(read-line port))
res)))

(define (exec command-string)
"Run the shell COMMAND using ‘/bin/sh -c’ with ‘OPEN_READ’ mode, ie. to read
from the subprocess. Wait for the command to terminate and return a list of strings
containing its output."
;; (format #t "[DEBUG] command-string: ~a\n" command-string)
(let* ((port (open-input-pipe command-string)) ; open-input-pipe from (ice-9 popen)
;; `read-all-strings' must be called before `close-pipe'.
(results (read-all-strings port)))
(close-pipe port) ;; the return code of the command execution is ignored
results))
```

- If there are any Fish-shell commands commented out, convert them too.

- Whenever applicable prefer constructing strings using `(format ...)` instead
of using `string-append` in the conversion result.

- `exec` returns a list of strings, so for better readability of the DEBUG
outputs use `string-join`. E.g. convert:
```fish
# printf "[DEBUG] <variable>:\n$<variable>\n"
```
to:
```scheme
;; (format #t "[DEBUG] <variable>:\n~a\n" (string-join <variable> "\n"))
```

- Simple conversion rules:
| Fish shell code | Guile scheme code |
|--------------------------------------------------------------------------------------------|
| `set <variable> <value>` | `(define <variable> <value>)` |
| `set --append <variable> <value>` | `(set! <variable> (append <variable> (list <value>)))` |
| `$<variable>[$i]` | `(list-ref <variable> i)` |
| `test -n "$<variable>"` | `(> (length <variable>) 0)` |
| `test -d $<variable>` | `(access? <variable> F_OK)` |

- Conversion of an empty Fish variable:
| Fish shell code | Guile scheme code |
|--------------------------------------------------------------------------------------------|
| `set <variable> # ...` | `(define <variable> (list)) ; ...` |

- Fish-shell uses parentheses for operating system command substitution:
| Fish shell code | Guile scheme code |
|--------------------------------------------------------------------------------------------|
| `set <variable> (<OS-command>)` | `(define <variable> (exec "<OS-command>"))` |

The `exec` is a Guile Scheme function accepting some command-string as a
parameter. Here:

+ The `"<OS-command>"` in the conversion result is built by printing to
string:
```scheme
(format #f <guile-format-string> <variable0> ... <variableN>)`
```
+ Make sure the regexp used for `grep` will be surrounded by properly
escaped double quotes in the conversion result. E.g.: `grep ... \"~a\"`

- `(define <variable> <value>)` cannot be used inside `(begin ...)`. Use
`(let* ((<variable> <value>)) ...)` instead.

- Conversion of the loops. Make sure you understand the specific actions
performed within the loops and how other commands should be translated.
Then:
```fish
for <index-variable> in <variable>
...
end
```
corresponds to:
```scheme
(for-each
(lambda (<index-variable>)
...)
<variable>)
```
#+END_SRC

... and keep on iterating the prompt again and again... and again and
again... and again and again because even if our ChatGPT alter-ego is rather
smart, it:
- sometimes forgets what exactly the Fish shell scripts contains
- escapes other than the backslashes, despite of being explicitly instructed not to do so.
- occasionally freezes
- can correct its mistakes but a couple of steps later starts to make the same mistakes again.
- sometimes freezes at the last step, when the resulting Guile Scheme file.
- etc.


* Summary / Final thoughts

Using ChatGPT is like trying to learn how to teach using the trial-and-error method.

You can easily spend 10+ hours on improving your script conversion prompt and
never get the consistency of good results.

As of now. (December 2023)
It's a learning process - how to use ChatGPT and how to craft prompts.

Sometimes abandoning current dialogue are restarting the whole conversion from
the scratch yields better results. When you can't get out of the valley of
the local optimization minimum [TODO link to a picture]

It's like building up a sandcastle too close to the sea.
carefully crafting

Since ChatGPT uses Python for text conversion a better idea might be to write
the cleanup-script in Python and then try to convert it to Guile Scheme.

need to try out the upcoming Gemini.

Maybe

Here is the Guile Scheme script anyway. (Compare with its Fish version)

Warnings - read and understand the code before you run it.

Decrease the variation parameter.

See also
https://www.zdnet.com/article/i-used-chatgpt-to-write-the-same-routine-in-12-top-programming-languages-heres-how-it-did/
Loading

0 comments on commit 23c3458

Please sign in to comment.