| 405 | | (unless grep-command |
|---|
| 406 | | (setq grep-command |
|---|
| 407 | | (let ((required-options (if grep-use-null-device "-n" "-nH"))) |
|---|
| 408 | | (if (equal (condition-case nil ; in case "grep" isn't in exec-path |
|---|
| 409 | | (call-process grep-program nil nil nil |
|---|
| 410 | | "-e" "foo" null-device) |
|---|
| 411 | | (error nil)) |
|---|
| 412 | | 1) |
|---|
| 413 | | (format "%s %s -e " grep-program required-options) |
|---|
| 414 | | (format "%s %s " grep-program required-options))))) |
|---|
| 415 | | (unless grep-find-use-xargs |
|---|
| 416 | | (setq grep-find-use-xargs |
|---|
| 417 | | (if (and |
|---|
| 418 | | (equal (call-process "find" nil nil nil |
|---|
| 419 | | null-device "-print0") |
|---|
| 420 | | 0) |
|---|
| 421 | | (equal (call-process "xargs" nil nil nil |
|---|
| 422 | | "-0" "-e" "echo") |
|---|
| 423 | | 0)) |
|---|
| 424 | | 'gnu))) |
|---|
| 425 | | (unless grep-find-command |
|---|
| 426 | | (setq grep-find-command |
|---|
| 427 | | (cond ((eq grep-find-use-xargs 'gnu) |
|---|
| 428 | | (format "%s . -type f -print0 | xargs -0 -e %s" |
|---|
| 429 | | find-program grep-command)) |
|---|
| 430 | | (grep-find-use-xargs |
|---|
| 431 | | (format "%s . -type f -print | xargs %s" |
|---|
| 432 | | find-program grep-command)) |
|---|
| 433 | | (t (cons (format "%s . -type f -exec %s {} %s \\;" |
|---|
| 434 | | find-program grep-command null-device) |
|---|
| 435 | | (+ 22 (length grep-command))))))) |
|---|
| 436 | | (unless grep-tree-command |
|---|
| 437 | | (setq grep-tree-command |
|---|
| 438 | | (let* ((glen (length grep-program)) |
|---|
| 439 | | (gcmd (concat grep-program " <C>" (substring grep-command glen)))) |
|---|
| 440 | | (cond ((eq grep-find-use-xargs 'gnu) |
|---|
| 441 | | (format "%s <D> <X> -type f <F> -print0 | xargs -0 -e %s <R>" |
|---|
| 442 | | find-program gcmd)) |
|---|
| 443 | | (grep-find-use-xargs |
|---|
| 444 | | (format "%s <D> <X> -type f <F> -print | xargs %s <R>" |
|---|
| 445 | | find-program gcmd)) |
|---|
| 446 | | (t (format "%s <D> <X> -type f <F> -exec %s <R> {} %s \\;" |
|---|
| 447 | | find-program gcmd null-device)))))) |
|---|
| | 407 | (unless (and grep-command grep-find-command |
|---|
| | 408 | grep-template grep-find-template) |
|---|
| | 409 | (let ((grep-options |
|---|
| | 410 | (concat (if grep-use-null-device "-n" "-nH") |
|---|
| | 411 | (if (grep-probe grep-program |
|---|
| | 412 | `(nil nil nil "-e" "foo" ,null-device) |
|---|
| | 413 | nil 1) |
|---|
| | 414 | " -e")))) |
|---|
| | 415 | (unless grep-command |
|---|
| | 416 | (setq grep-command |
|---|
| | 417 | (format "%s %s " grep-program grep-options))) |
|---|
| | 418 | (unless grep-template |
|---|
| | 419 | (setq grep-template |
|---|
| | 420 | (format "%s <C> %s <R> <F>" grep-program grep-options))) |
|---|
| | 421 | (unless grep-find-use-xargs |
|---|
| | 422 | (setq grep-find-use-xargs |
|---|
| | 423 | (if (and |
|---|
| | 424 | (grep-probe find-program `(nil nil nil ,null-device "-print0")) |
|---|
| | 425 | (grep-probe "xargs" `(nil nil nil "-0" "-e" "echo"))) |
|---|
| | 426 | 'gnu))) |
|---|
| | 427 | (unless grep-find-command |
|---|
| | 428 | (setq grep-find-command |
|---|
| | 429 | (cond ((eq grep-find-use-xargs 'gnu) |
|---|
| | 430 | (format "%s . -type f -print0 | xargs -0 -e %s" |
|---|
| | 431 | find-program grep-command)) |
|---|
| | 432 | (grep-find-use-xargs |
|---|
| | 433 | (format "%s . -type f -print | xargs %s" |
|---|
| | 434 | find-program grep-command)) |
|---|
| | 435 | (t (cons (format "%s . -type f -exec %s {} %s \\;" |
|---|
| | 436 | find-program grep-command null-device) |
|---|
| | 437 | (+ 22 (length grep-command))))))) |
|---|
| | 438 | (unless grep-find-template |
|---|
| | 439 | (setq grep-find-template |
|---|
| | 440 | (let ((gcmd (format "%s <C> %s <R>" |
|---|
| | 441 | grep-program grep-options))) |
|---|
| | 442 | (cond ((eq grep-find-use-xargs 'gnu) |
|---|
| | 443 | (format "%s . <X> -type f <F> -print0 | xargs -0 -e %s" |
|---|
| | 444 | find-program gcmd)) |
|---|
| | 445 | (grep-find-use-xargs |
|---|
| | 446 | (format "%s . <X> -type f <F> -print | xargs %s" |
|---|
| | 447 | find-program gcmd)) |
|---|
| | 448 | (t (format "%s . <X> -type f <F> -exec %s {} %s \\;" |
|---|
| | 449 | find-program gcmd null-device)))))))) |
|---|
| 490 | | ;;;###autoload |
|---|
| 491 | | (defun grep (command-args &optional highlight-regexp) |
|---|
| 492 | | "Run grep, with user-specified args, and collect output in a buffer. |
|---|
| 493 | | While grep runs asynchronously, you can use \\[next-error] (M-x next-error), |
|---|
| 494 | | or \\<grep-mode-map>\\[compile-goto-error] in the grep \ |
|---|
| 495 | | output buffer, to go to the lines |
|---|
| 496 | | where grep found matches. |
|---|
| 497 | | |
|---|
| 498 | | This command uses a special history list for its COMMAND-ARGS, so you can |
|---|
| 499 | | easily repeat a grep command. |
|---|
| 500 | | |
|---|
| 501 | | A prefix argument says to default the argument based upon the current |
|---|
| 502 | | tag the cursor is over, substituting it into the last grep command |
|---|
| 503 | | in the grep command history (or into `grep-command' |
|---|
| 504 | | if that history list is empty). |
|---|
| 505 | | |
|---|
| 506 | | If specified, optional second arg HIGHLIGHT-REGEXP is the regexp to |
|---|
| 507 | | temporarily highlight in visited source lines." |
|---|
| 508 | | (interactive |
|---|
| 509 | | (progn |
|---|
| 510 | | (unless (and grep-command |
|---|
| 511 | | (or (not grep-use-null-device) (eq grep-use-null-device t))) |
|---|
| 512 | | (grep-compute-defaults)) |
|---|
| 513 | | (let ((default (grep-default-command))) |
|---|
| 514 | | (list (read-from-minibuffer "Run grep (like this): " |
|---|
| 515 | | (if current-prefix-arg |
|---|
| 516 | | default grep-command) |
|---|
| 517 | | nil nil 'grep-history |
|---|
| 518 | | (if current-prefix-arg nil default)))))) |
|---|
| 519 | | |
|---|
| 520 | | ;; Setting process-setup-function makes exit-message-function work |
|---|
| 521 | | ;; even when async processes aren't supported. |
|---|
| 522 | | (compilation-start (if (and grep-use-null-device null-device) |
|---|
| 523 | | (concat command-args " " null-device) |
|---|
| 524 | | command-args) |
|---|
| 525 | | 'grep-mode nil highlight-regexp)) |
|---|
| | 502 | |
|---|
| | 503 | ;;;###autoload |
|---|
| | 504 | (defun grep (command-args) |
|---|
| | 505 | "Run grep, with user-specified args, and collect output in a buffer. |
|---|
| | 506 | While grep runs asynchronously, you can use \\[next-error] (M-x next-error), |
|---|
| | 507 | or \\<grep-mode-map>\\[compile-goto-error] in the grep \ |
|---|
| | 508 | output buffer, to go to the lines |
|---|
| | 509 | where grep found matches. |
|---|
| | 510 | |
|---|
| | 511 | This command uses a special history list for its COMMAND-ARGS, so you can |
|---|
| | 512 | easily repeat a grep command. |
|---|
| | 513 | |
|---|
| | 514 | A prefix argument says to default the argument based upon the current |
|---|
| | 515 | tag the cursor is over, substituting it into the last grep command |
|---|
| | 516 | in the grep command history (or into `grep-command' |
|---|
| | 517 | if that history list is empty)." |
|---|
| | 518 | (interactive |
|---|
| | 519 | (progn |
|---|
| | 520 | (grep-compute-defaults) |
|---|
| | 521 | (let ((default (grep-default-command))) |
|---|
| | 522 | (list (read-from-minibuffer "Run grep (like this): " |
|---|
| | 523 | (if current-prefix-arg |
|---|
| | 524 | default grep-command) |
|---|
| | 525 | nil nil 'grep-history |
|---|
| | 526 | (if current-prefix-arg nil default)))))) |
|---|
| | 527 | |
|---|
| | 528 | ;; Setting process-setup-function makes exit-message-function work |
|---|
| | 529 | ;; even when async processes aren't supported. |
|---|
| | 530 | (compilation-start (if (and grep-use-null-device null-device) |
|---|
| | 531 | (concat command-args " " null-device) |
|---|
| | 532 | command-args) |
|---|
| | 533 | 'grep-mode)) |
|---|
| | 534 | |
|---|
| | 535 | |
|---|
| 568 | | (defun grep-expand-command-macros (command &optional regexp files dir excl case-fold) |
|---|
| 569 | | "Patch grep COMMAND replacing <D>, etc." |
|---|
| 570 | | (setq command |
|---|
| 571 | | (replace-regexp-in-string "<D>" |
|---|
| 572 | | (or dir ".") command t t)) |
|---|
| 573 | | (setq command |
|---|
| 574 | | (replace-regexp-in-string "<X>" |
|---|
| 575 | | (or excl "") command t t)) |
|---|
| 576 | | (setq command |
|---|
| 577 | | (replace-regexp-in-string "<F>" |
|---|
| 578 | | (or files "") command t t)) |
|---|
| 579 | | (setq command |
|---|
| 580 | | (replace-regexp-in-string "<C>" |
|---|
| 581 | | (if case-fold "-i" "") command t t)) |
|---|
| 582 | | (setq command |
|---|
| 583 | | (replace-regexp-in-string "<R>" |
|---|
| 584 | | (or regexp "") command t t)) |
|---|
| 585 | | command) |
|---|
| 586 | | |
|---|
| 587 | | (defvar grep-tree-last-regexp "") |
|---|
| 588 | | (defvar grep-tree-last-files (car (car grep-tree-files-aliases))) |
|---|
| 589 | | |
|---|
| 590 | | ;;;###autoload |
|---|
| 591 | | (defun grep-tree (regexp files dir &optional subdirs) |
|---|
| 592 | | "Grep for REGEXP in FILES in directory tree rooted at DIR. |
|---|
| 593 | | Collect output in a buffer. |
|---|
| 594 | | Interactively, prompt separately for each search parameter. |
|---|
| 595 | | With prefix arg, reuse previous REGEXP. |
|---|
| | 563 | |
|---|
| | 564 | ;; User-friendly interactive API. |
|---|
| | 565 | |
|---|
| | 566 | (defconst grep-expand-keywords |
|---|
| | 567 | '(("<C>" . (and cf (isearch-no-upper-case-p regexp t) "-i")) |
|---|
| | 568 | ("<D>" . dir) |
|---|
| | 569 | ("<F>" . files) |
|---|
| | 570 | ("<N>" . null-device) |
|---|
| | 571 | ("<X>" . excl) |
|---|
| | 572 | ("<R>" . (shell-quote-argument (or regexp "")))) |
|---|
| | 573 | "List of substitutions performed by `grep-expand-template'. |
|---|
| | 574 | If car of an element matches, the cdr is evalled in to get the |
|---|
| | 575 | substitution string. Note dynamic scoping of variables.") |
|---|
| | 576 | |
|---|
| | 577 | (defun grep-expand-template (template &optional regexp files dir excl) |
|---|
| | 578 | "Patch grep COMMAND string replacing <C>, <D>, <F>, <R>, and <X>." |
|---|
| | 579 | (let ((command template) |
|---|
| | 580 | (cf case-fold-search) |
|---|
| | 581 | (case-fold-search nil)) |
|---|
| | 582 | (dolist (kw grep-expand-keywords command) |
|---|
| | 583 | (if (string-match (car kw) command) |
|---|
| | 584 | (setq command |
|---|
| | 585 | (replace-match |
|---|
| | 586 | (or (if (symbolp (cdr kw)) |
|---|
| | 587 | (symbol-value (cdr kw)) |
|---|
| | 588 | (save-match-data (eval (cdr kw)))) |
|---|
| | 589 | "") |
|---|
| | 590 | t t command)))))) |
|---|
| | 591 | |
|---|
| | 592 | (defun grep-read-regexp () |
|---|
| | 593 | "Read regexp arg for interactive grep." |
|---|
| | 594 | (let ((default |
|---|
| | 595 | (or (funcall (or find-tag-default-function |
|---|
| | 596 | (get major-mode 'find-tag-default-function) |
|---|
| | 597 | 'find-tag-default)) |
|---|
| | 598 | ""))) |
|---|
| | 599 | (read-string |
|---|
| | 600 | (concat "Search for" |
|---|
| | 601 | (if (and default (> (length default) 0)) |
|---|
| | 602 | (format " (default %s): " default) ": ")) |
|---|
| | 603 | nil 'grep-regexp-history default))) |
|---|
| | 604 | |
|---|
| | 605 | (defun grep-read-files (regexp) |
|---|
| | 606 | "Read files arg for interactive grep." |
|---|
| | 607 | (let* ((bn (or (buffer-file-name) (buffer-name))) |
|---|
| | 608 | (fn (and bn |
|---|
| | 609 | (stringp bn) |
|---|
| | 610 | (file-name-nondirectory bn))) |
|---|
| | 611 | (default |
|---|
| | 612 | (or (and fn |
|---|
| | 613 | (let ((aliases grep-files-aliases) |
|---|
| | 614 | alias) |
|---|
| | 615 | (while aliases |
|---|
| | 616 | (setq alias (car aliases) |
|---|
| | 617 | aliases (cdr aliases)) |
|---|
| | 618 | (if (string-match (wildcard-to-regexp (cdr alias)) fn) |
|---|
| | 619 | (setq aliases nil) |
|---|
| | 620 | (setq alias nil))) |
|---|
| | 621 | (cdr alias))) |
|---|
| | 622 | (and fn |
|---|
| | 623 | (let ((ext (file-name-extension fn))) |
|---|
| | 624 | (and ext (concat "*." ext)))))) |
|---|
| | 625 | (files (read-string |
|---|
| | 626 | (concat "Search for \"" regexp |
|---|
| | 627 | "\" in files" |
|---|
| | 628 | (if default (concat " (default " default ")")) |
|---|
| | 629 | ": ") |
|---|
| | 630 | nil 'grep-files-history default))) |
|---|
| | 631 | (and files |
|---|
| | 632 | (or (cdr (assoc files grep-files-aliases)) |
|---|
| | 633 | files)))) |
|---|
| | 634 | |
|---|
| | 635 | ;;;###autoload |
|---|
| | 636 | (defun lgrep (regexp &optional files) |
|---|
| | 637 | "Run grep, searching for REGEXP in FILES in current directory. |
|---|
| 600 | | While find runs asynchronously, you can use the \\[next-error] command |
|---|
| 601 | | to find the text that grep hits refer to. |
|---|
| 602 | | |
|---|
| 603 | | This command uses a special history list for its arguments, so you can |
|---|
| 604 | | easily repeat a find command. |
|---|
| 605 | | |
|---|
| 606 | | When used non-interactively, optional arg SUBDIRS limits the search to |
|---|
| 607 | | those sub directories of DIR." |
|---|
| | 642 | With \\[universal-argument] prefix, allow user to edit the constructed |
|---|
| | 643 | shell command line before it is executed. |
|---|
| | 644 | With two \\[universal-argument] prefixes, edit and run grep shell command. |
|---|
| | 645 | |
|---|
| | 646 | Collect output in a buffer. While grep runs asynchronously, you |
|---|
| | 647 | can use \\[next-error] (M-x next-error), or \\<grep-mode-map>\\[compile-goto-error] |
|---|
| | 648 | in the grep output buffer, to go to the lines where grep found matches. |
|---|
| | 649 | |
|---|
| | 650 | This command shares argument histories with \\[rgrep] and \\[grep]." |
|---|
| 609 | | (let* ((regexp |
|---|
| 610 | | (if current-prefix-arg |
|---|
| 611 | | grep-tree-last-regexp |
|---|
| 612 | | (let* ((default (current-word)) |
|---|
| 613 | | (spec (read-string |
|---|
| 614 | | (concat "Search for" |
|---|
| 615 | | (if (and default (> (length default) 0)) |
|---|
| 616 | | (format " (default %s): " default) ": "))))) |
|---|
| 617 | | (if (equal spec "") default spec)))) |
|---|
| 618 | | (files |
|---|
| 619 | | (read-string (concat "Search for \"" regexp "\" in files (default " grep-tree-last-files "): "))) |
|---|
| 620 | | (dir |
|---|
| 621 | | (read-directory-name "Base directory: " nil default-directory t))) |
|---|
| 622 | | (list regexp files dir))) |
|---|
| 623 | | (unless grep-tree-command |
|---|
| 624 | | (grep-compute-defaults)) |
|---|
| 625 | | (unless (and (stringp files) (> (length files) 0)) |
|---|
| 626 | | (setq files grep-tree-last-files)) |
|---|
| 627 | | (when files |
|---|
| 628 | | (setq grep-tree-last-files files) |
|---|
| 629 | | (let ((mf (assoc files grep-tree-files-aliases))) |
|---|
| 630 | | (if mf |
|---|
| 631 | | (setq files (cdr mf))))) |
|---|
| 632 | | (let ((command-args (grep-expand-command-macros |
|---|
| 633 | | grep-tree-command |
|---|
| 634 | | (setq grep-tree-last-regexp regexp) |
|---|
| 635 | | (and files (concat "-name '" files "'")) |
|---|
| 636 | | (if subdirs |
|---|
| 637 | | (if (stringp subdirs) |
|---|
| 638 | | subdirs |
|---|
| 639 | | (mapconcat 'identity subdirs " ")) |
|---|
| 640 | | nil) ;; we change default-directory to dir |
|---|
| 641 | | (and grep-tree-ignore-CVS-directories "-path '*/CVS' -prune -o ") |
|---|
| 642 | | grep-tree-ignore-case)) |
|---|
| 643 | | (default-directory (file-name-as-directory (expand-file-name dir))) |
|---|
| 644 | | (null-device nil)) ; see grep |
|---|
| 645 | | (grep command-args regexp))) |
|---|
| | 652 | (progn |
|---|
| | 653 | (grep-compute-defaults) |
|---|
| | 654 | (cond |
|---|
| | 655 | ((and grep-command (equal current-prefix-arg '(16))) |
|---|
| | 656 | (list (read-from-minibuffer "Run: " grep-command |
|---|
| | 657 | nil nil 'grep-history) |
|---|
| | 658 | nil)) |
|---|
| | 659 | ((not grep-template) |
|---|
| | 660 | (list nil |
|---|
| | 661 | (read-string "grep.el: No `grep-template' available. Press RET."))) |
|---|
| | 662 | (t (let* ((regexp (grep-read-regexp)) |
|---|
| | 663 | (files (grep-read-files regexp))) |
|---|
| | 664 | (list regexp files)))))) |
|---|
| | 665 | (when (and (stringp regexp) (> (length regexp) 0)) |
|---|
| | 666 | (let ((command regexp)) |
|---|
| | 667 | (if (null files) |
|---|
| | 668 | (if (string= command grep-command) |
|---|
| | 669 | (setq command nil)) |
|---|
| | 670 | (setq command (grep-expand-template |
|---|
| | 671 | grep-template |
|---|
| | 672 | regexp |
|---|
| | 673 | files)) |
|---|
| | 674 | (when command |
|---|
| | 675 | (if (equal current-prefix-arg '(4)) |
|---|
| | 676 | (setq command |
|---|
| | 677 | (read-from-minibuffer "Confirm: " |
|---|
| | 678 | command nil nil 'grep-history)) |
|---|
| | 679 | (push command grep-history)))) |
|---|
| | 680 | (when command |
|---|
| | 681 | ;; Setting process-setup-function makes exit-message-function work |
|---|
| | 682 | ;; even when async processes aren't supported. |
|---|
| | 683 | (compilation-start (if (and grep-use-null-device null-device) |
|---|
| | 684 | (concat command " " null-device) |
|---|
| | 685 | command) 'grep-mode))))) |
|---|
| | 686 | |
|---|
| | 687 | |
|---|
| | 688 | ;;;###autoload |
|---|
| | 689 | (defun rgrep (regexp &optional files dir) |
|---|
| | 690 | "Recusively grep for REGEXP in FILES in directory tree rooted at DIR. |
|---|
| | 691 | The search is limited to file names matching shell pattern FILES. |
|---|
| | 692 | FILES may use abbreviations defined in `grep-files-aliases', e.g. |
|---|
| | 693 | entering `ch' is equivalent to `*.[ch]'. |
|---|
| | 694 | |
|---|
| | 695 | With \\[universal-argument] prefix, allow user to edit the constructed |
|---|
| | 696 | shell command line before it is executed. |
|---|
| | 697 | With two \\[universal-argument] prefixes, edit and run grep-find shell command. |
|---|
| | 698 | |
|---|
| | 699 | Collect output in a buffer. While find runs asynchronously, you |
|---|
| | 700 | can use \\[next-error] (M-x next-error), or \\<grep-mode-map>\\[compile-goto-error] |
|---|
| | 701 | in the grep output buffer, to go to the lines where grep found matches. |
|---|
| | 702 | |
|---|
| | 703 | This command shares argument histories with \\[lgrep] and \\[grep-find]." |
|---|
| | 704 | (interactive |
|---|
| | 705 | (progn |
|---|
| | 706 | (grep-compute-defaults) |
|---|
| | 707 | (cond |
|---|
| | 708 | ((and grep-find-command (equal current-prefix-arg '(16))) |
|---|
| | 709 | (list (read-from-minibuffer "Run: " grep-find-command |
|---|
| | 710 | nil nil 'grep-find-history) |
|---|
| | 711 | nil)) |
|---|
| | 712 | ((not grep-find-template) |
|---|
| | 713 | (list nil nil |
|---|
| | 714 | (read-string "grep.el: No `grep-find-template' available. Press RET."))) |
|---|
| | 715 | (t (let* ((regexp (grep-read-regexp)) |
|---|
| | 716 | (files (grep-read-files regexp)) |
|---|
| | 717 | (dir (read-directory-name "Base directory: " |
|---|
| | 718 | nil default-directory t))) |
|---|
| | 719 | (list regexp files dir)))))) |
|---|
| | 720 | (when (and (stringp regexp) (> (length regexp) 0)) |
|---|
| | 721 | (if (null files) |
|---|
| | 722 | (if (not (string= regexp grep-find-command)) |
|---|
| | 723 | (compilation-start regexp 'grep-mode)) |
|---|
| | 724 | (let* ((default-directory (file-name-as-directory (expand-file-name dir))) |
|---|
| | 725 | (command (grep-expand-template |
|---|
| | 726 | grep-find-template |
|---|
| | 727 | regexp |
|---|
| | 728 | (concat "\\( -name " |
|---|
| | 729 | (mapconcat #'shell-quote-argument |
|---|
| | 730 | (split-string files) |
|---|
| | 731 | " -o -name ") |
|---|
| | 732 | " \\)") |
|---|
| | 733 | default-directory |
|---|
| | 734 | (and grep-find-ignored-directories |
|---|
| | 735 | (concat "\\( -path '*/" |
|---|
| | 736 | (mapconcat #'identity |
|---|
| | 737 | grep-find-ignored-directories |
|---|
| | 738 | "' -o -path '*/") |
|---|
| | 739 | "' \\) -prune -o "))))) |
|---|
| | 740 | (when command |
|---|
| | 741 | (if current-prefix-arg |
|---|
| | 742 | (setq command |
|---|
| | 743 | (read-from-minibuffer "Confirm: " |
|---|
| | 744 | command nil nil 'grep-find-history)) |
|---|
| | 745 | (push command grep-find-history)) |
|---|
| | 746 | (compilation-start command 'grep-mode)))))) |
|---|