{"id":23231315,"url":"https://github.com/aartaka/graven-image","last_synced_at":"2026-01-19T11:32:27.192Z","repository":{"id":167016420,"uuid":"642572025","full_name":"aartaka/graven-image","owner":"aartaka","description":"Portability library for better interaction and debugging of a running Common Lisp image through text REPL.","archived":false,"fork":false,"pushed_at":"2024-04-12T15:50:45.000Z","size":437,"stargazers_count":15,"open_issues_count":15,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-12T23:08:20.480Z","etag":null,"topics":["benchmarking","common-lisp","debugging","garbage-collection","help","inspection","lisp","memory","reflection"],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aartaka.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-05-18T21:59:02.000Z","updated_at":"2024-04-14T18:22:43.696Z","dependencies_parsed_at":"2023-10-14T17:09:21.590Z","dependency_job_id":"93a17bc9-d32b-4d18-b6fe-f3bf4de1dcb1","html_url":"https://github.com/aartaka/graven-image","commit_stats":{"total_commits":550,"total_committers":2,"mean_commits":275.0,"dds":"0.0018181818181818299","last_synced_commit":"acf04fec60d85fc6429341d5c5ab9f711a533c43"},"previous_names":["aartaka/graven-image"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/aartaka/graven-image","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aartaka%2Fgraven-image","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aartaka%2Fgraven-image/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aartaka%2Fgraven-image/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aartaka%2Fgraven-image/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aartaka","download_url":"https://codeload.github.com/aartaka/graven-image/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aartaka%2Fgraven-image/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28566415,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-19T08:53:44.001Z","status":"ssl_error","status_checked_at":"2026-01-19T08:52:40.245Z","response_time":67,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmarking","common-lisp","debugging","garbage-collection","help","inspection","lisp","memory","reflection"],"created_at":"2024-12-19T02:14:17.240Z","updated_at":"2026-01-19T11:32:27.170Z","avatar_url":"https://github.com/aartaka.png","language":"Common Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE:Graven Image\n\n#+begin_quote\nThou shalt not make unto thee any graven image, or any likeness of any\nthing that is in heaven above, or that is in the earth beneath, or\nthat is in the water under the earth.\n#+end_quote\n\nGraven Image is a Common Lisp portability library (a less fancier name\nmight've been =trivial-debugging=) for better interaction and\ndebugging of a running Lisp image. One can inspect and debug all the\nthings under heaven and [[https://www.corecursive.com/lisp-in-space-with-ron-garret/][above it]]—all in their own REPL-resident Lisp\nimage!\n\nGraven Image reuses compiler internals to improve/redefine the\nexisting standard functions. This \"improvement\" often comes at a cost\nof changing the API of a function (for better customizability) or\nmaking it slightly less reliable (due to unstable, compiler-internal,\nor otherwise hacky implementation.)\n\nNOTE: Graven Image is currently being refactored into more focused libraries.\nLike [[https://github.com/aartaka/trivial-time][trivial-time]] and [[https://github.com/aartaka/trivial-inspect][trivial-inspect]].\n\nThe library is purposefully limited in scope:\n- Improving the standard functions has a priority over introducing new\n  ones.\n  - [[https://github.com/m-n/repl-utilities][repl-utilities]] as a contrasting approach: it defines lots of\n    \"DWIM\" functions, significantly altering the REPL interaction.\n  - There are helpers like =function-lambda-list*= in Graven Image,\n    but these are merely aliases/one-liners over standard/improved\n    functions.\n    - I get carried away sometimes, though. =benchmark*= is one of\n      such [[https://xkcd.com/356/][nerd snipes]]. But still, it's quite close to the underlying\n      =with-time*= 😉\n- The interaction paradigm of the improved functions should stay\n  standard (i.e. use =*query-io*= where standard requires\n  =*query-io*=.)\n  - This is to ensure that libraries like [[https://github.com/atlas-engineer/ndebug/][Ndebug]] can rely on standard\n    facilities safely when used with Graven Image.\n- Function arglist can be modified for convenience, but it should\n  preferably stay as close to standard/implementation-specific arglist\n  as possible.\n- Graven Image strives to not modify the REPL/image/shell in any way,\n  relying on implementation defaults instead.\n  - [[https://github.com/ciel-lang/CIEL][CIEL]] is taking a different direction: redefining the REPL for\n    increased utility.\n  - [[https://github.com/vseloved/flight-recorder][flight-recorder]] uses a separate shell script to add a new\n    functionality.\n- Portably reusing implementation-specific functionality is better\n  than re-implementing it. But if some functionality is e.g. unique to\n  SBCL, it might be dropped as non-portable.\n  - [[https://github.com/TeMPOraL/tracer][tracer]], [[https://github.com/fukamachi/supertrace][supertrace]], [[https://github.com/40ants/cl-flamegraph][cl-framegraph]], and other non-portable\n    SBCL-specific improvements.\n\n* Getting started\n\nClone the Git repository:\n#+begin_src sh\n  git clone --recursive https://github.com/aartaka/graven-image ~/common-lisp/\n#+end_src\n\nAnd load =:graven-image= in the REPL:\n#+begin_src lisp\n  (asdf:load-system :graven-image)\n  ;; or, if you use Quicklisp\n  (ql:quickload :graven-image)\n#+end_src\n\nYou can also install Graven Image via Guix, using the bundled\n=guix.scm= file:\n#+begin_src sh\n  guix package -f guix.scm\n#+end_src\n\nSomeday (after at least minor version bump) I'll send a patch to Guix\nso that you can also install it with =guix install=. Until then—stay\ntuned (or send the patch yourself—I don't mind.)\n\n* Convenient =use=\n\nIf you want your REPL to start with Graven Image constructs accessible\nwithout package prefix, then simply use the package directly.\n#+begin_src lisp\n  (asdf:load-system :graven-image)\n  ;; Imports external symbols of Graven Image into the current package\n  ;; (CL-USER?) Safe, because Graven Image shadows no CL symbols.\n  (use-package :graven-image)\n#+end_src\n\nOr, if you're more orderly and disciplined about packages than I am,\nyou can always use =trivial-package-local-nicknames= and define a\nshorter Graven Image nickname:\n#+begin_src lisp\n  (trivial-package-local-nicknames:add-package-local-nickname\n   :g :graven-image :cl-user)\n#+end_src\n\nIf you want to replace the standard utilities with Graven Image ones,\nyou can safely do so with the familiar:\n#+begin_src lisp\n  ;; For functions\n  (fmakunbound 'apropos)\n  (setf (fdefinition 'apropos) (fdefinition 'gimage:apropos*))\n  ;; For macros\n  (setf (macro-function 'time) (macro-function 'gimage:time*))\n#+end_src\n\n\n* Enhanced functions (mostly from ANSI CL [[https://cl-community-spec.github.io/pages/Debugging-Utilities.html][Debugging Utilities]] chapter)\n\nThe functions that Graven Image exposes are safely =:use=-able\nstar-appended functions/macros (i.e. =describe*= instead of\n=describe=). Currently improved ones are:\n  - =y-or-n-p*= / =yes-or-no-p*=\n  - =apropos*= / =apropos-list*=\n  - =function-lambda-expression*=\n  - =time*=\n  - =describe*= / =inspect*=\n  - =dribble*=\n  - =documentation*=\n\nAll of the functions exposed by Graven Image are generics, so one can\neasily define =:around= and other qualified methods for them.\n\n** =y-or-n-p*=, =yes-or-no-p*= (generic functions)\n\nSignature:\n#+begin_src lisp\ny-or-n-p* \u0026optional control \u0026rest arguments =\u003e generalized-boolean\nyes-or-no-p* \u0026optional control \u0026rest arguments =\u003e generalized-boolean\n#+end_src\n\n\nImprovements are:\n- Both functions accept options from =graven-image:*yes-or-no-options*=, thus\n  allowing for \"nope\" or \"ay\" to be valid responses too.\n- Both functions mean the same now, because it makes no sense in\n  differentiating them (and because most Emacs users use a magical\n  =(fset 'yes-or-no-p 'y-or-n-p)= in their config, setting the\n  precedent for shorter yes/no queries).\n- No beeps (just define a =yes-or-no-p* :before= method to add beeps\n  if you like 'em; see the \"Customization\" section below).\n\n** =apropos-list*=, =apropos*= (generic functions)\n\nSignature:\n#+begin_src lisp\napropos-list* string \u0026optional (package nil) exported-only docs-too =\u003e list of symbols\napropos* string \u0026optional (package nil) exported-only docs-too =\u003e no values\n#+end_src\n\n=apropos-list*= now allows listing exported symbols only (with\n=exported-only=), which was a non-portable privilege of SBCL/Allegro\nuntil now. Search over docs (more intuitive for =apropos(-list)*= than\nmere name search) is possible with =docs-too=.\n\nBased on this foundation, =apropos*= lists symbols with their types,\nvalues, and documentation, so that implementation-specific formats are\ngone for a better and more unified listing:\n\n#+begin_src lisp\n  (apropos* :max)\n  ;; MAX                                            [FUNCTION (NUMBER \u0026REST\n  ;;                                                           MORE-NUMBERS) : Return the greatest of its arguments; among EQUALP greatest, return...]\n  ;; :MAX                                           [SELF-EVALUATING]\n  ;; CFFI::MAX-ALIGN\n  ;; SB-ASSEM::MAX-ALIGNMENT                        [CONSTANT = 5]\n  ;; ...\n  ;; SB-C::MAXES\n  ;; ALEXANDRIA:MAXF                                [MACRO (#:PLACE \u0026REST NUMBERS) : Modify-macro for MAX. Sets place designated by the first argument to the...]\n  ;; SB-KERNEL::MAXIMAL-BITMAP\n  ;; ...\n  ;; SB-LOOP::LOOP-ACCUMULATE-MINIMAX-VALUE         [MACRO (LM OPERATION FORM)]\n  ;; SB-LOOP::LOOP-MAXMIN-COLLECTION                [FUNCTION (SPECIFICALLY)]\n  ;; SB-LOOP::LOOP-MINIMAX                          [CLASS (STRUCTURE-OBJECT)]\n  ;; ...\n#+end_src\n\n** =function-lambda-expression*= (generic function)\n\nSignature:\n#+begin_src lisp\n  function-lambda-expression* function/macro/method/symbol \u0026optional force =\u003e list, list, symbol, list\n  ;; Alias:\n  lambda-expression* function/macro/method/symbol \u0026optional force =\u003e list, list, symbol, list\n#+end_src\n\nThis function tries to read source files, process the definitions of\nfunctions, and build at least a barebones lambda from the arglist and\ndocumentation of the function. So that CL =function-lambda-expression=\nreturns:\n#+begin_src lisp\n  (function-lambda-expression #'identity)\n  ;; =\u003e NIL, T, IDENTITY\n  (function-lambda-expression #'print-object)\n  ;; =\u003e NIL, T, PRINT-OBJECT\n#+end_src\n\nWhile the new Graven Image =function-lambda-expression= now returns:\n#+begin_src lisp\n  (function-lambda-expression* #'idenitity)\n  ;; =\u003e (LAMBDA (THING) \"This function simply returns what was passed to it.\" THING),\n  ;;    NIL, IDENTITY, (FUNCTION (T) (VALUES T \u0026OPTIONAL))\n  (function-lambda-expression* #'print-object t) ; Notice the T for FORCE, to build a dummy lambda.\n  ;; =\u003e (LAMBDA (SB-PCL::OBJECT STREAM)), NIL, PRINT-OBJECT, (FUNCTION (T T) *)\n#+end_src\n\nWhich means:\n- =identity= is actually not a closure, and has a reliable source!\n- =print-object= is a generic and thus is not really inspectable, so\n  we build a dummy lambda for it when =force= argument is provided.\n  - This might be a questionable choice, but it at least allows us to\n    get function arglists from =function-lambda-expression= in a\n    portable-ish way. The standard doesn't provide us with much ways\n    to know an arglist of a function beside this.\n\n*** Return values\n\nThings that =function-lambda-expression*= now returns are:\n- Lambda expression.\n  - For lambda functions, their source.\n  - For regular functions, their =defun= turned into a =lambda=.\n  - For anything else, a constructed empty =(lambda (arglist...)\n    documentation nil)= (only when =force= is T).\n  - Or, in case all the rest fails, NIL.\n- Whether the thing is a closure\n  - If it is, might return an alist of the actual closed-over values,\n    whenever accessible (not for all implementations).\n  - If closed-over values are not accessible, returns T.\n  - If it's not a closure, returns NIL.\n- Function name. Mostly falls back to the standard\n  =function-lambda-expression=, but also inspects\n  implementation-specific function objects if necessary.\n- Function type, whenever accessible (SBCL and ECL).\n\n*** Helpers\n\nBased on these new features of =function-lambda-expression*=, here are\nsome Graven Image-specific helpers:\n- =function-lambda-list*= :: Get the lambda list of a function.\n  - =function-arglist*= :: Alias.\n  - =lambda-list*= :: Alias for =function-lambda-list*=.\n  - =arglist*= :: Alias.\n- =function-name*= :: Get the name of a function.\n- =function-type*= :: Get its ftype.\n\n#+begin_src lisp\n  function-lambda-list* function =\u003e list\n  function-arglist* function =\u003e list\n  lambda-list* function =\u003e list\n  arglist* function =\u003e list\n  function-name* function =\u003e symbol\n  function-type* function =\u003e list\n#+end_src\n\n** =time*= (macro)\n\nSignature:\n#+begin_src lisp\ntime* \u0026rest forms =\u003e return-values\n#+end_src\n\nThe improved =time*= from Graven Image reuses as much\nimplementation-specific APIs as possible, with the predictable output\nformat.\n\nAnd it also allows providing several forms, yay!\n\n*** =benchmark*= (macro)\n\nSignature:\n#+begin_src lisp\nbenchmark* (\u0026optional (repeat 1000)) \u0026body forms =\u003e return-values\n#+end_src\n\nWhile =time*= is the standard benchmarking/profiling solution, it's\nalmost always too simple for proper benchmarking. Most systems getting\ncomplex enough end up with some form of custom\nbenchmarking. Shinmera's [[https://github.com/Shinmera/trivial-benchmark/][trivial-benchmark]] is one such example. Graven\nImage =benchmark*= is heavily inspired by =trivial-benchmark=, but has\na more portable foundation in the form of =with-time*=.\n\nAs many other benchmarking macros, =benchmark*= repeats its body a\ncertain number of times, collecting timing stats for every run, and\nthen prints aggregate statistics for the total runs.\n#+begin_src lisp\n  (gimage::benchmark* (20) ;; Repeat count.\n    (loop for i below 1000 collect (make-list i) finally (return 1)))\n  ;; Benchmark for 20 runs of\n  ;; (LOOP FOR I BELOW 1000\n  ;;       COLLECT (MAKE-LIST I)\n  ;;       FINALLY (RETURN 1))\n  ;; -                   MINIMUM        AVERAGE        MAXIMUM        TOTAL\n  ;; REAL-TIME           0.0            0.00175        0.019          0.035\n  ;; USER-RUN-TIME       0.000668       0.0016634      0.016315       0.033268\n  ;; SYSTEM-RUN-TIME     0.0            0.00021195     0.003794       0.004239\n  ;; GC-RUN-TIME         0.0            0.00085        0.017          0.017\n  ;; BYTES-ALLOCATED     7997952.0      8008154.5      8030464.0      160163090.0\n#+end_src\n\n\n*** =with-time*= (macro)\n\nSignature:\n#+begin_src lisp\nwith-time* (\u0026rest time-keywords) (\u0026rest multiple-value-args) form \u0026body body\n#+end_src\n\nAs the implementation detail of =time*= and =benchmark*=, =with-time*=\nallows to get the timing data for interactive\nquerying. =time-keywords= allow =\u0026key=-matching the timing data (like\n=:gc= time or bytes =:allocated=) for processing in the body. While\n=multiple-value-args= allow matching against the return values of the\n=form=. So we get best of the both worlds: timing data and return\nvalues. This flexibility enables =time*=, with its requirements of\nprinting the data and returning the original values at the same time.\n\nFor example, here's how one would track the allocated bytes and\ngarbage collection times when running a cons-heavy code:\n#+begin_src lisp\n  (gimage:with-time* (\u0026key aborted gc-count gc allocated)\n      (lists lists-p)\n      (loop for i below 1000\n            collect (make-list i :initial-element :hello)\n              into lists\n            finally (return (values lists t)))\n    (unless aborted\n      (format t \"Bytes allocated: ~a, GC ran ~d times for ~a seconds\"\n              allocated gc-count gc)))\n  ;; Bytes allocated: 7997952, GC ran NIL times for 0 seconds\n#+end_src\n\n** =describe*= (generic function)\n\nSignature:\n#+begin_src lisp\ndescribe* object \u0026optional (stream t) respect-methods\n#+end_src\n\nDescribes the =object= to the stream, but this time with portable\nformat of description (determined by =graven-image:description*= and\nspecified for many standard classes) and with predictable set of\nproperties (=graven-image:fields*=). In Graven Image, both\n=describe= and =inspect= have the same format and the same set of\nfields.\n\nAs a note of respect to the original =describe=, Graven Image one\nallows to reuse the =describe-object= methods defined for user\nclasses. To enable this, pass T to =respect-methods=.\n\n*** =graven-image:fields*= (generic function)\n\nSignature:\n#+begin_src lisp\nfields* object \u0026key strip-null \u0026allow-other-keys\n#+end_src\n\nReturns an undotted alist of properties for the =object=. Custom\nfields provided by Graven Image are named with keywords, while the\nimplementation-specific ones use whatever the implementation\nuses. Arrays and hash-tables are inlined into fields to allow\nindexing these right from the inspector.\n\nSee =fields*= documentation for more details.\n\n*** =graven-image:description*= (generic function)\n\nSignature:\n#+begin_src lisp\ndescription* object \u0026optional stream\n#+end_src\n\nConcise and informative description of =object= to the\n=stream=. Useful information from most of the implementations\ntested—united into one description header.\n\n** =inspect*= (generic function)\n\nSignature:\n#+begin_src lisp\ninspect* object \u0026optional strip-null\n#+end_src\n\nNew'n'shiny =inspect*= has:\n- Most commands found in other implementation, with familiar names.\n- Abbreviations like =H -\u003e HELP= (inspired by SBCL).\n- Ability to set object field values with =(:set key value)= command\n  (inspired by CCL).\n- Built-in pagination with ways to scroll it (=:next-page=,\n  =:previous-page=, =:home=) and change it (=:length=).\n- Property indexing by both integer indices and property names (with\n  abbreviations for them too!).\n- Ability to ignore =nil= properties with =strip-null= argument\n  (inspired by SBCL). On by default!\n- And the ability to evaluate arbitrary expressions (with =:evaluate=\n  command or simply by inputting something that doesn't match any\n  command).\n\nAnd here's a help menu of the new =inspect*= (in this case, inspecting\n=*readtable*=), just to get you teased:\n\n#+begin_src\nThis is an interactive interface for 5\nAvailable commands are:\n:?                            Show the instructions for using this interface.\n:HELP                         Show the instructions for using this interface.\n:QUIT                         Exit the interface.\n:EXIT                         Exit the interface.\n(:LENGTH NEW)                 Change the page size.\n(:WIDTH NEW)                  Change the page size.\n(:WIDEN NEW)                  Change the page size.\n:NEXT                         Show the next page of fields (if any).\n:PREVIOUS                     Show the previous page of fields (if any).\n:PRINT                        Print the current page of fields.\n:PAGE                         Print the current page of fields.\n:HOME                         Scroll back to the first page of fields.\n:RESET                        Scroll back to the first page of fields.\n:TOP                          Scroll back to the first page of fields.\n:THIS                         Show the currently inspected object.\n:SELF                         Show the currently inspected object.\n:REDISPLAY                    Show the currently inspected object.\n:SHOW                         Show the currently inspected object.\n:CURRENT                      Show the currently inspected object.\n:AGAIN                        Show the currently inspected object.\n(:EVAL EXPRESSION)            Evaluate the EXPRESSION.\n:UP                           Go up to the previous level of the interface.\n:POP                          Go up to the previous level of the interface.\n:BACK                         Go up to the previous level of the interface.\n(:SET KEY VALUE)              Set the KEY-ed field to VALUE.\n(:MODIFY KEY VALUE)           Set the KEY-ed field to VALUE.\n(:ISTEP KEY)                  Inspect the object under KEY.\n(:INSPECT KEY)                Inspect the object under KEY.\n:STANDARD                     Print the inspected object readably.\n:AESTHETIC                    Print the inspected object aesthetically.\n\nPossible inputs are:\n- Mere symbols: run one of the commands above, matching the symbol.\n  - If there's no matching command, then match against fields.\n    - If nothing matches, evaluate the symbol.\n- Integer: act on the field indexed by this integer.\n  - If there are none, evaluate the integer.\n- Any other atom: find the field with this atom as a key.\n  - Evaluate it otherwise.\n- S-expression: match the list head against commands and fields,\n  as above.\n  - If the list head does not match anything, evaluate the\n    s-expression.\n  - Inside this s-expression, you can use the `$' function to fetch\n    the list of values under provided keys.\n#+end_src\n\n** =dribble*= (generic function)\n\nSignature:\n#+begin_src lisp\ndribble* \u0026optional pathname (if-exists :append)\n#+end_src\n\nDribble the REPL session to =pathname=. Unlike the\nimplementation-specific =dribble=, this one formats all of the session\nas =load=-able Lisp file fully reproducing the session. So all the\ninput forms are printed verbatim, and all the outputs are commented\nout.\n\nBeware: using any interactive function (like =inspect= etc.) breaks\nthe dribble REPL. But then, it's unlikely one'd want to record\ninteractive session into a dribble file.\n\n** =documentation*= (generic function)\n\nSignature:\n#+begin_src lisp\n  documentation* object \u0026optional (doc-type t)\n  doc* object \u0026optional (doc-type t)\n#+end_src\n\nImproved version of =documentation=. Two main improvements are:\n=doc-type= is now optional, and =doc*= alias is available for\nconvenience.\n\ndocumentation.lisp also defines more =documentation= methods (and\nrespective =setf= method) to simplify documentation fetching and\nsetting. In particular, method on =(symbol (eql t))= to simplify\nsymbol documentation search; and =(t (eql 'package))= with a new\ndoc-type for package documentation convenience.\n\n** =break*= (macro)\n\nSignature:\n#+begin_src lisp\n  break* \u0026rest arguments\n#+end_src\n\nA more useful wrapper for =break=, listing the function it's called\nfrom and the provided symbol values. See examples in the docstring.\n\n* Customization\n\nGraven Image is made to be extensible. That's why most of the improved\nfunctions are generic: one can define special methods for their data\nand patch the behavior with =:before=, =:after=, and =:around=\nmethods. Most of Graven Image functions mention the variables/things\ninfluencing them in the docstring. Here's a set of useful\ncustomizations:\n\n** Beeping before =yes-or-no-p*=\n\nRestoring the standard-ish (beeping with bell (ASCII 7) character) behavior:\n#+begin_src lisp\n  (defmethod gimage:yes-or-no-p* :before (\u0026optional control \u0026rest arguments)\n    (declare (ignore control arguments))\n    (write-char (code-char 7) *query-io*)\n    (finish-output *query-io*))\n#+end_src\n\n** Changing the accepted yes/no options for =yes-or-no-p*= and =y-or-n-p*=\n#+begin_src lisp\n  ;; Make it strict yes/no as per standard.\n  (defmethod gimage:yes-or-no-p* :around (\u0026optional control \u0026rest arguments)\n    (declare (ignore control arguments))\n    (let ((gimage:*yes-or-no-options*\n            '((\"yes\" . t)\n              (\"no\" . nil))))\n      (call-next-method)))\n\n  ;; Add more yes/no options (Russian, for example).\n  (defmethod gimage:y-or-n-p* :around (\u0026optional control \u0026rest arguments)\n    (declare (ignore control arguments))\n    (let ((gimage:*yes-or-no-options*\n            (append\n             gimage:*yes-or-no-options*\n             '((\"да\" . t)\n               (\"ага\" . t)\n               (\"нет\" . nil)\n               (\"не\" . nil)\n               (\"неа\" . nil)))))\n      (call-next-method)))\n#+end_src\n\n** Sorting =apropos-list*= lists\n\nImplementations are not good at sorting things, and their results are\nnot often useful. Sorting things the way one needs is a useful\nextension. Here's a simple yet effective =:around= method that sorts\nthings by =string= occurence:\n#+begin_src lisp\n  (defmethod gimage:apropos-list* :around (string \u0026optional packages external-only docs-too)\n    \"Sort symbols by the relation of subSTRING count to the length of symbol.\"\n    (declare (ignorable packages external-only docs-too))\n    (let ((result (call-next-method)))\n      (sort\n       (remove-duplicates result)\n       ;; For more comprehensive matching, see\n       ;; a1b4ebd649e0268b1566e80709e7cea41363d006 and other commits\n       ;; before c090d6dc14e05c561cf5c39cf5f6cc02e8cd04c5.\n       #'\u003e :key (lambda (sym)\n                  (let ((match-count 0))\n                    (uiop:frob-substrings\n                     (string sym) (list (string string))\n                     (lambda (sub frob)\n                       (incf match-count)\n                       (funcall frob sub)))\n                    (/ match-count (length (string sym))))))))\n#+end_src\n\n** Changing printer settings for Graven Image output\n\nGraven Image =inspect*= function uses =*interface-lines*= for the\nnumber of properties to list. If your screen is more than 20 lines\nhigh, you might want to add more lines:\n\n#+begin_src lisp\n  (defmethod gimage:inspect* :around (object)\n    (declare (ignore object))\n    (let ((gimage:*interface-lines* 45))\n      (call-next-method)))\n#+end_src\n\nMost of Graven Image functions also rely on\nimplementation/REPL-specific printer variables, which might be\nun-intuitive, overly verbose, or too short. Binding printer variables\naround Graven Image functions helps that too:\n\n#+begin_src lisp\n  (defmethod gimage:apropos* :around (string \u0026optional package external-only docs-too)\n    (declare (ignore string package external-only docs-too))\n    ;; Note that you can also use\n    ;; `sb-ext:*compiler-print-variable-alist*' and\n    ;; `sb-ext:*debug-print-variable-alist*' on SBCL.\n    (let ((*print-case* :downcase)\n          (*print-level* 2)\n          (*print-lines* 2)\n          (*print-length* 10))\n      (call-next-method)))\n#+end_src\n\nA noisy apropos function listing like\n#+begin_src lisp\n  X86::*X86-OPERAND-TYPE-NAMES* [VARIABLE = ((:REG8 . 1) (:REG16 . 2) (:REG32 . 4) (:REG64 . 8) (:IMM8 . 16) (:IMM8S . 32) (:IMM16 . 64) (:IMM32 . 128) (:IMM32S . 256) (:IMM64 . 512) (:IMM1 . 1024) (:BASEINDEX . 2048) (:DISP8 . 4096) (:DISP16 . 8192) (:DISP32 . 16384) (:DISP32S . 32768) (:DISP64 . 65536) (:INOUTPORTREG . 131072) (:SHIFTCOUNT . 262144) (:CONTROL . 524288) (:DEBUG . 1048576) (:TEST . 2097152) (:FLOATREG . 4194304) (:FLOATACC . 8388608) (:SREG2 . 16777216) (:SREG3 . 33554432) (:ACC . 67108864) (:JUMPABSOLUTE . 134217728) (:REGMMX . 268435456) (:REGXMM . 536870912) (:ESSEG . 1073741824) (:INVMEM . 2147483648) (:REG . 15) (:WORDREG . 14) (:IMPLICITREGISTER . 75890688) (:IMM . 1008) (:ENCIMM . 464) (:DISP . 126976) (:ANYMEM . 2147547136) (:LLONGMEM . 2147547136) (:LONGMEM . 2147547136) (:SHORTMEM . 2147547136) (:WORDMEM . 2147547136) (:BYTEMEM . 2147547136) (:LABEL . 4294967296) (:SELF . 8589934592))]\n#+end_src\nturns into a much more readable\n#+begin_src lisp\nx86::*x86-operand-type-names* [variable = ((:reg8 . 1) (:reg16 . 2) (:reg32 . 4) (:reg64 . 8) (:imm8 . 16) (:imm8s . 32) (:imm16 . 64) (:imm32 . 128) (:imm32s . 256) (:imm64 . 512) ...)]\n#+end_src\n\n** Suppressing documentation errors in =documentation*=\n\nSeveral implementations throw errors when trying to get documentation\nfor non-existent method combinations, classes, etc. It's convenient to\nsuppress these:\n#+begin_src lisp\n  (defmethod gimage:documentation* :around (object \u0026optional doc-type)\n    (ignore-errors (call-next-method)))\n#+end_src\n\nActually, one can try to write an =:around= method for regular\n=documentation=, but this modification is not guaranteed to work on\nall implementations.\n\n* Contributing\n\nYou can help with any of the [[https://github.com/aartaka/graven-image/issues?q=is%3Aopen+is%3Aissue][open issues]] most are well-described and\nsplit into bite-sized tasks. See .github/CONTIBUTING.md for the\ncontributing guidelines.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faartaka%2Fgraven-image","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faartaka%2Fgraven-image","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faartaka%2Fgraven-image/lists"}