├── .clang-format ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile.am ├── Makefile.w32 ├── NOTICE ├── README.md ├── _the_silver_searcher ├── ag.bashcomp.sh ├── autogen.sh ├── build.sh ├── configure.ac ├── doc ├── ag.1 ├── ag.1.md └── generate_man.sh ├── format.sh ├── m4 └── ax_pthread.m4 ├── pgo.sh ├── sanitize.sh ├── src ├── decompress.c ├── decompress.h ├── ignore.c ├── ignore.h ├── lang.c ├── lang.h ├── log.c ├── log.h ├── main.c ├── options.c ├── options.h ├── print.c ├── print.h ├── print_w32.c ├── scandir.c ├── scandir.h ├── search.c ├── search.h ├── uthash.h ├── util.c ├── util.h ├── win32 │ └── config.h └── zfile.c ├── tests ├── adjacent_matches.t ├── bad_path.t ├── big │ ├── big_file.t │ └── create_big_file.py ├── case_sensitivity.t ├── color.t ├── column.t ├── count.t ├── ds_store_ignore.t ├── empty_environment.t ├── empty_match.t ├── exitcodes.t ├── fail │ ├── unicode_case_insensitive.t │ └── unicode_case_insensitive.t.err ├── files_with_matches.t ├── filetype.t ├── hidden_option.t ├── ignore_abs_path.t ├── ignore_absolute_search_path_with_glob.t ├── ignore_backups.t ├── ignore_examine_parent_ignorefiles.t ├── ignore_extensions.t ├── ignore_gitignore.t ├── ignore_invert.t ├── ignore_pattern_in_subdirectory.t ├── ignore_slash_in_subdir.t ├── ignore_subdir.t ├── ignore_vcs.t ├── invert_match.t ├── is_binary.pdf ├── is_binary_pdf.t ├── line_width.t ├── list_file_types.t ├── literal_word_regexp.t ├── max_count.t ├── multiline.t ├── negated_options.t ├── one_device.t ├── only_matching.t ├── option_g.t ├── option_smartcase.t ├── passthrough.t ├── pipecontext.t ├── print_all_files.t ├── print_end.t ├── print_end.txt ├── search_stdin.t ├── setup.sh ├── stupid_fnmatch.t.disabled ├── vimgrep.t └── word_regexp.t └── the_silver_searcher.spec.in /.clang-format: -------------------------------------------------------------------------------- 1 | AlignEscapedNewlinesLeft: false 2 | AlignTrailingComments: true 3 | AllowAllParametersOfDeclarationOnNextLine: true 4 | AllowShortBlocksOnASingleLine: false 5 | AllowShortCaseLabelsOnASingleLine: false 6 | AllowShortFunctionsOnASingleLine: None 7 | AllowShortIfStatementsOnASingleLine: false 8 | AllowShortLoopsOnASingleLine: false 9 | AlwaysBreakAfterDefinitionReturnType: false 10 | AlwaysBreakBeforeMultilineStrings: false 11 | BinPackArguments: true 12 | BinPackParameters: true 13 | BreakBeforeBinaryOperators: None 14 | BreakBeforeBraces: Attach 15 | BreakBeforeTernaryOperators: true 16 | ColumnLimit: 0 17 | ContinuationIndentWidth: 4 18 | Cpp11BracedListStyle: false 19 | DerivePointerAlignment: false 20 | DisableFormat: false 21 | ExperimentalAutoDetectBinPacking: false 22 | IndentCaseLabels: true 23 | IndentFunctionDeclarationAfterType: false 24 | IndentWidth: 4 25 | IndentWrappedFunctionNames: false 26 | KeepEmptyLinesAtTheStartOfBlocks: false 27 | Language: Cpp 28 | MaxEmptyLinesToKeep: 2 29 | PointerAlignment: Right 30 | SpaceAfterCStyleCast: false 31 | SpaceBeforeAssignmentOperators: true 32 | SpaceBeforeParens: ControlStatements 33 | SpaceInEmptyParentheses: false 34 | SpacesBeforeTrailingComments: 1 35 | SpacesInCStyleCastParentheses: false 36 | SpacesInParentheses: false 37 | SpacesInSquareBrackets: false 38 | UseTab: Never 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dSYM 2 | *.gcda 3 | *.o 4 | *.plist 5 | .deps 6 | .dirstamp 7 | .DS_Store 8 | aclocal.m4 9 | ag 10 | ag.exe 11 | autom4te.cache 12 | cachegrind.out.* 13 | callgrind.out.* 14 | clang_output_* 15 | compile 16 | config.guess 17 | config.log 18 | config.status 19 | config.sub 20 | configure 21 | depcomp 22 | gmon.out 23 | install-sh 24 | Makefile 25 | Makefile.in 26 | missing 27 | src/config.h* 28 | stamp-h1 29 | tests/*.err 30 | tests/big/*.err 31 | tests/big/big_file.txt 32 | the_silver_searcher.spec 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | dist: xenial 3 | sudo: false 4 | 5 | branches: 6 | only: 7 | - master 8 | - ppc64le 9 | arch: 10 | - amd64 11 | - ppc64le 12 | 13 | compiler: 14 | - clang 15 | - gcc 16 | 17 | addons: 18 | apt: 19 | sources: 20 | - ubuntu-toolchain-r-test 21 | packages: 22 | - automake 23 | - liblzma-dev 24 | - libpcre3-dev 25 | - pkg-config 26 | - zlib1g-dev 27 | 28 | env: 29 | global: 30 | - LLVM_VERSION=6.0.1 31 | - LLVM_PATH=$HOME/clang+llvm 32 | - CLANG_FORMAT=$LLVM_PATH/bin/clang-format 33 | 34 | before_install: 35 | - wget http://llvm.org/releases/$LLVM_VERSION/clang+llvm-$LLVM_VERSION-x86_64-linux-gnu-ubuntu-16.04.tar.xz -O $LLVM_PATH.tar.xz 36 | - mkdir $LLVM_PATH 37 | - tar xf $LLVM_PATH.tar.xz -C $LLVM_PATH --strip-components=1 38 | - export PATH=$HOME/.local/bin:$PATH 39 | 40 | install: 41 | - pip install --user cram 42 | 43 | script: 44 | - ./build.sh && make test 45 | 46 | notifications: 47 | irc: 'chat.freenode.net#ag' 48 | on_success: change 49 | on_failure: always 50 | use_notice: true 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | I like when people send pull requests. It validates my existence. If you want to help out, check the [issue list](https://github.com/ggreer/the_silver_searcher/issues?sort=updated&state=open) or search the codebase for `TODO`. Don't worry if you lack experience writing C. If I think a pull request isn't ready to be merged, I'll give feedback in comments. Once everything looks good, I'll comment on your pull request with a cool animated gif and hit the merge button. 4 | 5 | ### Running the test suite 6 | 7 | If you contribute, you might want to run the test suite before and after writing 8 | some code, just to make sure you did not break anything. Adding tests along with 9 | your code is nice to have, because it makes regressions less likely to happen. 10 | Also, if you think you have found a bug, contributing a failing test case is a 11 | good way of making your point and adding value at the same time. 12 | 13 | The test suite uses [Cram](https://bitheap.org/cram/). You'll need to build ag 14 | first, and then you can run the suite from the root of the repository : 15 | 16 | make test 17 | 18 | ### Adding filetypes 19 | 20 | Ag can search files which belong to a certain class for example `ag --html test` 21 | searches all files with the extension defined in [lang.c](src/lang.c). 22 | 23 | If you want to add a new file 'class' to ag please modify [lang.c](src/lang.c) and [list_file_types.t](tests/list_file_types.t). 24 | 25 | `lang.c` adds the functionality and `list_file_types.t` adds the test case. 26 | Without adding a test case the test __will__ fail. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} 2 | 3 | bin_PROGRAMS = ag 4 | ag_SOURCES = src/ignore.c src/ignore.h src/log.c src/log.h src/options.c src/options.h src/print.c src/print_w32.c src/print.h src/scandir.c src/scandir.h src/search.c src/search.h src/lang.c src/lang.h src/util.c src/util.h src/decompress.c src/decompress.h src/uthash.h src/main.c src/zfile.c 5 | ag_LDADD = ${PCRE_LIBS} ${LZMA_LIBS} ${ZLIB_LIBS} $(PTHREAD_LIBS) 6 | 7 | dist_man_MANS = doc/ag.1 8 | 9 | bashcompdir = $(pkgdatadir)/completions 10 | dist_bashcomp_DATA = ag.bashcomp.sh 11 | zshcompdir = $(datadir)/zsh/site-functions 12 | dist_zshcomp_DATA = _the_silver_searcher 13 | 14 | EXTRA_DIST = Makefile.w32 LICENSE NOTICE the_silver_searcher.spec README.md 15 | 16 | all: 17 | @$(MAKE) ag -r 18 | 19 | test: ag 20 | cram -v tests/*.t 21 | if HAS_CLANG_FORMAT 22 | CLANG_FORMAT=${CLANG_FORMAT} ./format.sh test 23 | else 24 | @echo "clang-format is not available. Skipped clang-format test." 25 | endif 26 | 27 | test_big: ag 28 | cram -v tests/big/*.t 29 | 30 | test_fail: ag 31 | cram -v tests/fail/*.t 32 | 33 | .PHONY : all clean test test_big test_fail 34 | -------------------------------------------------------------------------------- /Makefile.w32: -------------------------------------------------------------------------------- 1 | SED=sed 2 | VERSION:=$(shell "$(SED)" -n "s/[^[]*\[\([0-9]\+\.[0-9]\+\.[0-9]\+\)\],/\1/p" configure.ac) 3 | 4 | CC=gcc 5 | RM=/bin/rm 6 | 7 | SRCS = \ 8 | src/decompress.c \ 9 | src/ignore.c \ 10 | src/lang.c \ 11 | src/log.c \ 12 | src/main.c \ 13 | src/options.c \ 14 | src/print.c \ 15 | src/scandir.c \ 16 | src/search.c \ 17 | src/util.c \ 18 | src/print_w32.c 19 | OBJS = $(subst .c,.o,$(SRCS)) 20 | 21 | CFLAGS = -O2 -Isrc/win32 -DPACKAGE_VERSION=\"$(VERSION)\" 22 | LIBS = -lz -lpthread -lpcre -llzma -lshlwapi 23 | TARGET = ag.exe 24 | 25 | all : $(TARGET) 26 | 27 | # depend on configure.ac to account for version changes 28 | $(TARGET) : $(OBJS) configure.ac 29 | $(CC) -o $@ $(OBJS) $(LIBS) 30 | 31 | .c.o : 32 | $(CC) -c $(CFLAGS) -Isrc $< -o $@ 33 | 34 | clean : 35 | $(RM) -f src/*.o $(TARGET) 36 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | The Silver Searcher 2 | Copyright 2011-2016 Geoff Greer 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Silver Searcher 2 | 3 | A code searching tool similar to `ack`, with a focus on speed. 4 | 5 | [![Build Status](https://travis-ci.org/ggreer/the_silver_searcher.svg?branch=master)](https://travis-ci.org/ggreer/the_silver_searcher) 6 | 7 | [![Floobits Status](https://floobits.com/ggreer/ag.svg)](https://floobits.com/ggreer/ag/redirect) 8 | 9 | [![#ag on Freenode](https://img.shields.io/badge/Freenode-%23ag-brightgreen.svg)](https://webchat.freenode.net/?channels=ag) 10 | 11 | Do you know C? Want to improve ag? [I invite you to pair with me](http://geoff.greer.fm/2014/10/13/help-me-get-to-ag-10/). 12 | 13 | 14 | ## What's so great about Ag? 15 | 16 | * It is an order of magnitude faster than `ack`. 17 | * It ignores file patterns from your `.gitignore` and `.hgignore`. 18 | * If there are files in your source repo you don't want to search, just add their patterns to a `.ignore` file. (\*cough\* `*.min.js` \*cough\*) 19 | * The command name is 33% shorter than `ack`, and all keys are on the home row! 20 | 21 | Ag is quite stable now. Most changes are new features, minor bug fixes, or performance improvements. It's much faster than Ack in my benchmarks: 22 | 23 | ack test_blah ~/code/ 104.66s user 4.82s system 99% cpu 1:50.03 total 24 | 25 | ag test_blah ~/code/ 4.67s user 4.58s system 286% cpu 3.227 total 26 | 27 | Ack and Ag found the same results, but Ag was 34x faster (3.2 seconds vs 110 seconds). My `~/code` directory is about 8GB. Thanks to git/hg/ignore, Ag only searched 700MB of that. 28 | 29 | There are also [graphs of performance across releases](http://geoff.greer.fm/ag/speed/). 30 | 31 | ## How is it so fast? 32 | 33 | * Ag uses [Pthreads](https://en.wikipedia.org/wiki/POSIX_Threads) to take advantage of multiple CPU cores and search files in parallel. 34 | * Files are `mmap()`ed instead of read into a buffer. 35 | * Literal string searching uses [Boyer-Moore strstr](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm). 36 | * Regex searching uses [PCRE's JIT compiler](http://sljit.sourceforge.net/pcre.html) (if Ag is built with PCRE >=8.21). 37 | * Ag calls `pcre_study()` before executing the same regex on every file. 38 | * Instead of calling `fnmatch()` on every pattern in your ignore files, non-regex patterns are loaded into arrays and binary searched. 39 | 40 | I've written several blog posts showing how I've improved performance. These include how I [added pthreads](http://geoff.greer.fm/2012/09/07/the-silver-searcher-adding-pthreads/), [wrote my own `scandir()`](http://geoff.greer.fm/2012/09/03/profiling-ag-writing-my-own-scandir/), [benchmarked every revision to find performance regressions](http://geoff.greer.fm/2012/08/25/the-silver-searcher-benchmarking-revisions/), and profiled with [gprof](http://geoff.greer.fm/2012/02/08/profiling-with-gprof/) and [Valgrind](http://geoff.greer.fm/2012/01/23/making-programs-faster-profiling/). 41 | 42 | 43 | ## Installing 44 | 45 | ### macOS 46 | 47 | brew install the_silver_searcher 48 | 49 | or 50 | 51 | port install the_silver_searcher 52 | 53 | 54 | ### Linux 55 | 56 | * Ubuntu >= 13.10 (Saucy) or Debian >= 8 (Jessie) 57 | 58 | apt-get install silversearcher-ag 59 | * Fedora 21 and lower 60 | 61 | yum install the_silver_searcher 62 | * Fedora 22+ 63 | 64 | dnf install the_silver_searcher 65 | * RHEL7+ 66 | 67 | yum install epel-release.noarch the_silver_searcher 68 | * Gentoo 69 | 70 | emerge -a sys-apps/the_silver_searcher 71 | * Arch 72 | 73 | pacman -S the_silver_searcher 74 | 75 | * Slackware 76 | 77 | sbopkg -i the_silver_searcher 78 | 79 | * openSUSE 80 | 81 | zypper install the_silver_searcher 82 | 83 | * CentOS 84 | 85 | yum install the_silver_searcher 86 | 87 | * NixOS/Nix/Nixpkgs 88 | 89 | nix-env -iA silver-searcher 90 | 91 | * SUSE Linux Enterprise: Follow [these simple instructions](https://software.opensuse.org/download.html?project=utilities&package=the_silver_searcher). 92 | 93 | 94 | ### BSD 95 | 96 | * FreeBSD 97 | 98 | pkg install the_silver_searcher 99 | * OpenBSD/NetBSD 100 | 101 | pkg_add the_silver_searcher 102 | 103 | ### Windows 104 | 105 | * Win32/64 106 | 107 | Unofficial daily builds are [available](https://github.com/k-takata/the_silver_searcher-win32). 108 | 109 | * winget 110 | 111 | winget install "The Silver Searcher" 112 | 113 | Notes: 114 | - This installs a [release](https://github.com/JFLarvoire/the_silver_searcher/releases) of ag.exe optimized for Windows. 115 | - winget is intended to become the default package manager client for Windows. 116 | As of June 2020, it's still in beta, and can be installed using instructions [there](https://github.com/microsoft/winget-cli). 117 | - The setup script in the Ag's winget package installs ag.exe in the first directory that matches one of these criteria: 118 | 1. Over a previous instance of ag.exe *from the same [origin](https://github.com/JFLarvoire/the_silver_searcher)* found in the PATH 119 | 2. In the directory defined in environment variable bindir_%PROCESSOR_ARCHITECTURE% 120 | 3. In the directory defined in environment variable bindir 121 | 4. In the directory defined in environment variable windir 122 | 123 | * Chocolatey 124 | 125 | choco install ag 126 | * MSYS2 127 | 128 | pacman -S mingw-w64-{i686,x86_64}-ag 129 | * Cygwin 130 | 131 | Run the relevant [`setup-*.exe`](https://cygwin.com/install.html), and select "the\_silver\_searcher" in the "Utils" category. 132 | 133 | ## Building from source 134 | 135 | ### Building master 136 | 137 | 1. Install dependencies (Automake, pkg-config, PCRE, LZMA): 138 | * macOS: 139 | 140 | brew install automake pkg-config pcre xz 141 | or 142 | 143 | port install automake pkgconfig pcre xz 144 | * Ubuntu/Debian: 145 | 146 | apt-get install -y automake pkg-config libpcre3-dev zlib1g-dev liblzma-dev 147 | * Fedora: 148 | 149 | yum -y install pkgconfig automake gcc zlib-devel pcre-devel xz-devel 150 | * CentOS: 151 | 152 | yum -y groupinstall "Development Tools" 153 | yum -y install pcre-devel xz-devel zlib-devel 154 | * openSUSE: 155 | 156 | zypper source-install --build-deps-only the_silver_searcher 157 | 158 | * Windows: It's complicated. See [this wiki page](https://github.com/ggreer/the_silver_searcher/wiki/Windows). 159 | 2. Run the build script (which just runs aclocal, automake, etc): 160 | 161 | ./build.sh 162 | 163 | On Windows (inside an msys/MinGW shell): 164 | 165 | make -f Makefile.w32 166 | 3. Make install: 167 | 168 | sudo make install 169 | 170 | 171 | ### Building a release tarball 172 | 173 | GPG-signed releases are available [here](http://geoff.greer.fm/ag). 174 | 175 | Building release tarballs requires the same dependencies, except for automake and pkg-config. Once you've installed the dependencies, just run: 176 | 177 | ./configure 178 | make 179 | make install 180 | 181 | You may need to use `sudo` or run as root for the make install. 182 | 183 | 184 | ## Editor Integration 185 | 186 | ### Vim 187 | 188 | You can use Ag with [ack.vim](https://github.com/mileszs/ack.vim) by adding the following line to your `.vimrc`: 189 | 190 | let g:ackprg = 'ag --nogroup --nocolor --column' 191 | 192 | or: 193 | 194 | let g:ackprg = 'ag --vimgrep' 195 | 196 | Which has the same effect but will report every match on the line. 197 | 198 | ### Emacs 199 | 200 | You can use [ag.el][] as an Emacs front-end to Ag. See also: [helm-ag]. 201 | 202 | [ag.el]: https://github.com/Wilfred/ag.el 203 | [helm-ag]: https://github.com/syohex/emacs-helm-ag 204 | 205 | ### TextMate 206 | 207 | TextMate users can use Ag with [my fork](https://github.com/ggreer/AckMate) of the popular AckMate plugin, which lets you use both Ack and Ag for searching. If you already have AckMate you just want to replace Ack with Ag, move or delete `"~/Library/Application Support/TextMate/PlugIns/AckMate.tmplugin/Contents/Resources/ackmate_ack"` and run `ln -s /usr/local/bin/ag "~/Library/Application Support/TextMate/PlugIns/AckMate.tmplugin/Contents/Resources/ackmate_ack"` 208 | 209 | ## Other stuff you might like 210 | 211 | * [Ack](https://github.com/petdance/ack3) - Better than grep. Without Ack, Ag would not exist. 212 | * [ack.vim](https://github.com/mileszs/ack.vim) 213 | * [Exuberant Ctags](http://ctags.sourceforge.net/) - Faster than Ag, but it builds an index beforehand. Good for *really* big codebases. 214 | * [Git-grep](http://git-scm.com/docs/git-grep) - As fast as Ag but only works on git repos. 215 | * [fzf](https://github.com/junegunn/fzf) - A command-line fuzzy finder 216 | * [ripgrep](https://github.com/BurntSushi/ripgrep) 217 | * [Sack](https://github.com/sampson-chen/sack) - A utility that wraps Ack and Ag. It removes a lot of repetition from searching and opening matching files. 218 | -------------------------------------------------------------------------------- /_the_silver_searcher: -------------------------------------------------------------------------------- 1 | #compdef ag 2 | 3 | # Completion function for zsh 4 | 5 | local ret=1 6 | local -a args expl 7 | 8 | # Intentionally avoided many possible mutual exlusions because it is 9 | # likely that earlier options come from an alias. In line with this 10 | # the following conditionally adds options that assert defaults. 11 | [[ -n $words[(r)(-[is]|--ignore-case|--case-sensitive)] ]] && args+=( 12 | '(-S --smart-case -s -s --ignore-case --case-sensitive)'{-S,--smart-case}'[insensitive match unless pattern includes uppercase]' 13 | ) 14 | [[ -n $words[(r)--nobreak] ]] && args+=( 15 | "(--nobreak)--break[print newlines between matches in different files]" 16 | ) 17 | [[ -n $words[(r)--nogroup] ]] && args+=( 18 | "(--nogroup)--group[don't repeat filename for each match line]" 19 | ) 20 | 21 | _tags normal-options file-types 22 | while _tags; do 23 | _requested normal-options && _arguments -S -s $args \ 24 | '--ackmate[print results in AckMate-parseable format]' \ 25 | '(--after -A)'{--after=-,-A+}'[specify lines of trailing context]::lines [2]' \ 26 | '(--before -B)'{--before=-,-B+}'[specify lines of leading context]::lines [2]' \ 27 | "--nobreak[don't print newlines between matches in different files]" \ 28 | '(--count -c)'{--count,-c}'[only print a count of matching lines]' \ 29 | '--color[enable color highlighting of output]' \ 30 | '(--color-line-number --color-match --color-path)--nocolor[disable color highlighting of output]' \ 31 | '--color-line-number=[specify color for line numbers]:color [1;33]' \ 32 | '--color-match=[specify color for result match numbers]:color [30;43]' \ 33 | '--color-path=[specify color for path names]:color [1;32]' \ 34 | '--column[print column numbers in results]' \ 35 | '(--context -C)'{--context=-,-C+}'[specify lines of context]::lines' \ 36 | '(--debug -D)'{--debug,-D}'[output debug information]' \ 37 | '--depth=[specify directory levels to descend when searching]:levels [25]' \ 38 | '(--noheading)--nofilename[suppress printing of filenames]' \ 39 | '(-f --follow)'{-f,--follow}'[follow symlinks]' \ 40 | '(-F --fixed-strings --literal -Q)'{--fixed-strings,-F,--literal,-Q}'[use literal strings]' \ 41 | '--nogroup[repeat filename for each match line]' \ 42 | '(1 -G --file-search-regex)-g+[print filenames matching a pattern]:regex' \ 43 | '(-G --file-search-regex)'{-G+,--file-search-regex=}'[limit search to filenames matching pattern]:regex' \ 44 | '(-H --heading --noheading)'{-H,--heading}'[print filename with each match]' \ 45 | '(-H --heading --noheading --nofilename)--noheading[suppress printing of filenames]' \ 46 | '--hidden[search hidden files (obeying .*ignore files)]' \ 47 | {--ignore=,--ignore-dir=}'[ignore files/directories matching pattern]:regex' \ 48 | '(-i --ignore-case)'{-i,--ignore-case}'[match case-insensitively]' \ 49 | '(-l --files-with-matches)'{-l,--files-with-matches}"[output matching files' names only]" \ 50 | '(-L --files-without-matches)'{-L,--files-without-matches}"[output non-matching files' names only]" \ 51 | '(--max-count -m)'{--max-count=,-m+}'[stop after specified no of matches in each file]:max number of matches' \ 52 | '--numbers[prefix output with line numbers, even for streams]' \ 53 | '--nonumbers[suppress printing of line numbers]' \ 54 | '(--only-matching -o)'{--only-matching,-o}'[show only matching part of line]' \ 55 | '(-p --path-to-ignore)'{-p+,--path-to-ignore=}'[use specified .ignore file]:file:_files' \ 56 | '--print-long-lines[print matches on very long lines]' \ 57 | "--passthrough[when searching a stream, print all lines even if they don't match]" \ 58 | '(-s --case-sensitive)'{-s,--case-sensitive}'[match case]' \ 59 | '--silent[suppress all log messages, including errors]' \ 60 | '(--stats-only)--stats[print stats (files scanned, time taken, etc.)]' \ 61 | '(--stats)--stats-only[print stats and nothing else]' \ 62 | '(-U --skip-vcs-ignores)'{-U,--skip-vcs-ignores}'[ignore VCS files (stil obey .ignore)]' \ 63 | '(-v --invert-match)'{-v,--invert-match}'[select non-matching lines]' \ 64 | '--vimgrep[output results like vim :vimgrep /pattern/g would]' \ 65 | '(-w --word-regexp)'{-w,--word-regexp}'[force pattern to match only whole words]' \ 66 | '(-z --search-zip)'{-z,--search-zip}'[search contents of compressed files]' \ 67 | '(-0 --null)'{-0,--null}'[separate filenames with null]' \ 68 | ': :_guard "^-*" pattern' \ 69 | '*:file:_files' \ 70 | '(- :)--list-file-types[list supported file types]' \ 71 | '(- :)'{-h,--help}'[display help information]' \ 72 | '(- :)'{-V,--version}'[display version information]' \ 73 | - '(ignores)' \ 74 | '(-a --all-types)'{-a,--all-types}'[search all files]' \ 75 | '--search-binary[search binary files for matches]' \ 76 | {-t,--all-text}'[search all text files (not including hidden files)]' \ 77 | {-u,--unrestricted}'[search all files]' && ret=0 78 | 79 | _requested file-types && { ! zstyle -T ":completion:${curcontext}:options" prefix-needed || 80 | [[ -prefix - ]] } && _all_labels file-types expl 'file type' \ 81 | compadd - ${(M)$(_call_program file-types $words[1] --list-file-types):#--*} && ret=0 82 | 83 | (( ret )) || break 84 | done 85 | 86 | return ret 87 | -------------------------------------------------------------------------------- /ag.bashcomp.sh: -------------------------------------------------------------------------------- 1 | _ag() { 2 | local lngopt shtopt split=false 3 | local cur prev 4 | 5 | COMPREPLY=() 6 | cur=$(_get_cword "=") 7 | prev="${COMP_WORDS[COMP_CWORD-1]}" 8 | 9 | _expand || return 0 10 | 11 | lngopt=' 12 | --ackmate 13 | --ackmate-dir-filter 14 | --affinity 15 | --after 16 | --all-text 17 | --all-types 18 | --before 19 | --break 20 | --case-sensitive 21 | --color 22 | --color-line-number 23 | --color-match 24 | --color-path 25 | --color-win-ansi 26 | --column 27 | --context 28 | --count 29 | --debug 30 | --depth 31 | --file-search-regex 32 | --filename 33 | --files-with-matches 34 | --files-without-matches 35 | --fixed-strings 36 | --follow 37 | --group 38 | --heading 39 | --help 40 | --hidden 41 | --ignore 42 | --ignore-case 43 | --ignore-dir 44 | --invert-match 45 | --line-numbers 46 | --list-file-types 47 | --literal 48 | --match 49 | --max-count 50 | --no-numbers 51 | --no-recurse 52 | --noaffinity 53 | --nobreak 54 | --nocolor 55 | --nofilename 56 | --nofollow 57 | --nogroup 58 | --noheading 59 | --nonumbers 60 | --nopager 61 | --norecurse 62 | --null 63 | --numbers 64 | --one-device 65 | --only-matching 66 | --pager 67 | --parallel 68 | --passthrough 69 | --passthru 70 | --path-to-ignore 71 | --print-long-lines 72 | --print0 73 | --recurse 74 | --search-binary 75 | --search-files 76 | --search-zip 77 | --silent 78 | --skip-vcs-ignores 79 | --smart-case 80 | --stats 81 | --unrestricted 82 | --version 83 | --vimgrep 84 | --word-regexp 85 | --workers 86 | ' 87 | shtopt=' 88 | -a -A -B -C -D 89 | -f -F -g -G -h 90 | -i -l -L -m -n 91 | -p -Q -r -R -s 92 | -S -t -u -U -v 93 | -V -w -z 94 | ' 95 | 96 | types=$(ag --list-file-types |grep -- '--') 97 | 98 | # these options require an argument 99 | if [[ "${prev}" == -[ABCGgm] ]] ; then 100 | return 0 101 | fi 102 | 103 | _split_longopt && split=true 104 | 105 | case "${prev}" in 106 | --ignore-dir) # directory completion 107 | _filedir -d 108 | return 0;; 109 | --path-to-ignore) # file completion 110 | _filedir 111 | return 0;; 112 | --pager) # command completion 113 | COMPREPLY=( $(compgen -c -- "${cur}") ) 114 | return 0;; 115 | --ackmate-dir-filter|--after|--before|--color-*|--context|--depth\ 116 | |--file-search-regex|--ignore|--max-count|--workers) 117 | return 0;; 118 | esac 119 | 120 | $split && return 0 121 | 122 | case "${cur}" in 123 | -*) 124 | COMPREPLY=( $(compgen -W \ 125 | "${lngopt} ${shtopt} ${types}" -- "${cur}") ) 126 | return 0;; 127 | *) 128 | _filedir 129 | return 0;; 130 | esac 131 | } && 132 | 133 | # shellcheck disable=SC2086 134 | # shellcheck disable=SC2154,SC2086 135 | complete -F _ag ${nospace} ag 136 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd "$(dirname "$0")" 5 | 6 | AC_SEARCH_OPTS="" 7 | # For those of us with pkg-config and other tools in /usr/local 8 | PATH=$PATH:/usr/local/bin 9 | 10 | # This is to make life easier for people who installed pkg-config in /usr/local 11 | # but have autoconf/make/etc in /usr/. AKA most mac users 12 | if [ -d "/usr/local/share/aclocal" ] 13 | then 14 | AC_SEARCH_OPTS="-I /usr/local/share/aclocal" 15 | fi 16 | 17 | # shellcheck disable=2086 18 | aclocal $AC_SEARCH_OPTS 19 | autoconf 20 | autoheader 21 | automake --add-missing 22 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd "$(dirname "$0")" 5 | 6 | ./autogen.sh 7 | ./configure "$@" 8 | make -j4 9 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT( 2 | [the_silver_searcher], 3 | [2.2.0], 4 | [https://github.com/ggreer/the_silver_searcher/issues], 5 | [the_silver_searcher], 6 | [https://github.com/ggreer/the_silver_searcher]) 7 | 8 | AM_INIT_AUTOMAKE([no-define foreign subdir-objects]) 9 | 10 | AC_PROG_CC 11 | AM_PROG_CC_C_O 12 | AC_PREREQ([2.59]) 13 | AC_PROG_GREP 14 | 15 | m4_ifdef( 16 | [AM_SILENT_RULES], 17 | [AM_SILENT_RULES([yes])]) 18 | 19 | PKG_CHECK_MODULES([PCRE], [libpcre]) 20 | 21 | m4_include([m4/ax_pthread.m4]) 22 | AX_PTHREAD( 23 | [AC_CHECK_HEADERS([pthread.h])], 24 | [AC_MSG_WARN([No pthread support. Ag will be slower due to running single-threaded.])] 25 | ) 26 | 27 | # Run CFLAGS="-pg" ./configure if you want debug symbols 28 | if ! echo "$CFLAGS" | "$GREP" '\(^\|[[[:space:]]]\)-O' > /dev/null; then 29 | CFLAGS="$CFLAGS -O2" 30 | fi 31 | 32 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS $PCRE_CFLAGS -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -Wshadow" 33 | CFLAGS="$CFLAGS -Wpointer-arith -Wcast-qual -Wmissing-prototypes -Wno-missing-braces -std=gnu89 -D_GNU_SOURCE" 34 | LDFLAGS="$LDFLAGS" 35 | 36 | case $host in 37 | *mingw*) 38 | AC_CHECK_LIB(shlwapi, main,, AC_MSG_ERROR(libshlwapi missing)) 39 | esac 40 | 41 | LIBS="$PTHREAD_LIBS $LIBS" 42 | 43 | AC_ARG_ENABLE([zlib], 44 | AS_HELP_STRING([--disable-zlib], [Disable zlib compressed search support])) 45 | 46 | AS_IF([test "x$enable_zlib" != "xno"], [ 47 | AC_CHECK_HEADERS([zlib.h]) 48 | AC_SEARCH_LIBS([inflate], [zlib, z]) 49 | ]) 50 | 51 | AC_ARG_ENABLE([lzma], 52 | AS_HELP_STRING([--disable-lzma], [Disable lzma compressed search support])) 53 | 54 | AS_IF([test "x$enable_lzma" != "xno"], [ 55 | AC_CHECK_HEADERS([lzma.h]) 56 | PKG_CHECK_MODULES([LZMA], [liblzma]) 57 | ]) 58 | 59 | AC_CHECK_DECL([PCRE_CONFIG_JIT], [AC_DEFINE([USE_PCRE_JIT], [], [Use PCRE JIT])], [], [#include ]) 60 | 61 | AC_CHECK_DECL([CPU_ZERO, CPU_SET], [AC_DEFINE([USE_CPU_SET], [], [Use CPU_SET macros])] , [], [#include ]) 62 | AC_CHECK_HEADERS([sys/cpuset.h err.h]) 63 | 64 | AC_CHECK_MEMBER([struct dirent.d_type], [AC_DEFINE([HAVE_DIRENT_DTYPE], [], [Have dirent struct member d_type])], [], [[#include ]]) 65 | AC_CHECK_MEMBER([struct dirent.d_namlen], [AC_DEFINE([HAVE_DIRENT_DNAMLEN], [], [Have dirent struct member d_namlen])], [], [[#include ]]) 66 | 67 | AC_CHECK_FUNCS(fgetln fopencookie getline realpath strlcpy strndup vasprintf madvise posix_fadvise pthread_setaffinity_np pledge) 68 | 69 | AC_CONFIG_FILES([Makefile the_silver_searcher.spec]) 70 | AC_CONFIG_HEADERS([src/config.h]) 71 | 72 | AC_CHECK_PROGS( 73 | [CLANG_FORMAT], 74 | [clang-format-3.8 clang-format-3.7 clang-format-3.6 clang-format], 75 | [no] 76 | ) 77 | AM_CONDITIONAL([HAS_CLANG_FORMAT], [test x$CLANG_FORMAT != xno]) 78 | AM_COND_IF( 79 | [HAS_CLANG_FORMAT], 80 | [AC_MSG_NOTICE([clang-format found. 'make test' will detect improperly-formatted files.])], 81 | [AC_MSG_WARN([clang-format not found. 'make test' will not detect improperly-formatted files.])] 82 | ) 83 | 84 | AC_OUTPUT 85 | -------------------------------------------------------------------------------- /doc/ag.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "AG" "1" "December 2016" "" "" 5 | . 6 | .SH "NAME" 7 | \fBag\fR \- The Silver Searcher\. Like ack, but faster\. 8 | . 9 | .SH "SYNOPSIS" 10 | \fBag\fR [\fIoptions\fR] \fIpattern\fR [\fIpath \.\.\.\fR] 11 | . 12 | .SH "DESCRIPTION" 13 | Recursively search for PATTERN in PATH\. Like grep or ack, but faster\. 14 | . 15 | .SH "OPTIONS" 16 | . 17 | .TP 18 | \fB\-\-ackmate\fR 19 | Output results in a format parseable by AckMate \fIhttps://github\.com/protocool/AckMate\fR\. 20 | . 21 | .TP 22 | \fB\-\-[no]affinity\fR 23 | Set thread affinity (if platform supports it)\. Default is true\. 24 | . 25 | .TP 26 | \fB\-a \-\-all\-types\fR 27 | Search all files\. This doesn\'t include hidden files, and doesn\'t respect any ignore files\. 28 | . 29 | .TP 30 | \fB\-A \-\-after [LINES]\fR 31 | Print lines after match\. If not provided, LINES defaults to 2\. 32 | . 33 | .TP 34 | \fB\-B \-\-before [LINES]\fR 35 | Print lines before match\. If not provided, LINES defaults to 2\. 36 | . 37 | .TP 38 | \fB\-\-[no]break\fR 39 | Print a newline between matches in different files\. Enabled by default\. 40 | . 41 | .TP 42 | \fB\-c \-\-count\fR 43 | Only print the number of matches in each file\. Note: This is the number of matches, \fBnot\fR the number of matching lines\. Pipe output to \fBwc \-l\fR if you want the number of matching lines\. 44 | . 45 | .TP 46 | \fB\-\-[no]color\fR 47 | Print color codes in results\. Enabled by default\. 48 | . 49 | .TP 50 | \fB\-\-color\-line\-number\fR 51 | Color codes for line numbers\. Default is 1;33\. 52 | . 53 | .TP 54 | \fB\-\-color\-match\fR 55 | Color codes for result match numbers\. Default is 30;43\. 56 | . 57 | .TP 58 | \fB\-\-color\-path\fR 59 | Color codes for path names\. Default is 1;32\. 60 | . 61 | .TP 62 | \fB\-\-column\fR 63 | Print column numbers in results\. 64 | . 65 | .TP 66 | \fB\-C \-\-context [LINES]\fR 67 | Print lines before and after matches\. Default is 2\. 68 | . 69 | .TP 70 | \fB\-D \-\-debug\fR 71 | Output ridiculous amounts of debugging info\. Not useful unless you\'re actually debugging\. 72 | . 73 | .TP 74 | \fB\-\-depth NUM\fR 75 | Search up to NUM directories deep, \-1 for unlimited\. Default is 25\. 76 | . 77 | .TP 78 | \fB\-\-[no]filename\fR 79 | Print file names\. Enabled by default, except when searching a single file\. 80 | . 81 | .TP 82 | \fB\-f \-\-[no]follow\fR 83 | Follow symlinks\. Default is false\. 84 | . 85 | .TP 86 | \fB\-F \-\-fixed\-strings\fR 87 | Alias for \-\-literal for compatibility with grep\. 88 | . 89 | .TP 90 | \fB\-\-[no]group\fR 91 | The default, \fB\-\-group\fR, lumps multiple matches in the same file together, and presents them under a single occurrence of the filename\. \fB\-\-nogroup\fR refrains from this, and instead places the filename at the start of each match line\. 92 | . 93 | .TP 94 | \fB\-g PATTERN\fR 95 | Print filenames matching PATTERN\. 96 | . 97 | .TP 98 | \fB\-G \-\-file\-search\-regex PATTERN\fR 99 | Only search files whose names match PATTERN\. 100 | . 101 | .TP 102 | \fB\-H \-\-[no]heading\fR 103 | Print filenames above matching contents\. 104 | . 105 | .TP 106 | \fB\-\-hidden\fR 107 | Search hidden files\. This option obeys ignored files\. 108 | . 109 | .TP 110 | \fB\-\-ignore PATTERN\fR 111 | Ignore files/directories whose names match this pattern\. Literal file and directory names are also allowed\. 112 | . 113 | .TP 114 | \fB\-\-ignore\-dir NAME\fR 115 | Alias for \-\-ignore for compatibility with ack\. 116 | . 117 | .TP 118 | \fB\-i \-\-ignore\-case\fR 119 | Match case\-insensitively\. 120 | . 121 | .TP 122 | \fB\-l \-\-files\-with\-matches\fR 123 | Only print the names of files containing matches, not the matching lines\. An empty query will print all files that would be searched\. 124 | . 125 | .TP 126 | \fB\-L \-\-files\-without\-matches\fR 127 | Only print the names of files that don\'t contain matches\. 128 | . 129 | .TP 130 | \fB\-\-list\-file\-types\fR 131 | See \fBFILE TYPES\fR below\. 132 | . 133 | .TP 134 | \fB\-m \-\-max\-count NUM\fR 135 | Skip the rest of a file after NUM matches\. Default is 0, which never skips\. 136 | . 137 | .TP 138 | \fB\-\-[no]mmap\fR 139 | Toggle use of memory\-mapped I/O\. Defaults to true on platforms where \fBmmap()\fR is faster than \fBread()\fR\. (All but macOS\.) 140 | . 141 | .TP 142 | \fB\-\-[no]multiline\fR 143 | Match regexes across newlines\. Enabled by default\. 144 | . 145 | .TP 146 | \fB\-n \-\-norecurse\fR 147 | Don\'t recurse into directories\. 148 | . 149 | .TP 150 | \fB\-\-[no]numbers\fR 151 | Print line numbers\. Default is to omit line numbers when searching streams\. 152 | . 153 | .TP 154 | \fB\-o \-\-only\-matching\fR 155 | Print only the matching part of the lines\. 156 | . 157 | .TP 158 | \fB\-\-one\-device\fR 159 | When recursing directories, don\'t scan dirs that reside on other storage devices\. This lets you avoid scanning slow network mounts\. This feature is not supported on all platforms\. 160 | . 161 | .TP 162 | \fB\-p \-\-path\-to\-ignore STRING\fR 163 | Provide a path to a specific \.ignore file\. 164 | . 165 | .TP 166 | \fB\-\-pager COMMAND\fR 167 | Use a pager such as \fBless\fR\. Use \fB\-\-nopager\fR to override\. This option is also ignored if output is piped to another program\. 168 | . 169 | .TP 170 | \fB\-\-parallel\fR 171 | Parse the input stream as a search term, not data to search\. This is meant to be used with tools such as GNU parallel\. For example: \fBecho "foo\enbar\enbaz" | parallel "ag {} \."\fR will run 3 instances of ag, searching the current directory for "foo", "bar", and "baz"\. 172 | . 173 | .TP 174 | \fB\-\-print\-long\-lines\fR 175 | Print matches on very long lines (> 2k characters by default)\. 176 | . 177 | .TP 178 | \fB\-\-passthrough \-\-passthru\fR 179 | When searching a stream, print all lines even if they don\'t match\. 180 | . 181 | .TP 182 | \fB\-Q \-\-literal\fR 183 | Do not parse PATTERN as a regular expression\. Try to match it literally\. 184 | . 185 | .TP 186 | \fB\-r \-\-recurse\fR 187 | Recurse into directories when searching\. Default is true\. 188 | . 189 | .TP 190 | \fB\-s \-\-case\-sensitive\fR 191 | Match case\-sensitively\. 192 | . 193 | .TP 194 | \fB\-S \-\-smart\-case\fR 195 | Match case\-sensitively if there are any uppercase letters in PATTERN, case\-insensitively otherwise\. Enabled by default\. 196 | . 197 | .TP 198 | \fB\-\-search\-binary\fR 199 | Search binary files for matches\. 200 | . 201 | .TP 202 | \fB\-\-silent\fR 203 | Suppress all log messages, including errors\. 204 | . 205 | .TP 206 | \fB\-\-stats\fR 207 | Print stats (files scanned, time taken, etc)\. 208 | . 209 | .TP 210 | \fB\-\-stats\-only\fR 211 | Print stats (files scanned, time taken, etc) and nothing else\. 212 | . 213 | .TP 214 | \fB\-t \-\-all\-text\fR 215 | Search all text files\. This doesn\'t include hidden files\. 216 | . 217 | .TP 218 | \fB\-u \-\-unrestricted\fR 219 | Search \fIall\fR files\. This ignores \.ignore, \.gitignore, etc\. It searches binary and hidden files as well\. 220 | . 221 | .TP 222 | \fB\-U \-\-skip\-vcs\-ignores\fR 223 | Ignore VCS ignore files (\.gitignore, \.hgignore), but still use \.ignore\. 224 | . 225 | .TP 226 | \fB\-v \-\-invert\-match\fR 227 | Match every line \fInot\fR containing the specified pattern\. 228 | . 229 | .TP 230 | \fB\-V \-\-version\fR 231 | Print version info\. 232 | . 233 | .TP 234 | \fB\-\-vimgrep\fR 235 | Output results in the same form as Vim\'s \fB:vimgrep /pattern/g\fR 236 | . 237 | .IP 238 | Here is a ~/\.vimrc configuration example: 239 | . 240 | .IP 241 | \fBset grepprg=ag\e \-\-vimgrep\e $*\fR \fBset grepformat=%f:%l:%c:%m\fR 242 | . 243 | .IP 244 | Then use \fB:grep\fR to grep for something\. Then use \fB:copen\fR, \fB:cn\fR, \fB:cp\fR, etc\. to navigate through the matches\. 245 | . 246 | .TP 247 | \fB\-w \-\-word\-regexp\fR 248 | Only match whole words\. 249 | . 250 | .TP 251 | \fB\-\-workers NUM\fR 252 | Use NUM worker threads\. Default is the number of CPU cores, with a max of 8\. 253 | . 254 | .TP 255 | \fB\-z \-\-search\-zip\fR 256 | Search contents of compressed files\. Currently, gz and xz are supported\. This option requires that ag is built with lzma and zlib\. 257 | . 258 | .TP 259 | \fB\-0 \-\-null \-\-print0\fR 260 | Separate the filenames with \fB\e0\fR, rather than \fB\en\fR: this allows \fBxargs \-0 \fR to correctly process filenames containing spaces or newlines\. 261 | . 262 | .SH "FILE TYPES" 263 | It is possible to restrict the types of files searched\. For example, passing \fB\-\-html\fR will search only files with the extensions \fBhtm\fR, \fBhtml\fR, \fBshtml\fR or \fBxhtml\fR\. For a list of supported types, run \fBag \-\-list\-file\-types\fR\. 264 | . 265 | .SH "IGNORING FILES" 266 | By default, ag will ignore files whose names match patterns in \.gitignore, \.hgignore, or \.ignore\. These files can be anywhere in the directories being searched\. Binary files are ignored by default as well\. Finally, ag looks in $HOME/\.agignore for ignore patterns\. 267 | . 268 | .P 269 | If you want to ignore \.gitignore and \.hgignore, but still take \.ignore into account, use \fB\-U\fR\. 270 | . 271 | .P 272 | Use the \fB\-t\fR option to search all text files; \fB\-a\fR to search all files; and \fB\-u\fR to search all, including hidden files\. 273 | . 274 | .SH "EXAMPLES" 275 | \fBag printf\fR: Find matches for "printf" in the current directory\. 276 | . 277 | .P 278 | \fBag foo /bar/\fR: Find matches for "foo" in path /bar/\. 279 | . 280 | .P 281 | \fBag \-\- \-\-foo\fR: Find matches for "\-\-foo" in the current directory\. (As with most UNIX command line utilities, "\-\-" is used to signify that the remaining arguments should not be treated as options\.) 282 | . 283 | .SH "ABOUT" 284 | ag was originally created by Geoff Greer\. More information (and the latest release) can be found at http://geoff\.greer\.fm/ag 285 | . 286 | .SH "SEE ALSO" 287 | grep(1) 288 | -------------------------------------------------------------------------------- /doc/ag.1.md: -------------------------------------------------------------------------------- 1 | ag(1) -- The Silver Searcher. Like ack, but faster. 2 | ============================================= 3 | 4 | ## SYNOPSIS 5 | 6 | `ag` [_options_] _pattern_ [_path ..._] 7 | 8 | ## DESCRIPTION 9 | 10 | Recursively search for PATTERN in PATH. Like grep or ack, but faster. 11 | 12 | ## OPTIONS 13 | 14 | * `--ackmate`: 15 | Output results in a format parseable by [AckMate](https://github.com/protocool/AckMate). 16 | 17 | * `--[no]affinity`: 18 | Set thread affinity (if platform supports it). Default is true. 19 | 20 | * `-a --all-types`: 21 | Search all files. This doesn't include hidden files, and doesn't respect any ignore files. 22 | 23 | * `-A --after [LINES]`: 24 | Print lines after match. If not provided, LINES defaults to 2. 25 | 26 | * `-B --before [LINES]`: 27 | Print lines before match. If not provided, LINES defaults to 2. 28 | 29 | * `--[no]break`: 30 | Print a newline between matches in different files. Enabled by default. 31 | 32 | * `-c --count`: 33 | Only print the number of matches in each file. 34 | Note: This is the number of matches, **not** the number of matching lines. 35 | Pipe output to `wc -l` if you want the number of matching lines. 36 | 37 | * `--[no]color`: 38 | Print color codes in results. Enabled by default. 39 | 40 | * `--color-line-number`: 41 | Color codes for line numbers. Default is 1;33. 42 | 43 | * `--color-match`: 44 | Color codes for result match numbers. Default is 30;43. 45 | 46 | * `--color-path`: 47 | Color codes for path names. Default is 1;32. 48 | 49 | * `--column`: 50 | Print column numbers in results. 51 | 52 | * `-C --context [LINES]`: 53 | Print lines before and after matches. Default is 2. 54 | 55 | * `-D --debug`: 56 | Output ridiculous amounts of debugging info. Not useful unless you're actually debugging. 57 | 58 | * `--depth NUM`: 59 | Search up to NUM directories deep, -1 for unlimited. Default is 25. 60 | 61 | * `--[no]filename`: 62 | Print file names. Enabled by default, except when searching a single file. 63 | 64 | * `-f --[no]follow`: 65 | Follow symlinks. Default is false. 66 | 67 | * `-F --fixed-strings`: 68 | Alias for --literal for compatibility with grep. 69 | 70 | * `--[no]group`: 71 | The default, `--group`, lumps multiple matches in the same file 72 | together, and presents them under a single occurrence of the 73 | filename. `--nogroup` refrains from this, and instead places the 74 | filename at the start of each match line. 75 | 76 | * `-g PATTERN`: 77 | Print filenames matching PATTERN. 78 | 79 | * `-G --file-search-regex PATTERN`: 80 | Only search files whose names match PATTERN. 81 | 82 | * `-H --[no]heading`: 83 | Print filenames above matching contents. 84 | 85 | * `--hidden`: 86 | Search hidden files. This option obeys ignored files. 87 | 88 | * `--ignore PATTERN`: 89 | Ignore files/directories whose names match this pattern. Literal 90 | file and directory names are also allowed. 91 | 92 | * `--ignore-dir NAME`: 93 | Alias for --ignore for compatibility with ack. 94 | 95 | * `-i --ignore-case`: 96 | Match case-insensitively. 97 | 98 | * `-l --files-with-matches`: 99 | Only print the names of files containing matches, not the matching 100 | lines. An empty query will print all files that would be searched. 101 | 102 | * `-L --files-without-matches`: 103 | Only print the names of files that don't contain matches. 104 | 105 | * `--list-file-types`: 106 | See `FILE TYPES` below. 107 | 108 | * `-m --max-count NUM`: 109 | Skip the rest of a file after NUM matches. Default is 0, which never skips. 110 | 111 | * `--[no]mmap`: 112 | Toggle use of memory-mapped I/O. Defaults to true on platforms where 113 | `mmap()` is faster than `read()`. (All but macOS.) 114 | 115 | * `--[no]multiline`: 116 | Match regexes across newlines. Enabled by default. 117 | 118 | * `-n --norecurse`: 119 | Don't recurse into directories. 120 | 121 | * `--[no]numbers`: 122 | Print line numbers. Default is to omit line numbers when searching streams. 123 | 124 | * `-o --only-matching`: 125 | Print only the matching part of the lines. 126 | 127 | * `--one-device`: 128 | When recursing directories, don't scan dirs that reside on other storage 129 | devices. This lets you avoid scanning slow network mounts. 130 | This feature is not supported on all platforms. 131 | 132 | * `-p --path-to-ignore STRING`: 133 | Provide a path to a specific .ignore file. 134 | 135 | * `--pager COMMAND`: 136 | Use a pager such as `less`. Use `--nopager` to override. This option 137 | is also ignored if output is piped to another program. 138 | 139 | * `--parallel`: 140 | Parse the input stream as a search term, not data to search. This is meant 141 | to be used with tools such as GNU parallel. For example: 142 | `echo "foo\nbar\nbaz" | parallel "ag {} ."` will run 3 instances of ag, 143 | searching the current directory for "foo", "bar", and "baz". 144 | 145 | * `--print-long-lines`: 146 | Print matches on very long lines (> 2k characters by default). 147 | 148 | * `--passthrough --passthru`: 149 | When searching a stream, print all lines even if they don't match. 150 | 151 | * `-Q --literal`: 152 | Do not parse PATTERN as a regular expression. Try to match it literally. 153 | 154 | * `-r --recurse`: 155 | Recurse into directories when searching. Default is true. 156 | 157 | * `-s --case-sensitive`: 158 | Match case-sensitively. 159 | 160 | * `-S --smart-case`: 161 | Match case-sensitively if there are any uppercase letters in PATTERN, 162 | case-insensitively otherwise. Enabled by default. 163 | 164 | * `--search-binary`: 165 | Search binary files for matches. 166 | 167 | * `--silent`: 168 | Suppress all log messages, including errors. 169 | 170 | * `--stats`: 171 | Print stats (files scanned, time taken, etc). 172 | 173 | * `--stats-only`: 174 | Print stats (files scanned, time taken, etc) and nothing else. 175 | 176 | * `-t --all-text`: 177 | Search all text files. This doesn't include hidden files. 178 | 179 | * `-u --unrestricted`: 180 | Search *all* files. This ignores .ignore, .gitignore, etc. It searches 181 | binary and hidden files as well. 182 | 183 | * `-U --skip-vcs-ignores`: 184 | Ignore VCS ignore files (.gitignore, .hgignore), but still 185 | use .ignore. 186 | 187 | * `-v --invert-match`: 188 | Match every line *not* containing the specified pattern. 189 | 190 | * `-V --version`: 191 | Print version info. 192 | 193 | * `--vimgrep`: 194 | Output results in the same form as Vim's `:vimgrep /pattern/g` 195 | 196 | Here is a ~/.vimrc configuration example: 197 | 198 | `set grepprg=ag\ --vimgrep\ $*` 199 | `set grepformat=%f:%l:%c:%m` 200 | 201 | Then use `:grep` to grep for something. 202 | Then use `:copen`, `:cn`, `:cp`, etc. to navigate through the matches. 203 | 204 | * `-w --word-regexp`: 205 | Only match whole words. 206 | 207 | * `--workers NUM`: 208 | Use NUM worker threads. Default is the number of CPU cores, with a max of 8. 209 | 210 | * `-W --width NUM`: 211 | Truncate match lines after NUM characters. 212 | 213 | * `-z --search-zip`: 214 | Search contents of compressed files. Currently, gz and xz are supported. 215 | This option requires that ag is built with lzma and zlib. 216 | 217 | * `-0 --null --print0`: 218 | Separate the filenames with `\0`, rather than `\n`: 219 | this allows `xargs -0 ` to correctly process filenames containing 220 | spaces or newlines. 221 | 222 | 223 | ## FILE TYPES 224 | 225 | It is possible to restrict the types of files searched. For example, passing 226 | `--html` will search only files with the extensions `htm`, `html`, `shtml` 227 | or `xhtml`. For a list of supported types, run `ag --list-file-types`. 228 | 229 | ## IGNORING FILES 230 | 231 | By default, ag will ignore files whose names match patterns in .gitignore, 232 | .hgignore, or .ignore. These files can be anywhere in the directories being 233 | searched. Binary files are ignored by default as well. Finally, ag looks in 234 | $HOME/.agignore for ignore patterns. 235 | 236 | If you want to ignore .gitignore and .hgignore, but still take .ignore into 237 | account, use `-U`. 238 | 239 | Use the `-t` option to search all text files; `-a` to search all files; and `-u` 240 | to search all, including hidden files. 241 | 242 | ## EXAMPLES 243 | 244 | `ag printf`: 245 | Find matches for "printf" in the current directory. 246 | 247 | `ag foo /bar/`: 248 | Find matches for "foo" in path /bar/. 249 | 250 | `ag -- --foo`: 251 | Find matches for "--foo" in the current directory. (As with most UNIX command 252 | line utilities, "--" is used to signify that the remaining arguments should 253 | not be treated as options.) 254 | 255 | ## ABOUT 256 | 257 | ag was originally created by Geoff Greer. More information (and the latest 258 | release) can be found at http://geoff.greer.fm/ag 259 | 260 | ## SEE ALSO 261 | 262 | grep(1) 263 | -------------------------------------------------------------------------------- /doc/generate_man.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ronn is used to turn the markdown into a manpage. 4 | # Get ronn at https://github.com/rtomayko/ronn 5 | # Alternately, since ronn is a Ruby gem, you can just 6 | # `gem install ronn` 7 | 8 | sed -e 's/\\0/\\\\0/' ag.1.md.tmp 9 | ronn -r ag.1.md.tmp 10 | 11 | rm -f ag.1.md.tmp 12 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function usage() { 4 | echo "Usage: $0 test|reformat" 5 | } 6 | 7 | if [ $# -eq 0 ] 8 | then 9 | usage 10 | exit 0 11 | fi 12 | 13 | if [ -z "$CLANG_FORMAT" ] 14 | then 15 | CLANG_FORMAT=clang-format 16 | echo "No CLANG_FORMAT set. Using $CLANG_FORMAT" 17 | fi 18 | 19 | if ! type "$CLANG_FORMAT" &> /dev/null 20 | then 21 | echo "The command \"$CLANG_FORMAT\" was not found" 22 | exit 1 23 | fi 24 | 25 | SOURCE_FILES=$(git ls-files src/) 26 | 27 | if [ "$1" == "reformat" ] 28 | then 29 | echo "Reformatting source files" 30 | # shellcheck disable=2086 31 | echo $CLANG_FORMAT -style=file -i $SOURCE_FILES 32 | # shellcheck disable=2086 33 | $CLANG_FORMAT -style=file -i $SOURCE_FILES 34 | exit 0 35 | elif [ "$1" == "test" ] 36 | then 37 | # shellcheck disable=2086 38 | RESULT=$($CLANG_FORMAT -style=file -output-replacements-xml $SOURCE_FILES | grep -c ' 57 | # Copyright (c) 2011 Daniel Richard G. 58 | # 59 | # This program is free software: you can redistribute it and/or modify it 60 | # under the terms of the GNU General Public License as published by the 61 | # Free Software Foundation, either version 3 of the License, or (at your 62 | # option) any later version. 63 | # 64 | # This program is distributed in the hope that it will be useful, but 65 | # WITHOUT ANY WARRANTY; without even the implied warranty of 66 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 67 | # Public License for more details. 68 | # 69 | # You should have received a copy of the GNU General Public License along 70 | # with this program. If not, see . 71 | # 72 | # As a special exception, the respective Autoconf Macro's copyright owner 73 | # gives unlimited permission to copy, distribute and modify the configure 74 | # scripts that are the output of Autoconf when processing the Macro. You 75 | # need not follow the terms of the GNU General Public License when using 76 | # or distributing such scripts, even though portions of the text of the 77 | # Macro appear in them. The GNU General Public License (GPL) does govern 78 | # all other use of the material that constitutes the Autoconf Macro. 79 | # 80 | # This special exception to the GPL applies to versions of the Autoconf 81 | # Macro released by the Autoconf Archive. When you make and distribute a 82 | # modified version of the Autoconf Macro, you may extend this special 83 | # exception to the GPL to apply to your modified version as well. 84 | 85 | #serial 21 86 | 87 | AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) 88 | AC_DEFUN([AX_PTHREAD], [ 89 | AC_REQUIRE([AC_CANONICAL_HOST]) 90 | AC_LANG_PUSH([C]) 91 | ax_pthread_ok=no 92 | 93 | # We used to check for pthread.h first, but this fails if pthread.h 94 | # requires special compiler flags (e.g. on True64 or Sequent). 95 | # It gets checked for in the link test anyway. 96 | 97 | # First of all, check if the user has set any of the PTHREAD_LIBS, 98 | # etcetera environment variables, and if threads linking works using 99 | # them: 100 | if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then 101 | save_CFLAGS="$CFLAGS" 102 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 103 | save_LIBS="$LIBS" 104 | LIBS="$PTHREAD_LIBS $LIBS" 105 | AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) 106 | AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes]) 107 | AC_MSG_RESULT([$ax_pthread_ok]) 108 | if test x"$ax_pthread_ok" = xno; then 109 | PTHREAD_LIBS="" 110 | PTHREAD_CFLAGS="" 111 | fi 112 | LIBS="$save_LIBS" 113 | CFLAGS="$save_CFLAGS" 114 | fi 115 | 116 | # We must check for the threads library under a number of different 117 | # names; the ordering is very important because some systems 118 | # (e.g. DEC) have both -lpthread and -lpthreads, where one of the 119 | # libraries is broken (non-POSIX). 120 | 121 | # Create a list of thread flags to try. Items starting with a "-" are 122 | # C compiler flags, and other items are library names, except for "none" 123 | # which indicates that we try without any flags at all, and "pthread-config" 124 | # which is a program returning the flags for the Pth emulation library. 125 | 126 | ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" 127 | 128 | # The ordering *is* (sometimes) important. Some notes on the 129 | # individual items follow: 130 | 131 | # pthreads: AIX (must check this before -lpthread) 132 | # none: in case threads are in libc; should be tried before -Kthread and 133 | # other compiler flags to prevent continual compiler warnings 134 | # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) 135 | # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) 136 | # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) 137 | # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) 138 | # -pthreads: Solaris/gcc 139 | # -mthreads: Mingw32/gcc, Lynx/gcc 140 | # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it 141 | # doesn't hurt to check since this sometimes defines pthreads too; 142 | # also defines -D_REENTRANT) 143 | # ... -mt is also the pthreads flag for HP/aCC 144 | # pthread: Linux, etcetera 145 | # --thread-safe: KAI C++ 146 | # pthread-config: use pthread-config program (for GNU Pth library) 147 | 148 | case ${host_os} in 149 | solaris*) 150 | 151 | # On Solaris (at least, for some versions), libc contains stubbed 152 | # (non-functional) versions of the pthreads routines, so link-based 153 | # tests will erroneously succeed. (We need to link with -pthreads/-mt/ 154 | # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather 155 | # a function called by this macro, so we could check for that, but 156 | # who knows whether they'll stub that too in a future libc.) So, 157 | # we'll just look for -pthreads and -lpthread first: 158 | 159 | ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" 160 | ;; 161 | 162 | darwin*) 163 | ax_pthread_flags="-pthread $ax_pthread_flags" 164 | ;; 165 | esac 166 | 167 | # Clang doesn't consider unrecognized options an error unless we specify 168 | # -Werror. We throw in some extra Clang-specific options to ensure that 169 | # this doesn't happen for GCC, which also accepts -Werror. 170 | 171 | AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags]) 172 | save_CFLAGS="$CFLAGS" 173 | ax_pthread_extra_flags="-Werror" 174 | CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" 175 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])], 176 | [AC_MSG_RESULT([yes])], 177 | [ax_pthread_extra_flags= 178 | AC_MSG_RESULT([no])]) 179 | CFLAGS="$save_CFLAGS" 180 | 181 | if test x"$ax_pthread_ok" = xno; then 182 | for flag in $ax_pthread_flags; do 183 | 184 | case $flag in 185 | none) 186 | AC_MSG_CHECKING([whether pthreads work without any flags]) 187 | ;; 188 | 189 | -*) 190 | AC_MSG_CHECKING([whether pthreads work with $flag]) 191 | PTHREAD_CFLAGS="$flag" 192 | ;; 193 | 194 | pthread-config) 195 | AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) 196 | if test x"$ax_pthread_config" = xno; then continue; fi 197 | PTHREAD_CFLAGS="`pthread-config --cflags`" 198 | PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" 199 | ;; 200 | 201 | *) 202 | AC_MSG_CHECKING([for the pthreads library -l$flag]) 203 | PTHREAD_LIBS="-l$flag" 204 | ;; 205 | esac 206 | 207 | save_LIBS="$LIBS" 208 | save_CFLAGS="$CFLAGS" 209 | LIBS="$PTHREAD_LIBS $LIBS" 210 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" 211 | 212 | # Check for various functions. We must include pthread.h, 213 | # since some functions may be macros. (On the Sequent, we 214 | # need a special flag -Kthread to make this header compile.) 215 | # We check for pthread_join because it is in -lpthread on IRIX 216 | # while pthread_create is in libc. We check for pthread_attr_init 217 | # due to DEC craziness with -lpthreads. We check for 218 | # pthread_cleanup_push because it is one of the few pthread 219 | # functions on Solaris that doesn't have a non-functional libc stub. 220 | # We try pthread_create on general principles. 221 | AC_LINK_IFELSE([AC_LANG_PROGRAM([#include 222 | static void routine(void *a) { a = 0; } 223 | static void *start_routine(void *a) { return a; }], 224 | [pthread_t th; pthread_attr_t attr; 225 | pthread_create(&th, 0, start_routine, 0); 226 | pthread_join(th, 0); 227 | pthread_attr_init(&attr); 228 | pthread_cleanup_push(routine, 0); 229 | pthread_cleanup_pop(0) /* ; */])], 230 | [ax_pthread_ok=yes], 231 | []) 232 | 233 | LIBS="$save_LIBS" 234 | CFLAGS="$save_CFLAGS" 235 | 236 | AC_MSG_RESULT([$ax_pthread_ok]) 237 | if test "x$ax_pthread_ok" = xyes; then 238 | break; 239 | fi 240 | 241 | PTHREAD_LIBS="" 242 | PTHREAD_CFLAGS="" 243 | done 244 | fi 245 | 246 | # Various other checks: 247 | if test "x$ax_pthread_ok" = xyes; then 248 | save_LIBS="$LIBS" 249 | LIBS="$PTHREAD_LIBS $LIBS" 250 | save_CFLAGS="$CFLAGS" 251 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 252 | 253 | # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. 254 | AC_MSG_CHECKING([for joinable pthread attribute]) 255 | attr_name=unknown 256 | for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do 257 | AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], 258 | [int attr = $attr; return attr /* ; */])], 259 | [attr_name=$attr; break], 260 | []) 261 | done 262 | AC_MSG_RESULT([$attr_name]) 263 | if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then 264 | AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name], 265 | [Define to necessary symbol if this constant 266 | uses a non-standard name on your system.]) 267 | fi 268 | 269 | AC_MSG_CHECKING([if more special flags are required for pthreads]) 270 | flag=no 271 | case ${host_os} in 272 | aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; 273 | osf* | hpux*) flag="-D_REENTRANT";; 274 | solaris*) 275 | if test "$GCC" = "yes"; then 276 | flag="-D_REENTRANT" 277 | else 278 | # TODO: What about Clang on Solaris? 279 | flag="-mt -D_REENTRANT" 280 | fi 281 | ;; 282 | esac 283 | AC_MSG_RESULT([$flag]) 284 | if test "x$flag" != xno; then 285 | PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" 286 | fi 287 | 288 | AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], 289 | [ax_cv_PTHREAD_PRIO_INHERIT], [ 290 | AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], 291 | [[int i = PTHREAD_PRIO_INHERIT;]])], 292 | [ax_cv_PTHREAD_PRIO_INHERIT=yes], 293 | [ax_cv_PTHREAD_PRIO_INHERIT=no]) 294 | ]) 295 | AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], 296 | [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])]) 297 | 298 | LIBS="$save_LIBS" 299 | CFLAGS="$save_CFLAGS" 300 | 301 | # More AIX lossage: compile with *_r variant 302 | if test "x$GCC" != xyes; then 303 | case $host_os in 304 | aix*) 305 | AS_CASE(["x/$CC"], 306 | [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], 307 | [#handle absolute path differently from PATH based program lookup 308 | AS_CASE(["x$CC"], 309 | [x/*], 310 | [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], 311 | [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) 312 | ;; 313 | esac 314 | fi 315 | fi 316 | 317 | test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" 318 | 319 | AC_SUBST([PTHREAD_LIBS]) 320 | AC_SUBST([PTHREAD_CFLAGS]) 321 | AC_SUBST([PTHREAD_CC]) 322 | 323 | # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: 324 | if test x"$ax_pthread_ok" = xyes; then 325 | ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) 326 | : 327 | else 328 | ax_pthread_ok=no 329 | $2 330 | fi 331 | AC_LANG_POP 332 | ])dnl AX_PTHREAD 333 | -------------------------------------------------------------------------------- /pgo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd "$(dirname "$0")" 5 | 6 | make clean 7 | ./build.sh CFLAGS="$CFLAGS -fprofile-generate" 8 | ./ag example .. 9 | make clean 10 | ./build.sh CFLAGS="$CFLAGS -fprofile-correction -fprofile-use" 11 | -------------------------------------------------------------------------------- /sanitize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2016 Allen Wild 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | AVAILABLE_SANITIZERS=( 17 | address 18 | thread 19 | undefined 20 | valgrind 21 | ) 22 | 23 | DEFAULT_SANITIZERS=( 24 | address 25 | thread 26 | undefined 27 | ) 28 | 29 | usage() { 30 | cat < and then runs the test suite. 34 | Memory leaks or other errors will be printed in ag's output, thus failing the test. 35 | 36 | Available LLVM sanitizers are: ${AVAILABLE_SANITIZERS[*]} 37 | 38 | The compile-time sanitizers are supported in clang/llvm >= 3.1 and gcc >= 4.8 39 | for x86_64 Linux only. clang is preferred and will be used, if available. 40 | 41 | For function names and line numbers in error output traces, llvm-symbolizer needs 42 | to be available in PATH or set through ASAN_SYMBOLIZER_PATH. 43 | 44 | If 'valgrind' is passed as the sanitizer, then ag will be run through valgrind 45 | without recompiling. If $(dirname $0)/ag doesn't exist, then it will be built. 46 | 47 | WARNING: This script will run "make distclean" and "./configure" to recompile ag 48 | once per sanitizer (except for valgrind). If you need to pass additional 49 | options to ./configure, put them in the CONFIGOPTS environment variable. 50 | EOF 51 | } 52 | 53 | vrun() { 54 | echo "Running: $*" 55 | "$@" 56 | } 57 | 58 | die() { 59 | echo "Fatal: $*" 60 | exit 1 61 | } 62 | 63 | valid_sanitizer() { 64 | for san in "${AVAILABLE_SANITIZERS[@]}"; do 65 | if [[ "$1" == "$san" ]]; then 66 | return 0 67 | fi 68 | done 69 | return 1 70 | } 71 | 72 | run_sanitizer() { 73 | sanitizer=$1 74 | if [[ "$sanitizer" == "valgrind" ]]; then 75 | run_valgrind 76 | return $? 77 | fi 78 | 79 | echo -e "\nCompiling for sanitizer '$sanitizer'" 80 | [[ -f Makefile ]] && vrun make distclean 81 | vrun ./configure $CONFIGOPTS CC=$SANITIZE_CC \ 82 | CFLAGS="-g -O0 -fsanitize=$sanitizer $EXTRA_CFLAGS" 83 | if [[ $? != 0 ]]; then 84 | echo "ERROR: Failed to configure. Try setting CONFIGOPTS?" 85 | return 1 86 | fi 87 | 88 | vrun make 89 | if [[ $? != 0 ]]; then 90 | echo "ERROR: failed to build" 91 | return 1 92 | fi 93 | 94 | echo "Testing with sanitizer '$sanitizer'" 95 | vrun make test 96 | if [[ $? != 0 ]]; then 97 | echo "Tests for sanitizer '$sanitizer' FAIL!" 98 | echo "Check the above output for failure information" 99 | return 2 100 | else 101 | echo "Tests for sanitizer '$sanitizer' PASS!" 102 | return 0 103 | fi 104 | } 105 | 106 | run_valgrind() { 107 | echo "Compiling ag normally for use with valgrind" 108 | [[ -f Makefile ]] && vrun make distclean 109 | vrun ./configure $CONFIGOPTS 110 | if [[ $? != 0 ]]; then 111 | echo "ERROR: Failed to configure. Try setting CONFIGOPTS?" 112 | return 1 113 | fi 114 | 115 | vrun make 116 | if [[ $? != 0 ]]; then 117 | echo "ERROR: failed to build" 118 | return 1 119 | fi 120 | 121 | echo "Running: AGPROG=\"valgrind -q $PWD/ag\" make test" 122 | AGPROG="valgrind -q $PWD/ag" make test 123 | if [[ $? != 0 ]]; then 124 | echo "Valgrind tests FAIL!" 125 | return 1 126 | else 127 | echo "Valgrind tests PASS!" 128 | return 0 129 | fi 130 | } 131 | 132 | #### MAIN #### 133 | run_sanitizers=() 134 | for opt in "$@"; do 135 | if [[ "$opt" == -* ]]; then 136 | case opt in 137 | -h|--help) 138 | usage 139 | exit 0 140 | ;; 141 | *) 142 | echo "Unknown option: '$opt'" 143 | usage 144 | exit 1 145 | ;; 146 | esac 147 | else 148 | if valid_sanitizer "$opt"; then 149 | run_sanitizers+=("$opt") 150 | else 151 | echo "Invalid Sanitizer: '$opt'" 152 | usage 153 | exit 1 154 | fi 155 | fi 156 | done 157 | 158 | if [[ ${#run_sanitizers[@]} == 0 ]]; then 159 | run_sanitizers=(${DEFAULT_SANITIZERS[@]}) 160 | fi 161 | 162 | if [[ -n $CC ]]; then 163 | echo "Using CC=$CC" 164 | SANITIZE_CC="$CC" 165 | elif which clang &>/dev/null; then 166 | SANITIZE_CC="clang" 167 | else 168 | echo "Warning: CC unset and clang not found" 169 | fi 170 | 171 | if [[ -n $CFLAGS ]]; then 172 | EXTRA_CFLAGS="$CFLAGS" 173 | unset CFLAGS 174 | fi 175 | 176 | if [[ ! -e ./configure ]]; then 177 | echo "Warning: ./configure not found. Running autogen" 178 | vrun ./autogen.sh || die "autogen.sh failed" 179 | fi 180 | 181 | echo "Running sanitizers: ${run_sanitizers[*]}" 182 | failedsan=() 183 | for san in "${run_sanitizers[@]}"; do 184 | run_sanitizer $san 185 | if [[ $? != 0 ]]; then 186 | failedsan+=($san) 187 | fi 188 | done 189 | 190 | if [[ ${#failedsan[@]} == 0 ]]; then 191 | echo "All sanitizers PASSED" 192 | exit 0 193 | else 194 | echo "The following sanitizers FAILED: ${failedsan[*]}" 195 | exit ${#failedsan[@]} 196 | fi 197 | -------------------------------------------------------------------------------- /src/decompress.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "decompress.h" 5 | 6 | #ifdef HAVE_LZMA_H 7 | #include 8 | 9 | /* http://tukaani.org/xz/xz-file-format.txt */ 10 | const uint8_t XZ_HEADER_MAGIC[6] = { 0xFD, '7', 'z', 'X', 'Z', 0x00 }; 11 | const uint8_t LZMA_HEADER_SOMETIMES[3] = { 0x5D, 0x00, 0x00 }; 12 | #endif 13 | 14 | 15 | #ifdef HAVE_ZLIB_H 16 | #define ZLIB_CONST 1 17 | #include 18 | 19 | /* Code in decompress_zlib from 20 | * 21 | * https://raw.github.com/madler/zlib/master/examples/zpipe.c 22 | * 23 | * zpipe.c: example of proper use of zlib's inflate() and deflate() 24 | * Not copyrighted -- provided to the public domain 25 | * Version 1.4 11 December 2005 Mark Adler 26 | */ 27 | static void *decompress_zlib(const void *buf, const int buf_len, 28 | const char *dir_full_path, int *new_buf_len) { 29 | int ret = 0; 30 | unsigned char *result = NULL; 31 | size_t result_size = 0; 32 | size_t pagesize = 0; 33 | z_stream stream; 34 | 35 | log_debug("Decompressing zlib file %s", dir_full_path); 36 | 37 | /* allocate inflate state */ 38 | stream.zalloc = Z_NULL; 39 | stream.zfree = Z_NULL; 40 | stream.opaque = Z_NULL; 41 | stream.avail_in = 0; 42 | stream.next_in = Z_NULL; 43 | 44 | /* Add 32 to allow zlib and gzip format detection */ 45 | if (inflateInit2(&stream, 32 + 15) != Z_OK) { 46 | log_err("Unable to initialize zlib: %s", stream.msg); 47 | goto error_out; 48 | } 49 | 50 | stream.avail_in = buf_len; 51 | /* Explicitly cast away the const-ness of buf */ 52 | stream.next_in = (Bytef *)buf; 53 | 54 | pagesize = getpagesize(); 55 | result_size = ((buf_len + pagesize - 1) & ~(pagesize - 1)); 56 | do { 57 | do { 58 | unsigned char *tmp_result = result; 59 | /* Double the buffer size and realloc */ 60 | result_size *= 2; 61 | result = (unsigned char *)realloc(result, result_size * sizeof(unsigned char)); 62 | if (result == NULL) { 63 | free(tmp_result); 64 | log_err("Unable to allocate %d bytes to decompress file %s", result_size * sizeof(unsigned char), dir_full_path); 65 | inflateEnd(&stream); 66 | goto error_out; 67 | } 68 | 69 | stream.avail_out = result_size / 2; 70 | stream.next_out = &result[stream.total_out]; 71 | ret = inflate(&stream, Z_SYNC_FLUSH); 72 | log_debug("inflate ret = %d", ret); 73 | switch (ret) { 74 | case Z_STREAM_ERROR: { 75 | log_err("Found stream error while decompressing zlib stream: %s", stream.msg); 76 | inflateEnd(&stream); 77 | goto error_out; 78 | } 79 | case Z_NEED_DICT: 80 | case Z_DATA_ERROR: 81 | case Z_MEM_ERROR: { 82 | log_err("Found mem/data error while decompressing zlib stream: %s", stream.msg); 83 | inflateEnd(&stream); 84 | goto error_out; 85 | } 86 | } 87 | } while (stream.avail_out == 0); 88 | } while (ret == Z_OK); 89 | 90 | *new_buf_len = stream.total_out; 91 | inflateEnd(&stream); 92 | 93 | if (ret == Z_STREAM_END) { 94 | return result; 95 | } 96 | 97 | error_out: 98 | *new_buf_len = 0; 99 | return NULL; 100 | } 101 | #endif 102 | 103 | 104 | static void *decompress_lzw(const void *buf, const int buf_len, 105 | const char *dir_full_path, int *new_buf_len) { 106 | (void)buf; 107 | (void)buf_len; 108 | log_err("LZW (UNIX compress) files not yet supported: %s", dir_full_path); 109 | *new_buf_len = 0; 110 | return NULL; 111 | } 112 | 113 | 114 | static void *decompress_zip(const void *buf, const int buf_len, 115 | const char *dir_full_path, int *new_buf_len) { 116 | (void)buf; 117 | (void)buf_len; 118 | log_err("Zip files not yet supported: %s", dir_full_path); 119 | *new_buf_len = 0; 120 | return NULL; 121 | } 122 | 123 | 124 | #ifdef HAVE_LZMA_H 125 | static void *decompress_lzma(const void *buf, const int buf_len, 126 | const char *dir_full_path, int *new_buf_len) { 127 | lzma_stream stream = LZMA_STREAM_INIT; 128 | lzma_ret lzrt; 129 | unsigned char *result = NULL; 130 | size_t result_size = 0; 131 | size_t pagesize = 0; 132 | 133 | stream.avail_in = buf_len; 134 | stream.next_in = buf; 135 | 136 | lzrt = lzma_auto_decoder(&stream, -1, 0); 137 | 138 | if (lzrt != LZMA_OK) { 139 | log_err("Unable to initialize lzma_auto_decoder: %d", lzrt); 140 | goto error_out; 141 | } 142 | 143 | pagesize = getpagesize(); 144 | result_size = ((buf_len + pagesize - 1) & ~(pagesize - 1)); 145 | do { 146 | do { 147 | unsigned char *tmp_result = result; 148 | /* Double the buffer size and realloc */ 149 | result_size *= 2; 150 | result = (unsigned char *)realloc(result, result_size * sizeof(unsigned char)); 151 | if (result == NULL) { 152 | free(tmp_result); 153 | log_err("Unable to allocate %d bytes to decompress file %s", result_size * sizeof(unsigned char), dir_full_path); 154 | goto error_out; 155 | } 156 | 157 | stream.avail_out = result_size / 2; 158 | stream.next_out = &result[stream.total_out]; 159 | lzrt = lzma_code(&stream, LZMA_RUN); 160 | log_debug("lzma_code ret = %d", lzrt); 161 | switch (lzrt) { 162 | case LZMA_OK: 163 | case LZMA_STREAM_END: 164 | break; 165 | default: 166 | log_err("Found mem/data error while decompressing xz/lzma stream: %d", lzrt); 167 | goto error_out; 168 | } 169 | } while (stream.avail_out == 0); 170 | } while (lzrt == LZMA_OK); 171 | 172 | *new_buf_len = stream.total_out; 173 | 174 | if (lzrt == LZMA_STREAM_END) { 175 | lzma_end(&stream); 176 | return result; 177 | } 178 | 179 | 180 | error_out: 181 | lzma_end(&stream); 182 | *new_buf_len = 0; 183 | if (result) { 184 | free(result); 185 | } 186 | return NULL; 187 | } 188 | #endif 189 | 190 | 191 | /* This function is very hot. It's called on every file when zip is enabled. */ 192 | void *decompress(const ag_compression_type zip_type, const void *buf, const int buf_len, 193 | const char *dir_full_path, int *new_buf_len) { 194 | switch (zip_type) { 195 | #ifdef HAVE_ZLIB_H 196 | case AG_GZIP: 197 | return decompress_zlib(buf, buf_len, dir_full_path, new_buf_len); 198 | #endif 199 | case AG_COMPRESS: 200 | return decompress_lzw(buf, buf_len, dir_full_path, new_buf_len); 201 | case AG_ZIP: 202 | return decompress_zip(buf, buf_len, dir_full_path, new_buf_len); 203 | #ifdef HAVE_LZMA_H 204 | case AG_XZ: 205 | return decompress_lzma(buf, buf_len, dir_full_path, new_buf_len); 206 | #endif 207 | case AG_NO_COMPRESSION: 208 | log_err("File %s is not compressed", dir_full_path); 209 | break; 210 | default: 211 | log_err("Unsupported compression type: %d", zip_type); 212 | } 213 | 214 | *new_buf_len = 0; 215 | return NULL; 216 | } 217 | 218 | 219 | /* This function is very hot. It's called on every file. */ 220 | ag_compression_type is_zipped(const void *buf, const int buf_len) { 221 | /* Zip magic numbers 222 | * compressed file: { 0x1F, 0x9B } 223 | * http://en.wikipedia.org/wiki/Compress 224 | * 225 | * gzip file: { 0x1F, 0x8B } 226 | * http://www.gzip.org/zlib/rfc-gzip.html#file-format 227 | * 228 | * zip file: { 0x50, 0x4B, 0x03, 0x04 } 229 | * http://www.pkware.com/documents/casestudies/APPNOTE.TXT (Section 4.3) 230 | */ 231 | 232 | const unsigned char *buf_c = buf; 233 | 234 | if (buf_len == 0) 235 | return AG_NO_COMPRESSION; 236 | 237 | /* Check for gzip & compress */ 238 | if (buf_len >= 2) { 239 | if (buf_c[0] == 0x1F) { 240 | if (buf_c[1] == 0x8B) { 241 | #ifdef HAVE_ZLIB_H 242 | log_debug("Found gzip-based stream"); 243 | return AG_GZIP; 244 | #endif 245 | } else if (buf_c[1] == 0x9B) { 246 | log_debug("Found compress-based stream"); 247 | return AG_COMPRESS; 248 | } 249 | } 250 | } 251 | 252 | /* Check for zip */ 253 | if (buf_len >= 4) { 254 | if (buf_c[0] == 0x50 && buf_c[1] == 0x4B && buf_c[2] == 0x03 && buf_c[3] == 0x04) { 255 | log_debug("Found zip-based stream"); 256 | return AG_ZIP; 257 | } 258 | } 259 | 260 | #ifdef HAVE_LZMA_H 261 | if (buf_len >= 6) { 262 | if (memcmp(XZ_HEADER_MAGIC, buf_c, 6) == 0) { 263 | log_debug("Found xz based stream"); 264 | return AG_XZ; 265 | } 266 | } 267 | 268 | /* LZMA doesn't really have a header: http://www.mail-archive.com/xz-devel@tukaani.org/msg00003.html */ 269 | if (buf_len >= 3) { 270 | if (memcmp(LZMA_HEADER_SOMETIMES, buf_c, 3) == 0) { 271 | log_debug("Found lzma-based stream"); 272 | return AG_XZ; 273 | } 274 | } 275 | #endif 276 | 277 | return AG_NO_COMPRESSION; 278 | } 279 | -------------------------------------------------------------------------------- /src/decompress.h: -------------------------------------------------------------------------------- 1 | #ifndef DECOMPRESS_H 2 | #define DECOMPRESS_H 3 | 4 | #include 5 | 6 | #include "config.h" 7 | #include "log.h" 8 | #include "options.h" 9 | 10 | typedef enum { 11 | AG_NO_COMPRESSION, 12 | AG_GZIP, 13 | AG_COMPRESS, 14 | AG_ZIP, 15 | AG_XZ, 16 | } ag_compression_type; 17 | 18 | ag_compression_type is_zipped(const void *buf, const int buf_len); 19 | 20 | void *decompress(const ag_compression_type zip_type, const void *buf, const int buf_len, const char *dir_full_path, int *new_buf_len); 21 | 22 | #if HAVE_FOPENCOOKIE 23 | FILE *decompress_open(int fd, const char *mode, ag_compression_type ctype); 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/ignore.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ignore.h" 10 | #include "log.h" 11 | #include "options.h" 12 | #include "scandir.h" 13 | #include "util.h" 14 | 15 | #ifdef _WIN32 16 | #include 17 | #define fnmatch(x, y, z) (!PathMatchSpec(y, x)) 18 | #else 19 | #include 20 | const int fnmatch_flags = FNM_PATHNAME; 21 | #endif 22 | 23 | ignores *root_ignores; 24 | 25 | /* TODO: build a huge-ass list of files we want to ignore by default (build cache stuff, pyc files, etc) */ 26 | 27 | const char *evil_hardcoded_ignore_files[] = { 28 | ".", 29 | "..", 30 | NULL 31 | }; 32 | 33 | /* Warning: changing the first two strings will break skip_vcs_ignores. */ 34 | const char *ignore_pattern_files[] = { 35 | ".ignore", 36 | ".gitignore", 37 | ".git/info/exclude", 38 | ".hgignore", 39 | NULL 40 | }; 41 | 42 | int is_empty(ignores *ig) { 43 | return (ig->extensions_len + ig->names_len + ig->slash_names_len + ig->regexes_len + ig->slash_regexes_len == 0); 44 | }; 45 | 46 | ignores *init_ignore(ignores *parent, const char *dirname, const size_t dirname_len) { 47 | ignores *ig = ag_malloc(sizeof(ignores)); 48 | ig->extensions = NULL; 49 | ig->extensions_len = 0; 50 | ig->names = NULL; 51 | ig->names_len = 0; 52 | ig->slash_names = NULL; 53 | ig->slash_names_len = 0; 54 | ig->regexes = NULL; 55 | ig->regexes_len = 0; 56 | ig->invert_regexes = NULL; 57 | ig->invert_regexes_len = 0; 58 | ig->slash_regexes = NULL; 59 | ig->slash_regexes_len = 0; 60 | ig->dirname = dirname; 61 | ig->dirname_len = dirname_len; 62 | 63 | if (parent && is_empty(parent) && parent->parent) { 64 | ig->parent = parent->parent; 65 | } else { 66 | ig->parent = parent; 67 | } 68 | 69 | if (parent && parent->abs_path_len > 0) { 70 | ag_asprintf(&(ig->abs_path), "%s/%s", parent->abs_path, dirname); 71 | ig->abs_path_len = parent->abs_path_len + 1 + dirname_len; 72 | } else if (dirname_len == 1 && dirname[0] == '.') { 73 | ig->abs_path = ag_malloc(sizeof(char)); 74 | ig->abs_path[0] = '\0'; 75 | ig->abs_path_len = 0; 76 | } else { 77 | ag_asprintf(&(ig->abs_path), "%s", dirname); 78 | ig->abs_path_len = dirname_len; 79 | } 80 | return ig; 81 | } 82 | 83 | void cleanup_ignore(ignores *ig) { 84 | if (ig == NULL) { 85 | return; 86 | } 87 | free_strings(ig->extensions, ig->extensions_len); 88 | free_strings(ig->names, ig->names_len); 89 | free_strings(ig->slash_names, ig->slash_names_len); 90 | free_strings(ig->regexes, ig->regexes_len); 91 | free_strings(ig->invert_regexes, ig->invert_regexes_len); 92 | free_strings(ig->slash_regexes, ig->slash_regexes_len); 93 | if (ig->abs_path) { 94 | free(ig->abs_path); 95 | } 96 | free(ig); 97 | } 98 | 99 | void add_ignore_pattern(ignores *ig, const char *pattern) { 100 | int i; 101 | int pattern_len; 102 | 103 | /* Strip off the leading dot so that matches are more likely. */ 104 | if (strncmp(pattern, "./", 2) == 0) { 105 | pattern++; 106 | } 107 | 108 | /* Kill trailing whitespace */ 109 | for (pattern_len = strlen(pattern); pattern_len > 0; pattern_len--) { 110 | if (!isspace(pattern[pattern_len - 1])) { 111 | break; 112 | } 113 | } 114 | 115 | if (pattern_len == 0) { 116 | log_debug("Pattern is empty. Not adding any ignores."); 117 | return; 118 | } 119 | 120 | char ***patterns_p; 121 | size_t *patterns_len; 122 | if (is_fnmatch(pattern)) { 123 | if (pattern[0] == '*' && pattern[1] == '.' && strchr(pattern + 2, '.') && !is_fnmatch(pattern + 2)) { 124 | patterns_p = &(ig->extensions); 125 | patterns_len = &(ig->extensions_len); 126 | pattern += 2; 127 | pattern_len -= 2; 128 | } else if (pattern[0] == '/') { 129 | patterns_p = &(ig->slash_regexes); 130 | patterns_len = &(ig->slash_regexes_len); 131 | pattern++; 132 | pattern_len--; 133 | } else if (pattern[0] == '!') { 134 | patterns_p = &(ig->invert_regexes); 135 | patterns_len = &(ig->invert_regexes_len); 136 | pattern++; 137 | pattern_len--; 138 | } else { 139 | patterns_p = &(ig->regexes); 140 | patterns_len = &(ig->regexes_len); 141 | } 142 | } else { 143 | if (pattern[0] == '/') { 144 | patterns_p = &(ig->slash_names); 145 | patterns_len = &(ig->slash_names_len); 146 | pattern++; 147 | pattern_len--; 148 | } else { 149 | patterns_p = &(ig->names); 150 | patterns_len = &(ig->names_len); 151 | } 152 | } 153 | 154 | ++*patterns_len; 155 | 156 | char **patterns; 157 | 158 | /* a balanced binary tree is best for performance, but I'm lazy */ 159 | *patterns_p = patterns = ag_realloc(*patterns_p, (*patterns_len) * sizeof(char *)); 160 | /* TODO: de-dupe these patterns */ 161 | for (i = *patterns_len - 1; i > 0; i--) { 162 | if (strcmp(pattern, patterns[i - 1]) > 0) { 163 | break; 164 | } 165 | patterns[i] = patterns[i - 1]; 166 | } 167 | patterns[i] = ag_strndup(pattern, pattern_len); 168 | log_debug("added ignore pattern %s to %s", pattern, 169 | ig == root_ignores ? "root ignores" : ig->abs_path); 170 | } 171 | 172 | /* For loading git/hg ignore patterns */ 173 | void load_ignore_patterns(ignores *ig, const char *path) { 174 | FILE *fp = NULL; 175 | fp = fopen(path, "r"); 176 | if (fp == NULL) { 177 | log_debug("Skipping ignore file %s: not readable", path); 178 | return; 179 | } 180 | log_debug("Loading ignore file %s.", path); 181 | 182 | char *line = NULL; 183 | ssize_t line_len = 0; 184 | size_t line_cap = 0; 185 | 186 | while ((line_len = getline(&line, &line_cap, fp)) > 0) { 187 | if (line_len == 0 || line[0] == '\n' || line[0] == '#') { 188 | continue; 189 | } 190 | if (line[line_len - 1] == '\n') { 191 | line[line_len - 1] = '\0'; /* kill the \n */ 192 | } 193 | add_ignore_pattern(ig, line); 194 | } 195 | 196 | free(line); 197 | fclose(fp); 198 | } 199 | 200 | static int ackmate_dir_match(const char *dir_name) { 201 | if (opts.ackmate_dir_filter == NULL) { 202 | return 0; 203 | } 204 | /* we just care about the match, not where the matches are */ 205 | return pcre_exec(opts.ackmate_dir_filter, NULL, dir_name, strlen(dir_name), 0, 0, NULL, 0); 206 | } 207 | 208 | /* This is the hottest code in Ag. 10-15% of all execution time is spent here */ 209 | static int path_ignore_search(const ignores *ig, const char *path, const char *filename) { 210 | char *temp; 211 | int temp_start_pos; 212 | size_t i; 213 | int match_pos; 214 | 215 | match_pos = binary_search(filename, ig->names, 0, ig->names_len); 216 | if (match_pos >= 0) { 217 | log_debug("file %s ignored because name matches static pattern %s", filename, ig->names[match_pos]); 218 | return 1; 219 | } 220 | 221 | ag_asprintf(&temp, "%s/%s", path[0] == '.' ? path + 1 : path, filename); 222 | //ig->abs_path has its leading slash stripped, so we have to strip the leading slash 223 | //of temp as well 224 | temp_start_pos = (temp[0] == '/') ? 1 : 0; 225 | 226 | if (strncmp(temp + temp_start_pos, ig->abs_path, ig->abs_path_len) == 0) { 227 | char *slash_filename = temp + temp_start_pos + ig->abs_path_len; 228 | if (slash_filename[0] == '/') { 229 | slash_filename++; 230 | } 231 | match_pos = binary_search(slash_filename, ig->names, 0, ig->names_len); 232 | if (match_pos >= 0) { 233 | log_debug("file %s ignored because name matches static pattern %s", temp, ig->names[match_pos]); 234 | free(temp); 235 | return 1; 236 | } 237 | 238 | match_pos = binary_search(slash_filename, ig->slash_names, 0, ig->slash_names_len); 239 | if (match_pos >= 0) { 240 | log_debug("file %s ignored because name matches slash static pattern %s", slash_filename, ig->slash_names[match_pos]); 241 | free(temp); 242 | return 1; 243 | } 244 | 245 | for (i = 0; i < ig->names_len; i++) { 246 | char *pos = strstr(slash_filename, ig->names[i]); 247 | if (pos == slash_filename || (pos && *(pos - 1) == '/')) { 248 | pos += strlen(ig->names[i]); 249 | if (*pos == '\0' || *pos == '/') { 250 | log_debug("file %s ignored because path somewhere matches name %s", slash_filename, ig->names[i]); 251 | free(temp); 252 | return 1; 253 | } 254 | } 255 | log_debug("pattern %s doesn't match path %s", ig->names[i], slash_filename); 256 | } 257 | 258 | for (i = 0; i < ig->slash_regexes_len; i++) { 259 | if (fnmatch(ig->slash_regexes[i], slash_filename, fnmatch_flags) == 0) { 260 | log_debug("file %s ignored because name matches slash regex pattern %s", slash_filename, ig->slash_regexes[i]); 261 | free(temp); 262 | return 1; 263 | } 264 | log_debug("pattern %s doesn't match slash file %s", ig->slash_regexes[i], slash_filename); 265 | } 266 | } 267 | 268 | for (i = 0; i < ig->invert_regexes_len; i++) { 269 | if (fnmatch(ig->invert_regexes[i], filename, fnmatch_flags) == 0) { 270 | log_debug("file %s not ignored because name matches regex pattern !%s", filename, ig->invert_regexes[i]); 271 | free(temp); 272 | return 0; 273 | } 274 | log_debug("pattern !%s doesn't match file %s", ig->invert_regexes[i], filename); 275 | } 276 | 277 | for (i = 0; i < ig->regexes_len; i++) { 278 | if (fnmatch(ig->regexes[i], filename, fnmatch_flags) == 0) { 279 | log_debug("file %s ignored because name matches regex pattern %s", filename, ig->regexes[i]); 280 | free(temp); 281 | return 1; 282 | } 283 | log_debug("pattern %s doesn't match file %s", ig->regexes[i], filename); 284 | } 285 | 286 | int rv = ackmate_dir_match(temp); 287 | free(temp); 288 | return rv; 289 | } 290 | 291 | /* This function is REALLY HOT. It gets called for every file */ 292 | int filename_filter(const char *path, const struct dirent *dir, void *baton) { 293 | const char *filename = dir->d_name; 294 | if (!opts.search_hidden_files && filename[0] == '.') { 295 | return 0; 296 | } 297 | 298 | size_t i; 299 | for (i = 0; evil_hardcoded_ignore_files[i] != NULL; i++) { 300 | if (strcmp(filename, evil_hardcoded_ignore_files[i]) == 0) { 301 | return 0; 302 | } 303 | } 304 | 305 | if (!opts.follow_symlinks && is_symlink(path, dir)) { 306 | log_debug("File %s ignored becaused it's a symlink", dir->d_name); 307 | return 0; 308 | } 309 | 310 | if (is_named_pipe(path, dir)) { 311 | log_debug("%s ignored because it's a named pipe or socket", path); 312 | return 0; 313 | } 314 | 315 | if (opts.search_all_files && !opts.path_to_ignore) { 316 | return 1; 317 | } 318 | 319 | scandir_baton_t *scandir_baton = (scandir_baton_t *)baton; 320 | const char *path_start = scandir_baton->path_start; 321 | 322 | const char *extension = strchr(filename, '.'); 323 | if (extension) { 324 | if (extension[1]) { 325 | // The dot is not the last character, extension starts at the next one 326 | ++extension; 327 | } else { 328 | // No extension 329 | extension = NULL; 330 | } 331 | } 332 | 333 | #ifdef HAVE_DIRENT_DNAMLEN 334 | size_t filename_len = dir->d_namlen; 335 | #else 336 | size_t filename_len = 0; 337 | #endif 338 | 339 | if (strncmp(filename, "./", 2) == 0) { 340 | #ifndef HAVE_DIRENT_DNAMLEN 341 | filename_len = strlen(filename); 342 | #endif 343 | filename++; 344 | filename_len--; 345 | } 346 | 347 | const ignores *ig = scandir_baton->ig; 348 | 349 | while (ig != NULL) { 350 | if (extension) { 351 | int match_pos = binary_search(extension, ig->extensions, 0, ig->extensions_len); 352 | if (match_pos >= 0) { 353 | log_debug("file %s ignored because name matches extension %s", filename, ig->extensions[match_pos]); 354 | return 0; 355 | } 356 | } 357 | 358 | if (path_ignore_search(ig, path_start, filename)) { 359 | return 0; 360 | } 361 | 362 | if (is_directory(path, dir)) { 363 | #ifndef HAVE_DIRENT_DNAMLEN 364 | if (!filename_len) { 365 | filename_len = strlen(filename); 366 | } 367 | #endif 368 | if (filename[filename_len - 1] != '/') { 369 | char *temp; 370 | ag_asprintf(&temp, "%s/", filename); 371 | int rv = path_ignore_search(ig, path_start, temp); 372 | free(temp); 373 | if (rv) { 374 | return 0; 375 | } 376 | } 377 | } 378 | ig = ig->parent; 379 | } 380 | 381 | log_debug("%s not ignored", filename); 382 | return 1; 383 | } 384 | -------------------------------------------------------------------------------- /src/ignore.h: -------------------------------------------------------------------------------- 1 | #ifndef IGNORE_H 2 | #define IGNORE_H 3 | 4 | #include 5 | #include 6 | 7 | struct ignores { 8 | char **extensions; /* File extensions to ignore */ 9 | size_t extensions_len; 10 | 11 | char **names; /* Non-regex ignore lines. Sorted so we can binary search them. */ 12 | size_t names_len; 13 | char **slash_names; /* Same but starts with a slash */ 14 | size_t slash_names_len; 15 | 16 | char **regexes; /* For patterns that need fnmatch */ 17 | size_t regexes_len; 18 | char **invert_regexes; /* For "!" patterns */ 19 | size_t invert_regexes_len; 20 | char **slash_regexes; 21 | size_t slash_regexes_len; 22 | 23 | const char *dirname; 24 | size_t dirname_len; 25 | char *abs_path; 26 | size_t abs_path_len; 27 | 28 | struct ignores *parent; 29 | }; 30 | typedef struct ignores ignores; 31 | 32 | extern ignores *root_ignores; 33 | 34 | extern const char *evil_hardcoded_ignore_files[]; 35 | extern const char *ignore_pattern_files[]; 36 | 37 | ignores *init_ignore(ignores *parent, const char *dirname, const size_t dirname_len); 38 | void cleanup_ignore(ignores *ig); 39 | 40 | void add_ignore_pattern(ignores *ig, const char *pattern); 41 | 42 | void load_ignore_patterns(ignores *ig, const char *path); 43 | 44 | int filename_filter(const char *path, const struct dirent *dir, void *baton); 45 | 46 | int is_empty(ignores *ig); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/lang.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "lang.h" 5 | #include "util.h" 6 | 7 | lang_spec_t langs[] = { 8 | { "actionscript", { "as", "mxml" } }, 9 | { "ada", { "ada", "adb", "ads" } }, 10 | { "asciidoc", { "adoc", "ad", "asc", "asciidoc" } }, 11 | { "apl", { "apl" } }, 12 | { "asm", { "asm", "s" } }, 13 | { "asp", { "asp", "asa", "aspx", "asax", "ashx", "ascx", "asmx" } }, 14 | { "aspx", { "asp", "asa", "aspx", "asax", "ashx", "ascx", "asmx" } }, 15 | { "batch", { "bat", "cmd" } }, 16 | { "bazel", { "bazel" } }, 17 | { "bitbake", { "bb", "bbappend", "bbclass", "inc" } }, 18 | { "cc", { "c", "h", "xs" } }, 19 | { "cfmx", { "cfc", "cfm", "cfml" } }, 20 | { "chpl", { "chpl" } }, 21 | { "clojure", { "clj", "cljs", "cljc", "cljx", "edn" } }, 22 | { "coffee", { "coffee", "cjsx" } }, 23 | { "config", { "config" } }, 24 | { "coq", { "coq", "g", "v" } }, 25 | { "cpp", { "cpp", "cc", "C", "cxx", "m", "hpp", "hh", "h", "H", "hxx", "tpp" } }, 26 | { "crystal", { "cr", "ecr" } }, 27 | { "csharp", { "cs" } }, 28 | { "cshtml", { "cshtml" } }, 29 | { "css", { "css" } }, 30 | { "cython", { "pyx", "pxd", "pxi" } }, 31 | { "delphi", { "pas", "int", "dfm", "nfm", "dof", "dpk", "dpr", "dproj", "groupproj", "bdsgroup", "bdsproj" } }, 32 | { "dlang", { "d", "di" } }, 33 | { "dot", { "dot", "gv" } }, 34 | { "dts", { "dts", "dtsi" } }, 35 | { "ebuild", { "ebuild", "eclass" } }, 36 | { "elisp", { "el" } }, 37 | { "elixir", { "ex", "eex", "exs" } }, 38 | { "elm", { "elm" } }, 39 | { "erlang", { "erl", "hrl" } }, 40 | { "factor", { "factor" } }, 41 | { "fortran", { "f", "F", "f77", "f90", "F90", "f95", "f03", "for", "ftn", "fpp", "FPP" } }, 42 | { "fsharp", { "fs", "fsi", "fsx" } }, 43 | { "gettext", { "po", "pot", "mo" } }, 44 | { "glsl", { "vert", "tesc", "tese", "geom", "frag", "comp" } }, 45 | { "go", { "go" } }, 46 | { "gradle", { "gradle" } }, 47 | { "groovy", { "groovy", "gtmpl", "gpp", "grunit", "gradle" } }, 48 | { "haml", { "haml" } }, 49 | { "handlebars", { "hbs" } }, 50 | { "haskell", { "hs", "hsig", "lhs" } }, 51 | { "haxe", { "hx" } }, 52 | { "hh", { "h" } }, 53 | { "html", { "htm", "html", "shtml", "xhtml" } }, 54 | { "idris", { "idr", "ipkg", "lidr" } }, 55 | { "ini", { "ini" } }, 56 | { "ipython", { "ipynb" } }, 57 | { "isabelle", { "thy" } }, 58 | { "j", { "ijs" } }, 59 | { "jade", { "jade" } }, 60 | { "java", { "java", "properties" } }, 61 | { "jinja2", { "j2" } }, 62 | { "js", { "es6", "js", "jsx", "vue" } }, 63 | { "json", { "json" } }, 64 | { "jsp", { "jsp", "jspx", "jhtm", "jhtml", "jspf", "tag", "tagf" } }, 65 | { "julia", { "jl" } }, 66 | { "kotlin", { "kt" } }, 67 | { "less", { "less" } }, 68 | { "liquid", { "liquid" } }, 69 | { "lisp", { "lisp", "lsp" } }, 70 | { "log", { "log" } }, 71 | { "lua", { "lua" } }, 72 | { "m4", { "m4" } }, 73 | { "make", { "Makefiles", "mk", "mak" } }, 74 | { "mako", { "mako" } }, 75 | { "markdown", { "markdown", "mdown", "mdwn", "mkdn", "mkd", "md" } }, 76 | { "mason", { "mas", "mhtml", "mpl", "mtxt" } }, 77 | { "matlab", { "m" } }, 78 | { "mathematica", { "m", "wl" } }, 79 | { "md", { "markdown", "mdown", "mdwn", "mkdn", "mkd", "md" } }, 80 | { "mercury", { "m", "moo" } }, 81 | { "naccess", { "asa", "rsa" } }, 82 | { "nim", { "nim" } }, 83 | { "nix", { "nix" } }, 84 | { "objc", { "m", "h" } }, 85 | { "objcpp", { "mm", "h" } }, 86 | { "ocaml", { "ml", "mli", "mll", "mly" } }, 87 | { "octave", { "m" } }, 88 | { "org", { "org" } }, 89 | { "parrot", { "pir", "pasm", "pmc", "ops", "pod", "pg", "tg" } }, 90 | { "pdb", { "pdb" } }, 91 | { "perl", { "pl", "pm", "pm6", "pod", "t" } }, 92 | { "php", { "php", "phpt", "php3", "php4", "php5", "phtml" } }, 93 | { "pike", { "pike", "pmod" } }, 94 | { "plist", { "plist" } }, 95 | { "plone", { "pt", "cpt", "metadata", "cpy", "py", "xml", "zcml" } }, 96 | { "powershell", { "ps1" } }, 97 | { "proto", { "proto" } }, 98 | { "ps1", { "ps1" } }, 99 | { "pug", { "pug" } }, 100 | { "puppet", { "pp" } }, 101 | { "python", { "py" } }, 102 | { "qml", { "qml" } }, 103 | { "racket", { "rkt", "ss", "scm" } }, 104 | { "rake", { "Rakefile" } }, 105 | { "razor", { "cshtml" } }, 106 | { "restructuredtext", { "rst" } }, 107 | { "rs", { "rs" } }, 108 | { "r", { "r", "R", "Rmd", "Rnw", "Rtex", "Rrst" } }, 109 | { "rdoc", { "rdoc" } }, 110 | { "ruby", { "rb", "rhtml", "rjs", "rxml", "erb", "rake", "spec" } }, 111 | { "rust", { "rs" } }, 112 | { "salt", { "sls" } }, 113 | { "sass", { "sass", "scss" } }, 114 | { "scala", { "scala" } }, 115 | { "scheme", { "scm", "ss" } }, 116 | { "shell", { "sh", "bash", "csh", "tcsh", "ksh", "zsh", "fish" } }, 117 | { "smalltalk", { "st" } }, 118 | { "sml", { "sml", "fun", "mlb", "sig" } }, 119 | { "sql", { "sql", "ctl" } }, 120 | { "stata", { "do", "ado" } }, 121 | { "stylus", { "styl" } }, 122 | { "swift", { "swift" } }, 123 | { "tcl", { "tcl", "itcl", "itk" } }, 124 | { "terraform", { "tf", "tfvars" } }, 125 | { "tex", { "tex", "cls", "sty" } }, 126 | { "thrift", { "thrift" } }, 127 | { "tla", { "tla" } }, 128 | { "tt", { "tt", "tt2", "ttml" } }, 129 | { "toml", { "toml" } }, 130 | { "ts", { "ts", "tsx" } }, 131 | { "twig", { "twig" } }, 132 | { "vala", { "vala", "vapi" } }, 133 | { "vb", { "bas", "cls", "frm", "ctl", "vb", "resx" } }, 134 | { "velocity", { "vm", "vtl", "vsl" } }, 135 | { "verilog", { "v", "vh", "sv", "svh" } }, 136 | { "vhdl", { "vhd", "vhdl" } }, 137 | { "vim", { "vim" } }, 138 | { "vue", { "vue" } }, 139 | { "wix", { "wxi", "wxs" } }, 140 | { "wsdl", { "wsdl" } }, 141 | { "wadl", { "wadl" } }, 142 | { "xml", { "xml", "dtd", "xsl", "xslt", "xsd", "ent", "tld", "plist", "wsdl" } }, 143 | { "yaml", { "yaml", "yml" } }, 144 | { "zeek", { "zeek", "bro", "bif" } }, 145 | { "zephir", { "zep" } } 146 | }; 147 | 148 | size_t get_lang_count() { 149 | return sizeof(langs) / sizeof(lang_spec_t); 150 | } 151 | 152 | char *make_lang_regex(char *ext_array, size_t num_exts) { 153 | int regex_capacity = 100; 154 | char *regex = ag_malloc(regex_capacity); 155 | int regex_length = 3; 156 | int subsequent = 0; 157 | char *extension; 158 | size_t i; 159 | 160 | strcpy(regex, "\\.("); 161 | 162 | for (i = 0; i < num_exts; ++i) { 163 | extension = ext_array + i * SINGLE_EXT_LEN; 164 | int extension_length = strlen(extension); 165 | while (regex_length + extension_length + 3 + subsequent > regex_capacity) { 166 | regex_capacity *= 2; 167 | regex = ag_realloc(regex, regex_capacity); 168 | } 169 | if (subsequent) { 170 | regex[regex_length++] = '|'; 171 | } else { 172 | subsequent = 1; 173 | } 174 | strcpy(regex + regex_length, extension); 175 | regex_length += extension_length; 176 | } 177 | 178 | regex[regex_length++] = ')'; 179 | regex[regex_length++] = '$'; 180 | regex[regex_length++] = 0; 181 | return regex; 182 | } 183 | 184 | size_t combine_file_extensions(size_t *extension_index, size_t len, char **exts) { 185 | /* Keep it fixed as 100 for the reason that if you have more than 100 186 | * file types to search, you'd better search all the files. 187 | * */ 188 | size_t ext_capacity = 100; 189 | (*exts) = (char *)ag_malloc(ext_capacity * SINGLE_EXT_LEN); 190 | memset((*exts), 0, ext_capacity * SINGLE_EXT_LEN); 191 | size_t num_of_extensions = 0; 192 | 193 | size_t i; 194 | for (i = 0; i < len; ++i) { 195 | size_t j = 0; 196 | const char *ext = langs[extension_index[i]].extensions[j]; 197 | do { 198 | if (num_of_extensions == ext_capacity) { 199 | break; 200 | } 201 | char *pos = (*exts) + num_of_extensions * SINGLE_EXT_LEN; 202 | strncpy(pos, ext, strlen(ext)); 203 | ++num_of_extensions; 204 | ext = langs[extension_index[i]].extensions[++j]; 205 | } while (ext); 206 | } 207 | 208 | return num_of_extensions; 209 | } 210 | -------------------------------------------------------------------------------- /src/lang.h: -------------------------------------------------------------------------------- 1 | #ifndef LANG_H 2 | #define LANG_H 3 | 4 | #define MAX_EXTENSIONS 12 5 | #define SINGLE_EXT_LEN 20 6 | 7 | typedef struct { 8 | const char *name; 9 | const char *extensions[MAX_EXTENSIONS]; 10 | } lang_spec_t; 11 | 12 | extern lang_spec_t langs[]; 13 | 14 | /** 15 | Return the language count. 16 | */ 17 | size_t get_lang_count(void); 18 | 19 | /** 20 | Convert a NULL-terminated array of language extensions 21 | into a regular expression of the form \.(extension1|extension2...)$ 22 | 23 | Caller is responsible for freeing the returned string. 24 | */ 25 | char *make_lang_regex(char *ext_array, size_t num_exts); 26 | 27 | 28 | /** 29 | Combine multiple file type extensions into one array. 30 | 31 | The combined result is returned through *exts*; 32 | *exts* is one-dimension array, which can contain up to 100 extensions; 33 | The number of extensions that *exts* actually contain is returned. 34 | */ 35 | size_t combine_file_extensions(size_t *extension_index, size_t len, char **exts); 36 | #endif 37 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "log.h" 5 | #include "util.h" 6 | 7 | pthread_mutex_t print_mtx = PTHREAD_MUTEX_INITIALIZER; 8 | static enum log_level log_threshold = LOG_LEVEL_ERR; 9 | 10 | void set_log_level(enum log_level threshold) { 11 | log_threshold = threshold; 12 | } 13 | 14 | void log_debug(const char *fmt, ...) { 15 | va_list args; 16 | va_start(args, fmt); 17 | vplog(LOG_LEVEL_DEBUG, fmt, args); 18 | va_end(args); 19 | } 20 | 21 | void log_msg(const char *fmt, ...) { 22 | va_list args; 23 | va_start(args, fmt); 24 | vplog(LOG_LEVEL_MSG, fmt, args); 25 | va_end(args); 26 | } 27 | 28 | void log_warn(const char *fmt, ...) { 29 | va_list args; 30 | va_start(args, fmt); 31 | vplog(LOG_LEVEL_WARN, fmt, args); 32 | va_end(args); 33 | } 34 | 35 | void log_err(const char *fmt, ...) { 36 | va_list args; 37 | va_start(args, fmt); 38 | vplog(LOG_LEVEL_ERR, fmt, args); 39 | va_end(args); 40 | } 41 | 42 | void vplog(const unsigned int level, const char *fmt, va_list args) { 43 | if (level < log_threshold) { 44 | return; 45 | } 46 | 47 | pthread_mutex_lock(&print_mtx); 48 | FILE *stream = out_fd; 49 | 50 | switch (level) { 51 | case LOG_LEVEL_DEBUG: 52 | fprintf(stream, "DEBUG: "); 53 | break; 54 | case LOG_LEVEL_MSG: 55 | fprintf(stream, "MSG: "); 56 | break; 57 | case LOG_LEVEL_WARN: 58 | fprintf(stream, "WARN: "); 59 | break; 60 | case LOG_LEVEL_ERR: 61 | stream = stderr; 62 | fprintf(stream, "ERR: "); 63 | break; 64 | } 65 | 66 | vfprintf(stream, fmt, args); 67 | fprintf(stream, "\n"); 68 | pthread_mutex_unlock(&print_mtx); 69 | } 70 | 71 | void plog(const unsigned int level, const char *fmt, ...) { 72 | va_list args; 73 | va_start(args, fmt); 74 | vplog(level, fmt, args); 75 | va_end(args); 76 | } 77 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | 4 | #include 5 | 6 | #include "config.h" 7 | 8 | #ifdef HAVE_PTHREAD_H 9 | #include 10 | #endif 11 | 12 | extern pthread_mutex_t print_mtx; 13 | 14 | enum log_level { 15 | LOG_LEVEL_DEBUG = 10, 16 | LOG_LEVEL_MSG = 20, 17 | LOG_LEVEL_WARN = 30, 18 | LOG_LEVEL_ERR = 40, 19 | LOG_LEVEL_NONE = 100 20 | }; 21 | 22 | void set_log_level(enum log_level threshold); 23 | 24 | void log_debug(const char *fmt, ...); 25 | void log_msg(const char *fmt, ...); 26 | void log_warn(const char *fmt, ...); 27 | void log_err(const char *fmt, ...); 28 | 29 | void vplog(const unsigned int level, const char *fmt, va_list args); 30 | void plog(const unsigned int level, const char *fmt, ...); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #ifdef _WIN32 9 | #include 10 | #endif 11 | 12 | #include "config.h" 13 | 14 | #ifdef HAVE_SYS_CPUSET_H 15 | #include 16 | #endif 17 | 18 | #ifdef HAVE_PTHREAD_H 19 | #include 20 | #endif 21 | 22 | #if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(__FreeBSD__) 23 | #include 24 | #endif 25 | 26 | #include "log.h" 27 | #include "options.h" 28 | #include "search.h" 29 | #include "util.h" 30 | 31 | typedef struct { 32 | pthread_t thread; 33 | int id; 34 | } worker_t; 35 | 36 | int main(int argc, char **argv) { 37 | char **base_paths = NULL; 38 | char **paths = NULL; 39 | int i; 40 | int pcre_opts = PCRE_MULTILINE; 41 | int study_opts = 0; 42 | worker_t *workers = NULL; 43 | int workers_len; 44 | int num_cores; 45 | 46 | #ifdef HAVE_PLEDGE 47 | if (pledge("stdio rpath proc exec", NULL) == -1) { 48 | die("pledge: %s", strerror(errno)); 49 | } 50 | #endif 51 | 52 | set_log_level(LOG_LEVEL_WARN); 53 | 54 | work_queue = NULL; 55 | work_queue_tail = NULL; 56 | root_ignores = init_ignore(NULL, "", 0); 57 | out_fd = stdout; 58 | 59 | parse_options(argc, argv, &base_paths, &paths); 60 | log_debug("PCRE Version: %s", pcre_version()); 61 | if (opts.stats) { 62 | memset(&stats, 0, sizeof(stats)); 63 | gettimeofday(&(stats.time_start), NULL); 64 | } 65 | 66 | #ifdef USE_PCRE_JIT 67 | int has_jit = 0; 68 | pcre_config(PCRE_CONFIG_JIT, &has_jit); 69 | if (has_jit) { 70 | study_opts |= PCRE_STUDY_JIT_COMPILE; 71 | } 72 | #endif 73 | 74 | #ifdef _WIN32 75 | { 76 | SYSTEM_INFO si; 77 | GetSystemInfo(&si); 78 | num_cores = si.dwNumberOfProcessors; 79 | } 80 | #else 81 | num_cores = (int)sysconf(_SC_NPROCESSORS_ONLN); 82 | #endif 83 | 84 | workers_len = num_cores < 8 ? num_cores : 8; 85 | if (opts.literal) { 86 | workers_len--; 87 | } 88 | if (opts.workers) { 89 | workers_len = opts.workers; 90 | } 91 | if (workers_len < 1) { 92 | workers_len = 1; 93 | } 94 | 95 | log_debug("Using %i workers", workers_len); 96 | done_adding_files = FALSE; 97 | workers = ag_calloc(workers_len, sizeof(worker_t)); 98 | if (pthread_cond_init(&files_ready, NULL)) { 99 | die("pthread_cond_init failed!"); 100 | } 101 | if (pthread_mutex_init(&print_mtx, NULL)) { 102 | die("pthread_mutex_init failed!"); 103 | } 104 | if (opts.stats && pthread_mutex_init(&stats_mtx, NULL)) { 105 | die("pthread_mutex_init failed!"); 106 | } 107 | if (pthread_mutex_init(&work_queue_mtx, NULL)) { 108 | die("pthread_mutex_init failed!"); 109 | } 110 | 111 | if (opts.casing == CASE_SMART) { 112 | opts.casing = is_lowercase(opts.query) ? CASE_INSENSITIVE : CASE_SENSITIVE; 113 | } 114 | 115 | if (opts.literal) { 116 | if (opts.casing == CASE_INSENSITIVE) { 117 | /* Search routine needs the query to be lowercase */ 118 | char *c = opts.query; 119 | for (; *c != '\0'; ++c) { 120 | *c = (char)tolower(*c); 121 | } 122 | } 123 | generate_alpha_skip(opts.query, opts.query_len, alpha_skip_lookup, opts.casing == CASE_SENSITIVE); 124 | find_skip_lookup = NULL; 125 | generate_find_skip(opts.query, opts.query_len, &find_skip_lookup, opts.casing == CASE_SENSITIVE); 126 | generate_hash(opts.query, opts.query_len, h_table, opts.casing == CASE_SENSITIVE); 127 | if (opts.word_regexp) { 128 | init_wordchar_table(); 129 | opts.literal_starts_wordchar = is_wordchar(opts.query[0]); 130 | opts.literal_ends_wordchar = is_wordchar(opts.query[opts.query_len - 1]); 131 | } 132 | } else { 133 | if (opts.casing == CASE_INSENSITIVE) { 134 | pcre_opts |= PCRE_CASELESS; 135 | } 136 | if (opts.word_regexp) { 137 | char *word_regexp_query; 138 | ag_asprintf(&word_regexp_query, "\\b(?:%s)\\b", opts.query); 139 | free(opts.query); 140 | opts.query = word_regexp_query; 141 | opts.query_len = strlen(opts.query); 142 | } 143 | compile_study(&opts.re, &opts.re_extra, opts.query, pcre_opts, study_opts); 144 | } 145 | 146 | if (opts.search_stream) { 147 | search_stream(stdin, ""); 148 | } else { 149 | for (i = 0; i < workers_len; i++) { 150 | workers[i].id = i; 151 | int rv = pthread_create(&(workers[i].thread), NULL, &search_file_worker, &(workers[i].id)); 152 | if (rv != 0) { 153 | die("Error in pthread_create(): %s", strerror(rv)); 154 | } 155 | #if defined(HAVE_PTHREAD_SETAFFINITY_NP) && (defined(USE_CPU_SET) || defined(HAVE_SYS_CPUSET_H)) 156 | if (opts.use_thread_affinity) { 157 | #if defined(__linux__) || defined(__midipix__) 158 | cpu_set_t cpu_set; 159 | #elif __FreeBSD__ 160 | cpuset_t cpu_set; 161 | #endif 162 | CPU_ZERO(&cpu_set); 163 | CPU_SET(i % num_cores, &cpu_set); 164 | rv = pthread_setaffinity_np(workers[i].thread, sizeof(cpu_set), &cpu_set); 165 | if (rv) { 166 | log_err("Error in pthread_setaffinity_np(): %s", strerror(rv)); 167 | log_err("Performance may be affected. Use --noaffinity to suppress this message."); 168 | } else { 169 | log_debug("Thread %i set to CPU %i", i, i); 170 | } 171 | } else { 172 | log_debug("Thread affinity disabled."); 173 | } 174 | #else 175 | log_debug("No CPU affinity support."); 176 | #endif 177 | } 178 | 179 | #ifdef HAVE_PLEDGE 180 | if (pledge("stdio rpath", NULL) == -1) { 181 | die("pledge: %s", strerror(errno)); 182 | } 183 | #endif 184 | for (i = 0; paths[i] != NULL; i++) { 185 | log_debug("searching path %s for %s", paths[i], opts.query); 186 | symhash = NULL; 187 | ignores *ig = init_ignore(root_ignores, "", 0); 188 | struct stat s = { .st_dev = 0 }; 189 | #ifndef _WIN32 190 | /* The device is ignored if opts.one_dev is false, so it's fine 191 | * to leave it at the default 0 192 | */ 193 | if (opts.one_dev && lstat(paths[i], &s) == -1) { 194 | log_err("Failed to get device information for path %s. Skipping...", paths[i]); 195 | } 196 | #endif 197 | search_dir(ig, base_paths[i], paths[i], 0, s.st_dev); 198 | cleanup_ignore(ig); 199 | } 200 | pthread_mutex_lock(&work_queue_mtx); 201 | done_adding_files = TRUE; 202 | pthread_cond_broadcast(&files_ready); 203 | pthread_mutex_unlock(&work_queue_mtx); 204 | for (i = 0; i < workers_len; i++) { 205 | if (pthread_join(workers[i].thread, NULL)) { 206 | die("pthread_join failed!"); 207 | } 208 | } 209 | } 210 | 211 | if (opts.stats) { 212 | gettimeofday(&(stats.time_end), NULL); 213 | double time_diff = ((long)stats.time_end.tv_sec * 1000000 + stats.time_end.tv_usec) - 214 | ((long)stats.time_start.tv_sec * 1000000 + stats.time_start.tv_usec); 215 | time_diff /= 1000000; 216 | printf("%zu matches\n%zu files contained matches\n%zu files searched\n%zu bytes searched\n%f seconds\n", 217 | stats.total_matches, stats.total_file_matches, stats.total_files, stats.total_bytes, time_diff); 218 | pthread_mutex_destroy(&stats_mtx); 219 | } 220 | 221 | if (opts.pager) { 222 | pclose(out_fd); 223 | } 224 | cleanup_options(); 225 | pthread_cond_destroy(&files_ready); 226 | pthread_mutex_destroy(&work_queue_mtx); 227 | pthread_mutex_destroy(&print_mtx); 228 | cleanup_ignore(root_ignores); 229 | free(workers); 230 | for (i = 0; paths[i] != NULL; i++) { 231 | free(paths[i]); 232 | free(base_paths[i]); 233 | } 234 | free(base_paths); 235 | free(paths); 236 | if (find_skip_lookup) { 237 | free(find_skip_lookup); 238 | } 239 | return !opts.match_found; 240 | } 241 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONS_H 2 | #define OPTIONS_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define DEFAULT_AFTER_LEN 2 10 | #define DEFAULT_BEFORE_LEN 2 11 | #define DEFAULT_CONTEXT_LEN 2 12 | #define DEFAULT_MAX_SEARCH_DEPTH 25 13 | enum case_behavior { 14 | CASE_DEFAULT, /* Changes to CASE_SMART at the end of option parsing */ 15 | CASE_SENSITIVE, 16 | CASE_INSENSITIVE, 17 | CASE_SMART, 18 | CASE_SENSITIVE_RETRY_INSENSITIVE /* for future use */ 19 | }; 20 | 21 | enum path_print_behavior { 22 | PATH_PRINT_DEFAULT, /* PRINT_TOP if > 1 file being searched, else PRINT_NOTHING */ 23 | PATH_PRINT_DEFAULT_EACH_LINE, /* PRINT_EACH_LINE if > 1 file being searched, else PRINT_NOTHING */ 24 | PATH_PRINT_TOP, 25 | PATH_PRINT_EACH_LINE, 26 | PATH_PRINT_NOTHING 27 | }; 28 | 29 | typedef struct { 30 | int ackmate; 31 | pcre *ackmate_dir_filter; 32 | pcre_extra *ackmate_dir_filter_extra; 33 | size_t after; 34 | size_t before; 35 | enum case_behavior casing; 36 | const char *file_search_string; 37 | int match_files; 38 | pcre *file_search_regex; 39 | pcre_extra *file_search_regex_extra; 40 | int color; 41 | char *color_line_number; 42 | char *color_match; 43 | char *color_path; 44 | int color_win_ansi; 45 | int column; 46 | int context; 47 | int follow_symlinks; 48 | int invert_match; 49 | int literal; 50 | int literal_starts_wordchar; 51 | int literal_ends_wordchar; 52 | size_t max_matches_per_file; 53 | int max_search_depth; 54 | int mmap; 55 | int multiline; 56 | int one_dev; 57 | int only_matching; 58 | char path_sep; 59 | int path_to_ignore; 60 | int print_break; 61 | int print_count; 62 | int print_filename_only; 63 | int print_nonmatching_files; 64 | int print_path; 65 | int print_all_paths; 66 | int print_line_numbers; 67 | int print_long_lines; /* TODO: support this in print.c */ 68 | int passthrough; 69 | pcre *re; 70 | pcre_extra *re_extra; 71 | int recurse_dirs; 72 | int search_all_files; 73 | int skip_vcs_ignores; 74 | int search_binary_files; 75 | int search_zip_files; 76 | int search_hidden_files; 77 | int search_stream; /* true if tail -F blah | ag */ 78 | int stats; 79 | size_t stream_line_num; /* This should totally not be in here */ 80 | int match_found; /* This should totally not be in here */ 81 | ino_t stdout_inode; 82 | char *query; 83 | int query_len; 84 | char *pager; 85 | int paths_len; 86 | int parallel; 87 | int use_thread_affinity; 88 | int vimgrep; 89 | size_t width; 90 | int word_regexp; 91 | int workers; 92 | } cli_options; 93 | 94 | /* global options. parse_options gives it sane values, everything else reads from it */ 95 | extern cli_options opts; 96 | 97 | typedef struct option option_t; 98 | 99 | void usage(void); 100 | void print_version(void); 101 | 102 | void init_options(void); 103 | void parse_options(int argc, char **argv, char **base_paths[], char **paths[]); 104 | void cleanup_options(void); 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /src/print.h: -------------------------------------------------------------------------------- 1 | #ifndef PRINT_H 2 | #define PRINT_H 3 | 4 | #include "util.h" 5 | 6 | void print_init_context(void); 7 | void print_cleanup_context(void); 8 | void print_context_append(const char *line, size_t len); 9 | void print_trailing_context(const char *path, const char *buf, size_t n); 10 | void print_path(const char *path, const char sep); 11 | void print_path_count(const char *path, const char sep, const size_t count); 12 | void print_line(const char *buf, size_t buf_pos, size_t prev_line_offset); 13 | void print_binary_file_matches(const char *path); 14 | void print_file_matches(const char *path, const char *buf, const size_t buf_len, const match_t matches[], const size_t matches_len); 15 | void print_line_number(size_t line, const char sep); 16 | void print_column_number(const match_t matches[], size_t last_printed_match, 17 | size_t prev_line_offset, const char sep); 18 | void print_file_separator(void); 19 | const char *normalize_path(const char *path); 20 | 21 | #ifdef _WIN32 22 | void windows_use_ansi(int use_ansi); 23 | int fprintf_w32(FILE *fp, const char *format, ...); 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/print_w32.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #include "print.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef FOREGROUND_MASK 10 | #define FOREGROUND_MASK (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY) 11 | #endif 12 | #ifndef BACKGROUND_MASK 13 | #define BACKGROUND_MASK (BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_INTENSITY) 14 | #endif 15 | 16 | #define FG_RGB (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN) 17 | #define BG_RGB (BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN) 18 | 19 | // BUFSIZ is guarenteed to be "at least 256 bytes" which might 20 | // not be enough for us. Use an arbitrary but reasonably big 21 | // buffer. win32 colored output will be truncated to this length. 22 | #define BUF_SIZE (16 * 1024) 23 | 24 | // max consecutive ansi sequence values beyond which we're aborting 25 | // e.g. this is 3 values: \e[0;1;33m 26 | #define MAX_VALUES 8 27 | 28 | static int g_use_ansi = 0; 29 | void windows_use_ansi(int use_ansi) { 30 | g_use_ansi = use_ansi; 31 | } 32 | 33 | int fprintf_w32(FILE *fp, const char *format, ...) { 34 | va_list args; 35 | char buf[BUF_SIZE] = { 0 }, *ptr = buf; 36 | static WORD attr_reset; 37 | static BOOL attr_initialized = FALSE; 38 | HANDLE stdo = INVALID_HANDLE_VALUE; 39 | WORD attr; 40 | DWORD written, csize; 41 | CONSOLE_CURSOR_INFO cci; 42 | CONSOLE_SCREEN_BUFFER_INFO csbi; 43 | COORD coord; 44 | 45 | // if we don't output to screen (tty) e.g. for pager/pipe or 46 | // if for other reason we can't access the screen info, of if 47 | // the user just prefers ansi, do plain passthrough. 48 | BOOL passthrough = 49 | g_use_ansi || 50 | !isatty(fileno(fp)) || 51 | INVALID_HANDLE_VALUE == (stdo = (HANDLE)_get_osfhandle(fileno(fp))) || 52 | !GetConsoleScreenBufferInfo(stdo, &csbi); 53 | 54 | if (passthrough) { 55 | int rv; 56 | va_start(args, format); 57 | rv = vfprintf(fp, format, args); 58 | va_end(args); 59 | return rv; 60 | } 61 | 62 | va_start(args, format); 63 | // truncates to (null terminated) BUF_SIZE if too long. 64 | // if too long - vsnprintf will fill count chars without 65 | // terminating null. buf is zeroed, so make sure we don't fill it. 66 | vsnprintf(buf, BUF_SIZE - 1, format, args); 67 | va_end(args); 68 | 69 | attr = csbi.wAttributes; 70 | if (!attr_initialized) { 71 | // reset is defined to have all (non color) attributes off 72 | attr_reset = attr & (FG_RGB | BG_RGB); 73 | attr_initialized = TRUE; 74 | } 75 | 76 | while (*ptr) { 77 | if (*ptr == '\033') { 78 | unsigned char c; 79 | int i, n = 0, m = '\0', v[MAX_VALUES], w, h; 80 | for (i = 0; i < MAX_VALUES; i++) 81 | v[i] = -1; 82 | ptr++; 83 | retry: 84 | if ((c = *ptr++) == 0) 85 | break; 86 | if (isdigit(c)) { 87 | if (v[n] == -1) 88 | v[n] = c - '0'; 89 | else 90 | v[n] = v[n] * 10 + c - '0'; 91 | goto retry; 92 | } 93 | if (c == '[') { 94 | goto retry; 95 | } 96 | if (c == ';') { 97 | if (++n == MAX_VALUES) 98 | break; 99 | goto retry; 100 | } 101 | if (c == '>' || c == '?') { 102 | m = c; 103 | goto retry; 104 | } 105 | 106 | switch (c) { 107 | // n is the last occupied index, so we have n+1 values 108 | case 'h': 109 | if (m == '?') { 110 | for (i = 0; i <= n; i++) { 111 | switch (v[i]) { 112 | case 3: 113 | GetConsoleScreenBufferInfo(stdo, &csbi); 114 | w = csbi.dwSize.X; 115 | h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 116 | csize = w * (h + 1); 117 | coord.X = 0; 118 | coord.Y = csbi.srWindow.Top; 119 | FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written); 120 | FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written); 121 | SetConsoleCursorPosition(stdo, csbi.dwCursorPosition); 122 | csbi.dwSize.X = 132; 123 | SetConsoleScreenBufferSize(stdo, csbi.dwSize); 124 | csbi.srWindow.Right = csbi.srWindow.Left + 131; 125 | SetConsoleWindowInfo(stdo, TRUE, &csbi.srWindow); 126 | break; 127 | case 5: 128 | attr = 129 | ((attr & FOREGROUND_MASK) << 4) | 130 | ((attr & BACKGROUND_MASK) >> 4); 131 | SetConsoleTextAttribute(stdo, attr); 132 | break; 133 | case 9: 134 | break; 135 | case 25: 136 | GetConsoleCursorInfo(stdo, &cci); 137 | cci.bVisible = TRUE; 138 | SetConsoleCursorInfo(stdo, &cci); 139 | break; 140 | case 47: 141 | coord.X = 0; 142 | coord.Y = 0; 143 | SetConsoleCursorPosition(stdo, coord); 144 | break; 145 | default: 146 | break; 147 | } 148 | } 149 | } else if (m == '>' && v[0] == 5) { 150 | GetConsoleCursorInfo(stdo, &cci); 151 | cci.bVisible = FALSE; 152 | SetConsoleCursorInfo(stdo, &cci); 153 | } 154 | break; 155 | case 'l': 156 | if (m == '?') { 157 | for (i = 0; i <= n; i++) { 158 | switch (v[i]) { 159 | case 3: 160 | GetConsoleScreenBufferInfo(stdo, &csbi); 161 | w = csbi.dwSize.X; 162 | h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 163 | csize = w * (h + 1); 164 | coord.X = 0; 165 | coord.Y = csbi.srWindow.Top; 166 | FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written); 167 | FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written); 168 | SetConsoleCursorPosition(stdo, csbi.dwCursorPosition); 169 | csbi.srWindow.Right = csbi.srWindow.Left + 79; 170 | SetConsoleWindowInfo(stdo, TRUE, &csbi.srWindow); 171 | csbi.dwSize.X = 80; 172 | SetConsoleScreenBufferSize(stdo, csbi.dwSize); 173 | break; 174 | case 5: 175 | attr = 176 | ((attr & FOREGROUND_MASK) << 4) | 177 | ((attr & BACKGROUND_MASK) >> 4); 178 | SetConsoleTextAttribute(stdo, attr); 179 | break; 180 | case 25: 181 | GetConsoleCursorInfo(stdo, &cci); 182 | cci.bVisible = FALSE; 183 | SetConsoleCursorInfo(stdo, &cci); 184 | break; 185 | default: 186 | break; 187 | } 188 | } 189 | } else if (m == '>' && v[0] == 5) { 190 | GetConsoleCursorInfo(stdo, &cci); 191 | cci.bVisible = TRUE; 192 | SetConsoleCursorInfo(stdo, &cci); 193 | } 194 | break; 195 | case 'm': 196 | for (i = 0; i <= n; i++) { 197 | if (v[i] == -1 || v[i] == 0) 198 | attr = attr_reset; 199 | else if (v[i] == 1) 200 | attr |= FOREGROUND_INTENSITY; 201 | else if (v[i] == 4) 202 | attr |= FOREGROUND_INTENSITY; 203 | else if (v[i] == 5) // blink is typically applied as bg intensity 204 | attr |= BACKGROUND_INTENSITY; 205 | else if (v[i] == 7) 206 | attr = 207 | ((attr & FOREGROUND_MASK) << 4) | 208 | ((attr & BACKGROUND_MASK) >> 4); 209 | else if (v[i] == 10) 210 | ; // symbol on 211 | else if (v[i] == 11) 212 | ; // symbol off 213 | else if (v[i] == 22) 214 | attr &= ~FOREGROUND_INTENSITY; 215 | else if (v[i] == 24) 216 | attr &= ~FOREGROUND_INTENSITY; 217 | else if (v[i] == 25) 218 | attr &= ~BACKGROUND_INTENSITY; 219 | else if (v[i] == 27) 220 | attr = 221 | ((attr & FOREGROUND_MASK) << 4) | 222 | ((attr & BACKGROUND_MASK) >> 4); 223 | else if (v[i] >= 30 && v[i] <= 37) { 224 | attr &= ~FG_RGB; // doesn't affect attributes 225 | if ((v[i] - 30) & 1) 226 | attr |= FOREGROUND_RED; 227 | if ((v[i] - 30) & 2) 228 | attr |= FOREGROUND_GREEN; 229 | if ((v[i] - 30) & 4) 230 | attr |= FOREGROUND_BLUE; 231 | } else if (v[i] == 39) // reset fg color and attributes 232 | attr = (attr & ~FOREGROUND_MASK) | (attr_reset & FG_RGB); 233 | else if (v[i] >= 40 && v[i] <= 47) { 234 | attr &= ~BG_RGB; 235 | if ((v[i] - 40) & 1) 236 | attr |= BACKGROUND_RED; 237 | if ((v[i] - 40) & 2) 238 | attr |= BACKGROUND_GREEN; 239 | if ((v[i] - 40) & 4) 240 | attr |= BACKGROUND_BLUE; 241 | } else if (v[i] == 49) // reset bg color 242 | attr = (attr & ~BACKGROUND_MASK) | (attr_reset & BG_RGB); 243 | } 244 | SetConsoleTextAttribute(stdo, attr); 245 | break; 246 | case 'K': 247 | GetConsoleScreenBufferInfo(stdo, &csbi); 248 | coord = csbi.dwCursorPosition; 249 | switch (v[0]) { 250 | default: 251 | case 0: 252 | csize = csbi.dwSize.X - coord.X; 253 | break; 254 | case 1: 255 | csize = coord.X; 256 | coord.X = 0; 257 | break; 258 | case 2: 259 | csize = csbi.dwSize.X; 260 | coord.X = 0; 261 | break; 262 | } 263 | FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written); 264 | FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written); 265 | SetConsoleCursorPosition(stdo, csbi.dwCursorPosition); 266 | break; 267 | case 'J': 268 | GetConsoleScreenBufferInfo(stdo, &csbi); 269 | w = csbi.dwSize.X; 270 | h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 271 | coord = csbi.dwCursorPosition; 272 | switch (v[0]) { 273 | default: 274 | case 0: 275 | csize = w * (h - coord.Y) - coord.X; 276 | coord.X = 0; 277 | break; 278 | case 1: 279 | csize = w * coord.Y + coord.X; 280 | coord.X = 0; 281 | coord.Y = csbi.srWindow.Top; 282 | break; 283 | case 2: 284 | csize = w * (h + 1); 285 | coord.X = 0; 286 | coord.Y = csbi.srWindow.Top; 287 | break; 288 | } 289 | FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written); 290 | FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written); 291 | SetConsoleCursorPosition(stdo, csbi.dwCursorPosition); 292 | break; 293 | case 'H': 294 | GetConsoleScreenBufferInfo(stdo, &csbi); 295 | coord = csbi.dwCursorPosition; 296 | if (v[0] != -1) { 297 | if (v[1] != -1) { 298 | coord.Y = csbi.srWindow.Top + v[0] - 1; 299 | coord.X = v[1] - 1; 300 | } else 301 | coord.X = v[0] - 1; 302 | } else { 303 | coord.X = 0; 304 | coord.Y = csbi.srWindow.Top; 305 | } 306 | if (coord.X < csbi.srWindow.Left) 307 | coord.X = csbi.srWindow.Left; 308 | else if (coord.X > csbi.srWindow.Right) 309 | coord.X = csbi.srWindow.Right; 310 | if (coord.Y < csbi.srWindow.Top) 311 | coord.Y = csbi.srWindow.Top; 312 | else if (coord.Y > csbi.srWindow.Bottom) 313 | coord.Y = csbi.srWindow.Bottom; 314 | SetConsoleCursorPosition(stdo, coord); 315 | break; 316 | default: 317 | break; 318 | } 319 | } else { 320 | putchar(*ptr); 321 | ptr++; 322 | } 323 | } 324 | return ptr - buf; 325 | } 326 | 327 | #endif /* _WIN32 */ 328 | -------------------------------------------------------------------------------- /src/scandir.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "scandir.h" 5 | #include "util.h" 6 | 7 | int ag_scandir(const char *dirname, 8 | struct dirent ***namelist, 9 | filter_fp filter, 10 | void *baton) { 11 | DIR *dirp = NULL; 12 | struct dirent **names = NULL; 13 | struct dirent *entry, *d; 14 | int names_len = 32; 15 | int results_len = 0; 16 | 17 | dirp = opendir(dirname); 18 | if (dirp == NULL) { 19 | goto fail; 20 | } 21 | 22 | names = malloc(sizeof(struct dirent *) * names_len); 23 | if (names == NULL) { 24 | goto fail; 25 | } 26 | 27 | while ((entry = readdir(dirp)) != NULL) { 28 | if ((*filter)(dirname, entry, baton) == FALSE) { 29 | continue; 30 | } 31 | if (results_len >= names_len) { 32 | struct dirent **tmp_names = names; 33 | names_len *= 2; 34 | names = realloc(names, sizeof(struct dirent *) * names_len); 35 | if (names == NULL) { 36 | free(tmp_names); 37 | goto fail; 38 | } 39 | } 40 | 41 | #if defined(__MINGW32__) || defined(__CYGWIN__) 42 | d = malloc(sizeof(struct dirent)); 43 | #else 44 | d = malloc(entry->d_reclen); 45 | #endif 46 | 47 | if (d == NULL) { 48 | goto fail; 49 | } 50 | #if defined(__MINGW32__) || defined(__CYGWIN__) 51 | memcpy(d, entry, sizeof(struct dirent)); 52 | #else 53 | memcpy(d, entry, entry->d_reclen); 54 | #endif 55 | 56 | names[results_len] = d; 57 | results_len++; 58 | } 59 | 60 | closedir(dirp); 61 | *namelist = names; 62 | return results_len; 63 | 64 | fail: 65 | if (dirp) { 66 | closedir(dirp); 67 | } 68 | 69 | if (names != NULL) { 70 | int i; 71 | for (i = 0; i < results_len; i++) { 72 | free(names[i]); 73 | } 74 | free(names); 75 | } 76 | return -1; 77 | } 78 | -------------------------------------------------------------------------------- /src/scandir.h: -------------------------------------------------------------------------------- 1 | #ifndef SCANDIR_H 2 | #define SCANDIR_H 3 | 4 | #include "ignore.h" 5 | 6 | typedef struct { 7 | const ignores *ig; 8 | const char *base_path; 9 | size_t base_path_len; 10 | const char *path_start; 11 | } scandir_baton_t; 12 | 13 | typedef int (*filter_fp)(const char *path, const struct dirent *, void *); 14 | 15 | int ag_scandir(const char *dirname, 16 | struct dirent ***namelist, 17 | filter_fp filter, 18 | void *baton); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/search.h: -------------------------------------------------------------------------------- 1 | #ifndef SEARCH_H 2 | #define SEARCH_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #ifdef _WIN32 13 | #include 14 | #else 15 | #include 16 | #endif 17 | #include 18 | #include 19 | 20 | #include "config.h" 21 | 22 | #ifdef HAVE_PTHREAD_H 23 | #include 24 | #endif 25 | 26 | #include "decompress.h" 27 | #include "ignore.h" 28 | #include "log.h" 29 | #include "options.h" 30 | #include "print.h" 31 | #include "uthash.h" 32 | #include "util.h" 33 | 34 | extern size_t alpha_skip_lookup[256]; 35 | extern size_t *find_skip_lookup; 36 | extern uint8_t h_table[H_SIZE] __attribute__((aligned(64))); 37 | 38 | struct work_queue_t { 39 | char *path; 40 | struct work_queue_t *next; 41 | }; 42 | typedef struct work_queue_t work_queue_t; 43 | 44 | extern work_queue_t *work_queue; 45 | extern work_queue_t *work_queue_tail; 46 | extern int done_adding_files; 47 | extern pthread_cond_t files_ready; 48 | extern pthread_mutex_t stats_mtx; 49 | extern pthread_mutex_t work_queue_mtx; 50 | 51 | 52 | /* For symlink loop detection */ 53 | #define SYMLOOP_ERROR (-1) 54 | #define SYMLOOP_OK (0) 55 | #define SYMLOOP_LOOP (1) 56 | 57 | typedef struct { 58 | dev_t dev; 59 | ino_t ino; 60 | } dirkey_t; 61 | 62 | typedef struct { 63 | dirkey_t key; 64 | UT_hash_handle hh; 65 | } symdir_t; 66 | 67 | extern symdir_t *symhash; 68 | 69 | ssize_t search_buf(const char *buf, const size_t buf_len, 70 | const char *dir_full_path); 71 | ssize_t search_stream(FILE *stream, const char *path); 72 | void search_file(const char *file_full_path); 73 | 74 | void *search_file_worker(void *i); 75 | 76 | void search_dir(ignores *ig, const char *base_path, const char *path, const int depth, dev_t original_dev); 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "config.h" 12 | #include "log.h" 13 | #include "options.h" 14 | 15 | extern FILE *out_fd; 16 | 17 | #ifndef TRUE 18 | #define TRUE 1 19 | #endif 20 | 21 | #ifndef FALSE 22 | #define FALSE 0 23 | #endif 24 | 25 | #define H_SIZE (64 * 1024) 26 | 27 | #ifdef __clang__ 28 | #define NO_SANITIZE_ALIGNMENT __attribute__((no_sanitize("alignment"))) 29 | #else 30 | #define NO_SANITIZE_ALIGNMENT 31 | #endif 32 | 33 | void *ag_malloc(size_t size); 34 | void *ag_realloc(void *ptr, size_t size); 35 | void *ag_calloc(size_t nelem, size_t elsize); 36 | char *ag_strdup(const char *s); 37 | char *ag_strndup(const char *s, size_t size); 38 | 39 | typedef struct { 40 | size_t start; /* Byte at which the match starts */ 41 | size_t end; /* and where it ends */ 42 | } match_t; 43 | 44 | typedef struct { 45 | size_t total_bytes; 46 | size_t total_files; 47 | size_t total_matches; 48 | size_t total_file_matches; 49 | struct timeval time_start; 50 | struct timeval time_end; 51 | } ag_stats; 52 | 53 | 54 | extern ag_stats stats; 55 | 56 | /* Union to translate between chars and words without violating strict aliasing */ 57 | typedef union { 58 | char as_chars[sizeof(uint16_t)]; 59 | uint16_t as_word; 60 | } word_t; 61 | 62 | void free_strings(char **strs, const size_t strs_len); 63 | 64 | void generate_alpha_skip(const char *find, size_t f_len, size_t skip_lookup[], const int case_sensitive); 65 | int is_prefix(const char *s, const size_t s_len, const size_t pos, const int case_sensitive); 66 | size_t suffix_len(const char *s, const size_t s_len, const size_t pos, const int case_sensitive); 67 | void generate_find_skip(const char *find, const size_t f_len, size_t **skip_lookup, const int case_sensitive); 68 | void generate_hash(const char *find, const size_t f_len, uint8_t *H, const int case_sensitive); 69 | 70 | /* max is already defined on spec-violating compilers such as MinGW */ 71 | size_t ag_max(size_t a, size_t b); 72 | size_t ag_min(size_t a, size_t b); 73 | 74 | const char *boyer_moore_strnstr(const char *s, const char *find, const size_t s_len, const size_t f_len, 75 | const size_t alpha_skip_lookup[], const size_t *find_skip_lookup, const int case_insensitive); 76 | const char *hash_strnstr(const char *s, const char *find, const size_t s_len, const size_t f_len, uint8_t *h_table, const int case_sensitive); 77 | 78 | size_t invert_matches(const char *buf, const size_t buf_len, match_t matches[], size_t matches_len); 79 | void realloc_matches(match_t **matches, size_t *matches_size, size_t matches_len); 80 | void compile_study(pcre **re, pcre_extra **re_extra, char *q, const int pcre_opts, const int study_opts); 81 | 82 | 83 | int is_binary(const void *buf, const size_t buf_len); 84 | int is_regex(const char *query); 85 | int is_fnmatch(const char *filename); 86 | int binary_search(const char *needle, char **haystack, int start, int end); 87 | 88 | void init_wordchar_table(void); 89 | int is_wordchar(char ch); 90 | 91 | int is_lowercase(const char *s); 92 | 93 | int is_directory(const char *path, const struct dirent *d); 94 | int is_symlink(const char *path, const struct dirent *d); 95 | int is_named_pipe(const char *path, const struct dirent *d); 96 | 97 | void die(const char *fmt, ...); 98 | 99 | void ag_asprintf(char **ret, const char *fmt, ...); 100 | 101 | ssize_t buf_getline(const char **line, const char *buf, const size_t buf_len, const size_t buf_offset); 102 | 103 | #ifndef HAVE_FGETLN 104 | char *fgetln(FILE *fp, size_t *lenp); 105 | #endif 106 | #ifndef HAVE_GETLINE 107 | ssize_t getline(char **lineptr, size_t *n, FILE *stream); 108 | #endif 109 | #ifndef HAVE_REALPATH 110 | char *realpath(const char *path, char *resolved_path); 111 | #endif 112 | #ifndef HAVE_STRLCPY 113 | size_t strlcpy(char *dest, const char *src, size_t size); 114 | #endif 115 | #ifndef HAVE_VASPRINTF 116 | int vasprintf(char **ret, const char *fmt, va_list args); 117 | #endif 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /src/win32/config.h: -------------------------------------------------------------------------------- 1 | #define HAVE_LZMA_H 2 | #define HAVE_PTHREAD_H 3 | -------------------------------------------------------------------------------- /src/zfile.c: -------------------------------------------------------------------------------- 1 | #ifdef __FreeBSD__ 2 | #include 3 | #endif 4 | #include 5 | 6 | #ifdef __CYGWIN__ 7 | typedef _off64_t off64_t; 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "config.h" 21 | 22 | #ifdef HAVE_ERR_H 23 | #include 24 | #endif 25 | #ifdef HAVE_ZLIB_H 26 | #include 27 | #endif 28 | #ifdef HAVE_LZMA_H 29 | #include 30 | #endif 31 | 32 | #include "decompress.h" 33 | 34 | #if HAVE_FOPENCOOKIE 35 | 36 | #define min(a, b) ({ \ 37 | __typeof (a) _a = (a); \ 38 | __typeof (b) _b = (b); \ 39 | _a < _b ? _a : _b; }) 40 | 41 | static cookie_read_function_t zfile_read; 42 | static cookie_seek_function_t zfile_seek; 43 | static cookie_close_function_t zfile_close; 44 | 45 | static const cookie_io_functions_t zfile_io = { 46 | .read = zfile_read, 47 | .write = NULL, 48 | .seek = zfile_seek, 49 | .close = zfile_close, 50 | }; 51 | 52 | #define KB (1024) 53 | struct zfile { 54 | FILE *in; // Source FILE stream 55 | uint64_t logic_offset, // Logical offset in output (forward seeks) 56 | decode_offset, // Where we've decoded to 57 | actual_len; 58 | uint32_t outbuf_start; 59 | 60 | ag_compression_type ctype; 61 | 62 | union { 63 | z_stream gz; 64 | lzma_stream lzma; 65 | } stream; 66 | 67 | uint8_t inbuf[32 * KB]; 68 | uint8_t outbuf[256 * KB]; 69 | bool eof; 70 | }; 71 | 72 | #define CAVAIL_IN(c) ((c)->ctype == AG_GZIP ? (c)->stream.gz.avail_in : (c)->stream.lzma.avail_in) 73 | #define CNEXT_OUT(c) ((c)->ctype == AG_GZIP ? (c)->stream.gz.next_out : (c)->stream.lzma.next_out) 74 | 75 | static int 76 | zfile_cookie_init(struct zfile *cookie) { 77 | #ifdef HAVE_LZMA_H 78 | lzma_ret lzrc; 79 | #endif 80 | int rc; 81 | 82 | assert(cookie->logic_offset == 0); 83 | assert(cookie->decode_offset == 0); 84 | 85 | cookie->actual_len = 0; 86 | 87 | switch (cookie->ctype) { 88 | #ifdef HAVE_ZLIB_H 89 | case AG_GZIP: 90 | memset(&cookie->stream.gz, 0, sizeof cookie->stream.gz); 91 | rc = inflateInit2(&cookie->stream.gz, 32 + 15); 92 | if (rc != Z_OK) { 93 | log_err("Unable to initialize zlib: %s", zError(rc)); 94 | return EIO; 95 | } 96 | cookie->stream.gz.next_in = NULL; 97 | cookie->stream.gz.avail_in = 0; 98 | cookie->stream.gz.next_out = cookie->outbuf; 99 | cookie->stream.gz.avail_out = sizeof cookie->outbuf; 100 | break; 101 | #endif 102 | #ifdef HAVE_LZMA_H 103 | case AG_XZ: 104 | cookie->stream.lzma = (lzma_stream)LZMA_STREAM_INIT; 105 | lzrc = lzma_auto_decoder(&cookie->stream.lzma, -1, 0); 106 | if (lzrc != LZMA_OK) { 107 | log_err("Unable to initialize lzma_auto_decoder: %d", lzrc); 108 | return EIO; 109 | } 110 | cookie->stream.lzma.next_in = NULL; 111 | cookie->stream.lzma.avail_in = 0; 112 | cookie->stream.lzma.next_out = cookie->outbuf; 113 | cookie->stream.lzma.avail_out = sizeof cookie->outbuf; 114 | break; 115 | #endif 116 | default: 117 | log_err("Unsupported compression type: %d", cookie->ctype); 118 | return EINVAL; 119 | } 120 | 121 | 122 | cookie->outbuf_start = 0; 123 | cookie->eof = false; 124 | return 0; 125 | } 126 | 127 | static void 128 | zfile_cookie_cleanup(struct zfile *cookie) { 129 | switch (cookie->ctype) { 130 | #ifdef HAVE_ZLIB_H 131 | case AG_GZIP: 132 | inflateEnd(&cookie->stream.gz); 133 | break; 134 | #endif 135 | #ifdef HAVE_LZMA_H 136 | case AG_XZ: 137 | lzma_end(&cookie->stream.lzma); 138 | break; 139 | #endif 140 | default: 141 | /* Compiler false positive - unreachable. */ 142 | break; 143 | } 144 | } 145 | 146 | /* 147 | * Open compressed file 'path' as a (forward-)seekable (and rewindable), 148 | * read-only stream. 149 | */ 150 | FILE * 151 | decompress_open(int fd, const char *mode, ag_compression_type ctype) { 152 | struct zfile *cookie; 153 | FILE *res, *in; 154 | int error; 155 | 156 | cookie = NULL; 157 | in = res = NULL; 158 | if (strstr(mode, "w") || strstr(mode, "a")) { 159 | errno = EINVAL; 160 | goto out; 161 | } 162 | 163 | in = fdopen(fd, mode); 164 | if (in == NULL) 165 | goto out; 166 | 167 | /* 168 | * No validation of compression type is done -- file is assumed to 169 | * match input. In Ag, the compression type is already detected, so 170 | * that's ok. 171 | */ 172 | cookie = malloc(sizeof *cookie); 173 | if (cookie == NULL) { 174 | errno = ENOMEM; 175 | goto out; 176 | } 177 | 178 | cookie->in = in; 179 | cookie->logic_offset = 0; 180 | cookie->decode_offset = 0; 181 | cookie->ctype = ctype; 182 | 183 | error = zfile_cookie_init(cookie); 184 | if (error != 0) { 185 | errno = error; 186 | goto out; 187 | } 188 | 189 | res = fopencookie(cookie, mode, zfile_io); 190 | 191 | out: 192 | if (res == NULL) { 193 | if (in != NULL) 194 | fclose(in); 195 | if (cookie != NULL) 196 | free(cookie); 197 | } 198 | return res; 199 | } 200 | 201 | /* 202 | * Return number of bytes into buf, 0 on EOF, -1 on error. Update stream 203 | * offset. 204 | */ 205 | static ssize_t 206 | zfile_read(void *cookie_, char *buf, size_t size) { 207 | struct zfile *cookie = cookie_; 208 | size_t nb, ignorebytes; 209 | ssize_t total = 0; 210 | lzma_ret lzret; 211 | int ret; 212 | 213 | assert(size <= SSIZE_MAX); 214 | 215 | if (size == 0) 216 | return 0; 217 | 218 | if (cookie->eof) 219 | return 0; 220 | 221 | ret = Z_OK; 222 | lzret = LZMA_OK; 223 | 224 | ignorebytes = cookie->logic_offset - cookie->decode_offset; 225 | assert(ignorebytes == 0); 226 | 227 | do { 228 | size_t inflated; 229 | 230 | /* Drain output buffer first */ 231 | while (CNEXT_OUT(cookie) > 232 | &cookie->outbuf[cookie->outbuf_start]) { 233 | size_t left = CNEXT_OUT(cookie) - 234 | &cookie->outbuf[cookie->outbuf_start]; 235 | size_t ignoreskip = min(ignorebytes, left); 236 | size_t toread; 237 | 238 | if (ignoreskip > 0) { 239 | ignorebytes -= ignoreskip; 240 | left -= ignoreskip; 241 | cookie->outbuf_start += ignoreskip; 242 | cookie->decode_offset += ignoreskip; 243 | } 244 | 245 | // Ran out of output before we seek()ed up. 246 | if (ignorebytes > 0) 247 | break; 248 | 249 | toread = min(left, size); 250 | memcpy(buf, &cookie->outbuf[cookie->outbuf_start], 251 | toread); 252 | 253 | buf += toread; 254 | size -= toread; 255 | left -= toread; 256 | cookie->outbuf_start += toread; 257 | cookie->decode_offset += toread; 258 | cookie->logic_offset += toread; 259 | total += toread; 260 | 261 | if (size == 0) 262 | break; 263 | } 264 | 265 | if (size == 0) 266 | break; 267 | 268 | /* 269 | * If we have not satisfied read, the output buffer must be 270 | * empty. 271 | */ 272 | assert(cookie->stream.gz.next_out == 273 | &cookie->outbuf[cookie->outbuf_start]); 274 | 275 | if ((cookie->ctype == AG_XZ && lzret == LZMA_STREAM_END) || 276 | (cookie->ctype == AG_GZIP && ret == Z_STREAM_END)) { 277 | cookie->eof = true; 278 | break; 279 | } 280 | 281 | /* Read more input if empty */ 282 | if (CAVAIL_IN(cookie) == 0) { 283 | nb = fread(cookie->inbuf, 1, sizeof cookie->inbuf, 284 | cookie->in); 285 | if (ferror(cookie->in)) { 286 | warn("error read core"); 287 | exit(1); 288 | } 289 | if (nb == 0 && feof(cookie->in)) { 290 | warn("truncated file"); 291 | exit(1); 292 | } 293 | if (cookie->ctype == AG_XZ) { 294 | cookie->stream.lzma.avail_in = nb; 295 | cookie->stream.lzma.next_in = cookie->inbuf; 296 | } else { 297 | cookie->stream.gz.avail_in = nb; 298 | cookie->stream.gz.next_in = cookie->inbuf; 299 | } 300 | } 301 | 302 | /* Reset stream state to beginning of output buffer */ 303 | if (cookie->ctype == AG_XZ) { 304 | cookie->stream.lzma.next_out = cookie->outbuf; 305 | cookie->stream.lzma.avail_out = sizeof cookie->outbuf; 306 | } else { 307 | cookie->stream.gz.next_out = cookie->outbuf; 308 | cookie->stream.gz.avail_out = sizeof cookie->outbuf; 309 | } 310 | cookie->outbuf_start = 0; 311 | 312 | if (cookie->ctype == AG_GZIP) { 313 | ret = inflate(&cookie->stream.gz, Z_NO_FLUSH); 314 | if (ret != Z_OK && ret != Z_STREAM_END) { 315 | log_err("Found mem/data error while decompressing zlib stream: %s", zError(ret)); 316 | return -1; 317 | } 318 | } else { 319 | lzret = lzma_code(&cookie->stream.lzma, LZMA_RUN); 320 | if (lzret != LZMA_OK && lzret != LZMA_STREAM_END) { 321 | log_err("Found mem/data error while decompressing xz/lzma stream: %d", lzret); 322 | return -1; 323 | } 324 | } 325 | inflated = CNEXT_OUT(cookie) - &cookie->outbuf[0]; 326 | cookie->actual_len += inflated; 327 | } while (!ferror(cookie->in) && size > 0); 328 | 329 | assert(total <= SSIZE_MAX); 330 | return total; 331 | } 332 | 333 | static int 334 | zfile_seek(void *cookie_, off64_t *offset_, int whence) { 335 | struct zfile *cookie = cookie_; 336 | off64_t new_offset = 0, offset = *offset_; 337 | 338 | if (whence == SEEK_SET) { 339 | new_offset = offset; 340 | } else if (whence == SEEK_CUR) { 341 | new_offset = (off64_t)cookie->logic_offset + offset; 342 | } else { 343 | /* SEEK_END not ok */ 344 | return -1; 345 | } 346 | 347 | if (new_offset < 0) 348 | return -1; 349 | 350 | /* Backward seeks to anywhere but 0 are not ok */ 351 | if (new_offset < (off64_t)cookie->logic_offset && new_offset != 0) { 352 | return -1; 353 | } 354 | 355 | if (new_offset == 0) { 356 | /* rewind(3) */ 357 | cookie->decode_offset = 0; 358 | cookie->logic_offset = 0; 359 | zfile_cookie_cleanup(cookie); 360 | zfile_cookie_init(cookie); 361 | } else if ((uint64_t)new_offset > cookie->logic_offset) { 362 | /* Emulate forward seek by skipping ... */ 363 | char *buf; 364 | const size_t bsz = 32 * 1024; 365 | 366 | buf = malloc(bsz); 367 | while ((uint64_t)new_offset > cookie->logic_offset) { 368 | size_t diff = min(bsz, 369 | (uint64_t)new_offset - cookie->logic_offset); 370 | ssize_t err = zfile_read(cookie_, buf, diff); 371 | if (err < 0) { 372 | free(buf); 373 | return -1; 374 | } 375 | 376 | /* Seek past EOF gets positioned at EOF */ 377 | if (err == 0) { 378 | assert(cookie->eof); 379 | new_offset = cookie->logic_offset; 380 | break; 381 | } 382 | } 383 | free(buf); 384 | } 385 | 386 | assert(cookie->logic_offset == (uint64_t)new_offset); 387 | 388 | *offset_ = new_offset; 389 | return 0; 390 | } 391 | 392 | static int 393 | zfile_close(void *cookie_) { 394 | struct zfile *cookie = cookie_; 395 | 396 | zfile_cookie_cleanup(cookie); 397 | fclose(cookie->in); 398 | free(cookie); 399 | 400 | return 0; 401 | } 402 | 403 | #endif /* HAVE_FOPENCOOKIE */ 404 | -------------------------------------------------------------------------------- /tests/adjacent_matches.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ alias ag="$TESTDIR/../ag --noaffinity --workers=1 --parallel --color" 5 | $ printf 'blahfoofooblah\n' > ./fooblah.txt 6 | 7 | Highlights are adjacent: 8 | 9 | $ ag --no-numbers foo 10 | \x1b[1;32mfooblah.txt\x1b[0m\x1b[K:blah\x1b[30;43mfoo\x1b[0m\x1b[K\x1b[30;43mfoo\x1b[0m\x1b[Kblah (esc) 11 | -------------------------------------------------------------------------------- /tests/bad_path.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | 5 | Complain about nonexistent path: 6 | 7 | $ ag foo doesnt_exist 8 | ERR: Error stat()ing: doesnt_exist 9 | ERR: Error opening directory doesnt_exist: No such file or directory 10 | [1] 11 | -------------------------------------------------------------------------------- /tests/big/big_file.t: -------------------------------------------------------------------------------- 1 | Setup and create really big file: 2 | 3 | $ . $TESTDIR/../setup.sh 4 | $ python3 $TESTDIR/create_big_file.py $TESTDIR/big_file.txt 5 | 6 | Search a big file: 7 | 8 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel hello $TESTDIR/big_file.txt 9 | 33554432:hello1073741824 10 | 67108864:hello2147483648 11 | 100663296:hello3221225472 12 | 134217728:hello4294967296 13 | 167772160:hello5368709120 14 | 201326592:hello6442450944 15 | 234881024:hello7516192768 16 | 268435456:hello 17 | 18 | Fail to regex search a big file: 19 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel 'hello.*' $TESTDIR/big_file.txt 20 | ERR: Skipping */big_file.txt: pcre_exec() can't handle files larger than 2147483647 bytes. (glob) 21 | [1] 22 | -------------------------------------------------------------------------------- /tests/big/create_big_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Create an 8GB file of mostly "abcdefghijklmnopqrstuvwxyz01234", 4 | # with a few instances of "hello" 5 | 6 | import sys 7 | 8 | if len(sys.argv) != 2: 9 | print("Usage: %s big_file.txt" % sys.argv[0]) 10 | sys.exit(1) 11 | 12 | big_file = sys.argv[1] 13 | 14 | 15 | def create_big_file(): 16 | with open(big_file, "w") as fd: 17 | for i in range(1, 2**28): 18 | byte = i * 32 19 | if byte % 2**30 == 0: 20 | fd.write("hello%s\n" % byte) 21 | else: 22 | fd.write("abcdefghijklmnopqrstuvwxyz01234\n") 23 | fd.write("hello\n") 24 | 25 | 26 | try: 27 | fd = open(big_file, "r") 28 | except Exception as e: 29 | create_big_file() 30 | -------------------------------------------------------------------------------- /tests/case_sensitivity.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'Foo\n' >> ./sample 5 | $ printf 'bar\n' >> ./sample 6 | 7 | Smart case by default: 8 | 9 | $ ag foo sample 10 | 1:Foo 11 | $ ag FOO sample 12 | [1] 13 | $ ag 'f.o' sample 14 | 1:Foo 15 | $ ag Foo sample 16 | 1:Foo 17 | $ ag 'F.o' sample 18 | 1:Foo 19 | 20 | Case sensitive mode: 21 | 22 | $ ag -s foo sample 23 | [1] 24 | $ ag -s FOO sample 25 | [1] 26 | $ ag -s 'f.o' sample 27 | [1] 28 | $ ag -s Foo sample 29 | 1:Foo 30 | $ ag -s 'F.o' sample 31 | 1:Foo 32 | 33 | Case insensitive mode: 34 | 35 | $ ag fOO -i sample 36 | 1:Foo 37 | $ ag fOO --ignore-case sample 38 | 1:Foo 39 | $ ag 'f.o' -i sample 40 | 1:Foo 41 | 42 | Case insensitive file regex 43 | 44 | $ ag -i -g 'Samp.*' 45 | sample 46 | -------------------------------------------------------------------------------- /tests/color.t: -------------------------------------------------------------------------------- 1 | Setup. Note that we have to turn --color on manually since ag detects that 2 | stdout isn't a tty when running in cram. 3 | 4 | $ . $TESTDIR/setup.sh 5 | $ alias ag="$TESTDIR/../ag --noaffinity --workers=1 --parallel --color" 6 | $ printf 'foo\n' > ./blah.txt 7 | $ printf 'bar\n' >> ./blah.txt 8 | 9 | Matches should contain colors: 10 | 11 | $ ag --no-numbers foo blah.txt 12 | \x1b[30;43mfoo\x1b[0m\x1b[K (esc) 13 | 14 | --nocolor should suppress colors: 15 | 16 | $ ag --nocolor foo blah.txt 17 | 1:foo 18 | 19 | --invert-match should suppress colors: 20 | 21 | $ ag --invert-match foo blah.txt 22 | 2:bar 23 | 24 | -v is the same as --invert-match 25 | 26 | $ ag -v foo blah.txt 27 | 2:bar 28 | -------------------------------------------------------------------------------- /tests/column.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf "blah\nblah2\n" > blah.txt 5 | 6 | Ensure column is correct: 7 | 8 | $ ag --column "blah\nb" 9 | blah.txt:1:1:blah 10 | blah.txt:2:0:blah2 11 | 12 | # Test ackmate output. Not quite right, but at least offsets are in the 13 | # ballpark instead of being 9 quintillion 14 | 15 | $ ag --ackmate "lah\nb" 16 | :blah.txt 17 | 1;blah 18 | 2;1 5:blah2 19 | -------------------------------------------------------------------------------- /tests/count.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ unalias ag 5 | $ alias ag="$TESTDIR/../ag --noaffinity --nocolor --workers=1" 6 | $ printf "blah\n" > blah.txt 7 | $ printf "blah2\n" >> blah.txt 8 | $ printf "blah_OTHER\n" > other_file.txt 9 | $ printf "blah_OTHER\n" >> other_file.txt 10 | 11 | Count matches: 12 | 13 | $ ag --count --parallel blah | sort 14 | blah.txt:2 15 | other_file.txt:2 16 | 17 | Count stream matches: 18 | 19 | $ printf 'blah blah blah\n' | ag --count blah 20 | 3 21 | 22 | Count stream matches per line (not very useful since it does not print zero): 23 | 24 | $ cat blah.txt | ag --count blah 25 | 1 26 | 1 27 | -------------------------------------------------------------------------------- /tests/ds_store_ignore.t: -------------------------------------------------------------------------------- 1 | Setup. 2 | $ . $TESTDIR/setup.sh 3 | $ mkdir -p dir0/dir1/dir2 4 | $ printf '*.DS_Store\n' > dir0/.ignore 5 | $ printf 'blah\n' > dir0/dir1/dir2/blah.txt 6 | $ touch dir0/dir1/.DS_Store 7 | 8 | Find blah in blah.txt 9 | 10 | $ ag blah 11 | dir0/dir1/dir2/blah.txt:1:blah 12 | -------------------------------------------------------------------------------- /tests/empty_environment.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf "hello world\n" >test.txt 5 | 6 | Verify ag runs with an empty environment: 7 | 8 | $ env -i $TESTDIR/../ag --noaffinity --nocolor --workers=1 --parallel hello 9 | test.txt:1:hello world 10 | -------------------------------------------------------------------------------- /tests/empty_match.t: -------------------------------------------------------------------------------- 1 | Setup. 2 | $ . $TESTDIR/setup.sh 3 | $ touch empty.txt 4 | $ printf 'foo\n' > nonempty.txt 5 | 6 | Zero-length match on an empty file should fail silently with return code 1 7 | 8 | $ ag "^" empty.txt 9 | [1] 10 | 11 | A genuine zero-length match should succeed: 12 | $ ag "^" nonempty.txt 13 | 1:foo 14 | 15 | Empty files should be listed with --unrestricted --files-with-matches (-ul) 16 | $ ag -lu --stats | sed '$d' | sort # Remove the last line about timing which will differ 17 | 2 files contained matches 18 | 2 files searched 19 | 2 matches 20 | 4 bytes searched 21 | empty.txt 22 | nonempty.txt 23 | -------------------------------------------------------------------------------- /tests/exitcodes.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'foo\n' > ./exitcodes_test.txt 5 | $ printf 'bar\n' >> ./exitcodes_test.txt 6 | 7 | Normal matching: 8 | 9 | $ ag foo exitcodes_test.txt 10 | 1:foo 11 | $ ag zoo exitcodes_test.txt 12 | [1] 13 | 14 | Inverted matching: 15 | 16 | $ ag -v foo exitcodes_test.txt 17 | 2:bar 18 | $ ag -v zoo exitcodes_test.txt 19 | 1:foo 20 | 2:bar 21 | $ ag -v "foo|bar" exitcodes_test.txt 22 | [1] 23 | -------------------------------------------------------------------------------- /tests/fail/unicode_case_insensitive.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/../setup.sh 4 | $ printf "hello=你好\n" > test.txt 5 | $ printf "hello=你好\n" >> test.txt 6 | 7 | Normal search: 8 | 9 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel 你好 10 | test.txt:1:hello=你好 11 | test.txt:2:hello=你好 12 | 13 | Case-insensitive search: 14 | 15 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel -i 你好 16 | test.txt:1:hello=你好 17 | test.txt:2:hello=你好 18 | -------------------------------------------------------------------------------- /tests/fail/unicode_case_insensitive.t.err: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/../setup.sh 4 | $ printf "hello=你好\n" > test.txt 5 | $ printf "hello=你好\n" >> test.txt 6 | 7 | Normal search: 8 | 9 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel 你好 10 | test.txt:1:hello=\xe4\xbd\xa0\xe5\xa5\xbd (esc) 11 | test.txt:2:hello=\xe4\xbd\xa0\xe5\xa5\xbd (esc) 12 | 13 | Case-insensitive search: 14 | 15 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel -i 你好 16 | test.txt:1:hello=\xe4\xbd\xa0\xe5\xa5\xbd (esc) 17 | test.txt:2:hello=\xe4\xbd\xa0\xe5\xa5\xbd (esc) 18 | -------------------------------------------------------------------------------- /tests/files_with_matches.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'foo\n' > ./foo.txt 5 | $ printf 'bar\n' > ./bar.txt 6 | $ printf 'foo\nbar\nbaz\n' > ./baz.txt 7 | $ printf 'duck\nanother duck\nyet another duck\n' > ./duck.txt 8 | $ cp duck.txt goose.txt 9 | $ echo "GOOSE!!!" >> ./goose.txt 10 | 11 | Files with matches: 12 | 13 | $ ag --files-with-matches foo foo.txt 14 | foo.txt 15 | $ ag --files-with-matches foo foo.txt bar.txt 16 | foo.txt 17 | $ ag --files-with-matches foo bar.txt 18 | [1] 19 | $ ag --files-with-matches foo foo.txt bar.txt baz.txt 20 | foo.txt 21 | baz.txt 22 | $ ag --files-with-matches bar foo.txt bar.txt baz.txt 23 | bar.txt 24 | baz.txt 25 | $ ag --files-with-matches foo bar.txt baz.txt 26 | baz.txt 27 | 28 | Files without matches: 29 | (Prints names of files in which no line matches query) 30 | 31 | $ ag --files-without-matches bar foo.txt 32 | foo.txt 33 | $ ag --files-without-matches bar foo.txt bar.txt 34 | foo.txt 35 | $ ag --files-without-matches bar bar.txt 36 | [1] 37 | $ ag --files-without-matches foo foo.txt bar.txt baz.txt 38 | bar.txt 39 | $ ag --files-without-matches bar foo.txt bar.txt baz.txt 40 | foo.txt 41 | 42 | Files with inverted matches: 43 | (Prints names of files in which some line doesn't match query) 44 | 45 | $ ag --files-with-matches --invert-match bar bar.txt 46 | [1] 47 | $ ag --files-with-matches --invert-match foo foo.txt bar.txt baz.txt 48 | bar.txt 49 | baz.txt 50 | $ ag --files-with-matches --invert-match bar foo.txt bar.txt baz.txt 51 | foo.txt 52 | baz.txt 53 | 54 | Files without inverted matches: 55 | (Prints names of files in which no line doesn't match query, 56 | i.e. where every line matches query) 57 | 58 | $ ag --files-without-matches --invert-match duck duck.txt 59 | duck.txt 60 | $ ag --files-without-matches --invert-match duck goose.txt 61 | [1] 62 | $ ag --files-without-matches --invert-match duck duck.txt goose.txt 63 | duck.txt 64 | -------------------------------------------------------------------------------- /tests/filetype.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ TEST_FILETYPE_EXT1=`ag --list-file-types | grep -E '^[ \t]+\..+' | head -n 1 | awk '{ print $1 }'` 5 | $ TEST_FILETYPE_EXT2=`ag --list-file-types | grep -E '^[ \t]+\..+' | tail -n 1 | awk '{ print $1 }'` 6 | $ TEST_FILETYPE_DIR=filetype_test 7 | $ mkdir $TEST_FILETYPE_DIR 8 | $ printf "This is filetype test1.\n" > $TEST_FILETYPE_DIR/test.$TEST_FILETYPE_EXT1 9 | $ printf "This is filetype test2.\n" > $TEST_FILETYPE_DIR/test.$TEST_FILETYPE_EXT2 10 | 11 | Match only top file type: 12 | 13 | $ TEST_FILETYPE_OPTION=`ag --list-file-types | grep -E '^[ \t]+--.+' | head -n 1 | awk '{ print $1 }'` 14 | $ ag 'This is filetype test' --nofilename $TEST_FILETYPE_OPTION $TEST_FILETYPE_DIR 15 | This is filetype test1. 16 | -------------------------------------------------------------------------------- /tests/hidden_option.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir hidden_bug 5 | $ cd hidden_bug 6 | $ printf "test\n" > a.txt 7 | $ git init --quiet 8 | $ if [ ! -d .git/info ] ; then mkdir .git/info ; fi 9 | $ printf "a.txt\n" > .git/info/exclude 10 | 11 | $ ag --ignore-dir .git test 12 | [1] 13 | 14 | $ ag --hidden --ignore-dir .git test 15 | [1] 16 | 17 | $ ag -U --ignore-dir .git test 18 | a.txt:1:test 19 | 20 | $ ag --hidden -U --ignore-dir .git test 21 | a.txt:1:test 22 | 23 | $ mkdir -p ./.hidden 24 | $ printf 'whatever\n' > ./.hidden/a.txt 25 | 26 | $ ag whatever 27 | [1] 28 | 29 | $ ag --hidden whatever 30 | [1] 31 | 32 | $ printf "\n" > .git/info/exclude 33 | 34 | $ ag whatever 35 | [1] 36 | 37 | $ ag --hidden whatever 38 | .hidden/a.txt:1:whatever 39 | -------------------------------------------------------------------------------- /tests/ignore_abs_path.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p ./a/b/c 5 | $ printf 'whatever1\n' > ./a/b/c/blah.yml 6 | $ printf 'whatever2\n' > ./a/b/foo.yml 7 | $ printf '/a/b/foo.yml\n' > ./.ignore 8 | 9 | Ignore foo.yml but not blah.yml: 10 | 11 | $ ag whatever . 12 | a/b/c/blah.yml:1:whatever1 13 | 14 | Dont ignore anything (unrestricted search): 15 | 16 | $ ag -u whatever . | sort 17 | a/b/c/blah.yml:1:whatever1 18 | a/b/foo.yml:1:whatever2 19 | 20 | Ignore foo.yml given an absolute search path [#448]: 21 | 22 | $ ag whatever $(pwd) 23 | /.*/a/b/c/blah.yml:1:whatever1 (re) 24 | -------------------------------------------------------------------------------- /tests/ignore_absolute_search_path_with_glob.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p parent/multi-part 5 | $ printf 'match1\n' > parent/multi-part/file1.txt 6 | $ printf 'parent/multi-*\n' > .ignore 7 | 8 | # Ignore directory specified by glob: 9 | 10 | # $ ag match . 11 | # [1] 12 | 13 | # Ignore directory specified by glob with absolute search path (#448): 14 | 15 | # $ ag match $(pwd) 16 | # [1] 17 | -------------------------------------------------------------------------------- /tests/ignore_backups.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p ./a/b/c 5 | $ printf 'whatever1\n' > ./a/b/c/foo.yml 6 | $ printf 'whatever2\n' > ./a/b/c/foo.yml~ 7 | $ printf 'whatever3\n' > ./a/b/c/.foo.yml.swp 8 | $ printf 'whatever4\n' > ./a/b/c/.foo.yml.swo 9 | $ printf 'whatever5\n' > ./a/b/foo.yml 10 | $ printf 'whatever6\n' > ./a/b/foo.yml~ 11 | $ printf 'whatever7\n' > ./a/b/.foo.yml.swp 12 | $ printf 'whatever8\n' > ./a/b/.foo.yml.swo 13 | $ printf 'whatever9\n' > ./a/foo.yml 14 | $ printf 'whatever10\n' > ./a/foo.yml~ 15 | $ printf 'whatever11\n' > ./a/.foo.yml.swp 16 | $ printf 'whatever12\n' > ./a/.foo.yml.swo 17 | $ printf 'whatever13\n' > ./foo.yml 18 | $ printf 'whatever14\n' > ./foo.yml~ 19 | $ printf 'whatever15\n' > ./.foo.yml.swp 20 | $ printf 'whatever16\n' > ./.foo.yml.swo 21 | $ printf '*~\n' > ./.ignore 22 | $ printf '*.sw[po]\n' >> ./.ignore 23 | 24 | Ignore all files except foo.yml 25 | 26 | $ ag whatever . | sort 27 | a/b/c/foo.yml:1:whatever1 28 | a/b/foo.yml:1:whatever5 29 | a/foo.yml:1:whatever9 30 | foo.yml:1:whatever13 31 | 32 | Dont ignore anything (unrestricted search): 33 | 34 | $ ag -u whatever . | sort 35 | .foo.yml.swo:1:whatever16 36 | .foo.yml.swp:1:whatever15 37 | a/.foo.yml.swo:1:whatever12 38 | a/.foo.yml.swp:1:whatever11 39 | a/b/.foo.yml.swo:1:whatever8 40 | a/b/.foo.yml.swp:1:whatever7 41 | a/b/c/.foo.yml.swo:1:whatever4 42 | a/b/c/.foo.yml.swp:1:whatever3 43 | a/b/c/foo.yml:1:whatever1 44 | a/b/c/foo.yml~:1:whatever2 45 | a/b/foo.yml:1:whatever5 46 | a/b/foo.yml~:1:whatever6 47 | a/foo.yml:1:whatever9 48 | a/foo.yml~:1:whatever10 49 | foo.yml:1:whatever13 50 | foo.yml~:1:whatever14 51 | -------------------------------------------------------------------------------- /tests/ignore_examine_parent_ignorefiles.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p subdir 5 | $ printf 'match1\n' > subdir/file1.txt 6 | $ printf 'file1.txt\n' > .ignore 7 | 8 | Ignore directory specified by name: 9 | 10 | $ ag match 11 | [1] 12 | 13 | # Ignore directory specified by name in parent directory when using path (#144): 14 | 15 | # $ ag match subdir 16 | # [1] 17 | 18 | # Ignore directory specified by name in parent directory when using current directory (#144): 19 | 20 | # $ cd subdir 21 | # $ ag match 22 | # [1] 23 | -------------------------------------------------------------------------------- /tests/ignore_extensions.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf '*.js\n' > .ignore 5 | $ printf '*.test.txt\n' >> .ignore 6 | $ printf 'targetA\n' > something.js 7 | $ printf 'targetB\n' > aFile.test.txt 8 | $ printf 'targetC\n' > aFile.txt 9 | $ printf 'targetG\n' > something.min.js 10 | $ mkdir -p subdir 11 | $ printf 'targetD\n' > subdir/somethingElse.js 12 | $ printf 'targetE\n' > subdir/anotherFile.test.txt 13 | $ printf 'targetF\n' > subdir/anotherFile.txt 14 | $ printf 'targetH\n' > subdir/somethingElse.min.js 15 | 16 | Ignore patterns with single extension in root directory: 17 | 18 | $ ag "targetA" 19 | [1] 20 | 21 | Ignore patterns with multiple extensions in root directory: 22 | 23 | $ ag "targetB" 24 | [1] 25 | 26 | *.js ignores *.min.js in root directory: 27 | 28 | $ ag "targetG" 29 | [1] 30 | 31 | Do not ignore patterns with partial extensions in root directory: 32 | 33 | $ ag "targetC" 34 | aFile.txt:1:targetC 35 | 36 | Ignore patterns with single extension in subdirectory: 37 | 38 | $ ag "targetD" 39 | [1] 40 | 41 | Ignore patterns with multiple extensions in subdirectory: 42 | 43 | $ ag "targetE" 44 | [1] 45 | 46 | *.js ignores *.min.js in subdirectory: 47 | 48 | $ ag "targetH" 49 | [1] 50 | 51 | Do not ignore patterns with partial extensions in subdirectory: 52 | 53 | $ ag "targetF" 54 | subdir/anotherFile.txt:1:targetF 55 | -------------------------------------------------------------------------------- /tests/ignore_gitignore.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ export HOME=$PWD 5 | $ printf '[core]\nexcludesfile = ~/.gitignore.global' >> $HOME/.gitconfig 6 | $ printf 'PATTERN_MARKER\n' > .gitignore.global 7 | 8 | Test that the ignore pattern got picked up: 9 | 10 | $ ag --debug . | grep PATTERN_MARKER 11 | DEBUG: added ignore pattern PATTERN_MARKER to root ignores 12 | 13 | -------------------------------------------------------------------------------- /tests/ignore_invert.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'blah1\n' > ./printme.txt 5 | $ printf 'blah2\n' > ./dontprintme.c 6 | $ printf '*\n' > ./.ignore 7 | $ printf '!*.txt\n' >> ./.ignore 8 | 9 | Ignore .gitignore patterns but not .ignore patterns: 10 | 11 | $ ag blah 12 | printme.txt:1:blah1 13 | -------------------------------------------------------------------------------- /tests/ignore_pattern_in_subdirectory.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir subdir 5 | $ printf 'first\n' > file1.txt 6 | $ printf 'second\n' > subdir/file2.txt 7 | $ printf '*.txt\n' > .gitignore 8 | 9 | Ignore file based on extension match: 10 | 11 | $ ag first 12 | [1] 13 | 14 | Ignore file in subdirectory based on extension match (#442): 15 | 16 | $ ag second 17 | [1] 18 | -------------------------------------------------------------------------------- /tests/ignore_slash_in_subdir.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p subdir/ignoredir 5 | $ mkdir ignoredir 6 | $ printf 'match1\n' > subdir/ignoredir/file1.txt 7 | $ printf 'match1\n' > ignoredir/file1.txt 8 | $ printf '/ignoredir\n' > subdir/.ignore 9 | 10 | Ignore file in subdir/ignoredir, but not in ignoredir: 11 | 12 | $ ag match 13 | ignoredir/file1.txt:1:match1 14 | 15 | From subdir, ignore file in subdir/ignoredir: 16 | 17 | $ cd subdir 18 | $ ag match 19 | [1] 20 | -------------------------------------------------------------------------------- /tests/ignore_subdir.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p ./a/b/c 5 | $ printf 'whatever1\n' > ./a/b/c/blah.yml 6 | $ printf 'whatever2\n' > ./a/b/foo.yml 7 | $ printf 'a/b/foo.yml\n' > ./.gitignore 8 | # TODO: have this work instead of the above 9 | # $ printf 'a/b/*.yml\n' > ./.gitignore 10 | 11 | Ignore foo.yml but not blah.yml: 12 | 13 | $ ag whatever . 14 | a/b/c/blah.yml:1:whatever1 15 | 16 | Dont ignore anything (unrestricted search): 17 | 18 | $ ag -u whatever . | sort 19 | a/b/c/blah.yml:1:whatever1 20 | a/b/foo.yml:1:whatever2 21 | -------------------------------------------------------------------------------- /tests/ignore_vcs.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'whatever1\n' > ./always.txt 5 | $ printf 'whatever2\n' > ./git.txt 6 | $ printf 'whatever3\n' > ./text.txt 7 | $ printf 'git.txt\n' > ./.gitignore 8 | $ printf 'text.*\n' > ./.ignore 9 | 10 | Obey .gitignore and .ignore patterns: 11 | 12 | $ ag whatever . 13 | always.txt:1:whatever1 14 | 15 | Ignore .gitignore patterns but not .ignore patterns: 16 | 17 | $ ag -U whatever . | sort 18 | always.txt:1:whatever1 19 | git.txt:1:whatever2 20 | -------------------------------------------------------------------------------- /tests/invert_match.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'valid: 1\n' > ./blah.txt 5 | $ printf 'some_string\n' >> ./blah.txt 6 | $ printf 'valid: 654\n' >> ./blah.txt 7 | $ printf 'some_other_string\n' >> ./blah.txt 8 | $ printf 'valid: 0\n' >> ./blah.txt 9 | $ printf 'valid: 23\n' >> ./blah.txt 10 | $ printf 'valid: 0\n' >> ./blah.txt 11 | 12 | Search for lines not matching "valid: 0" in blah.txt: 13 | 14 | $ ag -v 'valid: ' 15 | blah.txt:2:some_string 16 | blah.txt:4:some_other_string 17 | -------------------------------------------------------------------------------- /tests/is_binary.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggreer/the_silver_searcher/a61f1780b64266587e7bc30f0f5f71c6cca97c0f/tests/is_binary.pdf -------------------------------------------------------------------------------- /tests/is_binary_pdf.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ cp $TESTDIR/is_binary.pdf . 5 | 6 | PDF files are binary. Do not search them by default: 7 | 8 | $ ag PDF 9 | [1] 10 | 11 | OK, search binary files 12 | 13 | $ ag --search-binary PDF 14 | Binary file is_binary.pdf matches. 15 | -------------------------------------------------------------------------------- /tests/line_width.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf "12345678901234567890123456789012345678901234567890\n" >> ./blah.txt 5 | 6 | Truncate to width inside input line length: 7 | 8 | $ ag -W 20 1 < ./blah.txt 9 | blah.txt:1:12345678901234567890 [...] 10 | 11 | Truncate to width inside input line length, long-form: 12 | 13 | $ ag --width 20 1 < ./blah.txt 14 | blah.txt:1:12345678901234567890 [...] 15 | 16 | Truncate to width outside input line length: 17 | 18 | $ ag -W 60 1 < ./blah.txt 19 | blah.txt:1:12345678901234567890123456789012345678901234567890 20 | 21 | Truncate to width one less than input line length: 22 | 23 | $ ag -W 49 1 < ./blah.txt 24 | blah.txt:1:1234567890123456789012345678901234567890123456789 [...] 25 | 26 | Truncate to width exactly input line length: 27 | 28 | $ ag -W 50 1 < ./blah.txt 29 | blah.txt:1:12345678901234567890123456789012345678901234567890 30 | 31 | -------------------------------------------------------------------------------- /tests/list_file_types.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | 5 | Language types are output: 6 | 7 | $ ag --list-file-types 8 | The following file types are supported: 9 | --actionscript 10 | .as .mxml 11 | 12 | --ada 13 | .ada .adb .ads 14 | 15 | --asciidoc 16 | .adoc .ad .asc .asciidoc 17 | 18 | --apl 19 | .apl 20 | 21 | --asm 22 | .asm .s 23 | 24 | --asp 25 | .asp .asa .aspx .asax .ashx .ascx .asmx 26 | 27 | --aspx 28 | .asp .asa .aspx .asax .ashx .ascx .asmx 29 | 30 | --batch 31 | .bat .cmd 32 | 33 | --bazel 34 | .bazel 35 | 36 | --bitbake 37 | .bb .bbappend .bbclass .inc 38 | 39 | --cc 40 | .c .h .xs 41 | 42 | --cfmx 43 | .cfc .cfm .cfml 44 | 45 | --chpl 46 | .chpl 47 | 48 | --clojure 49 | .clj .cljs .cljc .cljx .edn 50 | 51 | --coffee 52 | .coffee .cjsx 53 | 54 | --config 55 | .config 56 | 57 | --coq 58 | .coq .g .v 59 | 60 | --cpp 61 | .cpp .cc .C .cxx .m .hpp .hh .h .H .hxx .tpp 62 | 63 | --crystal 64 | .cr .ecr 65 | 66 | --csharp 67 | .cs 68 | 69 | --cshtml 70 | .cshtml 71 | 72 | --css 73 | .css 74 | 75 | --cython 76 | .pyx .pxd .pxi 77 | 78 | --delphi 79 | .pas .int .dfm .nfm .dof .dpk .dpr .dproj .groupproj .bdsgroup .bdsproj 80 | 81 | --dlang 82 | .d .di 83 | 84 | --dot 85 | .dot .gv 86 | 87 | --dts 88 | .dts .dtsi 89 | 90 | --ebuild 91 | .ebuild .eclass 92 | 93 | --elisp 94 | .el 95 | 96 | --elixir 97 | .ex .eex .exs 98 | 99 | --elm 100 | .elm 101 | 102 | --erlang 103 | .erl .hrl 104 | 105 | --factor 106 | .factor 107 | 108 | --fortran 109 | .f .F .f77 .f90 .F90 .f95 .f03 .for .ftn .fpp .FPP 110 | 111 | --fsharp 112 | .fs .fsi .fsx 113 | 114 | --gettext 115 | .po .pot .mo 116 | 117 | --glsl 118 | .vert .tesc .tese .geom .frag .comp 119 | 120 | --go 121 | .go 122 | 123 | --gradle 124 | .gradle 125 | 126 | --groovy 127 | .groovy .gtmpl .gpp .grunit .gradle 128 | 129 | --haml 130 | .haml 131 | 132 | --handlebars 133 | .hbs 134 | 135 | --haskell 136 | .hs .hsig .lhs 137 | 138 | --haxe 139 | .hx 140 | 141 | --hh 142 | .h 143 | 144 | --html 145 | .htm .html .shtml .xhtml 146 | 147 | --idris 148 | .idr .ipkg .lidr 149 | 150 | --ini 151 | .ini 152 | 153 | --ipython 154 | .ipynb 155 | 156 | --isabelle 157 | .thy 158 | 159 | --j 160 | .ijs 161 | 162 | --jade 163 | .jade 164 | 165 | --java 166 | .java .properties 167 | 168 | --jinja2 169 | .j2 170 | 171 | --js 172 | .es6 .js .jsx .vue 173 | 174 | --json 175 | .json 176 | 177 | --jsp 178 | .jsp .jspx .jhtm .jhtml .jspf .tag .tagf 179 | 180 | --julia 181 | .jl 182 | 183 | --kotlin 184 | .kt 185 | 186 | --less 187 | .less 188 | 189 | --liquid 190 | .liquid 191 | 192 | --lisp 193 | .lisp .lsp 194 | 195 | --log 196 | .log 197 | 198 | --lua 199 | .lua 200 | 201 | --m4 202 | .m4 203 | 204 | --make 205 | .Makefiles .mk .mak 206 | 207 | --mako 208 | .mako 209 | 210 | --markdown 211 | .markdown .mdown .mdwn .mkdn .mkd .md 212 | 213 | --mason 214 | .mas .mhtml .mpl .mtxt 215 | 216 | --matlab 217 | .m 218 | 219 | --mathematica 220 | .m .wl 221 | 222 | --md 223 | .markdown .mdown .mdwn .mkdn .mkd .md 224 | 225 | --mercury 226 | .m .moo 227 | 228 | --naccess 229 | .asa .rsa 230 | 231 | --nim 232 | .nim 233 | 234 | --nix 235 | .nix 236 | 237 | --objc 238 | .m .h 239 | 240 | --objcpp 241 | .mm .h 242 | 243 | --ocaml 244 | .ml .mli .mll .mly 245 | 246 | --octave 247 | .m 248 | 249 | --org 250 | .org 251 | 252 | --parrot 253 | .pir .pasm .pmc .ops .pod .pg .tg 254 | 255 | --pdb 256 | .pdb 257 | 258 | --perl 259 | .pl .pm .pm6 .pod .t 260 | 261 | --php 262 | .php .phpt .php3 .php4 .php5 .phtml 263 | 264 | --pike 265 | .pike .pmod 266 | 267 | --plist 268 | .plist 269 | 270 | --plone 271 | .pt .cpt .metadata .cpy .py .xml .zcml 272 | 273 | --powershell 274 | .ps1 275 | 276 | --proto 277 | .proto 278 | 279 | --ps1 280 | .ps1 281 | 282 | --pug 283 | .pug 284 | 285 | --puppet 286 | .pp 287 | 288 | --python 289 | .py 290 | 291 | --qml 292 | .qml 293 | 294 | --racket 295 | .rkt .ss .scm 296 | 297 | --rake 298 | .Rakefile 299 | 300 | --razor 301 | .cshtml 302 | 303 | --restructuredtext 304 | .rst 305 | 306 | --rs 307 | .rs 308 | 309 | --r 310 | .r .R .Rmd .Rnw .Rtex .Rrst 311 | 312 | --rdoc 313 | .rdoc 314 | 315 | --ruby 316 | .rb .rhtml .rjs .rxml .erb .rake .spec 317 | 318 | --rust 319 | .rs 320 | 321 | --salt 322 | .sls 323 | 324 | --sass 325 | .sass .scss 326 | 327 | --scala 328 | .scala 329 | 330 | --scheme 331 | .scm .ss 332 | 333 | --shell 334 | .sh .bash .csh .tcsh .ksh .zsh .fish 335 | 336 | --smalltalk 337 | .st 338 | 339 | --sml 340 | .sml .fun .mlb .sig 341 | 342 | --sql 343 | .sql .ctl 344 | 345 | --stata 346 | .do .ado 347 | 348 | --stylus 349 | .styl 350 | 351 | --swift 352 | .swift 353 | 354 | --tcl 355 | .tcl .itcl .itk 356 | 357 | --terraform 358 | .tf .tfvars 359 | 360 | --tex 361 | .tex .cls .sty 362 | 363 | --thrift 364 | .thrift 365 | 366 | --tla 367 | .tla 368 | 369 | --tt 370 | .tt .tt2 .ttml 371 | 372 | --toml 373 | .toml 374 | 375 | --ts 376 | .ts .tsx 377 | 378 | --twig 379 | .twig 380 | 381 | --vala 382 | .vala .vapi 383 | 384 | --vb 385 | .bas .cls .frm .ctl .vb .resx 386 | 387 | --velocity 388 | .vm .vtl .vsl 389 | 390 | --verilog 391 | .v .vh .sv .svh 392 | 393 | --vhdl 394 | .vhd .vhdl 395 | 396 | --vim 397 | .vim 398 | 399 | --vue 400 | .vue 401 | 402 | --wix 403 | .wxi .wxs 404 | 405 | --wsdl 406 | .wsdl 407 | 408 | --wadl 409 | .wadl 410 | 411 | --xml 412 | .xml .dtd .xsl .xslt .xsd .ent .tld .plist .wsdl 413 | 414 | --yaml 415 | .yaml .yml 416 | 417 | --zeek 418 | .zeek .bro .bif 419 | 420 | --zephir 421 | .zep 422 | -------------------------------------------------------------------------------- /tests/literal_word_regexp.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ echo 'blah abc def' > blah1.txt 5 | $ echo 'abc blah def' > blah2.txt 6 | $ echo 'abc def blah' > blah3.txt 7 | $ echo 'abcblah def' > blah4.txt 8 | $ echo 'abc blahdef' >> blah4.txt 9 | $ echo 'blahx blah' > blah5.txt 10 | $ echo 'abcblah blah blah' > blah6.txt 11 | 12 | Match a word of the beginning: 13 | 14 | $ ag -wF --column 'blah' blah1.txt 15 | 1:1:blah abc def 16 | 17 | Match a middle word: 18 | 19 | $ ag -wF --column 'blah' blah2.txt 20 | 1:5:abc blah def 21 | 22 | Match a last word: 23 | 24 | $ ag -wF --column 'blah' blah3.txt 25 | 1:9:abc def blah 26 | 27 | No match: 28 | 29 | $ ag -wF --column 'blah' blah4.txt 30 | [1] 31 | 32 | Match: 33 | 34 | $ ag -wF --column 'blah' blah5.txt 35 | 1:7:blahx blah 36 | 37 | Case of a word repeating the same part: 38 | 39 | $ ag -wF --column 'blah blah' blah6.txt 40 | 1:9:abcblah blah blah 41 | -------------------------------------------------------------------------------- /tests/max_count.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf "blah\n" > blah.txt 5 | $ printf "blah2\n" >> blah.txt 6 | $ printf "blah2\n" > blah2.txt 7 | $ printf "blah2\n" >> blah2.txt 8 | $ printf "blah2\n" >> blah2.txt 9 | $ printf "blah2\n" >> blah2.txt 10 | $ printf "blah2\n" >> blah2.txt 11 | $ printf "blah2\n" >> blah2.txt 12 | $ printf "blah2\n" >> blah2.txt 13 | $ printf "blah2\n" >> blah2.txt 14 | $ printf "blah2\n" >> blah2.txt 15 | $ printf "blah2\n" >> blah2.txt # 10 lines 16 | 17 | Max match of 1: 18 | 19 | $ ag --max-count 1 blah blah.txt 20 | ERR: Too many matches in blah.txt. Skipping the rest of this file. 21 | 1:blah 22 | 23 | Max match of 10, one file: 24 | 25 | $ ag --count --max-count 10 blah blah2.txt 26 | ERR: Too many matches in blah2.txt. Skipping the rest of this file. 27 | 10 28 | 29 | Max match of 10, multiple files: 30 | 31 | $ ag --count --max-count 10 blah blah.txt blah2.txt 32 | ERR: Too many matches in blah2.txt. Skipping the rest of this file. 33 | blah.txt:2 34 | blah2.txt:10 35 | -------------------------------------------------------------------------------- /tests/multiline.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'what\n' > blah.txt 5 | $ printf 'ever\n' >> blah.txt 6 | $ printf 'whatever\n' >> blah.txt 7 | 8 | Multiline: 9 | 10 | $ ag 'wh[^w]+er' . 11 | blah.txt:1:what 12 | blah.txt:2:ever 13 | blah.txt:3:whatever 14 | 15 | No multiline: 16 | 17 | $ ag --nomultiline 'wh[^w]+er' . 18 | blah.txt:3:whatever 19 | 20 | Multiline explicit: 21 | 22 | $ ag '^wh[^w\n]+er$' . 23 | blah.txt:3:whatever 24 | -------------------------------------------------------------------------------- /tests/negated_options.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . "${TESTDIR}/setup.sh" 4 | 5 | Should accept both --no-