├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── appveyor.yml ├── bin ├── travis_before_release └── travis_publish ├── build-all.sh ├── hgrep.cabal ├── main └── hgrep.hs ├── src └── Language │ └── Haskell │ ├── HGrep.hs │ └── HGrep │ ├── Internal │ ├── Data.hs │ ├── Lens.hs │ ├── Lens │ │ └── Rules.hs │ └── Print.hs │ ├── Prelude.hs │ ├── Print.hs │ └── Query.hs ├── stack-8.0.1.yaml ├── stack-8.2.1.yaml ├── stack-8.2.2.yaml ├── stack-8.4.3.yaml ├── stack-8.4.4.yaml ├── stack-8.6.3.yaml ├── stack-8.6.5.yaml └── stack.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-newstyle 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.chi 7 | *.chs.h 8 | .virtualenv 9 | .cabal-sandbox 10 | .stack-work/ 11 | cabal.sandbox.config 12 | /tmp 13 | /gen 14 | .stack-work 15 | 16 | *~ 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This Travis job script has been generated by a script via 2 | # 3 | # haskell-ci 'hgrep.cabal' 4 | # 5 | # For more information, see https://github.com/haskell-CI/haskell-ci 6 | # 7 | # version: 0.3.20190730 8 | # 9 | language: c 10 | dist: xenial 11 | git: 12 | # whether to recursively clone submodules 13 | submodules: false 14 | cache: 15 | directories: 16 | - $HOME/.cabal/packages 17 | - $HOME/.cabal/store 18 | before_cache: 19 | - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log 20 | # remove files that are regenerated by 'cabal update' 21 | - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.* 22 | - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json 23 | - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache 24 | - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar 25 | - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx 26 | - rm -rfv $CABALHOME/packages/head.hackage 27 | matrix: 28 | include: 29 | - compiler: ghc-8.6.5 30 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-2.4"]}} 31 | - compiler: ghc-8.6.4 32 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.4","cabal-install-2.4"]}} 33 | - compiler: ghc-8.6.3 34 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.3","cabal-install-2.4"]}} 35 | - compiler: ghc-8.6.2 36 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.2","cabal-install-2.4"]}} 37 | - compiler: ghc-8.6.1 38 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.1","cabal-install-2.4"]}} 39 | - compiler: ghc-8.4.4 40 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-2.4"]}} 41 | - compiler: ghc-8.4.3 42 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.3","cabal-install-2.4"]}} 43 | - compiler: ghc-8.4.2 44 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.2","cabal-install-2.4"]}} 45 | - compiler: ghc-8.4.1 46 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.1","cabal-install-2.4"]}} 47 | - compiler: ghc-8.2.2 48 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-2.4"]}} 49 | - compiler: ghc-8.0.2 50 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-2.4"]}} 51 | - compiler: ghc-8.0.1 52 | addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.1","cabal-install-2.4"]}} 53 | before_install: 54 | - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//') 55 | - HCPKG="$HC-pkg" 56 | - unset CC 57 | - CABAL=/opt/ghc/bin/cabal 58 | - CABALHOME=$HOME/.cabal 59 | - export PATH="$CABALHOME/bin:$PATH" 60 | - TOP=$(pwd) 61 | - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') )) 62 | - echo $HCNUMVER 63 | - CABAL="$CABAL -vnormal+nowrap+markoutput" 64 | - set -o pipefail 65 | - | 66 | echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }' >> .colorful.awk 67 | echo 'BEGIN { state = "output"; }' >> .colorful.awk 68 | echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }' >> .colorful.awk 69 | echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }' >> .colorful.awk 70 | echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk 71 | echo ' if (state == "cabal") {' >> .colorful.awk 72 | echo ' print blue($0)' >> .colorful.awk 73 | echo ' } else {' >> .colorful.awk 74 | echo ' print $0' >> .colorful.awk 75 | echo ' }' >> .colorful.awk 76 | echo '}' >> .colorful.awk 77 | - cat .colorful.awk 78 | - | 79 | color_cabal_output () { 80 | awk -f $TOP/.colorful.awk 81 | } 82 | - echo text | color_cabal_output 83 | install: 84 | - ${CABAL} --version 85 | - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]" 86 | - TEST=--enable-tests 87 | - BENCH=--enable-benchmarks 88 | - HEADHACKAGE=${HEADHACKAGE-false} 89 | - rm -f $CABALHOME/config 90 | - | 91 | echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config 92 | echo "remote-build-reporting: anonymous" >> $CABALHOME/config 93 | echo "write-ghc-environment-files: always" >> $CABALHOME/config 94 | echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config 95 | echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config 96 | echo "world-file: $CABALHOME/world" >> $CABALHOME/config 97 | echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config 98 | echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config 99 | echo "installdir: $CABALHOME/bin" >> $CABALHOME/config 100 | echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config 101 | echo "store-dir: $CABALHOME/store" >> $CABALHOME/config 102 | echo "install-dirs user" >> $CABALHOME/config 103 | echo " prefix: $CABALHOME" >> $CABALHOME/config 104 | echo "repository hackage.haskell.org" >> $CABALHOME/config 105 | echo " url: http://hackage.haskell.org/" >> $CABALHOME/config 106 | - cat $CABALHOME/config 107 | - rm -fv cabal.project cabal.project.local cabal.project.freeze 108 | - travis_retry ${CABAL} v2-update -v 109 | # Generate cabal.project 110 | - rm -rf cabal.project cabal.project.local cabal.project.freeze 111 | - touch cabal.project 112 | - | 113 | echo 'packages: "."' >> cabal.project 114 | - | 115 | - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(hgrep)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done" 116 | - cat cabal.project || true 117 | - cat cabal.project.local || true 118 | - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi 119 | - ${CABAL} v2-freeze -w ${HC} ${TEST} ${BENCH} | color_cabal_output 120 | - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'" 121 | - rm cabal.project.freeze 122 | - ${CABAL} v2-build -w ${HC} ${TEST} ${BENCH} --dep -j2 all | color_cabal_output 123 | - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output 124 | script: 125 | - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX) 126 | # Packaging... 127 | - ${CABAL} v2-sdist all | color_cabal_output 128 | # Unpacking... 129 | - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/ 130 | - cd ${DISTDIR} || false 131 | - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \; 132 | # Generate cabal.project 133 | - rm -rf cabal.project cabal.project.local cabal.project.freeze 134 | - touch cabal.project 135 | - | 136 | echo 'packages: "hgrep-*/*.cabal"' >> cabal.project 137 | - | 138 | - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(hgrep)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done" 139 | - cat cabal.project || true 140 | - cat cabal.project.local || true 141 | # Building... 142 | # this builds all libraries and executables (without tests/benchmarks) 143 | - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks all | color_cabal_output 144 | # Building with tests and benchmarks... 145 | # build & run tests, build benchmarks 146 | - ${CABAL} v2-build -w ${HC} ${TEST} ${BENCH} all | color_cabal_output 147 | # cabal check... 148 | - (cd hgrep-* && ${CABAL} -vnormal check) 149 | # haddock... 150 | - ${CABAL} v2-haddock -w ${HC} ${TEST} ${BENCH} all | color_cabal_output 151 | # Building without installed constraints for packages in global-db... 152 | - rm -f cabal.project.local 153 | - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks all | color_cabal_output 154 | 155 | # REGENDATA ["hgrep.cabal"] 156 | # EOF 157 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Version 0.2 (2017-10-08) 2 | 3 | - Print line numbers (optionally disabled with `--no-numbers`) 4 | - Combine adjacent declarations when printing 5 | - Add exception handling for ExactPrint exceptions (Bug fix) 6 | 7 | ## Version 0.1 (2017-10-04) 8 | 9 | - Support Perl-compatible regular expressions (`[-e|--regex]`) 10 | - Remove redundant dependencies 11 | 12 | ## Version 0.0 (2017-10-03) 13 | 14 | - Initial release 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | This document contains a few rules of thumb to ensure your changes 4 | will be accepted. It should be really easy to submit a good PR to this 5 | project. Please let me know if the process could be improved. 6 | 7 | Often I'll lapse on these to make the process a little easier on 8 | contributors. Follow the spirit, not the letter. 9 | 10 | ## Be respectful 11 | 12 | This is a fairly inconsequential piece of open source software, 13 | written and maintained by volunteers at no charge to you. 14 | 15 | With that in mind, please keep all correspondence pleasant, 16 | constructive, and respectful. Repeat offenders will be banned from the 17 | repository. 18 | 19 | ## Get in touch before chasing geese 20 | 21 | If you're planning to do a substantial chunk of work, it might be 22 | worth getting in touch first. Just open an issue and outline what 23 | you're planning to do. Don't surprise maintainers with huge patches. 24 | 25 | For smaller chunks and things already spelled out in existing GitHub 26 | issues, don't worry about this! Just go for it. 27 | 28 | ## Haskell style guidelines 29 | 30 | ### Language pragmas 31 | Language pragmas go at the top of the module using them: 32 | 33 | ```haskell 34 | {-# LANGUAGE NoImplicitPrelude #-} 35 | {-# LANGUAGE OverloadedStrings #-} 36 | module FooBar where 37 | ``` 38 | 39 | This ensures GHCi and other such tools always work. 40 | 41 | Don't add language extensions to the cabal file. 42 | 43 | ### Imports 44 | 45 | Qualified or explicit import lists for Hackage packages. 46 | 47 | ```haskell 48 | import Data.Map.Strict (Map) 49 | import qualified Data.Map.Strict as M 50 | ``` 51 | 52 | This makes it much easier to understand your code. 53 | 54 | Add padding when `qualified` keyword is not in use. 55 | 56 | ```haskell 57 | import Prelude 58 | ``` 59 | 60 | This reduces visual noise and keeps diffs clean. 61 | 62 | ### Exports 63 | 64 | Explicit export lists always. This helps identify dead code. 65 | 66 | ```haskell 67 | module Foo ( 68 | Foo 69 | , parseFoo 70 | ) where 71 | ``` 72 | 73 | ### Formatting 74 | 75 | Try to mimic the formatting of the rest of the codebase. 76 | 77 | As a general rule, use 2 spaces, plenty of newlines, be sparing with `($)`. 78 | Use Utrecht-style lists with leading commas. 79 | Don't go too overboard with whitespace alignment. Please don't use `hindent`. 80 | 81 | This style is intended to be fairly consistent, keeping diffs clean and free 82 | of formatting / indentation adjustments. 83 | 84 | ```haskell 85 | msg :: [Char] 86 | msg = 87 | fold [ 88 | "Hello, " 89 | , printf "%s!\n" "World!" 90 | ] 91 | 92 | foo :: Abc -> Def -> Ghi 93 | foo abc = 94 | foldM 95 | (\l r -> traverse (foo l) r) 96 | abc 97 | [1..20] 98 | 99 | bar :: 100 | Show xyz 101 | => AbcDefGhi MonadIO Really Long Type 102 | -> xyz 103 | -> IO () 104 | bar = do 105 | e <- parseFoo abc 106 | case e of 107 | Right abc -> 108 | pure 123 109 | Left def -> 110 | IO.hPutStrLn IO.stderr def 111 | 112 | baz :: IO () 113 | baz = 114 | for_ [1..10] $ do 115 | q <- abc (bar baz quux) 116 | undefined q 117 | ``` 118 | 119 | ## Use the project Prelude 120 | 121 | This project has its own Prelude module. Use this instead of the usual one. 122 | 123 | ```haskell 124 | {-# LANGUAGE NoImplicitPrelude #-} 125 | module FooBar where 126 | 127 | import Project.Name.Prelude 128 | ``` 129 | 130 | The project Prelude exists to identify a common vocabulary, to put useful 131 | and general concepts at your fingertips, and to hide / attach warnings to 132 | unsafe functions from `base`. 133 | 134 | 135 | Add new things to the Prelude only if 136 | - ... we've talked about it first, or 137 | - ... it exists in `base`, and 138 | - ... you actually need to use it, and 139 | - ... it has an unambiguous name, and 140 | - ... it is safe to use. 141 | - e.g. `fromJust` is not acceptable, `readMaybe` is 142 | 143 | ## Don't lint 144 | 145 | The only linter that matters is the one configured in CI. 146 | 147 | Please don't submit PRs based around fixing things `hlint` identified. 148 | 149 | For example, redundant `do` notation hurts nobody. 150 | 151 | ## Keep the build green 152 | 153 | - Do your best to ensure all CI checks pass 154 | - Feel free to ask for help if you're stuck or unsure about this! 155 | 156 | ## Write tests 157 | 158 | If I haven't written any tests, you can probably skip this step. Don't 159 | feel obliged to set up a test framework or anything like that! When there 160 | are tests in place, aspire to include a test or two with each PR. 161 | 162 | - Favour property tests and golden tests over unit tests. 163 | - Favour unit tests over nothing at all. 164 | - If it's hard to test, break your code down into smaller, more testable 165 | functions. 166 | - If it's still hard to test, ask for help. 167 | 168 | ## Rebase your changes 169 | 170 | When PRs fall behind master, they become difficult to merge. 171 | 172 | Automatic merging is possible in this scenario, but it means master 173 | will contain code that may not work or even compile. I'll then have to 174 | double back and re-integrate your changes. This is OK sometimes, but 175 | you are in a much better position to do that, since you wrote the code! 176 | 177 | - Please rebase onto `origin/master` when your branch falls behind. 178 | - `git fetch origin; git rebase origin/master` 179 | - Ensure CI passes on your rebased branch. 180 | - If the changes have been approved, they will then be merged. 181 | - If you're struggling with this process, let me know! 182 | 183 | ## Adhere to the PVP 184 | 185 | This project should always be buildable with both Stack and Cabal. 186 | Version bounds should be accurate and PVP-compatible. 187 | 188 | Please call me out and open PRs when I've failed at this. 189 | 190 | ## Don't change the version number 191 | 192 | Let me manage the releases. Don't bump the version number yourself. 193 | 194 | ## Feel free to request a release 195 | 196 | Sometimes it's awkward to demand a new Hackage release. 197 | 198 | Please feel free to create GitHub issues about this! 199 | 200 | ## Update the CHANGELOG 201 | 202 | Feel free to add your changes to the CHANGELOG. 203 | 204 | I may tweak the wording before release. 205 | 206 | ## Add yourself to CONTRIBUTORS 207 | 208 | I'll try to keep this up to date, but feel free to add yourself! 209 | 210 | This is not an exclusive list: anyone contributing to any issue or PR 211 | is considered a contributor. 212 | 213 | If you'd like to be removed from this list for some reason, please get 214 | in touch privately. 215 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | This small tool with very limited scope is still a significant amount 2 | of work. The following people have contributed to the project in some 3 | way and are worthy of your praise and admiration. In chronological 4 | order: 5 | 6 | - Chris Allen ([@bitemyapp](https://github.com/bitemyapp)) 7 | - Vaibhav Sagar ([@vaibhavsagar](https://github.com/vaibhavsagar)) 8 | - Veronika Romashkina ([@vrom911](https://github.com/vrom911)) 9 | - Ryan James Spencer ([@justanotherdot](https://github.com/justanotherdot)) 10 | - Eoin Houlihan ([@houli](https://github.com/houli)) 11 | - Bobby Rauchenberg ([@bobbyrauchenberg](https://github.com/bobbyrauchenberg)) 12 | - Dmitry Kovanikov ([@ChShersh](https://github.com/ChShersh)) 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017, Tim Humphries 2 | All Rights Reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of 16 | its contributors may be used to endorse or promote products derived 17 | from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hgrep 2 | [![Travis Build Status](https://travis-ci.org/thumphries/hgrep.svg?branch=master)](https://travis-ci.org/thumphries/hgrep) 3 | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/2xmkp3jbt1q078xv/branch/master?svg=true)](https://ci.appveyor.com/project/thumphries/hgrep) 4 | [![Hackage page (downloads and API reference)](http://img.shields.io/hackage/v/hgrep.svg)](http://hackage.haskell.org/package/hgrep) 5 | [![Hackage-Deps](https://img.shields.io/hackage-deps/v/hgrep.svg)](http://packdeps.haskellers.com/reverse/hgrep) 6 | 7 | Search Haskell source code from the command line. 8 | 9 | Powered by ghc-exactprint. 10 | 11 | ## Usage 12 | 13 | ```haskell 14 | $> hgrep 15 | Usage: hgrep [-e|--regex] EXPRESSION [FILE] 16 | ``` 17 | 18 | `hgrep` requires an expression and a set of files to search across. 19 | 20 | An expression can be one of 21 | - The name of a type, e.g. `FooBar` 22 | - The name of an expression, e.g. `foo` 23 | - A regular expression (via the `-e` flag), e.g. `-e Foo$` 24 | 25 | Each file will be parsed and searched. Results will be printed to the 26 | console, with syntax highlighting where possible. 27 | 28 | ### Requirements 29 | 30 | You can use `cabal` or `stack` to install, but you may need `libpcre`. In case you don't have it: 31 | 32 | #### Ubuntu 33 | 34 | ```bash 35 | sudo apt update 36 | sudo apt install libpcre3-dev 37 | ``` 38 | 39 | #### Fedora 40 | 41 | ``` 42 | sudo dnf update 43 | sudo dnf install pcre-devel 44 | ``` 45 | 46 | #### NixOS / Nix Package Manager 47 | 48 | ``` 49 | nix-env -i pcre 50 | ``` 51 | 52 | ### Install 53 | 54 | #### Stack 55 | 56 | ``` 57 | git clone https://github.com/thumphries/hgrep.git 58 | cd !$ 59 | stack install 60 | ``` 61 | 62 | 63 | #### Cabal 64 | 65 | ``` 66 | git clone https://github.com/thumphries/hgrep.git 67 | cd !$ 68 | cabal new-build 69 | ``` 70 | 71 | #### Mafia 72 | 73 | ``` 74 | git clone https://github.com/thumphries/hgrep.git 75 | cd !$ 76 | mafia build 77 | ``` 78 | 79 | #### Nix 80 | ``` 81 | git clone https://github.com/thumphries/hgrep.git 82 | cd !$ 83 | nix-build -E 'with import {}; haskellPackages.callCabal2nix "hgrep" ./. {}' 84 | ``` 85 | 86 | ### Searching for top-level expressions 87 | 88 | ```haskell 89 | $> hgrep main main/hgrep.hs 90 | main/hgrep.hs:16:1-13 91 | 92 | -- | Run the program. 93 | main :: IO () 94 | main/hgrep.hs:(17,1)-(18:27) 95 | 96 | main = do 97 | putStrLn "Hello, world!" 98 | ``` 99 | 100 | ### Searching for type declarations 101 | 102 | ```haskell 103 | $> hgrep PrintOpts src/**/*.hs 104 | src/Language/Haskell/HGrep/Internal/Data.hs:(40,1)-(42,28) 105 | 106 | data PrintOpts = PrintOpts { 107 | poColourOpts :: ColourOpts 108 | } deriving (Eq, Ord, Show) 109 | ``` 110 | 111 | ### Searching with a regular expression 112 | 113 | ```haskell 114 | $> hgrep -e 'Opts$' src/**/*.hs 115 | src/Language/Haskell/HGrep/Internal/Data.hs:(57,1)-(59,28) 116 | 117 | data PrintOpts = PrintOpts { 118 | poColourOpts :: ColourOpts 119 | } deriving (Eq, Ord, Show) 120 | src/Language/Haskell/HGrep/Internal/Data.hs:(61,1)-(64,26) 121 | 122 | defaultPrintOpts :: PrintOpts 123 | src/Language/Haskell/HGrep/Internal/Data.hs:(67,1)-(70,5) 124 | 125 | defaultPrintOpts = 126 | PrintOpts { 127 | poColourOpts = DefaultColours 128 | } 129 | ``` 130 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | branches: 3 | except: 4 | - gh-pages 5 | build: false 6 | cache: 7 | - "c:\\hgrep\\dist" 8 | clone_folder: "c:\\hgrep" 9 | environment: 10 | global: 11 | STACK_ROOT: "c:\\sr" 12 | matrix: 13 | - 14 | GHC: ghc-8.0.1 15 | GHC_URL: "https://downloads.haskell.org/~ghc/8.0.1/ghc-8.0.1-x86_64-unknown-mingw32.tar.xz" 16 | STACK_YAML: stack-8.0.1.yaml 17 | - 18 | GHC: ghc-8.0.2 19 | GHC_URL: "https://downloads.haskell.org/~ghc/8.0.2/ghc-8.0.2-x86_64-unknown-mingw32.tar.xz" 20 | STACK_YAML: stack.yaml 21 | - 22 | GHC: ghc-8.2.1 23 | GHC_URL: "https://downloads.haskell.org/~ghc/8.2.1/ghc-8.2.1-x86_64-unknown-mingw32.tar.xz" 24 | STACK_YAML: stack-8.2.1.yaml 25 | - 26 | GHC: ghc-8.2.2 27 | GHC_URL: "https://downloads.haskell.org/~ghc/8.2.2/ghc-8.2.2-x86_64-unknown-mingw32.tar.xz" 28 | STACK_YAML: stack-8.2.2.yaml 29 | image: "Visual Studio 2015" 30 | install: 31 | - "mkdir c:\\ghc" 32 | - "curl -sS -oghc.tar.xz -L --insecure %GHC_URL%" 33 | - "7z x ghc.tar.xz -oc:\\ghc -r" 34 | - "7z x c:\\ghc\\ghc.tar -oc:\\ghc -r" 35 | - "set PATH=C:\\ghc\\%GHC%\\bin;%PATH%;C:\\ghc\\%GHC%\\mingw\\bin;C:\\ghc\\%GHC%\\mingw\\x86_64-w64-mingw32\\bin" 36 | - "set PATH=C:\\msys64\\mingw64\\bin;C:\\Program Files\\Git\\mingw64\\bin;C:\\msys64\\usr\\bin;%PATH%" 37 | - "curl -sS -ostack.zip -L --insecure http://www.stackage.org/stack/windows-x86_64" 38 | - "7z x stack.zip stack.exe" 39 | - "curl -sS -ocabal.zip -L --insecure https://www.haskell.org/cabal/release/cabal-install-1.24.0.2/cabal-install-1.24.0.2-x86_64-unknown-mingw32.zip" 40 | - "7z x cabal.zip cabal.exe" 41 | - "pacman -Syu --noconfirm" 42 | - "pacman -S mingw-w64-x86_64-pcre --noconfirm" 43 | - "pacman -S mingw-w64-x86_64-pkg-config --noconfirm" 44 | - "stack config set system-ghc --global true" 45 | - "pip install awscli" 46 | matrix: 47 | fast_finish: true 48 | test_script: 49 | - "cabal update" 50 | - "cabal install --only-dependencies" 51 | - "cabal configure" 52 | - "cabal build" 53 | - "cabal test" 54 | - "dist\\build\\hgrep\\hgrep.exe hgrep main\\hgrep.hs" 55 | - "echo \"\" | stack --stack-yaml %STACK_YAML% test" 56 | - "stack exec -- hgrep hgrep main\\hgrep.hs" 57 | deploy_script: 58 | - "bash bin/travis_publish" 59 | -------------------------------------------------------------------------------- /bin/travis_before_release: -------------------------------------------------------------------------------- 1 | #!/bin/sh -exu 2 | 3 | PROJECT=hgrep 4 | BINARIES=hgrep 5 | PLATFORMS="x86_64-unknown-linux x86_64-apple-darwin" 6 | GHC_VERSION=8.0.2 7 | 8 | export AWS_ACCESS_KEY_ID=${PUBLISH_KEY} 9 | export AWS_SECRET_ACCESS_KEY=${PUBLISH_SECRET} 10 | export AWS_DEFAULT_REGION=${PUBLISH_REGION} 11 | 12 | BUCKET=${PUBLISH_BUCKET} 13 | COMMIT=$(git rev-parse --verify HEAD) 14 | 15 | for P in $PLATFORMS; do 16 | for B in $BINARIES; do 17 | aws s3 cp "${BUCKET}/${PROJECT}/${COMMIT}/${P}/${GHC_VERSION}/${B}.tar.gz" "${B}-${P}.tar.gz" 18 | done 19 | done 20 | -------------------------------------------------------------------------------- /bin/travis_publish: -------------------------------------------------------------------------------- 1 | #!/bin/sh -exu 2 | 3 | PROJECT=hgrep 4 | BINARIES=hgrep 5 | 6 | if [ "${PUBLISH:-}" = "true" ]; then 7 | 8 | export AWS_ACCESS_KEY_ID=${PUBLISH_KEY} 9 | export AWS_SECRET_ACCESS_KEY=${PUBLISH_SECRET} 10 | export AWS_DEFAULT_REGION=${PUBLISH_REGION} 11 | 12 | BUCKET=${PUBLISH_BUCKET} 13 | COMMIT=$(git rev-parse --verify HEAD) 14 | 15 | GHC_VERSION= 16 | GHC_PLATFORM= 17 | DIST= 18 | case "$BUILD" in 19 | stack) 20 | GHC_VERSION=$(stack ghc -- --numeric-version) 21 | GHC_PLATFORM=$(stack ghc -- --print-target-platform) 22 | DIST=$(stack path --dist-dir) 23 | ;; 24 | *) 25 | GHC_VERSION=$(ghc --numeric-version) 26 | GHC_PLATFORM=$(ghc --print-target-platform) 27 | DIST=dist 28 | ;; 29 | esac 30 | 31 | for B in $BINARIES; do 32 | cp "${DIST}/build/${B}/${B}" "${B}" 33 | tar cvzf "${B}.tar.gz" "${B}" 34 | aws s3 cp "${B}.tar.gz" "${BUCKET}/${PROJECT}/${COMMIT}/${GHC_PLATFORM}/${GHC_VERSION}/${B}.tar.gz" 35 | done 36 | 37 | fi 38 | -------------------------------------------------------------------------------- /build-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | tempfile=`mktemp` 4 | 5 | for file in stack-*.yaml 6 | do 7 | if 8 | stack --stack-yaml "$file" build 9 | then 10 | echo -e "Build with $file\t: success." >> "$tempfile" 11 | else 12 | echo -e "Build with $file\t: failure." >> "$tempfile" 13 | fi 14 | done 15 | 16 | cat "$tempfile" 17 | -------------------------------------------------------------------------------- /hgrep.cabal: -------------------------------------------------------------------------------- 1 | name: hgrep 2 | version: 0.2 3 | homepage: https://github.com/thumphries/hgrep 4 | author: Tim Humphries 5 | maintainer: tim@utf8.me 6 | bug-reports: https://github.com/thumphries/hgrep 7 | license: BSD3 8 | license-file: LICENSE 9 | category: Language 10 | build-type: Simple 11 | cabal-version: >=1.10 12 | tested-with: GHC == 8.0.1, GHC == 8.0.2, GHC == 8.2.2 13 | , GHC == 8.4.1, GHC == 8.4.2, GHC == 8.4.3, GHC == 8.4.4, GHC == 8.6.1 14 | , GHC == 8.6.2, GHC == 8.6.3, GHC == 8.6.4, GHC == 8.6.5 15 | extra-source-files: 16 | README.md 17 | CHANGELOG.md 18 | 19 | synopsis: 20 | Search Haskell source code from the command line 21 | description: 22 | Search Haskell source code from the command line. 23 | . 24 | Powered by . 25 | 26 | source-repository head 27 | type: git 28 | location: git://github.com/thumphries/hgrep.git 29 | 30 | executable hgrep 31 | default-language: Haskell2010 32 | hs-source-dirs: main 33 | ghc-options: -Wall -threaded -rtsopts 34 | main-is: hgrep.hs 35 | build-depends: 36 | base >= 4.9 && < 4.13 37 | , hgrep 38 | , ansi-terminal >= 0.6.3 && < 0.10 39 | , directory >= 1.2 && < 1.4 40 | , filepath >= 1.4 && < 1.5 41 | , optparse-applicative >= 0.13 && < 0.15 42 | 43 | 44 | library 45 | default-language: Haskell2010 46 | hs-source-dirs: src 47 | ghc-options: -Wall 48 | build-depends: 49 | base >= 4.9 && < 4.13 50 | , ansi-terminal >= 0.6.3 && < 0.10 51 | , bytestring >= 0.10.4 && < 0.12 52 | , containers >= 0.5.7 && < 0.7 53 | , ghc >= 7.10.2 && < 8.7 54 | , ghc-exactprint >= 0.5.4.0 && < 0.7 55 | , hscolour >= 1.24 && < 1.25 56 | , lens >= 4.15 && < 4.18 57 | , pcre-heavy >= 1.0 && < 1.1 58 | , template-haskell >= 2.11 && < 2.15 59 | 60 | exposed-modules: 61 | Language.Haskell.HGrep 62 | Language.Haskell.HGrep.Query 63 | Language.Haskell.HGrep.Prelude 64 | Language.Haskell.HGrep.Print 65 | 66 | Language.Haskell.HGrep.Internal.Data 67 | Language.Haskell.HGrep.Internal.Lens 68 | Language.Haskell.HGrep.Internal.Lens.Rules 69 | Language.Haskell.HGrep.Internal.Print 70 | -------------------------------------------------------------------------------- /main/hgrep.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Main where 4 | 5 | 6 | import qualified Language.Haskell.HGrep as HGrep 7 | import qualified Language.Haskell.HGrep.Internal.Data as HGrep 8 | import Language.Haskell.HGrep.Prelude 9 | 10 | import qualified Options.Applicative as O 11 | 12 | import qualified System.Console.ANSI as ANSI 13 | import System.Exit (ExitCode (..), exitWith) 14 | import qualified System.IO as IO 15 | import qualified System.Directory as D 16 | import qualified System.FilePath as FP 17 | 18 | 19 | main :: IO () 20 | main = do 21 | IO.hSetBuffering IO.stdout IO.LineBuffering 22 | IO.hSetBuffering IO.stderr IO.LineBuffering 23 | opts <- parseOpts 24 | colour <- detectColour 25 | case parseQuery (cmdQuery opts) (cmdRegex opts) of 26 | Left er -> do 27 | IO.hPutStrLn IO.stderr er 28 | exitWith (ExitFailure 2) 29 | Right q -> do 30 | -- if no directory provided -> search in current directory 31 | files <- 32 | case cmdFiles opts of 33 | [] -> pure <$> D.getCurrentDirectory 34 | x -> pure x 35 | allFiles <- foldMap getAllHsFiles files 36 | found <- 37 | fmap sum $ 38 | for allFiles $ \fp -> do 39 | hgrep (HGrep.PrintOpts colour (cmdLineNums opts)) q fp 40 | exitWith (exitCode found) 41 | 42 | getAllHsFiles :: FilePath -> IO [FilePath] 43 | getAllHsFiles fp = do 44 | isDir <- D.doesDirectoryExist fp 45 | if isDir 46 | then do 47 | fs <- fmap (fp FP.) <$> D.listDirectory fp 48 | foldMap getAllHsFiles fs 49 | else pure $ 50 | case FP.takeExtension fp of 51 | ".hs" -> [fp] 52 | ".lhs" -> [fp] 53 | _ -> [] 54 | 55 | exitCode :: Integer -> ExitCode 56 | exitCode found 57 | | found == 0 = ExitFailure 1 58 | | otherwise = ExitSuccess 59 | 60 | detectColour :: IO HGrep.ColourOpts 61 | detectColour = do 62 | out <- ANSI.hSupportsANSI IO.stdout 63 | err <- ANSI.hSupportsANSI IO.stderr 64 | pure $ case out && err of 65 | True -> 66 | HGrep.DefaultColours 67 | False -> 68 | HGrep.NoColours 69 | 70 | hgrep :: HGrep.PrintOpts -> HGrep.Query -> FilePath -> IO Integer 71 | hgrep popts q fp = do 72 | esrc <- HGrep.parseModule fp 73 | case esrc of 74 | Left err -> do 75 | IO.hPutStrLn IO.stderr (HGrep.printParseError err) 76 | pure 0 77 | 78 | Right src -> do 79 | let results = HGrep.queryModule q src 80 | HGrep.printResults popts results 81 | pure $ fromIntegral (length results) 82 | 83 | 84 | parseQuery :: [Char] -> Bool -> Either [Char] HGrep.Query 85 | parseQuery str regex = 86 | case regex of 87 | True -> 88 | HGrep.MatchRegex <$> HGrep.compileRegex str 89 | False -> 90 | pure $ HGrep.MatchSimple str 91 | 92 | -- ----------------------------------------------------------------------------- 93 | 94 | data CmdOpts = CmdOpts { 95 | cmdQuery :: [Char] 96 | , cmdRegex :: Bool 97 | , cmdFiles :: [FilePath] 98 | , cmdLineNums :: HGrep.LineNumOpts 99 | } deriving (Eq, Ord, Show) 100 | 101 | parseOpts :: IO CmdOpts 102 | parseOpts = 103 | O.execParser $ 104 | O.info 105 | (parser <**> O.helper) 106 | (O.header "hgrep - search Haskell source code from the command line") 107 | 108 | parser :: O.Parser CmdOpts 109 | parser = 110 | CmdOpts 111 | <$> O.argument O.str (O.metavar "QUERY") 112 | <*> O.switch 113 | (O.short 'e' <> O.long "regex" <> O.help "Match a regular expression") 114 | <*> many (O.argument O.str (O.metavar "FILE")) 115 | <*> O.flag HGrep.PrintLineNums HGrep.NoLineNums 116 | (O.short 'n' <> O.long "no-numbers" <> O.help "Turn line numbering off") 117 | -------------------------------------------------------------------------------- /src/Language/Haskell/HGrep.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Language.Haskell.HGrep ( 4 | -- * Parsing 5 | ParsedSource 6 | , parseModule 7 | , ParseError 8 | , HP.printParseError 9 | -- * Searching 10 | -- ** Queries 11 | , Query (..) 12 | , Regex 13 | , compileRegex 14 | -- ** Running queries 15 | , SearchResult 16 | , queryModule 17 | -- * Printing results 18 | , PrintOpts (..) 19 | , defaultPrintOpts 20 | , ColourOpts (..) 21 | , printResults 22 | , HP.printSearchResult 23 | , HP.printSearchResultLocation 24 | ) where 25 | 26 | 27 | import qualified Control.Exception as E 28 | 29 | import Language.Haskell.HGrep.Internal.Data 30 | import Language.Haskell.HGrep.Internal.Print 31 | import Language.Haskell.HGrep.Prelude 32 | import qualified Language.Haskell.HGrep.Print as HP 33 | import qualified Language.Haskell.HGrep.Query as HQ 34 | 35 | import qualified Language.Haskell.GHC.ExactPrint as EP 36 | 37 | import System.Exit (ExitCode (..)) 38 | import qualified System.IO as IO 39 | 40 | import qualified SrcLoc 41 | 42 | 43 | parseModule :: FilePath -> IO (Either ParseError ParsedSource) 44 | parseModule hs = do 45 | res <- E.tryJust handler (bimap ExactPrintParseError ParsedSource <$> EP.parseModule hs) 46 | return $ case res of 47 | Left e -> Left e 48 | Right v -> v 49 | where handler :: ExitCode -> Maybe ParseError 50 | handler (ExitFailure _) = Just ExactPrintException 51 | handler _ = Nothing 52 | 53 | queryModule :: Query -> ParsedSource -> [SearchResult] 54 | queryModule q src = 55 | (<>) 56 | (HQ.findTypeDecl q src) 57 | (HQ.findValueDecl q src) 58 | 59 | printResults :: PrintOpts -> [SearchResult] -> IO () 60 | printResults opts results = do 61 | let printedResult = fmap (printWithLocation opts) results 62 | for_ (foldAdjacent printedResult) $ \(textResult, span) -> do 63 | IO.hPutStr IO.stdout (printSrcSpan opts span) 64 | IO.hPutStrLn IO.stdout textResult 65 | 66 | type TextWithLocation = ([Char], SrcLoc.SrcSpan) 67 | 68 | printWithLocation :: PrintOpts -> SearchResult -> TextWithLocation 69 | printWithLocation opts result@(SearchResult _ loc) = 70 | (HP.printSearchResult opts result, SrcLoc.getLoc loc) 71 | 72 | foldAdjacent :: [TextWithLocation] -> [TextWithLocation] 73 | foldAdjacent [] = [] 74 | foldAdjacent [result] = [result] 75 | foldAdjacent (firstResult:secondResult:rest) = 76 | case (firstResult, secondResult) of 77 | ((firstText, firstSpan), (secondText, secondSpan)) -> 78 | case mergedLocs firstSpan secondSpan of 79 | Nothing -> 80 | firstResult : foldAdjacent (secondResult:rest) 81 | Just span -> 82 | foldAdjacent $ (firstText <> secondText, span) : rest 83 | 84 | mergedLocs :: SrcLoc.SrcSpan -> SrcLoc.SrcSpan -> Maybe SrcLoc.SrcSpan 85 | mergedLocs span1 span2 86 | | areAdjacentSpans span1 span2 = Just $ SrcLoc.combineSrcSpans span1 span2 87 | | otherwise = Nothing 88 | 89 | areAdjacentSpans :: SrcLoc.SrcSpan -> SrcLoc.SrcSpan -> Bool 90 | areAdjacentSpans span1 span2 = 91 | areAdjacentLocs (SrcLoc.srcSpanEnd span1) (SrcLoc.srcSpanStart span2) 92 | 93 | areAdjacentLocs :: SrcLoc.SrcLoc -> SrcLoc.SrcLoc -> Bool 94 | areAdjacentLocs (SrcLoc.RealSrcLoc loc1) (SrcLoc.RealSrcLoc loc2) = 95 | SrcLoc.srcLocLine loc1 + 1 == SrcLoc.srcLocLine loc2 96 | areAdjacentLocs _ _ = False 97 | -------------------------------------------------------------------------------- /src/Language/Haskell/HGrep/Internal/Data.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ExistentialQuantification #-} 2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 3 | {-# LANGUAGE NoImplicitPrelude #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | {-# LANGUAGE RankNTypes #-} 6 | {-# LANGUAGE CPP #-} 7 | module Language.Haskell.HGrep.Internal.Data ( 8 | ParsedSource (..) 9 | , ParseError (..) 10 | , Query (..) 11 | , Regex (..) 12 | , compileRegex 13 | , SearchResult (..) 14 | , PrintOpts (..) 15 | , defaultPrintOpts 16 | , ColourOpts (..) 17 | , LineNumOpts (..) 18 | ) where 19 | 20 | 21 | import qualified Data.ByteString.Char8 as B8 22 | 23 | import Language.Haskell.HGrep.Prelude 24 | 25 | import qualified Language.Haskell.GHC.ExactPrint.Annotater as EA 26 | import qualified Language.Haskell.GHC.ExactPrint.Types as ET 27 | 28 | import qualified Text.Regex.PCRE.Heavy as PCRE 29 | 30 | import qualified GHC 31 | import qualified SrcLoc 32 | 33 | 34 | newtype ParsedSource = ParsedSource { 35 | #if !MIN_VERSION_base(4,11,0) 36 | unParsedSource :: (ET.Anns, GHC.Located (GHC.HsModule GHC.RdrName)) 37 | #else 38 | unParsedSource :: (ET.Anns, GHC.Located (GHC.HsModule GHC.GhcPs)) 39 | #endif 40 | } 41 | 42 | data ParseError = 43 | ExactPrintParseError (SrcLoc.SrcSpan, [Char]) 44 | | ExactPrintException 45 | 46 | data Query = 47 | MatchSimple [Char] 48 | | MatchRegex Regex 49 | deriving (Eq, Ord, Show) 50 | 51 | newtype Regex = Regex { 52 | unRegex :: PCRE.Regex 53 | } deriving (Eq, Ord, Show) 54 | 55 | compileRegex :: [Char] -> Either [Char] Regex 56 | compileRegex str = 57 | fmap Regex (PCRE.compileM (B8.pack str) []) 58 | 59 | data SearchResult = 60 | forall ast. EA.Annotate ast => 61 | SearchResult ET.Anns (SrcLoc.Located ast) 62 | 63 | data PrintOpts = PrintOpts { 64 | poColourOpts :: ColourOpts 65 | , poLineNumOpts :: LineNumOpts 66 | } deriving (Eq, Ord, Show) 67 | 68 | data ColourOpts = 69 | DefaultColours 70 | | NoColours 71 | deriving (Eq, Ord, Show) 72 | 73 | data LineNumOpts = 74 | PrintLineNums 75 | | NoLineNums 76 | deriving (Eq, Ord, Show) 77 | 78 | defaultPrintOpts :: PrintOpts 79 | defaultPrintOpts = 80 | PrintOpts { 81 | poColourOpts = DefaultColours 82 | , poLineNumOpts = PrintLineNums 83 | } 84 | -------------------------------------------------------------------------------- /src/Language/Haskell/HGrep/Internal/Lens.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE FunctionalDependencies #-} 3 | {-# LANGUAGE GADTs #-} 4 | {-# LANGUAGE MultiParamTypeClasses #-} 5 | {-# LANGUAGE NoImplicitPrelude #-} 6 | {-# LANGUAGE OverloadedStrings #-} 7 | {-# LANGUAGE TemplateHaskell #-} 8 | {-# LANGUAGE TypeSynonymInstances #-} 9 | {-# LANGUAGE UndecidableInstances #-} 10 | {-# LANGUAGE CPP #-} 11 | module Language.Haskell.HGrep.Internal.Lens where 12 | 13 | 14 | import Control.Lens 15 | 16 | import Language.Haskell.HGrep.Internal.Lens.Rules (makeOptics) 17 | import Language.Haskell.HGrep.Prelude 18 | 19 | import HsBinds 20 | import HsDecls 21 | import HsExpr 22 | import HsLit 23 | import HsSyn 24 | import Name 25 | import RdrName 26 | import SrcLoc 27 | 28 | 29 | makeOptics ''GenLocated 30 | 31 | _loc :: Lens' (Located e) SrcSpan 32 | _loc = _L . _1 33 | 34 | _unloc :: Lens' (Located e) e 35 | _unloc = _L . _2 36 | 37 | makeOptics ''OccName 38 | makeOptics ''RdrName 39 | makeOptics ''Name 40 | 41 | makeOptics ''HsModule 42 | 43 | makeOptics ''HsDecl 44 | makeOptics ''TyClDecl 45 | makeOptics ''InstDecl 46 | makeOptics ''DerivDecl 47 | makeOptics ''Sig 48 | makeOptics ''DefaultDecl 49 | makeOptics ''ForeignDecl 50 | makeOptics ''WarnDecls 51 | makeOptics ''AnnDecl 52 | makeOptics ''RuleDecls 53 | #if !MIN_VERSION_base(4,11,0) 54 | makeOptics ''VectDecl 55 | #endif 56 | makeOptics ''SpliceDecl 57 | makeOptics ''DocDecl 58 | makeOptics ''RoleAnnotDecl 59 | 60 | makeOptics ''HsBindLR 61 | 62 | makeOptics ''HsExpr 63 | makeOptics ''SyntaxExpr 64 | makeOptics ''MatchGroup 65 | makeOptics ''StmtLR 66 | 67 | makeOptics ''HsLit 68 | 69 | makeOptics ''HsType 70 | -------------------------------------------------------------------------------- /src/Language/Haskell/HGrep/Internal/Lens/Rules.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Language.Haskell.HGrep.Internal.Lens.Rules where 4 | 5 | 6 | import Control.Lens 7 | 8 | import Language.Haskell.HGrep.Prelude 9 | 10 | import Language.Haskell.TH (Name, DecsQ, mkName, nameBase) 11 | 12 | 13 | rules :: LensRules 14 | rules = 15 | lensRules 16 | & set' lensField namer 17 | & set' simpleLenses False 18 | & set' createClass False 19 | & set' generateSignatures True 20 | 21 | namer :: FieldNamer 22 | namer _tn _fields field = 23 | [TopName (mkName ("_" <> nameBase field))] 24 | 25 | makeOptics :: Name -> DecsQ 26 | makeOptics tn = 27 | (<>) 28 | <$> makeLensesWith rules tn 29 | <*> makePrisms tn 30 | -------------------------------------------------------------------------------- /src/Language/Haskell/HGrep/Internal/Print.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | 3 | -- | Internal private functions for 'Language.Haskell.HGrep.Print'. 4 | 5 | module Language.Haskell.HGrep.Internal.Print ( 6 | printSrcSpan 7 | , unsafePpr 8 | ) where 9 | 10 | import qualified Data.List as L 11 | 12 | import Language.Haskell.HGrep.Internal.Data 13 | import Language.Haskell.HGrep.Prelude 14 | 15 | import qualified Outputable 16 | import qualified System.Console.ANSI as ANSI 17 | import qualified SrcLoc 18 | 19 | printSrcSpan :: PrintOpts -> SrcLoc.SrcSpan -> [Char] 20 | printSrcSpan (PrintOpts co _) span = 21 | let loc = chomp (unsafePpr span) in 22 | case co of 23 | DefaultColours -> 24 | ansiLocationFormat <> loc <> ansiReset 25 | NoColours -> 26 | loc 27 | 28 | ansiLocationFormat :: [Char] 29 | ansiLocationFormat = 30 | ANSI.setSGRCode [ 31 | ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Green 32 | , ANSI.SetUnderlining ANSI.SingleUnderline 33 | ] 34 | 35 | ansiReset :: [Char] 36 | ansiReset = 37 | ANSI.setSGRCode [] 38 | 39 | unsafePpr :: Outputable.Outputable o => o -> [Char] 40 | unsafePpr = 41 | Outputable.showSDocUnsafe . Outputable.ppr 42 | 43 | chomp :: [Char] -> [Char] 44 | chomp = 45 | L.unlines . L.lines 46 | -------------------------------------------------------------------------------- /src/Language/Haskell/HGrep/Prelude.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE NoImplicitPrelude #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# LANGUAGE PatternSynonyms #-} 5 | module Language.Haskell.HGrep.Prelude ( 6 | -- * Primitive types 7 | -- ** Bool 8 | Bool (..) 9 | , bool 10 | , (&&) 11 | , (||) 12 | , not 13 | , otherwise 14 | -- ** Char 15 | , Char 16 | -- ** Int 17 | , Integer 18 | , Int 19 | , Int8 20 | , Int16 21 | , Int32 22 | , Int64 23 | -- ** Word 24 | , Word64 25 | -- ** Real 26 | , fromIntegral 27 | , fromRational 28 | 29 | -- * Algebraic structures 30 | -- ** Monoid 31 | , Monoid (..) 32 | , (<>) 33 | -- ** Functor 34 | , Functor (..) 35 | , (<$>) 36 | , ($>) 37 | , void 38 | , with 39 | -- ** Bifunctor 40 | , Bifunctor (..) 41 | -- ** Applicative 42 | , Applicative (..) 43 | , (<**>) 44 | -- ** Alternative 45 | , Alternative (..) 46 | , asum 47 | -- ** Monad 48 | , Monad (..) 49 | , join 50 | -- ** MonadPlus 51 | , MonadPlus (..) 52 | , guard 53 | , msum 54 | -- ** MonadIO 55 | , MonadIO (..) 56 | 57 | -- * Data structures 58 | -- ** Either 59 | , Either (..) 60 | , either 61 | , note 62 | -- ** Maybe 63 | , Maybe (..) 64 | , fromMaybe 65 | , maybe 66 | , hush 67 | -- ** Tuple 68 | , fst 69 | , snd 70 | , curry 71 | , uncurry 72 | 73 | -- * Typeclasses 74 | -- ** Enum 75 | , Enum (..) 76 | -- ** Num 77 | , Num (..) 78 | -- ** Eq 79 | , Eq (..) 80 | -- ** Read 81 | , Read (..) 82 | , readEither 83 | , readMaybe 84 | -- ** Show 85 | , Show (..) 86 | -- *** ShowS 87 | , ShowS 88 | , showString 89 | -- ** Foldable 90 | , Foldable (..) 91 | , for_ 92 | , all 93 | -- ** Ord 94 | , Ord (..) 95 | , Ordering (..) 96 | , comparing 97 | -- ** Traversable 98 | , Traversable (..) 99 | , for 100 | , traverse_ 101 | 102 | -- * Combinators 103 | , id 104 | , (.) 105 | , ($) 106 | , ($!) 107 | , (&) 108 | , const 109 | , flip 110 | , fix 111 | , on 112 | , seq 113 | 114 | -- * System 115 | -- ** IO 116 | , IO 117 | , FilePath 118 | 119 | -- * Partial functions 120 | , undefined 121 | , error 122 | 123 | -- * Debugging facilities 124 | , trace 125 | , traceM 126 | , traceIO 127 | ) where 128 | 129 | 130 | import Control.Monad as Monad ( 131 | Monad (..) 132 | , MonadPlus (..) 133 | , guard 134 | , join 135 | , msum 136 | ) 137 | import Control.Monad.IO.Class ( 138 | MonadIO (..) 139 | ) 140 | import Control.Applicative as Applicative ( 141 | Applicative (..) 142 | , (<**>) 143 | , Alternative (..) 144 | , empty 145 | ) 146 | 147 | import Data.Bifunctor as Bifunctor ( 148 | Bifunctor (..) 149 | ) 150 | import Data.Bool as Bool ( 151 | Bool (..) 152 | , bool 153 | , (&&) 154 | , (||) 155 | , not 156 | , otherwise 157 | ) 158 | import Data.Char as Char ( 159 | Char 160 | ) 161 | import Data.Either as Either ( 162 | Either (..) 163 | , either 164 | ) 165 | import Data.Foldable as Foldable ( 166 | Foldable (..) 167 | , asum 168 | , traverse_ 169 | , for_ 170 | , all 171 | ) 172 | import Data.Function as Function ( 173 | id 174 | , (.) 175 | , ($) 176 | , (&) 177 | , const 178 | , flip 179 | , fix 180 | , on 181 | ) 182 | import Data.Functor as Functor ( 183 | Functor (..) 184 | , (<$>) 185 | , ($>) 186 | , void 187 | ) 188 | import Data.Eq as Eq ( 189 | Eq (..) 190 | ) 191 | import Data.Int as Int ( 192 | Int 193 | , Int8 194 | , Int16 195 | , Int32 196 | , Int64 197 | ) 198 | import Data.Maybe as Maybe ( 199 | Maybe (..) 200 | , fromMaybe 201 | , maybe 202 | ) 203 | import Data.Monoid as Monoid ( 204 | Monoid (..) 205 | , (<>) 206 | ) 207 | import Data.Ord as Ord ( 208 | Ord (..) 209 | , Ordering (..) 210 | , comparing 211 | ) 212 | import Data.Traversable as Traversable ( 213 | Traversable (..) 214 | , for 215 | ) 216 | import Data.Tuple as Tuple ( 217 | fst 218 | , snd 219 | , curry 220 | , uncurry 221 | ) 222 | import Data.Word as Word ( 223 | Word64 224 | ) 225 | 226 | import qualified Debug.Trace as Trace 227 | 228 | import GHC.Real as Real ( 229 | fromIntegral 230 | , fromRational 231 | ) 232 | #if MIN_VERSION_base(4,9,0) 233 | import GHC.Stack (HasCallStack) 234 | #endif 235 | 236 | import Prelude as Prelude ( 237 | Enum (..) 238 | , Num (..) 239 | , Integer 240 | , seq 241 | , ($!) 242 | ) 243 | import qualified Prelude as Unsafe 244 | 245 | import System.IO as IO ( 246 | FilePath 247 | , IO 248 | ) 249 | 250 | import Text.Read as Read ( 251 | Read (..) 252 | , readEither 253 | , readMaybe 254 | ) 255 | import Text.Show as Show ( 256 | Show (..) 257 | , ShowS 258 | , showString 259 | ) 260 | 261 | 262 | #if MIN_VERSION_base(4,9,0) 263 | undefined :: HasCallStack => a 264 | #else 265 | undefined :: a 266 | #endif 267 | undefined = 268 | Unsafe.undefined 269 | {-# WARNING undefined "'undefined' is unsafe" #-} 270 | 271 | #if MIN_VERSION_base(4,9,0) 272 | error :: HasCallStack => [Char] -> a 273 | #else 274 | error :: [Char] -> a 275 | #endif 276 | error = 277 | Unsafe.error 278 | {-# WARNING error "'error' is unsafe" #-} 279 | 280 | trace :: [Char] -> a -> a 281 | trace = 282 | Trace.trace 283 | {-# WARNING trace "'trace' should only be used while debugging" #-} 284 | 285 | #if MIN_VERSION_base(4,9,0) 286 | traceM :: Applicative f => [Char] -> f () 287 | #else 288 | traceM :: Monad m => [Char] -> m () 289 | #endif 290 | traceM = 291 | Trace.traceM 292 | {-# WARNING traceM "'traceM' should only be used while debugging" #-} 293 | 294 | traceIO :: [Char] -> IO () 295 | traceIO = 296 | Trace.traceIO 297 | {-# WARNING traceIO "'traceIO' should only be used while debugging" #-} 298 | 299 | with :: Functor f => f a -> (a -> b) -> f b 300 | with = 301 | flip fmap 302 | {-# INLINE with #-} 303 | 304 | -- | Tag a 'Nothing'. 305 | note :: a -> Maybe b -> Either a b 306 | note a Nothing = Left a 307 | note _ (Just b) = Right b 308 | {-# INLINEABLE note #-} 309 | 310 | -- | Eliminate a 'Left'. 311 | hush :: Either a b -> Maybe b 312 | hush (Left _) = Nothing 313 | hush (Right b) = Just b 314 | {-# INLINEABLE hush #-} 315 | -------------------------------------------------------------------------------- /src/Language/Haskell/HGrep/Print.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Language.Haskell.HGrep.Print ( 4 | printParseError 5 | , printSearchResult 6 | , printSearchResultLocation 7 | ) where 8 | 9 | 10 | import qualified Data.List as L 11 | import qualified Data.Map as Map 12 | 13 | import qualified Text.Printf as T 14 | 15 | import qualified Language.Haskell.GHC.ExactPrint as EP 16 | import qualified Language.Haskell.GHC.ExactPrint.Types as EP 17 | import qualified Language.Haskell.HsColour as HsColour 18 | import qualified Language.Haskell.HsColour.Colourise as HsColour 19 | 20 | import Language.Haskell.HGrep.Internal.Data 21 | import Language.Haskell.HGrep.Internal.Print 22 | import Language.Haskell.HGrep.Prelude 23 | 24 | import qualified SrcLoc 25 | 26 | 27 | printParseError :: ParseError -> [Char] 28 | printParseError ExactPrintException = "Parsing failed unexpectedly with an exception" 29 | printParseError (ExactPrintParseError (loc, msg)) = 30 | L.intercalate ": " [ 31 | unsafePpr loc 32 | , msg 33 | ] 34 | 35 | printSearchResult :: PrintOpts -> SearchResult -> [Char] 36 | printSearchResult (PrintOpts co lno) (SearchResult anns ast) = 37 | -- Get the start position of the comment before search result 38 | colorize $ case lno of 39 | PrintLineNums -> numberedSrc 40 | NoLineNums -> nonNumberedSrc 41 | where 42 | colorize :: [Char] -> [Char] 43 | colorize anySrc = 44 | case co of 45 | DefaultColours -> hscolour anySrc 46 | NoColours -> anySrc 47 | 48 | resSpan :: SrcLoc.SrcSpan 49 | resSpan = SrcLoc.getLoc ast 50 | 51 | isSameLoc :: EP.AnnKey -> Bool 52 | isSameLoc (EP.AnnKey loc _) = loc == resSpan 53 | 54 | -- Returns the line number if possible to find 55 | getSpanStartLine :: SrcLoc.SrcSpan -> Maybe Int 56 | getSpanStartLine someSpan = 57 | case SrcLoc.srcSpanStart someSpan of 58 | SrcLoc.RealSrcLoc x -> Just $ SrcLoc.srcLocLine x 59 | _ -> Nothing 60 | 61 | -- ignore empty lines before the actual result 62 | wholeSrc :: [Char] 63 | wholeSrc = EP.exactPrint ast anns 64 | 65 | nonEmptySrc :: [[Char]] 66 | (_, nonEmptySrc) = L.span null $ L.lines wholeSrc 67 | 68 | nonNumberedSrc = L.unlines nonEmptySrc 69 | 70 | -- Doesn't prepent locations when there is no start line number 71 | printWithLineNums :: Maybe Int -> [Char] 72 | printWithLineNums Nothing = nonNumberedSrc 73 | printWithLineNums (Just start) = 74 | L.unlines $ L.zipWith prependLineNum [start..] nonEmptySrc 75 | 76 | annsPairs = Map.toList anns 77 | targetAnnPairs = L.filter (isSameLoc .fst) annsPairs 78 | resLoc = getSpanStartLine resSpan 79 | startLineNum = 80 | case targetAnnPairs of 81 | [] -> resLoc 82 | ((_, ann) : _) -> 83 | case EP.annPriorComments ann of 84 | [] -> resLoc 85 | ((comment, _) : _) -> getSpanStartLine $ EP.commentIdentifier comment 86 | 87 | numberedSrc = printWithLineNums startLineNum 88 | 89 | -- Adds line numbers at the start of each line 90 | prependLineNum :: Int -> [Char] -> [Char] 91 | prependLineNum i l = T.printf "%5d " i <> l 92 | 93 | printSearchResultLocation :: PrintOpts -> SearchResult -> [Char] 94 | printSearchResultLocation opts (SearchResult _anns ast) = 95 | printSrcSpan opts (SrcLoc.getLoc ast) 96 | 97 | hscolour :: [Char] -> [Char] 98 | hscolour = 99 | HsColour.hscolour HsColour.TTY HsColour.defaultColourPrefs False False "" False 100 | -------------------------------------------------------------------------------- /src/Language/Haskell/HGrep/Query.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE RankNTypes #-} 4 | {-# LANGUAGE CPP #-} 5 | module Language.Haskell.HGrep.Query ( 6 | findTypeDecl 7 | , findValueDecl 8 | ) where 9 | 10 | 11 | import Control.Lens 12 | 13 | import Data.Foldable (any) 14 | import qualified Data.List as L 15 | import Data.Maybe (fromMaybe) 16 | import Data.Monoid (First) 17 | 18 | import Language.Haskell.HGrep.Internal.Data 19 | import Language.Haskell.HGrep.Internal.Lens 20 | import Language.Haskell.HGrep.Prelude 21 | 22 | import Text.Regex.PCRE.Heavy ((=~)) 23 | 24 | import qualified FastString 25 | import qualified HsDecls 26 | import qualified OccName 27 | import qualified RdrName 28 | import SrcLoc (unLoc) 29 | #if MIN_VERSION_base(4,11,0) 30 | import qualified GHC 31 | #endif 32 | 33 | 34 | findTypeDecl :: Query -> ParsedSource -> [SearchResult] 35 | findTypeDecl q src = 36 | matchDecls src $ \decl -> 37 | fromMaybe False . match decl $ 38 | #if !MIN_VERSION_base(4,12,0) 39 | _TyClD . _DataDecl . _1 . _unloc . to (nameQuery q) 40 | <> _TyClD . _SynDecl . _1 . _unloc . to (nameQuery q) 41 | #else 42 | _TyClD . _2 . _DataDecl . _2 . _unloc . to (nameQuery q) 43 | <> _TyClD . _2 . _SynDecl . _2 . _unloc . to (nameQuery q) 44 | #endif 45 | 46 | findValueDecl :: Query -> ParsedSource -> [SearchResult] 47 | findValueDecl q src = 48 | matchDecls src $ \decl -> 49 | fromMaybe False . match decl $ 50 | #if !MIN_VERSION_base(4,12,0) 51 | _ValD . _FunBind . _1 . _unloc . to (nameQuery q) 52 | <> _ValD . _VarBind . _1 . to (nameQuery q) 53 | <> _SigD . _TypeSig . _1 . to (any (nameQuery q . unLoc)) 54 | #else 55 | _ValD . _2 . _FunBind . _2 . _unloc . to (nameQuery q) 56 | <> _ValD . _2 . _VarBind . _2 . to (nameQuery q) 57 | <> _SigD . _2 . _TypeSig . _2 . to (any (nameQuery q . unLoc)) 58 | #endif 59 | 60 | #if !MIN_VERSION_base(4,11,0) 61 | matchDecls :: ParsedSource -> (HsDecls.HsDecl RdrName.RdrName -> Bool) -> [SearchResult] 62 | #else 63 | matchDecls :: ParsedSource -> (HsDecls.HsDecl GHC.GhcPs -> Bool) -> [SearchResult] 64 | #endif 65 | matchDecls (ParsedSource (anns, locMod)) p = 66 | fmap (SearchResult anns) $ 67 | L.filter (p . unLoc) (locMod ^. _unloc . _hsmodDecls) 68 | 69 | nameQuery :: Query -> RdrName.RdrName -> Bool 70 | nameQuery q n = 71 | case q of 72 | MatchSimple name -> 73 | compareName name n 74 | MatchRegex (Regex rex) -> 75 | nameToString n =~ rex 76 | 77 | compareName :: [Char] -> RdrName.RdrName -> Bool 78 | compareName name n = 79 | case n of 80 | RdrName.Unqual ocn -> 81 | fastEq name (OccName.occNameFS ocn) 82 | RdrName.Qual _ ocn -> 83 | fastEq name (OccName.occNameFS ocn) 84 | _ -> 85 | False 86 | 87 | nameToFS :: RdrName.RdrName -> FastString.FastString 88 | nameToFS n = 89 | OccName.occNameFS $ 90 | case n of 91 | RdrName.Unqual ocn -> 92 | ocn 93 | RdrName.Qual _mod ocn -> 94 | ocn 95 | RdrName.Orig _mod ocn -> 96 | ocn 97 | RdrName.Exact name -> 98 | name ^. _n_occ 99 | 100 | nameToString :: RdrName.RdrName -> [Char] 101 | nameToString = 102 | FastString.unpackFS . nameToFS 103 | 104 | fastEq :: [Char] -> FastString.FastString -> Bool 105 | fastEq s fs = 106 | FastString.mkFastString s == fs 107 | 108 | match :: s -> Getting (First a) s a -> Maybe a 109 | match = 110 | flip preview 111 | {-# INLINE match #-} 112 | -------------------------------------------------------------------------------- /stack-8.0.1.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-7.24 2 | 3 | extra-deps: 4 | - ghc-exactprint-0.5.4.0 5 | - lens-4.15.4 6 | - optparse-applicative-0.14.0.0 7 | - profunctors-5.2.1 8 | - th-abstraction-0.2.6.0 9 | 10 | packages: 11 | - . 12 | 13 | flags: {} 14 | 15 | extra-package-dbs: [] 16 | 17 | nix: 18 | enable: false 19 | packages: 20 | - pcre 21 | - pkgconfig 22 | -------------------------------------------------------------------------------- /stack-8.2.1.yaml: -------------------------------------------------------------------------------- 1 | resolver: nightly-2017-10-08 2 | 3 | packages: 4 | - . 5 | 6 | flags: {} 7 | 8 | extra-package-dbs: [] 9 | 10 | nix: 11 | enable: false 12 | packages: 13 | - pcre 14 | - pkgconfig 15 | -------------------------------------------------------------------------------- /stack-8.2.2.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-11.22 2 | 3 | packages: 4 | - . 5 | 6 | flags: {} 7 | 8 | extra-package-dbs: [] 9 | 10 | nix: 11 | enable: false 12 | packages: 13 | - pcre 14 | - pkgconfig 15 | -------------------------------------------------------------------------------- /stack-8.4.3.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-12.14 2 | 3 | packages: 4 | - . 5 | 6 | flags: {} 7 | 8 | extra-package-dbs: [] 9 | 10 | nix: 11 | enable: false 12 | packages: 13 | - pcre 14 | - pkgconfig 15 | -------------------------------------------------------------------------------- /stack-8.4.4.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-12.26 2 | 3 | packages: 4 | - . 5 | 6 | flags: {} 7 | 8 | extra-package-dbs: [] 9 | 10 | nix: 11 | enable: false 12 | packages: 13 | - pcre 14 | - pkgconfig 15 | -------------------------------------------------------------------------------- /stack-8.6.3.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-13.11 2 | 3 | packages: 4 | - . 5 | 6 | flags: {} 7 | 8 | extra-package-dbs: [] 9 | 10 | nix: 11 | enable: false 12 | packages: 13 | - pcre 14 | - pkgconfig 15 | -------------------------------------------------------------------------------- /stack-8.6.5.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-14.20 2 | 3 | packages: 4 | - . 5 | 6 | flags: {} 7 | 8 | extra-package-dbs: [] 9 | 10 | nix: 11 | enable: false 12 | packages: 13 | - pcre 14 | - pkgconfig 15 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-14.20 2 | 3 | packages: 4 | - . 5 | 6 | flags: {} 7 | 8 | extra-package-dbs: [] 9 | 10 | nix: 11 | enable: false 12 | packages: 13 | - pcre 14 | - pkgconfig 15 | --------------------------------------------------------------------------------