├── LICENSE
├── README.md
├── clipboard.rkt
├── commons.rkt
├── csv-import-example.rkt
├── csv-import.rkt
├── dialogs.rkt
├── gui.rkt
├── hash.rkt
├── io.rkt
├── menu-bar.rkt
├── sql.rkt
├── system.rkt
└── xml.rkt
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dexter Santucci
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Racket Commons
2 |
3 | A collection of useful procedures divided into modules for Racket.
4 |
5 | ## Version history
6 |
7 | - v1.0.24 - added clean-var, inspect macro
8 | - v1.0.23 - added sort-filepaths-by-inc-size amd formatted-file-size to common module
9 | - v1.0.22 - improved group procedure safety in common module
10 | - v1.0.21 - updated get-latest-version
11 | - v1.0.20 - sorted provide in common module
12 | - v1.0.19 - added get-current-executable-path
13 | - v1.0.18 - added non-empty-or-none-string?
14 | - v1.0.17 - added get-latest-version-number
15 | - v1.0.16 - added non-empty-list-of-numbers?
16 | - v1.0.15 - added media-file?.
17 | - v1.0.14 - added list! and maybe-hash
18 | - v1.0.13 - updated transpose and improved unit test coverage.
19 | - v1.0.12 - added remove-indexed-items.
20 | - v1.0.11 - added system->ports.
21 | - v1.0.10 - added string->list-of-numbers.
22 | - v1.0.9 - added dies.
23 | - v1.0.8 - added string-contains-one-of?.
24 | - v1.0.7 - added str-list-contains.
25 | - v1.0.6 - added strip-newlines-returns.
26 | - v1.0.5 - added string-replace3.
27 | - v1.0.5 - added auto-quote.
28 | - v1.0.4 - added take-up-to and group (cuts a list into chunks).
29 | - v1.0.3 - added remove-non-alphanumeric-or-underscore.
30 | - v1.0.2 - added string-chop.
31 | - v1.0.1 - added non-empty-list-of-strings?.
32 | - v1.0 - initial release.
33 |
34 | ## Commons procedures
35 |
36 |
37 | (provide all-but-last ; (all-but-last l)
38 | auto-quote ; (auto-quote str)
39 | clean-var-name ; (clean-var-name var)
40 | chop ; (chop l n)
41 | combine-with ; (combine-with f l1 l2)
42 | comp_ ; (comp_ stx) [MACRO]
43 | curry-ref ; (curry-ref ... ) [MACRO]
44 | currym ; (currym func param1 param3)
45 | define-command-line-params ; (define-command-line-params ... ) [MACRO]
46 | dies ; (dies s1 s2 ...)
47 | each-of-each ; (each-of-each l)
48 | echo ; (echo s1 s2 ...)
49 | filter-zip ; (filter-zip pred-list list)
50 | first-letter ; (first-letter s)
51 | first-of-each ; (first-of-each l)
52 | first-two-of-each ; (first-two-of-each l)
53 | formatted-file-size ; (formatted-file-size filepath)
54 | get-current-executable-path ; (get-current-executable-path)
55 | get-file-content-type ; (get-file-content-type filepath)
56 | get-latest-version-number ; (get-latest-version-number versions prefix)
57 | get-matching-seconds ; (get-matching-seconds lst key)
58 | get-unique-prefix-line ; (get-unique-prefix-line lst prefix)
59 | grep ; (grep lines regex-pattern)
60 | grepl ; (grepl lines prefix)
61 | group ; (group n lst)
62 | if-defined ; (if-defined stx) [MACRO]
63 | inspect ; (inspect x ...)
64 | label->filename ; (label->filename label ext)
65 | license-almost-expired? ; (license-almost-expired? license-month)
66 | license-expired? ; (license-expired? license-year)
67 | list! ; (list! v)
68 | list-of-one? ; (list-of-one? l)
69 | list-to-string-lines ; (list-to-string-lines l)
70 | mask ; (mask l1 l2)
71 | mask-not ; (mask-not l1 l2)
72 | media-file? ; (media-file? f)
73 | multi-replace-line ; (multi-replace-line line source-list destination)
74 | multi-replace-lines ; (multi-replace-lines lines source-list destination)
75 | non-empty-list-of-list? ; (non-empty-list-of-list? l)
76 | non-empty-list-of-numbers? ; (non-empty-list-of-numbers? l)
77 | non-empty-list-of-strings? ; (non-empty-list-of-strings? l)
78 | non-empty-list? ; (non-empty-list? l)
79 | non-empty-or-none-string? ; (non-empty-or-none-string? s)
80 | pad ; (pad l len default)
81 | pad* ; (pad* l default)
82 | print-list ; (print-list l)
83 | remove-indexed-items ; (remove-indexed-items items indexes)
84 | remove-non-alphanumeric-or-underscore ; (remove-non-alphanumeric-or-underscore s)
85 | remove-nth ; (remove-nth lst n)
86 | replace-filename-in-path ; (replace-filename-in-path full-path new-filename)
87 | rest-of-each ; (rest-of-each l)
88 | second-last ; (second-last l)
89 | second-of-each ; (second-of-each l)
90 | second-true? ; (second-true? l)
91 | second? ; (second? l)
92 | sort-filepaths-by-inc-size ; (sort-filepaths-by-inc-size filepaths)
93 | str-list-contains ; (str-list-contains l s)
94 | str-list-contains? ; (str-list-contains? l s)
95 | string->label ; (string->label s)
96 | string->list-of-numbers ; (string->list-of-numbers str)
97 | string-chop ; (string-chop s len)
98 | string-contains-one-of? ; (string-contains-one-of? s l)
99 | string-nth ; (string-nth str nth (sep #px"\\s+"))
100 | string-replace-list ; (string-replace-list source pattern-list destination)
101 | string-replace2 ; (string-replace2 s from1 to1 from2 to2)
102 | string-replace3 ; (string-replace3 s from1 to1 from2 to2 from3 to3)
103 | strip-newlines-returns ; (strip-newlines-returns str)
104 | swap-columns-and-rows ; (swap-columns-and-rows l)
105 | swap-columns-to-rows-vector ; (swap-columns-to-rows-vector v)
106 | system->ports ; (system->ports command)
107 | take-everything-after-including ; (take-everything-after-including l starts-with)
108 | take-everything-starts-with ; (take-everything-starts-with l prefix)
109 | take-everything-until-including ; (take-everything-until-including l starts-with)
110 | take-up-to ; (take-up-to n lst)
111 | transpose ; (transpose l)
112 | zip) ; (zip l1 l2)
113 |
114 |
115 | ## IO procedures
116 |
117 |
118 | (provide add-file-extension ; (add-file-extension filename extension)
119 | add-file-extensions ; (add-file-extensions filenames extension)
120 | copy-file-to-folders ; (copy-file-to-folders source-path destination-list overwrite?)
121 | copy-or-die ; (copy-or-die src dest)
122 | create-folders-or-die ; (create-folders-or-die paths)
123 | create-list-of-files ; (create-list-of-files filenames content)
124 | directories-exist? ; (directories-exist? list-of-dirs)
125 | directory-list-str ; (directory-list-str path)
126 | display-error-count ; (display-error-count result msg)
127 | filename-path->string ; (filename-path->string filename-path)
128 | find-files# ; (find-files# pred path)
129 | file-path? ; (file-path? path)
130 | file-paths? ; (file-paths? paths)
131 | folder-path? ; (folder-path? path)
132 | folder-paths? ; (folder-paths? paths)
133 | get-error-count ; (get-error-count result msg)
134 | get-file-extension ; (get-file-extension filename-path)
135 | get-file-extensions ; (get-file-extensions filenames)
136 | get-file-lines ; (get-file-lines path)
137 | get-file-list-from-prefix-ext ; (get-file-list-from-prefix-ext path prefix extension)
138 | get-filename ; (get-filename filename-w-ext)
139 | get-filenames ; (get-filenames filenames-w-ext)
140 | get-file-name ; (get-file-name path)
141 | get-last-path-part ; (get-last-path-part path)
142 | list->file ; (list->file l file)
143 | make-backup-file ; (make-backup-file path)
144 | maybe-copy-file ; (maybe-copy-file source destination error-message exists-ok?)
145 | move-or-die ; (move-or-die src dest)
146 | path! ; (path! path-or-string)
147 | path<? ; (path<? p1 p2)
148 | process-text-files ; (process-text-files process file-list)
149 | replace-filename-in-path ; (replace-filename-in-path full-path new-filename)
150 | run-if-not-exists ; (run-if-not-exists list-of-files operation)
151 | sort-paths ; (sort-paths paths)
152 | write-file-lines) ; (write-file-lines lines path)
153 |
154 |
155 | ## Dialogs procedures
156 |
157 |
158 | (provide centered-listbox-dialog ; (centered-listbox-dialog title message initial-listbox-contents style width-ratio height-ratio)
159 | die ; (die msg)
160 | die# ; (die# msg)
161 | get-directory-list ; (get-directory-list title msg path)
162 | get-directory-list-w-prefix ; (get-directory-list-w-prefix title msg path folder_prefix)
163 | get-single-directory ; (get-single-directory title msg path)
164 | get-string-or-die ; (get-string-or-die msg error)
165 | hide-loading ; (hide-loading)
166 | listbox-dialog ; (listbox-dialog title message initial-listbox-contents style)
167 | listbox-dialog# ; (listbox-dialog# title message headers initial-listbox-contents selection-type width height)
168 | listbox-selectall ; (listbox-selectall list-box item-count select?)
169 | my-get-file-list ; (my-get-file-list message path filetype_name filetype_pattern)
170 | msgbox ; (msgbox message)
171 | populate-listbox ; (populate-listbox listbox listbox-contents)
172 | show-error-message ; (show-error-message message)
173 | show-loading ; (show-loading)
174 | show-confirmation-dialog ; (show-confirmation-dialog message)
175 | show-warning-message) ; (show-warning-message message)
176 |
177 |
178 | ## GUI procedures
179 |
180 |
181 | (provide get-bitmap ; (get-bitmap filename width height)
182 | is-checked? ; (is-checked? check-box)
183 | make-elastic-frame) ; (make-elastic-frame appname)
184 |
185 |
186 | ## Menu-bar procedures
187 |
188 | This is intended to be a good copy-paste candidate when one needs a simple, standard-looking menu bar.
189 |
190 | (provide menu-bar
191 | ------------------------
192 | file-menu
193 | file-new
194 | file-open
195 | file-save
196 | file-save-as
197 | file-exit
198 | edit-menu
199 | edit-copy
200 | edit-paste
201 | edit-select-all
202 | help-menu
203 | help-about)
204 |
205 |
206 | ## SQL procedures
207 |
208 |
209 | (provide query-execute ; (query-execute db query)
210 | query-record ; (query-record db query)
211 | query-string ; (query-string db query)
212 | get-query-headers ; (get-query-headers query) ('AS' required)
213 | get-query-headers# ; (get-query-headers# query) ('AS' not required, ignores subqueries)
214 | get-query-headers* ; (get-query-headers* db query) ('AS' not required, but does not support complex sub-queries)
215 | get-query-results ; (get-query-results db query wildcard-list)
216 | list->file ; (list->file l file)
217 | ml->sl ; (ml->sl l)
218 | sql-ml->sl ; (sql-ml->sl l)
219 | get-tables ; (get-tables db)
220 | get-tables* ; (get-tables* db-schema)
221 | get-table-columns ; (get-table-columns db table)
222 | get-table-columns-and-types ; (get-table-columns-and-types db table)
223 | get-db-schema ; (get-db-schema db tables)
224 | make-select-query ; (make-select-query db table columns)
225 | table-contains? ; (table-contains? db column table)
226 | which-tables-contain? ; (which-tables-contain? db tables column)
227 | which-tables-contain?* ; (which-tables-contain?* db-schema column)
228 | write-db-schema-to-file ; (write-db-schema-to-file db tables file)
229 | read-db-schema-from-file ; (read-db-schema-from-file file)
230 | update-db-schema ; (update-db-schema db db-schema)
231 | get-tables-that-contain-each-column-in-query) ; (get-tables-that-contain-each-column-in-query db db-schema query)
232 |
233 |
234 | ## Hash procedures
235 |
236 |
237 |
238 | (provide hash->string-list ; (hash->string-list h)
239 | hash->sorted-string-list ; (hash->sorted-string-list h)
240 | hash->flat-sorted-string-list ; (hash->flat-sorted-string-list h)
241 | maybe-hash) ; (maybe-hash cond possible-hash)
242 |
243 |
244 | ## Clipboard procedures
245 |
246 |
247 | (provide get-clipboard ; (get-clipboard-text)
248 | set-clipboard-text) ; (set-clipboard-text s)
249 |
250 |
251 | ## CSV-import procedures
252 |
253 | See csv-import-example.rkt for a sample implementation of the following module.
254 |
255 |
256 | (provide import-csv) ; (import-csv file processor-func (delimiter #\,))
257 |
258 |
259 | ## XML procedures
260 |
261 |
262 | (provide get-xml-value-from-id) ; (get-xml-value-from-id file pattern)
263 |
264 |
265 | ## System procedures
266 |
267 |
268 | (provide execute-async ; (execute-async startup-path program-binary-path command-line-parameters)
269 | system->ports) ; (system->ports command)
270 |
271 |
272 | ## License
273 | Racket Commons is free software; see [LICENSE](https://github.com/DexterLagan/racket-commons/blob/main/LICENSE) for more details.
274 |
--------------------------------------------------------------------------------
/clipboard.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/gui
2 |
3 | (provide get-clipboard ; (get-clipboard-text)
4 | set-clipboard-text) ; (set-clipboard-text s)
5 |
6 | ;;; defs
7 |
8 | ; returns the contents of the cliboard as text
9 | (define (get-clipboard-text)
10 | (send the-clipboard get-clipboard-string 0))
11 |
12 | ; set the contents of the cliboard as text
13 | (define (set-clipboard-text s)
14 | (send the-clipboard set-clipboard-string s 0))
15 |
16 |
17 | ; EOF
18 |
--------------------------------------------------------------------------------
/commons.rkt:
--------------------------------------------------------------------------------
1 | #lang racket
2 | (require racket/date)
3 | (provide all-but-last ; (all-but-last l)
4 | auto-quote ; (auto-quote str)
5 | clean-var-name ; (clean-var-name var)
6 | chop ; (chop l n)
7 | combine-with ; (combine-with f l1 l2)
8 | comp_ ; (comp_ stx) [MACRO]
9 | curry-ref ; (curry-ref ... ) [MACRO]
10 | currym ; (currym func param1 param3)
11 | define-command-line-params ; (define-command-line-params ... ) [MACRO]
12 | dies ; (dies s1 s2 ...)
13 | each-of-each ; (each-of-each l)
14 | echo ; (echo s1 s2 ...)
15 | filter-zip ; (filter-zip pred-list list)
16 | first-letter ; (first-letter s)
17 | first-of-each ; (first-of-each l)
18 | first-two-of-each ; (first-two-of-each l)
19 | formatted-file-size ; (formatted-file-size filepath)
20 | get-current-executable-path ; (get-current-executable-path)
21 | get-file-content-type ; (get-file-content-type filepath)
22 | get-latest-version-number ; (get-latest-version-number versions prefix)
23 | get-matching-seconds ; (get-matching-seconds lst key)
24 | get-unique-prefix-line ; (get-unique-prefix-line lst prefix)
25 | grep ; (grep lines regex-pattern)
26 | grepl ; (grepl lines prefix)
27 | group ; (group n lst)
28 | if-defined ; (if-defined stx) [MACRO]
29 | inspect ; (inspect x ...)
30 | label->filename ; (label->filename label ext)
31 | license-almost-expired? ; (license-almost-expired? license-month)
32 | license-expired? ; (license-expired? license-year)
33 | list! ; (list! v)
34 | list-of-one? ; (list-of-one? l)
35 | list-to-string-lines ; (list-to-string-lines l)
36 | mask ; (mask l1 l2)
37 | mask-not ; (mask-not l1 l2)
38 | media-file? ; (media-file? f)
39 | multi-replace-line ; (multi-replace-line line source-list destination)
40 | multi-replace-lines ; (multi-replace-lines lines source-list destination)
41 | non-empty-list-of-list? ; (non-empty-list-of-list? l)
42 | non-empty-list-of-numbers? ; (non-empty-list-of-numbers? l)
43 | non-empty-list-of-strings? ; (non-empty-list-of-strings? l)
44 | non-empty-list? ; (non-empty-list? l)
45 | non-empty-or-none-string? ; (non-empty-or-none-string? s)
46 | pad ; (pad l len default)
47 | pad* ; (pad* l default)
48 | print-list ; (print-list l)
49 | remove-indexed-items ; (remove-indexed-items items indexes)
50 | remove-non-alphanumeric-or-underscore ; (remove-non-alphanumeric-or-underscore s)
51 | remove-nth ; (remove-nth lst n)
52 | replace-filename-in-path ; (replace-filename-in-path full-path new-filename)
53 | rest-of-each ; (rest-of-each l)
54 | second-last ; (second-last l)
55 | second-of-each ; (second-of-each l)
56 | second-true? ; (second-true? l)
57 | second? ; (second? l)
58 | sort-filepaths-by-inc-size ; (sort-filepaths-by-inc-size filepaths)
59 | str-list-contains ; (str-list-contains l s)
60 | str-list-contains? ; (str-list-contains? l s)
61 | string->label ; (string->label s)
62 | string->list-of-numbers ; (string->list-of-numbers str)
63 | string-chop ; (string-chop s len)
64 | string-contains-one-of? ; (string-contains-one-of? s l)
65 | string-nth ; (string-nth str nth (sep #px"\\s+"))
66 | string-replace-list ; (string-replace-list source pattern-list destination)
67 | string-replace2 ; (string-replace2 s from1 to1 from2 to2)
68 | string-replace3 ; (string-replace3 s from1 to1 from2 to2 from3 to3)
69 | strip-newlines-returns ; (strip-newlines-returns str)
70 | swap-columns-and-rows ; (swap-columns-and-rows l)
71 | swap-columns-to-rows-vector ; (swap-columns-to-rows-vector v)
72 | system->ports ; (system->ports command)
73 | take-everything-after-including ; (take-everything-after-including l starts-with)
74 | take-everything-starts-with ; (take-everything-starts-with l prefix)
75 | take-everything-until-including ; (take-everything-until-including l starts-with)
76 | take-up-to ; (take-up-to n lst)
77 | transpose ; (transpose l)
78 | ;write-log ; (write-log s1 s2 ...)
79 | zip)
80 |
81 | (module+ test
82 | (require rackunit))
83 |
84 | ;;; purpose
85 |
86 | ; a library of common useful functions
87 |
88 | ;
89 | ;
90 | ; ; ;;
91 | ; ;; ;;
92 | ; ;; ;; ;;;; ;;;; ;; ;; ;;; ;;;;
93 | ; ;; ;; ; ;; ;; ; ; ; ;
94 | ; ;;; ; ;;; ; ; ; ; ;;;
95 | ; ; ; ; ; ; ; ; ; ; ;;
96 | ; ; ; ; ; ; ;; ; ; ; ;
97 | ; ; ; ;;; ; ;;;; ;;; ;;; ;;;;
98 | ;
99 | ;
100 | ;
101 |
102 | ;; returns a function that composes parameters in order,
103 | ;; using a placeholder _ for passing values between functions.
104 | (require (for-syntax racket/base))
105 | (define-syntax (comp_ stx)
106 | ; macro to compose functions passing an '_' parameter
107 | (syntax-case stx ()
108 | ((_ f1 ...)
109 | (with-syntax ([x-var (datum->syntax stx '_)])
110 | #'(apply compose1 (reverse (list (λ (x-var) f1) ...)))))))
111 | ; unit test
112 | (module+ test
113 | (check-equal? ((comp_ (string-trim _)
114 | (string-downcase _)
115 | (string-replace _ " " "-")
116 | ) "Hello World")
117 | "hello-world"))
118 |
119 | ;; helper function turns variable names into readable formatted string
120 | ;; i.e. *harmony-binary-path* -> "Harmony Binary Path"
121 | (define (clean-var-name var)
122 | ((comp_ (string-replace (symbol->string _) "-" " ")
123 | (string-replace _ "*" "")
124 | (string-titlecase _))
125 | var))
126 |
127 | ;; macro displays any number of variables and their values
128 | (define-syntax-rule (inspect x ...)
129 | (begin (printf " ~a: '~a'\n" (clean-var-name 'x) x) ...))
130 |
131 | ;
132 | ;
133 | ; ;;;; ;;;
134 | ; ; ;; ;;
135 | ; ; ; ;;; ;;;;; ;;;;
136 | ; ; ; ; ; ;; ;
137 | ; ; ; ;;;;; ;; ;;;
138 | ; ; ; ; ;; ;;
139 | ; ; ;; ; ;; ;
140 | ; ;;;; ;;;; ;; ;;;;
141 | ;
142 | ;
143 | ;
144 |
145 | ;; returns the current executable's path
146 | ;; sample result: C:\Users\baxter\OneDrive\Documents\Projects\Racket\Common\superlative.exe
147 | (define (get-current-executable-path)
148 | (find-system-path 'run-file))
149 |
150 | ;; helper function returns #t if given string isn't empty or "None"
151 | (define (non-empty-or-none-string? s)
152 | (and (non-empty-string? s)
153 | (not (string=? s "None"))))
154 | ; unit test
155 | (module+ test
156 | (check-false (non-empty-or-none-string? ""))
157 | (check-false (non-empty-or-none-string? "None"))
158 | (check-true (non-empty-or-none-string? "Something")))
159 |
160 | ;; macro executes the second form if symbol is defined, the third if not
161 | ;; i.e. (if-defined some-symbol (display "defined") (display "not defined"))
162 | (define-syntax (if-defined stx)
163 | (syntax-case stx ()
164 | [(_ id iftrue iffalse)
165 | (let ([where (identifier-binding #'id)])
166 | (if where #'iftrue #'iffalse))]))
167 |
168 | ;; writes an entry in a log file
169 | ;; the file is created if it doesn't exist, updated otherwise
170 | ;; i.e. (write-log "hello, " world ", you're beautiful")
171 | (if-defined *log-file* (void) (define *log-file* "c:\\temp\\csv-exporter.log"))
172 | (define write-log
173 | (λ args
174 | (display-lines-to-file (list (apply ~a args))
175 | *log-file*
176 | #:separator "\r\n"
177 | #:exists 'append)))
178 |
179 | ;; forces any value as a list
180 | (define (list! v)
181 | (if (list? v) v
182 | (list v)))
183 |
184 | ;; replace a filename in a full path with another filename
185 | (define (replace-filename-in-path full-path new-filename)
186 | (string-append (path->string (path-only full-path)) new-filename))
187 | ; unit test
188 | (module+ test
189 | (check-equal? (replace-filename-in-path (string->path "C:\\test\\path\\some-folder") "package-name")
190 | "C:\\test\\path\\package-name"
191 | "get-package-path-string"))
192 |
193 | ;; returns the nth part of a string split on the given separator (whitespaces by default)
194 | (define (string-nth str nth [sep #px"\\s+"])
195 | (if (non-empty-string? str)
196 | (let ((parts (string-split str sep)))
197 | (if (>= (length parts) nth)
198 | (list-ref parts (- nth 1))
199 | #f))
200 | #f))
201 | ; unit test
202 | (module+ test
203 | (check-equal? (string-nth "hello beautiful world" 2)
204 | "beautiful"
205 | "string-nth")
206 | (check-equal? (string-nth "hello beautiful world" 4)
207 | #f
208 | "string-nth")
209 | (check-equal? (string-nth "hello_beautiful_world" 3 "_")
210 | "world"
211 | "string-nth"))
212 |
213 |
214 | ;; mask l1 onto l2
215 | (define (mask l1 l2)
216 | (if (= (length l1) (length l2))
217 | (let ((results (map (λ (a b) (if a b #f)) l1 l2)))
218 | (filter identity results))
219 | null))
220 | ; unit test
221 | (module+ test
222 | (check-equal? (mask '(#t #t #f #f #t)
223 | '(1 2 3 4 5))
224 | '(1 2 5)
225 | "mask"))
226 |
227 | ;; reverse mask l1 onto l2
228 | (define (mask-not l1 l2)
229 | (if (= (length l1) (length l2))
230 | (let ((results (map (λ (a b) (if a #f b)) l1 l2)))
231 | (filter identity results))
232 | null))
233 | ; unit test
234 | (module+ test
235 | (check-equal? (mask-not '(#t #t #f #f #t)
236 | '(1 2 3 4 5))
237 | '(3 4)
238 | "reverse-mask"))
239 |
240 | ;; returns a chopped list of n lists
241 | (define (chop l n)
242 | (if (null? l) null
243 | (cons (take l n) (chop (drop l n) n))))
244 |
245 | ;; returns a list of matches (second list item) given one first list item
246 | ;; in a list of lists
247 | (define (get-matching-seconds lst key)
248 | (if (and (non-empty-list? lst)
249 | (non-empty-string? key))
250 | (filter identity ; anything not false
251 | (map (λ (element)
252 | (if (string=? key (first element))
253 | (second element)
254 | #f))
255 | lst))
256 | #f))
257 | ; unit test
258 | (module+ test
259 | (check-equal? (get-matching-seconds '(("Varian_Nose_TX" "some-path")
260 | ("XAVIER_Cheek_TX" 'some-other-thing)
261 | ("XAVIER_Nose_TX" "yet-another-path"))
262 | "XAVIER_Cheek_TX")
263 | '('some-other-thing))
264 | (check-equal? (get-matching-seconds '(("key" "first-string")
265 | ("not-key" "more-string")
266 | ("key" "third-string")
267 | ("other-key" "last-string"))
268 | "key")
269 | '("first-string" "third-string"))
270 | (check-equal? (get-matching-seconds null "key") #f)
271 | (check-equal? (get-matching-seconds '(("key" "first-string")
272 | ("not-key" "more-string")
273 | ("key" "third-string")
274 | ("other-key" "last-string"))
275 | "") #f))
276 |
277 | ;; predicate returns true if list contains one element
278 | (define (list-of-one? l)
279 | (and (list? l)
280 | (= 1 (length l))))
281 | ; unit test
282 | (module+ test
283 | (check-equal? (list-of-one? '()) #f)
284 | (check-equal? (list-of-one? '(1)) #t)
285 | (check-equal? (list-of-one? '(1 2)) #f)
286 | (check-equal? (list-of-one? 'test) #f))
287 |
288 | ;; returns true if a list is not empty, false otherwise
289 | (define (non-empty-list? l)
290 | (if (list? l)
291 | (not (empty? l))
292 | #f))
293 | ; unit test
294 | (module+ test
295 | (check-false (non-empty-list? #f))
296 | (check-false (non-empty-list? '()))
297 | (check-true (non-empty-list? '(1 2)))
298 | (check-true (non-empty-list? '("a" "b" "c"))))
299 |
300 | ;; predicate returns true if all lists given are non-empty and of equal length
301 | (define non-empty-same-length?
302 | (λ args (and (andmap non-empty-list? args)
303 | (apply = (map length args)))))
304 |
305 | ;; Check that license is valid for a given license year limit
306 | (define (license-expired? license-year)
307 | (let ((this-year (date-year (current-date))))
308 | (if (<= this-year license-year) #f #t)))
309 |
310 | ;; Check that license is valid for a given license year limit
311 | (define (license-almost-expired? license-month)
312 | (let ((this-month (date-month (current-date))))
313 | (if (>= this-month license-month) #t #f)))
314 |
315 | ;; Curries a function that takes 3 parameters by its middle parameter
316 | (define (currym func param1 param3)
317 | (lambda (param2)
318 | (func param1 param2 param3)))
319 | ;; unit test
320 | (module+ test
321 | (check-equal? ((currym ~a 'a 'c) 'b) "abc"))
322 |
323 | ;; Macro that curries a function by replacing the referred parameter
324 | ;; by the single parameter provided to the curried function
325 | (define-syntax curry-ref
326 | (syntax-rules ()
327 | ((curry-ref id params ref)
328 | (lambda args
329 | (apply id (list-set params ref (car args)))))))
330 |
331 | ;; macro that defines whichever parameters are fed to it and fills them in from command line
332 | (define-syntax define-command-line-params
333 | (syntax-rules ()
334 | ((define-command-line-params appname param1 ...)
335 | (define-values (param1 ...)
336 | (command-line #:program appname
337 | #:args (param1 ...)
338 | (values param1 ...))))))
339 |
340 | ;; Fancy varargs println
341 | (define echo
342 | (lambda args
343 | (displayln (apply ~a args))))
344 |
345 | ;; displays an error message and quit with an error code
346 | (define dies
347 | (lambda args
348 | (displayln (apply ~a args))
349 | (exit 1)))
350 |
351 | ;; General list printing
352 | (define (print-list l)
353 | (for-each displayln l))
354 |
355 | ;; Concatenate all strings in a list to a single string with line feeds
356 | (define (list-to-string-lines l)
357 | (if (non-empty-list? l)
358 | (let* ((parts (map (curryr string-append "\n") l)))
359 | (apply string-append parts))
360 | ""))
361 | ; unit test
362 | (module+ test
363 | (check-equal? (list-to-string-lines '("Sweet" "Jesus")) "Sweet\nJesus\n"))
364 |
365 | ;; Shortcut for string-append
366 | (define ++ string-append)
367 |
368 | ;; Like prefix but one letter
369 | (define (first-letter s)
370 | (string-ref s 0))
371 |
372 | ;; Name says it all
373 | (define (second-last l)
374 | (second (reverse l)))
375 |
376 | ;; Zip two lists
377 | (define zip
378 | (lambda (l1 l2) (map list l1 l2)))
379 |
380 | ;; Find the first orrurence of source-list in str and
381 | ;; return str with this occurence replaced by destination
382 | (define (string-replace-list source pattern-list destination)
383 | (if (and (non-empty-string? source)
384 | (list? pattern-list)
385 | (not (null? pattern-list))
386 | (string? destination))
387 | (if (string-contains? source (car pattern-list)) ; if pattern is found in source
388 | (string-replace source (car pattern-list) destination) ; replace pattern and return result
389 | (string-replace-list source (rest pattern-list) destination)) ; else call proc recursively
390 | source)) ; else return original string
391 | ; unit test
392 | (module+ test
393 | (check-equal? (string-replace-list
394 | "sweet jesus lord"
395 | '("sweet" "lord")
396 | "awesome")
397 | "awesome jesus lord"))
398 |
399 | ;; Replace a list of strings by a single destination in a line
400 | (define (multi-replace-line line source-list destination)
401 | (if (and (non-empty-string? line)
402 | (list? source-list)
403 | (non-empty-string? destination))
404 | (curryr string-replace-list line source-list destination)
405 | null))
406 | ; unit test
407 | (module+ test
408 | (check-equal?
409 | (multi-replace-line ""
410 | '("test1.plt" "test2.plt")
411 | "test4.plt")
412 | ""))
413 |
414 | ;; Replace a list of strings by a single destination in a list of lines
415 | (define (multi-replace-lines lines source-list destination)
416 | (if (and (list? lines)
417 | (list? source-list)
418 | (non-empty-string? destination))
419 | (map (curryr string-replace-list source-list destination) lines)
420 | null))
421 | ; unit test
422 | (module+ test
423 | (check-equal?
424 | (multi-replace-lines '(""
425 | ""
426 | "")
427 | '("test1.plt" "test2.plt")
428 | "test4.plt")
429 | '(""
430 | ""
431 | "")))
432 |
433 | ;; combine two lists using the provided function as glue
434 | (define (combine-with f l1 l2)
435 | (let ((combiner (lambda (a1 a2 acc) (cons (f a1 a2) acc))))
436 | (foldr combiner '() l1 l2)))
437 | ; unit test
438 | (module+ test
439 | (check-equal? (combine-with string-append '("a" "b" "c") '("d" "e" "f")) '("ad" "be" "cf")))
440 |
441 | ;; returns all the lines, including the one starting with, drop the rest
442 | (define (take-everything-until-including l starts-with)
443 | (dropf-right l (λ (s) (not (string-prefix? s starts-with)))))
444 |
445 | ;; returns all the lines that start with the given prefix
446 | (define (take-everything-starts-with l prefix)
447 | (filter (λ (s) (string-prefix? s prefix)) l))
448 |
449 | ;; returns all the lines after the line that starts with given prefix
450 | (define (take-everything-after-including l starts-with)
451 | (dropf l (λ (s) (not (string-prefix? s starts-with)))))
452 |
453 | ;; returns the matching unique line starting with prefix
454 | ;; ensures a single element line exists in the list
455 | ;; ensures the prefix exists
456 | ;; returns false otherwise
457 | (define (get-unique-prefix-line lst prefix)
458 | (if (not (list? lst)) #f
459 | (let ((bin-line (grepl lst prefix)))
460 | (if (and (list? bin-line)
461 | (= (length bin-line) 1))
462 | (string-replace (first bin-line) prefix "")
463 | #f))))
464 |
465 | ;; generate a filename from a title
466 | ;; i.e. The Lion Guard --> the-lion-guard
467 | (define (label->filename label ext)
468 | (string-append
469 | (string-downcase
470 | (string-replace label " " "-"))
471 | ext))
472 |
473 | ;; grep using a regex
474 | (define (grep lines regex-pattern)
475 | (filter (λ (line) (regexp-match? regex-pattern line)) lines))
476 |
477 | ;; grep using a prefix only
478 | (define (grepl lines prefix)
479 | (filter (λ (line) (string-prefix? line prefix)) lines))
480 |
481 | ;; transpose a list of lists
482 | ;; returns the transposed list of lists if lists are equal lengths, #f otherwise
483 | (define (transpose l)
484 | (let/cc return
485 | (unless (and (non-empty-list? l)
486 | (non-empty-list? (first l)))
487 | (return #f))
488 | ; consider first list as the length to match
489 | (define standard-length
490 | (length (first l)))
491 | (if (andmap (λ (line)
492 | (equal? (length line) standard-length))
493 | l)
494 | (apply map list l)
495 | #f)))
496 | ; unit test
497 | (module+ test
498 | (check-equal? (transpose '((1 2 3) (4 5 6)))
499 | '((1 4) (2 5) (3 6)))
500 | (check-equal? (transpose '((1 2 3) (4 5 6 7)))
501 | #f)
502 | (check-equal? (transpose '("hello" (4 5 6 7)))
503 | #f))
504 |
505 | ;; searches a string in a list. Returns #t if found, #f otherwise
506 | (define (str-list-contains? l s)
507 | (ormap (λ (search)
508 | (string-contains? search s))
509 | l))
510 | ; unit test
511 | (module+ test
512 | (check-true (str-list-contains? '("a" "b" "c") "b"))
513 | (check-false (str-list-contains? '("a" "b" "c") "e")))
514 |
515 | ;; searches a string in a list. Returns the string if found, #f otherwise
516 | ;; same as previous procedure, but returns the line containing the needle instead
517 | (define (str-list-contains l s)
518 | (define results
519 | (filter-map (λ (str)
520 | (if (string-contains? str s)
521 | str
522 | #f))
523 | l))
524 | (if (non-empty-list? results)
525 | (first results)
526 | #f))
527 | ; unit test
528 | (module+ test
529 | (check-equal? (str-list-contains '("a" "b" "c") "b") "b")
530 | (check-equal? (str-list-contains '("a" "b" "c") "e") #f)
531 | (check-equal? (str-list-contains '("sweet" "cool stuff" "naice") "cool") "cool stuff"))
532 |
533 | ;; returns #t if the string contains one of the listed strings, #f otherwise
534 | (define (string-contains-one-of? s l)
535 | (and (non-empty-list? l)
536 | (ormap (λ (str)
537 | (string-contains? s str))
538 | l)))
539 | ; unit test
540 | (module+ test
541 | (check-true (string-contains-one-of? "b" '("a" "b" "c")))
542 | (check-false (string-contains-one-of? "e" '("a" "b" "c")))
543 | (check-true (string-contains-one-of? "souris usb" '("batterie" "adaptateur" "clé" "clavier" "dalle" "souris" "encadrement" "capot" "haut parleur" "carte pci"))))
544 |
545 | (define (first-of-each l) ; -> list of atoms!
546 | (if (null? (car l)) null; make sure there is a first
547 | (map car l)))
548 | ;(first-of-each '((1 2 3) (a b c) ("str1" "str2" "str3")))
549 | ;'(1 a "str1")
550 |
551 | (define (first-two-of-each l) ; -> list of lists
552 | (cond [(null? (caar l)) null] ; if the first item of the first list if there
553 | [(null? (second (car l))) null]; if the second item of the first list
554 | [else (let ((first-two (lambda (l) (list (car l) (second l)))))
555 | (map first-two l))]))
556 | ;(first-two-of-each '((1 2 3) (a b c) ("str1" "str2" "str3")))
557 | ;'((1 2) (a b) ("str1" "str2"))
558 |
559 | (define (rest-of-each l) ; -> list of lists!
560 | (if (null? (car l)) null
561 | (map cdr l)))
562 | ;(rest-of-each '((1 2 3) (a b c) ("str1" "str2" "str3")))
563 | ;'((2 3) (b c) ("str2" "str3"))
564 |
565 | (define (second-of-each l) ; -> list of atoms!
566 | (first-of-each
567 | (rest-of-each l)))
568 | ;(second-of-each '((1 2 3) (a b c) ("str1" "str2" "str3")))
569 | ;'(2 b "str2")
570 |
571 | (define (each-of-each l) ; a.k.a. swap columns and rows
572 | (if (null? (car (cdr l))) null
573 | (cons (first-of-each l) (each-of-each (rest-of-each l)))))
574 | ;(each-of-each '((1 2 3) (a b c) ("str1" "str2" "str3")))
575 | ;'((1 a "str1") (2 b "str2") (3 c "str3"))
576 |
577 | ; Better version of each-of-each
578 | (define (swap-columns-and-rows l)
579 | (apply map list l))
580 |
581 | ; Same as swap-columns-and-rows but works with vectors
582 | (define (swap-columns-to-rows-vector v)
583 | (apply vector-map vector (vector->list v)))
584 |
585 | ; Filters a list by another list of booleans
586 | (define (filter-zip pred-list list)
587 | (if (null? list) null
588 | (let ((bool (car pred-list))
589 | (elem (car list)))
590 | (if bool (cons elem (filter-zip (cdr pred-list) (cdr list)))
591 | (filter-zip (cdr pred-list) (cdr list))))))
592 |
593 | ; Pad a list with default. Returns list if length is same or inferior to the list's
594 | (define (pad l len default)
595 | (if (< (- len (length l)) 1) l
596 | (append l (make-list (- len (length l)) default))))
597 | ; Test
598 | (module+ test
599 | (check-equal? (pad '(1 2 3 4) 6 0) '(1 2 3 4 0 0)))
600 |
601 | ; Automatically pad each list of a list to the longest list using default as padding (can be "", 0, null or anything)
602 | (define (pad* l default)
603 | (let* ((lengths (map length l))
604 | (max-length (apply max lengths)))
605 | (map (curryr pad max-length default) l)))
606 | ; Test
607 | (module+ test
608 | (check-equal? (pad* '((1 2 3) (1 2 3 4 5) (1 2 3) (1 2)) 9) '((1 2 3 9 9) (1 2 3 4 5) (1 2 3 9 9) (1 2 9 9 9))))
609 |
610 | ;; convert a string so it complies to a label-string's requirements
611 | ;; (200 chars max string)
612 | (define (string->label s)
613 | (~a s #:max-width 200 #:limit-marker "..."))
614 | (module+ test
615 | (check-equal? (string->label "The expense is accounted for when a vendor bill is validated, except in anglo-saxon accounting with perpetual inventory valuation in which case the expense (Cost of Goods Sold account) is recognized at the customer invoice validation.")
616 | "The expense is accounted for when a vendor bill is validated, except in anglo-saxon accounting with perpetual inventory valuation in which case the expense (Cost of Goods Sold account) is recognize..."))
617 |
618 | ;; all but last
619 | (define (all-but-last l)
620 | (reverse (cdr (reverse l))))
621 |
622 | ; Predicate that returns true if the list has a second element
623 | (define (second? l)
624 | (if (list? l)
625 | (if (>= (length l) 2)
626 | (if (second l) #t #f)
627 | #f) #f))
628 | ; unit test
629 | (module+ test
630 | (check-equal? (second? '(1)) #f)
631 | (check-equal? (second? '(1 2)) #t)
632 | (check-equal? (second? '(1 2 3)) #t))
633 |
634 | ; Predicate that returns true if the second element of the list is '#true (and nothing else!)
635 | (define (second-true? l)
636 | (if (second? l)
637 | (if (equal? (second l) #t) #t #f)
638 | #f))
639 | ; unit test
640 | (module+ test
641 | (check-equal? (second-true? '(1 anything)) #f)
642 | (check-equal? (second-true? '(1 #t)) #t))
643 |
644 | ; Replace two strings, not just one
645 | (define (string-replace2 s from1 to1 from2 to2)
646 | (string-replace (string-replace s from1 to1) from2 to2))
647 |
648 | ; Replace three strings, not just two
649 | (define (string-replace3 s from1 to1 from2 to2 from3 to3)
650 | (string-replace (string-replace (string-replace s from1 to1) from2 to2) from3 to3))
651 |
652 | ;; returns true if l is a non-empty list of list, #f otherwise
653 | (define (non-empty-list-of-list? l)
654 | (and (non-empty-list? l)
655 | (non-empty-list? (car l))))
656 | ; unit test
657 | (module+ test
658 | (check-false (non-empty-list-of-list? '("" "")))
659 | (check-false (non-empty-list-of-list? '("abc" "")))
660 | (check-false (non-empty-list-of-list? '("" "abc")))
661 | (check-false (non-empty-list-of-list? '(1 2 3)))
662 | (check-false (non-empty-list-of-list? '("abc" #f)))
663 | (check-false (non-empty-list-of-list? '("abc" '("test") #f "abc")))
664 | (check-false (non-empty-list-of-list? '("abc" 12 "cde")))
665 | (check-true (non-empty-list-of-list? '(("test"))))
666 | (check-true (non-empty-list-of-list? '(("test" "test2"))))
667 | (check-true (non-empty-list-of-list? '(("test" "test2") ("test3" "test4")))))
668 |
669 | ;; returns true if l is a non-empty list of strings, #f otherwise
670 | (define (non-empty-list-of-strings? l)
671 | (and (non-empty-list? l)
672 | (andmap non-empty-string? l)))
673 | ; unit test
674 | (module+ test
675 | (check-false (non-empty-list-of-strings? '("")))
676 | (check-false (non-empty-list-of-strings? '("abc" "")))
677 | (check-false (non-empty-list-of-strings? '("" "abc")))
678 | (check-false (non-empty-list-of-strings? '(1 2 3)))
679 | (check-false (non-empty-list-of-strings? '("abc" #f)))
680 | (check-false (non-empty-list-of-strings? '("abc" "def" #f "abc")))
681 | (check-false (non-empty-list-of-strings? '("abc" 12 "cde")))
682 | (check-true (non-empty-list-of-strings? '("abc" "def" "ghf"))))
683 |
684 | ;; returns #t if a list of numbers, #f otherwise
685 | (define (non-empty-list-of-numbers? l)
686 | (and (list? l)
687 | (not (empty? l))
688 | (andmap number? l)))
689 | ; unit test
690 | (module+ test
691 | (check-false (non-empty-list-of-numbers? #f))
692 | (check-false (non-empty-list-of-numbers? '()))
693 | (check-true (non-empty-list-of-numbers? '(1 2)))
694 | (check-false (non-empty-list-of-numbers? '("a" "b" "c"))))
695 |
696 | ;; chops a string at the desired length
697 | (define (string-chop s len)
698 | (and (non-empty-string? s)
699 | (if (> (string-length s) len)
700 | (substring s 0 len)
701 | s)))
702 | ;unit test
703 | (module+ test
704 | (check-equal? (string-chop "12345" 3) "123")
705 | (check-equal? (string-chop "123" 5) "123")
706 | (check-equal? (string-chop 12345 3) #f))
707 |
708 | ;; returns a string with non-alphanumeric characters removed, leaves underscores alone
709 | (define (remove-non-alphanumeric-or-underscore s)
710 | (define (alphanum? c)
711 | (or (char-numeric? c)
712 | (char-alphabetic? c)
713 | (eq? c #\_)))
714 | (list->string (filter alphanum? (string->list s))))
715 | (module+ test
716 | (check-equal? (remove-non-alphanumeric-or-underscore "a0 9-14_*&(") "a0914_"))
717 |
718 | ;; returns a string with non-alphanumeric characters removed, leaves underscores alone
719 | (define (remove-non-alphanumeric-or-underscore s)
720 | (define (alphanum? c)
721 | (or (char-numeric? c)
722 | (char-alphabetic? c)
723 | (eq? c #\_)))
724 | (list->string (filter alphanum? (string->list s))))
725 | (module+ test
726 | (check-equal? (remove-non-alphanumeric-or-underscore "a0 9-14_*&(") "a0914_"))
727 |
728 | ;; returns a list of up to n items off the given list lst
729 | (define (take-up-to n lst)
730 | (if (or (zero? n) (null? lst))
731 | '()
732 | (cons (car lst) (take-up-to (- n 1) (cdr lst)))))
733 | (module+ test
734 | (check-equal? (take-up-to 0 '("a" "b" "c")) '())
735 | (check-equal? (take-up-to 2 '("a" "b" "c")) '("a" "b"))
736 | (check-equal? (take-up-to 1 '("a" "b" "c")) '("a"))
737 | (check-equal? (take-up-to 4 '("a" "b" "c")) '("a" "b" "c")))
738 |
739 | ;; split/group a list into n equal chunks.
740 | ;; returns a list of lists.
741 | (define (group n lst)
742 | (let/cc return
743 | (when (non-empty-string? n)
744 | (set! n (string->number n)))
745 | (unless (number? n)
746 | (return (list lst)))
747 | (define (loop grouped lst l)
748 | (cond
749 | [(empty? lst)
750 | (reverse grouped)]
751 | [(<= l n)
752 | (loop (cons lst grouped) '() 0)]
753 | [else (let-values ([(taken dropped) (split-at lst n)])
754 | (loop (cons taken grouped)
755 | dropped
756 | (- l n)))]))
757 | (let ([l (length lst)])
758 | (if (>= n l) ; ERROR: >=: contract violation
759 | ; expected: real?
760 | ; given: "8"
761 | (list lst)
762 | (loop '() lst l)))))
763 | ; unit test
764 | (module+ test
765 | (check-equal? (group 3 '(1 2 3)) '((1 2 3)))
766 | (check-equal? (group 'x '(1 2 3)) '((1 2 3)))
767 | (check-equal? (group 3 '("sweet" "naice" "cool" "extra")) '(("sweet" "naice" "cool") ("extra")))
768 | (check-equal? (group 2 '("sweet" "naice" "cool" "extra")) '(("sweet" "naice") ("cool" "extra")))
769 | (check-equal? (group "2" '("sweet" "naice" "cool" "extra")) '(("sweet" "naice") ("cool" "extra"))))
770 |
771 | ;; utility function double-quotes a string if it contains a comma, and doubles double-quotes
772 | (define (auto-quote str)
773 | (if (string-contains? str ",")
774 | (string-append "\"" (string-replace str "\"" "\"\"") "\"")
775 | (string-replace str "\"" "\"\"")))
776 | ; unit test
777 | (module+ test
778 | (check-equal? (auto-quote "no quotes!") "no quotes!")
779 | (check-equal? (auto-quote "there is a comma in this string, I should quote it")
780 | "\"there is a comma in this string, I should quote it\"")
781 | (check-equal? (auto-quote "there is a comma and \"double-quotes\" in this string, I should double-quote it")
782 | "\"there is a comma and \"\"double-quotes\"\" in this string, I should double-quote it\""))
783 |
784 | ;; strips both newlines and line feeds
785 | (define (strip-newlines-returns str)
786 | (string-replace2 str
787 | "\n" ""
788 | "\r" ""))
789 |
790 |
791 | ;; returns a list of numbers, given a string containing a list of numbers
792 | ;; returns #f otherwise
793 | (define (string->list-of-numbers str)
794 | (let/cc return
795 | ((comp_ (string-split _ ",")
796 | (if (or (null? _)
797 | (< (length _) 2))
798 | (return #f)
799 | (map string-trim _))
800 | (map string->number _)
801 | (if (andmap number? _)
802 | _
803 | #f))
804 | str)))
805 | ; unit test
806 | (module+ test
807 | (check-equal? (string->list-of-numbers "0,2") '(0 2))
808 | (check-equal? (string->list-of-numbers "1, 2, 3") '(1 2 3))
809 | (check-equal? (string->list-of-numbers "1,") #f)
810 | (check-equal? (string->list-of-numbers ",2") #f)
811 | (check-equal? (string->list-of-numbers "hello") #f)
812 | (check-equal? (string->list-of-numbers "a,b") #f)
813 | (check-equal? (string->list-of-numbers "1,a") #f)
814 | (check-equal? (string->list-of-numbers "b,2") #f))
815 |
816 | ;; removes items from a list which are matching the given indexes
817 | (define (remove-indexed-items items indexes)
818 | (filter (λ (item)
819 | (not (member (index-of items item)
820 | indexes)))
821 | items))
822 | ; unit test
823 | (module+ test
824 | (check-equal? (remove-indexed-items '(1 2 3 4) '(0 1)) '(3 4))
825 | (check-equal? (remove-indexed-items '("001" "002" "003" "004") '(1 3)) '("001" "003")))
826 |
827 | ;; matches media files, returns #t otherwise.
828 | ;; to be used in combination with find-files
829 | (define (media-file? f)
830 | (let/cc return
831 | ; ignore directories
832 | (unless (file-exists? f)
833 | (return #f))
834 | ; get extension
835 | (define ext
836 | (path-get-extension f))
837 | ; match media file extensions
838 | (or (eq? ext #".tga")
839 | (eq? ext #".png")
840 | (eq? ext #".sgi")
841 | (eq? ext #".exr")
842 | (eq? ext #".mov")
843 | (eq? ext #".jpg")
844 | (eq? ext #".jpeg")
845 | (eq? ext #".psd")
846 | (eq? ext #".psb")
847 | (eq? ext #".gif")
848 | (eq? ext #".mp4")
849 | (eq? ext #".avi"))))
850 |
851 | ;; determines the last version number, given a list of versions and the expected prefix
852 | ;; sample version list: '("SOMEPREFIX_SOMETHING_V1" "SOMEPREFIX_SOMETHING_V2" "SOMEPREFIX_SOMETHING_V3" "OTHERPREFIX_SOMETHINGELSE_V1")
853 | ;; sample prefix: "SOMEPREFIX_SOMETHING_V"
854 | (define/contract (get-latest-version-number versions prefix)
855 | (list? string? . -> . number?)
856 | (let/cc return
857 | ; if empty list, return 0
858 | (unless (non-empty-list? versions)
859 | (return 0))
860 | ; if stump not found in the list of versions, return 0
861 | (unless (str-list-contains? versions prefix)
862 | (return 0))
863 | ; grab only the version names containing the target stump
864 | (define target-versions
865 | (filter (curryr string-contains? prefix)
866 | versions))
867 | ; remove stump from object-version-names
868 | (define version-number-strings?
869 | (map (curryr string-replace prefix "")
870 | target-versions))
871 | ; convert to a list of numbers
872 | (define version-numbers?
873 | (if (non-empty-list-of-strings? version-number-strings?)
874 | (map string->number version-number-strings?)
875 | '(0)))
876 | ; sort list of resulting numbers
877 | (if (non-empty-list-of-numbers? version-numbers?)
878 | (car (sort version-numbers? >))
879 | 0)))
880 | ; unit test
881 | (module+ test
882 | (check-equal? (get-latest-version-number '() "GOODSTUMP_V")
883 | 0)
884 | (check-equal? (get-latest-version-number '("GOODSTUMP_V1" "GOODSTUMP_V2" "GOODSTUMP_V3" "BADSTUMP_V1") "GOODSTUMP_V")
885 | 3)
886 | (check-equal? (get-latest-version-number '("GOODSTUMP_V1" "GOODSTUMP_V2" "GOODSTUMP_V3" "BADSTUMP_V1") "BADSTUMP_V")
887 | 1)
888 | (check-equal? (get-latest-version-number '("GOODSTUMP_V1" "GOODSTUMP_V2" "GOODSTUMP_V3" "BADSTUMP_V1") "OTHERSTUMP_V")
889 | 0))
890 |
891 | ;; helper function returns a list of the given filepaths in increasing order of size
892 | (define/contract (sort-filepaths-by-inc-size filepaths)
893 | (non-empty-list? . -> . (or/c non-empty-list? boolean?))
894 | (let/cc return
895 | ; build a sorted list of filepaths by their respective sizes
896 | (define sorted-files
897 | (let ((file+sizes
898 | (map (λ (filepath)
899 | (if (file-exists? filepath)
900 | (list filepath (file-size filepath))
901 | (return #f))) ; return an error if one of the filepaths isn't a file
902 | filepaths)))
903 | (sort file+sizes #:key cdr <)))
904 | ; deduce a flat list of the filepaths in order of size
905 | (if (non-empty-list? sorted-files)
906 | (map car sorted-files)
907 | #f)))
908 |
909 | ;; helper function returns a string with the correctly formatted file size of the given filepath (i.e. 1,234,567 KB)
910 | ;; function returns "0 KB" if the file does not exist
911 | (define/contract (formatted-file-size filepath)
912 | ((or/c path? path-string?) . -> . non-empty-string?)
913 | (if (file-exists? filepath)
914 | (string-append (~r (file-size filepath) #:groups '(3) #:group-sep ",") " KB")
915 | "0 KB"))
916 |
917 |
918 | ; EOF
919 |
--------------------------------------------------------------------------------
/csv-import-example.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/gui
2 | (require "dialogs.rkt"
3 | "commons.rkt"
4 | "csv-import.rkt"
5 | csv-reading)
6 |
7 | (provide partners-import) ; (import-contact name email company-name company-type buyer-name salesperson-name customer-axis-name customer-type customer-subtype code-site code-site-description street city country-name phone sage-ref siret)
8 |
9 | ;;; purpose
10 |
11 | ; import a CSV file containing partners into Odoo (an open-source ERP)
12 |
13 | ;;; consts
14 |
15 | (define *appname* "Sample CSV Importer")
16 | (define *total-lines* 0)
17 | (define *lines-processed* 0)
18 |
19 | ;;; defs
20 |
21 | ;; extract and process a list of CSV line's fields
22 | (define (line-processor l)
23 | ; extract CSV columns
24 | (define company-type (list-ref l 0)) ; 0 Company Type
25 | (define sage-ref (list-ref l 1)) ; 1 Référence
26 | (define name (list-ref l 2)) ; 2 Nom affiché
27 | (define street (list-ref l 3)) ; 3 Rue
28 | (define postal-code (list-ref l 4)) ; 4 Code Postal
29 | (define city (list-ref l 5)) ; 5 Ville
30 | (define country (list-ref l 6)) ; 6 Pays
31 | (define company-group (list-ref l 7)) ; 7 Groupe de Sociétés
32 | (define site-code (list-ref l 8)) ; 8 Code Site
33 | (define site-code-desc (list-ref l 9)) ; 9 Description code site
34 | (define tva (list-ref l 10)) ; 10 N° TVA
35 | (define siret (list-ref l 11)) ; 11 SIRET
36 | (define phone (list-ref l 12)) ; 12 Téléphone
37 | (define email (list-ref l 13)) ; 13 Email
38 | (define etiquette (list-ref l 14)) ; 14 Étiquettes
39 | (define contact-name (list-ref l 15)) ; 15 Contact/Nom
40 | (define salesperson (list-ref l 16)) ; 16 Vendeur
41 | (define buyer (list-ref l 17)) ; 17 Buyer
42 | (define fiscal-pos (list-ref l 18)) ; 18 Position fiscale
43 | (define price-list (list-ref l 19)) ; 19 Liste de prix
44 | (define customer-axis (list-ref l 20)) ; 20 Customer axis
45 | (define customer-type (list-ref l 21)) ; 21 Customer type
46 | (define customer-subtype (list-ref l 22)) ; 22 Customer Subtype
47 | (define commercial-team (list-ref l 23)) ; 23 Équipe Commerciale
48 | ; create the partner
49 | (define result (get/create-partner name
50 | email
51 | name
52 | company-type
53 | buyer
54 | salesperson
55 | customer-axis
56 | customer-type
57 | customer-subtype
58 | site-code
59 | site-code-desc
60 | street
61 | city
62 | postal-code
63 | country
64 | phone
65 | sage-ref
66 | siret
67 | tva
68 | company-group
69 | etiquette
70 | contact-name
71 | fiscal-pos
72 | price-list
73 | commercial-team))
74 | (set! *lines-processed* (add1 *lines-processed*))
75 | (echo *lines-processed* " out of " *total-lines* " lines processed.")
76 | result)
77 |
78 | ;;; main
79 |
80 | ;; import partners from a CSV file:
81 | ;; - check access rights to the target model;
82 | ;; - ask file from user;
83 | ;; - count file lines;
84 | ;; - import file lines;
85 | ;; - display result string.
86 | (define (partners-import)
87 | (let/cc return
88 | ; check access rights to the partner model
89 | (unless (check-access-rights *partner* '(create write))
90 | (show-error-message "No create right on partner model. Aborting.")
91 | (return #f))
92 | ; ask user for file
93 | (define file (get-file))
94 | (unless file
95 | (return #t))
96 | ; count file lines
97 | (set! *total-lines* (- (length (file->lines file)) 1))
98 | (when (= *total-lines* 0)
99 | (show-error-message "Empty file. Aborting."))
100 | ; process CSV file
101 | (define results (import-csv file line-processor))
102 | ; process results
103 | (if results
104 | ; display import log
105 | (void (message-box *appname* (string-join results "\n")))
106 | (return #f))))
107 | ; unit test
108 | ;(partners-import)
109 |
110 |
111 | ; EOF
112 |
--------------------------------------------------------------------------------
/csv-import.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require csv-reading
3 | "pmap.rkt") ; see pmap package from the same author
4 | (provide import-csv) ; (import-csv file processor-func (delimiter #\,))
5 |
6 | ;;; purpose
7 |
8 | ; an easy-to-use library to import data from CSV files
9 |
10 | ;;; version history
11 |
12 | ; v1.1 - parallelized using pmap
13 | ; v1.0 - initial version
14 |
15 | ;;; defs
16 |
17 | ;; imports the given CSV file using the provided processor-func and optional, default comma delimiter
18 | ;; returns a list of results from the application of the processor-func
19 | (define (import-csv file processor-func (delimiter #\,))
20 | (let/cc return
21 | ; if no file, return
22 | (unless file
23 | (return #f))
24 | ; create a CSV parser with Excel's default UTF8 CSV specs
25 | (define make-partner-csv-reader
26 | (make-csv-reader-maker
27 | (list (cons 'separator-chars (list delimiter))
28 | (cons 'strip-leading-whitespace? #t)
29 | (cons 'strip-trailing-whitespace? #t))))
30 | ; create a reader for our CSV file
31 | (define reader
32 | (make-partner-csv-reader (open-input-file file)))
33 | ; skip headers
34 | (reader)
35 | ; process lines, gather results
36 | (csv-map processor-func reader)))
37 |
38 | ;; parallel version of the previous procedure
39 | ;; returns a list of results from the application of the processor-func
40 | (define (import-csv! file processor-func (delimiter #\,))
41 | (let/cc return
42 | ; if no file, return
43 | (unless file
44 | (return #f))
45 | ; create a CSV parser with Excel's default UTF8 CSV specs
46 | (define make-partner-csv-reader
47 | (make-csv-reader-maker
48 | (list (cons 'separator-chars (list delimiter))
49 | (cons 'strip-leading-whitespace? #t)
50 | (cons 'strip-trailing-whitespace? #t))))
51 | ; create a reader for our CSV file
52 | (define reader
53 | (make-partner-csv-reader (open-input-file file)))
54 | ; skip headers
55 | (reader)
56 | ; read csv as a list
57 | (define csv-lines (csv->list reader))
58 | ; process lines in parallel, gather results
59 | (pmap processor-func csv-lines)))
60 |
61 |
62 |
63 | ; EOF
64 |
--------------------------------------------------------------------------------
/dialogs.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/gui
2 | (require mrlib/path-dialog)
3 | (provide centered-listbox-dialog ; (centered-listbox-dialog title message initial-listbox-contents style width-ratio height-ratio)
4 | die ; (die msg)
5 | die# ; (die# msg)
6 | get-directory-list ; (get-directory-list title msg path)
7 | get-directory-list-w-prefix ; (get-directory-list-w-prefix title msg path folder_prefix)
8 | get-single-directory ; (get-single-directory title msg path)
9 | get-string-or-die ; (get-string-or-die msg error)
10 | hide-loading ; (hide-loading)
11 | listbox-dialog ; (listbox-dialog title message initial-listbox-contents style)
12 | listbox-dialog# ; (listbox-dialog# title message headers initial-listbox-contents selection-type width height)
13 | listbox-selectall ; (listbox-selectall list-box item-count select?)
14 | my-get-file-list ; (my-get-file-list message path filetype_name filetype_pattern)
15 | msgbox ; (msgbox message)
16 | populate-listbox ; (populate-listbox listbox listbox-contents)
17 | show-error-message ; (show-error-message message)
18 | show-loading ; (show-loading)
19 | show-confirmation-dialog ; (show-confirmation-dialog message)
20 | show-warning-message) ; (show-warning-message message)
21 | (module+ test
22 | (require rackunit))
23 |
24 | ;;; purpose
25 |
26 | ; a library of useful dialogs and interactive message boxes
27 |
28 | ;;; version history
29 |
30 | ; v1.0 - this version.
31 |
32 | ;;; defs
33 |
34 | ;; displays a text message box
35 | (define msgbox
36 | (λ args
37 | (void (message-box *appname*
38 | (string-append (apply ~a args) " ")
39 | #f
40 | (list 'ok)))))
41 |
42 | ;; displays an error message before quitting
43 | (define die
44 | (λ args
45 | (show-error-message (apply ~a args))
46 | (exit 1)))
47 |
48 | ;; command line version of die
49 | (define die#
50 | (λ args
51 | (displayln (apply ~a args))
52 | (exit 1)))
53 |
54 | ;; show a confirmation dialog. Returns true if user confirmed, false otherwise
55 | (define show-confirmation-dialog
56 | (λ args
57 | (eq? (message-box *appname* (apply ~a args) #f (list 'yes-no))
58 | 'yes)))
59 |
60 | ;; ask user to enter a string, quit with the given error message if none provided
61 | (define (get-string-or-die msg error)
62 | (define input
63 | (get-text-from-user *appname* msg))
64 | (unless (non-empty-string? input)
65 | (die error))
66 | (string-trim input))
67 |
68 | ;; a generic loading dialog
69 | (define loading-dialog
70 | (let* ((new-dialog (new dialog%
71 | [label "Loading"]
72 | [parent #f]
73 | [width 225]
74 | [height 80]
75 | [min-width 225]
76 | [min-height 80]
77 | [stretchable-width #f]
78 | [stretchable-height #f]))
79 | (new-panel (new vertical-panel% [parent new-dialog]
80 | [alignment '(center center)]))
81 | (new-message (new message% [parent new-panel]
82 | [label "Please wait..."])))
83 | new-dialog))
84 |
85 | ; Helper function to show the loading dialog
86 | (define (show-loading)
87 | (send loading-dialog show-without-yield))
88 |
89 | ; Helper function to hide the loading dialog
90 | (define (hide-loading)
91 | (send loading-dialog show #f))
92 |
93 | ;; Generic warning message dialog
94 | (define show-warning-message
95 | (λ args
96 | (void (message-box "Warning"
97 | (string-append (apply ~a args) " ")
98 | #f
99 | (list 'ok 'caution)))))
100 |
101 | ;; displays an error dialog
102 | (define show-error-message
103 | (λ args
104 | (message-box *appname* (apply ~a args) #f '(ok stop))))
105 |
106 | ; Ask user to select a list of scenes - could be used to obtain scene list.
107 | ; Example: (getfilelist "Please select a list of scenes." "C:\\" "Harmony Scene" "scene-*")
108 | (define (my-get-file-list message path filetype_name filetype_pattern)
109 | (get-file-list message
110 | #f ;parent
111 | (string->path path) ;directory
112 | #f ;filename
113 | #f ;extension
114 | null ;style
115 | (list (list filetype_name filetype_pattern)) )) ;filters
116 |
117 | ;; Ask user to select a list of scenes - could be used to obtain scene list.
118 | ;; Example:
119 | ;; (get-directory-list appname "Please select a list of scenes:" (string-append filesystem "\\" job)
120 | ;; "scene-")
121 | (define dir-ok?
122 | (λ (x) (non-empty-string? x)))
123 |
124 | (define (get-single-directory title msg path)
125 | (begin
126 | (define pathdialog (new path-dialog%
127 | [label title]
128 | [message msg]
129 | [directory path]
130 | [dir? #t]
131 | [ok? dir-ok?]
132 | [filters null]))
133 | (send pathdialog run)))
134 |
135 | ;; Ask user to select a list of scenes - could be used to obtain scene list.
136 | ;; Example:
137 | ;; (get-directory-list appname "Please select a list of scenes:" (string-append filesystem "\\" job)
138 | ;; "scene-")
139 | (define (get-directory-list title msg path)
140 | (begin
141 | (define pathdialog (new path-dialog%
142 | [label title]
143 | [message msg]
144 | [directory path]
145 | [dir? #t]
146 | [multi? #t]
147 | [ok? dir-ok?]
148 | [filters null]))
149 | (send pathdialog run)))
150 |
151 | ;; Ask user to select a list of scenes - could be used to obtain scene list.
152 | ;; Example:
153 | ;; (get-directory-list appname "Please select a list of scenes:" (string-append filesystem "\\" job)
154 | ;; "scene-")
155 | (define (get-directory-list-w-prefix title msg path folder_prefix)
156 | (begin
157 | (define pathdialog (new path-dialog%
158 | [label title]
159 | [message msg]
160 | [directory path]
161 | [dir? #t]
162 | [multi? #t]
163 | [filters null]
164 | ; [show-dir? (λ (x) (string-prefix? x folder_prefix))]
165 | [ok? (λ (x) (string-prefix? x folder_prefix))]
166 | ))
167 | (send pathdialog run)))
168 |
169 | ; Helper function to select/deselect all items in a list-box
170 | (define (listbox-selectall list-box item-count select?)
171 | (let ((select-item (lambda (item) (send list-box select item select?))))
172 | (for ([x (in-range item-count)]) (select-item x))))
173 |
174 | ; Helper function to populate a list-box control
175 | (define (populate-listbox listbox listbox-contents)
176 | (send listbox set listbox-contents))
177 |
178 | ; List-box dialog with 'select all' and a name filter
179 | (define (listbox-dialog title message initial-listbox-contents style)
180 | (let* ((dialog (new dialog%
181 | [label title]
182 | [parent #f]
183 | [width 320]
184 | [height 480]
185 | [min-width 320]
186 | [min-height 480]))
187 | ;[stretchable-width #f]
188 | ;[stretchable-height #f]
189 |
190 | ; Currenr listbox contents
191 | (current-listbox-contents initial-listbox-contents)
192 | (current-selected-contents null)
193 |
194 | ; Control defs
195 | (top-panel (new vertical-panel% [parent dialog]
196 | [alignment '(center center)]))
197 |
198 | (new-message (new message% [parent top-panel]
199 | [label message]))
200 |
201 | (new-listbox
202 | (new list-box%
203 | [label #f]
204 | [choices initial-listbox-contents]
205 | [parent top-panel]
206 | [style (list style)]
207 | [callback
208 | (lambda (l e)
209 | (let ((event-type (send e get-event-type)))
210 | (if (equal? event-type 'list-box-dclick)
211 | (let* ((current-listbox-selection (send l get-selections))
212 | (selected-contents (map (curry list-ref current-listbox-contents)
213 | current-listbox-selection)))
214 | (begin
215 | (set! current-selected-contents selected-contents)
216 | (send dialog show #f)))
217 | null)))]))
218 |
219 | (bottom-panel (new horizontal-panel%
220 | [parent dialog]
221 | [alignment '(center bottom)]
222 | [stretchable-height #f]))
223 |
224 | (bottom-left-panel (new horizontal-panel%
225 | [parent bottom-panel]
226 | [alignment '(left bottom)]))
227 |
228 | (bottom-right-panel (new horizontal-panel%
229 | [parent bottom-panel]
230 | [alignment '(right bottom)]))
231 |
232 | ; Callbacks
233 | (selectall-button-callback
234 | (lambda (b e)
235 | (listbox-selectall new-listbox (length current-listbox-contents) #t)))
236 |
237 | (cancel-button-callback
238 | (lambda (b e)
239 | (begin
240 | (set! current-selected-contents #f)
241 | (send dialog show #f))))
242 |
243 | (ok-button-callback
244 | (lambda (b e)
245 | (let* ((current-listbox-selection (send new-listbox get-selections))
246 | (selected-contents (map (curry list-ref current-listbox-contents)
247 | current-listbox-selection)))
248 | (begin
249 | (set! current-selected-contents selected-contents)
250 | (send dialog show #f)))))
251 |
252 | (filter-textfield-callback
253 | (lambda (t e)
254 | (let ((new-listbox-contents
255 | (filter (curryr string-contains? (send t get-value)) initial-listbox-contents)))
256 | (begin
257 | (set! current-listbox-contents new-listbox-contents)
258 | (populate-listbox new-listbox new-listbox-contents)))))
259 |
260 | ; Buttons defs
261 | (filter-textfield (new text-field%
262 | [label "Filter: "]
263 | [parent bottom-left-panel]
264 | [callback filter-textfield-callback]))
265 |
266 | (selectall-button (new button%
267 | [label "Select All"]
268 | [enabled (if (equal? style 'single) #f #t)] ; Enable or disable
269 | [parent bottom-left-panel] ; the Select All button
270 | [callback selectall-button-callback])) ; depending on style
271 |
272 | (cancel-button (new button%
273 | [label "Cancel"]
274 | [parent bottom-right-panel]
275 | [callback cancel-button-callback]))
276 |
277 | (ok-button (new button%
278 | [label "OK"]
279 | [parent bottom-right-panel]
280 | [callback ok-button-callback])))
281 | (begin
282 | (send dialog show #t)
283 | current-selected-contents))) ; Return list of selected items (null if cancel pressed)
284 |
285 | ; Unit test
286 | ;(listbox-dialog "PaletteCopier"
287 | ; "Please select destination elements:"
288 | ; (list "Naice" "Sweet" "Dude")
289 | ; 'multiple)
290 |
291 | ;; List-box dialog with 'select all', name filter and support for columns
292 | ;; Returns list of selected items, null otherwise
293 | ;; initial-listbox-contents is a list of columns
294 | (define (listbox-dialog# title message headers initial-listbox-contents selection-type width height)
295 | (let* ((dialog (new dialog%
296 | [label title]
297 | [parent #f]
298 | [border 10]
299 | [spacing 10]
300 | [style (list 'resize-border)]
301 | [width width]
302 | [height height]
303 | [min-width width]
304 | [min-height height]))
305 | ;[stretchable-width #f]
306 | ;[stretchable-height #f]
307 |
308 | ; Currenr listbox contents
309 | (current-listbox-contents initial-listbox-contents)
310 | (current-selected-contents null)
311 |
312 | ; Control defs
313 | (top-panel (new vertical-panel% [parent dialog]
314 | [alignment '(center center)]))
315 |
316 | (new-message (new message% [parent top-panel]
317 | [label message]))
318 |
319 | (new-listbox (new list-box%
320 | [label #f]
321 | [columns headers]
322 | [choices '()]
323 | [parent top-panel]
324 | [style (cons selection-type
325 | '(variable-columns
326 | clickable-headers
327 | column-headers
328 | reorderable-headers))]
329 | [callback
330 | (lambda (l e)
331 | (let ((event-type (send e get-event-type)))
332 | (if (equal? event-type 'list-box-dclick)
333 | (let* ((current-listbox-selection (send l get-selections))
334 | (selected-contents (map (curry list-ref current-listbox-contents)
335 | current-listbox-selection)))
336 | (begin
337 | (set! current-selected-contents selected-contents)
338 | (send dialog show #f)))
339 | null)))]))
340 |
341 | (bottom-panel
342 | (new horizontal-panel%
343 | [parent dialog]
344 | [alignment '(center bottom)]
345 | [stretchable-height #f]))
346 |
347 | (bottom-left-panel
348 | (new horizontal-panel%
349 | [parent bottom-panel]
350 | [alignment '(left bottom)]))
351 |
352 | (bottom-right-panel
353 | (new horizontal-panel%
354 | [parent bottom-panel]
355 | [alignment '(right bottom)]))
356 |
357 | ; Callbacks
358 | (selectall-button-callback
359 | (lambda (b e)
360 | (listbox-selectall new-listbox (length current-listbox-contents) #t)))
361 |
362 | (cancel-button-callback
363 | (lambda (b e)
364 | (begin
365 | (set! current-selected-contents #f)
366 | (send dialog show #f))))
367 |
368 | (ok-button-callback
369 | (lambda (b e)
370 | (let* ((current-listbox-selection (send new-listbox get-selections))
371 | (selected-contents
372 | (map (curry list-ref current-listbox-contents)
373 | current-listbox-selection)))
374 | (begin
375 | (set! current-selected-contents selected-contents)
376 | (send dialog show #f)))))
377 |
378 | (filter-textfield-callback
379 | (lambda (t e)
380 | ; grab user input in text field
381 | (define search-str
382 | (send t get-value))
383 | ; utility func to find out if a list contains a string anywhere
384 | (define (str-list-contains? l)
385 | (ormap (λ (s)
386 | (string-contains? s search-str))
387 | l))
388 | ; filter list and re-populate listbox
389 | (let ((new-listbox-contents
390 | (filter str-list-contains? initial-listbox-contents)))
391 | (begin
392 | (set! current-listbox-contents new-listbox-contents)
393 | (populate-listbox# new-listbox new-listbox-contents)))))
394 |
395 | ; Buttons defs
396 | (filter-textfield
397 | (new text-field%
398 | [label "Filter: "]
399 | [parent bottom-left-panel]
400 | [callback filter-textfield-callback]))
401 |
402 | (selectall-button
403 | (new button%
404 | [label "Select All"]
405 | [enabled (if (equal? selection-type 'single) #f #t)] ; Enable or disable the Select All button depending on global style
406 | [parent bottom-left-panel]
407 | [callback selectall-button-callback]))
408 |
409 | (cancel-button
410 | (new button%
411 | [label "Cancel"]
412 | [parent bottom-right-panel]
413 | [callback cancel-button-callback]))
414 |
415 | (ok-button
416 | (new button%
417 | [label "OK"]
418 | [parent bottom-right-panel]
419 | [callback ok-button-callback])))
420 | (begin
421 | (populate-listbox# new-listbox headers initial-listbox-contents)
422 | (send dialog show #t)
423 | current-selected-contents)))
424 |
425 | ; Unit test
426 | ;(listbox-dialog# "Some Title"
427 | ; "Please select stuff:"
428 | ; (list "Field" "Value")
429 | ; (list (list "Naice" "Sweet")
430 | ; (list "Dude" "Naice"))
431 | ; 'multiple 480 640)
432 |
433 | ; Display a generic, centered dialog using the width and height ratios (2 by default)
434 | (define (centered-listbox-dialog title message initial-listbox-contents style width-ratio height-ratio)
435 | (let* ((dialog (let*-values ([(display-width display-height) (get-display-size)]
436 | [(dialog-width) (round (/ display-width width-ratio))]
437 | [(dialog-height) (round (/ display-height height-ratio))])
438 | (new dialog%
439 | [label title]
440 | [x (- (round (/ display-width 2)) (round (/ dialog-width 2)))]
441 | [y (- (round (/ display-height 2)) (round (/ dialog-height 2)))]
442 | [width dialog-width]
443 | [height dialog-height])))
444 |
445 | ; Control defs
446 | (top-panel (new vertical-panel% [parent dialog]
447 | [alignment '(center center)]))
448 |
449 | (new-message (new message% [parent top-panel]
450 | [label message]))
451 |
452 | (new-listbox (new list-box%
453 | [label #f]
454 | [choices initial-listbox-contents]
455 | [parent top-panel]
456 | [style (list style)]))
457 |
458 | (bottom-panel (new horizontal-panel%
459 | [parent dialog]
460 | [alignment '(center bottom)]
461 | [stretchable-height #f]))
462 |
463 | (bottom-left-panel (new horizontal-panel%
464 | [parent bottom-panel]
465 | [alignment '(left bottom)]))
466 |
467 | (bottom-right-panel (new horizontal-panel%
468 | [parent bottom-panel]
469 | [alignment '(right bottom)]))
470 |
471 | ; Currenr listbox contents
472 | (current-listbox-contents initial-listbox-contents)
473 | (current-selected-contents null)
474 |
475 | ; Callbacks
476 | (selectall-button-callback (lambda (b e) (listbox-selectall new-listbox (length current-listbox-contents) #t)))
477 |
478 | (cancel-button-callback (lambda (b e) (begin
479 | (set! current-selected-contents #f)
480 | (send dialog show #f))))
481 |
482 | (ok-button-callback (lambda (b e) (let* ((current-listbox-selection (send new-listbox get-selections))
483 | (selected-contents (map (curry list-ref current-listbox-contents) current-listbox-selection)))
484 | (begin
485 | (set! current-selected-contents selected-contents)
486 | (send dialog show #f)))))
487 |
488 | (filter-textfield-callback (lambda (t e) (let ((new-listbox-contents (filter (curryr string-contains? (send t get-value)) initial-listbox-contents)))
489 | (begin
490 | (set! current-listbox-contents new-listbox-contents)
491 | (populate-listbox new-listbox new-listbox-contents)))))
492 |
493 | ; Buttons defs
494 | (filter-textfield (new text-field%
495 | [label "Filter: "]
496 | [parent bottom-left-panel]
497 | [callback filter-textfield-callback]))
498 |
499 | (selectall-button (new button%
500 | [label "Select All"]
501 | [enabled (if (equal? style 'single) #f #t)] ; Enable or disable the Select All button depending on global style
502 | [parent bottom-left-panel]
503 | [callback selectall-button-callback]))
504 |
505 | (cancel-button (new button%
506 | [label "Cancel"]
507 | [parent bottom-right-panel]
508 | [callback cancel-button-callback]))
509 |
510 | (ok-button (new button%
511 | [label "OK"]
512 | [parent bottom-right-panel]
513 | [callback ok-button-callback])))
514 | (begin
515 | (send dialog show #t)
516 | current-selected-contents))) ; Return list of selected items (null if cancel pressed)
517 | ; unit test
518 | ;(centered-listbox-dialog "Title" "Message" '("Sweet" "Dude" "Naice") 'single 3 1)
519 |
520 | ; EOF
521 |
--------------------------------------------------------------------------------
/gui.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/gui
2 |
3 | (provide get-bitmap ; (get-bitmap filename width height)
4 | is-checked? ; (is-checked? check-box)
5 | make-elastic-frame) ; (make-elastic-frame appname)
6 |
7 | ;;; purpose
8 |
9 | ; to provide a collection of useful gui functions
10 |
11 | ;;; version history
12 |
13 | ; v1.0 - this version.
14 |
15 | ;;; defs
16 |
17 | ;; returns true if check-box is checked, false otherwise
18 | (define (is-checked? cb)
19 | (send cb get-value))
20 |
21 | ;; generates a small frame to fill with controls
22 | (define (make-elastic-frame appname)
23 | (new (class frame% (super-new)
24 | (define/augment (on-close)
25 | (exit 0)))
26 | [label appname]
27 | [width 0]
28 | [height 0]
29 | [stretchable-width 1024]
30 | [stretchable-height #f]))
31 |
32 | ;; generate a bitmap from a filename
33 | (define (get-bitmap filename width height)
34 | (let ((b (make-object bitmap% width height #f #t 1.0)))
35 | ; (make-object bitmap% (/ button-size 2) (/ button-size 2))
36 | (send b load-file filename)
37 | b))
38 |
39 |
40 | ; EOF
41 |
--------------------------------------------------------------------------------
/hash.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/format
3 | racket/list
4 | racket/string)
5 |
6 | (provide hash->string-list ; (hash->string-list h)
7 | hash->sorted-string-list ; (hash->sorted-string-list h)
8 | hash->flat-sorted-string-list ; (hash->flat-sorted-string-list h)
9 | maybe-hash) ; (maybe-hash cond possible-hash)
10 |
11 | (module+ test
12 | (require rackunit))
13 |
14 | ;;; purpose
15 |
16 | ; to provide several useful hash functions.
17 |
18 | ;;; defs
19 |
20 | ;; used along hash-union can conditionally and funcitonally build a hash bit by bit
21 | (define (maybe-hash cond possible-hash)
22 | (and (hash? possible-hash)
23 | (if cond possible-hash (hash))))
24 |
25 | ;; converts a hash table to a list of list of strings
26 | (define (hash->string-list h)
27 | (map (λ (i) (list (~a (car i)) (~a (cdr i)))) (hash->list h)))
28 | ; unit test
29 | (module+ test
30 | (check-equal? (hash->string-list
31 | (hash 'a 1 'b 2))
32 | '(("b" "2") ("a" "1"))))
33 |
34 | ;; converts a hash table to a list of list of strings
35 | (define (hash->sorted-string-list h)
36 | (let ((unsorted (map (λ (i)
37 | (if (hash? (cdr i))
38 | (list (~a (car i)) (hash->sorted-string-list (cdr i)))
39 | (list (~a (car i)) (~a (cdr i)))))
40 | (hash->list h))))
41 | (sort unsorted (λ (x y) (string (car x) (car y))))))
42 | ; unit test
43 | (module+ test
44 | (check-equal? (hash->sorted-string-list
45 | (hash 'a 1 'b 2))
46 | '(("a" "1") ("b" "2")))
47 | (check-equal? (hash->sorted-string-list
48 | (hash 'a 1 'b (hash 'c 2 'd 3)))
49 | '(("a" "1") ("b" (("c" "2") ("d" "3"))))))
50 |
51 | ;; converts a hash table to a flat list of strings, ignoring first items in second lists
52 | (define (hash->flat-sorted-string-list h)
53 | (let ((unsorted (map (λ (i)
54 | (if (hash? (cdr i))
55 | (flatten (list (~a (car i)) (hash->sorted-string-list (cdr i))))
56 | (list (~a (car i)) (~a (cdr i)))))
57 | (hash->list h))))
58 | (sort unsorted (λ (x y) (string (car x) (car y))))))
59 | ; unit test
60 | (module+ test
61 | (check-equal? (hash->flat-sorted-string-list
62 | (hash 'a 1 'b 2))
63 | '(("a" "1") ("b" "2")))
64 | (check-equal? (hash->flat-sorted-string-list
65 | (hash 'a 1 'b (hash 'c 2 'd 3)))
66 | '(("a" "1") ("b" "c" "2" "d" "3"))))
67 |
68 |
69 | ; EOF
70 |
--------------------------------------------------------------------------------
/io.rkt:
--------------------------------------------------------------------------------
1 | #lang racket
2 | (require "commons.rkt") ; (multi-replace-lines lines source-list destination)
3 | (provide add-file-extension ; (add-file-extension filename extension)
4 | add-file-extensions ; (add-file-extensions filenames extension)
5 | copy-file-to-folders ; (copy-file-to-folders source-path destination-list overwrite?)
6 | copy-or-die ; (copy-or-die src dest)
7 | create-folders-or-die ; (create-folders-or-die paths)
8 | create-list-of-files ; (create-list-of-files filenames content)
9 | directories-exist? ; (directories-exist? list-of-dirs)
10 | directory-list-str ; (directory-list-str path)
11 | display-error-count ; (display-error-count result msg)
12 | filename-path->string ; (filename-path->string filename-path)
13 | find-files# ; (find-files# pred path)
14 | file-path? ; (file-path? path)
15 | file-paths? ; (file-paths? paths)
16 | folder-path? ; (folder-path? path)
17 | folder-paths? ; (folder-paths? paths)
18 | get-error-count ; (get-error-count result msg)
19 | get-file-extension ; (get-file-extension filename-path)
20 | get-file-extensions ; (get-file-extensions filenames)
21 | get-file-lines ; (get-file-lines path)
22 | get-file-list-from-prefix-ext ; (get-file-list-from-prefix-ext path prefix extension)
23 | get-filename ; (get-filename filename-w-ext)
24 | get-filenames ; (get-filenames filenames-w-ext)
25 | get-file-name ; (get-file-name path)
26 | get-last-path-part ; (get-last-path-part path)
27 | list->file ; (list->file l file)
28 | make-backup-file ; (make-backup-file path)
29 | maybe-copy-file ; (maybe-copy-file source destination error-message exists-ok?)
30 | move-or-die ; (move-or-die src dest)
31 | path! ; (path! path-or-string)
32 | path ; (path p1 p2)
33 | process-text-files ; (process-text-files process file-list)
34 | replace-filename-in-path ; (replace-filename-in-path full-path new-filename)
35 | run-if-not-exists ; (run-if-not-exists list-of-files operation)
36 | sort-paths ; (sort-paths paths)
37 | write-file-lines) ; (write-file-lines lines path)
38 | (module+ test
39 | (require rackunit))
40 |
41 | ;;; purpose
42 |
43 | ; a library of file I/O functions
44 |
45 | ;;; version history
46 |
47 | ; v1.0 - initial release for Overrider
48 | ; v1.1 - for TextureFixer
49 | ; v1.2 - added find-files#, directory-list-str
50 | ; v1.3 - added file-path?, file-paths?, folder-path?, folder-paths?, copy-or-die, move-or-die
51 | ; create-folders-or-die, get-file-name, path, sort-paths.
52 |
53 | ;;; defs
54 |
55 | ;; find-files replacement
56 | (define (find-files# pred path)
57 | (define lst (directory-list path))
58 | (define results (map pred lst))
59 | (define filenames (mask results lst))
60 | (define (build filename) (build-path path filename))
61 | (map build filenames))
62 | ; unit test
63 | ;(module+ test
64 | ; (define (frame? filename-path)
65 | ; (let ((filename-text (if (path? filename-path)
66 | ; (filename-path->string filename-path)
67 | ; filename-path))) ; convert path to string if necessary
68 | ; (or (string-suffix? filename-text ".sgi")
69 | ; (string-suffix? filename-text ".tga")
70 | ; (string-suffix? filename-text ".dpx"))))
71 | ;
72 | ; (find-files# frame? "\\\\zeus\\usadata0\\Proteus\\usadata0\\833T-303\\scene-026\\frames"))
73 |
74 | ;; returns a string list of filenames from the path
75 | (define (directory-list-str path)
76 | (map (λ (path) (path->string path))
77 | (directory-list path #:build? #t)))
78 |
79 | ;; convert filename path to string if necessary
80 | (define (filename-path->string filename-path)
81 | (if (path? filename-path) (path-element->string filename-path) filename-path))
82 |
83 | ;; create a list of files with the same provided content
84 | (define (create-list-of-files filenames content)
85 | (let ((create-text-file (λ (file) (display-lines-to-file content file
86 | #:separator #"\n"
87 | #:mode 'binary
88 | #:exists 'replace))))
89 | (map create-text-file filenames)))
90 |
91 | ;; Runs a file operation only if the first file in the list doesn't exist
92 | (define (run-if-not-exists list-of-files operation)
93 | (if (not (file-exists? (first list-of-files)))
94 | (operation)
95 | (display "Files already exist. Skipping...\n")))
96 | ; unit test
97 | ; (run-if-not-exists list-of-files (create-list-of-files list-of-files file-lines))
98 |
99 | ;; Displays a file operation's error count with custom message from its result.
100 | (define (get-error-count result msg)
101 | (if (null? result) (string-append "No " msg ".")
102 | (string-append (number->string (length result)) " " msg " with "
103 | (number->string (count (λ (r) (equal? r 'error)) result)) " error(s).")))
104 | ; unit test
105 | ;(check-equal? (get-error-count (process-text-files my-process list-of-files) "files processed")
106 | ; "3 text files processed with 0 error(s).")
107 |
108 | ;; Displays a file operation's error count with custom message from its result.
109 | (define (display-error-count result msg)
110 | (if (null? result) (display (string-append "No " msg "."))
111 | (display
112 | (string-append (number->string (length result)) " " msg " with "
113 | (number->string (count (λ (r) (equal? r 'error)) result)) " error(s)."))))
114 | ; unit test
115 | ;(display-error-count (process-text-files my-process list-of-files) "files processed")
116 | ; --> "X text files processed with 0 error(s)."
117 |
118 | ;; applies an operation to a list of files, line by line :
119 | ; - load each file as a list;
120 | ; - apply the process to the list;
121 | ; - save the file.
122 | (define (process-text-files process file-list)
123 | (if (and (procedure? process)
124 | (non-empty-list? file-list))
125 | (let ((process (λ (file)
126 | (let* ((file-lines (file->lines file #:line-mode 'any #:mode 'binary))
127 | (new-lines (map process file-lines)))
128 | (begin (make-backup-file file) ; make a backup file before update
129 | (display-lines-to-file new-lines file
130 | #:separator #"\n"
131 | #:mode 'binary
132 | #:exists 'replace))))))
133 | (map process file-list))
134 | null))
135 | ; unit test
136 | ;(define (my-process line) (string-replace line "source" "destination"))
137 | ;(display-error-count (process-text-files my-process list-of-files) "text files processed")
138 | ; --> "3 text files processed with 0 error(s)."
139 |
140 | ; Predicate that tests of all items in the list are paths of directories and exist
141 | (define (directories-exist? list-of-dirs)
142 | (if (list? list-of-dirs)
143 | (if (andmap path? list-of-dirs)
144 | (andmap directory-exists? list-of-dirs)
145 | #f)
146 | #f))
147 |
148 | ; Writes a backup file in the same folder - supports paths and string-paths
149 | (define (make-backup-file path)
150 | (if (path-for-some-system? path)
151 | (copy-file path (string->path (string-append (path->string path) ".old")) #t)
152 | (copy-file path (string-append path ".old") #t)))
153 |
154 | ; Add a file extension to a string if string isn't empty
155 | (define (add-file-extension filename extension)
156 | (if (non-empty-string? filename)
157 | (string-append filename extension)
158 | ""))
159 |
160 | ; Add a file extension to a list of strings if not empty
161 | (define (add-file-extensions filenames extension)
162 | (if (non-empty-list? filenames)
163 | (map (curryr string-append extension) filenames)
164 | null))
165 |
166 | ; Copy one file to multiple folders
167 | (define (copy-file-to-folders source-path destination-list overwrite?)
168 | (let ((copy-func (lambda (dest)
169 | (begin ; Delete file prior to copy as copy-file doesn't seem to overwrite files
170 | (if (and overwrite? (file-exists? dest)) (delete-file dest) null)
171 | (copy-file source-path dest overwrite?))))
172 | (dest-list (map
173 | (curry ; curry fonction to build destination path
174 | (lambda (source destination)
175 | (build-path destination (last (explode-path source))))
176 | source-path)
177 | destination-list)))
178 | (for-each copy-func dest-list)))
179 |
180 | ; returns the last part of a path (the file name)
181 | (define (get-last-path-part path)
182 | (if (non-empty-string? path)
183 | (if (string-contains? path "\\")
184 | (last (string-split path "\\"))
185 | (last (string-split path "/")))
186 | ""))
187 | ; unit test
188 | (module+ test
189 | (check-equal?
190 | (get-last-path-part "/USA_DB/jobs/833T-201/palette-library/00C_BNTWNB_Baron_Thug_Twin_B.plt")
191 | "00C_BNTWNB_Baron_Thug_Twin_B.plt")
192 | (check-equal?
193 | (get-last-path-part "00C_BNTWNB_Baron_Thug_Twin_B.plt")
194 | "00C_BNTWNB_Baron_Thug_Twin_B.plt")
195 | (check-equal?
196 | (get-last-path-part "")
197 | ""))
198 |
199 | ; returns the filename without extension
200 | (define (get-filename filename-w-ext)
201 | (if (non-empty-string? filename-w-ext)
202 | (first (string-split filename-w-ext "."))
203 | ""))
204 | ; unit test
205 | (module+ test
206 | (check-equal? (get-filename "00C_BNTWNB_Baron_Thug_Twin_B.plt") "00C_BNTWNB_Baron_Thug_Twin_B")
207 | (check-equal? (get-filename "") ""))
208 |
209 | ; returns a list of filenames without their extension
210 | (define (get-filenames filenames-w-ext)
211 | (if (list? filenames-w-ext)
212 | (map get-filename filenames-w-ext)
213 | null))
214 | ; unit test
215 | (module+ test
216 | (check-equal?
217 | (get-filenames '("00C_BNTWNB_Baron_Thug_Twin_A.plt" "00C_BNTWNB_Baron_Thug_Twin_B.plt"))
218 | '("00C_BNTWNB_Baron_Thug_Twin_A" "00C_BNTWNB_Baron_Thug_Twin_B"))
219 | (check-equal? (get-filenames '()) null))
220 |
221 | ;; returns filename extension
222 | (define (get-file-extension filename-path)
223 | (let ((filename-text (filename-path->string filename-path))) ; convert path to string if necessary
224 | (string-append "." (last (string-split filename-text ".")))))
225 | ; unit test
226 | (module+ test
227 | (check-equal? (get-file-extension "final-0001.cgi") ".cgi"))
228 |
229 | ;; returns filename extensions from a list of filenames
230 | (define (get-file-extensions filenames)
231 | (if (non-empty-list? filenames) (map get-file-extension filenames)
232 | null))
233 | ; unit test
234 | (module+ test
235 | (check-equal? (get-file-extensions (list "final-0001.cgi" "frame-0590.tga")) (list ".cgi" ".tga")))
236 |
237 | ;; automatically converts a string to path if it's a string, else keeps the path
238 | (define (path! path-or-string)
239 | (if (path-for-some-system? path-or-string) path-or-string (string->path path-or-string)))
240 |
241 | ;; returns a list of lines from a file - supports paths and string-paths
242 | (define (get-file-lines path)
243 | (file->lines (path! path) #:mode 'binary #:line-mode 'any))
244 | ; unit test
245 | ;(get-file-lines "C:\\TEMP\\_TX-palette-changes\\scene-382\\elements\\VRNSTCU_Staff_2\\PALETTE_LIST")
246 |
247 | ;; writes a list of file lines to disk - supports paths and string-paths
248 | (define (write-file-lines lines path)
249 | (display-lines-to-file lines (path! path) #:mode 'binary #:exists 'replace))
250 |
251 | ;; returns a list of files which match a prefix and an extension from a root path
252 | (define (get-file-list-from-prefix-ext path prefix extension)
253 | (define (file-pred filepath)
254 | (let ((filename (path->string (file-name-from-path filepath))))
255 | (if (and (string-prefix? filename prefix)
256 | (string-suffix? filename extension)) #t #f)))
257 | (find-files file-pred path))
258 |
259 | ; Predicate that tests of all items in the list are paths of directories and exist
260 | (define (directories-exist? l)
261 | (if (list? l)
262 | (if (andmap path? l)
263 | (andmap directory-exists? l)
264 | #f)
265 | #f))
266 |
267 | ;; replace a filename in a full path with another filename
268 | (define (replace-filename-in-path full-path new-filename)
269 | (string-append (path->string (path-only full-path)) new-filename))
270 | ; unit test
271 | (module+ test
272 | (check-equal? (replace-filename-in-path (string->path "C:\\test\\path\\some-folder") "package-name")
273 | "C:\\test\\path\\package-name"
274 | "get-package-path-string"))
275 |
276 | ;; returns the name of a folder from its path as string
277 | (define (get-file-name path)
278 | (path->string (last (explode-path path))))
279 |
280 | ;; helper function to sort paths - converts two paths to a string and compare them
281 | (define (path p1 p2)
282 | (if (and (path? p1) (path? p2))
283 | (string (path->string p1) (path->string p2))
284 | #f))
285 |
286 | ;; sort a list of paths alphabetically
287 | (define (sort-paths paths)
288 | (if (andmap path? paths)
289 | (sort paths path)
290 | null))
291 |
292 | ;; create folders automatically from a list, with support for exceptions
293 | (define (create-folders-or-die paths)
294 | (with-handlers ([exn:fail:filesystem? (λ (e) (begin (show-error-message "Unable to create directory structure. Access denied. Aborting. ")
295 | (exit)))])
296 | (for-each make-directory* paths)))
297 |
298 | ;; copies a file or folder to destination or die, with support for exceptions and copying a list of paths
299 | (define (copy-or-die src dest)
300 | (with-handlers ([exn:fail:filesystem? (λ (e) (begin (show-error-message "Unable to copy file/directory. Already exists? Aborting. ")
301 | (exit)))])
302 | (if (list? src) (for-each (λ (this-src) (copy-directory/files this-src (build-path dest (get-file-name this-src)))) src) ; if src is a list of paths, copy them all to destination
303 | (copy-directory/files src (build-path dest (get-file-name src)))))) ; else copy src to dest as usual.
304 |
305 | ;; moves a file or folder to destination or die, with support for exceptions and copying a list of paths
306 | (define (move-or-die src dest)
307 | (with-handlers ([exn:fail:filesystem? (λ (e) (begin (show-error-message "Unable to move file/directory. Already exists? Aborting. ")
308 | (exit)))])
309 | (if (list? src) (for-each (λ (this-src) (rename-file-or-directory this-src (build-path dest (get-file-name this-src)))) src) ; if src is a list of paths, copy them all to destination
310 | (rename-file-or-directory src (build-path dest (get-file-name src)))))) ; else copy src to dest as usual.
311 |
312 | ;; predicate returns true if argument is a path and points to an existing file.
313 | (define (file-path? path)
314 | (and (path? path)
315 | (file-exists? path)))
316 |
317 | ;; predicate returns true if argument is a list of paths which point to existing files.
318 | (define (file-paths? paths)
319 | (and (list? paths)
320 | (not (null? paths))
321 | (andmap file-path? paths)))
322 |
323 | ;; predicate returns true if argument is a path and points to an existing folder - supports paths and path-strings
324 | (define (folder-path? path)
325 | (and (or (path? path)
326 | (non-empty-string? path))
327 | (directory-exists? (if (path? path) path (string->path path)))))
328 |
329 | ;; predicate returns true if argument is a list of paths which point to existing files.
330 | (define (folder-paths? paths)
331 | (and (list? paths)
332 | (not (null? paths))
333 | (andmap folder-path? paths)))
334 |
335 | ;; attempts to cleanly copy a file with exception handling
336 | ;; displays an error if copy fails
337 | (define (maybe-copy-file source destination error-message exists-ok?)
338 | (when (and (non-empty-string? source) (file-exists? source))
339 | (with-handlers ([exn:fail:filesystem? (λ (e) (show-error-message error-message))])
340 | (when (and exists-ok? (file-exists? destination)) (delete-file destination)) ; Racket bugfix
341 | (copy-file source destination exists-ok?))))
342 |
343 | ;; write a list to file as is
344 | (define (list->file l file)
345 | (write-to-file l file #:exists 'replace #:mode 'text))
346 |
347 |
348 | ; EOF
349 |
--------------------------------------------------------------------------------
/menu-bar.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/gui
2 | (provide menu-bar
3 | ------------------------
4 | file-menu
5 | file-new
6 | file-open
7 | file-save
8 | file-save-as
9 | file-exit
10 | edit-menu
11 | edit-copy
12 | edit-paste
13 | edit-select-all
14 | help-menu
15 | help-about)
16 |
17 | ;;; purpose
18 |
19 | ; to provide a nice default menu bar.
20 |
21 | ;;; utilities
22 |
23 | ;; makes a separator
24 | (define (------------------------)
25 | (void (new separator-menu-item% (parent file-menu))))
26 |
27 | ;;; defs
28 |
29 | ;; main menu bar
30 | (define menu-bar
31 | (new menu-bar% (parent main-window)))
32 |
33 | ;; [file] menu
34 | (define file-menu
35 | (new menu% (parent menu-bar)
36 | (label "&File")))
37 |
38 | ; new menu item
39 | (define file-new
40 | (new menu-item% (parent file-menu)
41 | (label "&New")
42 | (callback (λ (c e) (void)))))
43 |
44 | (------------------------)
45 |
46 | ; open menu item
47 | (define file-open
48 | (new menu-item% (parent file-menu)
49 | (label "&Open...")
50 | (callback (λ (c e) (void)))))
51 |
52 | ; save menu item
53 | (define file-save
54 | (new menu-item% (parent file-menu)
55 | (label "&Save")
56 | (callback (λ (c e) (void)))))
57 |
58 | ; save as menu item
59 | (define file-save-as
60 | (new menu-item% (parent file-menu)
61 | (label "Save &As...")
62 | (callback (λ (c e) (void)))))
63 |
64 | (------------------------)
65 |
66 | ; exit menu item
67 | (define file-exit
68 | (new menu-item% (parent file-menu)
69 | (label "&Exit")
70 | (callback file-exit-callback)))
71 |
72 | ;; [edit] menu
73 | (define edit-menu
74 | (new menu% (parent menu-bar)
75 | (label "Edit")))
76 |
77 | ; copy menu item
78 | (define edit-copy
79 | (new menu-item% (parent edit-menu)
80 | (label "&Copy")
81 | (callback (λ (c e) (void)))))
82 |
83 | ; paste menu item
84 | (define edit-paste
85 | (new menu-item% (parent edit-menu)
86 | (label "&Paste")
87 | (callback (λ (c e) (void)))))
88 |
89 | ; select all menu item
90 | (define edit-select-all
91 | (new menu-item% (parent edit-menu)
92 | (label "Select &All")
93 | (callback (λ (c e) (void)))))
94 |
95 | ;; [help] menu
96 | (define help-menu
97 | (new menu% (parent menu-bar)
98 | (label "Help")))
99 |
100 | ; about menu item
101 | (define help-about
102 | (new menu-item% (parent help-menu)
103 | (label "&About...")
104 | (callback help-about-callback)))
105 |
106 |
107 | ; EOF
108 |
--------------------------------------------------------------------------------
/sql.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/gui
2 | (require db)
3 | (require srfi/19)
4 | (require db/util/datetime)
5 | (require "commons.rkt")
6 | (provide query-execute ; (query-execute db query)
7 | query-record ; (query-record db query)
8 | query-string ; (query-string db query)
9 | get-query-headers ; (get-query-headers query) ('AS' required)
10 | get-query-headers# ; (get-query-headers# query) ('AS' not required, ignores subqueries)
11 | get-query-headers* ; (get-query-headers* db query) ('AS' not required, but does not support complex sub-queries)
12 | get-query-results ; (get-query-results db query wildcard-list)
13 | list->file ; (list->file l file)
14 | ml->sl ; (ml->sl l)
15 | sql-ml->sl ; (sql-ml->sl l)
16 | get-tables ; (get-tables db)
17 | get-tables* ; (get-tables* db-schema)
18 | get-table-columns ; (get-table-columns db table)
19 | get-table-columns-and-types ; (get-table-columns-and-types db table)
20 | get-db-schema ; (get-db-schema db tables)
21 | make-select-query ; (make-select-query db table columns)
22 | table-contains? ; (table-contains? db column table)
23 | which-tables-contain? ; (which-tables-contain? db tables column)
24 | which-tables-contain?* ; (which-tables-contain?* db-schema column)
25 | write-db-schema-to-file ; (write-db-schema-to-file db tables file)
26 | read-db-schema-from-file ; (read-db-schema-from-file file)
27 | update-db-schema ; (update-db-schema db db-schema)
28 | get-tables-that-contain-each-column-in-query) ; (get-tables-that-contain-each-column-in-query db db-schema query)
29 | (module+ test
30 | (require rackunit))
31 |
32 | ;;; purpose
33 |
34 | ; to provide a collection of useful procedures for handling SQL connections and queries
35 |
36 | ;;; notes
37 |
38 | ; Sample queries :
39 |
40 | ; Straight execution
41 | ;(query-exec nodixianet "create temporary table the_numbers (n integer, d varchar(20))")
42 |
43 | ; Read more than one row
44 | ;(query-rows pgc "select n, d from the_numbers where n % 2 = 0")
45 |
46 | ; Read just one row
47 | ;(query-row pgc "select * from the_numbers where n = 0")
48 |
49 | ; Read just one column as list
50 | ;(query-list pgc "select d from the_numbers order by n")
51 |
52 | ; Read just one value
53 | ;(query-value pgc "select count(*) from the_numbers")
54 |
55 | ; If query returns zero or more rows, use
56 | ;(query-maybe-row pgc "select d from the_numbers where n = 5") --> #f if row, row if any
57 | ;(query-maybe-value pgc "select d from the_numbers where n = 5") --> #f if value, value if any
58 |
59 | ; Parametrized queries:
60 | ;(query-value pgc "select d from the_numbers where n = $1" 2)
61 | ;(query-list pgc "select n from the_numbers where n > $1 and n < $2" 0 3)
62 |
63 | ;;; defs
64 |
65 | ;; write a list to file as is
66 | (define (list->file l file)
67 | (write-to-file l file #:exists 'replace #:mode 'text))
68 |
69 | ; Returns a list of list of tables that contain the specified column
70 | (define (get-tables-that-contain-each-column-in-query db db-schema query)
71 | (let* ((query-columns (get-query-headers* db query))
72 | (tables-that-contain-one-of-the-columns (map (curry which-tables-contain?* db-schema) query-columns)))
73 | tables-that-contain-one-of-the-columns))
74 | ; Test
75 | ;(get-tables-that-contain-each-column-in-query "SELECT produit_num, produit_idp FROM produit WHERE produit_num = 0")
76 |
77 | ; Update a db schema by reading it from the database (the associated table list is also refreshed with a restart)
78 | (define (update-db-schema db db-schema)
79 | (begin
80 | (displayln "Updating db schema. This can take a while...")
81 | (set! db-schema (get-db-schema db null))
82 | (displayln "All done!")))
83 |
84 | ; Returns a list of table names and their respective column list. If [tables] is null, the table list is read from the database
85 | (define (get-db-schema db tables)
86 | (let* ((table-list (if (null? tables) (get-tables db) tables)) ; gather table list from db if none provided
87 | (column-list (map (curry get-table-columns db) table-list)))
88 | (zip table-list column-list)))
89 |
90 | ; Writes the full database schema to file. [tables] is optional and the function can gather the table list directly if it is not specified
91 | (define (write-db-schema-to-file db tables file)
92 | (let* ((table-list (if (null? tables) (get-tables db) tables)) ; gather table list from db if none provided
93 | (tables-and-columns (get-db-schema db table-list)))
94 | (list->file tables-and-columns file)))
95 | ; Test
96 | ;(write-db-schema-to-file db nodixia-tables "nodixianet.schema")
97 |
98 | ; Reads a database schema from file
99 | (define (read-db-schema-from-file file)
100 | (with-input-from-file file read))
101 |
102 | ; Return a list of tables from db
103 | (define (get-tables db)
104 | (flatten (sql-ml->sl (query-rows db "SHOW TABLES"))))
105 |
106 | ; Same as get-tables but uses cached db schema for best performance
107 | (define (get-tables* db-schema)
108 | (if (list? db-schema)
109 | (first-of-each db-schema)
110 | null))
111 |
112 | ; Return a list of columns from a table
113 | (define (get-table-columns db table)
114 | (first-of-each (sql-ml->sl (query-rows db (format "SHOW COLUMNS FROM ~a" table)))))
115 |
116 | ; Return a list of columns and types from a table
117 | (define (get-table-columns-and-types db table)
118 | (sql-ml->sl (query-rows db (format "SHOW COLUMNS FROM ~a" table))))
119 |
120 | ; Predicate that returns true if the specified column name exists in the specified table
121 | (define (table-contains? db column table)
122 | (if (and (non-empty-string? table) (non-empty-string? column))
123 | (let ((columns (get-table-columns db table)))
124 | (if (member column columns) #t #f)) #f))
125 | ; Test
126 | ;(table-contains? db "produit" "produit_num")
127 |
128 | ; Predicate that returns true if the list has a second element
129 | (define (second? l)
130 | (if (list? l)
131 | (if (>= (length l) 2)
132 | (if (second l) #t #f)
133 | #f) #f))
134 | ; unit test
135 | (module+ test
136 | (check-equal? (second? '(1)) #f)
137 | (check-equal? (second? '(1 2)) #t)
138 | (check-equal? (second? '(1 2 3)) #t))
139 |
140 | ; Predicate that returns true if the second element of the list is '#true (and nothing else!)
141 | (define (second-true? l)
142 | (if (second? l)
143 | (if (equal? (second l) #t) #t #f)
144 | #f))
145 | ; unit test
146 | (module+ test
147 | (check-equal? (second-true? '(1 anything)) #f)
148 | (check-equal? (second-true? '(1 #t)) #t))
149 |
150 | ; Returns a list of tables which contain the given column name
151 | (define (which-tables-contain? db tables column)
152 | (if (and (list? tables) (non-empty-string? column))
153 | (let* ((results (map (curry table-contains? db column) tables))
154 | (zipped (zip tables results)))
155 | (first-of-each (filter second-true? zipped)))
156 | null))
157 | ; Test
158 | ;(which-tables-contain? db nodixia-tables "produit_num")
159 |
160 | ; same as which-tables-contain? uses stored db schema for best performance
161 | (define (which-tables-contain?* db-schema column)
162 | (if (and (list? db-schema) (non-empty-string? column))
163 | (let* ((tables (first-of-each db-schema)) ; list of strings.
164 | (list-of-column-lists (second-of-each db-schema)) ; list of lists.
165 | (column-in-list? (lambda (c l) (if (member c l) #t #f)))
166 | (results (map (curry column-in-list? column) list-of-column-lists))
167 | (zipped (zip tables results)))
168 | (first-of-each (filter second-true? zipped)))
169 | null))
170 | ; Test
171 | ;(which-tables-contain?* nodixia-schema "produit_num")
172 |
173 | ; Generic query launcher with automatic conversion to list of strings and swap columns and rows for display in a listbox
174 | (define (get-query-results db query wildcard-list)
175 | (if (not (string? query)) #f
176 | (swap-columns-and-rows (sql-ml->sl (apply query-rows db query wildcard-list)))))
177 |
178 | ; Get MySQL table column names for listbox headers - TESTED
179 | (define (get-table-headers db table)
180 | (if (null? table) #f
181 | (map (lambda (l) (car l))
182 | (ml->sl (query-rows db (format "SHOW COLUMNS FROM ~a" table))))))
183 |
184 | ; Get an SQL query's SELECT items as a string (helper for get-query-headers#)
185 | (define (get-query-params query)
186 | (string-join (match (string-split query " ")
187 | [(list "SELECT" params ... "FROM" tables ...) params]) " "))
188 |
189 | ; Build a SELECT query from the name of a table and a list of columns
190 | (define (make-select-query db table columns)
191 | (if (and (non-empty-string? table) (list? columns))
192 | (let* ((first-id (cond [(null? columns) null]
193 | [(equal? columns #f) #f]
194 | [else (car columns)]))
195 | (comma-delimited-ids (cond [(null? columns) "*"]
196 | [(equal? columns #f) null]
197 | [else (string-append (apply string-append (map (curryr string-append ", ") (all-but-last columns))) (last columns))])))
198 | (cond [(equal? columns #f) #f] ; exit
199 | [(null? columns) (string-append "SELECT " comma-delimited-ids " FROM " table " LIMIT 50")] ; Return SELECT * type query
200 | [else (string-append "SELECT " comma-delimited-ids " FROM " table " ORDER BY " first-id " LIMIT 50")])) #f)) ; Return complete query or false if no table provided
201 |
202 | ; Utility function to convert SQL query string to list the proper way
203 | (define (sql-query->list query-params)
204 | (map (curryr string-split " ") (map (curryr string-replace2 "(" " ( " ")" " ) ") (map string-trim (string-split query-params ",")))))
205 |
206 | ; Same as get-query-headers but with support for arbitrary queries mixing 'AS' operators and straight columns without 'AS', as well as subqueries (which are ignored) - TESTED
207 | (define (get-query-headers# query)
208 | (let ((param-list (sql-query->list (get-query-params query))))
209 | (match param-list
210 | [(list (or (list columnname) (list _ "AS" columnname) (list _ ... "AS" columnname)) ...) columnname])))
211 | ; Unit-test
212 | (module+ test
213 | (check-equal? (get-query-headers# "SELECT d.a AS c1, d.b AS c2, d.c AS c3, c4, c5, (SELECT d FROM other-table WHERE e = 9) AS c6 FROM table d LEFT JOING table2 ON (t.column1 = t2.column2) WHERE e = 1 AND f = 2 LIMIT 100") '("c1" "c2" "c3" "c4" "c5" "c6")))
214 |
215 | ; Same as get-query-headers# but with support for parsing column names from nested queries lacking AS
216 | (define (get-query-headers## query)
217 | (let ((param-list (sql-query->list (get-query-params query))))
218 | (match param-list
219 | [(list (or
220 | (list columnname)
221 | (list _ "AS" columnname)
222 | (list _ ... "AS" columnname)
223 | (list "(" "SELECT" columnname ... "FROM" _ ... ")")
224 | (list "(" "IFNULL" columnname _ ... ")") ; IFNULL(d.lig_nodoc, IF(p.produit_palette > '', (SELECT lig_nodoc FROM mw_doclig WHERE lig_ref = p.produit_palette LIMIT 1), ''))
225 | (list "(" "IFNULL" _ ... "AS" columnname ")")
226 | )
227 | ...) columnname])))
228 | ; Unit-test
229 | ;(check-equal? (get-query-headers## "SELECT d.a AS c1, IFNULL(c2, IF(c25 > '', (SELECT c26 FROM more-tables WHERE c27 = 2 LIMIT 1), '')), d.c AS c3, c4, c5, (SELECT d FROM other-table WHERE e = 9) AS c6 FROM table d WHERE e = 1 AND f = 2 LIMIT 100") '("c1" "c2" "c3" "c4" "c5" "c6"))
230 | ;(get-query-headers## "SELECT d.a AS c1, IFNULL(c2, IF(c25 > '', (SELECT c26 FROM more-tables WHERE c27 = 2 LIMIT 1), '')) AS somecolumn, d.c AS c3, c4, c5, (SELECT d FROM other-table WHERE e = 9) AS c6 FROM table d WHERE e = 1 AND f = 2 LIMIT 100")
231 |
232 | ; Returns a list of the SQL 'AS' columns - TESTED
233 | ; Does NOT support nested 'AS' operators!
234 | (define (get-query-headers q)
235 | (let* ((q1 (string-split q " AS ")) ; split string at " AS "
236 | (q2 (map (curryr string-split ",") q1)) ; for each element, split again by ","
237 | (q3 (map car q2)) ; make a list of the first items in each comma-delimited list
238 | (q4 (cdr q3)) ; save the post-AS to another list
239 | (q5 (all-but-last q4)) ;
240 | (q6 (last q4))
241 | (q7 (car (string-split q6 " "))))
242 | (append q5 (list q7))))
243 | ; Unit-test
244 | (module+ test
245 | (check-equal? (get-query-headers "SELECT d.a AS c1, d.b AS c2, d.c AS c3, (SELECT a FROM b WHERE c) AS c4 FROM table d WHERE e = 1 AND f = 2 LIMIT 100") '("c1" "c2" "c3" "c4")))
246 | ;(get-query-headers "SELECT d.a AS c1, IFNULL(c2, IF(c25 > '', (SELECT c26 FROM more-tables WHERE c27 = 2 LIMIT 1), '')) AS somecolumn, d.c AS c3, c4, c5, (SELECT d FROM other-table WHERE e = 9) AS c6 FROM table d WHERE e = 1 AND f = 2 LIMIT 100")
247 |
248 | ; Same as previous but displays the stuff
249 | (define (get-query-headers-parts q)
250 | (let* ((q1 (string-split q " AS ")) ; split string at " AS "
251 | (q2 (map (curryr string-split ",") q1)) ; for each element, split again by ","
252 | (q3 (map car q2)) ; make a list of the first items in each comma-delimited list
253 | (q4 (cdr q3)) ; save the post-AS to another list
254 | (q5 (all-but-last q4)) ;
255 | (q6 (last q4))
256 | (q7 (car (string-split q6 " ")))
257 | (result (append q5 (list q7))))
258 | (begin
259 | (echo "Query: \"" q "\"")
260 | (echo "q1 - split on AS : " q1)
261 | (echo "q2 - map split comma q1 : " q2)
262 | (echo "q3 - map car q2 : " q3)
263 | (echo "q4 - rest q3 : " q4)
264 | (echo "q5 - all-but-last q4 : " q5)
265 | (echo "q6 - last q4 : " q6)
266 | (echo "q7 - car of split q6 on space : " q7)
267 | (echo "result (q5 + q7) column names : " result))))
268 | ; Unit test
269 | ;(get-query-headers-parts "SELECT a AS b, c AS d, e AS f, g AS h FROM table WHERE a = 5")
270 |
271 | ; Same as get-query-headers but with support for '*' statements but limited to somewhat simple queries with mixed columns and AS's - TESTED
272 | (define (get-query-headers* db query)
273 | (if (string-prefix? query "SELECT *")
274 | (let ((table (car (match (string-split query " ") ((list "SELECT" stuff ... "FROM" table ...) table))))) ; Pattern-match * SQL query
275 | (get-table-headers db table))
276 | (get-query-headers## query)))
277 |
278 | ; Convert an sql-date, sql-time or sql-timestamp to human-readable format
279 | (define (sql-date->string date)
280 | (cond [(sql-date? date) (if (equal? (sql-date-month date) 0) "No date"
281 | (date->string (sql-datetime->srfi-date date)))]
282 | [(sql-timestamp? date) (if (equal? (sql-timestamp-month date) 0) "No date"
283 | (date->string (sql-datetime->srfi-date date)))]
284 | [(sql-time? date) (date->string (sql-datetime->srfi-date date))]
285 | [else "Unknown date"]))
286 |
287 | ; Convert a mixed list to a list of strings
288 | ; formerly mixed-list->string-list
289 | (define (ml->sl l)
290 | (cond [(null? l) null]
291 | [(equal? l #f) null]
292 | [(list? (car l)) (cons (ml->sl (car l)) (ml->sl (cdr l)))]
293 | [(vector? (car l)) (cons (ml->sl (vector->list (car l))) (ml->sl (cdr l)))]
294 | [else (cons (~a (car l)) (ml->sl (cdr l)))]))
295 |
296 | ; Convert an sql mixed list to a list of strings
297 | (define (sql-ml->sl l)
298 | (if (null? l) null
299 | (let ((f (car l))
300 | (r (cdr l)))
301 | (cond [(list? f) (cons (sql-ml->sl f) (sql-ml->sl r))]
302 | [(vector? f) (cons (sql-ml->sl (vector->list f)) (sql-ml->sl r))]
303 | [(sql-date? f) (cons (sql-date->string f) (sql-ml->sl r))]
304 | [(sql-time? f) (cons (sql-date->string f) (sql-ml->sl r))]
305 | [(sql-timestamp? f) (cons (sql-date->string f) (sql-ml->sl r))]
306 | [else (cons (~a f) (sql-ml->sl r))]))))
307 |
308 | ;;; Utility functions
309 |
310 | ;; executes an SQL query. Returns #t if successful, #f otherwise.
311 | (define (query-execute db query)
312 | (and (non-empty-string? query)
313 | (query-exec db query)))
314 |
315 | ;; executes an SQL query and returns rows, returns #f if zero row returned
316 | (define (query-record db query)
317 | (and (non-empty-string? query)
318 | (query-rows db query)))
319 |
320 | ;; executes an SQL query and returns the string value, returns #f if zero row returned
321 | (define (query-string db query)
322 | (and (non-empty-string? query)
323 | (query-maybe-value db query)))
324 |
325 |
326 | ; EOF
327 |
--------------------------------------------------------------------------------
/system.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/gui
2 | (provide execute-async ; (execute-async startup-path program-binary-path command-line-parameters)
3 | system->ports) ; (system->ports command)
4 | ;;; defs
5 |
6 | ;; launches a program in a cross-platform way
7 | (define (execute-async startup-path program-binary-path command-line-parameters)
8 | (if (and (non-empty-string? startup-path)
9 | (non-empty-string? program-binary-path)
10 | (file-exists? program-binary-path))
11 | (if (equal? (system-type 'os) 'windows)
12 | (shell-execute #f
13 | program-binary-path
14 | command-line-parameters
15 | startup-path
16 | 'sw_shownormal) ; possible values: 'sw_shownormal 'sw_hide 'sw_minimize
17 | (process program-binary-path))
18 | (show-error-message "This program is not installed.")))
19 |
20 | ;; redirect output and error ports to string, returning both as values
21 | (define (system->ports command)
22 | (let ((out (open-output-string))
23 | (err (open-output-string)))
24 | (parameterize ((current-output-port out)
25 | (current-error-port err))
26 | (system command)
27 | (values (get-output-string out)
28 | (get-output-string err)))))
29 | ; unit test
30 | ;(define-values (output err)
31 | ; (system->ports "notepad.exe"))
32 |
33 | ; EOF
34 |
--------------------------------------------------------------------------------
/xml.rkt:
--------------------------------------------------------------------------------
1 | #lang racket
2 | (provide get-xml-value-from-id) ; (get-xml-value-from-id file pattern)
3 |
4 | ;;; purpose
5 |
6 | ; to provide a useful collection of functions for handling XML and XML files.
7 |
8 | ;;; version history
9 |
10 | ; v1.0 - this version.
11 |
12 | ;;; defs
13 |
14 | ;; returns the value of an XML line given its id (harmony format with value before id)
15 | ;; returns the username given the contents of a localprefs.xml file
16 | ;;
17 | ;; sample use:
18 | ;; (get-xml-value-from-id "C:\\Users\\user\\Desktop\\test.xml" "SOME_XML_VALUE")
19 | (define (get-xml-value-from-id file pattern)
20 | ; read file to lines
21 | (define prefs-lines
22 | (file->lines file))
23 | ; helper func to find value line
24 | (define (login line)
25 | (string-contains? line pattern))
26 | ; helper func to extract value alone
27 | (define (clean line)
28 | (define parts (string-split line "\""))
29 | (second parts))
30 | ; build list of matches and return unique match if found
31 | (define matches (filter login prefs-lines))
32 | (if (= (length matches) 1)
33 | (clean (first matches))
34 | #f))
35 |
36 |
37 | ; EOF
38 |
--------------------------------------------------------------------------------