Showing preview only (361K chars total). Download the full file or copy to clipboard to get everything.
Repository: ggreer/the_silver_searcher
Branch: master
Commit: a61f1780b642
Files: 95
Total size: 338.7 KB
Directory structure:
gitextract_qvo96f4r/
├── .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.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
================================================
FILE CONTENTS
================================================
================================================
FILE: .clang-format
================================================
AlignEscapedNewlinesLeft: false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakBeforeMultilineStrings: false
BinPackArguments: true
BinPackParameters: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
ColumnLimit: 0
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: true
IndentFunctionDeclarationAfterType: false
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MaxEmptyLinesToKeep: 2
PointerAlignment: Right
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
UseTab: Never
================================================
FILE: .gitignore
================================================
*.dSYM
*.gcda
*.o
*.plist
.deps
.dirstamp
.DS_Store
aclocal.m4
ag
ag.exe
autom4te.cache
cachegrind.out.*
callgrind.out.*
clang_output_*
compile
config.guess
config.log
config.status
config.sub
configure
depcomp
gmon.out
install-sh
Makefile
Makefile.in
missing
src/config.h*
stamp-h1
tests/*.err
tests/big/*.err
tests/big/big_file.txt
the_silver_searcher.spec
================================================
FILE: .travis.yml
================================================
language: c
dist: xenial
sudo: false
branches:
only:
- master
- ppc64le
arch:
- amd64
- ppc64le
compiler:
- clang
- gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- automake
- liblzma-dev
- libpcre3-dev
- pkg-config
- zlib1g-dev
env:
global:
- LLVM_VERSION=6.0.1
- LLVM_PATH=$HOME/clang+llvm
- CLANG_FORMAT=$LLVM_PATH/bin/clang-format
before_install:
- 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
- mkdir $LLVM_PATH
- tar xf $LLVM_PATH.tar.xz -C $LLVM_PATH --strip-components=1
- export PATH=$HOME/.local/bin:$PATH
install:
- pip install --user cram
script:
- ./build.sh && make test
notifications:
irc: 'chat.freenode.net#ag'
on_success: change
on_failure: always
use_notice: true
================================================
FILE: CONTRIBUTING.md
================================================
## Contributing
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.
### Running the test suite
If you contribute, you might want to run the test suite before and after writing
some code, just to make sure you did not break anything. Adding tests along with
your code is nice to have, because it makes regressions less likely to happen.
Also, if you think you have found a bug, contributing a failing test case is a
good way of making your point and adding value at the same time.
The test suite uses [Cram](https://bitheap.org/cram/). You'll need to build ag
first, and then you can run the suite from the root of the repository :
make test
### Adding filetypes
Ag can search files which belong to a certain class for example `ag --html test`
searches all files with the extension defined in [lang.c](src/lang.c).
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).
`lang.c` adds the functionality and `list_file_types.t` adds the test case.
Without adding a test case the test __will__ fail.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile.am
================================================
ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS}
bin_PROGRAMS = ag
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
ag_LDADD = ${PCRE_LIBS} ${LZMA_LIBS} ${ZLIB_LIBS} $(PTHREAD_LIBS)
dist_man_MANS = doc/ag.1
bashcompdir = $(pkgdatadir)/completions
dist_bashcomp_DATA = ag.bashcomp.sh
zshcompdir = $(datadir)/zsh/site-functions
dist_zshcomp_DATA = _the_silver_searcher
EXTRA_DIST = Makefile.w32 LICENSE NOTICE the_silver_searcher.spec README.md
all:
@$(MAKE) ag -r
test: ag
cram -v tests/*.t
if HAS_CLANG_FORMAT
CLANG_FORMAT=${CLANG_FORMAT} ./format.sh test
else
@echo "clang-format is not available. Skipped clang-format test."
endif
test_big: ag
cram -v tests/big/*.t
test_fail: ag
cram -v tests/fail/*.t
.PHONY : all clean test test_big test_fail
================================================
FILE: Makefile.w32
================================================
SED=sed
VERSION:=$(shell "$(SED)" -n "s/[^[]*\[\([0-9]\+\.[0-9]\+\.[0-9]\+\)\],/\1/p" configure.ac)
CC=gcc
RM=/bin/rm
SRCS = \
src/decompress.c \
src/ignore.c \
src/lang.c \
src/log.c \
src/main.c \
src/options.c \
src/print.c \
src/scandir.c \
src/search.c \
src/util.c \
src/print_w32.c
OBJS = $(subst .c,.o,$(SRCS))
CFLAGS = -O2 -Isrc/win32 -DPACKAGE_VERSION=\"$(VERSION)\"
LIBS = -lz -lpthread -lpcre -llzma -lshlwapi
TARGET = ag.exe
all : $(TARGET)
# depend on configure.ac to account for version changes
$(TARGET) : $(OBJS) configure.ac
$(CC) -o $@ $(OBJS) $(LIBS)
.c.o :
$(CC) -c $(CFLAGS) -Isrc $< -o $@
clean :
$(RM) -f src/*.o $(TARGET)
================================================
FILE: NOTICE
================================================
The Silver Searcher
Copyright 2011-2016 Geoff Greer
================================================
FILE: README.md
================================================
# The Silver Searcher
A code searching tool similar to `ack`, with a focus on speed.
[](https://travis-ci.org/ggreer/the_silver_searcher)
[](https://floobits.com/ggreer/ag/redirect)
[](https://webchat.freenode.net/?channels=ag)
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/).
## What's so great about Ag?
* It is an order of magnitude faster than `ack`.
* It ignores file patterns from your `.gitignore` and `.hgignore`.
* 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\*)
* The command name is 33% shorter than `ack`, and all keys are on the home row!
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:
ack test_blah ~/code/ 104.66s user 4.82s system 99% cpu 1:50.03 total
ag test_blah ~/code/ 4.67s user 4.58s system 286% cpu 3.227 total
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.
There are also [graphs of performance across releases](http://geoff.greer.fm/ag/speed/).
## How is it so fast?
* Ag uses [Pthreads](https://en.wikipedia.org/wiki/POSIX_Threads) to take advantage of multiple CPU cores and search files in parallel.
* Files are `mmap()`ed instead of read into a buffer.
* Literal string searching uses [Boyer-Moore strstr](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm).
* Regex searching uses [PCRE's JIT compiler](http://sljit.sourceforge.net/pcre.html) (if Ag is built with PCRE >=8.21).
* Ag calls `pcre_study()` before executing the same regex on every file.
* Instead of calling `fnmatch()` on every pattern in your ignore files, non-regex patterns are loaded into arrays and binary searched.
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/).
## Installing
### macOS
brew install the_silver_searcher
or
port install the_silver_searcher
### Linux
* Ubuntu >= 13.10 (Saucy) or Debian >= 8 (Jessie)
apt-get install silversearcher-ag
* Fedora 21 and lower
yum install the_silver_searcher
* Fedora 22+
dnf install the_silver_searcher
* RHEL7+
yum install epel-release.noarch the_silver_searcher
* Gentoo
emerge -a sys-apps/the_silver_searcher
* Arch
pacman -S the_silver_searcher
* Slackware
sbopkg -i the_silver_searcher
* openSUSE
zypper install the_silver_searcher
* CentOS
yum install the_silver_searcher
* NixOS/Nix/Nixpkgs
nix-env -iA silver-searcher
* SUSE Linux Enterprise: Follow [these simple instructions](https://software.opensuse.org/download.html?project=utilities&package=the_silver_searcher).
### BSD
* FreeBSD
pkg install the_silver_searcher
* OpenBSD/NetBSD
pkg_add the_silver_searcher
### Windows
* Win32/64
Unofficial daily builds are [available](https://github.com/k-takata/the_silver_searcher-win32).
* winget
winget install "The Silver Searcher"
Notes:
- This installs a [release](https://github.com/JFLarvoire/the_silver_searcher/releases) of ag.exe optimized for Windows.
- winget is intended to become the default package manager client for Windows.
As of June 2020, it's still in beta, and can be installed using instructions [there](https://github.com/microsoft/winget-cli).
- The setup script in the Ag's winget package installs ag.exe in the first directory that matches one of these criteria:
1. Over a previous instance of ag.exe *from the same [origin](https://github.com/JFLarvoire/the_silver_searcher)* found in the PATH
2. In the directory defined in environment variable bindir_%PROCESSOR_ARCHITECTURE%
3. In the directory defined in environment variable bindir
4. In the directory defined in environment variable windir
* Chocolatey
choco install ag
* MSYS2
pacman -S mingw-w64-{i686,x86_64}-ag
* Cygwin
Run the relevant [`setup-*.exe`](https://cygwin.com/install.html), and select "the\_silver\_searcher" in the "Utils" category.
## Building from source
### Building master
1. Install dependencies (Automake, pkg-config, PCRE, LZMA):
* macOS:
brew install automake pkg-config pcre xz
or
port install automake pkgconfig pcre xz
* Ubuntu/Debian:
apt-get install -y automake pkg-config libpcre3-dev zlib1g-dev liblzma-dev
* Fedora:
yum -y install pkgconfig automake gcc zlib-devel pcre-devel xz-devel
* CentOS:
yum -y groupinstall "Development Tools"
yum -y install pcre-devel xz-devel zlib-devel
* openSUSE:
zypper source-install --build-deps-only the_silver_searcher
* Windows: It's complicated. See [this wiki page](https://github.com/ggreer/the_silver_searcher/wiki/Windows).
2. Run the build script (which just runs aclocal, automake, etc):
./build.sh
On Windows (inside an msys/MinGW shell):
make -f Makefile.w32
3. Make install:
sudo make install
### Building a release tarball
GPG-signed releases are available [here](http://geoff.greer.fm/ag).
Building release tarballs requires the same dependencies, except for automake and pkg-config. Once you've installed the dependencies, just run:
./configure
make
make install
You may need to use `sudo` or run as root for the make install.
## Editor Integration
### Vim
You can use Ag with [ack.vim](https://github.com/mileszs/ack.vim) by adding the following line to your `.vimrc`:
let g:ackprg = 'ag --nogroup --nocolor --column'
or:
let g:ackprg = 'ag --vimgrep'
Which has the same effect but will report every match on the line.
### Emacs
You can use [ag.el][] as an Emacs front-end to Ag. See also: [helm-ag].
[ag.el]: https://github.com/Wilfred/ag.el
[helm-ag]: https://github.com/syohex/emacs-helm-ag
### TextMate
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"`
## Other stuff you might like
* [Ack](https://github.com/petdance/ack3) - Better than grep. Without Ack, Ag would not exist.
* [ack.vim](https://github.com/mileszs/ack.vim)
* [Exuberant Ctags](http://ctags.sourceforge.net/) - Faster than Ag, but it builds an index beforehand. Good for *really* big codebases.
* [Git-grep](http://git-scm.com/docs/git-grep) - As fast as Ag but only works on git repos.
* [fzf](https://github.com/junegunn/fzf) - A command-line fuzzy finder
* [ripgrep](https://github.com/BurntSushi/ripgrep)
* [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.
================================================
FILE: _the_silver_searcher
================================================
#compdef ag
# Completion function for zsh
local ret=1
local -a args expl
# Intentionally avoided many possible mutual exlusions because it is
# likely that earlier options come from an alias. In line with this
# the following conditionally adds options that assert defaults.
[[ -n $words[(r)(-[is]|--ignore-case|--case-sensitive)] ]] && args+=(
'(-S --smart-case -s -s --ignore-case --case-sensitive)'{-S,--smart-case}'[insensitive match unless pattern includes uppercase]'
)
[[ -n $words[(r)--nobreak] ]] && args+=(
"(--nobreak)--break[print newlines between matches in different files]"
)
[[ -n $words[(r)--nogroup] ]] && args+=(
"(--nogroup)--group[don't repeat filename for each match line]"
)
_tags normal-options file-types
while _tags; do
_requested normal-options && _arguments -S -s $args \
'--ackmate[print results in AckMate-parseable format]' \
'(--after -A)'{--after=-,-A+}'[specify lines of trailing context]::lines [2]' \
'(--before -B)'{--before=-,-B+}'[specify lines of leading context]::lines [2]' \
"--nobreak[don't print newlines between matches in different files]" \
'(--count -c)'{--count,-c}'[only print a count of matching lines]' \
'--color[enable color highlighting of output]' \
'(--color-line-number --color-match --color-path)--nocolor[disable color highlighting of output]' \
'--color-line-number=[specify color for line numbers]:color [1;33]' \
'--color-match=[specify color for result match numbers]:color [30;43]' \
'--color-path=[specify color for path names]:color [1;32]' \
'--column[print column numbers in results]' \
'(--context -C)'{--context=-,-C+}'[specify lines of context]::lines' \
'(--debug -D)'{--debug,-D}'[output debug information]' \
'--depth=[specify directory levels to descend when searching]:levels [25]' \
'(--noheading)--nofilename[suppress printing of filenames]' \
'(-f --follow)'{-f,--follow}'[follow symlinks]' \
'(-F --fixed-strings --literal -Q)'{--fixed-strings,-F,--literal,-Q}'[use literal strings]' \
'--nogroup[repeat filename for each match line]' \
'(1 -G --file-search-regex)-g+[print filenames matching a pattern]:regex' \
'(-G --file-search-regex)'{-G+,--file-search-regex=}'[limit search to filenames matching pattern]:regex' \
'(-H --heading --noheading)'{-H,--heading}'[print filename with each match]' \
'(-H --heading --noheading --nofilename)--noheading[suppress printing of filenames]' \
'--hidden[search hidden files (obeying .*ignore files)]' \
{--ignore=,--ignore-dir=}'[ignore files/directories matching pattern]:regex' \
'(-i --ignore-case)'{-i,--ignore-case}'[match case-insensitively]' \
'(-l --files-with-matches)'{-l,--files-with-matches}"[output matching files' names only]" \
'(-L --files-without-matches)'{-L,--files-without-matches}"[output non-matching files' names only]" \
'(--max-count -m)'{--max-count=,-m+}'[stop after specified no of matches in each file]:max number of matches' \
'--numbers[prefix output with line numbers, even for streams]' \
'--nonumbers[suppress printing of line numbers]' \
'(--only-matching -o)'{--only-matching,-o}'[show only matching part of line]' \
'(-p --path-to-ignore)'{-p+,--path-to-ignore=}'[use specified .ignore file]:file:_files' \
'--print-long-lines[print matches on very long lines]' \
"--passthrough[when searching a stream, print all lines even if they don't match]" \
'(-s --case-sensitive)'{-s,--case-sensitive}'[match case]' \
'--silent[suppress all log messages, including errors]' \
'(--stats-only)--stats[print stats (files scanned, time taken, etc.)]' \
'(--stats)--stats-only[print stats and nothing else]' \
'(-U --skip-vcs-ignores)'{-U,--skip-vcs-ignores}'[ignore VCS files (stil obey .ignore)]' \
'(-v --invert-match)'{-v,--invert-match}'[select non-matching lines]' \
'--vimgrep[output results like vim :vimgrep /pattern/g would]' \
'(-w --word-regexp)'{-w,--word-regexp}'[force pattern to match only whole words]' \
'(-z --search-zip)'{-z,--search-zip}'[search contents of compressed files]' \
'(-0 --null)'{-0,--null}'[separate filenames with null]' \
': :_guard "^-*" pattern' \
'*:file:_files' \
'(- :)--list-file-types[list supported file types]' \
'(- :)'{-h,--help}'[display help information]' \
'(- :)'{-V,--version}'[display version information]' \
- '(ignores)' \
'(-a --all-types)'{-a,--all-types}'[search all files]' \
'--search-binary[search binary files for matches]' \
{-t,--all-text}'[search all text files (not including hidden files)]' \
{-u,--unrestricted}'[search all files]' && ret=0
_requested file-types && { ! zstyle -T ":completion:${curcontext}:options" prefix-needed ||
[[ -prefix - ]] } && _all_labels file-types expl 'file type' \
compadd - ${(M)$(_call_program file-types $words[1] --list-file-types):#--*} && ret=0
(( ret )) || break
done
return ret
================================================
FILE: ag.bashcomp.sh
================================================
_ag() {
local lngopt shtopt split=false
local cur prev
COMPREPLY=()
cur=$(_get_cword "=")
prev="${COMP_WORDS[COMP_CWORD-1]}"
_expand || return 0
lngopt='
--ackmate
--ackmate-dir-filter
--affinity
--after
--all-text
--all-types
--before
--break
--case-sensitive
--color
--color-line-number
--color-match
--color-path
--color-win-ansi
--column
--context
--count
--debug
--depth
--file-search-regex
--filename
--files-with-matches
--files-without-matches
--fixed-strings
--follow
--group
--heading
--help
--hidden
--ignore
--ignore-case
--ignore-dir
--invert-match
--line-numbers
--list-file-types
--literal
--match
--max-count
--no-numbers
--no-recurse
--noaffinity
--nobreak
--nocolor
--nofilename
--nofollow
--nogroup
--noheading
--nonumbers
--nopager
--norecurse
--null
--numbers
--one-device
--only-matching
--pager
--parallel
--passthrough
--passthru
--path-to-ignore
--print-long-lines
--print0
--recurse
--search-binary
--search-files
--search-zip
--silent
--skip-vcs-ignores
--smart-case
--stats
--unrestricted
--version
--vimgrep
--word-regexp
--workers
'
shtopt='
-a -A -B -C -D
-f -F -g -G -h
-i -l -L -m -n
-p -Q -r -R -s
-S -t -u -U -v
-V -w -z
'
types=$(ag --list-file-types |grep -- '--')
# these options require an argument
if [[ "${prev}" == -[ABCGgm] ]] ; then
return 0
fi
_split_longopt && split=true
case "${prev}" in
--ignore-dir) # directory completion
_filedir -d
return 0;;
--path-to-ignore) # file completion
_filedir
return 0;;
--pager) # command completion
COMPREPLY=( $(compgen -c -- "${cur}") )
return 0;;
--ackmate-dir-filter|--after|--before|--color-*|--context|--depth\
|--file-search-regex|--ignore|--max-count|--workers)
return 0;;
esac
$split && return 0
case "${cur}" in
-*)
COMPREPLY=( $(compgen -W \
"${lngopt} ${shtopt} ${types}" -- "${cur}") )
return 0;;
*)
_filedir
return 0;;
esac
} &&
# shellcheck disable=SC2086
# shellcheck disable=SC2154,SC2086
complete -F _ag ${nospace} ag
================================================
FILE: autogen.sh
================================================
#!/bin/sh
set -e
cd "$(dirname "$0")"
AC_SEARCH_OPTS=""
# For those of us with pkg-config and other tools in /usr/local
PATH=$PATH:/usr/local/bin
# This is to make life easier for people who installed pkg-config in /usr/local
# but have autoconf/make/etc in /usr/. AKA most mac users
if [ -d "/usr/local/share/aclocal" ]
then
AC_SEARCH_OPTS="-I /usr/local/share/aclocal"
fi
# shellcheck disable=2086
aclocal $AC_SEARCH_OPTS
autoconf
autoheader
automake --add-missing
================================================
FILE: build.sh
================================================
#!/bin/sh
set -e
cd "$(dirname "$0")"
./autogen.sh
./configure "$@"
make -j4
================================================
FILE: configure.ac
================================================
AC_INIT(
[the_silver_searcher],
[2.2.0],
[https://github.com/ggreer/the_silver_searcher/issues],
[the_silver_searcher],
[https://github.com/ggreer/the_silver_searcher])
AM_INIT_AUTOMAKE([no-define foreign subdir-objects])
AC_PROG_CC
AM_PROG_CC_C_O
AC_PREREQ([2.59])
AC_PROG_GREP
m4_ifdef(
[AM_SILENT_RULES],
[AM_SILENT_RULES([yes])])
PKG_CHECK_MODULES([PCRE], [libpcre])
m4_include([m4/ax_pthread.m4])
AX_PTHREAD(
[AC_CHECK_HEADERS([pthread.h])],
[AC_MSG_WARN([No pthread support. Ag will be slower due to running single-threaded.])]
)
# Run CFLAGS="-pg" ./configure if you want debug symbols
if ! echo "$CFLAGS" | "$GREP" '\(^\|[[[:space:]]]\)-O' > /dev/null; then
CFLAGS="$CFLAGS -O2"
fi
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $PCRE_CFLAGS -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -Wshadow"
CFLAGS="$CFLAGS -Wpointer-arith -Wcast-qual -Wmissing-prototypes -Wno-missing-braces -std=gnu89 -D_GNU_SOURCE"
LDFLAGS="$LDFLAGS"
case $host in
*mingw*)
AC_CHECK_LIB(shlwapi, main,, AC_MSG_ERROR(libshlwapi missing))
esac
LIBS="$PTHREAD_LIBS $LIBS"
AC_ARG_ENABLE([zlib],
AS_HELP_STRING([--disable-zlib], [Disable zlib compressed search support]))
AS_IF([test "x$enable_zlib" != "xno"], [
AC_CHECK_HEADERS([zlib.h])
AC_SEARCH_LIBS([inflate], [zlib, z])
])
AC_ARG_ENABLE([lzma],
AS_HELP_STRING([--disable-lzma], [Disable lzma compressed search support]))
AS_IF([test "x$enable_lzma" != "xno"], [
AC_CHECK_HEADERS([lzma.h])
PKG_CHECK_MODULES([LZMA], [liblzma])
])
AC_CHECK_DECL([PCRE_CONFIG_JIT], [AC_DEFINE([USE_PCRE_JIT], [], [Use PCRE JIT])], [], [#include <pcre.h>])
AC_CHECK_DECL([CPU_ZERO, CPU_SET], [AC_DEFINE([USE_CPU_SET], [], [Use CPU_SET macros])] , [], [#include <sched.h>])
AC_CHECK_HEADERS([sys/cpuset.h err.h])
AC_CHECK_MEMBER([struct dirent.d_type], [AC_DEFINE([HAVE_DIRENT_DTYPE], [], [Have dirent struct member d_type])], [], [[#include <dirent.h>]])
AC_CHECK_MEMBER([struct dirent.d_namlen], [AC_DEFINE([HAVE_DIRENT_DNAMLEN], [], [Have dirent struct member d_namlen])], [], [[#include <dirent.h>]])
AC_CHECK_FUNCS(fgetln fopencookie getline realpath strlcpy strndup vasprintf madvise posix_fadvise pthread_setaffinity_np pledge)
AC_CONFIG_FILES([Makefile the_silver_searcher.spec])
AC_CONFIG_HEADERS([src/config.h])
AC_CHECK_PROGS(
[CLANG_FORMAT],
[clang-format-3.8 clang-format-3.7 clang-format-3.6 clang-format],
[no]
)
AM_CONDITIONAL([HAS_CLANG_FORMAT], [test x$CLANG_FORMAT != xno])
AM_COND_IF(
[HAS_CLANG_FORMAT],
[AC_MSG_NOTICE([clang-format found. 'make test' will detect improperly-formatted files.])],
[AC_MSG_WARN([clang-format not found. 'make test' will not detect improperly-formatted files.])]
)
AC_OUTPUT
================================================
FILE: doc/ag.1
================================================
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "AG" "1" "December 2016" "" ""
.
.SH "NAME"
\fBag\fR \- The Silver Searcher\. Like ack, but faster\.
.
.SH "SYNOPSIS"
\fBag\fR [\fIoptions\fR] \fIpattern\fR [\fIpath \.\.\.\fR]
.
.SH "DESCRIPTION"
Recursively search for PATTERN in PATH\. Like grep or ack, but faster\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-ackmate\fR
Output results in a format parseable by AckMate \fIhttps://github\.com/protocool/AckMate\fR\.
.
.TP
\fB\-\-[no]affinity\fR
Set thread affinity (if platform supports it)\. Default is true\.
.
.TP
\fB\-a \-\-all\-types\fR
Search all files\. This doesn\'t include hidden files, and doesn\'t respect any ignore files\.
.
.TP
\fB\-A \-\-after [LINES]\fR
Print lines after match\. If not provided, LINES defaults to 2\.
.
.TP
\fB\-B \-\-before [LINES]\fR
Print lines before match\. If not provided, LINES defaults to 2\.
.
.TP
\fB\-\-[no]break\fR
Print a newline between matches in different files\. Enabled by default\.
.
.TP
\fB\-c \-\-count\fR
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\.
.
.TP
\fB\-\-[no]color\fR
Print color codes in results\. Enabled by default\.
.
.TP
\fB\-\-color\-line\-number\fR
Color codes for line numbers\. Default is 1;33\.
.
.TP
\fB\-\-color\-match\fR
Color codes for result match numbers\. Default is 30;43\.
.
.TP
\fB\-\-color\-path\fR
Color codes for path names\. Default is 1;32\.
.
.TP
\fB\-\-column\fR
Print column numbers in results\.
.
.TP
\fB\-C \-\-context [LINES]\fR
Print lines before and after matches\. Default is 2\.
.
.TP
\fB\-D \-\-debug\fR
Output ridiculous amounts of debugging info\. Not useful unless you\'re actually debugging\.
.
.TP
\fB\-\-depth NUM\fR
Search up to NUM directories deep, \-1 for unlimited\. Default is 25\.
.
.TP
\fB\-\-[no]filename\fR
Print file names\. Enabled by default, except when searching a single file\.
.
.TP
\fB\-f \-\-[no]follow\fR
Follow symlinks\. Default is false\.
.
.TP
\fB\-F \-\-fixed\-strings\fR
Alias for \-\-literal for compatibility with grep\.
.
.TP
\fB\-\-[no]group\fR
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\.
.
.TP
\fB\-g PATTERN\fR
Print filenames matching PATTERN\.
.
.TP
\fB\-G \-\-file\-search\-regex PATTERN\fR
Only search files whose names match PATTERN\.
.
.TP
\fB\-H \-\-[no]heading\fR
Print filenames above matching contents\.
.
.TP
\fB\-\-hidden\fR
Search hidden files\. This option obeys ignored files\.
.
.TP
\fB\-\-ignore PATTERN\fR
Ignore files/directories whose names match this pattern\. Literal file and directory names are also allowed\.
.
.TP
\fB\-\-ignore\-dir NAME\fR
Alias for \-\-ignore for compatibility with ack\.
.
.TP
\fB\-i \-\-ignore\-case\fR
Match case\-insensitively\.
.
.TP
\fB\-l \-\-files\-with\-matches\fR
Only print the names of files containing matches, not the matching lines\. An empty query will print all files that would be searched\.
.
.TP
\fB\-L \-\-files\-without\-matches\fR
Only print the names of files that don\'t contain matches\.
.
.TP
\fB\-\-list\-file\-types\fR
See \fBFILE TYPES\fR below\.
.
.TP
\fB\-m \-\-max\-count NUM\fR
Skip the rest of a file after NUM matches\. Default is 0, which never skips\.
.
.TP
\fB\-\-[no]mmap\fR
Toggle use of memory\-mapped I/O\. Defaults to true on platforms where \fBmmap()\fR is faster than \fBread()\fR\. (All but macOS\.)
.
.TP
\fB\-\-[no]multiline\fR
Match regexes across newlines\. Enabled by default\.
.
.TP
\fB\-n \-\-norecurse\fR
Don\'t recurse into directories\.
.
.TP
\fB\-\-[no]numbers\fR
Print line numbers\. Default is to omit line numbers when searching streams\.
.
.TP
\fB\-o \-\-only\-matching\fR
Print only the matching part of the lines\.
.
.TP
\fB\-\-one\-device\fR
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\.
.
.TP
\fB\-p \-\-path\-to\-ignore STRING\fR
Provide a path to a specific \.ignore file\.
.
.TP
\fB\-\-pager COMMAND\fR
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\.
.
.TP
\fB\-\-parallel\fR
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"\.
.
.TP
\fB\-\-print\-long\-lines\fR
Print matches on very long lines (> 2k characters by default)\.
.
.TP
\fB\-\-passthrough \-\-passthru\fR
When searching a stream, print all lines even if they don\'t match\.
.
.TP
\fB\-Q \-\-literal\fR
Do not parse PATTERN as a regular expression\. Try to match it literally\.
.
.TP
\fB\-r \-\-recurse\fR
Recurse into directories when searching\. Default is true\.
.
.TP
\fB\-s \-\-case\-sensitive\fR
Match case\-sensitively\.
.
.TP
\fB\-S \-\-smart\-case\fR
Match case\-sensitively if there are any uppercase letters in PATTERN, case\-insensitively otherwise\. Enabled by default\.
.
.TP
\fB\-\-search\-binary\fR
Search binary files for matches\.
.
.TP
\fB\-\-silent\fR
Suppress all log messages, including errors\.
.
.TP
\fB\-\-stats\fR
Print stats (files scanned, time taken, etc)\.
.
.TP
\fB\-\-stats\-only\fR
Print stats (files scanned, time taken, etc) and nothing else\.
.
.TP
\fB\-t \-\-all\-text\fR
Search all text files\. This doesn\'t include hidden files\.
.
.TP
\fB\-u \-\-unrestricted\fR
Search \fIall\fR files\. This ignores \.ignore, \.gitignore, etc\. It searches binary and hidden files as well\.
.
.TP
\fB\-U \-\-skip\-vcs\-ignores\fR
Ignore VCS ignore files (\.gitignore, \.hgignore), but still use \.ignore\.
.
.TP
\fB\-v \-\-invert\-match\fR
Match every line \fInot\fR containing the specified pattern\.
.
.TP
\fB\-V \-\-version\fR
Print version info\.
.
.TP
\fB\-\-vimgrep\fR
Output results in the same form as Vim\'s \fB:vimgrep /pattern/g\fR
.
.IP
Here is a ~/\.vimrc configuration example:
.
.IP
\fBset grepprg=ag\e \-\-vimgrep\e $*\fR \fBset grepformat=%f:%l:%c:%m\fR
.
.IP
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\.
.
.TP
\fB\-w \-\-word\-regexp\fR
Only match whole words\.
.
.TP
\fB\-\-workers NUM\fR
Use NUM worker threads\. Default is the number of CPU cores, with a max of 8\.
.
.TP
\fB\-z \-\-search\-zip\fR
Search contents of compressed files\. Currently, gz and xz are supported\. This option requires that ag is built with lzma and zlib\.
.
.TP
\fB\-0 \-\-null \-\-print0\fR
Separate the filenames with \fB\e0\fR, rather than \fB\en\fR: this allows \fBxargs \-0 <command>\fR to correctly process filenames containing spaces or newlines\.
.
.SH "FILE TYPES"
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\.
.
.SH "IGNORING FILES"
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\.
.
.P
If you want to ignore \.gitignore and \.hgignore, but still take \.ignore into account, use \fB\-U\fR\.
.
.P
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\.
.
.SH "EXAMPLES"
\fBag printf\fR: Find matches for "printf" in the current directory\.
.
.P
\fBag foo /bar/\fR: Find matches for "foo" in path /bar/\.
.
.P
\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\.)
.
.SH "ABOUT"
ag was originally created by Geoff Greer\. More information (and the latest release) can be found at http://geoff\.greer\.fm/ag
.
.SH "SEE ALSO"
grep(1)
================================================
FILE: doc/ag.1.md
================================================
ag(1) -- The Silver Searcher. Like ack, but faster.
=============================================
## SYNOPSIS
`ag` [_options_] _pattern_ [_path ..._]
## DESCRIPTION
Recursively search for PATTERN in PATH. Like grep or ack, but faster.
## OPTIONS
* `--ackmate`:
Output results in a format parseable by [AckMate](https://github.com/protocool/AckMate).
* `--[no]affinity`:
Set thread affinity (if platform supports it). Default is true.
* `-a --all-types`:
Search all files. This doesn't include hidden files, and doesn't respect any ignore files.
* `-A --after [LINES]`:
Print lines after match. If not provided, LINES defaults to 2.
* `-B --before [LINES]`:
Print lines before match. If not provided, LINES defaults to 2.
* `--[no]break`:
Print a newline between matches in different files. Enabled by default.
* `-c --count`:
Only print the number of matches in each file.
Note: This is the number of matches, **not** the number of matching lines.
Pipe output to `wc -l` if you want the number of matching lines.
* `--[no]color`:
Print color codes in results. Enabled by default.
* `--color-line-number`:
Color codes for line numbers. Default is 1;33.
* `--color-match`:
Color codes for result match numbers. Default is 30;43.
* `--color-path`:
Color codes for path names. Default is 1;32.
* `--column`:
Print column numbers in results.
* `-C --context [LINES]`:
Print lines before and after matches. Default is 2.
* `-D --debug`:
Output ridiculous amounts of debugging info. Not useful unless you're actually debugging.
* `--depth NUM`:
Search up to NUM directories deep, -1 for unlimited. Default is 25.
* `--[no]filename`:
Print file names. Enabled by default, except when searching a single file.
* `-f --[no]follow`:
Follow symlinks. Default is false.
* `-F --fixed-strings`:
Alias for --literal for compatibility with grep.
* `--[no]group`:
The default, `--group`, lumps multiple matches in the same file
together, and presents them under a single occurrence of the
filename. `--nogroup` refrains from this, and instead places the
filename at the start of each match line.
* `-g PATTERN`:
Print filenames matching PATTERN.
* `-G --file-search-regex PATTERN`:
Only search files whose names match PATTERN.
* `-H --[no]heading`:
Print filenames above matching contents.
* `--hidden`:
Search hidden files. This option obeys ignored files.
* `--ignore PATTERN`:
Ignore files/directories whose names match this pattern. Literal
file and directory names are also allowed.
* `--ignore-dir NAME`:
Alias for --ignore for compatibility with ack.
* `-i --ignore-case`:
Match case-insensitively.
* `-l --files-with-matches`:
Only print the names of files containing matches, not the matching
lines. An empty query will print all files that would be searched.
* `-L --files-without-matches`:
Only print the names of files that don't contain matches.
* `--list-file-types`:
See `FILE TYPES` below.
* `-m --max-count NUM`:
Skip the rest of a file after NUM matches. Default is 0, which never skips.
* `--[no]mmap`:
Toggle use of memory-mapped I/O. Defaults to true on platforms where
`mmap()` is faster than `read()`. (All but macOS.)
* `--[no]multiline`:
Match regexes across newlines. Enabled by default.
* `-n --norecurse`:
Don't recurse into directories.
* `--[no]numbers`:
Print line numbers. Default is to omit line numbers when searching streams.
* `-o --only-matching`:
Print only the matching part of the lines.
* `--one-device`:
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.
* `-p --path-to-ignore STRING`:
Provide a path to a specific .ignore file.
* `--pager COMMAND`:
Use a pager such as `less`. Use `--nopager` to override. This option
is also ignored if output is piped to another program.
* `--parallel`:
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:
`echo "foo\nbar\nbaz" | parallel "ag {} ."` will run 3 instances of ag,
searching the current directory for "foo", "bar", and "baz".
* `--print-long-lines`:
Print matches on very long lines (> 2k characters by default).
* `--passthrough --passthru`:
When searching a stream, print all lines even if they don't match.
* `-Q --literal`:
Do not parse PATTERN as a regular expression. Try to match it literally.
* `-r --recurse`:
Recurse into directories when searching. Default is true.
* `-s --case-sensitive`:
Match case-sensitively.
* `-S --smart-case`:
Match case-sensitively if there are any uppercase letters in PATTERN,
case-insensitively otherwise. Enabled by default.
* `--search-binary`:
Search binary files for matches.
* `--silent`:
Suppress all log messages, including errors.
* `--stats`:
Print stats (files scanned, time taken, etc).
* `--stats-only`:
Print stats (files scanned, time taken, etc) and nothing else.
* `-t --all-text`:
Search all text files. This doesn't include hidden files.
* `-u --unrestricted`:
Search *all* files. This ignores .ignore, .gitignore, etc. It searches
binary and hidden files as well.
* `-U --skip-vcs-ignores`:
Ignore VCS ignore files (.gitignore, .hgignore), but still
use .ignore.
* `-v --invert-match`:
Match every line *not* containing the specified pattern.
* `-V --version`:
Print version info.
* `--vimgrep`:
Output results in the same form as Vim's `:vimgrep /pattern/g`
Here is a ~/.vimrc configuration example:
`set grepprg=ag\ --vimgrep\ $*`
`set grepformat=%f:%l:%c:%m`
Then use `:grep` to grep for something.
Then use `:copen`, `:cn`, `:cp`, etc. to navigate through the matches.
* `-w --word-regexp`:
Only match whole words.
* `--workers NUM`:
Use NUM worker threads. Default is the number of CPU cores, with a max of 8.
* `-W --width NUM`:
Truncate match lines after NUM characters.
* `-z --search-zip`:
Search contents of compressed files. Currently, gz and xz are supported.
This option requires that ag is built with lzma and zlib.
* `-0 --null --print0`:
Separate the filenames with `\0`, rather than `\n`:
this allows `xargs -0 <command>` to correctly process filenames containing
spaces or newlines.
## FILE TYPES
It is possible to restrict the types of files searched. For example, passing
`--html` will search only files with the extensions `htm`, `html`, `shtml`
or `xhtml`. For a list of supported types, run `ag --list-file-types`.
## IGNORING FILES
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.
If you want to ignore .gitignore and .hgignore, but still take .ignore into
account, use `-U`.
Use the `-t` option to search all text files; `-a` to search all files; and `-u`
to search all, including hidden files.
## EXAMPLES
`ag printf`:
Find matches for "printf" in the current directory.
`ag foo /bar/`:
Find matches for "foo" in path /bar/.
`ag -- --foo`:
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.)
## ABOUT
ag was originally created by Geoff Greer. More information (and the latest
release) can be found at http://geoff.greer.fm/ag
## SEE ALSO
grep(1)
================================================
FILE: doc/generate_man.sh
================================================
#!/bin/sh
# ronn is used to turn the markdown into a manpage.
# Get ronn at https://github.com/rtomayko/ronn
# Alternately, since ronn is a Ruby gem, you can just
# `gem install ronn`
sed -e 's/\\0/\\\\0/' <ag.1.md >ag.1.md.tmp
ronn -r ag.1.md.tmp
rm -f ag.1.md.tmp
================================================
FILE: format.sh
================================================
#!/bin/bash
function usage() {
echo "Usage: $0 test|reformat"
}
if [ $# -eq 0 ]
then
usage
exit 0
fi
if [ -z "$CLANG_FORMAT" ]
then
CLANG_FORMAT=clang-format
echo "No CLANG_FORMAT set. Using $CLANG_FORMAT"
fi
if ! type "$CLANG_FORMAT" &> /dev/null
then
echo "The command \"$CLANG_FORMAT\" was not found"
exit 1
fi
SOURCE_FILES=$(git ls-files src/)
if [ "$1" == "reformat" ]
then
echo "Reformatting source files"
# shellcheck disable=2086
echo $CLANG_FORMAT -style=file -i $SOURCE_FILES
# shellcheck disable=2086
$CLANG_FORMAT -style=file -i $SOURCE_FILES
exit 0
elif [ "$1" == "test" ]
then
# shellcheck disable=2086
RESULT=$($CLANG_FORMAT -style=file -output-replacements-xml $SOURCE_FILES | grep -c '<replacement ')
if [ "$RESULT" -eq 0 ]
then
echo "code is formatted correctly :)"
exit 0
else
echo "code is not formatted correctly! :("
echo "Suggested change:"
cp -r src clang_format_src
$CLANG_FORMAT -style=file -i clang_format_src/*.c clang_format_src/*.h
diff -ur src clang_format_src
rm -r clang_format_src
echo "Run '$0 reformat' to fix formatting"
exit 1
fi
else
echo "invalid command: $1"
usage
exit 1
fi
================================================
FILE: m4/ax_pthread.m4
================================================
# ===========================================================================
# http://www.gnu.org/software/autoconf-archive/ax_pthread.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
#
# DESCRIPTION
#
# This macro figures out how to build C programs using POSIX threads. It
# sets the PTHREAD_LIBS output variable to the threads library and linker
# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
# flags that are needed. (The user can also force certain compiler
# flags/libs to be tested by setting these environment variables.)
#
# Also sets PTHREAD_CC to any special C compiler that is needed for
# multi-threaded programs (defaults to the value of CC otherwise). (This
# is necessary on AIX to use the special cc_r compiler alias.)
#
# NOTE: You are assumed to not only compile your program with these flags,
# but also link it with them as well. e.g. you should link with
# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
#
# If you are only building threads programs, you may wish to use these
# variables in your default LIBS, CFLAGS, and CC:
#
# LIBS="$PTHREAD_LIBS $LIBS"
# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
# CC="$PTHREAD_CC"
#
# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name
# (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
#
# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
# PTHREAD_CFLAGS.
#
# ACTION-IF-FOUND is a list of shell commands to run if a threads library
# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
# is not found. If ACTION-IF-FOUND is not specified, the default action
# will define HAVE_PTHREAD.
#
# Please let the authors know if this macro fails on any platform, or if
# you have any other suggestions or comments. This macro was based on work
# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
# Alejandro Forero Cuervo to the autoconf macro repository. We are also
# grateful for the helpful feedback of numerous users.
#
# Updated for Autoconf 2.68 by Daniel Richard G.
#
# LICENSE
#
# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
# As a special exception, the respective Autoconf Macro's copyright owner
# gives unlimited permission to copy, distribute and modify the configure
# scripts that are the output of Autoconf when processing the Macro. You
# need not follow the terms of the GNU General Public License when using
# or distributing such scripts, even though portions of the text of the
# Macro appear in them. The GNU General Public License (GPL) does govern
# all other use of the material that constitutes the Autoconf Macro.
#
# This special exception to the GPL applies to versions of the Autoconf
# Macro released by the Autoconf Archive. When you make and distribute a
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
#serial 21
AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
AC_DEFUN([AX_PTHREAD], [
AC_REQUIRE([AC_CANONICAL_HOST])
AC_LANG_PUSH([C])
ax_pthread_ok=no
# We used to check for pthread.h first, but this fails if pthread.h
# requires special compiler flags (e.g. on True64 or Sequent).
# It gets checked for in the link test anyway.
# First of all, check if the user has set any of the PTHREAD_LIBS,
# etcetera environment variables, and if threads linking works using
# them:
if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
save_LIBS="$LIBS"
LIBS="$PTHREAD_LIBS $LIBS"
AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes])
AC_MSG_RESULT([$ax_pthread_ok])
if test x"$ax_pthread_ok" = xno; then
PTHREAD_LIBS=""
PTHREAD_CFLAGS=""
fi
LIBS="$save_LIBS"
CFLAGS="$save_CFLAGS"
fi
# We must check for the threads library under a number of different
# names; the ordering is very important because some systems
# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
# libraries is broken (non-POSIX).
# Create a list of thread flags to try. Items starting with a "-" are
# C compiler flags, and other items are library names, except for "none"
# which indicates that we try without any flags at all, and "pthread-config"
# which is a program returning the flags for the Pth emulation library.
ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
# The ordering *is* (sometimes) important. Some notes on the
# individual items follow:
# pthreads: AIX (must check this before -lpthread)
# none: in case threads are in libc; should be tried before -Kthread and
# other compiler flags to prevent continual compiler warnings
# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
# -pthreads: Solaris/gcc
# -mthreads: Mingw32/gcc, Lynx/gcc
# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
# doesn't hurt to check since this sometimes defines pthreads too;
# also defines -D_REENTRANT)
# ... -mt is also the pthreads flag for HP/aCC
# pthread: Linux, etcetera
# --thread-safe: KAI C++
# pthread-config: use pthread-config program (for GNU Pth library)
case ${host_os} in
solaris*)
# On Solaris (at least, for some versions), libc contains stubbed
# (non-functional) versions of the pthreads routines, so link-based
# tests will erroneously succeed. (We need to link with -pthreads/-mt/
# -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
# a function called by this macro, so we could check for that, but
# who knows whether they'll stub that too in a future libc.) So,
# we'll just look for -pthreads and -lpthread first:
ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags"
;;
darwin*)
ax_pthread_flags="-pthread $ax_pthread_flags"
;;
esac
# Clang doesn't consider unrecognized options an error unless we specify
# -Werror. We throw in some extra Clang-specific options to ensure that
# this doesn't happen for GCC, which also accepts -Werror.
AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags])
save_CFLAGS="$CFLAGS"
ax_pthread_extra_flags="-Werror"
CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])],
[AC_MSG_RESULT([yes])],
[ax_pthread_extra_flags=
AC_MSG_RESULT([no])])
CFLAGS="$save_CFLAGS"
if test x"$ax_pthread_ok" = xno; then
for flag in $ax_pthread_flags; do
case $flag in
none)
AC_MSG_CHECKING([whether pthreads work without any flags])
;;
-*)
AC_MSG_CHECKING([whether pthreads work with $flag])
PTHREAD_CFLAGS="$flag"
;;
pthread-config)
AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
if test x"$ax_pthread_config" = xno; then continue; fi
PTHREAD_CFLAGS="`pthread-config --cflags`"
PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
;;
*)
AC_MSG_CHECKING([for the pthreads library -l$flag])
PTHREAD_LIBS="-l$flag"
;;
esac
save_LIBS="$LIBS"
save_CFLAGS="$CFLAGS"
LIBS="$PTHREAD_LIBS $LIBS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags"
# Check for various functions. We must include pthread.h,
# since some functions may be macros. (On the Sequent, we
# need a special flag -Kthread to make this header compile.)
# We check for pthread_join because it is in -lpthread on IRIX
# while pthread_create is in libc. We check for pthread_attr_init
# due to DEC craziness with -lpthreads. We check for
# pthread_cleanup_push because it is one of the few pthread
# functions on Solaris that doesn't have a non-functional libc stub.
# We try pthread_create on general principles.
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
static void routine(void *a) { a = 0; }
static void *start_routine(void *a) { return a; }],
[pthread_t th; pthread_attr_t attr;
pthread_create(&th, 0, start_routine, 0);
pthread_join(th, 0);
pthread_attr_init(&attr);
pthread_cleanup_push(routine, 0);
pthread_cleanup_pop(0) /* ; */])],
[ax_pthread_ok=yes],
[])
LIBS="$save_LIBS"
CFLAGS="$save_CFLAGS"
AC_MSG_RESULT([$ax_pthread_ok])
if test "x$ax_pthread_ok" = xyes; then
break;
fi
PTHREAD_LIBS=""
PTHREAD_CFLAGS=""
done
fi
# Various other checks:
if test "x$ax_pthread_ok" = xyes; then
save_LIBS="$LIBS"
LIBS="$PTHREAD_LIBS $LIBS"
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
# Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
AC_MSG_CHECKING([for joinable pthread attribute])
attr_name=unknown
for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
[int attr = $attr; return attr /* ; */])],
[attr_name=$attr; break],
[])
done
AC_MSG_RESULT([$attr_name])
if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name],
[Define to necessary symbol if this constant
uses a non-standard name on your system.])
fi
AC_MSG_CHECKING([if more special flags are required for pthreads])
flag=no
case ${host_os} in
aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";;
osf* | hpux*) flag="-D_REENTRANT";;
solaris*)
if test "$GCC" = "yes"; then
flag="-D_REENTRANT"
else
# TODO: What about Clang on Solaris?
flag="-mt -D_REENTRANT"
fi
;;
esac
AC_MSG_RESULT([$flag])
if test "x$flag" != xno; then
PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
fi
AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
[ax_cv_PTHREAD_PRIO_INHERIT], [
AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
[[int i = PTHREAD_PRIO_INHERIT;]])],
[ax_cv_PTHREAD_PRIO_INHERIT=yes],
[ax_cv_PTHREAD_PRIO_INHERIT=no])
])
AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"],
[AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])])
LIBS="$save_LIBS"
CFLAGS="$save_CFLAGS"
# More AIX lossage: compile with *_r variant
if test "x$GCC" != xyes; then
case $host_os in
aix*)
AS_CASE(["x/$CC"],
[x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
[#handle absolute path differently from PATH based program lookup
AS_CASE(["x$CC"],
[x/*],
[AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
[AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
;;
esac
fi
fi
test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
AC_SUBST([PTHREAD_LIBS])
AC_SUBST([PTHREAD_CFLAGS])
AC_SUBST([PTHREAD_CC])
# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
if test x"$ax_pthread_ok" = xyes; then
ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
:
else
ax_pthread_ok=no
$2
fi
AC_LANG_POP
])dnl AX_PTHREAD
================================================
FILE: pgo.sh
================================================
#!/bin/sh
set -e
cd "$(dirname "$0")"
make clean
./build.sh CFLAGS="$CFLAGS -fprofile-generate"
./ag example ..
make clean
./build.sh CFLAGS="$CFLAGS -fprofile-correction -fprofile-use"
================================================
FILE: sanitize.sh
================================================
#!/bin/bash
# Copyright 2016 Allen Wild
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
AVAILABLE_SANITIZERS=(
address
thread
undefined
valgrind
)
DEFAULT_SANITIZERS=(
address
thread
undefined
)
usage() {
cat <<EOF
Usage: $0 [-h] [valgrind | [SANITIZERS ...]]
This script recompiles ag using -fsanitize=<SANITIZER> and then runs the test suite.
Memory leaks or other errors will be printed in ag's output, thus failing the test.
Available LLVM sanitizers are: ${AVAILABLE_SANITIZERS[*]}
The compile-time sanitizers are supported in clang/llvm >= 3.1 and gcc >= 4.8
for x86_64 Linux only. clang is preferred and will be used, if available.
For function names and line numbers in error output traces, llvm-symbolizer needs
to be available in PATH or set through ASAN_SYMBOLIZER_PATH.
If 'valgrind' is passed as the sanitizer, then ag will be run through valgrind
without recompiling. If $(dirname $0)/ag doesn't exist, then it will be built.
WARNING: This script will run "make distclean" and "./configure" to recompile ag
once per sanitizer (except for valgrind). If you need to pass additional
options to ./configure, put them in the CONFIGOPTS environment variable.
EOF
}
vrun() {
echo "Running: $*"
"$@"
}
die() {
echo "Fatal: $*"
exit 1
}
valid_sanitizer() {
for san in "${AVAILABLE_SANITIZERS[@]}"; do
if [[ "$1" == "$san" ]]; then
return 0
fi
done
return 1
}
run_sanitizer() {
sanitizer=$1
if [[ "$sanitizer" == "valgrind" ]]; then
run_valgrind
return $?
fi
echo -e "\nCompiling for sanitizer '$sanitizer'"
[[ -f Makefile ]] && vrun make distclean
vrun ./configure $CONFIGOPTS CC=$SANITIZE_CC \
CFLAGS="-g -O0 -fsanitize=$sanitizer $EXTRA_CFLAGS"
if [[ $? != 0 ]]; then
echo "ERROR: Failed to configure. Try setting CONFIGOPTS?"
return 1
fi
vrun make
if [[ $? != 0 ]]; then
echo "ERROR: failed to build"
return 1
fi
echo "Testing with sanitizer '$sanitizer'"
vrun make test
if [[ $? != 0 ]]; then
echo "Tests for sanitizer '$sanitizer' FAIL!"
echo "Check the above output for failure information"
return 2
else
echo "Tests for sanitizer '$sanitizer' PASS!"
return 0
fi
}
run_valgrind() {
echo "Compiling ag normally for use with valgrind"
[[ -f Makefile ]] && vrun make distclean
vrun ./configure $CONFIGOPTS
if [[ $? != 0 ]]; then
echo "ERROR: Failed to configure. Try setting CONFIGOPTS?"
return 1
fi
vrun make
if [[ $? != 0 ]]; then
echo "ERROR: failed to build"
return 1
fi
echo "Running: AGPROG=\"valgrind -q $PWD/ag\" make test"
AGPROG="valgrind -q $PWD/ag" make test
if [[ $? != 0 ]]; then
echo "Valgrind tests FAIL!"
return 1
else
echo "Valgrind tests PASS!"
return 0
fi
}
#### MAIN ####
run_sanitizers=()
for opt in "$@"; do
if [[ "$opt" == -* ]]; then
case opt in
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: '$opt'"
usage
exit 1
;;
esac
else
if valid_sanitizer "$opt"; then
run_sanitizers+=("$opt")
else
echo "Invalid Sanitizer: '$opt'"
usage
exit 1
fi
fi
done
if [[ ${#run_sanitizers[@]} == 0 ]]; then
run_sanitizers=(${DEFAULT_SANITIZERS[@]})
fi
if [[ -n $CC ]]; then
echo "Using CC=$CC"
SANITIZE_CC="$CC"
elif which clang &>/dev/null; then
SANITIZE_CC="clang"
else
echo "Warning: CC unset and clang not found"
fi
if [[ -n $CFLAGS ]]; then
EXTRA_CFLAGS="$CFLAGS"
unset CFLAGS
fi
if [[ ! -e ./configure ]]; then
echo "Warning: ./configure not found. Running autogen"
vrun ./autogen.sh || die "autogen.sh failed"
fi
echo "Running sanitizers: ${run_sanitizers[*]}"
failedsan=()
for san in "${run_sanitizers[@]}"; do
run_sanitizer $san
if [[ $? != 0 ]]; then
failedsan+=($san)
fi
done
if [[ ${#failedsan[@]} == 0 ]]; then
echo "All sanitizers PASSED"
exit 0
else
echo "The following sanitizers FAILED: ${failedsan[*]}"
exit ${#failedsan[@]}
fi
================================================
FILE: src/decompress.c
================================================
#include <string.h>
#include <unistd.h>
#include "decompress.h"
#ifdef HAVE_LZMA_H
#include <lzma.h>
/* http://tukaani.org/xz/xz-file-format.txt */
const uint8_t XZ_HEADER_MAGIC[6] = { 0xFD, '7', 'z', 'X', 'Z', 0x00 };
const uint8_t LZMA_HEADER_SOMETIMES[3] = { 0x5D, 0x00, 0x00 };
#endif
#ifdef HAVE_ZLIB_H
#define ZLIB_CONST 1
#include <zlib.h>
/* Code in decompress_zlib from
*
* https://raw.github.com/madler/zlib/master/examples/zpipe.c
*
* zpipe.c: example of proper use of zlib's inflate() and deflate()
* Not copyrighted -- provided to the public domain
* Version 1.4 11 December 2005 Mark Adler
*/
static void *decompress_zlib(const void *buf, const int buf_len,
const char *dir_full_path, int *new_buf_len) {
int ret = 0;
unsigned char *result = NULL;
size_t result_size = 0;
size_t pagesize = 0;
z_stream stream;
log_debug("Decompressing zlib file %s", dir_full_path);
/* allocate inflate state */
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.avail_in = 0;
stream.next_in = Z_NULL;
/* Add 32 to allow zlib and gzip format detection */
if (inflateInit2(&stream, 32 + 15) != Z_OK) {
log_err("Unable to initialize zlib: %s", stream.msg);
goto error_out;
}
stream.avail_in = buf_len;
/* Explicitly cast away the const-ness of buf */
stream.next_in = (Bytef *)buf;
pagesize = getpagesize();
result_size = ((buf_len + pagesize - 1) & ~(pagesize - 1));
do {
do {
unsigned char *tmp_result = result;
/* Double the buffer size and realloc */
result_size *= 2;
result = (unsigned char *)realloc(result, result_size * sizeof(unsigned char));
if (result == NULL) {
free(tmp_result);
log_err("Unable to allocate %d bytes to decompress file %s", result_size * sizeof(unsigned char), dir_full_path);
inflateEnd(&stream);
goto error_out;
}
stream.avail_out = result_size / 2;
stream.next_out = &result[stream.total_out];
ret = inflate(&stream, Z_SYNC_FLUSH);
log_debug("inflate ret = %d", ret);
switch (ret) {
case Z_STREAM_ERROR: {
log_err("Found stream error while decompressing zlib stream: %s", stream.msg);
inflateEnd(&stream);
goto error_out;
}
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR: {
log_err("Found mem/data error while decompressing zlib stream: %s", stream.msg);
inflateEnd(&stream);
goto error_out;
}
}
} while (stream.avail_out == 0);
} while (ret == Z_OK);
*new_buf_len = stream.total_out;
inflateEnd(&stream);
if (ret == Z_STREAM_END) {
return result;
}
error_out:
*new_buf_len = 0;
return NULL;
}
#endif
static void *decompress_lzw(const void *buf, const int buf_len,
const char *dir_full_path, int *new_buf_len) {
(void)buf;
(void)buf_len;
log_err("LZW (UNIX compress) files not yet supported: %s", dir_full_path);
*new_buf_len = 0;
return NULL;
}
static void *decompress_zip(const void *buf, const int buf_len,
const char *dir_full_path, int *new_buf_len) {
(void)buf;
(void)buf_len;
log_err("Zip files not yet supported: %s", dir_full_path);
*new_buf_len = 0;
return NULL;
}
#ifdef HAVE_LZMA_H
static void *decompress_lzma(const void *buf, const int buf_len,
const char *dir_full_path, int *new_buf_len) {
lzma_stream stream = LZMA_STREAM_INIT;
lzma_ret lzrt;
unsigned char *result = NULL;
size_t result_size = 0;
size_t pagesize = 0;
stream.avail_in = buf_len;
stream.next_in = buf;
lzrt = lzma_auto_decoder(&stream, -1, 0);
if (lzrt != LZMA_OK) {
log_err("Unable to initialize lzma_auto_decoder: %d", lzrt);
goto error_out;
}
pagesize = getpagesize();
result_size = ((buf_len + pagesize - 1) & ~(pagesize - 1));
do {
do {
unsigned char *tmp_result = result;
/* Double the buffer size and realloc */
result_size *= 2;
result = (unsigned char *)realloc(result, result_size * sizeof(unsigned char));
if (result == NULL) {
free(tmp_result);
log_err("Unable to allocate %d bytes to decompress file %s", result_size * sizeof(unsigned char), dir_full_path);
goto error_out;
}
stream.avail_out = result_size / 2;
stream.next_out = &result[stream.total_out];
lzrt = lzma_code(&stream, LZMA_RUN);
log_debug("lzma_code ret = %d", lzrt);
switch (lzrt) {
case LZMA_OK:
case LZMA_STREAM_END:
break;
default:
log_err("Found mem/data error while decompressing xz/lzma stream: %d", lzrt);
goto error_out;
}
} while (stream.avail_out == 0);
} while (lzrt == LZMA_OK);
*new_buf_len = stream.total_out;
if (lzrt == LZMA_STREAM_END) {
lzma_end(&stream);
return result;
}
error_out:
lzma_end(&stream);
*new_buf_len = 0;
if (result) {
free(result);
}
return NULL;
}
#endif
/* This function is very hot. It's called on every file when zip is enabled. */
void *decompress(const ag_compression_type zip_type, const void *buf, const int buf_len,
const char *dir_full_path, int *new_buf_len) {
switch (zip_type) {
#ifdef HAVE_ZLIB_H
case AG_GZIP:
return decompress_zlib(buf, buf_len, dir_full_path, new_buf_len);
#endif
case AG_COMPRESS:
return decompress_lzw(buf, buf_len, dir_full_path, new_buf_len);
case AG_ZIP:
return decompress_zip(buf, buf_len, dir_full_path, new_buf_len);
#ifdef HAVE_LZMA_H
case AG_XZ:
return decompress_lzma(buf, buf_len, dir_full_path, new_buf_len);
#endif
case AG_NO_COMPRESSION:
log_err("File %s is not compressed", dir_full_path);
break;
default:
log_err("Unsupported compression type: %d", zip_type);
}
*new_buf_len = 0;
return NULL;
}
/* This function is very hot. It's called on every file. */
ag_compression_type is_zipped(const void *buf, const int buf_len) {
/* Zip magic numbers
* compressed file: { 0x1F, 0x9B }
* http://en.wikipedia.org/wiki/Compress
*
* gzip file: { 0x1F, 0x8B }
* http://www.gzip.org/zlib/rfc-gzip.html#file-format
*
* zip file: { 0x50, 0x4B, 0x03, 0x04 }
* http://www.pkware.com/documents/casestudies/APPNOTE.TXT (Section 4.3)
*/
const unsigned char *buf_c = buf;
if (buf_len == 0)
return AG_NO_COMPRESSION;
/* Check for gzip & compress */
if (buf_len >= 2) {
if (buf_c[0] == 0x1F) {
if (buf_c[1] == 0x8B) {
#ifdef HAVE_ZLIB_H
log_debug("Found gzip-based stream");
return AG_GZIP;
#endif
} else if (buf_c[1] == 0x9B) {
log_debug("Found compress-based stream");
return AG_COMPRESS;
}
}
}
/* Check for zip */
if (buf_len >= 4) {
if (buf_c[0] == 0x50 && buf_c[1] == 0x4B && buf_c[2] == 0x03 && buf_c[3] == 0x04) {
log_debug("Found zip-based stream");
return AG_ZIP;
}
}
#ifdef HAVE_LZMA_H
if (buf_len >= 6) {
if (memcmp(XZ_HEADER_MAGIC, buf_c, 6) == 0) {
log_debug("Found xz based stream");
return AG_XZ;
}
}
/* LZMA doesn't really have a header: http://www.mail-archive.com/xz-devel@tukaani.org/msg00003.html */
if (buf_len >= 3) {
if (memcmp(LZMA_HEADER_SOMETIMES, buf_c, 3) == 0) {
log_debug("Found lzma-based stream");
return AG_XZ;
}
}
#endif
return AG_NO_COMPRESSION;
}
================================================
FILE: src/decompress.h
================================================
#ifndef DECOMPRESS_H
#define DECOMPRESS_H
#include <stdio.h>
#include "config.h"
#include "log.h"
#include "options.h"
typedef enum {
AG_NO_COMPRESSION,
AG_GZIP,
AG_COMPRESS,
AG_ZIP,
AG_XZ,
} ag_compression_type;
ag_compression_type is_zipped(const void *buf, const int buf_len);
void *decompress(const ag_compression_type zip_type, const void *buf, const int buf_len, const char *dir_full_path, int *new_buf_len);
#if HAVE_FOPENCOOKIE
FILE *decompress_open(int fd, const char *mode, ag_compression_type ctype);
#endif
#endif
================================================
FILE: src/ignore.c
================================================
#include <ctype.h>
#include <dirent.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "ignore.h"
#include "log.h"
#include "options.h"
#include "scandir.h"
#include "util.h"
#ifdef _WIN32
#include <shlwapi.h>
#define fnmatch(x, y, z) (!PathMatchSpec(y, x))
#else
#include <fnmatch.h>
const int fnmatch_flags = FNM_PATHNAME;
#endif
ignores *root_ignores;
/* TODO: build a huge-ass list of files we want to ignore by default (build cache stuff, pyc files, etc) */
const char *evil_hardcoded_ignore_files[] = {
".",
"..",
NULL
};
/* Warning: changing the first two strings will break skip_vcs_ignores. */
const char *ignore_pattern_files[] = {
".ignore",
".gitignore",
".git/info/exclude",
".hgignore",
NULL
};
int is_empty(ignores *ig) {
return (ig->extensions_len + ig->names_len + ig->slash_names_len + ig->regexes_len + ig->slash_regexes_len == 0);
};
ignores *init_ignore(ignores *parent, const char *dirname, const size_t dirname_len) {
ignores *ig = ag_malloc(sizeof(ignores));
ig->extensions = NULL;
ig->extensions_len = 0;
ig->names = NULL;
ig->names_len = 0;
ig->slash_names = NULL;
ig->slash_names_len = 0;
ig->regexes = NULL;
ig->regexes_len = 0;
ig->invert_regexes = NULL;
ig->invert_regexes_len = 0;
ig->slash_regexes = NULL;
ig->slash_regexes_len = 0;
ig->dirname = dirname;
ig->dirname_len = dirname_len;
if (parent && is_empty(parent) && parent->parent) {
ig->parent = parent->parent;
} else {
ig->parent = parent;
}
if (parent && parent->abs_path_len > 0) {
ag_asprintf(&(ig->abs_path), "%s/%s", parent->abs_path, dirname);
ig->abs_path_len = parent->abs_path_len + 1 + dirname_len;
} else if (dirname_len == 1 && dirname[0] == '.') {
ig->abs_path = ag_malloc(sizeof(char));
ig->abs_path[0] = '\0';
ig->abs_path_len = 0;
} else {
ag_asprintf(&(ig->abs_path), "%s", dirname);
ig->abs_path_len = dirname_len;
}
return ig;
}
void cleanup_ignore(ignores *ig) {
if (ig == NULL) {
return;
}
free_strings(ig->extensions, ig->extensions_len);
free_strings(ig->names, ig->names_len);
free_strings(ig->slash_names, ig->slash_names_len);
free_strings(ig->regexes, ig->regexes_len);
free_strings(ig->invert_regexes, ig->invert_regexes_len);
free_strings(ig->slash_regexes, ig->slash_regexes_len);
if (ig->abs_path) {
free(ig->abs_path);
}
free(ig);
}
void add_ignore_pattern(ignores *ig, const char *pattern) {
int i;
int pattern_len;
/* Strip off the leading dot so that matches are more likely. */
if (strncmp(pattern, "./", 2) == 0) {
pattern++;
}
/* Kill trailing whitespace */
for (pattern_len = strlen(pattern); pattern_len > 0; pattern_len--) {
if (!isspace(pattern[pattern_len - 1])) {
break;
}
}
if (pattern_len == 0) {
log_debug("Pattern is empty. Not adding any ignores.");
return;
}
char ***patterns_p;
size_t *patterns_len;
if (is_fnmatch(pattern)) {
if (pattern[0] == '*' && pattern[1] == '.' && strchr(pattern + 2, '.') && !is_fnmatch(pattern + 2)) {
patterns_p = &(ig->extensions);
patterns_len = &(ig->extensions_len);
pattern += 2;
pattern_len -= 2;
} else if (pattern[0] == '/') {
patterns_p = &(ig->slash_regexes);
patterns_len = &(ig->slash_regexes_len);
pattern++;
pattern_len--;
} else if (pattern[0] == '!') {
patterns_p = &(ig->invert_regexes);
patterns_len = &(ig->invert_regexes_len);
pattern++;
pattern_len--;
} else {
patterns_p = &(ig->regexes);
patterns_len = &(ig->regexes_len);
}
} else {
if (pattern[0] == '/') {
patterns_p = &(ig->slash_names);
patterns_len = &(ig->slash_names_len);
pattern++;
pattern_len--;
} else {
patterns_p = &(ig->names);
patterns_len = &(ig->names_len);
}
}
++*patterns_len;
char **patterns;
/* a balanced binary tree is best for performance, but I'm lazy */
*patterns_p = patterns = ag_realloc(*patterns_p, (*patterns_len) * sizeof(char *));
/* TODO: de-dupe these patterns */
for (i = *patterns_len - 1; i > 0; i--) {
if (strcmp(pattern, patterns[i - 1]) > 0) {
break;
}
patterns[i] = patterns[i - 1];
}
patterns[i] = ag_strndup(pattern, pattern_len);
log_debug("added ignore pattern %s to %s", pattern,
ig == root_ignores ? "root ignores" : ig->abs_path);
}
/* For loading git/hg ignore patterns */
void load_ignore_patterns(ignores *ig, const char *path) {
FILE *fp = NULL;
fp = fopen(path, "r");
if (fp == NULL) {
log_debug("Skipping ignore file %s: not readable", path);
return;
}
log_debug("Loading ignore file %s.", path);
char *line = NULL;
ssize_t line_len = 0;
size_t line_cap = 0;
while ((line_len = getline(&line, &line_cap, fp)) > 0) {
if (line_len == 0 || line[0] == '\n' || line[0] == '#') {
continue;
}
if (line[line_len - 1] == '\n') {
line[line_len - 1] = '\0'; /* kill the \n */
}
add_ignore_pattern(ig, line);
}
free(line);
fclose(fp);
}
static int ackmate_dir_match(const char *dir_name) {
if (opts.ackmate_dir_filter == NULL) {
return 0;
}
/* we just care about the match, not where the matches are */
return pcre_exec(opts.ackmate_dir_filter, NULL, dir_name, strlen(dir_name), 0, 0, NULL, 0);
}
/* This is the hottest code in Ag. 10-15% of all execution time is spent here */
static int path_ignore_search(const ignores *ig, const char *path, const char *filename) {
char *temp;
int temp_start_pos;
size_t i;
int match_pos;
match_pos = binary_search(filename, ig->names, 0, ig->names_len);
if (match_pos >= 0) {
log_debug("file %s ignored because name matches static pattern %s", filename, ig->names[match_pos]);
return 1;
}
ag_asprintf(&temp, "%s/%s", path[0] == '.' ? path + 1 : path, filename);
//ig->abs_path has its leading slash stripped, so we have to strip the leading slash
//of temp as well
temp_start_pos = (temp[0] == '/') ? 1 : 0;
if (strncmp(temp + temp_start_pos, ig->abs_path, ig->abs_path_len) == 0) {
char *slash_filename = temp + temp_start_pos + ig->abs_path_len;
if (slash_filename[0] == '/') {
slash_filename++;
}
match_pos = binary_search(slash_filename, ig->names, 0, ig->names_len);
if (match_pos >= 0) {
log_debug("file %s ignored because name matches static pattern %s", temp, ig->names[match_pos]);
free(temp);
return 1;
}
match_pos = binary_search(slash_filename, ig->slash_names, 0, ig->slash_names_len);
if (match_pos >= 0) {
log_debug("file %s ignored because name matches slash static pattern %s", slash_filename, ig->slash_names[match_pos]);
free(temp);
return 1;
}
for (i = 0; i < ig->names_len; i++) {
char *pos = strstr(slash_filename, ig->names[i]);
if (pos == slash_filename || (pos && *(pos - 1) == '/')) {
pos += strlen(ig->names[i]);
if (*pos == '\0' || *pos == '/') {
log_debug("file %s ignored because path somewhere matches name %s", slash_filename, ig->names[i]);
free(temp);
return 1;
}
}
log_debug("pattern %s doesn't match path %s", ig->names[i], slash_filename);
}
for (i = 0; i < ig->slash_regexes_len; i++) {
if (fnmatch(ig->slash_regexes[i], slash_filename, fnmatch_flags) == 0) {
log_debug("file %s ignored because name matches slash regex pattern %s", slash_filename, ig->slash_regexes[i]);
free(temp);
return 1;
}
log_debug("pattern %s doesn't match slash file %s", ig->slash_regexes[i], slash_filename);
}
}
for (i = 0; i < ig->invert_regexes_len; i++) {
if (fnmatch(ig->invert_regexes[i], filename, fnmatch_flags) == 0) {
log_debug("file %s not ignored because name matches regex pattern !%s", filename, ig->invert_regexes[i]);
free(temp);
return 0;
}
log_debug("pattern !%s doesn't match file %s", ig->invert_regexes[i], filename);
}
for (i = 0; i < ig->regexes_len; i++) {
if (fnmatch(ig->regexes[i], filename, fnmatch_flags) == 0) {
log_debug("file %s ignored because name matches regex pattern %s", filename, ig->regexes[i]);
free(temp);
return 1;
}
log_debug("pattern %s doesn't match file %s", ig->regexes[i], filename);
}
int rv = ackmate_dir_match(temp);
free(temp);
return rv;
}
/* This function is REALLY HOT. It gets called for every file */
int filename_filter(const char *path, const struct dirent *dir, void *baton) {
const char *filename = dir->d_name;
if (!opts.search_hidden_files && filename[0] == '.') {
return 0;
}
size_t i;
for (i = 0; evil_hardcoded_ignore_files[i] != NULL; i++) {
if (strcmp(filename, evil_hardcoded_ignore_files[i]) == 0) {
return 0;
}
}
if (!opts.follow_symlinks && is_symlink(path, dir)) {
log_debug("File %s ignored becaused it's a symlink", dir->d_name);
return 0;
}
if (is_named_pipe(path, dir)) {
log_debug("%s ignored because it's a named pipe or socket", path);
return 0;
}
if (opts.search_all_files && !opts.path_to_ignore) {
return 1;
}
scandir_baton_t *scandir_baton = (scandir_baton_t *)baton;
const char *path_start = scandir_baton->path_start;
const char *extension = strchr(filename, '.');
if (extension) {
if (extension[1]) {
// The dot is not the last character, extension starts at the next one
++extension;
} else {
// No extension
extension = NULL;
}
}
#ifdef HAVE_DIRENT_DNAMLEN
size_t filename_len = dir->d_namlen;
#else
size_t filename_len = 0;
#endif
if (strncmp(filename, "./", 2) == 0) {
#ifndef HAVE_DIRENT_DNAMLEN
filename_len = strlen(filename);
#endif
filename++;
filename_len--;
}
const ignores *ig = scandir_baton->ig;
while (ig != NULL) {
if (extension) {
int match_pos = binary_search(extension, ig->extensions, 0, ig->extensions_len);
if (match_pos >= 0) {
log_debug("file %s ignored because name matches extension %s", filename, ig->extensions[match_pos]);
return 0;
}
}
if (path_ignore_search(ig, path_start, filename)) {
return 0;
}
if (is_directory(path, dir)) {
#ifndef HAVE_DIRENT_DNAMLEN
if (!filename_len) {
filename_len = strlen(filename);
}
#endif
if (filename[filename_len - 1] != '/') {
char *temp;
ag_asprintf(&temp, "%s/", filename);
int rv = path_ignore_search(ig, path_start, temp);
free(temp);
if (rv) {
return 0;
}
}
}
ig = ig->parent;
}
log_debug("%s not ignored", filename);
return 1;
}
================================================
FILE: src/ignore.h
================================================
#ifndef IGNORE_H
#define IGNORE_H
#include <dirent.h>
#include <sys/types.h>
struct ignores {
char **extensions; /* File extensions to ignore */
size_t extensions_len;
char **names; /* Non-regex ignore lines. Sorted so we can binary search them. */
size_t names_len;
char **slash_names; /* Same but starts with a slash */
size_t slash_names_len;
char **regexes; /* For patterns that need fnmatch */
size_t regexes_len;
char **invert_regexes; /* For "!" patterns */
size_t invert_regexes_len;
char **slash_regexes;
size_t slash_regexes_len;
const char *dirname;
size_t dirname_len;
char *abs_path;
size_t abs_path_len;
struct ignores *parent;
};
typedef struct ignores ignores;
extern ignores *root_ignores;
extern const char *evil_hardcoded_ignore_files[];
extern const char *ignore_pattern_files[];
ignores *init_ignore(ignores *parent, const char *dirname, const size_t dirname_len);
void cleanup_ignore(ignores *ig);
void add_ignore_pattern(ignores *ig, const char *pattern);
void load_ignore_patterns(ignores *ig, const char *path);
int filename_filter(const char *path, const struct dirent *dir, void *baton);
int is_empty(ignores *ig);
#endif
================================================
FILE: src/lang.c
================================================
#include <stdlib.h>
#include <string.h>
#include "lang.h"
#include "util.h"
lang_spec_t langs[] = {
{ "actionscript", { "as", "mxml" } },
{ "ada", { "ada", "adb", "ads" } },
{ "asciidoc", { "adoc", "ad", "asc", "asciidoc" } },
{ "apl", { "apl" } },
{ "asm", { "asm", "s" } },
{ "asp", { "asp", "asa", "aspx", "asax", "ashx", "ascx", "asmx" } },
{ "aspx", { "asp", "asa", "aspx", "asax", "ashx", "ascx", "asmx" } },
{ "batch", { "bat", "cmd" } },
{ "bazel", { "bazel" } },
{ "bitbake", { "bb", "bbappend", "bbclass", "inc" } },
{ "cc", { "c", "h", "xs" } },
{ "cfmx", { "cfc", "cfm", "cfml" } },
{ "chpl", { "chpl" } },
{ "clojure", { "clj", "cljs", "cljc", "cljx", "edn" } },
{ "coffee", { "coffee", "cjsx" } },
{ "config", { "config" } },
{ "coq", { "coq", "g", "v" } },
{ "cpp", { "cpp", "cc", "C", "cxx", "m", "hpp", "hh", "h", "H", "hxx", "tpp" } },
{ "crystal", { "cr", "ecr" } },
{ "csharp", { "cs" } },
{ "cshtml", { "cshtml" } },
{ "css", { "css" } },
{ "cython", { "pyx", "pxd", "pxi" } },
{ "delphi", { "pas", "int", "dfm", "nfm", "dof", "dpk", "dpr", "dproj", "groupproj", "bdsgroup", "bdsproj" } },
{ "dlang", { "d", "di" } },
{ "dot", { "dot", "gv" } },
{ "dts", { "dts", "dtsi" } },
{ "ebuild", { "ebuild", "eclass" } },
{ "elisp", { "el" } },
{ "elixir", { "ex", "eex", "exs" } },
{ "elm", { "elm" } },
{ "erlang", { "erl", "hrl" } },
{ "factor", { "factor" } },
{ "fortran", { "f", "F", "f77", "f90", "F90", "f95", "f03", "for", "ftn", "fpp", "FPP" } },
{ "fsharp", { "fs", "fsi", "fsx" } },
{ "gettext", { "po", "pot", "mo" } },
{ "glsl", { "vert", "tesc", "tese", "geom", "frag", "comp" } },
{ "go", { "go" } },
{ "gradle", { "gradle" } },
{ "groovy", { "groovy", "gtmpl", "gpp", "grunit", "gradle" } },
{ "haml", { "haml" } },
{ "handlebars", { "hbs" } },
{ "haskell", { "hs", "hsig", "lhs" } },
{ "haxe", { "hx" } },
{ "hh", { "h" } },
{ "html", { "htm", "html", "shtml", "xhtml" } },
{ "idris", { "idr", "ipkg", "lidr" } },
{ "ini", { "ini" } },
{ "ipython", { "ipynb" } },
{ "isabelle", { "thy" } },
{ "j", { "ijs" } },
{ "jade", { "jade" } },
{ "java", { "java", "properties" } },
{ "jinja2", { "j2" } },
{ "js", { "es6", "js", "jsx", "vue" } },
{ "json", { "json" } },
{ "jsp", { "jsp", "jspx", "jhtm", "jhtml", "jspf", "tag", "tagf" } },
{ "julia", { "jl" } },
{ "kotlin", { "kt" } },
{ "less", { "less" } },
{ "liquid", { "liquid" } },
{ "lisp", { "lisp", "lsp" } },
{ "log", { "log" } },
{ "lua", { "lua" } },
{ "m4", { "m4" } },
{ "make", { "Makefiles", "mk", "mak" } },
{ "mako", { "mako" } },
{ "markdown", { "markdown", "mdown", "mdwn", "mkdn", "mkd", "md" } },
{ "mason", { "mas", "mhtml", "mpl", "mtxt" } },
{ "matlab", { "m" } },
{ "mathematica", { "m", "wl" } },
{ "md", { "markdown", "mdown", "mdwn", "mkdn", "mkd", "md" } },
{ "mercury", { "m", "moo" } },
{ "naccess", { "asa", "rsa" } },
{ "nim", { "nim" } },
{ "nix", { "nix" } },
{ "objc", { "m", "h" } },
{ "objcpp", { "mm", "h" } },
{ "ocaml", { "ml", "mli", "mll", "mly" } },
{ "octave", { "m" } },
{ "org", { "org" } },
{ "parrot", { "pir", "pasm", "pmc", "ops", "pod", "pg", "tg" } },
{ "pdb", { "pdb" } },
{ "perl", { "pl", "pm", "pm6", "pod", "t" } },
{ "php", { "php", "phpt", "php3", "php4", "php5", "phtml" } },
{ "pike", { "pike", "pmod" } },
{ "plist", { "plist" } },
{ "plone", { "pt", "cpt", "metadata", "cpy", "py", "xml", "zcml" } },
{ "powershell", { "ps1" } },
{ "proto", { "proto" } },
{ "ps1", { "ps1" } },
{ "pug", { "pug" } },
{ "puppet", { "pp" } },
{ "python", { "py" } },
{ "qml", { "qml" } },
{ "racket", { "rkt", "ss", "scm" } },
{ "rake", { "Rakefile" } },
{ "razor", { "cshtml" } },
{ "restructuredtext", { "rst" } },
{ "rs", { "rs" } },
{ "r", { "r", "R", "Rmd", "Rnw", "Rtex", "Rrst" } },
{ "rdoc", { "rdoc" } },
{ "ruby", { "rb", "rhtml", "rjs", "rxml", "erb", "rake", "spec" } },
{ "rust", { "rs" } },
{ "salt", { "sls" } },
{ "sass", { "sass", "scss" } },
{ "scala", { "scala" } },
{ "scheme", { "scm", "ss" } },
{ "shell", { "sh", "bash", "csh", "tcsh", "ksh", "zsh", "fish" } },
{ "smalltalk", { "st" } },
{ "sml", { "sml", "fun", "mlb", "sig" } },
{ "sql", { "sql", "ctl" } },
{ "stata", { "do", "ado" } },
{ "stylus", { "styl" } },
{ "swift", { "swift" } },
{ "tcl", { "tcl", "itcl", "itk" } },
{ "terraform", { "tf", "tfvars" } },
{ "tex", { "tex", "cls", "sty" } },
{ "thrift", { "thrift" } },
{ "tla", { "tla" } },
{ "tt", { "tt", "tt2", "ttml" } },
{ "toml", { "toml" } },
{ "ts", { "ts", "tsx" } },
{ "twig", { "twig" } },
{ "vala", { "vala", "vapi" } },
{ "vb", { "bas", "cls", "frm", "ctl", "vb", "resx" } },
{ "velocity", { "vm", "vtl", "vsl" } },
{ "verilog", { "v", "vh", "sv", "svh" } },
{ "vhdl", { "vhd", "vhdl" } },
{ "vim", { "vim" } },
{ "vue", { "vue" } },
{ "wix", { "wxi", "wxs" } },
{ "wsdl", { "wsdl" } },
{ "wadl", { "wadl" } },
{ "xml", { "xml", "dtd", "xsl", "xslt", "xsd", "ent", "tld", "plist", "wsdl" } },
{ "yaml", { "yaml", "yml" } },
{ "zeek", { "zeek", "bro", "bif" } },
{ "zephir", { "zep" } }
};
size_t get_lang_count() {
return sizeof(langs) / sizeof(lang_spec_t);
}
char *make_lang_regex(char *ext_array, size_t num_exts) {
int regex_capacity = 100;
char *regex = ag_malloc(regex_capacity);
int regex_length = 3;
int subsequent = 0;
char *extension;
size_t i;
strcpy(regex, "\\.(");
for (i = 0; i < num_exts; ++i) {
extension = ext_array + i * SINGLE_EXT_LEN;
int extension_length = strlen(extension);
while (regex_length + extension_length + 3 + subsequent > regex_capacity) {
regex_capacity *= 2;
regex = ag_realloc(regex, regex_capacity);
}
if (subsequent) {
regex[regex_length++] = '|';
} else {
subsequent = 1;
}
strcpy(regex + regex_length, extension);
regex_length += extension_length;
}
regex[regex_length++] = ')';
regex[regex_length++] = '$';
regex[regex_length++] = 0;
return regex;
}
size_t combine_file_extensions(size_t *extension_index, size_t len, char **exts) {
/* Keep it fixed as 100 for the reason that if you have more than 100
* file types to search, you'd better search all the files.
* */
size_t ext_capacity = 100;
(*exts) = (char *)ag_malloc(ext_capacity * SINGLE_EXT_LEN);
memset((*exts), 0, ext_capacity * SINGLE_EXT_LEN);
size_t num_of_extensions = 0;
size_t i;
for (i = 0; i < len; ++i) {
size_t j = 0;
const char *ext = langs[extension_index[i]].extensions[j];
do {
if (num_of_extensions == ext_capacity) {
break;
}
char *pos = (*exts) + num_of_extensions * SINGLE_EXT_LEN;
strncpy(pos, ext, strlen(ext));
++num_of_extensions;
ext = langs[extension_index[i]].extensions[++j];
} while (ext);
}
return num_of_extensions;
}
================================================
FILE: src/lang.h
================================================
#ifndef LANG_H
#define LANG_H
#define MAX_EXTENSIONS 12
#define SINGLE_EXT_LEN 20
typedef struct {
const char *name;
const char *extensions[MAX_EXTENSIONS];
} lang_spec_t;
extern lang_spec_t langs[];
/**
Return the language count.
*/
size_t get_lang_count(void);
/**
Convert a NULL-terminated array of language extensions
into a regular expression of the form \.(extension1|extension2...)$
Caller is responsible for freeing the returned string.
*/
char *make_lang_regex(char *ext_array, size_t num_exts);
/**
Combine multiple file type extensions into one array.
The combined result is returned through *exts*;
*exts* is one-dimension array, which can contain up to 100 extensions;
The number of extensions that *exts* actually contain is returned.
*/
size_t combine_file_extensions(size_t *extension_index, size_t len, char **exts);
#endif
================================================
FILE: src/log.c
================================================
#include <stdarg.h>
#include <stdio.h>
#include "log.h"
#include "util.h"
pthread_mutex_t print_mtx = PTHREAD_MUTEX_INITIALIZER;
static enum log_level log_threshold = LOG_LEVEL_ERR;
void set_log_level(enum log_level threshold) {
log_threshold = threshold;
}
void log_debug(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vplog(LOG_LEVEL_DEBUG, fmt, args);
va_end(args);
}
void log_msg(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vplog(LOG_LEVEL_MSG, fmt, args);
va_end(args);
}
void log_warn(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vplog(LOG_LEVEL_WARN, fmt, args);
va_end(args);
}
void log_err(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vplog(LOG_LEVEL_ERR, fmt, args);
va_end(args);
}
void vplog(const unsigned int level, const char *fmt, va_list args) {
if (level < log_threshold) {
return;
}
pthread_mutex_lock(&print_mtx);
FILE *stream = out_fd;
switch (level) {
case LOG_LEVEL_DEBUG:
fprintf(stream, "DEBUG: ");
break;
case LOG_LEVEL_MSG:
fprintf(stream, "MSG: ");
break;
case LOG_LEVEL_WARN:
fprintf(stream, "WARN: ");
break;
case LOG_LEVEL_ERR:
stream = stderr;
fprintf(stream, "ERR: ");
break;
}
vfprintf(stream, fmt, args);
fprintf(stream, "\n");
pthread_mutex_unlock(&print_mtx);
}
void plog(const unsigned int level, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vplog(level, fmt, args);
va_end(args);
}
================================================
FILE: src/log.h
================================================
#ifndef LOG_H
#define LOG_H
#include <stdarg.h>
#include "config.h"
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#endif
extern pthread_mutex_t print_mtx;
enum log_level {
LOG_LEVEL_DEBUG = 10,
LOG_LEVEL_MSG = 20,
LOG_LEVEL_WARN = 30,
LOG_LEVEL_ERR = 40,
LOG_LEVEL_NONE = 100
};
void set_log_level(enum log_level threshold);
void log_debug(const char *fmt, ...);
void log_msg(const char *fmt, ...);
void log_warn(const char *fmt, ...);
void log_err(const char *fmt, ...);
void vplog(const unsigned int level, const char *fmt, va_list args);
void plog(const unsigned int level, const char *fmt, ...);
#endif
================================================
FILE: src/main.c
================================================
#include <ctype.h>
#include <pcre.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include "config.h"
#ifdef HAVE_SYS_CPUSET_H
#include <sys/cpuset.h>
#endif
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#endif
#if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(__FreeBSD__)
#include <pthread_np.h>
#endif
#include "log.h"
#include "options.h"
#include "search.h"
#include "util.h"
typedef struct {
pthread_t thread;
int id;
} worker_t;
int main(int argc, char **argv) {
char **base_paths = NULL;
char **paths = NULL;
int i;
int pcre_opts = PCRE_MULTILINE;
int study_opts = 0;
worker_t *workers = NULL;
int workers_len;
int num_cores;
#ifdef HAVE_PLEDGE
if (pledge("stdio rpath proc exec", NULL) == -1) {
die("pledge: %s", strerror(errno));
}
#endif
set_log_level(LOG_LEVEL_WARN);
work_queue = NULL;
work_queue_tail = NULL;
root_ignores = init_ignore(NULL, "", 0);
out_fd = stdout;
parse_options(argc, argv, &base_paths, &paths);
log_debug("PCRE Version: %s", pcre_version());
if (opts.stats) {
memset(&stats, 0, sizeof(stats));
gettimeofday(&(stats.time_start), NULL);
}
#ifdef USE_PCRE_JIT
int has_jit = 0;
pcre_config(PCRE_CONFIG_JIT, &has_jit);
if (has_jit) {
study_opts |= PCRE_STUDY_JIT_COMPILE;
}
#endif
#ifdef _WIN32
{
SYSTEM_INFO si;
GetSystemInfo(&si);
num_cores = si.dwNumberOfProcessors;
}
#else
num_cores = (int)sysconf(_SC_NPROCESSORS_ONLN);
#endif
workers_len = num_cores < 8 ? num_cores : 8;
if (opts.literal) {
workers_len--;
}
if (opts.workers) {
workers_len = opts.workers;
}
if (workers_len < 1) {
workers_len = 1;
}
log_debug("Using %i workers", workers_len);
done_adding_files = FALSE;
workers = ag_calloc(workers_len, sizeof(worker_t));
if (pthread_cond_init(&files_ready, NULL)) {
die("pthread_cond_init failed!");
}
if (pthread_mutex_init(&print_mtx, NULL)) {
die("pthread_mutex_init failed!");
}
if (opts.stats && pthread_mutex_init(&stats_mtx, NULL)) {
die("pthread_mutex_init failed!");
}
if (pthread_mutex_init(&work_queue_mtx, NULL)) {
die("pthread_mutex_init failed!");
}
if (opts.casing == CASE_SMART) {
opts.casing = is_lowercase(opts.query) ? CASE_INSENSITIVE : CASE_SENSITIVE;
}
if (opts.literal) {
if (opts.casing == CASE_INSENSITIVE) {
/* Search routine needs the query to be lowercase */
char *c = opts.query;
for (; *c != '\0'; ++c) {
*c = (char)tolower(*c);
}
}
generate_alpha_skip(opts.query, opts.query_len, alpha_skip_lookup, opts.casing == CASE_SENSITIVE);
find_skip_lookup = NULL;
generate_find_skip(opts.query, opts.query_len, &find_skip_lookup, opts.casing == CASE_SENSITIVE);
generate_hash(opts.query, opts.query_len, h_table, opts.casing == CASE_SENSITIVE);
if (opts.word_regexp) {
init_wordchar_table();
opts.literal_starts_wordchar = is_wordchar(opts.query[0]);
opts.literal_ends_wordchar = is_wordchar(opts.query[opts.query_len - 1]);
}
} else {
if (opts.casing == CASE_INSENSITIVE) {
pcre_opts |= PCRE_CASELESS;
}
if (opts.word_regexp) {
char *word_regexp_query;
ag_asprintf(&word_regexp_query, "\\b(?:%s)\\b", opts.query);
free(opts.query);
opts.query = word_regexp_query;
opts.query_len = strlen(opts.query);
}
compile_study(&opts.re, &opts.re_extra, opts.query, pcre_opts, study_opts);
}
if (opts.search_stream) {
search_stream(stdin, "");
} else {
for (i = 0; i < workers_len; i++) {
workers[i].id = i;
int rv = pthread_create(&(workers[i].thread), NULL, &search_file_worker, &(workers[i].id));
if (rv != 0) {
die("Error in pthread_create(): %s", strerror(rv));
}
#if defined(HAVE_PTHREAD_SETAFFINITY_NP) && (defined(USE_CPU_SET) || defined(HAVE_SYS_CPUSET_H))
if (opts.use_thread_affinity) {
#if defined(__linux__) || defined(__midipix__)
cpu_set_t cpu_set;
#elif __FreeBSD__
cpuset_t cpu_set;
#endif
CPU_ZERO(&cpu_set);
CPU_SET(i % num_cores, &cpu_set);
rv = pthread_setaffinity_np(workers[i].thread, sizeof(cpu_set), &cpu_set);
if (rv) {
log_err("Error in pthread_setaffinity_np(): %s", strerror(rv));
log_err("Performance may be affected. Use --noaffinity to suppress this message.");
} else {
log_debug("Thread %i set to CPU %i", i, i);
}
} else {
log_debug("Thread affinity disabled.");
}
#else
log_debug("No CPU affinity support.");
#endif
}
#ifdef HAVE_PLEDGE
if (pledge("stdio rpath", NULL) == -1) {
die("pledge: %s", strerror(errno));
}
#endif
for (i = 0; paths[i] != NULL; i++) {
log_debug("searching path %s for %s", paths[i], opts.query);
symhash = NULL;
ignores *ig = init_ignore(root_ignores, "", 0);
struct stat s = { .st_dev = 0 };
#ifndef _WIN32
/* The device is ignored if opts.one_dev is false, so it's fine
* to leave it at the default 0
*/
if (opts.one_dev && lstat(paths[i], &s) == -1) {
log_err("Failed to get device information for path %s. Skipping...", paths[i]);
}
#endif
search_dir(ig, base_paths[i], paths[i], 0, s.st_dev);
cleanup_ignore(ig);
}
pthread_mutex_lock(&work_queue_mtx);
done_adding_files = TRUE;
pthread_cond_broadcast(&files_ready);
pthread_mutex_unlock(&work_queue_mtx);
for (i = 0; i < workers_len; i++) {
if (pthread_join(workers[i].thread, NULL)) {
die("pthread_join failed!");
}
}
}
if (opts.stats) {
gettimeofday(&(stats.time_end), NULL);
double time_diff = ((long)stats.time_end.tv_sec * 1000000 + stats.time_end.tv_usec) -
((long)stats.time_start.tv_sec * 1000000 + stats.time_start.tv_usec);
time_diff /= 1000000;
printf("%zu matches\n%zu files contained matches\n%zu files searched\n%zu bytes searched\n%f seconds\n",
stats.total_matches, stats.total_file_matches, stats.total_files, stats.total_bytes, time_diff);
pthread_mutex_destroy(&stats_mtx);
}
if (opts.pager) {
pclose(out_fd);
}
cleanup_options();
pthread_cond_destroy(&files_ready);
pthread_mutex_destroy(&work_queue_mtx);
pthread_mutex_destroy(&print_mtx);
cleanup_ignore(root_ignores);
free(workers);
for (i = 0; paths[i] != NULL; i++) {
free(paths[i]);
free(base_paths[i]);
}
free(base_paths);
free(paths);
if (find_skip_lookup) {
free(find_skip_lookup);
}
return !opts.match_found;
}
================================================
FILE: src/options.c
================================================
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <unistd.h>
#include "config.h"
#include "ignore.h"
#include "lang.h"
#include "log.h"
#include "options.h"
#include "print.h"
#include "util.h"
const char *color_line_number = "\033[1;33m"; /* bold yellow */
const char *color_match = "\033[30;43m"; /* black with yellow background */
const char *color_path = "\033[1;32m"; /* bold green */
cli_options opts;
/* TODO: try to obey out_fd? */
void usage(void) {
printf("\n");
printf("Usage: ag [FILE-TYPE] [OPTIONS] PATTERN [PATH]\n\n");
printf(" Recursively search for PATTERN in PATH.\n");
printf(" Like grep or ack, but faster.\n\n");
printf("Example:\n ag -i foo /bar/\n\n");
printf("\
Output Options:\n\
--ackmate Print results in AckMate-parseable format\n\
-A --after [LINES] Print lines after match (Default: 2)\n\
-B --before [LINES] Print lines before match (Default: 2)\n\
--[no]break Print newlines between matches in different files\n\
(Enabled by default)\n\
-c --count Only print the number of matches in each file.\n\
(This often differs from the number of matching lines)\n\
--[no]color Print color codes in results (Enabled by default)\n\
--color-line-number Color codes for line numbers (Default: 1;33)\n\
--color-match Color codes for result match numbers (Default: 30;43)\n\
--color-path Color codes for path names (Default: 1;32)\n\
");
#ifdef _WIN32
printf("\
--color-win-ansi Use ansi colors on Windows even where we can use native\n\
(pager/pipe colors are ansi regardless) (Default: off)\n\
");
#endif
printf("\
--column Print column numbers in results\n\
--[no]filename Print file names (Enabled unless searching a single file)\n\
-H --[no]heading Print file names before each file's matches\n\
(Enabled by default)\n\
-C --context [LINES] Print lines before and after matches (Default: 2)\n\
--[no]group Same as --[no]break --[no]heading\n\
-g --filename-pattern PATTERN\n\
Print filenames matching PATTERN\n\
-l --files-with-matches Only print filenames that contain matches\n\
(don't print the matching lines)\n\
-L --files-without-matches\n\
Only print filenames that don't contain matches\n\
--print-all-files Print headings for all files searched, even those that\n\
don't contain matches\n\
--[no]numbers Print line numbers. Default is to omit line numbers\n\
when searching streams\n\
-o --only-matching Prints only the matching part of the lines\n\
--print-long-lines Print matches on very long lines (Default: >2k characters)\n\
--passthrough When searching a stream, print all lines even if they\n\
don't match\n\
--silent Suppress all log messages, including errors\n\
--stats Print stats (files scanned, time taken, etc.)\n\
--stats-only Print stats and nothing else.\n\
(Same as --count when searching a single file)\n\
--vimgrep Print results like vim's :vimgrep /pattern/g would\n\
(it reports every match on the line)\n\
-0 --null --print0 Separate filenames with null (for 'xargs -0')\n\
\n\
Search Options:\n\
-a --all-types Search all files (doesn't include hidden files\n\
or patterns from ignore files)\n\
-D --debug Ridiculous debugging (probably not useful)\n\
--depth NUM Search up to NUM directories deep (Default: 25)\n\
-f --follow Follow symlinks\n\
-F --fixed-strings Alias for --literal for compatibility with grep\n\
-G --file-search-regex PATTERN Limit search to filenames matching PATTERN\n\
--hidden Search hidden files (obeys .*ignore files)\n\
-i --ignore-case Match case insensitively\n\
--ignore PATTERN Ignore files/directories matching PATTERN\n\
(literal file/directory names also allowed)\n\
--ignore-dir NAME Alias for --ignore for compatibility with ack.\n\
-m --max-count NUM Skip the rest of a file after NUM matches (Default: 10,000)\n\
--one-device Don't follow links to other devices.\n\
-p --path-to-ignore STRING\n\
Use .ignore file at STRING\n\
-Q --literal Don't parse PATTERN as a regular expression\n\
-s --case-sensitive Match case sensitively\n\
-S --smart-case Match case insensitively unless PATTERN contains\n\
uppercase characters (Enabled by default)\n\
--search-binary Search binary files for matches\n\
-t --all-text Search all text files (doesn't include hidden files)\n\
-u --unrestricted Search all files (ignore .ignore, .gitignore, etc.;\n\
searches binary and hidden files as well)\n\
-U --skip-vcs-ignores Ignore VCS ignore files\n\
(.gitignore, .hgignore; still obey .ignore)\n\
-v --invert-match\n\
-w --word-regexp Only match whole words\n\
-W --width NUM Truncate match lines after NUM characters\n\
-z --search-zip Search contents of compressed (e.g., gzip) files\n\
\n");
printf("File Types:\n\
The search can be restricted to certain types of files. Example:\n\
ag --html needle\n\
- Searches for 'needle' in files with suffix .htm, .html, .shtml or .xhtml.\n\
\n\
For a list of supported file types run:\n\
ag --list-file-types\n\n\
ag was originally created by Geoff Greer. More information (and the latest release)\n\
can be found at http://geoff.greer.fm/ag\n");
}
void print_version(void) {
char jit = '-';
char lzma = '-';
char zlib = '-';
#ifdef USE_PCRE_JIT
jit = '+';
#endif
#ifdef HAVE_LZMA_H
lzma = '+';
#endif
#ifdef HAVE_ZLIB_H
zlib = '+';
#endif
printf("ag version %s\n\n", PACKAGE_VERSION);
printf("Features:\n");
printf(" %cjit %clzma %czlib\n", jit, lzma, zlib);
}
void init_options(void) {
char *term = getenv("TERM");
memset(&opts, 0, sizeof(opts));
opts.casing = CASE_DEFAULT;
opts.color = TRUE;
if (term && !strcmp(term, "dumb")) {
opts.color = FALSE;
}
opts.color_win_ansi = FALSE;
opts.max_matches_per_file = 0;
opts.max_search_depth = DEFAULT_MAX_SEARCH_DEPTH;
#if defined(__APPLE__) || defined(__MACH__)
/* mamp() is slower than normal read() on macos. default to off */
opts.mmap = FALSE;
#else
opts.mmap = TRUE;
#endif
opts.multiline = TRUE;
opts.width = 0;
opts.path_sep = '\n';
opts.print_break = TRUE;
opts.print_path = PATH_PRINT_DEFAULT;
opts.print_all_paths = FALSE;
opts.print_line_numbers = TRUE;
opts.recurse_dirs = TRUE;
opts.color_path = ag_strdup(color_path);
opts.color_match = ag_strdup(color_match);
opts.color_line_number = ag_strdup(color_line_number);
opts.use_thread_affinity = TRUE;
}
void cleanup_options(void) {
free(opts.color_path);
free(opts.color_match);
free(opts.color_line_number);
if (opts.query) {
free(opts.query);
}
pcre_free(opts.re);
if (opts.re_extra) {
/* Using pcre_free_study on pcre_extra* can segfault on some versions of PCRE */
pcre_free(opts.re_extra);
}
if (opts.ackmate_dir_filter) {
pcre_free(opts.ackmate_dir_filter);
}
if (opts.ackmate_dir_filter_extra) {
pcre_free(opts.ackmate_dir_filter_extra);
}
if (opts.file_search_regex) {
pcre_free(opts.file_search_regex);
}
if (opts.file_search_regex_extra) {
pcre_free(opts.file_search_regex_extra);
}
}
void parse_options(int argc, char **argv, char **base_paths[], char **paths[]) {
int ch;
size_t i;
int path_len = 0;
int base_path_len = 0;
int useless = 0;
int group = 1;
int help = 0;
int version = 0;
int list_file_types = 0;
int opt_index = 0;
char *num_end;
const char *home_dir = getenv("HOME");
char *ignore_file_path = NULL;
int accepts_query = 1;
int needs_query = 1;
struct stat statbuf;
int rv;
size_t lang_count;
size_t lang_num = 0;
int has_filetype = 0;
size_t longopts_len, full_len;
option_t *longopts;
char *lang_regex = NULL;
size_t *ext_index = NULL;
char *extensions = NULL;
size_t num_exts = 0;
init_options();
option_t base_longopts[] = {
{ "ackmate", no_argument, &opts.ackmate, 1 },
{ "ackmate-dir-filter", required_argument, NULL, 0 },
{ "affinity", no_argument, &opts.use_thread_affinity, 1 },
{ "after", optional_argument, NULL, 'A' },
{ "all-text", no_argument, NULL, 't' },
{ "all-types", no_argument, NULL, 'a' },
{ "before", optional_argument, NULL, 'B' },
{ "break", no_argument, &opts.print_break, 1 },
{ "case-sensitive", no_argument, NULL, 's' },
{ "color", no_argument, &opts.color, 1 },
{ "color-line-number", required_argument, NULL, 0 },
{ "color-match", required_argument, NULL, 0 },
{ "color-path", required_argument, NULL, 0 },
{ "color-win-ansi", no_argument, &opts.color_win_ansi, TRUE },
{ "column", no_argument, &opts.column, 1 },
{ "context", optional_argument, NULL, 'C' },
{ "count", no_argument, NULL, 'c' },
{ "debug", no_argument, NULL, 'D' },
{ "depth", required_argument, NULL, 0 },
{ "filename", no_argument, NULL, 0 },
{ "filename-pattern", required_argument, NULL, 'g' },
{ "file-search-regex", required_argument, NULL, 'G' },
{ "files-with-matches", no_argument, NULL, 'l' },
{ "files-without-matches", no_argument, NULL, 'L' },
{ "fixed-strings", no_argument, NULL, 'F' },
{ "follow", no_argument, &opts.follow_symlinks, 1 },
{ "group", no_argument, &group, 1 },
{ "heading", no_argument, &opts.print_path, PATH_PRINT_TOP },
{ "help", no_argument, NULL, 'h' },
{ "hidden", no_argument, &opts.search_hidden_files, 1 },
{ "ignore", required_argument, NULL, 0 },
{ "ignore-case", no_argument, NULL, 'i' },
{ "ignore-dir", required_argument, NULL, 0 },
{ "invert-match", no_argument, NULL, 'v' },
/* deprecated for --numbers. Remove eventually. */
{ "line-numbers", no_argument, &opts.print_line_numbers, 2 },
{ "list-file-types", no_argument, &list_file_types, 1 },
{ "literal", no_argument, NULL, 'Q' },
{ "match", no_argument, &useless, 0 },
{ "max-count", required_argument, NULL, 'm' },
{ "mmap", no_argument, &opts.mmap, TRUE },
{ "multiline", no_argument, &opts.multiline, TRUE },
/* Accept both --no-* and --no* forms for convenience/BC */
{ "no-affinity", no_argument, &opts.use_thread_affinity, 0 },
{ "noaffinity", no_argument, &opts.use_thread_affinity, 0 },
{ "no-break", no_argument, &opts.print_break, 0 },
{ "nobreak", no_argument, &opts.print_break, 0 },
{ "no-color", no_argument, &opts.color, 0 },
{ "nocolor", no_argument, &opts.color, 0 },
{ "no-filename", no_argument, NULL, 0 },
{ "nofilename", no_argument, NULL, 0 },
{ "no-follow", no_argument, &opts.follow_symlinks, 0 },
{ "nofollow", no_argument, &opts.follow_symlinks, 0 },
{ "no-group", no_argument, &group, 0 },
{ "nogroup", no_argument, &group, 0 },
{ "no-heading", no_argument, &opts.print_path, PATH_PRINT_EACH_LINE },
{ "noheading", no_argument, &opts.print_path, PATH_PRINT_EACH_LINE },
{ "no-mmap", no_argument, &opts.mmap, FALSE },
{ "nommap", no_argument, &opts.mmap, FALSE },
{ "no-multiline", no_argument, &opts.multiline, FALSE },
{ "nomultiline", no_argument, &opts.multiline, FALSE },
{ "no-numbers", no_argument, &opts.print_line_numbers, FALSE },
{ "nonumbers", no_argument, &opts.print_line_numbers, FALSE },
{ "no-pager", no_argument, NULL, 0 },
{ "nopager", no_argument, NULL, 0 },
{ "no-recurse", no_argument, NULL, 'n' },
{ "norecurse", no_argument, NULL, 'n' },
{ "null", no_argument, NULL, '0' },
{ "numbers", no_argument, &opts.print_line_numbers, 2 },
{ "only-matching", no_argument, NULL, 'o' },
{ "one-device", no_argument, &opts.one_dev, 1 },
{ "pager", required_argument, NULL, 0 },
{ "parallel", no_argument, &opts.parallel, 1 },
{ "passthrough", no_argument, &opts.passthrough, 1 },
{ "passthru", no_argument, &opts.passthrough, 1 },
{ "path-to-ignore", required_argument, NULL, 'p' },
{ "print0", no_argument, NULL, '0' },
{ "print-all-files", no_argument, NULL, 0 },
{ "print-long-lines", no_argument, &opts.print_long_lines, 1 },
{ "recurse", no_argument, NULL, 'r' },
{ "search-binary", no_argument, &opts.search_binary_files, 1 },
{ "search-files", no_argument, &opts.search_stream, 0 },
{ "search-zip", no_argument, &opts.search_zip_files, 1 },
{ "silent", no_argument, NULL, 0 },
{ "skip-vcs-ignores", no_argument, NULL, 'U' },
{ "smart-case", no_argument, NULL, 'S' },
{ "stats", no_argument, &opts.stats, 1 },
{ "stats-only", no_argument, NULL, 0 },
{ "unrestricted", no_argument, NULL, 'u' },
{ "version", no_argument, &version, 1 },
{ "vimgrep", no_argument, &opts.vimgrep, 1 },
{ "width", required_argument, NULL, 'W' },
{ "word-regexp", no_argument, NULL, 'w' },
{ "workers", required_argument, NULL, 0 },
};
lang_count = get_lang_count();
longopts_len = (sizeof(base_longopts) / sizeof(option_t));
full_len = (longopts_len + lang_count + 1);
longopts = ag_malloc(full_len * sizeof(option_t));
memcpy(longopts, base_longopts, sizeof(base_longopts));
ext_index = (size_t *)ag_malloc(sizeof(size_t) * lang_count);
memset(ext_index, 0, sizeof(size_t) * lang_count);
for (i = 0; i < lang_count; i++) {
option_t opt = { langs[i].name, no_argument, NULL, 0 };
longopts[i + longopts_len] = opt;
}
longopts[full_len - 1] = (option_t){ NULL, 0, NULL, 0 };
if (argc < 2) {
usage();
cleanup_ignore(root_ignores);
cleanup_options();
exit(1);
}
rv = fstat(fileno(stdin), &statbuf);
if (rv == 0) {
if (S_ISFIFO(statbuf.st_mode) || S_ISREG(statbuf.st_mode)) {
opts.search_stream = 1;
}
}
/* If we're not outputting to a terminal. change output to:
* turn off colors
* print filenames on every line
*/
if (!isatty(fileno(stdout))) {
opts.color = 0;
group = 0;
/* Don't search the file that stdout is redirected to */
rv = fstat(fileno(stdout), &statbuf);
if (rv != 0) {
die("Error fstat()ing stdout");
}
opts.stdout_inode = statbuf.st_ino;
}
char *file_search_regex = NULL;
while ((ch = getopt_long(argc, argv, "A:aB:C:cDG:g:FfHhiLlm:nop:QRrSsvVtuUwW:z0", longopts, &opt_index)) != -1) {
switch (ch) {
case 'A':
if (optarg) {
opts.after = strtol(optarg, &num_end, 10);
if (num_end == optarg || *num_end != '\0' || errno == ERANGE) {
/* This arg must be the search string instead of the after length */
optind--;
opts.after = DEFAULT_AFTER_LEN;
}
} else {
opts.after = DEFAULT_AFTER_LEN;
}
break;
case 'a':
opts.search_all_files = 1;
opts.search_binary_files = 1;
break;
case 'B':
if (optarg) {
opts.before = strtol(optarg, &num_end, 10);
if (num_end == optarg || *num_end != '\0' || errno == ERANGE) {
/* This arg must be the search string instead of the before length */
optind--;
opts.before = DEFAULT_BEFORE_LEN;
}
} else {
opts.before = DEFAULT_BEFORE_LEN;
}
break;
case 'C':
if (optarg) {
opts.context = strtol(optarg, &num_end, 10);
if (num_end == optarg || *num_end != '\0' || errno == ERANGE) {
/* This arg must be the search string instead of the context length */
optind--;
opts.context = DEFAULT_CONTEXT_LEN;
}
} else {
opts.context = DEFAULT_CONTEXT_LEN;
}
break;
case 'c':
opts.print_count = 1;
opts.print_filename_only = 1;
break;
case 'D':
set_log_level(LOG_LEVEL_DEBUG);
break;
case 'f':
opts.follow_symlinks = 1;
break;
case 'g':
needs_query = accepts_query = 0;
opts.match_files = 1;
/* fall through */
case 'G':
if (file_search_regex) {
log_err("File search regex (-g or -G) already specified.");
usage();
exit(1);
}
file_search_regex = ag_strdup(optarg);
break;
case 'H':
opts.print_path = PATH_PRINT_TOP;
break;
case 'h':
help = 1;
break;
case 'i':
opts.casing = CASE_INSENSITIVE;
break;
case 'L':
opts.print_nonmatching_files = 1;
opts.print_path = PATH_PRINT_TOP;
break;
case 'l':
needs_query = 0;
opts.print_filename_only = 1;
opts.print_path = PATH_PRINT_TOP;
break;
case 'm':
opts.max_matches_per_file = atoi(optarg);
break;
case 'n':
opts.recurse_dirs = 0;
break;
case 'p':
opts.path_to_ignore = TRUE;
load_ignore_patterns(root_ignores, optarg);
break;
case 'o':
opts.only_matching = 1;
break;
case 'F':
case 'Q':
opts.literal = 1;
break;
case 'R':
case 'r':
opts.recurse_dirs = 1;
break;
case 'S':
opts.casing = CASE_SMART;
break;
case 's':
opts.casing = CASE_SENSITIVE;
break;
case 't':
opts.search_all_files = 1;
break;
case 'u':
opts.search_binary_files = 1;
opts.search_all_files = 1;
opts.search_hidden_files = 1;
break;
case 'U':
opts.skip_vcs_ignores = 1;
break;
case 'v':
opts.invert_match = 1;
/* Color highlighting doesn't make sense when inverting matches */
opts.color = 0;
break;
case 'V':
version = 1;
break;
case 'w':
opts.word_regexp = 1;
break;
case 'W':
opts.width = strtol(optarg, &num_end, 10);
if (num_end == optarg || *num_end != '\0' || errno == ERANGE) {
die("Invalid width\n");
}
break;
case 'z':
opts.search_zip_files = 1;
break;
case '0':
opts.path_sep = '\0';
break;
case 0: /* Long option */
if (strcmp(longopts[opt_index].name, "ackmate-dir-filter") == 0) {
compile_study(&opts.ackmate_dir_filter, &opts.ackmate_dir_filter_extra, optarg, 0, 0);
break;
} else if (strcmp(longopts[opt_index].name, "depth") == 0) {
opts.max_search_depth = atoi(optarg);
break;
} else if (strcmp(longopts[opt_index].name, "filename") == 0) {
opts.print_path = PATH_PRINT_DEFAULT;
opts.print_line_numbers = TRUE;
break;
} else if (strcmp(longopts[opt_index].name, "ignore-dir") == 0) {
add_ignore_pattern(root_ignores, optarg);
break;
} else if (strcmp(longopts[opt_index].name, "ignore") == 0) {
add_ignore_pattern(root_ignores, optarg);
break;
} else if (strcmp(longopts[opt_index].name, "no-filename") == 0 ||
strcmp(longopts[opt_index].name, "nofilename") == 0) {
opts.print_path = PATH_PRINT_NOTHING;
opts.print_line_numbers = FALSE;
break;
} else if (strcmp(longopts[opt_index].name, "no-pager") == 0 ||
strcmp(longopts[opt_index].name, "nopager") == 0) {
out_fd = stdout;
opts.pager = NULL;
break;
} else if (strcmp(longopts[opt_index].name, "pager") == 0) {
opts.pager = optarg;
break;
} else if (strcmp(longopts[opt_index].name, "print-all-files") == 0) {
opts.print_all_paths = TRUE;
break;
} else if (strcmp(longopts[opt_index].name, "workers") == 0) {
opts.workers = atoi(optarg);
break;
} else if (strcmp(longopts[opt_index].name, "color-line-number") == 0) {
free(opts.color_line_number);
ag_asprintf(&opts.color_line_number, "\033[%sm", optarg);
break;
} else if (strcmp(longopts[opt_index].name, "color-match") == 0) {
free(opts.color_match);
ag_asprintf(&opts.color_match, "\033[%sm", optarg);
break;
} else if (strcmp(longopts[opt_index].name, "color-path") == 0) {
free(opts.color_path);
ag_asprintf(&opts.color_path, "\033[%sm", optarg);
break;
} else if (strcmp(longopts[opt_index].name, "silent") == 0) {
set_log_level(LOG_LEVEL_NONE);
break;
} else if (strcmp(longopts[opt_index].name, "stats-only") == 0) {
opts.print_filename_only = 1;
opts.print_path = PATH_PRINT_NOTHING;
opts.stats = 1;
break;
}
/* Continue to usage if we don't recognize the option */
if (longopts[opt_index].flag != 0) {
break;
}
for (i = 0; i < lang_count; i++) {
if (strcmp(longopts[opt_index].name, langs[i].name) == 0) {
has_filetype = 1;
ext_index[lang_num++] = i;
break;
}
}
if (i != lang_count) {
break;
}
log_err("option %s does not take a value", longopts[opt_index].name);
/* fall through */
default:
usage();
exit(1);
}
}
if (opts.casing == CASE_DEFAULT) {
opts.casing = CASE_SMART;
}
if (file_search_regex) {
int pcre_opts = 0;
if (opts.casing == CASE_INSENSITIVE || (opts.casing == CASE_SMART && is_lowercase(file_search_regex))) {
pcre_opts |= PCRE_CASELESS;
}
if (opts.word_regexp) {
char *old_file_search_regex = file_search_regex;
ag_asprintf(&file_search_regex, "\\b%s\\b", file_search_regex);
free(old_file_search_regex);
}
compile_study(&opts.file_search_regex, &opts.file_search_regex_extra, file_search_regex, pcre_opts, 0);
free(file_search_regex);
}
if (has_filetype) {
num_exts = combine_file_extensions(ext_index, lang_num, &extensions);
lang_regex = make_lang_regex(extensions, num_exts);
compile_study(&opts.file_search_regex, &opts.file_search_regex_extra, lang_regex, 0, 0);
}
if (extensions) {
free(extensions);
}
free(ext_index);
if (lang_regex) {
free(lang_regex);
}
free(longopts);
argc -= optind;
argv += optind;
if (opts.pager) {
out_fd = popen(opts.pager, "w");
if (!out_fd) {
perror("Failed to run pager");
exit(1);
}
}
#ifdef HAVE_PLEDGE
if (opts.skip_vcs_ignores) {
if (pledge("stdio rpath proc", NULL) == -1) {
die("pledge: %s", strerror(errno));
}
}
#endif
if (help) {
usage();
exit(0);
}
if (version) {
print_version();
exit(0);
}
if (list_file_types) {
size_t lang_index;
printf("The following file types are supported:\n");
for (lang_index = 0; lang_index < lang_count; lang_index++) {
printf(" --%s\n ", langs[lang_index].name);
int j;
for (j = 0; j < MAX_EXTENSIONS && langs[lang_index].extensions[j]; j++) {
printf(" .%s", langs[lang_index].extensions[j]);
}
printf("\n\n");
}
exit(0);
}
if (needs_query && argc == 0) {
log_err("What do you want to search for?");
exit(1);
}
if (home_dir && !opts.search_all_files) {
log_debug("Found user's home dir: %s", home_dir);
ag_asprintf(&ignore_file_path, "%s/.agignore", home_dir);
load_ignore_patterns(root_ignores, ignore_file_path);
free(ignore_file_path);
}
if (!opts.skip_vcs_ignores) {
FILE *gitconfig_file = NULL;
size_t buf_len = 0;
char *gitconfig_res = NULL;
#ifdef _WIN32
gitconfig_file = popen("git config -z --path --get core.excludesfile 2>NUL", "r");
#else
gitconfig_file = popen("git config -z --path --get core.excludesfile 2>/dev/null", "r");
#endif
if (gitconfig_file != NULL) {
do {
gitconfig_res = ag_realloc(gitconfig_res, buf_len + 65);
buf_len += fread(gitconfig_res + buf_len, 1, 64, gitconfig_file);
} while (!feof(gitconfig_file) && buf_len > 0 && buf_len % 64 == 0);
gitconfig_res[buf_len] = '\0';
if (buf_len == 0) {
free(gitconfig_res);
const char *config_home = getenv("XDG_CONFIG_HOME");
if (config_home) {
ag_asprintf(&gitconfig_res, "%s/%s", config_home, "git/ignore");
} else if (home_dir) {
ag_asprintf(&gitconfig_res, "%s/%s", home_dir, ".config/git/ignore");
} else {
gitconfig_res = ag_strdup("");
}
}
log_debug("global core.excludesfile: %s", gitconfig_res);
load_ignore_patterns(root_ignores, gitconfig_res);
free(gitconfig_res);
pclose(gitconfig_file);
}
}
#ifdef HAVE_PLEDGE
if (pledge("stdio rpath proc", NULL) == -1) {
die("pledge: %s", strerror(errno));
}
#endif
if (opts.context > 0) {
opts.before = opts.context;
opts.after = opts.context;
}
if (opts.ackmate) {
opts.color = 0;
opts.print_break = 1;
group = 1;
opts.search_stream = 0;
}
if (opts.vimgrep) {
opts.color = 0;
opts.print_break = 0;
group = 1;
opts.search_stream = 0;
opts.print_path = PATH_PRINT_NOTHING;
}
if (opts.parallel) {
opts.search_stream = 0;
}
if (!(opts.print_path != PATH_PRINT_DEFAULT || opts.print_break == 0)) {
if (group) {
opts.print_break = 1;
} else {
opts.print_path = PATH_PRINT_DEFAULT_EACH_LINE;
opts.print_break = 0;
}
}
if (opts.search_stream) {
opts.print_break = 0;
opts.print_path = PATH_PRINT_NOTHING;
if (opts.print_line_numbers != 2) {
opts.print_line_numbers = 0;
}
}
if (accepts_query && argc > 0) {
if (!needs_query && strlen(argv[0]) == 0) {
// use default query
opts.query = ag_strdup(".");
} else {
// use the provided query
opts.query = ag_strdup(argv[0]);
}
argc--;
argv++;
} else if (!needs_query) {
// use default query
opts.query = ag_strdup(".");
}
opts.query_len = strlen(opts.query);
log_debug("Query is %s", opts.query);
if (opts.query_len == 0) {
log_err("Error: No query. What do you want to search for?");
exit(1);
}
if (!is_regex(opts.query)) {
opts.literal = 1;
}
char *path = NULL;
char *base_path = NULL;
#ifdef PATH_MAX
char *tmp = NULL;
#endif
opts.paths_len = argc;
if (argc > 0) {
*paths = ag_calloc(sizeof(char *), argc + 1);
*base_paths = ag_calloc(sizeof(char *), argc + 1);
for (i = 0; i < (size_t)argc; i++) {
path = ag_strdup(argv[i]);
path_len = strlen(path);
/* kill trailing slash */
if (path_len > 1 && path[path_len - 1] == '/') {
path[path_len - 1] = '\0';
}
(*paths)[i] = path;
#ifdef PATH_MAX
tmp = ag_malloc(PATH_MAX);
base_path = realpath(path, tmp);
#else
base_path = realpath(path, NULL);
#endif
if (base_path) {
base_path_len = strlen(base_path);
/* add trailing slash */
if (base_path_len > 1 && base_path[base_path_len - 1] != '/') {
base_path = ag_realloc(base_path, base_path_len + 2);
base_path[base_path_len] = '/';
base_path[base_path_len + 1] = '\0';
}
}
(*base_paths)[i] = base_path;
}
/* Make sure we search these paths instead of stdin. */
opts.search_stream = 0;
} else {
path = ag_strdup(".");
*paths = ag_malloc(sizeof(char *) * 2);
*base_paths = ag_malloc(sizeof(char *) * 2);
(*paths)[0] = path;
#ifdef PATH_MAX
tmp = ag_malloc(PATH_MAX);
(*base_paths)[0] = realpath(path, tmp);
#else
(*base_paths)[0] = realpath(path, NULL);
#endif
i = 1;
}
(*paths)[i] = NULL;
(*base_paths)[i] = NULL;
#ifdef _WIN32
windows_use_ansi(opts.color_win_ansi);
#endif
}
================================================
FILE: src/options.h
================================================
#ifndef OPTIONS_H
#define OPTIONS_H
#include <getopt.h>
#include <sys/stat.h>
#include <pcre.h>
#define DEFAULT_AFTER_LEN 2
#define DEFAULT_BEFORE_LEN 2
#define DEFAULT_CONTEXT_LEN 2
#define DEFAULT_MAX_SEARCH_DEPTH 25
enum case_behavior {
CASE_DEFAULT, /* Changes to CASE_SMART at the end of option parsing */
CASE_SENSITIVE,
CASE_INSENSITIVE,
CASE_SMART,
CASE_SENSITIVE_RETRY_INSENSITIVE /* for future use */
};
enum path_print_behavior {
PATH_PRINT_DEFAULT, /* PRINT_TOP if > 1 file being searched, else PRINT_NOTHING */
PATH_PRINT_DEFAULT_EACH_LINE, /* PRINT_EACH_LINE if > 1 file being searched, else PRINT_NOTHING */
PATH_PRINT_TOP,
PATH_PRINT_EACH_LINE,
PATH_PRINT_NOTHING
};
typedef struct {
int ackmate;
pcre *ackmate_dir_filter;
pcre_extra *ackmate_dir_filter_extra;
size_t after;
size_t before;
enum case_behavior casing;
const char *file_search_string;
int match_files;
pcre *file_search_regex;
pcre_extra *file_search_regex_extra;
int color;
char *color_line_number;
char *color_match;
char *color_path;
int color_win_ansi;
int column;
int context;
int follow_symlinks;
int invert_match;
int literal;
int literal_starts_wordchar;
int literal_ends_wordchar;
size_t max_matches_per_file;
int max_search_depth;
int mmap;
int multiline;
int one_dev;
int only_matching;
char path_sep;
int path_to_ignore;
int print_break;
int print_count;
int print_filename_only;
int print_nonmatching_files;
int print_path;
int print_all_paths;
int print_line_numbers;
int print_long_lines; /* TODO: support this in print.c */
int passthrough;
pcre *re;
pcre_extra *re_extra;
int recurse_dirs;
int search_all_files;
int skip_vcs_ignores;
int search_binary_files;
int search_zip_files;
int search_hidden_files;
int search_stream; /* true if tail -F blah | ag */
int stats;
size_t stream_line_num; /* This should totally not be in here */
int match_found; /* This should totally not be in here */
ino_t stdout_inode;
char *query;
int query_len;
char *pager;
int paths_len;
int parallel;
int use_thread_affinity;
int vimgrep;
size_t width;
int word_regexp;
int workers;
} cli_options;
/* global options. parse_options gives it sane values, everything else reads from it */
extern cli_options opts;
typedef struct option option_t;
void usage(void);
void print_version(void);
void init_options(void);
void parse_options(int argc, char **argv, char **base_paths[], char **paths[]);
void cleanup_options(void);
#endif
================================================
FILE: src/print.c
================================================
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ignore.h"
#include "log.h"
#include "options.h"
#include "print.h"
#include "search.h"
#include "util.h"
#ifdef _WIN32
#define fprintf(...) fprintf_w32(__VA_ARGS__)
#endif
int first_file_match = 1;
const char *color_reset = "\033[0m\033[K";
const char *truncate_marker = " [...]";
__thread struct print_context {
size_t line;
char **context_prev_lines;
size_t prev_line;
size_t last_prev_line;
size_t prev_line_offset;
size_t line_preceding_current_match_offset;
size_t lines_since_last_match;
size_t last_printed_match;
int in_a_match;
int printing_a_match;
} print_context;
void print_init_context(void) {
if (print_context.context_prev_lines != NULL) {
return;
}
print_context.context_prev_lines = ag_calloc(sizeof(char *), (opts.before + 1));
print_context.line = 1;
print_context.prev_line = 0;
print_context.last_prev_line = 0;
print_context.prev_line_offset = 0;
print_context.line_preceding_current_match_offset = 0;
print_context.lines_since_last_match = INT_MAX;
print_context.last_printed_match = 0;
print_context.in_a_match = FALSE;
print_context.printing_a_match = FALSE;
}
void print_cleanup_context(void) {
size_t i;
if (print_context.context_prev_lines == NULL) {
return;
}
for (i = 0; i < opts.before; i++) {
if (print_context.context_prev_lines[i] != NULL) {
free(print_context.context_prev_lines[i]);
}
}
free(print_context.context_prev_lines);
print_context.context_prev_lines = NULL;
}
void print_context_append(const char *line, size_t len) {
if (opts.before == 0) {
return;
}
if (print_context.context_prev_lines[print_context.last_prev_line] != NULL) {
free(print_context.context_prev_lines[print_context.last_prev_line]);
}
print_context.context_prev_lines[print_context.last_prev_line] = ag_strndup(line, len);
print_context.last_prev_line = (print_context.last_prev_line + 1) % opts.before;
}
void print_trailing_context(const char *path, const char *buf, size_t n) {
char sep = '-';
if (opts.ackmate || opts.vimgrep) {
sep = ':';
}
if (print_context.lines_since_last_match != 0 &&
print_context.lines_since_last_match <= opts.after) {
if (opts.print_path == PATH_PRINT_EACH_LINE) {
print_path(path, ':');
}
print_line_number(print_context.line, sep);
fwrite(buf, 1, n, out_fd);
fputc('\n', out_fd);
}
print_context.line++;
if (!print_context.in_a_match && print_context.lines_since_last_match < INT_MAX) {
print_context.lines_since_last_match++;
}
}
void print_path(const char *path, const char sep) {
if (opts.print_path == PATH_PRINT_NOTHING && !opts.vimgrep) {
return;
}
path = normalize_path(path);
if (opts.ackmate) {
fprintf(out_fd, ":%s%c", path, sep);
} else if (opts.vimgrep) {
fprintf(out_fd, "%s%c", path, sep);
} else {
if (opts.color) {
fprintf(out_fd, "%s%s%s%c", opts.color_path, path, color_reset, sep);
} else {
fprintf(out_fd, "%s%c", path, sep);
}
}
}
void print_path_count(const char *path, const char sep, const size_t count) {
if (*path) {
print_path(path, ':');
}
if (opts.color) {
fprintf(out_fd, "%s%lu%s%c", opts.color_line_number, (unsigned long)count, color_reset, sep);
} else {
fprintf(out_fd, "%lu%c", (unsigned long)count, sep);
}
}
void print_line(const char *buf, size_t buf_pos, size_t prev_line_offset) {
size_t write_chars = buf_pos - prev_line_offset + 1;
if (opts.width > 0 && opts.width < write_chars) {
write_chars = opts.width;
}
fwrite(buf + prev_line_offset, 1, write_chars, out_fd);
}
void print_binary_file_matches(const char *path) {
path = normalize_path(path);
print_file_separator();
fprintf(out_fd, "Binary file %s matches.\n", path);
}
void print_file_matches(const char *path, const char *buf, const size_t buf_len, const match_t matches[], const size_t matches_len) {
size_t cur_match = 0;
ssize_t lines_to_print = 0;
char sep = '-';
size_t i, j;
int blanks_between_matches = opts.context || opts.after || opts.before;
if (opts.ackmate || opts.vimgrep) {
sep = ':';
}
print_file_separator();
if (opts.print_path == PATH_PRINT_DEFAULT) {
opts.print_path = PATH_PRINT_TOP;
} else if (opts.print_path == PATH_PRINT_DEFAULT_EACH_LINE) {
opts.print_path = PATH_PRINT_EACH_LINE;
}
if (opts.print_path == PATH_PRINT_TOP) {
if (opts.print_count) {
print_path_count(path, opts.path_sep, matches_len);
} else {
print_path(path, opts.path_sep);
}
}
for (i = 0; i <= buf_len && (cur_match < matches_len || print_context.lines_since_last_match <= opts.after); i++) {
if (cur_match < matches_len && i == matches[cur_match].start) {
print_context.in_a_match = TRUE;
/* We found the start of a match */
if (cur_match > 0 && blanks_between_matches && print_context.lines_since_last_match > (opts.before + opts.after + 1)) {
fprintf(out_fd, "--\n");
}
if (print_context.lines_since_last_match > 0 && opts.before > 0) {
/* TODO: better, but still needs work */
/* print the previous line(s) */
lines_to_print = print_context.lines_since_last_match - (opts.after + 1);
if (lines_to_print < 0) {
lines_to_print = 0;
} else if ((size_t)lines_to_print > opts.before) {
lines_to_print = opts.before;
}
for (j = (opts.before - lines_to_print); j < opts.before; j++) {
print_context.prev_line = (print_context.last_prev_line + j) % opts.before;
if (print_context.context_prev_lines[print_context.prev_line] != NULL) {
if (opts.print_path == PATH_PRINT_EACH_LINE) {
print_path(path, ':');
}
print_line_number(print_context.line - (opts.before - j), sep);
fprintf(out_fd, "%s\n", print_context.context_prev_lines[print_context.prev_line]);
}
}
}
print_context.lines_since_last_match = 0;
}
if (cur_match < matches_len && i == matches[cur_match].end) {
/* We found the end of a match. */
cur_match++;
print_context.in_a_match = FALSE;
}
/* We found the end of a line. */
if ((i == buf_len || buf[i] == '\n') && opts.before > 0) {
/* We don't want to strcpy the \n */
print_context_append(&buf[print_context.prev_line_offset], i - print_context.prev_line_offset);
}
if (i == buf_len || buf[i] == '\n') {
if (print_context.lines_since_last_match == 0) {
if (opts.print_path == PATH_PRINT_EACH_LINE && !opts.search_stream) {
print_path(path, ':');
}
if (opts.ackmate) {
/* print headers for ackmate to parse */
print_line_number(print_context.line, ';');
for (; print_context.last_printed_match < cur_match; print_context.last_printed_match++) {
size_t start = matches[print_context.last_printed_match].start - print_context.line_preceding_current_match_offset;
fprintf(out_fd, "%lu %lu",
start,
matches[print_context.last_printed_match].end - matches[print_context.last_printed_match].start);
print_context.last_printed_match == cur_match - 1 ? fputc(':', out_fd) : fputc(',', out_fd);
}
print_line(buf, i, print_context.prev_line_offset);
} else if (opts.vimgrep) {
for (; print_context.last_printed_match < cur_match; print_context.last_printed_match++) {
print_path(path, sep);
print_line_number(print_context.line, sep);
print_column_number(matches, print_context.last_printed_match, print_context.prev_line_offset, sep);
print_line(buf, i, print_context.prev_line_offset);
}
} else {
print_line_number(print_context.line, ':');
int printed_match = FALSE;
if (opts.column) {
print_column_number(matches, print_context.last_printed_match, print_context.prev_line_offset, ':');
}
if (print_context.printing_a_match && opts.color) {
fprintf(out_fd, "%s", opts.color_match);
}
for (j = print_context.prev_line_offset; j <= i; j++) {
/* close highlight of match term */
if (print_context.last_printed_match < matches_len && j == matches[print_context.last_printed_match].end) {
if (opts.color) {
fprintf(out_fd, "%s", color_reset);
}
print_context.printing_a_match = FALSE;
print_context.last_printed_match++;
printed_match = TRUE;
if (opts.only_matching) {
fputc('\n', out_fd);
}
}
/* skip remaining characters if truncation width exceeded, needs to be done
* before highlight opening */
if (j < buf_len && opts.width > 0 && j - print_context.prev_line_offset >= opts.width) {
if (j < i) {
fputs(truncate_marker, out_fd);
}
fputc('\n', out_fd);
/* prevent any more characters or highlights */
j = i;
print_context.last_printed_match = matches_len;
}
/* open highlight of match term */
if (print_context.last_printed_match < matches_len && j == matches[print_context.last_printed_match].start) {
if (opts.only_matching && printed_match) {
if (opts.print_path == PATH_PRINT_EACH_LINE) {
print_path(path, ':');
}
print_line_number(print_context.line, ':');
if (opts.column) {
print_column_number(matches, print_context.last_printed_match, print_context.prev_line_offset, ':');
}
}
if (opts.color) {
fprintf(out_fd, "%s", opts.color_match);
}
print_context.printing_a_match = TRUE;
}
/* Don't print the null terminator */
if (j < buf_len) {
/* if only_matching is set, print only matches and newlines */
if (!opts.only_matching || print_context.printing_a_match) {
if (opts.width == 0 || j - print_context.prev_line_offset < opts.width) {
fputc(buf[j], out_fd);
}
}
}
}
if (print_context.printing_a_match && opts.color) {
fprintf(out_fd, "%s", color_reset);
}
}
}
if (opts.search_stream) {
print_context.last_printed_match = 0;
break;
}
/* print context after matching line */
print_trailing_context(path, &buf[print_context.prev_line_offset], i - print_context.prev_line_offset);
print_context.prev_line_offset = i + 1; /* skip the newline */
if (!print_context.in_a_match) {
print_context.line_preceding_current_match_offset = i + 1;
}
/* File doesn't end with a newline. Print one so the output is pretty. */
if (i == buf_len && buf[i - 1] != '\n') {
fputc('\n', out_fd);
}
}
}
/* Flush output if stdout is not a tty */
if (opts.stdout_inode) {
fflush(out_fd);
}
}
void print_line_number(size_t line, const char sep) {
if (!opts.print_line_numbers) {
return;
}
if (opts.color) {
fprintf(out_fd, "%s%lu%s%c", opts.color_line_number, (unsigned long)line, color_reset, sep);
} else {
fprintf(out_fd, "%lu%c", (unsigned long)line, sep);
}
}
void print_column_number(const match_t matches[], size_t last_printed_match,
size_t prev_line_offset, const char sep) {
size_t column = 0;
if (prev_line_offset <= matches[last_printed_match].start) {
column = (matches[last_printed_match].start - prev_line_offset) + 1;
}
fprintf(out_fd, "%lu%c", (unsigned long)column, sep);
}
void print_file_separator(void) {
if (first_file_match == 0 && opts.print_break) {
fprintf(out_fd, "\n");
}
first_file_match = 0;
}
const char *normalize_path(const char *path) {
if (strlen(path) < 3) {
return path;
}
if (path[0] == '.' && path[1] == '/') {
return path + 2;
}
if (path[0] == '/' && path[1] == '/') {
return path + 1;
}
return path;
}
================================================
FILE: src/print.h
================================================
#ifndef PRINT_H
#define PRINT_H
#include "util.h"
void print_init_context(void);
void print_cleanup_context(void);
void print_context_append(const char *line, size_t len);
void print_trailing_context(const char *path, const char *buf, size_t n);
void print_path(const char *path, const char sep);
void print_path_count(const char *path, const char sep, const size_t count);
void print_line(const char *buf, size_t buf_pos, size_t prev_line_offset);
void print_binary_file_matches(const char *path);
void print_file_matches(const char *path, const char *buf, const size_t buf_len, const match_t matches[], const size_t matches_len);
void print_line_number(size_t line, const char sep);
void print_column_number(const match_t matches[], size_t last_printed_match,
size_t prev_line_offset, const char sep);
void print_file_separator(void);
const char *normalize_path(const char *path);
#ifdef _WIN32
void windows_use_ansi(int use_ansi);
int fprintf_w32(FILE *fp, const char *format, ...);
#endif
#endif
================================================
FILE: src/print_w32.c
================================================
#ifdef _WIN32
#include "print.h"
#include <io.h>
#include <stdarg.h>
#include <stdio.h>
#include <windows.h>
#ifndef FOREGROUND_MASK
#define FOREGROUND_MASK (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#endif
#ifndef BACKGROUND_MASK
#define BACKGROUND_MASK (BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_INTENSITY)
#endif
#define FG_RGB (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
#define BG_RGB (BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN)
// BUFSIZ is guarenteed to be "at least 256 bytes" which might
// not be enough for us. Use an arbitrary but reasonably big
// buffer. win32 colored output will be truncated to this length.
#define BUF_SIZE (16 * 1024)
// max consecutive ansi sequence values beyond which we're aborting
// e.g. this is 3 values: \e[0;1;33m
#define MAX_VALUES 8
static int g_use_ansi = 0;
void windows_use_ansi(int use_ansi) {
g_use_ansi = use_ansi;
}
int fprintf_w32(FILE *fp, const char *format, ...) {
va_list args;
char buf[BUF_SIZE] = { 0 }, *ptr = buf;
static WORD attr_reset;
static BOOL attr_initialized = FALSE;
HANDLE stdo = INVALID_HANDLE_VALUE;
WORD attr;
DWORD written, csize;
CONSOLE_CURSOR_INFO cci;
CONSOLE_SCREEN_BUFFER_INFO csbi;
COORD coord;
// if we don't output to screen (tty) e.g. for pager/pipe or
// if for other reason we can't access the screen info, of if
// the user just prefers ansi, do plain passthrough.
BOOL passthrough =
g_use_ansi ||
!isatty(fileno(fp)) ||
INVALID_HANDLE_VALUE == (stdo = (HANDLE)_get_osfhandle(fileno(fp))) ||
!GetConsoleScreenBufferInfo(stdo, &csbi);
if (passthrough) {
int rv;
va_start(args, format);
rv = vfprintf(fp, format, args);
va_end(args);
return rv;
}
va_start(args, format);
// truncates to (null terminated) BUF_SIZE if too long.
// if too long - vsnprintf will fill count chars without
// terminating null. buf is zeroed, so make sure we don't fill it.
vsnprintf(buf, BUF_SIZE - 1, format, args);
va_end(args);
attr = csbi.wAttributes;
if (!attr_initialized) {
// reset is defined to have all (non color) attributes off
attr_reset = attr & (FG_RGB | BG_RGB);
attr_initialized = TRUE;
}
while (*ptr) {
if (*ptr == '\033') {
unsigned char c;
int i, n = 0, m = '\0', v[MAX_VALUES], w, h;
for (i = 0; i < MAX_VALUES; i++)
v[i] = -1;
ptr++;
retry:
if ((c = *ptr++) == 0)
break;
if (isdigit(c)) {
if (v[n] == -1)
v[n] = c - '0';
else
v[n] = v[n] * 10 + c - '0';
goto retry;
}
if (c == '[') {
goto retry;
}
if (c == ';') {
if (++n == MAX_VALUES)
break;
goto retry;
}
if (c == '>' || c == '?') {
m = c;
goto retry;
}
switch (c) {
// n is the last occupied index, so we have n+1 values
case 'h':
if (m == '?') {
for (i = 0; i <= n; i++) {
switch (v[i]) {
case 3:
GetConsoleScreenBufferInfo(stdo, &csbi);
w = csbi.dwSize.X;
h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
csize = w * (h + 1);
coord.X = 0;
coord.Y = csbi.srWindow.Top;
FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written);
FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written);
SetConsoleCursorPosition(stdo, csbi.dwCursorPosition);
csbi.dwSize.X = 132;
SetConsoleScreenBufferSize(stdo, csbi.dwSize);
csbi.srWindow.Right = csbi.srWindow.Left + 131;
SetConsoleWindowInfo(stdo, TRUE, &csbi.srWindow);
break;
case 5:
attr =
((attr & FOREGROUND_MASK) << 4) |
((attr & BACKGROUND_MASK) >> 4);
SetConsoleTextAttribute(stdo, attr);
break;
case 9:
break;
case 25:
GetConsoleCursorInfo(stdo, &cci);
cci.bVisible = TRUE;
SetConsoleCursorInfo(stdo, &cci);
break;
case 47:
coord.X = 0;
coord.Y = 0;
SetConsoleCursorPosition(stdo, coord);
break;
default:
break;
}
}
} else if (m == '>' && v[0] == 5) {
GetConsoleCursorInfo(stdo, &cci);
cci.bVisible = FALSE;
SetConsoleCursorInfo(stdo, &cci);
}
break;
case 'l':
if (m == '?') {
for (i = 0; i <= n; i++) {
switch (v[i]) {
case 3:
GetConsoleScreenBufferInfo(stdo, &csbi);
w = csbi.dwSize.X;
h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
csize = w * (h + 1);
coord.X = 0;
coord.Y = csbi.srWindow.Top;
FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written);
FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written);
SetConsoleCursorPosition(stdo, csbi.dwCursorPosition);
csbi.srWindow.Right = csbi.srWindow.Left + 79;
SetConsoleWindowInfo(stdo, TRUE, &csbi.srWindow);
csbi.dwSize.X = 80;
SetConsoleScreenBufferSize(stdo, csbi.dwSize);
break;
case 5:
attr =
((attr & FOREGROUND_MASK) << 4) |
((attr & BACKGROUND_MASK) >> 4);
SetConsoleTextAttribute(stdo, attr);
break;
case 25:
GetConsoleCursorInfo(stdo, &cci);
cci.bVisible = FALSE;
SetConsoleCursorInfo(stdo, &cci);
break;
default:
break;
}
}
} else if (m == '>' && v[0] == 5) {
GetConsoleCursorInfo(stdo, &cci);
cci.bVisible = TRUE;
SetConsoleCursorInfo(stdo, &cci);
}
break;
case 'm':
for (i = 0; i <= n; i++) {
if (v[i] == -1 || v[i] == 0)
attr = attr_reset;
else if (v[i] == 1)
attr |= FOREGROUND_INTENSITY;
else if (v[i] == 4)
attr |= FOREGROUND_INTENSITY;
else if (v[i] == 5) // blink is typically applied as bg intensity
attr |= BACKGROUND_INTENSITY;
else if (v[i] == 7)
attr =
((attr & FOREGROUND_MASK) << 4) |
((attr & BACKGROUND_MASK) >> 4);
else if (v[i] == 10)
; // symbol on
else if (v[i] == 11)
; // symbol off
else if (v[i] == 22)
attr &= ~FOREGROUND_INTENSITY;
else if (v[i] == 24)
attr &= ~FOREGROUND_INTENSITY;
else if (v[i] == 25)
attr &= ~BACKGROUND_INTENSITY;
else if (v[i] == 27)
attr =
((attr & FOREGROUND_MASK) << 4) |
((attr & BACKGROUND_MASK) >> 4);
else if (v[i] >= 30 && v[i] <= 37) {
attr &= ~FG_RGB; // doesn't affect attributes
if ((v[i] - 30) & 1)
attr |= FOREGROUND_RED;
if ((v[i] - 30) & 2)
attr |= FOREGROUND_GREEN;
if ((v[i] - 30) & 4)
attr |= FOREGROUND_BLUE;
} else if (v[i] == 39) // reset fg color and attributes
attr = (attr & ~FOREGROUND_MASK) | (attr_reset & FG_RGB);
else if (v[i] >= 40 && v[i] <= 47) {
attr &= ~BG_RGB;
if ((v[i] - 40) & 1)
attr |= BACKGROUND_RED;
if ((v[i] - 40) & 2)
attr |= BACKGROUND_GREEN;
if ((v[i] - 40) & 4)
attr |= BACKGROUND_BLUE;
} else if (v[i] == 49) // reset bg color
attr = (attr & ~BACKGROUND_MASK) | (attr_reset & BG_RGB);
}
SetConsoleTextAttribute(stdo, attr);
break;
case 'K':
GetConsoleScreenBufferInfo(stdo, &csbi);
coord = csbi.dwCursorPosition;
switch (v[0]) {
default:
case 0:
csize = csbi.dwSize.X - coord.X;
break;
case 1:
csize = coord.X;
coord.X = 0;
break;
case 2:
csize = csbi.dwSize.X;
coord.X = 0;
break;
}
FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written);
FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written);
SetConsoleCursorPosition(stdo, csbi.dwCursorPosition);
break;
case 'J':
GetConsoleScreenBufferInfo(stdo, &csbi);
w = csbi.dwSize.X;
h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
coord = csbi.dwCursorPosition;
switch (v[0]) {
default:
case 0:
csize = w * (h - coord.Y) - coord.X;
coord.X = 0;
break;
case 1:
csize = w * coord.Y + coord.X;
coord.X = 0;
coord.Y = csbi.srWindow.Top;
break;
case 2:
csize = w * (h + 1);
coord.X = 0;
coord.Y = csbi.srWindow.Top;
break;
}
FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written);
FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written);
SetConsoleCursorPosition(stdo, csbi.dwCursorPosition);
break;
case 'H':
GetConsoleScreenBufferInfo(stdo, &csbi);
coord = csbi.dwCursorPosition;
if (v[0] != -1) {
if (v[1] != -1) {
coord.Y = csbi.srWindow.Top + v[0] - 1;
coord.X = v[1] - 1;
} else
coord.X = v[0] - 1;
} else {
coord.X = 0;
coord.Y = csbi.srWindow.Top;
}
if (coord.X < csbi.srWindow.Left)
coord.X = csbi.srWindow.Left;
else if (coord.X > csbi.srWindow.Right)
coord.X = csbi.srWindow.Right;
if (coord.Y < csbi.srWindow.Top)
coord.Y = csbi.srWindow.Top;
else if (coord.Y > csbi.srWindow.Bottom)
coord.Y = csbi.srWindow.Bottom;
SetConsoleCursorPosition(stdo, coord);
break;
default:
break;
}
} else {
putchar(*ptr);
ptr++;
}
}
return ptr - buf;
}
#endif /* _WIN32 */
================================================
FILE: src/scandir.c
================================================
#include <dirent.h>
#include <stdlib.h>
#include "scandir.h"
#include "util.h"
int ag_scandir(const char *dirname,
struct dirent ***namelist,
filter_fp filter,
void *baton) {
DIR *dirp = NULL;
struct dirent **names = NULL;
struct dirent *entry, *d;
int names_len = 32;
int results_len = 0;
dirp = opendir(dirname);
if (dirp == NULL) {
goto fail;
}
names = malloc(sizeof(struct dirent *) * names_len);
if (names == NULL) {
goto fail;
}
while ((entry = readdir(dirp)) != NULL) {
if ((*filter)(dirname, entry, baton) == FALSE) {
continue;
}
if (results_len >= names_len) {
struct dirent **tmp_names = names;
names_len *= 2;
names = realloc(names, sizeof(struct dirent *) * names_len);
if (names == NULL) {
free(tmp_names);
goto fail;
}
}
#if defined(__MINGW32__) || defined(__CYGWIN__)
d = malloc(sizeof(struct dirent));
#else
d = malloc(entry->d_reclen);
#endif
if (d == NULL) {
goto fail;
}
#if defined(__MINGW32__) || defined(__CYGWIN__)
memcpy(d, entry, sizeof(struct dirent));
#else
memcpy(d, entry, entry->d_reclen);
#endif
names[results_len] = d;
results_len++;
}
closedir(dirp);
*namelist = names;
return results_len;
fail:
if (dirp) {
closedir(dirp);
}
if (names != NULL) {
int i;
for (i = 0; i < results_len; i++) {
free(names[i]);
}
free(names);
}
return -1;
}
================================================
FILE: src/scandir.h
================================================
#ifndef SCANDIR_H
#define SCANDIR_H
#include "ignore.h"
typedef struct {
const ignores *ig;
const char *base_path;
size_t base_path_len;
const char *path_start;
} scandir_baton_t;
typedef int (*filter_fp)(const char *path, const struct dirent *, void *);
int ag_scandir(const char *dirname,
struct dirent ***namelist,
filter_fp filter,
void *baton);
#endif
================================================
FILE: src/search.c
================================================
#include "search.h"
#include "print.h"
#include "scandir.h"
size_t alpha_skip_lookup[256];
size_t *find_skip_lookup;
uint8_t h_table[H_SIZE] __attribute__((aligned(64)));
work_queue_t *work_queue = NULL;
work_queue_t *work_queue_tail = NULL;
int done_adding_files = 0;
pthread_cond_t files_ready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t stats_mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t work_queue_mtx = PTHREAD_MUTEX_INITIALIZER;
symdir_t *symhash = NULL;
/* Returns: -1 if skipped, otherwise # of matches */
ssize_t search_buf(const char *buf, const size_t buf_len,
const char *dir_full_path) {
int binary = -1; /* 1 = yes, 0 = no, -1 = don't know */
size_t buf_offset = 0;
if (opts.search_stream) {
binary = 0;
} else if (!opts.search_binary_files && opts.mmap) { /* if not using mmap, binary files have already been skipped */
binary = is_binary((const void *)buf, buf_len);
if (binary) {
log_debug("File %s is binary. Skipping...", dir_full_path);
return -1;
}
}
size_t matches_len = 0;
match_t *matches;
size_t matches_size;
size_t matches_spare;
if (opts.invert_match) {
/* If we are going to invert the set of matches at the end, we will need
* one extra match struct, even if there are no matches at all. So make
* sure we have a nonempty array; and make sure we always have spare
* capacity for one extra.
*/
matches_size = 100;
matches = ag_malloc(matches_size * sizeof(match_t));
matches_spare = 1;
} else {
matches_size = 0;
matches = NULL;
matches_spare = 0;
}
if (!opts.literal && opts.query_len == 1 && opts.query[0] == '.') {
matches_size = 1;
matches = matches == NULL ? ag_malloc(matches_size * sizeof(match_t)) : matches;
matches[0].start = 0;
matches[0].end = buf_len;
matches_len = 1;
} else if (opts.literal) {
const char *match_ptr = buf;
while (buf_offset < buf_len) {
/* hash_strnstr only for little-endian platforms that allow unaligned access */
#if defined(__i386__) || defined(__x86_64__)
/* Decide whether to fall back on boyer-moore */
if ((size_t)opts.query_len < 2 * sizeof(uint16_t) - 1 || opts.query_len >= UCHAR_MAX) {
match_ptr = boyer_moore_strnstr(match_ptr, opts.query, buf_len - buf_offset, opts.query_len, alpha_skip_lookup, find_skip_lookup, opts.casing == CASE_INSENSITIVE);
} else {
match_ptr = hash_strnstr(match_ptr, opts.query, buf_len - buf_offset, opts.query_len, h_table, opts.casing == CASE_SENSITIVE);
}
#else
match_ptr = boyer_moore_strnstr(match_ptr, opts.query, buf_len - buf_offset, opts.query_len, alpha_skip_lookup, find_skip_lookup, opts.casing == CASE_INSENSITIVE);
#endif
if (match_ptr == NULL) {
break;
}
if (opts.word_regexp) {
const char *start = match_ptr;
const char *end = match_ptr + opts.query_len;
/* Check whether both start and end of the match lie on a word
* boundary
*/
if ((start == buf ||
is_wordchar(*(start - 1)) != opts.literal_starts_wordchar) &&
(end == buf + buf_len ||
is_wordchar(*end) != opts.literal_ends_wordchar)) {
/* It's a match */
} else {
/* It's not a match */
match_ptr += find_skip_lookup[0] - opts.query_len + 1;
buf_offset = match_ptr - buf;
continue;
}
}
realloc_matches(&matches, &matches_size, matches_len + matches_spare);
matches[matches_len].start = match_ptr - buf;
matches[matches_len].end = matches[matches_len].start + opts.query_len;
buf_offset = matches[matches_len].end;
log_debug("Match found. File %s, offset %lu bytes.", dir_full_path, matches[matches_len].start);
matches_len++;
match_ptr += opts.query_len;
if (opts.max_matches_per_file > 0 && matches_len >= opts.max_matches_per_file) {
log_err("Too many matches in %s. Skipping the rest of this file.", dir_full_path);
break;
}
}
} else {
int offset_vector[3];
if (opts.multiline) {
while (buf_offset < buf_len &&
(pcre_exec(opts.re, opts.re_extra, buf, buf_len, buf_offset, 0, offset_vector, 3)) >= 0) {
log_debug("Regex match found. File %s, offset %i bytes.", dir_full_path, offset_vector[0]);
buf_offset = offset_vector[1];
if (offset_vector[0] == offset_vector[1]) {
++buf_offset;
log_debug("Regex match is of length zero. Advancing offset one byte.");
}
realloc_matches(&matches, &matches_size, matches_len + matches_spare);
matches[matches_len].start = offset_vector[0];
matches[matches_len].end = offset_vector[1];
matches_len++;
if (opts.max_matches_per_file > 0 && matches_len >= opts.max_matches_per_file) {
log_err("Too many matches in %s. Skipping the rest of this file.", dir_full_path);
break;
}
}
} else {
while (buf_offset < buf_len) {
const char *line;
size_t line_len = buf_getline(&line, buf, buf_len, buf_offset);
if (!line) {
break;
}
size_t line_offset = 0;
while (line_offset < line_len) {
int rv = pcre_exec(opts.re, opts.re_extra, line, line_len, line_offset, 0, offset_vector, 3);
if (rv < 0) {
break;
}
size_t line_to_buf = buf_offset + line_offset;
log_debug("Regex match found. File %s, offset %i bytes.", dir_full_path, offset_vector[0]);
line_offset = offset_vector[1];
if (offset_vector[0] == offset_vector[1]) {
++line_offset;
log_debug("Regex match is of length zero. Advancing offset one byte.");
}
realloc_matches(&matches, &matches_size, matches_len + matches_spare);
matches[matches_len].start = offset_vector[0] + line_to_buf;
matches[matches_len].end = offset_vector[1] + line_to_buf;
matches_len++;
if (opts.max_matches_per_file > 0 && matches_len >= opts.max_matches_per_file) {
log_err("Too many matches in %s. Skipping the rest of this file.", dir_full_path);
goto multiline_done;
}
}
buf_offset += line_len + 1;
}
}
}
multiline_done:
if (opts.invert_match) {
matches_len = invert_matches(buf, buf_len, matches, matches_len);
}
if (opts.stats) {
pthread_mutex_lock(&stats_mtx);
stats.total_bytes += buf_len;
stats.total_files++;
stats.total_matches += matches_len;
if (matches_len > 0) {
stats.total_file_matches++;
}
pthread_mutex_unlock(&stats_mtx);
}
if (!opts.print_nonmatching_files && (matches_len > 0 || opts.print_all_paths)) {
if (binary == -1 && !opts.print_filename_only) {
binary = is_binary((const void *)buf, buf_len);
}
pthread_mutex_lock(&print_mtx);
if (opts.print_filename_only) {
if (opts.print_count) {
print_path_count(dir_full_path, opts.path_sep, (size_t)matches_len);
} else {
print_path(dir_full_path, opts.path_sep);
}
} else if (binary) {
print_binary_file_matches(dir_full_path);
} else {
print_file_matches(dir_full_path, buf, buf_len, matches, matches_len);
}
pthread_mutex_unlock(&print_mtx);
opts.match_found = 1;
} else if (opts.search_stream && opts.passthrough) {
fprintf(out_fd, "%s", buf);
} else {
log_debug("No match in %s", dir_full_path);
}
if (matches_len == 0 && opts.search_stream) {
print_context_append(buf, buf_len - 1);
}
if (matches_size > 0) {
free(matches);
}
/* FIXME: handle case where matches_len > SSIZE_MAX */
return (ssize_t)matches_len;
}
/* Return value: -1 if skipped, otherwise # of matches */
/* TODO: this will only match single lines. multi-line regexes silently don't match */
ssize_t search_stream(FILE *stream, const char *path) {
char *line = NULL;
ssize_t matches_count = 0;
ssize_t line_len = 0;
size_t line_cap = 0;
size_t i;
print_init_context();
for (i = 1; (line_len = getline(&line, &line_cap, stream)) > 0; i++) {
ssize_t result;
opts.stream_line_num = i;
result = search_buf(line, line_len, path);
if (result > 0) {
if (matches_count == -1) {
matches_count = 0;
}
matches_count += result;
} else if (matches_count <= 0 && result == -1) {
matches_count = -1;
}
if (line[line_len - 1] == '\n') {
line_len--;
}
print_trailing_context(path, line, line_len);
}
free(line);
print_cleanup_context();
return matches_count;
}
void search_file(const char *file_full_path) {
int fd = -1;
off_t f_len = 0;
char *buf = NULL;
struct stat statbuf;
int rv = 0;
int matches_count = -1;
FILE *fp = NULL;
rv = stat(file_full_path, &statbuf);
if (rv != 0) {
log_err("Skipping %s: Error fstat()ing file.", file_full_path);
goto cleanup;
}
if (opts.stdout_inode != 0 && opts.stdout_inode == statbuf.st_ino) {
log_debug("Skipping %s: stdout is redirected to it", file_full_path);
goto cleanup;
}
// handling only regular files and FIFOs
if (!S_ISREG(statbuf.st_mode) && !S_ISFIFO(statbuf.st_mode)) {
log_err("Skipping %s: Mode %u is not a file.", file_full_path, statbuf.st_mode);
goto cleanup;
}
fd = open(file_full_path, O_RDONLY);
if (fd < 0) {
/* XXXX: strerror is not thread-safe */
log_err("Skipping %s: Error opening file: %s", file_full_path, strerror(errno));
goto cleanup;
}
// repeating stat check with file handle to prevent TOCTOU issue
rv = fstat(fd, &statbuf);
if (rv != 0) {
log_err("Skipping %s: Error fstat()ing file.", file_full_path);
goto cleanup;
}
if (opts.stdout_inode != 0 && opts.stdout_inode == statbuf.st_ino) {
log_debug("Skipping %s: stdout is redirected to it", file_full_path);
goto cleanup;
}
// handling only regular files and FIFOs
if (!S_ISREG(statbuf.st_mode) && !S_ISFIFO(statbuf.st_mode)) {
log_err("Skipping %s: Mode %u is not a file.", file_full_path, statbuf.st_mode);
goto cleanup;
}
print_init_context();
if (statbuf.st_mode & S_IFIFO) {
log_debug("%s is a named pipe. stream searching", file_full_path);
fp = fdopen(fd, "r");
matches_count = search_stream(fp, file_full_path);
fclose(fp);
goto cleanup;
}
f_len = statbuf.st_size;
if (f_len == 0) {
if (opts.query[0] == '.' && opts.query_len == 1 && !opts.literal && opts.search_all_files) {
matches_count = search_buf(buf, f_len, file_full_path);
} else {
log_debug("Skipping %s: file is empty.", file_full_path);
}
goto cleanup;
}
if (!opts.literal && f_len > INT_MAX) {
log_err("Skipping %s: pcre_exec() can't handle files larger than %i bytes.", file_full_path, INT_MAX);
goto cleanup;
}
#ifdef _WIN32
{
HANDLE hmmap = CreateFileMapping(
(HANDLE)_get_osfhandle(fd), 0, PAGE_READONLY, 0, f_len, NULL);
buf = (char *)MapViewOfFile(hmmap, FILE_SHARE_READ, 0, 0, f_len);
if (hmmap != NULL)
CloseHandle(hmmap);
}
if (buf == NULL) {
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), 0, (void *)&buf, 0, NULL);
log_err("File %s failed to load: %s.", file_full_path, buf);
LocalFree((void *)buf);
goto cleanup;
}
#else
if (opts.mmap) {
buf = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED) {
log_err("File %s failed to load: %s.", file_full_path, strerror(errno));
goto cleanup;
}
#if HAVE_MADVISE
madvise(buf, f_len, MADV_SEQUENTIAL);
#elif HAVE_POSIX_FADVISE
posix_fadvise(fd, 0, f_len, POSIX_MADV_SEQUENTIAL);
#endif
} else {
buf = ag_malloc(f_len);
ssize_t bytes_read = 0;
if (!opts.search_binary_files) {
bytes_read += read(fd, buf, ag_min(f_len, 512));
// Optimization: If skipping binary files, don't read the whole buffer before checking if binary or not.
if (is_binary(buf, f_len)) {
log_debug("File %s is binary. Skipping...", file_full_path);
goto cleanup;
}
}
while (bytes_read < f_len) {
bytes_read += read(fd, buf + bytes_read, f_len);
}
if (bytes_read != f_len) {
die("File %s read(): expected to read %u bytes but read %u", file_full_path, f_len, bytes_read);
}
}
#endif
if (opts.search_zip_files) {
ag_compression_type zip_type = is_zipped(buf, f_len);
if (zip_type != AG_NO_COMPRESSION) {
#if HAVE_FOPENCOOKIE
log_debug("%s is a compressed file. stream searching", file_full_path);
fp = decompress_open(fd, "r", zip_type);
matches_count = search_stream(fp, file_full_path);
fclose(fp);
#else
int _buf_len = (int)f_len;
char *_buf = decompress(zip_type, buf, f_len, file_full_path, &_buf_len);
if (_buf == NULL || _buf_len == 0) {
log_err("Cannot decompress zipped file %s", file_full_path);
goto cleanup;
}
matches_count = search_buf(_buf, _buf_len, file_full_path);
free(_buf);
#endif
goto cleanup;
}
}
matches_count = search_buf(buf, f_len, file_full_path);
cleanup:
if (opts.print_nonmatching_files && matches_count == 0) {
pthread_mutex_lock(&print_mtx);
print_path(file_full_path, opts.path_sep);
pthread_mutex_unlock(&print_mtx);
opts.match_found = 1;
}
print_cleanup_context();
if (buf != NULL) {
#ifdef _WIN32
UnmapViewOfFile(buf);
#else
if (opts.mmap) {
if (buf != MAP_FAILED) {
munmap(buf, f_len);
}
} else {
free(buf);
}
#endif
}
if (fd != -1) {
close(fd);
}
}
void *search_file_worker(void *i) {
work_queue_t *queue_item;
int worker_id = *(int *)i;
log_debug("Worker %i started", worker_id);
while (TRUE) {
pthread_mutex_lock(&work_queue_mtx);
while (work_queue == NULL) {
if (done_adding_files) {
pthread_mutex_unlock(&work_queue_mtx);
log_debug("Worker %i finished.", worker_id);
pthread_exit(NULL);
}
pthread_cond_wait(&files_ready, &work_queue_mtx);
}
queue_item = work_queue;
work_queue = work_queue->next;
if (work_queue == NULL) {
work_queue_tail = NULL;
}
pthread_mutex_unlock(&work_queue_mtx);
search_file(queue_item->path);
free(queue_item->path);
free(queue_item);
}
}
static int check_symloop_enter(const char *path, dirkey_t *outkey) {
#ifdef _WIN32
gitextract_qvo96f4r/ ├── .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.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
SYMBOL INDEX (112 symbols across 22 files)
FILE: src/decompress.c
function ag_compression_type (line 220) | ag_compression_type is_zipped(const void *buf, const int buf_len) {
FILE: src/decompress.h
type ag_compression_type (line 10) | typedef enum {
FILE: src/ignore.c
function is_empty (line 42) | int is_empty(ignores *ig) {
function ignores (line 46) | ignores *init_ignore(ignores *parent, const char *dirname, const size_t ...
function cleanup_ignore (line 83) | void cleanup_ignore(ignores *ig) {
function add_ignore_pattern (line 99) | void add_ignore_pattern(ignores *ig, const char *pattern) {
function load_ignore_patterns (line 173) | void load_ignore_patterns(ignores *ig, const char *path) {
function ackmate_dir_match (line 200) | static int ackmate_dir_match(const char *dir_name) {
function path_ignore_search (line 209) | static int path_ignore_search(const ignores *ig, const char *path, const...
function filename_filter (line 292) | int filename_filter(const char *path, const struct dirent *dir, void *ba...
FILE: src/ignore.h
type ignores (line 7) | struct ignores {
type ignores (line 30) | typedef struct ignores ignores;
type dirent (line 44) | struct dirent
FILE: src/lang.c
function get_lang_count (line 148) | size_t get_lang_count() {
function combine_file_extensions (line 184) | size_t combine_file_extensions(size_t *extension_index, size_t len, char...
FILE: src/lang.h
type lang_spec_t (line 7) | typedef struct {
FILE: src/log.c
type log_level (line 8) | enum log_level
function set_log_level (line 10) | void set_log_level(enum log_level threshold) {
function log_debug (line 14) | void log_debug(const char *fmt, ...) {
function log_msg (line 21) | void log_msg(const char *fmt, ...) {
function log_warn (line 28) | void log_warn(const char *fmt, ...) {
function log_err (line 35) | void log_err(const char *fmt, ...) {
function vplog (line 42) | void vplog(const unsigned int level, const char *fmt, va_list args) {
function plog (line 71) | void plog(const unsigned int level, const char *fmt, ...) {
FILE: src/log.h
type log_level (line 14) | enum log_level {
type log_level (line 22) | enum log_level
FILE: src/main.c
type worker_t (line 31) | typedef struct {
function main (line 36) | int main(int argc, char **argv) {
FILE: src/options.c
function usage (line 26) | void usage(void) {
function print_version (line 127) | void print_version(void) {
function init_options (line 147) | void init_options(void) {
function cleanup_options (line 179) | void cleanup_options(void) {
function parse_options (line 209) | void parse_options(int argc, char **argv, char **base_paths[], char **pa...
FILE: src/options.h
type case_behavior (line 13) | enum case_behavior {
type path_print_behavior (line 21) | enum path_print_behavior {
type cli_options (line 29) | typedef struct {
type option_t (line 97) | typedef struct option option_t;
FILE: src/print.c
type print_context (line 23) | struct print_context {
function print_init_context (line 36) | void print_init_context(void) {
function print_cleanup_context (line 52) | void print_cleanup_context(void) {
function print_context_append (line 68) | void print_context_append(const char *line, size_t len) {
function print_trailing_context (line 79) | void print_trailing_context(const char *path, const char *buf, size_t n) {
function print_path (line 103) | void print_path(const char *path, const char sep) {
function print_path_count (line 122) | void print_path_count(const char *path, const char sep, const size_t cou...
function print_line (line 133) | void print_line(const char *buf, size_t buf_pos, size_t prev_line_offset) {
function print_binary_file_matches (line 142) | void print_binary_file_matches(const char *path) {
function print_file_matches (line 148) | void print_file_matches(const char *path, const char *buf, const size_t ...
function print_line_number (line 334) | void print_line_number(size_t line, const char sep) {
function print_column_number (line 345) | void print_column_number(const match_t matches[], size_t last_printed_ma...
function print_file_separator (line 354) | void print_file_separator(void) {
FILE: src/print_w32.c
function windows_use_ansi (line 29) | void windows_use_ansi(int use_ansi) {
function fprintf_w32 (line 33) | int fprintf_w32(FILE *fp, const char *format, ...) {
FILE: src/scandir.c
function ag_scandir (line 7) | int ag_scandir(const char *dirname,
FILE: src/scandir.h
type scandir_baton_t (line 6) | typedef struct {
type dirent (line 13) | struct dirent
type dirent (line 16) | struct dirent
FILE: src/search.c
function search_buf (line 19) | ssize_t search_buf(const char *buf, const size_t buf_len,
function search_stream (line 229) | ssize_t search_stream(FILE *stream, const char *path) {
function search_file (line 261) | void search_file(const char *file_full_path) {
function check_symloop_enter (line 472) | static int check_symloop_enter(const char *path, dirkey_t *outkey) {
function check_symloop_leave (line 505) | static int check_symloop_leave(dirkey_t *dirkey) {
function search_dir (line 530) | void search_dir(ignores *ig, const char *base_path, const char *path, co...
FILE: src/search.h
type work_queue_t (line 38) | struct work_queue_t {
type work_queue_t (line 42) | typedef struct work_queue_t work_queue_t;
type dirkey_t (line 57) | typedef struct {
type symdir_t (line 62) | typedef struct {
FILE: src/uthash.h
type UT_hash_bucket (line 903) | typedef struct UT_hash_bucket {
type UT_hash_table (line 927) | typedef struct UT_hash_table {
type UT_hash_handle (line 960) | typedef struct UT_hash_handle {
FILE: src/util.c
function free_strings (line 58) | void free_strings(char **strs, const size_t strs_len) {
function generate_alpha_skip (line 69) | void generate_alpha_skip(const char *find, size_t f_len, size_t skip_loo...
function is_prefix (line 88) | int is_prefix(const char *s, const size_t s_len, const size_t pos, const...
function suffix_len (line 106) | size_t suffix_len(const char *s, const size_t s_len, const size_t pos, c...
function generate_find_skip (line 124) | void generate_find_skip(const char *find, const size_t f_len, size_t **s...
function ag_max (line 146) | size_t ag_max(size_t a, size_t b) {
function ag_min (line 153) | size_t ag_min(size_t a, size_t b) {
function generate_hash (line 160) | void generate_hash(const char *find, const size_t f_len, uint8_t *h_tabl...
function NO_SANITIZE_ALIGNMENT (line 206) | NO_SANITIZE_ALIGNMENT const char *hash_strnstr(const char *s, const char...
function invert_matches (line 242) | size_t invert_matches(const char *buf, const size_t buf_len, match_t mat...
function realloc_matches (line 307) | void realloc_matches(match_t **matches, size_t *matches_size, size_t mat...
function compile_study (line 316) | void compile_study(pcre **re, pcre_extra **re_extra, char *q, const int ...
function is_binary (line 333) | int is_binary(const void *buf, const size_t buf_len) {
function is_regex (line 388) | int is_regex(const char *query) {
function is_fnmatch (line 408) | int is_fnmatch(const char *filename) {
function binary_search (line 421) | int binary_search(const char *needle, char **haystack, int start, int en...
function init_wordchar_table (line 443) | void init_wordchar_table(void) {
function is_wordchar (line 455) | int is_wordchar(char ch) {
function is_lowercase (line 459) | int is_lowercase(const char *s) {
function is_directory (line 469) | int is_directory(const char *path, const struct dirent *d) {
function is_symlink (line 494) | int is_symlink(const char *path, const struct dirent *d) {
function is_named_pipe (line 519) | int is_named_pipe(const char *path, const struct dirent *d) {
function ag_asprintf (line 540) | void ag_asprintf(char **ret, const char *fmt, ...) {
function die (line 549) | void die(const char *fmt, ...) {
function getline (line 592) | ssize_t getline(char **lineptr, size_t *n, FILE *stream) {
function buf_getline (line 626) | ssize_t buf_getline(const char **line, const char *buf, const size_t buf...
function strlcpy (line 655) | size_t strlcpy(char *dst, const char *src, size_t size) {
function vasprintf (line 684) | int vasprintf(char **ret, const char *fmt, va_list args) {
FILE: src/util.h
type match_t (line 39) | typedef struct {
type ag_stats (line 44) | typedef struct {
type word_t (line 57) | typedef union {
type dirent (line 93) | struct dirent
type dirent (line 94) | struct dirent
type dirent (line 95) | struct dirent
FILE: src/zfile.c
type _off64_t (line 7) | typedef _off64_t off64_t;
type zfile (line 53) | struct zfile {
function zfile_cookie_init (line 75) | static int
function zfile_cookie_cleanup (line 127) | static void
function FILE (line 150) | FILE *
function zfile_read (line 205) | static ssize_t
function zfile_seek (line 333) | static int
function zfile_close (line 392) | static int
FILE: tests/big/create_big_file.py
function create_big_file (line 15) | def create_big_file():
Condensed preview — 95 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (365K chars).
[
{
"path": ".clang-format",
"chars": 1178,
"preview": "AlignEscapedNewlinesLeft: false\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBl"
},
{
"path": ".gitignore",
"chars": 359,
"preview": "*.dSYM\n*.gcda\n*.o\n*.plist\n.deps\n.dirstamp\n.DS_Store\naclocal.m4\nag\nag.exe\nautom4te.cache\ncachegrind.out.*\ncallgrind.out.*"
},
{
"path": ".travis.yml",
"chars": 886,
"preview": "language: c\ndist: xenial\nsudo: false\n\nbranches:\n only:\n - master\n - ppc64le\narch:\n - amd64\n - ppc64le\n\ncompiler"
},
{
"path": "CONTRIBUTING.md",
"chars": 1503,
"preview": "## Contributing\n\nI like when people send pull requests. It validates my existence. If you want to help out, check the [i"
},
{
"path": "LICENSE",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile.am",
"chars": 995,
"preview": "ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS}\n\nbin_PROGRAMS = ag\nag_SOURCES = src/ignore.c src/ignore.h src/log.c src/log.h src/opt"
},
{
"path": "Makefile.w32",
"chars": 668,
"preview": "SED=sed\nVERSION:=$(shell \"$(SED)\" -n \"s/[^[]*\\[\\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\)\\],/\\1/p\" configure.ac)\n\nCC=gcc\nRM=/bin/rm\n\n"
},
{
"path": "NOTICE",
"chars": 52,
"preview": "The Silver Searcher\nCopyright 2011-2016 Geoff Greer\n"
},
{
"path": "README.md",
"chars": 7985,
"preview": "# The Silver Searcher\n\nA code searching tool similar to `ack`, with a focus on speed.\n\n[ {\n local lngopt shtopt split=false\n local cur prev\n\n COMPREPLY=()\n cur=$(_get_cword \"=\")\n prev=\"${COMP_WORDS["
},
{
"path": "autogen.sh",
"chars": 475,
"preview": "#!/bin/sh\n\nset -e\ncd \"$(dirname \"$0\")\"\n\nAC_SEARCH_OPTS=\"\"\n# For those of us with pkg-config and other tools in /usr/loca"
},
{
"path": "build.sh",
"chars": 79,
"preview": "#!/bin/sh\n\nset -e\ncd \"$(dirname \"$0\")\"\n\n./autogen.sh\n./configure \"$@\"\nmake -j4\n"
},
{
"path": "configure.ac",
"chars": 2740,
"preview": "AC_INIT(\n [the_silver_searcher],\n [2.2.0],\n [https://github.com/ggreer/the_silver_searcher/issues],\n [the_si"
},
{
"path": "doc/ag.1",
"chars": 8407,
"preview": ".\\\" generated with Ronn/v0.7.3\n.\\\" http://github.com/rtomayko/ronn/tree/0.7.3\n.\n.TH \"AG\" \"1\" \"December 2016\" \"\" \"\"\n.\n.SH"
},
{
"path": "doc/ag.1.md",
"chars": 7914,
"preview": "ag(1) -- The Silver Searcher. Like ack, but faster.\n=============================================\n\n## SYNOPSIS\n\n`ag` [_o"
},
{
"path": "doc/generate_man.sh",
"chars": 269,
"preview": "#!/bin/sh\n\n# ronn is used to turn the markdown into a manpage.\n# Get ronn at https://github.com/rtomayko/ronn\n# Alternat"
},
{
"path": "format.sh",
"chars": 1291,
"preview": "#!/bin/bash\n\nfunction usage() {\n echo \"Usage: $0 test|reformat\"\n}\n\nif [ $# -eq 0 ]\nthen\n usage\n exit 0\nfi\n\nif ["
},
{
"path": "m4/ax_pthread.m4",
"chars": 13758,
"preview": "# ===========================================================================\n# http://www.gnu.org/software/autoc"
},
{
"path": "pgo.sh",
"chars": 188,
"preview": "#!/bin/sh\n\nset -e\ncd \"$(dirname \"$0\")\"\n\nmake clean\n./build.sh CFLAGS=\"$CFLAGS -fprofile-generate\"\n./ag example ..\nmake c"
},
{
"path": "sanitize.sh",
"chars": 4940,
"preview": "#!/bin/bash\n# Copyright 2016 Allen Wild\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may no"
},
{
"path": "src/decompress.c",
"chars": 8330,
"preview": "#include <string.h>\n#include <unistd.h>\n\n#include \"decompress.h\"\n\n#ifdef HAVE_LZMA_H\n#include <lzma.h>\n\n/* http://tukaa"
},
{
"path": "src/decompress.h",
"chars": 553,
"preview": "#ifndef DECOMPRESS_H\n#define DECOMPRESS_H\n\n#include <stdio.h>\n\n#include \"config.h\"\n#include \"log.h\"\n#include \"options.h\""
},
{
"path": "src/ignore.c",
"chars": 11889,
"preview": "#include <ctype.h>\n#include <dirent.h>\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#i"
},
{
"path": "src/ignore.h",
"chars": 1231,
"preview": "#ifndef IGNORE_H\n#define IGNORE_H\n\n#include <dirent.h>\n#include <sys/types.h>\n\nstruct ignores {\n char **extensions; /"
},
{
"path": "src/lang.c",
"chars": 7419,
"preview": "#include <stdlib.h>\n#include <string.h>\n\n#include \"lang.h\"\n#include \"util.h\"\n\nlang_spec_t langs[] = {\n { \"actionscrip"
},
{
"path": "src/lang.h",
"chars": 860,
"preview": "#ifndef LANG_H\n#define LANG_H\n\n#define MAX_EXTENSIONS 12\n#define SINGLE_EXT_LEN 20\n\ntypedef struct {\n const char *nam"
},
{
"path": "src/log.c",
"chars": 1670,
"preview": "#include <stdarg.h>\n#include <stdio.h>\n\n#include \"log.h\"\n#include \"util.h\"\n\npthread_mutex_t print_mtx = PTHREAD_MUTEX_IN"
},
{
"path": "src/log.h",
"chars": 633,
"preview": "#ifndef LOG_H\n#define LOG_H\n\n#include <stdarg.h>\n\n#include \"config.h\"\n\n#ifdef HAVE_PTHREAD_H\n#include <pthread.h>\n#endif"
},
{
"path": "src/main.c",
"chars": 7392,
"preview": "#include <ctype.h>\n#include <pcre.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/time.h>\n#i"
},
{
"path": "src/options.c",
"chars": 32019,
"preview": "#include <errno.h>\n#include <limits.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#i"
},
{
"path": "src/options.h",
"chars": 2719,
"preview": "#ifndef OPTIONS_H\n#define OPTIONS_H\n\n#include <getopt.h>\n#include <sys/stat.h>\n\n#include <pcre.h>\n\n#define DEFAULT_AFTER"
},
{
"path": "src/print.c",
"chars": 14334,
"preview": "#include <limits.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"ignore.h\"\n"
},
{
"path": "src/print.h",
"chars": 1029,
"preview": "#ifndef PRINT_H\n#define PRINT_H\n\n#include \"util.h\"\n\nvoid print_init_context(void);\nvoid print_cleanup_context(void);\nvoi"
},
{
"path": "src/print_w32.c",
"chars": 14355,
"preview": "#ifdef _WIN32\n\n#include \"print.h\"\n#include <io.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <windows.h>\n\n#ifndef F"
},
{
"path": "src/scandir.c",
"chars": 1689,
"preview": "#include <dirent.h>\n#include <stdlib.h>\n\n#include \"scandir.h\"\n#include \"util.h\"\n\nint ag_scandir(const char *dirname,\n "
},
{
"path": "src/scandir.h",
"chars": 423,
"preview": "#ifndef SCANDIR_H\n#define SCANDIR_H\n\n#include \"ignore.h\"\n\ntypedef struct {\n const ignores *ig;\n const char *base_p"
},
{
"path": "src/search.c",
"chars": 24343,
"preview": "#include \"search.h\"\n#include \"print.h\"\n#include \"scandir.h\"\n\nsize_t alpha_skip_lookup[256];\nsize_t *find_skip_lookup;\nui"
},
{
"path": "src/search.h",
"chars": 1600,
"preview": "#ifndef SEARCH_H\n#define SEARCH_H\n\n#include <dirent.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#includ"
},
{
"path": "src/uthash.h",
"chars": 75812,
"preview": "/*\nCopyright (c) 2003-2014, Troy D. Hanson http://troydhanson.github.com/uthash/\nAll rights reserved.\n\nRedistributio"
},
{
"path": "src/util.c",
"chars": 18961,
"preview": "#include <ctype.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n"
},
{
"path": "src/util.h",
"chars": 3634,
"preview": "#ifndef UTIL_H\n#define UTIL_H\n\n#include <dirent.h>\n#include <pcre.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <st"
},
{
"path": "src/win32/config.h",
"chars": 43,
"preview": "#define HAVE_LZMA_H\n#define HAVE_PTHREAD_H\n"
},
{
"path": "src/zfile.c",
"chars": 10501,
"preview": "#ifdef __FreeBSD__\n#include <sys/endian.h>\n#endif\n#include <sys/types.h>\n\n#ifdef __CYGWIN__\ntypedef _off64_t off64_t;\n#e"
},
{
"path": "tests/adjacent_matches.t",
"chars": 309,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ alias ag=\"$TESTDIR/../ag --noaffinity --workers=1 --parallel --color\"\n $ printf 'bl"
},
{
"path": "tests/bad_path.t",
"chars": 205,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n\nComplain about nonexistent path:\n\n $ ag foo doesnt_exist\n ERR: Error stat()ing: doesn"
},
{
"path": "tests/big/big_file.t",
"chars": 667,
"preview": "Setup and create really big file:\n\n $ . $TESTDIR/../setup.sh\n $ python3 $TESTDIR/create_big_file.py $TESTDIR/big_file."
},
{
"path": "tests/big/create_big_file.py",
"chars": 638,
"preview": "#!/usr/bin/env python\n\n# Create an 8GB file of mostly \"abcdefghijklmnopqrstuvwxyz01234\",\n# with a few instances of \"hell"
},
{
"path": "tests/case_sensitivity.t",
"chars": 604,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'Foo\\n' >> ./sample\n $ printf 'bar\\n' >> ./sample\n\nSmart case by default:\n\n "
},
{
"path": "tests/color.t",
"chars": 606,
"preview": "Setup. Note that we have to turn --color on manually since ag detects that\nstdout isn't a tty when running in cram.\n\n $"
},
{
"path": "tests/column.t",
"chars": 342,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf \"blah\\nblah2\\n\" > blah.txt\n\nEnsure column is correct:\n\n $ ag --column \"blah\\"
},
{
"path": "tests/count.t",
"chars": 549,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ unalias ag\n $ alias ag=\"$TESTDIR/../ag --noaffinity --nocolor --workers=1\"\n $ prin"
},
{
"path": "tests/ds_store_ignore.t",
"chars": 245,
"preview": "Setup.\n $ . $TESTDIR/setup.sh\n $ mkdir -p dir0/dir1/dir2\n $ printf '*.DS_Store\\n' > dir0/.ignore\n $ printf 'blah\\n' "
},
{
"path": "tests/empty_environment.t",
"chars": 216,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf \"hello world\\n\" >test.txt\n\nVerify ag runs with an empty environment:\n\n $ env"
},
{
"path": "tests/empty_match.t",
"chars": 539,
"preview": "Setup.\n $ . $TESTDIR/setup.sh\n $ touch empty.txt\n $ printf 'foo\\n' > nonempty.txt\n\nZero-length match on an empty file"
},
{
"path": "tests/exitcodes.t",
"chars": 366,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'foo\\n' > ./exitcodes_test.txt\n $ printf 'bar\\n' >> ./exitcodes_test.txt\n\nNo"
},
{
"path": "tests/fail/unicode_case_insensitive.t",
"chars": 357,
"preview": "Setup:\n\n $ . $TESTDIR/../setup.sh\n $ printf \"hello=你好\\n\" > test.txt\n $ printf \"hello=你好\\n\" >> test.txt\n\nNormal search"
},
{
"path": "tests/fail/unicode_case_insensitive.t.err",
"chars": 469,
"preview": "Setup:\n\n $ . $TESTDIR/../setup.sh\n $ printf \"hello=你好\\n\" > test.txt\n $ printf \"hello=你好\\n\" >> test.txt\n\nNormal search"
},
{
"path": "tests/files_with_matches.t",
"chars": 1717,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'foo\\n' > ./foo.txt\n $ printf 'bar\\n' > ./bar.txt\n $ printf 'foo\\nbar\\nbaz\\"
},
{
"path": "tests/filetype.t",
"chars": 719,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ TEST_FILETYPE_EXT1=`ag --list-file-types | grep -E '^[ \\t]+\\..+' | head -n 1 | awk '"
},
{
"path": "tests/hidden_option.t",
"chars": 639,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir hidden_bug\n $ cd hidden_bug\n $ printf \"test\\n\" > a.txt\n $ git init --quiet\n"
},
{
"path": "tests/ignore_abs_path.t",
"chars": 505,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir -p ./a/b/c\n $ printf 'whatever1\\n' > ./a/b/c/blah.yml\n $ printf 'whatever2\\n"
},
{
"path": "tests/ignore_absolute_search_path_with_glob.t",
"chars": 323,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir -p parent/multi-part\n $ printf 'match1\\n' > parent/multi-part/file1.txt\n $ p"
},
{
"path": "tests/ignore_backups.t",
"chars": 1515,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir -p ./a/b/c\n $ printf 'whatever1\\n' > ./a/b/c/foo.yml\n $ printf 'whatever2\\n"
},
{
"path": "tests/ignore_examine_parent_ignorefiles.t",
"chars": 433,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir -p subdir\n $ printf 'match1\\n' > subdir/file1.txt\n $ printf 'file1.txt\\n' > "
},
{
"path": "tests/ignore_extensions.t",
"chars": 1162,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf '*.js\\n' > .ignore\n $ printf '*.test.txt\\n' >> .ignore\n $ printf 'targetA\\n"
},
{
"path": "tests/ignore_gitignore.t",
"chars": 326,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ export HOME=$PWD\n $ printf '[core]\\nexcludesfile = ~/.gitignore.global' >> $HOME/.g"
},
{
"path": "tests/ignore_invert.t",
"chars": 261,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'blah1\\n' > ./printme.txt\n $ printf 'blah2\\n' > ./dontprintme.c\n $ printf '"
},
{
"path": "tests/ignore_pattern_in_subdirectory.t",
"chars": 299,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir subdir\n $ printf 'first\\n' > file1.txt\n $ printf 'second\\n' > subdir/file2.t"
},
{
"path": "tests/ignore_slash_in_subdir.t",
"chars": 402,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir -p subdir/ignoredir\n $ mkdir ignoredir\n $ printf 'match1\\n' > subdir/ignored"
},
{
"path": "tests/ignore_subdir.t",
"chars": 475,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir -p ./a/b/c\n $ printf 'whatever1\\n' > ./a/b/c/blah.yml\n $ printf 'whatever2\\n"
},
{
"path": "tests/ignore_vcs.t",
"chars": 432,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'whatever1\\n' > ./always.txt\n $ printf 'whatever2\\n' > ./git.txt\n $ printf "
},
{
"path": "tests/invert_match.t",
"chars": 444,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'valid: 1\\n' > ./blah.txt\n $ printf 'some_string\\n' >> ./blah.txt\n $ printf"
},
{
"path": "tests/is_binary_pdf.t",
"chars": 226,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ cp $TESTDIR/is_binary.pdf .\n\nPDF files are binary. Do not search them by default:\n\n "
},
{
"path": "tests/line_width.t",
"chars": 785,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf \"12345678901234567890123456789012345678901234567890\\n\" >> ./blah.txt\n\nTruncat"
},
{
"path": "tests/list_file_types.t",
"chars": 5326,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n\nLanguage types are output:\n\n $ ag --list-file-types\n The following file types are sup"
},
{
"path": "tests/literal_word_regexp.t",
"chars": 757,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ echo 'blah abc def' > blah1.txt\n $ echo 'abc blah def' > blah2.txt\n $ echo 'abc de"
},
{
"path": "tests/max_count.t",
"chars": 913,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf \"blah\\n\" > blah.txt\n $ printf \"blah2\\n\" >> blah.txt\n $ printf \"blah2\\n\" > b"
},
{
"path": "tests/multiline.t",
"chars": 365,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'what\\n' > blah.txt\n $ printf 'ever\\n' >> blah.txt\n $ printf 'whatever\\n' >"
},
{
"path": "tests/negated_options.t",
"chars": 982,
"preview": "Setup:\n\n $ . \"${TESTDIR}/setup.sh\"\n\nShould accept both --no-<option> and --no<option> forms.\n\n(Here we're just parsing "
},
{
"path": "tests/one_device.t",
"chars": 671,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n > if [ ! -e \"/dev/shm\" ]; then\n > echo \"No /dev/shm. Skipping test.\"\n > exit 80\n > "
},
{
"path": "tests/only_matching.t",
"chars": 1048,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf \"the quick brown foxy\\n\" > blah.txt\n $ printf \"blah blah blah\\n\" >> blah.txt"
},
{
"path": "tests/option_g.t",
"chars": 152,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ touch foobar\n\nSearch for lines matching \"hello\" in test_vimgrep.txt:\n\n $ ag -g foob"
},
{
"path": "tests/option_smartcase.t",
"chars": 285,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'asdf\\n' > test.txt\n $ printf 'AsDf\\n' >> test.txt\n\nSmart case search:\n\n $ "
},
{
"path": "tests/passthrough.t",
"chars": 551,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ unalias ag\n $ alias ag=\"$TESTDIR/../ag --noaffinity --nocolor --workers=1\"\n $ prin"
},
{
"path": "tests/pipecontext.t",
"chars": 502,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir pipecontext_dir\n $ printf \"a\\nb\\nc\\n\" > pipecontext_test.txt\n $ cd pipeconte"
},
{
"path": "tests/print_all_files.t",
"chars": 226,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'foo\\n' > ./foo.txt\n $ printf 'bar\\n' > ./bar.txt\n $ printf 'baz\\n' > ./baz"
},
{
"path": "tests/print_end.t",
"chars": 140,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ cp $TESTDIR/print_end.txt .\n\nPrint match at the end of a file\n\n $ ag ergneq1\n prin"
},
{
"path": "tests/print_end.txt",
"chars": 26,
"preview": "ergneqergneqergneq\nergneq1"
},
{
"path": "tests/search_stdin.t",
"chars": 138,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'blah\\n' > ./blah.txt\n\nFeed blah.txt from stdin:\n\n $ ag 'blah' < ./blah.txt\n"
},
{
"path": "tests/setup.sh",
"chars": 422,
"preview": "#!/bin/bash\n\n# All cram tests should use this. Make sure that \"ag\" runs the version\n# of ag we just built, and make the "
},
{
"path": "tests/stupid_fnmatch.t.disabled",
"chars": 286,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ mkdir -p ./a/bomb\n $ printf 'whatever\\n' > ./a/bomb/foo.yml\n $ printf '*b/foo.yml\\"
},
{
"path": "tests/vimgrep.t",
"chars": 824,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ printf 'Hello, \"Hello, world\" programs output \"Hello, world\".\\n' > ./test_vimgrep.tx"
},
{
"path": "tests/word_regexp.t",
"chars": 187,
"preview": "Setup:\n\n $ . $TESTDIR/setup.sh\n $ echo 'foo' > blah.txt\n $ echo 'bar' >> blah.txt\n $ echo 'foobar' >> blah.txt\n\nWord"
},
{
"path": "the_silver_searcher.spec.in",
"chars": 2315,
"preview": "%define _bashcompdir %_sysconfdir/bash_completion.d\n%define _zshcompdir %{_datadir}/zsh/site-functions\n\nName:\t\tthe_silve"
}
]
About this extraction
This page contains the full source code of the ggreer/the_silver_searcher GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 95 files (338.7 KB), approximately 87.1k tokens, and a symbol index with 112 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.