]> gitweb.factorcode.org Git - factor.git/commitdiff
inverse: Fix docs clean-linux-x86-32 clean-linux-x86-64 clean-windows-x86-64 main master
authorGiftpflanze <gifti@tools.wmflabs.org>
Sat, 27 Apr 2024 20:34:16 +0000 (22:34 +0200)
committerGiftpflanze <gifti@tools.wmflabs.org>
Sat, 27 Apr 2024 20:34:16 +0000 (22:34 +0200)
46 files changed:
.github/workflows/build.yml
.github/workflows/test_branch.yml
basis/cocoa/statusbar/statusbar.factor
basis/editors/notepadnext/authors.txt [new file with mode: 0644]
basis/editors/notepadnext/notepadnext.factor [new file with mode: 0644]
basis/editors/notepadnext/summary.txt [new file with mode: 0644]
basis/fixups/fixups.factor
basis/http/download/download-docs.factor
basis/http/download/download.factor
basis/inverse/inverse-docs.factor
basis/inverse/inverse-tests.factor
basis/inverse/inverse.factor
basis/io/directories/directories-tests.factor
basis/io/directories/directories.factor
basis/io/directories/windows/windows.factor
basis/math/combinatorics/combinatorics.factor
basis/sorting/specification/specification-docs.factor
basis/sorting/specification/specification-tests.factor
basis/sorting/specification/specification.factor
basis/unicode/breaks/breaks-tests.factor
basis/unicode/collation/collation-tests.factor
basis/unicode/normalize/normalize-tests.factor
basis/windows/kernel32/kernel32.factor
extra/build-from-source/build-from-source.factor
extra/build-from-source/windows/windows.factor
extra/bunny/model/model.factor
extra/codebase-analyzer/codebase-analyzer.factor
extra/curl/curl.factor
extra/discord/discord.factor
extra/google/translate/translate.factor
extra/gpu/demos/bunny/bunny.factor
extra/grouping/extras/extras-docs.factor
extra/machine-learning/data-sets/data-sets.factor
extra/macho/macho-tests.factor
extra/project-euler/098/098.factor
extra/rosetta-code/anagrams-deranged/anagrams-deranged.factor
extra/rosetta-code/ordered-words/ordered-words.factor
extra/rosetta-code/text-processing/max-licenses/max-licenses.factor
extra/scryfall/scryfall.factor
extra/sequences/extras/extras-tests.factor
extra/sequences/extras/extras.factor
extra/spelling/spelling.factor
extra/tldr/tldr.factor
extra/websites/factorcode/examples.txt
extra/youtube/youtube.factor
extra/zealot/factor/factor.factor

index 3c64297d91b6a12641c613b62a865eb4e5bd3633..dc4da781504568f46cbcb9a8c27c0b27d5c13652 100644 (file)
@@ -17,7 +17,7 @@ jobs:
     - name: bootstrap
       run: ./build.sh net-bootstrap
     - name: load-all
-      run: './factor -e="USING: memory namespaces parser.notes vocabs.hierarchy ; parser-quiet? off load-all save"'
+      run: './factor -e="USING: memory namespaces parser.notes syntax vocabs.hierarchy ; parser-quiet? off auto-use? off load-all save"'
     - name: test
       run: './factor -run=tools.test resource:core'
     - name: help-lint
@@ -30,7 +30,7 @@ jobs:
     - name: build
       run: arch -x86_64 ./build.sh net-bootstrap
     - name: load-all
-      run: './factor -e="USING: memory namespaces parser.notes vocabs.hierarchy ; parser-quiet? off load-all save"'
+      run: './factor -e="USING: memory namespaces parser.notes syntax vocabs.hierarchy ; parser-quiet? off auto-use? off load-all save"'
     - name: test
       run: './factor -run=tools.test resource:core'
     - name: help-lint
@@ -46,7 +46,7 @@ jobs:
       run: build.cmd net-bootstrap
     - name: load-all
       shell: cmd
-      run: 'factor -e="USING: memory namespaces parser.notes vocabs.hierarchy ; parser-quiet? off load-all save"'
+      run: 'factor -e="USING: memory namespaces parser.notes syntax vocabs.hierarchy ; parser-quiet? off load-all save"'
     - name: test
       shell: cmd
       run: 'factor -run=tools.test resource:core'
index eda09c80c662ca2bc3a605c5dd572560f409b799..125d82f9f45dfa0a0334a21bd6f27a76d5289e44 100644 (file)
@@ -14,7 +14,7 @@ jobs:
     - name: bootstrap
       run: ./build.sh net-bootstrap
     - name: load-all
-      run: './factor -e="USING: memory namespaces parser.notes vocabs.hierarchy ; parser-quiet? off load-all save"'
+      run: './factor -e="USING: memory namespaces parser.notes vocabs.hierarchy ; parser-quiet? off auto-use? off load-all save"'
     - name: test
       run: './factor -run=tools.test resource:core'
     - name: help-lint
@@ -27,7 +27,7 @@ jobs:
     - name: build
       run: arch -x86_64 ./build.sh net-bootstrap
     - name: load-all
-      run: './factor -e="USING: memory namespaces parser.notes vocabs.hierarchy ; parser-quiet? off load-all save"'
+      run: './factor -e="USING: memory namespaces parser.notes vocabs.hierarchy ; parser-quiet? off auto-use? off load-all save"'
     - name: test
       run: './factor -run=tools.test resource:core'
     - name: help-lint
index fd92792e7552ca30dbcccbb628afcfd28dc5332e..f7d3e7f4ef309328b32e32d72617cf545aa6624f 100644 (file)
@@ -2,7 +2,8 @@
 ! See https://factorcode.org/license.txt for BSD license.
 USING: accessors alien.c-types arrays cocoa cocoa.application
 cocoa.classes cocoa.messages cocoa.runtime cocoa.subclassing
-compiler.units kernel locals.backend math.parser sequences ;
+compiler.units kernel locals.backend math.parser quotations
+sequences ;
 IN: cocoa.statusbar
 
 << {
@@ -19,8 +20,18 @@ CONSTANT: NSSquareStatusItemLength -2.0
 : get-system-statusbar ( -- alien )
     NSStatusBar -> systemStatusBar ;
 
-TUPLE: platform-menu name items ;
-TUPLE: platform-menu-item title quot key-equivalent selector target ;
+TUPLE: platform-menu name items menu-alien statusbar-alien ;
+TUPLE: platform-menu-item title { quot callable } key-equivalent selector target ;
+
+: <platform-menu> ( name items -- platform-menu )
+    platform-menu new
+        swap >>items
+        swap >>name ;
+
+: <platform-menu-item> ( title quot -- platform-menu-item )
+    platform-menu-item new
+        swap >>quot
+        swap >>title ;
 
 : menu>dummy-class ( menu -- object )
     [ name>> "NSObject" V{ } ]
@@ -54,17 +65,28 @@ TUPLE: platform-menu-item title quot key-equivalent selector target ;
     ns-menu ns-menu-items [ -> addItem: ] with each
     ns-menu ;
 
-:: show-menu ( menu -- menu-alien statusbar-item-alien )
-    menu menu>alien :> menu-alien
+:: show-menu* ( platform-menu -- menu-alien statusbar-alien )
+    platform-menu menu>alien :> menu-alien
     get-system-statusbar :> system-alien
     system-alien
         NSVariableStatusItemLength -> statusItemWithLength: [ -> retain ] keep :> ns-status-item
-    ns-status-item menu name>> <NSString> -> setTitle:
+    ns-status-item platform-menu name>> <NSString> -> setTitle:
     menu-alien -> setMenu:
     menu-alien ns-status-item ;
 
+: show-statusbar ( platform-menu -- platform-menu )
+    [ show-menu* ] keep
+    [ statusbar-alien<< ] keep
+    [ menu-alien<< ] keep ;
+
 : enable-menu-item ( alien -- ) 1 -> setEnabled: ;
 : disable-menu-item ( alien -- ) 0 -> setEnabled: ;
 
 : hide-statusbar-item ( statusbar-item-alien -- )
     [ get-system-statusbar ] dip -> removeStatusItem: ;
+
+: hide-statusbar* ( platform-menu -- )
+    [ get-system-statusbar ] dip -> removeStatusItem: ;
+
+: hide-statusbar ( platform-menu -- platform-menu )
+    [ statusbar-alien>> hide-statusbar* ] keep ;
diff --git a/basis/editors/notepadnext/authors.txt b/basis/editors/notepadnext/authors.txt
new file mode 100644 (file)
index 0000000..7c1b2f2
--- /dev/null
@@ -0,0 +1 @@
+Doug Coleman
diff --git a/basis/editors/notepadnext/notepadnext.factor b/basis/editors/notepadnext/notepadnext.factor
new file mode 100644 (file)
index 0000000..f6b10a3
--- /dev/null
@@ -0,0 +1,33 @@
+! Copyright (C) 2024 Doug Coleman.
+! See https://factorcode.org/license.txt for BSD license.
+USING: editors io.pathnames io.standard-paths kernel make sequences system ;
+IN: editors.notepadnext
+
+SINGLETON: notepadnext
+
+HOOK: find-notepadnext-path os ( -- path line#? )
+
+M: macosx find-notepadnext-path
+    {
+        "com.yourcompany.NotepadNext"
+        "io.github.dail8859.NotepadNext"
+    } [
+        find-native-bundle [
+            "Contents/MacOS/NotepadNext" append-path
+        ] [
+            f
+        ] if*
+    ] map-find "io.github.dail8859.NotepadNext" = ;
+
+M: windows find-notepadnext-path
+    { "Notepad Next" } "NotepadNext.exe" find-in-applications
+    [ "NotepadNext.exe" ] unless* t ;
+
+M: linux find-notepadnext-path
+    "NotepadNext" find-in-path t ;
+
+M: notepadnext editor-command
+    '[
+        find-notepadnext-path
+        [ , _ , ] [ [ "-n" , _ , ] when ] bi*
+    ] { } make ;
diff --git a/basis/editors/notepadnext/summary.txt b/basis/editors/notepadnext/summary.txt
new file mode 100644 (file)
index 0000000..4caba5d
--- /dev/null
@@ -0,0 +1 @@
+NotepadNext editor integration
index 9ec5d6b285719462761a1d93f70a11de00e1b53c..21cad6c9eb002162c04786533e3c4221c71fe27e 100644 (file)
@@ -74,7 +74,8 @@ CONSTANT: word-renames {
     { "assoc-all-value?" { "all-values?" "0.100" } }
     { "assoc-any-key?" { "any-key?" "0.100" } }
     { "assoc-any-value?" { "any-value?" "0.100" } }
-    { "?download-to" { "download-once-to" "0.100" } }
+    { "?download-to" { "download-once-into" "0.100" } }
+    { "download-to" { "download-into" "0.100" } }
 }
 
 : compute-assoc-fixups ( continuation name assoc -- seq )
index 76f3a19dc07a7edaa08d4934fc53c2cb7a8e79a4..8f0a42c174980e035a54b4a007839423e6bc41b0 100644 (file)
@@ -4,24 +4,82 @@ USING: calendar help.markup help.syntax io.pathnames kernel math
 strings urls ;
 IN: http.download
 
+
 HELP: download
 { $values { "url" { $or url string } } { "path" "a pathname string" } }
 { $description "Downloads the contents of the URL to a file in the " { $link current-directory } " having the same file name and returns the pathname." }
 { $notes "Use this to download the file every time." }
 { $errors "Throws an error if the HTTP request fails." } ;
 
-HELP: download-to
-{ $values { "url" { $or url string } } { "file" "a pathname string" } { "path" "a pathname string" } }
+HELP: download-into
+{ $values
+    { "url" url } { "directory" "a pathname string" }
+    { "path" "a pathname string" }
+}
+{ $description "Downloads the contents of the URL to a file the given directory and returns the pathname." } ;
+
+HELP: download-as
+{ $values { "url" { $or url string } } { "path" "a pathname string" } }
 { $description "Downloads the contents of the URL to a file with the given pathname and returns the pathname." }
 { $notes "Use this to download the file every time." }
 { $errors "Throws an error if the HTTP request fails." } ;
 
-HELP: download-once-to
-{ $values { "url" { $or url string } } { "file" "a pathname string" } { "path" "a pathname string" } }
+
+HELP: download-once
+{ $values
+    { "url" url }
+    { "path" "a pathname string" }
+}
+{ $description "Downloads a file to " { $link current-directory } " and returns the path. If the path already exists, this word does not download it again." } ;
+
+HELP: download-once-into
+{ $values
+    { "url" url } { "directory" "a pathname string" }
+    { "path" "a pathname string" }
+}
+{ $description "Downloads a file to " { $snippet "directory" } " and returns the path. If the path already exists, this word does not download it again." } ;
+
+HELP: download-once-as
+{ $values { "url" { $or url string } } { "path" "a pathname string" } }
 { $description "If the file exists on disk, returns that pathname without downloading anything. Otherwise, downloads the contents of the URL to a file with the given pathname and returns the pathname." }
 { $notes "Use this if the contents of the URL are not expected to change." }
 { $errors "Throws an error if the HTTP request fails." } ;
 
+HELP: download-outdated
+{ $values
+    { "url" url } { "duration" duration }
+    { "path" "a pathname string" }
+}
+{ $description "Download a URL into " { $link current-directory } " unless the an existing file has a timestamp newer than " { $snippet "duration" } " ago." } ;
+
+HELP: download-outdated-as
+{ $values
+    { "url" url } { "path" "a pathname string" } { "duration" duration }
+    { "path'" "a pathname string" }
+}
+{ $description "Download a URL into a directory unless the an existing file has a timestamp newer than " { $snippet "duration" } " ago." } ;
+
+HELP: download-outdated-into
+{ $values
+    { "url" url } { "directory" "a pathname string" } { "duration" duration }
+    { "path" "a pathname string" }
+}
+{ $description "Download a URL into a directory unless the an existing file has a timestamp newer than " { $snippet "duration" } " ago." } ;
+
+
+HELP: download-to-temporary-file
+{ $values
+    { "url" url }
+    { "path" "a pathname string" }
+}
+{ $description "Downloads a url to a unique temporary file in " { $link current-directory } " named " { $snippet "temp.XXXXXXXXXreal-file-name.ext.temp" } "." } ;
+
+HELP: download-name
+{ $values
+    { "url" url }
+    { "name" object }
+}
+{ $description "Turns a URL into a filename suitable for downloading to locally." } ;
 
 ARTICLE: "http.download" "HTTP Download Utilities"
 "The " { $vocab-link "http.download" } " vocabulary provides utilities for downloading files from the web."
@@ -29,9 +87,20 @@ ARTICLE: "http.download" "HTTP Download Utilities"
 "Utilities to retrieve a " { $link url } " and save the contents to a file:"
 { $subsections
     download
-    download-to
-    download-once-to
+    download-into
+    download-as
+    download-once
+    download-once-into
+    download-once-as
+    download-outdated
+    download-outdated-into
+    download-outdated-as
 }
-;
+
+"Helper words:"
+{ $subsections
+    download-to-temporary-file
+    download-name
+} ;
 
 ABOUT: "http.download"
index 1d087a07bd471aa3d45b4afa82d318a7493adc64..ee464b9afcdf0320c6ae31b05a5f0e1a30d52b8e 100644 (file)
@@ -3,22 +3,22 @@
 USING: accessors calendar checksums combinators.short-circuit
 http.client io io.directories io.encodings.binary io.files
 io.files.info io.files.unique io.pathnames kernel math
-math.order math.parser present sequences shuffle splitting ;
-
+math.order math.parser namespaces present sequences shuffle
+splitting ;
 IN: http.download
 
-: file-too-old-or-not-exists? ( file duration -- ? )
+: file-too-old-or-not-exists? ( path duration -- ? )
     [ ?file-info [ created>> ] ?call ]
     [ ago ] bi*
     over [ before? ] [ 2drop t ] if ;
 
-: delete-when-old ( file duration -- deleted? )
+: delete-when-old ( path duration -- deleted/missing? )
     dupd file-too-old-or-not-exists? [ ?delete-file t ] [ drop f ] if ;
 
-: file-matches-checksum? ( file checksum-type bytes -- ? )
+: file-matches-checksum? ( path checksum-type bytes -- ? )
     [ checksum-file ] dip = ;
 
-: delete-when-checksum-mismatches ( file checksum-type bytes -- deleted? )
+: delete-when-checksum-mismatches ( path checksum-type bytes -- deleted? )
     dupdd file-matches-checksum? [ drop f ] [ ?delete-file t ] if ;
 
 : file-size= ( path n -- ? ) [ ?file-info [ size>> ] ?call ] dip = ;
@@ -32,7 +32,7 @@ IN: http.download
         drop t
     ] if ;
 
-: delete-when-file-size-mismatches? ( file size -- deleted? )
+: delete-when-file-size-mismatches? ( path size -- deleted? )
     dupd file-size= [ drop f ] [ ?delete-file t ] if ;
 
 : download-name ( url -- name )
@@ -84,20 +84,33 @@ IN: http.download
 
 PRIVATE>
 
-: download-to ( url file -- path )
-    [
-        [ download-temporary-name binary ] keep
-        '[ _ http-write-request ] with-unique-file-writer
-    ] dip [ move-file ] keep ;
-
-: download-once-to ( url file -- path )
-    dup file-exists? [ nip ] [ download-to ] if ;
+: download-to-temporary-file ( url -- path )
+    [ download-temporary-name binary ] keep
+    '[ _ http-write-request ] with-unique-file-writer ;
 
-: download-once ( url -- path )
-    dup download-name download-once-to ;
+: download-as ( url path -- path )
+    [ download-to-temporary-file ] dip [ ?move-file ] keep ;
 
-: download-outdated-to ( url file duration -- path )
-    2dup delete-when-old [ drop download-to ] [ drop nip ] if ;
+: download-into ( url directory -- path )
+    [ [ download-to-temporary-file ] keep ] dip
+    dup make-directories to-directory nip
+    [ move-file ] keep ;
 
 : download ( url -- path )
-    dup download-name download-to ;
+    dup download-name download-as ;
+
+: download-once-as ( url path -- path )
+    dup file-exists? [ nip ] [ download-as ] if ;
+
+: download-once-into ( url directory -- path ) to-directory download-once-as ;
+
+: download-once ( url -- path ) current-directory get download-once-into ;
+
+: download-outdated-as ( url path duration -- path' )
+    2dup delete-when-old [ drop download-as ] [ drop nip ] if ;
+
+: download-outdated-into ( url directory duration -- path )
+    [ to-directory ] dip download-outdated-as ;
+
+: download-outdated ( url duration -- path )
+    [ dup download-name current-directory get to-directory nip ] dip download-outdated-as ;
index adda3838cc03234966968c9b1cc791c5ad46824e..af0a6452f72e75455a3e53d3740285acea9a1443 100644 (file)
@@ -48,6 +48,18 @@ HELP: switch
 "    } switch ;" }
 { $see-also undo } ;
 
+HELP: under
+{ $values { "invertible-quot" quotation } { "quot" quotation } }
+{ $description "Applies " { $snippet "invertible-quot" } ", then " { $snippet "quot" } " and finally the inverse of " { $snippet "invertible-quot" } "." }
+{ $examples
+    "Round a decimal number to two decimals:"
+    { $example
+        "USING: inverse math math.functions prettyprint ;"
+        "123.456 [ 100 * ] [ round ] under ."
+        "123.46"
+    }
+} ;
+
 ARTICLE: { "inverse" "intro" } "Invertible quotations"
 "The inverse vocab defines a way to 'undo' quotations, and builds a pattern matching framework on that basis. A quotation can be inverted by reversing it and inverting each word. To define the inverse for particular word, use"
 { $subsections
index 04db91310840b134ae5052d09dc6bfc32c639974..c619156b8a0c950e4afffb29edb7bfa9270f82df 100644 (file)
@@ -97,3 +97,5 @@ TUPLE: funny-tuple ;
 
 { 0 } [ { 1 2 } [ [ 1 + 2 ] { } output>sequence ] undo ] unit-test
 { { 0 1 } } [ 1 2 [ [ [ 1 + ] bi@ ] input<sequence ] undo ] unit-test
+
+{ 123.46 } [ 123.456 [ 100 * ] [ round ] under ] unit-test
index 3a442db48e512bae153aae5e4fc00e64df7fc870..04ee00c361b76c8dd6d387e0e029fec0edc0b761 100644 (file)
@@ -291,3 +291,6 @@ MACRO: switch ( quot-alist -- quot ) [switch] ;
 SYNTAX: INVERSE: scan-word parse-definition define-inverse ;
 
 SYNTAX: DUAL: scan-word scan-word define-dual ;
+
+MACRO: under ( invertible-quot quot -- quot )
+    over [undo] '[ @ @ @ ] ;
index de41b112b0147ed6a1d4c7a41fa1a343359e4ce4..edfcba2037ccc2158b7b5d05e17b96e4654c64fb 100644 (file)
@@ -56,6 +56,14 @@ splitting.monotonic strings system tools.test ;
         "file6" link-info drop
     ] unit-test
 
+    { "file7 contents" } [
+        "file8" touch-file
+        "file7 contents" "file7" utf8 set-file-contents
+        "file7" "file8" move-file
+        "file8" utf8 file-contents
+        "file8" delete-file
+    ] unit-test
+
     { } [
         { "Hello world." }
         "test-foo.txt" ascii set-file-lines
index 5b30462c112f5f34458ccbb0b747574a37dfad65..6017361f9d5a662ffb8d12480dbd6f77aebaa9e5 100644 (file)
@@ -215,8 +215,13 @@ HOOK: delete-directory io-backend ( path -- )
 
 HOOK: move-file io-backend ( from to -- )
 
+: create-parent-directory ( path -- )
+    normalize-path parent-directory make-directories ;
+
 : ?move-file ( from to -- )
-    over file-exists? [ move-file ] [ 2drop ] if ;
+    over file-exists? [
+        dup create-parent-directory move-file
+    ] [ 2drop ] if ;
 
 HOOK: move-file-atomically io-backend ( from to -- )
 
index e51bda8433ebbac517c5422b7cd6164da399d3c1..609f605a03dec08178be72633924c8a75977ed4b 100644 (file)
@@ -1,7 +1,7 @@
 ! Copyright (C) 2008 Doug Coleman.
 ! See https://factorcode.org/license.txt for BSD license.
 USING: system io.directories alien.strings
-io.pathnames io.backend io.files.windows destructors
+io.pathnames io.backend io.files.windows literals destructors
 kernel accessors calendar windows windows.errors
 windows.kernel32 alien.c-types sequences splitting
 fry continuations classes.struct windows.time ;
@@ -21,10 +21,13 @@ M: windows truncate-file
     ] with-disposal ;
 
 M: windows move-file
-    [ normalize-path ] bi@ MoveFile win32-error=0/f ;
+    [ normalize-path ] bi@
+    flags{ MOVEFILE_REPLACE_EXISTING MOVEFILE_COPY_ALLOWED }
+    MoveFileEx win32-error=0/f ;
 
 M: windows move-file-atomically
-    [ normalize-path ] bi@ 0 MoveFileEx win32-error=0/f ;
+    [ normalize-path ] bi@ MOVEFILE_REPLACE_EXISTING
+    MoveFileEx win32-error=0/f ;
 
 ERROR: file-delete-failed path error ;
 
index 771dda2624b307e025b4e2db1631151ee32092eb..1d09824908e02d61b7424b70e16cfe0e4fb0a239 100644 (file)
@@ -2,8 +2,8 @@
 ! See https://factorcode.org/license.txt for BSD license.
 
 USING: accessors arrays assocs classes.tuple combinators hints
-kernel kernel.private make math math.functions math.order
-ranges sequences sequences.private sorting strings vectors ;
+kernel kernel.private make math math.functions math.order ranges
+sequences sequences.private sets sorting strings vectors ;
 IN: math.combinatorics
 
 <PRIVATE
@@ -301,6 +301,9 @@ PRIVATE>
 : all-combinations ( seq k -- seq' )
     [ ] map-combinations ;
 
+: all-unique-combinations ( seq n -- seq' )
+    HS{ } clone [ '[ _ adjoin ] each-combination ] keep members ;
+
 : all-combinations? ( ... seq k quot: ( ... elt -- ... ? ) -- ... ? )
     combinations-quot all? ; inline
 
index fb87d2667a032eabf75fb36002c7c4ff25aa3979..643227283f71347ed0e2599b034c0df038383d9e 100644 (file)
@@ -8,7 +8,7 @@ HELP: compare-with-spec
 { $values
   { "obj1" object }
   { "obj2" object }
-  { "sort-spec" "a sequence of sequences of accessors and a comparator" }
+  { "sort-spec" "a sequence of sequences of accessors/quotations and a comparator" }
   { "<=>" { $link +lt+ } ", " { $link +eq+ } " or " { $link +gt+ } }
 }
 { $description "Compares two objects using a chain of intrinsic linear orders such that if two objects are " { $link +eq+ } ", then the next ordering is tried." } ;
index 05fdbb8ff8576b4a353a8755939028cc5ca5aa24..84edff50866ad5bc58cc9242eb9d329ff72c6382 100644 (file)
@@ -4,6 +4,7 @@ USING: accessors math.order sorting.specification tools.test
 arrays sequences kernel assocs multiline sorting.functor ;
 IN: sorting.specification.tests
 
+
 TUPLE: sort-test a b c tuple2 ;
 
 TUPLE: tuple2 d ;
@@ -44,6 +45,25 @@ TUPLE: tuple2 d ;
     } { { a>> <=> } { b>> >=< } { c>> <=> } } sort-with-spec
 ] unit-test
 
+! Test with quotations too even though it's basically the same
+{
+    {
+        T{ sort-test { a 1 } { b 3 } { c 9 } }
+        T{ sort-test { a 1 } { b 1 } { c 10 } }
+        T{ sort-test { a 1 } { b 1 } { c 11 } }
+        T{ sort-test { a 2 } { b 5 } { c 2 } }
+        T{ sort-test { a 2 } { b 5 } { c 3 } }
+    }
+} [
+    {
+        T{ sort-test f 1 3 9 }
+        T{ sort-test f 1 1 10 }
+        T{ sort-test f 1 1 11 }
+        T{ sort-test f 2 5 3 }
+        T{ sort-test f 2 5 2 }
+    } { { [ a>> ] <=> } { [ b>> ] >=< } { [ c>> ] <=> } } sort-with-spec
+] unit-test
+
 { { } } [
     { } { { a>> <=> } { b>> >=< } { c>> <=> } } sort-with-spec
 ] unit-test
index 6132da38b12416132cd0937dd0c4a7949accfdf0..71c0b3c5490a5e991f433e90c8188bff3218051e 100644 (file)
@@ -1,11 +1,15 @@
 ! Copyright (C) 2009 Slava Pestov, Doug Coleman.
 ! See https://factorcode.org/license.txt for BSD license.
-USING: arrays assocs kernel math.order sequences sorting ;
+USING: arrays assocs kernel math.order quotations sequences
+sorting ;
 IN: sorting.specification
 
 : execute-comparator ( obj1 obj2 word -- <=>/f )
     execute( obj1 obj2 -- <=> ) dup +eq+ eq? [ drop f ] when ;
 
+: call-accessor ( obj1 obj2 quot -- obj1' obj2' )
+    '[ _ call( obj -- value ) ] bi@ ;
+
 : execute-accessor ( obj1 obj2 word -- obj1' obj2' )
     '[ _ execute( tuple -- value ) ] bi@ ;
 
@@ -14,7 +18,7 @@ IN: sorting.specification
     [
         dup array? [
             unclip-last-slice
-            [ [ execute-accessor ] each ] dip
+            [ [ dup quotation? [ call-accessor ] [ execute-accessor ] if ] each ] dip
         ] when execute-comparator
     ] 2with map-find drop +eq+ or ;
 
index 801b486ad8ef3d64c071ef314544cab5be47c26f..3c25b953952c8284400ad6da6e9e3a9443a59426 100644 (file)
@@ -21,11 +21,11 @@ IN: unicode.breaks.tests
 
 : grapheme-break-test ( -- filename )
     "https://downloads.factorcode.org/misc/UCD/15.1.0/auxiliary/GraphemeBreakTest.txt"
-    "GraphemeBreakTest-15.1.0.txt" cache-file download-once-to ;
+    "GraphemeBreakTest-15.1.0.txt" cache-file download-once-as ;
 
 : word-break-test ( -- filename )
     "https://downloads.factorcode.org/misc/UCD/15.1.0/auxiliary/WordBreakTest.txt"
-    "WordBreakTest-15.1.0.txt" cache-file download-once-to ;
+    "WordBreakTest-15.1.0.txt" cache-file download-once-as ;
 
 : parse-test-file ( file-name -- tests )
     utf8 file-lines
index 6e985c2bab91d424b858aafc432c5642fee8429c..9e6a7eab2b159600468d0d46b3f5fcd9aecd8ba4 100644 (file)
@@ -19,7 +19,7 @@ IN: unicode.collation.tests
 
 : collation-test-lines ( -- lines )
     "https://downloads.factorcode.org/misc/UCA/15.1.0/CollationTest_SHIFTED.txt"
-    "CollationTest_SHIFTED_15.1.0.txt" cache-file download-once-to
+    "CollationTest_SHIFTED_15.1.0.txt" cache-file download-once-as
     utf8 file-lines [ "#" head? ] reject harvest ;
 
 : parse-collation-test-shifted ( -- lines )
index edfc27dba8af89305d03ddc5a46f0b798d7f7013..d62dfbb70c2144544d49fe1149b371bfb1bc5781 100644 (file)
@@ -27,7 +27,7 @@ IN: unicode.normalize.tests
 ! Could use simple-flat-file after some cleanup
 : parse-normalization-tests ( -- tests )
     "https://downloads.factorcode.org/misc/UCD/15.1.0/NormalizationTest.txt"
-    "NormalizationTest-15.1.0.txt" cache-file download-once-to
+    "NormalizationTest-15.1.0.txt" cache-file download-once-as
     utf8 file-lines [ "#" head? ] reject
     [ "@" head? ] split*-when
     2 <groups> [ first2 [ first ] dip 2array ] map
index f5bd59a00bfb91170f0d2b22e307bde3a139b434..f49ff7c044b242df1d2c66ab4b1843de203dad73 100644 (file)
@@ -124,6 +124,13 @@ CONSTANT: FILE_SUPPORTS_OPEN_BY_FILE_ID     0x01000000
 CONSTANT: FILE_SUPPORTS_USN_JOURNAL         0x02000000
 CONSTANT: FILE_DAX_VOLUME                   0x20000000
 
+CONSTANT: MOVEFILE_COPY_ALLOWED 0x2
+CONSTANT: MOVEFILE_CREATE_HARDLINK 0x10
+CONSTANT: MOVEFILE_DELAY_UNTIL_REBOOT 0x4
+CONSTANT: MOVEFILE_FAIL_IF_NOT_TRACKABLE 0x20
+CONSTANT: MOVEFILE_REPLACE_EXISTING 0x1
+CONSTANT: MOVEFILE_WRITE_THROUGH 0x8
+
 CONSTANT: DONT_RESOLVE_DLL_REFERENCES 1
 
 STRUCT: FILE_NOTIFY_INFORMATION
index 02b5826d790305b673c034572265e91f42186438..5520619e48774b4f669f36b62732aa7efd99fed8 100644 (file)
@@ -184,9 +184,6 @@ ERROR: no-output-file path ;
         ] if*
     ] with-directory ; inline
 
-: ?download ( path -- )
-    dup file-name file-exists? [ drop ] [ download drop ] if ; inline
-
 : with-tar-gz ( path quot -- )
     '[
         _ dup "build-from-source considering tar.gz %s" sprintf print
@@ -195,10 +192,10 @@ ERROR: no-output-file path ;
         ] [
             "- building..." write
             [
-                [ ?download ]
-                [ file-name { "tar" "xvfz" } swap suffix try-process ]
-                [ file-name ".tar.gz" ?tail drop ] tri
-                prepend-current-path _ with-directory
+                download-once
+                [ { "tar" "xvfz" } swap suffix try-process ]
+                [ ".tar.gz" ?tail drop ] bi
+                _ with-directory
                 now timestamp>rfc3339
             ] dip utf8 set-file-contents
             "done!" print
index bc45a30fe7d88120967f107572d6e781081024c5..36622c7ee5d552ea6d34842a73816aef24f75ec1 100644 (file)
@@ -1,7 +1,7 @@
 ! Copyright (C) 2023 Doug Coleman.
 ! See https://factorcode.org/license.txt for BSD license.
 USING: build-from-source combinators.smart continuations
-environment http.client io.directories io.files.temp io.launcher
+environment http.download io.directories io.files.temp io.launcher
 io.pathnames kernel layouts qw sequences windows.shell32 ;
 IN: build-from-source.windows
 
@@ -396,7 +396,7 @@ IN: build-from-source.windows
 ! Probably not needed on Windows 10+
 : install-windows-redistributable ( -- )
     [
-        "https://aka.ms/vs/17/release/vc_redist.x64.exe" download
+        "https://aka.ms/vs/17/release/vc_redist.x64.exe" download drop
         qw{ vc_redist.x64.exe /install /passive /norestart } try-process
     ] with-temp-directory ;
 
index dd2003a4c92683e4e16f340f7f8c665f79580495..88d1652488069f0eed95e34d50eac4e5fc33db33 100644 (file)
@@ -46,7 +46,7 @@ CONSTANT: model-url
 "https://downloads.factorcode.org/misc/bun_zipper.ply"
 
 : download-bunny ( -- path )
-    model-url model-path download-once-to ;
+    model-url model-path download-once-as ;
 
 :: (draw-triangle) ( ns vs triple -- )
     triple [| elt |
index 33b9c1870908325e9648952e987a733675ecb068..112cdacac0c4c60fe766ef5958decd70cebeec0a 100644 (file)
@@ -170,7 +170,10 @@ IN: codebase-analyzer
 : uses-yaml? ( paths -- ? ) [ yaml-file? ] any? ;
 
 : docker-file? ( path -- ? ) >lower file-name { "dockerfile" ".dockerignore" "docker-compose.yaml" } member? ;
-: docker-files ( paths -- paths' ) [ docker-file? ] filter ;
+: docker-files ( paths -- paths' )
+    [ [ docker-file? ] filter ]
+    [ [ >lower "dockerfile" subseq-of? ] filter ] bi
+    append members ;
 : uses-docker? ( paths -- ? ) [ docker-file? ] any? ;
 
 : automake-file? ( path -- ? )
@@ -294,11 +297,14 @@ IN: codebase-analyzer
 
 : analyze-codebase-path ( path -- )
     {
-        [ normalize-path "project at path `%s`" sprintf print nl ]
+        [ normalize-path "project at path `%s`" sprintf print ]
         [ uses-git? [ "uses git" print ] when ]
         [ has-package-json? [ "has a package.json file" print ] when ]
     } cleave ;
 
+: file. ( path -- ) >pathname ... ;
+: files. ( paths -- ) [ file. ] each ;
+
 : analyze-codebase-paths ( paths -- )
     {
         [
@@ -306,32 +312,33 @@ IN: codebase-analyzer
             [ length "%d binary files" sprintf print ]
             [ length "%d text files" sprintf print ] bi*
         ]
-        [ github-files [ sort "has .github files" print ... ] unless-empty ]
-        [ license-files [ sort [ length "has %d license files" sprintf print ] [ ... ] bi ] unless-empty ]
-        [ readme-files [ sort "has readme files" print ... ] unless-empty ]
-        [ owners-files [ sort "has owners files" print ... ] unless-empty ]
-        [ codenotify-files [ sort "has codenotify files" print ... ] unless-empty ]
-        [ contributing-files [ sort "has contributing files" print ... ] unless-empty ]
-        [ changelog-files [ sort "has changelog files" print ... ] unless-empty ]
-        [ security-files [ sort "has security files" print ... ] unless-empty ]
-        [ notice-files [ sort "has notice files" print ... ] unless-empty ]
-        [ version-files [ sort "has version files" print ... ] unless-empty ]
+        [ github-files [ sort "has .github files" print files. ] unless-empty ]
+        [ license-files [ sort [ length "has %d license files" sprintf print ] [ files. ] bi ] unless-empty ]
+        [ readme-files [ sort "has readme files" print files. ] unless-empty ]
+        [ owners-files [ sort "has owners files" print files. ] unless-empty ]
+        [ codenotify-files [ sort "has codenotify files" print files. ] unless-empty ]
+        [ contributing-files [ sort "has contributing files" print files. ] unless-empty ]
+        [ changelog-files [ sort "has changelog files" print files. ] unless-empty ]
+        [ security-files [ sort "has security files" print files. ] unless-empty ]
+        [ notice-files [ sort "has notice files" print files. ] unless-empty ]
+        [ version-files [ sort "has version files" print files. ] unless-empty ]
         [
             { [ dot-files ] [ rc-files diff ] [ ignore-files diff ] } cleave
-            [ sort "has dot files" print ... ] unless-empty
+            [ sort "has dot files" print files. ] unless-empty
         ]
-        [ rc-files [ sort [ length "has %d rc files" sprintf print ] [ ... ] bi ] unless-empty ]
-        [ configure-files [ sort "uses configure files" print ... ] unless-empty ]
-        [ automake-files [ sort "uses automake" print ... ] unless-empty ]
-        [ make-files [ sort "uses make" print ... ] unless-empty ]
-        [ nmake-files [ sort "uses nmake" print ... ] unless-empty ]
-        [ cmake-files [ sort "uses cmake" print ... ] unless-empty ]
-        [ gradle-files [ sort "uses gradle" print ... ] unless-empty ]
-        [ cargo-files [ sort "uses rust/cargo" print ... ] unless-empty ]
-        [ julia-project-files [ sort "uses julia Project.toml" print ... ] unless-empty ]
-        [ in-files [ sort "uses 'in' files" print ... ] unless-empty ]
-        [ ignore-files [ sort [ length "has %d ignore files" sprintf print ] [ ... ] bi ] unless-empty nl ]
-        [ [ rust-project-dir? ] filter [ [ "rust projects at " print . ] [ [ analyze-rust-project ] each ] bi ] unless-empty nl ]
+        [ rc-files [ sort [ length "has %d rc files" sprintf print ] [ files. ] bi ] unless-empty ]
+        [ configure-files [ sort "uses configure files" print files. ] unless-empty ]
+        [ automake-files [ sort "uses automake" print files. ] unless-empty ]
+        [ make-files [ sort "uses make" print files. ] unless-empty ]
+        [ nmake-files [ sort "uses nmake" print files. ] unless-empty ]
+        [ cmake-files [ sort "uses cmake" print files. ] unless-empty ]
+        [ docker-files [ sort "uses docker" print files. ] unless-empty ]
+        [ gradle-files [ sort "uses gradle" print files. ] unless-empty ]
+        [ cargo-files [ sort "uses rust/cargo" print files. ] unless-empty ]
+        [ julia-project-files [ sort "uses julia Project.toml" print files. ] unless-empty ]
+        [ in-files [ sort "uses 'in' files" print files. ] unless-empty ]
+        [ ignore-files [ sort [ length "has %d ignore files" sprintf print ] [ files. ] bi ] unless-empty ]
+        [ [ rust-project-dir? ] filter [ [ "rust projects at " print file. ] [ [ analyze-rust-project ] each ] bi ] unless-empty ]
         [
             [ upper-files ] keep
             {
@@ -346,12 +353,12 @@ IN: codebase-analyzer
                 [ notice-files diff ]
                 [ version-files diff ]
             } cleave
-            [ sort [ length "has %d UPPER files (minus github,license,readme,owner,codenotify,contributing,changelog,security,notice,version)" sprintf print ] [ ... ] bi ] unless-empty nl
+            [ sort [ length "has %d UPPER files (minus github,license,readme,owner,codenotify,contributing,changelog,security,notice,version)" sprintf print ] [ files. ] bi ] unless-empty
         ]
-        [ "Top 20 largest files" print file-sizes sort-values 20 index-or-length tail* [ normalize-path ] map-keys reverse assoc. nl ]
-        [ "Top 10 file extension sizes" print sum-sizes-by-extension 10 index-or-length tail* reverse assoc. nl ]
-        [ "Top 10 text file line counts" print sum-line-counts-by-extension 10 index-or-length tail* reverse assoc. nl ]
-        [ "Top 10 file extension counts" print count-by-file-extension 10 index-or-length tail* reverse assoc. nl ]
+        [ "Top 20 largest files" print file-sizes sort-values 20 index-or-length tail* [ normalize-path ] map-keys reverse assoc. ]
+        [ "Top 10 file extension sizes" print sum-sizes-by-extension 10 index-or-length tail* reverse assoc. ]
+        [ "Top 10 text file line counts" print sum-line-counts-by-extension 10 index-or-length tail* reverse assoc. ]
+        [ "Top 10 file extension counts" print count-by-file-extension 10 index-or-length tail* reverse assoc. ]
     } cleave ;
 
 : analyze-codebase ( path -- )
index 2207d213da8bcf310e0baf7431786e07f7adff1d..03d438167b7737ea4c4b3611e282267a1a5028a4 100644 (file)
@@ -1,10 +1,9 @@
 ! Copyright (C) 2014 John Benediktsson
 ! See https://factorcode.org/license.txt for BSD license
 
-USING: alien alien.c-types alien.data alien.destructors
-alien.syntax command-line curl.ffi destructors io
-io.encodings.string io.encodings.utf8 io.streams.c kernel math
-namespaces present sequences ;
+USING: alien.destructors command-line curl.ffi destructors
+http.download io.backend io.streams.c kernel namespaces present
+sequences ;
 
 IN: curl
 
@@ -27,14 +26,14 @@ DESTRUCTOR: fclose
     CURLOPT_URL swap present curl-set-opt ;
 
 : curl-set-file ( CURL path -- )
-    CURLOPT_FILE swap "wb" fopen &fclose curl-set-opt ;
+    CURLOPT_FILE swap normalize-path "wb" fopen &fclose curl-set-opt ;
 
 : curl-perform ( CURL -- )
     curl_easy_perform check-code ;
 
 PRIVATE>
 
-: curl-download-to ( url path -- )
+: curl-download-as ( url path -- )
     [
         curl-init
         [ swap curl-set-file ]
@@ -42,6 +41,9 @@ PRIVATE>
         [ curl-perform ] tri
     ] with-destructors ;
 
+: curl-download ( url -- path )
+    dup download-name [ curl-download-as ] keep ;
+
 : curl-main ( -- )
     command-line get [
         curl-init
index daf9e3b477bf9b8ff33efedf626d3d46058a798e..4b132f06670758ed621f84f2744c55b633aaaf27 100644 (file)
@@ -17,7 +17,8 @@ TUPLE: discord-webhook url id token ;
 
 TUPLE: discord-bot-config
     client-id client-secret
-    token application-id guild-id channel-id permissions
+    token application-id guild-id channel-id
+    permissions intents
     user-callback obey-names
     metadata
     discord-bot mailbox connect-thread ;
@@ -73,6 +74,8 @@ TUPLE: discord-bot
     discord-post-request json-request ;
 : discord-post-json ( payload route -- json )
     [ >json ] dip discord-post-request add-json-header json-request ;
+: discord-post-json-no-resp ( payload route -- )
+    [ >json ] dip discord-post-request add-json-header http-request 2drop ;
 : discord-patch-json ( payload route -- json )
     [ >json ] dip discord-patch-request add-json-header json-request ;
 : discord-delete-json ( route -- json )
@@ -104,13 +107,12 @@ TUPLE: discord-bot
 : delete-discord-application-guild-command ( application-id -- json )
     "/applications/%s/commands" sprintf discord-delete-json ;
 
-: create-interaction-response ( interaction-id interaction-token -- json )
-    [ H{ { "type" 4 } { "data" "pang" } } clone ] 2dip
-    "/webhooks/%s/%s/messages/callback" sprintf discord-post ;
-
+: create-interaction-response ( json interaction-id interaction-token -- )
+    "/interactions/%s/%s/callback" sprintf discord-post-json-no-resp ;
 : get-original-interaction-response ( application-id interaction-token -- json )
     "/webhooks/%s/%s/messages/@original" sprintf discord-get ;
-
+: edit-interaction-response ( json application-id interaction-token -- json )
+    "/webhooks/%s/%s/messages/@original" sprintf discord-patch-json ;
 
 
 : send-message* ( string channel-id -- json )
@@ -137,7 +139,11 @@ TUPLE: discord-bot
 : get-discord-bot-gateway ( -- json ) "/gateway/bot" discord-get ;
 
 : gateway-identify-json ( -- json )
-    \ discord-bot get config>> token>> [[ {
+    \ discord-bot get
+    [ config>> ] ?call
+    [ [ token>> ] ?call "0" or  ]
+    [ [ intents>> ] ?call 3276541 or ] bi
+    [[ {
         "op": 2,
         "d": {
             "token": "%s",
@@ -147,7 +153,7 @@ TUPLE: discord-bot
                 "device": "discord.factor"
             },
             "large_threshold": 250,
-            "intents": 3276541
+            "intents": %d
         }
     }]] sprintf json> >json ;
 
index a8ddafa31fcbef820bbbe162b25b473c676002e6..175a66ee5264c3b136407db336b3cf4c03f3a0fd 100644 (file)
@@ -55,7 +55,7 @@ TUPLE: response-error response error ;
 : translate-tts ( text -- file )
     "https://translate.google.com/translate_tts?tl=en" >url
     swap "q" set-query-param [
-        "" ".mp3" unique-file download-to
+        "" ".mp3" unique-file download-as
     ] with-temp-directory ;
 
 ! Example:
index f5963e714e89dc848caacd3ff4476ec5b9c4ae84..c082f3484e90e958fdf45b3d502a8270bbc6abbd 100644 (file)
@@ -149,7 +149,7 @@ CONSTANT: bunny-model-url
 "https://downloads.factorcode.org/misc/bun_zipper.ply"
 
 : download-bunny ( -- path )
-    bunny-model-url bunny-model-path download-once-to ;
+    bunny-model-url bunny-model-path download-once-as ;
 
 : get-bunny-data ( bunny-state -- )
     download-bunny bunny-data
index add28a0332f45d5eea839fb2f651420013892e6b..c4af0e02ca651e4954008471f9652972afdc7fdf 100644 (file)
@@ -1,18 +1,25 @@
-USING: arrays grouping help.markup help.syntax kernel math
+USING: arrays assocs grouping help.markup help.syntax kernel math
 quotations sequences ;
 IN: grouping.extras
 
 HELP: group-by
 { $values { "seq" sequence } { "quot" { $quotation ( elt -- key ) } } { "groups" "a new assoc" } }
-{ $description "Groups the elements by the key received by applying quot to each element in the sequence." }
+{ $description "Groups consecutive elements by the key received by applying quot to each element in the sequence." }
 { $examples
   { $example
     "USING: grouping.extras unicode.data prettyprint sequences strings ;"
     "\"THis String Has  CasE!\" [ category ] group-by [ last >string ] { } map-as ."
     "{ \"TH\" \"is\" \" \" \"S\" \"tring\" \" \" \"H\" \"as\" \"  \" \"C\" \"as\" \"E\" \"!\" }"
   }
+  { $example
+    "USING: grouping.extras prettyprint sequences strings ;"
+    "{ \"apple\" \"anchovy\" \"banana\" \"anise\" \"bagel\" \"bratwurst\" } [ first 1string ] group-by ."
+    "V{\n    { \"a\" V{ \"apple\" \"anchovy\" } }\n    { \"b\" V{ \"banana\" } }\n    { \"a\" V{ \"anise\" } }\n    { \"b\" V{ \"bagel\" \"bratwurst\" } }\n}"
+  }
 } ;
 
+{ group-by collect-by } related-words
+
 HELP: <n-groups>
 { $values
     { "seq" sequence } { "n" integer }
index 8de52112510cdea90acec6a7431e11947d675716..61daf8e33c7390ead14ee36f17a69968fca2b07e 100644 (file)
@@ -98,7 +98,7 @@ CONSTANT: datasets-path "resource:datasets/"
             "https://github.com/golbin/TensorFlow-MNIST/raw/master/mnist/data/t10k-images-idx3-ubyte.gz"
             "https://github.com/golbin/TensorFlow-MNIST/raw/master/mnist/data/t10k-labels-idx1-ubyte.gz"
         }
-        [ [ download-once ] parallel-each ]
+        [ [ download-once-into ] parallel-each ]
         [ [ dup file-stem file-exists? [ drop ] [ file-name gzip-decompress-file ] if ] each ]
         [ [ file-stem binary file-contents ] map ] tri
         first4 {
index 5bcfa60378dfffb2b1e533e022a98d502936b8a5..73c7bb148af2a25a51e9395ce7ff2331d39470ca 100644 (file)
@@ -24,11 +24,11 @@ STRING: validation-output
 
 : a.macho ( -- path )
     URL" https://downloads.factorcode.org/misc/a.macho"
-    "a.macho" cache-file download-once-to ;
+    "a.macho" cache-file download-once-as ;
 
 : a2.macho ( -- path )
     URL" https://downloads.factorcode.org/misc/a2.macho"
-    "a2.macho" cache-file download-once-to ;
+    "a2.macho" cache-file download-once-as ;
 
 cpu ppc? [
     { $ validation-output }
index 4a97d916a900e2f0ea4c90d9cf9d3738ce6d116b..90003dfae2545f8a2d892b661a6c11a81a2b1ca2 100644 (file)
@@ -42,7 +42,7 @@ IN: project-euler.098
 
 : wordlist ( -- seq )
     "https://projecteuler.net/project/resources/p098_words.txt"
-    "p098_words.txt" temp-file download-once-to
+    "p098_words.txt" temp-file download-once-as
     utf8 file-contents "," split [ rest-slice but-last ] map ;
 
 : squarelist ( n -- seq )
index 53db81bb0036624e8eb3f26520aa13643cac6c7e..d2bd6b18427fdbe4a6ad021f133a5367ec4df2b1 100644 (file)
@@ -42,7 +42,7 @@ IN: rosettacode.anagrams-deranged
 
 : default-word-list ( -- path )
     URL" https://raw.githubusercontent.com/quinnj/Rosetta-Julia/master/unixdict.txt"
-    "unixdict.txt" temp-file download-once-to ;
+    "unixdict.txt" temp-file download-once-as ;
 
 : longest-deranged-anagrams ( -- anagrams )
     default-word-list (longest-deranged-anagrams) ;
index 8c37abfc3696542e6316a7294ea01c71874ebdfd..dff944af62461651a42ae86369fd59e8b76d1e38 100644 (file)
@@ -19,7 +19,7 @@ IN: rosetta-code.ordered-words
 MEMO: word-list ( -- seq )
     URL" https://raw.githubusercontent.com/quinnj/Rosetta-Julia/master/unixdict.txt"
     "unixdict.txt" temp-file
-    download-once-to utf8 file-lines ;
+    download-once-as utf8 file-lines ;
 
 : ordered-word? ( word -- ? )
     >lower [ <= ] monotonic? ;
index 2110ad19cda6f8d8fcf0216d69e5b80544a690af..6064dc87d6d964e5b6039679c98b900d42bbdc11 100644 (file)
@@ -63,7 +63,7 @@ TUPLE: maxlicense max-count current-count times ;
 
 MEMO: mlijobs ( -- lines )
     URL" https://raw.githubusercontent.com/def-/nim-unsorted/master/mlijobs.txt"
-    "mlijobs.txt" temp-file download-once-to ascii file-lines ;
+    "mlijobs.txt" temp-file download-once-as ascii file-lines ;
 
 PRIVATE>
 
index 820d104ee5525569fc93004324617c199ae2c6d0..1eef4b3f091dec7a721a67ddf3c4f0337659334f 100644 (file)
@@ -1,10 +1,14 @@
 ! Copyright (C) 2024 Doug Coleman.
 ! See https://factorcode.org/license.txt for BSD license.
 USING: accessors arrays assocs assocs.extras calendar
-combinators http.download images.loader images.viewer io
-io.directories json json.http kernel math math.parser
-math.statistics namespaces sequences sequences.extras sets
-sorting splitting ui.gadgets.panes unicode urls ;
+calendar.parser combinators combinators.short-circuit
+combinators.smart formatting grouping http.download
+images.loader images.viewer io io.directories json json.http
+kernel math math.combinatorics math.order math.parser
+math.statistics namespaces random sequences sequences.deep
+sequences.extras sequences.generalizations sets sorting
+sorting.specification splitting splitting.extras strings
+ui.gadgets.panes unicode urls ;
 IN: scryfall
 
 CONSTANT: scryfall-oracle-json-path "resource:scryfall-oracle-json"
@@ -14,6 +18,7 @@ CONSTANT: scryfall-all-json-path "resource:scryfall-all-json"
 CONSTANT: scryfall-rulings-json-path "resource:scryfall-rulings-json"
 CONSTANT: scryfall-images-path "resource:scryfall-images/"
 
+: ?write ( str/f -- ) [ write ] when* ;
 : ?print ( str/f -- ) [ print ] [ nl ] if* ;
 
 : download-scryfall-bulk-json ( -- json )
@@ -24,12 +29,12 @@ CONSTANT: scryfall-images-path "resource:scryfall-images/"
 
 : load-scryfall-json ( type path -- uri )
     [ find-scryfall-json "download_uri" of ] dip
-    120 hours download-outdated-to path>json ;
+    30 days download-outdated-as path>json ;
 
 MEMO: mtg-oracle-cards ( -- json )
     "oracle_cards" scryfall-oracle-json-path load-scryfall-json ;
 
-: mtg-artwork-cards ( -- json )
+MEMO: mtg-artwork-cards ( -- json )
     "unique_artwork" scryfall-artwork-json-path load-scryfall-json ;
 
 MEMO: scryfall-default-cards-json ( -- json )
@@ -48,8 +53,11 @@ MEMO: scryfall-rulings-json ( -- json )
     >url path>> "/" ?head drop "/" "-" replace
     scryfall-images-path "" prepend-as ;
 
-: map-card-faces ( assoc quot -- seq )
-    [ "card_faces" of ] dip map ; inline
+: filter-multi-card-faces ( assoc -- seq )
+    [ "card_faces" of length 1 > ] filter ; inline
+
+: multi-card-faces? ( assoc -- seq )
+    "card_faces" of length 1 > ; inline
 
 : card>image-uris ( assoc -- seq )
     [ "image_uris" of ]
@@ -61,7 +69,7 @@ MEMO: scryfall-rulings-json ( -- json )
 
 : download-scryfall-image ( assoc -- path )
     dup scryfall-local-image-path dup delete-when-zero-size
-    [ download-once-to ] [ nip ] if ;
+    [ download-once-as ] [ nip ] if ;
 
 : download-normal-images ( seq -- seq' )
     ensure-scryfall-images-directory
@@ -71,28 +79,37 @@ MEMO: scryfall-rulings-json ( -- json )
     ensure-scryfall-images-directory
     small-images [ download-scryfall-image load-image ] map ;
 
-MEMO: all-cards-by-name ( -- assoc )
-    mtg-oracle-cards
-    [ "name" of ] collect-by
-    [ first ] map-values ;
-
-: find-card-by-name ( seq name -- card ) '[ "name" of _ = ] filter ;
-: cards-by-name ( seq -- assoc ) [ "name" of ] collect-by ;
-: cards-by-cmc ( seq -- assoc ) [ "cmc" of ] collect-by ;
-: cards-by-color-identity ( seq -- assoc ) [ "color_identity" of ] collect-by-multi ;
-: red-color-identity ( seq -- seq' ) cards-by-color-identity "R" of ;
-: blue-color-identity ( seq -- seq' ) cards-by-color-identity "U" of ;
-: green-color-identity ( seq -- seq' ) cards-by-color-identity "G" of ;
-: black-color-identity ( seq -- seq' ) cards-by-color-identity "B" of ;
-: white-color-identity ( seq -- seq' ) cards-by-color-identity "W" of ;
+: collect-cards-by-name ( seq -- assoc ) [ "name" of ] collect-by ;
+: collect-cards-by-cmc ( seq -- assoc ) [ "cmc" of ] collect-by ;
+: collect-cards-by-mana-cost ( seq -- assoc ) [ "mana_cost" of ] collect-by ;
+: collect-cards-by-color-identity ( seq -- assoc ) [ "color_identity" of ] collect-by-multi ;
+: red-color-identity ( seq -- seq' ) collect-cards-by-color-identity "R" of ;
+: blue-color-identity ( seq -- seq' ) collect-cards-by-color-identity "U" of ;
+: green-color-identity ( seq -- seq' ) collect-cards-by-color-identity "G" of ;
+: black-color-identity ( seq -- seq' ) collect-cards-by-color-identity "B" of ;
+: white-color-identity ( seq -- seq' ) collect-cards-by-color-identity "W" of ;
 
 : find-card-by-color-identity-intersect ( cards colors -- cards' )
-    [ cards-by-color-identity ] dip [ of ] with map intersect-all ;
+    [ collect-cards-by-color-identity ] dip [ of ] with map intersect-all ;
 
 : find-any-color-identities ( cards colors -- cards' )
-    [ cards-by-color-identity ] dip [ of ] with map union-all ;
+    [ collect-cards-by-color-identity ] dip [ of ] with map union-all ;
+
+: color-identity-complement ( seq -- seq' ) [ { "B" "G" "R" "U" "W" } ] dip diff ;
 
-: color-identity-complement ( seq -- seq' ) [ { "W" "U" "B" "R" "G" } ] dip diff ;
+: split-mana-cost ( string -- seq )
+    f like [ " // " split1 swap ] { } loop>sequence nip ;
+
+: casting-cost-combinations ( seq -- seq' )
+    sequence-cartesian-product [ [ first ] sort-by ] map ;
+
+: parse-mana-cost ( string -- seq )
+    split-mana-cost
+    [
+        "{}" split harvest
+        [ "/" split ] map
+        casting-cost-combinations
+    ] map ;
 
 : remove-color-identities ( cards colors -- cards' )
     dupd find-any-color-identities diff ;
@@ -174,11 +191,11 @@ MEMO: all-cards-by-name ( -- assoc )
 : filter-sultai-exact ( seq -- seq' ) { "B" "G" "U" } find-exact-color-identities ;
 : filter-temur-exact ( seq -- seq' ) { "G" "U" "R" } find-exact-color-identities ;
 
-: filter-non-white ( seq -- seq' ) { "U" "B" "R" "G" } find-any-color-identities ;
-: filter-non-blue ( seq -- seq' ) { "W" "B" "R" "G" } find-any-color-identities ;
-: filter-non-black ( seq -- seq' ) { "W" "U" "R" "G" } find-any-color-identities ;
-: filter-non-red ( seq -- seq' ) { "W" "U" "B" "G" } find-any-color-identities ;
-: filter-non-green ( seq -- seq' ) { "W" "U" "B" "R" } find-any-color-identities ;
+: filter-non-white ( seq -- seq' ) { "U" "B" "R" "G" } find-only-color-identities ;
+: filter-non-blue ( seq -- seq' ) { "W" "B" "R" "G" } find-only-color-identities ;
+: filter-non-black ( seq -- seq' ) { "W" "U" "R" "G" } find-only-color-identities ;
+: filter-non-red ( seq -- seq' ) { "W" "U" "B" "G" } find-only-color-identities ;
+: filter-non-green ( seq -- seq' ) { "W" "U" "B" "R" } find-only-color-identities ;
 
 : filter-legalities ( seq name -- seq' ) '[ "legalities" of _ of "legal" = ] filter ;
 : filter-standard ( seq -- seq' ) "standard" filter-legalities ;
@@ -204,6 +221,11 @@ MEMO: all-cards-by-name ( -- assoc )
 : filter-premodern ( seq -- seq' ) "premodern" filter-legalities ;
 : filter-predh ( seq -- seq' ) "predh" filter-legalities ;
 
+: spanish-standard-cards ( -- seq )
+    scryfall-all-cards-json
+    filter-standard
+    [ "lang" of "es" = ] filter ;
+
 : filter-red-any ( seq -- seq' ) [ "colors" of "R" swap member? ] filter ;
 : filter-red-only ( seq -- seq' ) [ "colors" of { "R" } = ] filter ;
 : filter-blue-any ( seq -- seq' ) [ "colors" of "U" swap member? ] filter ;
@@ -231,17 +253,69 @@ MEMO: all-cards-by-name ( -- assoc )
     ] bi@ 2array sift ;
 
 : type-line-of ( assoc -- string ) "type_line" of parse-type-line ;
-: any-type? ( seq name -- ? ) [ type-line-of ] dip '[ first _ member-of? ] any? ;
-: any-subtype? ( seq name -- ? ) [ type-line-of ] dip '[ second _ member-of? ] any? ;
 
-: filter-creature-type ( seq type -- seq' ) '[ _ any-subtype? ] filter ;
+: types-of ( assoc -- seq ) type-line-of [ first ] map concat ;
+: subtypes-of ( assoc -- seq ) type-line-of [ second ] map concat ;
+
+! cards can have several type lines (one for each face)
+: any-type? ( json name -- ? )
+    [ type-line-of ] dip >lower '[ first [ >lower ] map _ member-of? ] any? ;
+: any-subtype? ( json name -- ? )
+    [ type-line-of ] dip >lower '[ second [ >lower ] map _ member-of? ] any? ;
 
+: type-intersects? ( json types -- ? )
+    [ type-line-of ] dip [ >lower ] map '[ first [ >lower ] map _ intersects? ] any? ;
+: subtype-intersects? ( json subtypes -- ? )
+    [ type-line-of ] dip [ >lower ] map '[ second [ >lower ] map _ intersects? ] any? ;
+
+: filter-type ( seq text -- seq' ) '[ _ any-type? ] filter ;
+: filter-subtype ( seq text -- seq' ) '[ _ any-subtype? ] filter ;
+: filter-type-intersects ( seq text -- seq' ) '[ _ type-intersects? ] filter ;
+: filter-subtype-intersects ( seq text -- seq' ) '[ _ subtype-intersects? ] filter ;
+
+: filter-basic ( seq -- seq' ) [ "Basic" any-type? ] filter ;
+: filter-basic-subtype ( seq text -- seq' ) [ filter-basic ] dip filter-subtype ;
 : filter-land ( seq -- seq' ) [ "Land" any-type? ] filter ;
+: filter-land-subtype ( seq text -- seq' ) [ filter-land ] dip filter-subtype ;
 : filter-creature ( seq -- seq' ) [ "Creature" any-type? ] filter ;
+: filter-creature-subtype ( seq text -- seq' ) [ filter-creature ] dip filter-subtype ;
+: filter-emblem ( seq -- seq' ) [ "Emblem" any-type? ] filter ;
+: filter-emblem-subtype ( seq text -- seq' ) [ filter-emblem ] dip filter-subtype ;
 : filter-enchantment ( seq -- seq' ) [ "Enchantment" any-type? ] filter ;
+: filter-enchantment-subtype ( seq text -- seq' ) [ filter-enchantment ] dip filter-subtype ;
+: filter-saga ( seq -- seq' ) "saga" filter-enchantment-subtype ;
 : filter-instant ( seq -- seq' ) [ "Instant" any-type? ] filter ;
+: filter-instant-subtype ( seq text -- seq' ) [ filter-instant ] dip filter-subtype ;
 : filter-sorcery ( seq -- seq' ) [ "Sorcery" any-type? ] filter ;
+: filter-sorcery-subtype ( seq text -- seq' ) [ filter-sorcery ] dip filter-subtype ;
 : filter-planeswalker ( seq -- seq' ) [ "Planeswalker" any-type? ] filter ;
+: filter-planeswalker-subtype ( seq text -- seq' ) [ filter-planeswalker ] dip filter-subtype ;
+: filter-legendary ( seq -- seq' ) [ "Legendary" any-type? ] filter ;
+: filter-legendary-subtype ( seq text -- seq' ) [ filter-legendary ] dip filter-subtype ;
+: filter-battle ( seq -- seq' ) [ "Battle" any-type? ] filter ;
+: filter-battle-subtype ( seq text -- seq' ) [ filter-battle ] dip filter-subtype ;
+: filter-artifact ( seq -- seq' ) [ "Artifact" any-type? ] filter ;
+: filter-artifact-subtype ( seq text -- seq' ) [ filter-artifact ] dip filter-subtype ;
+
+: reject-basic ( seq -- seq' ) [ "Basic" any-type? ] reject ;
+: reject-land ( seq -- seq' ) [ "Land" any-type? ] reject ;
+: reject-creature ( seq -- seq' ) [ "Creature" any-type? ] reject ;
+: reject-emblem ( seq -- seq' ) [ "Emblem" any-type? ] reject ;
+: reject-enchantment ( seq -- seq' ) [ "Enchantment" any-type? ] reject ;
+: reject-instant ( seq -- seq' ) [ "Instant" any-type? ] reject ;
+: reject-sorcery ( seq -- seq' ) [ "Sorcery" any-type? ] reject ;
+: reject-planeswalker ( seq -- seq' ) [ "Planeswalker" any-type? ] reject ;
+: reject-legendary ( seq -- seq' ) [ "Legendary" any-type? ] reject ;
+: reject-battle ( seq -- seq' ) [ "Battle" any-type? ] reject ;
+: reject-artifact ( seq -- seq' ) [ "Artifact" any-type? ] reject ;
+
+: filter-mounts ( seq -- seq' ) "mount" filter-subtype ;
+: filter-vehicles ( seq -- seq' ) "vehicle" filter-subtype ;
+: filter-adventure ( seq -- seq' ) "adventure" filter-subtype ;
+: filter-aura ( seq -- seq' ) "aura" filter-subtype ;
+: filter-aura-subtype ( seq text -- seq' ) [ filter-aura ] dip filter-subtype ;
+: filter-equipment ( seq -- seq' ) "Equipment" filter-subtype ;
+: filter-equipment-subtype ( seq text -- seq' ) [ filter-equipment ] dip filter-subtype ;
 
 : filter-common ( seq -- seq' ) '[ "rarity" of "common" = ] filter ;
 : filter-uncommon ( seq -- seq' ) '[ "rarity" of "uncommon" = ] filter ;
@@ -249,18 +323,158 @@ MEMO: all-cards-by-name ( -- assoc )
 : filter-mythic ( seq -- seq' ) '[ "rarity" of "mythic" = ] filter ;
 
 : standard-cards ( -- seq' ) mtg-oracle-cards filter-standard ;
+: historic-cards ( -- seq' ) mtg-oracle-cards filter-historic ;
+: modern-cards ( -- seq' ) mtg-oracle-cards filter-modern ;
 
 : sort-by-cmc ( assoc -- assoc' ) [ "cmc" of ] sort-by ;
 : histogram-by-cmc ( assoc -- assoc' ) [ "cmc" of ] histogram-by sort-keys ;
 
+: filter-by-itext-prop ( seq string prop -- seq' )
+    swap >lower '[ _ of >lower _ subseq-of? ] filter ;
+
+: filter-by-text-prop ( seq string prop -- seq' )
+    swap '[ _ of _ subseq-of? ] filter ;
+
+: map-card-faces ( json quot -- seq )
+    '[ [ "card_faces" of ] [ ] [ 1array ] ?if _ map ] map ; inline
+
+: all-card-types ( seq -- seq' )
+    [ "type_line" of ] map-card-faces
+    concat members sort ;
+
+: card>faces ( assoc -- seq )
+    [ "card_faces" of ] [ ] [ 1array ] ?if ;
+
+: filter-card-faces-sub-card ( seq quot -- seq )
+    [ [ card>faces ] map concat ] dip filter ; inline
+
+: filter-card-faces-sub-card-prop ( seq string prop -- seq' )
+    swap '[ _ of _ subseq-of? ] filter-card-faces-sub-card ;
+
+: filter-card-faces-sub-card-iprop ( seq string prop -- seq' )
+    swap >lower '[ _ of >lower _ subseq-of? ] filter-card-faces-sub-card ;
+
+: filter-card-faces-main-card ( seq quot -- seq )
+    dup '[ [ "card_faces" of ] [ _ any? ] _ ?if ] filter ; inline
+
+: filter-card-faces-main-card-prop ( seq string prop -- seq' )
+    swap '[ _ of _ subseq-of? ] filter-card-faces-main-card ;
+
+: filter-card-faces-main-card-iprop ( seq string prop -- seq' )
+    swap >lower '[ _ of >lower _ subseq-of? ] filter-card-faces-main-card ;
+
+: filter-by-flavor-text ( seq string -- seq' )
+    "flavor_text" filter-card-faces-main-card-prop ;
+
+: filter-by-flavor-itext ( seq string -- seq' )
+    "flavor_text" filter-card-faces-main-card-iprop ;
+
 : filter-by-oracle-text ( seq string -- seq' )
-    '[ "oracle_text" of _ subseq-of? ] filter ;
+    "oracle_text" filter-card-faces-main-card-prop ;
 
 : filter-by-oracle-itext ( seq string -- seq' )
-    >lower
-    '[ "oracle_text" of >lower _ subseq-of? ] filter ;
-
-: filter-flash ( seq -- seq' ) "Flash" filter-by-oracle-text ;
+    "oracle_text" filter-card-faces-main-card-iprop ;
+
+: filter-by-name-text ( seq string -- seq' ) "name" filter-by-text-prop ;
+: filter-by-name-itext ( seq string -- seq' ) "name" filter-by-itext-prop ;
+
+: filter-create-treasure ( seq -- seq' ) "create a treasure token" filter-by-oracle-itext ;
+: filter-treasure-token ( seq -- seq' ) "treasure token" filter-by-oracle-itext ;
+: filter-create-blood-token ( seq -- seq' ) "create a blood token" filter-by-oracle-itext ;
+: filter-blood-token ( seq -- seq' ) "blood token" filter-by-oracle-itext ;
+: filter-create-map-token ( seq -- seq' ) "create a map token" filter-by-oracle-itext ;
+: filter-map-token ( seq -- seq' ) "map token" filter-by-oracle-itext ;
+
+: filter-affinity ( seq -- seq' ) "affinity" filter-by-oracle-itext ;
+: filter-backup ( seq -- seq' ) "backup" filter-by-oracle-itext ;
+: filter-blitz ( seq -- seq' ) "blitz" filter-by-oracle-itext ;
+: filter-compleated ( seq -- seq' ) "compleated" filter-by-oracle-itext ;
+: filter-corrupted ( seq -- seq' ) "corrupted" filter-by-oracle-itext ;
+: filter-counter ( seq -- seq' ) "counter" filter-by-oracle-itext ;
+: filter-crew ( seq -- seq' ) "crew" filter-by-oracle-itext ;
+: filter-cycling ( seq -- seq' ) "cycling" filter-by-oracle-itext ;
+: filter-deathtouch ( seq -- seq' ) "deathtouch" filter-by-oracle-itext ;
+: filter-defender ( seq -- seq' ) "defender" filter-by-oracle-itext ;
+: filter-descend ( seq -- seq' ) "descend" filter-by-oracle-itext ;
+: filter-destroy-target ( seq -- seq' ) "destroy target" filter-by-oracle-itext ;
+: filter-discover ( seq -- seq' ) "discover" filter-by-oracle-itext ;
+: filter-disguise ( seq -- seq' ) "disguise" filter-by-oracle-itext ;
+: filter-domain ( seq -- seq' ) "domain" filter-by-oracle-itext ;
+: filter-double-strike ( seq -- seq' ) "double strike" filter-by-oracle-itext ;
+: filter-equip ( seq -- seq' ) "equip" filter-by-oracle-itext ;
+: filter-equip-n ( seq -- seq' ) "equip {" filter-by-oracle-itext ;
+: filter-exile ( seq -- seq' ) "exile" filter-by-oracle-itext ;
+: filter-fights ( seq -- seq' ) "fights" filter-by-oracle-itext ;
+: filter-first-strike ( seq -- seq' ) "first strike" filter-by-oracle-itext ;
+: filter-flash ( seq -- seq' ) "flash" filter-by-oracle-itext ;
+: filter-flying ( seq -- seq' ) "flying" filter-by-oracle-itext ;
+: filter-for-mirrodin ( seq -- seq' ) "for mirrodin!" filter-by-oracle-itext ;
+: filter-graveyard ( seq -- seq' ) "graveyard" filter-by-oracle-itext ;
+: filter-haste ( seq -- seq' ) "haste" filter-by-oracle-itext ;
+: filter-hideaway ( seq -- seq' ) "hideaway" filter-by-oracle-itext ;
+: filter-hexproof ( seq -- seq' ) "hexproof" filter-by-oracle-itext ;
+: filter-indestructible ( seq -- seq' ) "indestructible" filter-by-oracle-itext ;
+: filter-investigate ( seq -- seq' ) "investigate" filter-by-oracle-itext ;
+: filter-lifelink ( seq -- seq' ) "lifelink" filter-by-oracle-itext ;
+: filter-madness ( seq -- seq' ) "madness" filter-by-oracle-itext ;
+: filter-menace ( seq -- seq' ) "menace" filter-by-oracle-itext ;
+: filter-mill ( seq -- seq' ) "mill" filter-by-oracle-itext ;
+: filter-ninjutsu ( seq -- seq' ) "ninjutsu" filter-by-oracle-itext ;
+: filter-proliferate ( seq -- seq' ) "proliferate" filter-by-oracle-itext ;
+: filter-protection ( seq -- seq' ) "protection" filter-by-oracle-itext ;
+: filter-prowess ( seq -- seq' ) "prowess" filter-by-oracle-itext ;
+: filter-reach ( seq -- seq' ) "reach" filter-by-oracle-itext ;
+: filter-read-ahead ( seq -- seq' ) "read ahead" filter-by-oracle-itext ;
+: filter-reconfigure ( seq -- seq' ) "reconfigure" filter-by-oracle-itext ;
+: filter-role ( seq -- seq' ) "role" filter-by-oracle-itext ;
+: filter-sacrifice ( seq -- seq' ) "sacrifice" filter-by-oracle-itext ;
+: filter-scry ( seq -- seq' ) "scry" filter-by-oracle-itext ;
+: filter-shroud ( seq -- seq' ) "shroud" filter-by-oracle-itext ;
+: filter-token ( seq -- seq' ) "token" filter-by-oracle-itext ;
+: filter-toxic ( seq -- seq' ) "toxic" filter-by-oracle-itext ;
+: filter-trample ( seq -- seq' ) "trample" filter-by-oracle-itext ;
+: filter-vehicle ( seq -- seq' ) "vehicle" filter-by-oracle-itext ;
+: filter-vigilance ( seq -- seq' ) "vigilance" filter-by-oracle-itext ;
+: filter-ward ( seq -- seq' ) "ward" filter-by-oracle-itext ;
+
+: filter-day ( seq -- seq' ) "day" filter-by-oracle-itext ;
+: filter-night ( seq -- seq' ) "night" filter-by-oracle-itext ;
+: filter-daybound ( seq -- seq' ) "daybound" filter-by-oracle-itext ;
+: filter-nightbound ( seq -- seq' ) "nightbound" filter-by-oracle-itext ;
+
+: filter-cave ( seq -- seq' ) "cave" filter-land-subtype ;
+: filter-sphere ( seq -- seq' ) "sphere" filter-land-subtype ;
+
+: filter-mount ( seq -- seq' ) "mount" filter-by-oracle-itext ;
+: filter-outlaw ( seq -- seq' )
+    { "Assassin" "Mercenary" "Pirate" "Rogue" "Warlock" } filter-subtype-intersects ;
+: filter-plot ( seq -- seq' ) "plot" filter-by-oracle-itext ;
+: filter-saddle ( seq -- seq' ) "saddle" filter-by-oracle-itext ;
+: filter-spree ( seq -- seq' ) "saddle" filter-by-oracle-itext ;
+
+: power>n ( string -- n/f )
+    [ "*" = ] [ drop -1 ] [ string>number ] ?if ;
+
+: mtg<  ( string/n/f n -- seq' ) [ power>n ] dip { [ and ] [ < ] } 2&& ;
+: mtg<= ( string/n/f n -- seq' ) [ power>n ] dip { [ and ] [ <= ] } 2&& ;
+: mtg>  ( string/n/f n -- seq' ) [ power>n ] dip { [ and ] [ > ] } 2&& ;
+: mtg>= ( string/n/f n -- seq' ) [ power>n ] dip { [ and ] [ >= ] } 2&& ;
+: mtg=  ( string/n/f n -- seq' ) [ power>n ] dip { [ and ] [ = ] } 2&& ;
+
+: filter-power=* ( seq -- seq' ) [ "power" of "*" = ] filter-card-faces-main-card ;
+: filter-toughness=* ( seq -- seq' ) [ "toughness" of "*" = ] filter-card-faces-main-card ;
+
+: filter-power= ( seq n -- seq' ) '[ "power" of _ mtg= ] filter-card-faces-main-card ;
+: filter-power< ( seq n -- seq' ) '[ "power" of _ mtg< ] filter-card-faces-main-card ;
+: filter-power> ( seq n -- seq' ) '[ "power" of _ mtg> ] filter-card-faces-main-card ;
+: filter-power<= ( seq n -- seq' ) '[ "power" of _ mtg<= ] filter-card-faces-main-card ;
+: filter-power>= ( seq n -- seq' ) '[ "power" of _ mtg>= ] filter-card-faces-main-card ;
+
+: filter-toughness= ( seq n -- seq' ) '[ "toughness" of _ mtg= ] filter-card-faces-main-card ;
+: filter-toughness< ( seq n -- seq' ) '[ "toughness" of _ mtg< ] filter-card-faces-main-card ;
+: filter-toughness> ( seq n -- seq' ) '[ "toughness" of _ mtg> ] filter-card-faces-main-card ;
+: filter-toughness<= ( seq n -- seq' ) '[ "toughness" of _ mtg<= ] filter-card-faces-main-card ;
+: filter-toughness>= ( seq n -- seq' ) '[ "toughness" of _ mtg>= ] filter-card-faces-main-card ;
 
 : map-props ( seq props -- seq' ) '[ _ intersect-keys ] map ;
 
@@ -271,6 +485,10 @@ MEMO: all-cards-by-name ( -- assoc )
 
 : images. ( seq -- ) [ <image-gadget> ] map gadgets. ;
 
+: normal-images-grid. ( seq -- )
+    4 group
+    [ [ card>image-uris ] map concat download-normal-images images. ] each ;
+
 : small-card. ( assoc -- )
     card>image-uris download-small-images images. ;
 
@@ -280,26 +498,28 @@ MEMO: all-cards-by-name ( -- assoc )
     card>image-uris download-normal-images images. ;
 
 : normal-cards. ( seq -- ) [ normal-card. ] each ;
+: standard-cards. ( seq -- ) filter-standard normal-cards. ;
+: historic-cards. ( seq -- ) filter-historic normal-cards. ;
+: modern-cards. ( seq -- ) filter-modern normal-cards. ;
 
-: card-face-summary. ( seq -- )
+! rarity is only on main card `json` (if there are two faces)
+: card-face-summary. ( json seq -- )
     {
-        [ "name" of write bl ]
-        [ "mana_cost" of ?print ]
-        [ "type_line" of ?print ]
-        [ [ "power" of ] [ "toughness" of ] bi 2dup and [ "/" glue print ] [ 2drop ] if ]
-        [ "oracle_text" of ?print ]
-    } cleave nl ;
+        [ nip "name" of write bl ]
+        [ nip "mana_cost" of ?print ]
+        [ nip "type_line" of ?write ]
+        [ drop bl "--" write bl "rarity" of >title ?print ]
+        [ nip [ "power" of ] [ "toughness" of ] bi 2dup and [ "/" glue print ] [ 2drop ] if ]
+        [ nip "oracle_text" of ?print ]
+    } 2cleave nl ;
 
-: card-face-summaries. ( seq -- ) [ card-face-summary. ] each ;
+: card-face-summaries. ( json seq -- ) [ card-face-summary. ] with each ;
 
 : card-summary. ( assoc -- )
-    {
-        [
-            [ "card_faces" of ]
-            [ [ length number>string "Card Faces: " prepend print ] [ card-face-summaries. ] bi ]
-            [ card-face-summary. ] ?if
-        ]
-    } cleave nl nl nl ;
+    dup
+    [ "card_faces" of ]
+    [ [ length number>string "Card Faces: " prepend print ] [ card-face-summaries. ] bi ]
+    [ card-face-summary. ] ?if nl nl nl ;
 
 : card-summaries. ( seq -- ) [ card-summary. ] each ;
 
@@ -311,8 +531,254 @@ MEMO: all-cards-by-name ( -- assoc )
 
 : standard-dragons. ( -- )
     standard-cards
-    "Dragon" filter-creature-type
+    "Dragon" filter-creature-subtype
     sort-by-cmc
     normal-cards. ;
 
 : collect-by-cmc ( seq -- seq' ) [ "cmc" of ] collect-by ;
+
+MEMO: mtg-sets-by-abbrev ( -- assoc )
+    scryfall-all-cards-json
+    [ [ "set" of ] [ "set_name" of ] bi ] H{ } map>assoc ;
+
+MEMO: mtg-sets-by-name ( -- assoc )
+    scryfall-all-cards-json
+    [ [ "set_name" of ] [ "set" of ] bi ] H{ } map>assoc ;
+
+: filter-mtg-set ( seq abbrev -- seq ) '[ "set" of _ = ] filter ;
+
+: unique-set-names ( seq -- seq' ) [ "set_name" of ] map members ;
+: unique-set-abbrevs ( seq -- seq' ) [ "set" of ] map members ;
+
+: standard-set-names ( -- seq ) standard-cards unique-set-names ;
+: standard-set-abbrevs ( -- seq ) standard-cards unique-set-abbrevs ;
+
+
+: sets-by-release-date ( -- assoc )
+    scryfall-all-cards-json
+    [ [ "set_name" of ] [ "released_at" of ] bi ] H{ } map>assoc
+    sort-values ;
+
+: collect-cards-by-set-abbrev ( seq -- assoc ) [ "set" of ] collect-by ;
+: collect-cards-by-set-name ( seq -- assoc ) [ "set_name" of ] collect-by ;
+: cards-by-set-abbrev ( -- assoc ) mtg-oracle-cards collect-cards-by-set-abbrev ;
+: cards-by-set-name ( -- assoc ) mtg-oracle-cards collect-cards-by-set-name ;
+
+: filter-set ( seq abbrev -- seq ) >lower '[ "set" of _ = ] filter ;
+: filter-set-intersect ( seq abbrevs -- seq ) [ >lower ] map '[ "set" of _ member? ] filter ;
+
+: mid-cards ( -- seq ) mtg-oracle-cards "mid" filter-set ;
+: vow-cards ( -- seq ) mtg-oracle-cards "vow" filter-set ;
+: neo-cards ( -- seq ) mtg-oracle-cards "neo" filter-set ;
+: snc-cards ( -- seq ) mtg-oracle-cards "snc" filter-set ;
+: dmu-cards ( -- seq ) mtg-oracle-cards "dmu" filter-set ;
+: bro-cards ( -- seq ) mtg-oracle-cards "bro" filter-set ;
+: one-cards ( -- seq ) mtg-oracle-cards "one" filter-set ;
+: mom-cards ( -- seq ) mtg-oracle-cards "mom" filter-set ;
+: mat-cards ( -- seq ) mtg-oracle-cards "mat" filter-set ;
+: woe-cards ( -- seq ) mtg-oracle-cards "woe" filter-set ;
+: woe-cards-bonus ( -- seq ) mtg-oracle-cards [ "set" of "wot" = ] filter-set ;
+: woe-cards-all ( -- seq ) mtg-oracle-cards { "woe" "wot" } filter-set-intersect ;
+: lci-cards ( -- seq ) mtg-oracle-cards "lci" filter-set ;
+: mkm-cards ( -- seq ) mtg-oracle-cards "mkm" filter-set ;
+: otj-cards ( -- seq ) mtg-oracle-cards "otj" filter-set ;
+: otj-cards-bonus ( -- seq ) mtg-oracle-cards "big" filter-set ;
+: otj-cards-all ( -- seq ) mtg-oracle-cards { "otj" "big" } filter-set-intersect ;
+
+: sort-by-colors ( seq -- seq' )
+    {
+        { [ "color_identity" of length ] <=> }
+        { [ "color_identity" of sort ?first "A" or ] <=> }
+        { [ "cmc" of ] <=> }
+        { [ "mana_cost" of length ] <=> }
+        { [ "creature" any-type? -1 1 ? ] <=> }
+        { [ "power" of -1 1 ? ] <=> }
+        { [ "toughness" of -1 1 ? ] <=> }
+        { [ "name" of ] <=> }
+    } sort-with-spec ;
+
+: cards-by-color. ( seq -- ) sort-by-colors normal-cards. ;
+
+CONSTANT: rarity-to-number H{
+    { "common" 0 }
+    { "uncommon" 1 }
+    { "rare" 2 }
+    { "mythic" 3 }
+}
+
+: sort-by-rarity ( seq -- seq' )
+    {
+        { [ "rarity" of rarity-to-number at ] <=> }
+        { [ "color_identity" of length ] <=> }
+        { [ "color_identity" of sort ?first "A" or ] <=> }
+        { [ "cmc" of ] <=> }
+        { [ "mana_cost" of length ] <=> }
+        { [ "name" of ] <=> }
+    } sort-with-spec ;
+
+: cards-by-rarity. ( seq -- ) sort-by-rarity normal-cards. ;
+
+: sort-by-release ( seq -- seq' )
+    {
+        { [ "released_at" of ymd>timestamp ] <=> }
+        { [ "set" of ] <=> }
+    } sort-with-spec ;
+
+: cards-by-release. ( seq -- ) sort-by-release normal-cards. ;
+
+: sort-by-set-colors ( seq -- seq' )
+    {
+        { [ "released_at" of ymd>timestamp ] <=> }
+        { [ "set" of ] <=> }
+        { [ "color_identity" of length ] <=> }
+        { [ "color_identity" of sort ?first "A" or ] <=> }
+        { [ "cmc" of ] <=> }
+        { [ "mana_cost" of length ] <=> }
+        { [ "creature" any-type? -1 1 ? ] <=> }
+        { [ "power" of -1 1 ? ] <=> }
+        { [ "toughness" of -1 1 ? ] <=> }
+        { [ "name" of ] <=> }
+    } sort-with-spec ;
+
+: cards-by-set-colors. ( seq -- ) sort-by-set-colors normal-cards. ;
+
+: cards-by-name ( name -- seq' ) [ mtg-oracle-cards ] dip filter-by-name-itext sort-by-release ;
+: card-by-name ( name -- card )
+    [ mtg-oracle-cards ] dip >lower
+    [ '[ "name" of >lower _ = ] filter ?first ]
+    [ '[ "name" of >lower _ head? ] filter ?first ] 2bi or ;
+: cards-by-name. ( name -- ) cards-by-name normal-cards. ;
+: standard-cards-by-name. ( name -- ) cards-by-name standard-cards. ;
+: historic-cards-by-name. ( name -- ) cards-by-name historic-cards. ;
+: modern-cards-by-name. ( name -- ) cards-by-name modern-cards. ;
+
+: paren-set? ( string -- ? )
+    { [ "(" head? ] [ ")" tail? ] [ length 5 = ] } 1&& ;
+
+: remove-set-and-num ( string -- string' )
+    " " split
+    dup 2 ?lastn
+    [ paren-set? ] [ string>number ] bi* and [
+        2 head*
+    ] when " " join ;
+
+: assoc>cards ( assoc -- seq )
+    [ card-by-name <array> ] { } assoc>map concat ;
+
+: parse-mtga-card-line ( string -- array )
+    [ blank? ] trim
+    " " split1
+    [ string>number ]
+    [ remove-set-and-num card-by-name ] bi* <array> ;
+
+: parse-mtga-cards ( strings -- seq )
+    [ parse-mtga-card-line ] map concat ;
+
+TUPLE: mtga-deck name deck sideboard section ;
+
+: <mtga-deck> ( -- mtga-deck )
+    mtga-deck new "Deck" >>section ;
+
+: <moxfield-deck> ( name deck sideboard -- deck )
+    mtga-deck new
+        swap >>sideboard
+        swap >>deck
+        swap >>name ;
+
+ERROR: unknown-mtga-deck-section section ;
+: parse-mtga-deck ( string -- mtga-deck )
+    string-lines [ [ blank? ] trim ] map harvest
+    { "About" "Deck" "Sideboard" } split*
+    [ <mtga-deck> ] dip
+    [
+        dup { "About" "Deck" "Sideboard" } intersects? [
+            first >>section
+        ] [
+            over section>> {
+                { "About" [ first "Name " ?head drop [ blank? ] trim >>name ] }
+                { "Deck" [ parse-mtga-cards >>deck ] }
+                { "Sideboard" [ parse-mtga-cards >>sideboard ] }
+                [
+                    unknown-mtga-deck-section
+                ]
+            } case
+        ] if
+    ] each ;
+
+: sort-by-deck-order ( seq -- seq' )
+    [ "Land" any-type? not ] partition
+    [ sort-by-set-colors ] bi@ append ;
+
+: cards. ( seq -- ) sort-by-deck-order normal-cards. ;
+
+: sideboard. ( seq -- )
+    sideboard>> [ "Sideboard" print sort-by-deck-order normal-cards. ] when* ;
+
+GENERIC: deck. ( obj -- )
+
+M: string deck. parse-mtga-deck deck. ;
+
+M: mtga-deck deck. [ name>> ?print ] [ deck>> cards. ] bi ;
+
+M: sequence deck. cards. ;
+
+GENERIC: deck-and-sideboard. ( mtga-deck -- )
+
+M: string deck-and-sideboard. parse-mtga-deck deck-and-sideboard. ;
+
+M: mtga-deck deck-and-sideboard. [ deck. ] [ sideboard. ] bi ;
+
+M: sequence deck-and-sideboard. deck. ;
+
+: filter-mtg-cheat-sheet ( seq -- seq' )
+    [
+        {
+            [ filter-instant ]
+            [ filter-flash ]
+            [ filter-cycling ]
+            [ filter-disguise ]
+            [ filter-madness ]
+        } cleave
+    ] { } append-outputs-as sort-by-colors ;
+
+: mtg-cheat-sheet. ( seq -- ) filter-mtg-cheat-sheet normal-cards. ;
+: mtg-cheat-sheet-text. ( seq -- ) filter-mtg-cheat-sheet card-summaries. ;
+
+MEMO: get-moxfield-user ( username -- json )
+    "https://api2.moxfield.com/v2/users/%s/decks?pageNumber=1&pageSize=100" sprintf http-get-json nip ;
+
+MEMO: get-moxfield-deck ( public-id -- json )
+    "https://api2.moxfield.com/v3/decks/all/" prepend http-get-json nip ;
+
+: moxfield-board>cards ( board -- seq )
+    "cards" of values [
+        [ "quantity" of ] [ "card" of "name" of ] bi 2array
+    ] map assoc>cards ;
+
+: json>moxfield-deck ( json -- mtga-deck )
+    [ "name" of ]
+    [
+        "boards" of
+        [ "mainboard" of moxfield-board>cards ]
+        [ "sideboard" of moxfield-board>cards ] bi
+    ] bi
+    <moxfield-deck> ;
+
+: moxfield-decks-for-username ( username -- json )
+    get-moxfield-user "data" of ;
+
+: moxfield-random-deck-for-username ( username -- json )
+    moxfield-decks-for-username
+    random "publicId" of get-moxfield-deck
+    json>moxfield-deck ;
+
+: moxfield-latest-deck-for-username ( username -- json )
+    get-moxfield-user
+    "data" of ?first "publicId" of get-moxfield-deck
+    json>moxfield-deck ;
+
+: moxfield-latest-deck-for-username. ( username -- )
+    moxfield-latest-deck-for-username deck. ;
+
+: moxfield-latest-deck-and-sideboard-for-username. ( username -- )
+    moxfield-latest-deck-for-username deck-and-sideboard. ;
index 74180a963ad6ddb5c10b99d685e13476e1e46dd8..3653553f4f5d750395e4af6ae705c25226ce27ba 100644 (file)
@@ -522,3 +522,23 @@ strings tools.test ;
 
 { "34_01_" } [ 2 0 3 "01_34_" [ exchange-subseq ] keep ] unit-test
 { "cdebaf" } [ 3 0 2 "abcdef" [ exchange-subseq ] keep ] unit-test
+
+{ { } } [ { } sequence-cartesian-product ] unit-test
+{ { } } [ { { } } sequence-cartesian-product ] unit-test
+{ { } } [ { { 1 2 } { } } sequence-cartesian-product ] unit-test
+{ { { 1 } { 2 } } } [ { { 1 2 } } sequence-cartesian-product ] unit-test
+
+{
+    {
+        { 1 3 5 6 { 9 } }
+        { 1 3 5 7 { 9 } }
+        { 1 4 5 6 { 9 } }
+        { 1 4 5 7 { 9 } }
+        { 2 3 5 6 { 9 } }
+        { 2 3 5 7 { 9 } }
+        { 2 4 5 6 { 9 } }
+        { 2 4 5 7 { 9 } }
+    }
+} [
+    { { 1 2 } { 3 4 } { 5 } { 6 7 } { { 9 } } } sequence-cartesian-product
+] unit-test
index 9fbd24341184002805f18b14e78d66bb4bb7b05a..fd5b8ee2bf6b820c4f952313e504bd0113188471 100644 (file)
@@ -871,6 +871,8 @@ ERROR: slice-error-of from to seq ;
     [ find drop ] keepd swap
     [ cut ] [ f over like ] if* ; inline
 
+: ?cut ( seq n -- before after ) [ index-or-length head ] [ index-or-length tail ] 2bi ;
+
 : nth* ( n seq -- elt )
     [ length 1 - swap - ] [ nth ] bi ; inline
 
@@ -1207,3 +1209,11 @@ INSTANCE: virtual-zip-index immutable-sequence
         2dup _ exchange-unsafe
         [ 1 - ] [ 1 + ] [ 1 + ] tri*
     ] [ pick 0 > ] swap while 3drop ;
+
+: sequence-cartesian-product ( seqs -- seqs' )
+    dup length 1 <= [
+        [ [ 1array ] map ] map concat
+    ] [
+        2 cut [ first2 cartesian-product concat ] dip swap
+        [ [ suffix ] cartesian-map concat ] reduce
+    ] if ;
index f3c512f9cd2a8eaafeb603eff2a273a4c7065dc9..52c28626875b6218348b9d3302814f2cdf0c770a 100644 (file)
@@ -58,7 +58,7 @@ CONSTANT: ALPHABET "abcdefghijklmnopqrstuvwxyz"
 
 MEMO: default-dictionary ( -- counts )
     URL" https://norvig.com/big.txt" "big.txt" temp-file
-    download-once-to load-dictionary ;
+    download-once-as load-dictionary ;
 
 : (correct) ( word dictionary -- word/f )
     corrections ?first ;
index 47560add8b70734c6363d15a7fdbc3b86c5f07ec..750795fa02b913583ed7ca6a49f04524bd443b3f 100644 (file)
@@ -26,7 +26,7 @@ CONSTANT: tldr-zip URL" https://tldr-pages.github.io/assets/tldr.zip"
 
 : download-tldr ( -- )
     "tldr" cache-file dup make-directory [
-        tldr-zip "tldr.zip" download-once-to drop
+        tldr-zip "tldr.zip" download-once-as drop
         { "unzip" "tldr.zip" } try-process
     ] with-directory ;
 
index 72a7f1b1ef4b9de6daa55bb4bc1507a11b1d96d0..5343226edcb60e6ac1b78d6826f9efbb4e72bc83 100644 (file)
@@ -33,14 +33,12 @@ USING: accessors smtp ;
     "At Tracy's." >>body
 send-email
 ----
-USING: io.files io.encodings.utf8 kernel
-sequences splitting ;
+USING: io.encodings.utf8 io.files sequences
+splitting ;
 
 "table.txt" utf8 [
-    file-lines
     [ "|" split ] map flip [ "|" join ] map
-] 2keep
-set-file-lines
+] change-file-lines
 ----
 USING: sequences xml.syntax xml.writer ;
 
index 261165dbb454dd5e056b2e370943d7b8b6b58ab1..90a9e754aaa0339c152c46b91f03ec9f53d1d76f 100644 (file)
@@ -73,5 +73,5 @@ CONSTANT: video-info-url URL" https://www.youtube.com/get_video_info"
         video-formats [ "type" of "video/mp4" head? ] find nip
         video-download-url
     ] [
-        "title" of sanitize ".mp4" append download-once-to
+        "title" of sanitize ".mp4" append download-once-as
     ] tri ;
index dbf172aa47251d8f45ca7789e72371ba909105c0..86529461f7d99c0f7e3895366006deae1cc377a2 100644 (file)
@@ -26,7 +26,7 @@ IN: zealot.factor
     [ download-boot-checksum-branch ] if ;
 
 : download-boot-image ( path url -- )
-    '[ _ my-arch-name "boot.%s.image" sprintf download-to drop ] with-directory ;
+    '[ _ my-arch-name "boot.%s.image" sprintf download-as drop ] with-directory ;
 
 : arch-git-boot-image-path ( arch git-id -- str )
     "https://downloads.factorcode.org/images/build/boot.%s.image.%s" sprintf ;