REBOL [ Title "Ingredient index" Purpose: {Create a list of ingredients and the recipes that use them. Link from an ingredient to a recipe sub-list, and from an item on the sub-list to the recipe itself.} ] ;; -- Get a list of all text files in the current directory. TEXT-FILE?: func [ FILEID ] [ find [%.txt %.TXT] find/last FILEID "." ] FILE-LIST: read %. while [not tail? FILE-LIST] [ either TEXT-FILE? first FILE-LIST [ FILE-LIST: next FILE-LIST ] [ remove FILE-LIST ] ] FILE-LIST: head FILE-LIST ;; Don't forget to reposition to head. ;; -- Read each recipe and build a block of ;; -- ingredient-filename pairs. Sort the result on the ;; -- ingredient name. RECIPE-NAME: "" RECIPE-SOURCE: "" RECIPE-INGREDIENTS: [] RECIPE-PROCEDURE: "" RECIPE-NOTES: "" CLEAR-RECIPE-DATA: does [ RECIPE-NAME: copy "" RECIPE-SOURCE: copy "" RECIPE-INGREDIENTS: copy [] RECIPE-PROCEDURE: copy "" RECIPE-NOTES: copy "" ] ING-FILE-PAIRS: [] foreach FILE FILE-LIST [ CLEAR-RECIPE-DATA do load FILE foreach INGREDIENT RECIPE-INGREDIENTS [ append ING-FILE-PAIRS INGREDIENT/1 append ING-FILE-PAIRS FILE ] ] sort/skip ING-FILE-PAIRS 2 ;; -- Using the block of ingredient-filename pairs, ;; -- build another data structure consisting of pairs of ;; -- items, where the first item of a pair is an ingredient, ;; -- and the second is a block containing all the file names ;; -- that refer to that ingredient. CURRENT-ING: "" DATABLOCK: [] FILEBLOCK: [] foreach [INGREDIENT FILE] ING-FILE-PAIRS [ either not-equal? INGREDIENT CURRENT-ING [ either not-equal? "" CURRENT-ING [ append DATABLOCK CURRENT-ING append/only DATABLOCK FILEBLOCK CURRENT-ING: copy INGREDIENT FILEBLOCK: copy [] append FILEBLOCK FILE ] [ CURRENT-ING: copy INGREDIENT append FILEBLOCK FILE ] ] [ append FILEBLOCK FILE ] ] ;; -- Create a window with two text lists. ;; -- The first list is a list of ingredients. ;; -- The second is built when an ingredient is picked ;; -- from the first list, and is a list of all the files ;; -- that contain that ingredient. SHOW-FILES-FOR-INGREDIENT: func [ ;; Show filename text list INGREDIENT ] [ MAIN-FILES/data: select DATABLOCK INGREDIENT show MAIN-FILES ] EMAIL-PAGE: "" WS-INGREDIENT-LIST: "" SHOW-RECIPE: func [ ;; Copied out of recipe-to-email program FILE ] [ EMAIL-PAGE: copy "" WS-INGREDIENT-LIST: copy "" CLEAR-RECIPE-DATA do load to-file FILE foreach INGREDIENT-BLOCK RECIPE-INGREDIENTS [ foreach [INGREDIENT QUANTITY] INGREDIENT-BLOCK [ append WS-INGREDIENT-LIST rejoin [ INGREDIENT ", " QUANTITY newline ] ] ] append EMAIL-PAGE rejoin [ RECIPE-NAME newline newline ] append EMAIL-PAGE rejoin [ "Ingredients:" newline newline ] append EMAIL-PAGE rejoin [ WS-INGREDIENT-LIST newline ] append EMAIL-PAGE rejoin [ "Procedure:" newline ] append EMAIL-PAGE rejoin [ RECIPE-PROCEDURE newline ] append EMAIL-PAGE rejoin [ "Notes:" newline ] append EMAIL-PAGE rejoin [ RECIPE-NOTES newline ] ;; MAIN-TEXT/text: EMAIL-PAGE ;; MAIN-TEXT/line-list: none SCRLTXTV-LOAD MAIN-TEXT MAIN-SCROLLER EMAIL-PAGE show MAIN-TEXT show MAIN-SCROLLER ] ;; -- Scroll the text in response to the scroller. SCRLTXTV-SCROLL: func [TXT BAR] [ TXT/para/scroll/y: negate BAR/data * (max 0 TXT/user-data - TXT/size/y) show TXT ] ;; -- Load the text face with text passed to us in TDATA. SCRLTXTV-LOAD: func [TXT BAR TDATA] [ TXT/text: TDATA TXT/para/scroll/y: 0 TXT/line-list: none TXT/user-data: second size-text TXT BAR/data: 0 BAR/redrag TXT/size/y / TXT/user-data ;; show [TXT BAR] ;; Caller must do this, for better generality. ] view center-face layout [ across banner "Ingredient index" font [shadow: none] return text 300 "Pick an ingredient" text 300 "Pick a file name to view" return MAIN-INGREDIENTS: text-list 300x500 data (extract DATABLOCK 2) [SHOW-FILES-FOR-INGREDIENT MAIN-INGREDIENTS/picked] MAIN-FILES: text-list 300x500 [SHOW-RECIPE MAIN-FILES/picked] MAIN-TEXT: area 400x500 as-is MAIN-SCROLLER: scroller 20x500 [SCRLTXTV-SCROLL MAIN-TEXT MAIN-SCROLLER] return button "Quit" [quit] button "Debug" [halt] ]