├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.coffee ├── LICENSE.md ├── README.md ├── binding.gyp ├── package-lock.json ├── package.json ├── spec ├── ctags-line-number-spec.coffee ├── ctags-spec.coffee └── fixtures │ ├── line-number-tags │ └── tags └── src ├── ctags.coffee ├── readtags.c ├── readtags.h ├── tag-finder.cc ├── tag-finder.h ├── tag-reader.cc ├── tag-reader.h ├── tag.h ├── tags.cc └── tags.h /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | lib/*.js 4 | npm-debug.log 5 | .node-version 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/ 2 | spec/ 3 | *.coffee 4 | .travis.yml 5 | .npmignore 6 | npm-debug.log 7 | .node-version 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | 4 | sudo: false 5 | 6 | addons: 7 | apt: 8 | sources: 9 | - ubuntu-toolchain-r-test 10 | packages: 11 | - g++-4.8 12 | 13 | install: 14 | - export CXX=g++-4.8 15 | - $CXX --version 16 | - npm i 17 | 18 | language: node_js 19 | 20 | node_js: 21 | - "node" 22 | 23 | notifications: 24 | email: 25 | on_success: never 26 | on_failure: change 27 | 28 | git: 29 | depth: 10 30 | 31 | branches: 32 | only: 33 | - master 34 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | grunt.initConfig 3 | pkg: grunt.file.readJSON('package.json') 4 | 5 | coffee: 6 | glob_to_multiple: 7 | expand: true 8 | cwd: 'src' 9 | src: ['*.coffee'] 10 | dest: 'lib' 11 | ext: '.js' 12 | 13 | coffeelint: 14 | options: 15 | max_line_length: 16 | level: 'ignore' 17 | 18 | src: ['src/**/*.coffee'] 19 | test: ['spec/**/*.coffee'] 20 | gruntfile: ['Gruntfile.coffee'] 21 | 22 | cpplint: 23 | files: ['src/**/*.cc'] 24 | reporter: 'spec' 25 | verbosity: 1 26 | filters: 27 | build: 28 | include: false 29 | legal: 30 | copyright: false 31 | readability: 32 | braces: false 33 | 34 | shell: 35 | rebuild: 36 | command: 'npm build .' 37 | options: 38 | stdout: true 39 | stderr: true 40 | failOnError: true 41 | 42 | test: 43 | command: 'node_modules/.bin/jasmine-focused --captureExceptions --coffee spec' 44 | options: 45 | stdout: true 46 | stderr: true 47 | failOnError: true 48 | 49 | grunt.loadNpmTasks('grunt-contrib-coffee') 50 | grunt.loadNpmTasks('grunt-shell') 51 | grunt.loadNpmTasks('grunt-coffeelint') 52 | grunt.loadNpmTasks('node-cpplint') 53 | grunt.registerTask 'clean', -> 54 | rm = require('rimraf').sync 55 | rm('lib') 56 | rm('build') 57 | grunt.registerTask('lint', ['coffeelint', 'cpplint']) 58 | grunt.registerTask('default', ['lint', 'coffee', 'shell:rebuild']) 59 | grunt.registerTask('test', ['default', 'shell:test']) 60 | grunt.registerTask('prepublish', ['clean', 'coffee', 'lint', 'shell:rebuild']) 61 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | # Ctags Node module [![Build Status](https://travis-ci.org/atom/node-ctags.png)](https://travis-ci.org/atom/node-ctags) 3 | 4 | Read all about ctags [here](http://ctags.sourceforge.net/). 5 | 6 | ## Installing 7 | 8 | ```sh 9 | npm install ctags 10 | ``` 11 | 12 | ## Building 13 | * Clone the repository 14 | * Run `npm install` 15 | * Run `grunt` to compile the native and CoffeeScript code 16 | * Run `grunt test` to run the specs 17 | 18 | ## Documentation 19 | 20 | ### findTags(tagsFilePath, tag, [options], callback) 21 | 22 | Get all tags matching the tag specified from the tags file at the path. 23 | 24 | * `tagsFilePath` - The string path to the tags file. 25 | 26 | * `tag` - The string name of the tag to search for. 27 | 28 | * `options` - An optional options object containing the following keys: 29 | 30 | * `caseInsensitive` - `true` to include tags that match case insensitively, 31 | (default: `false`) 32 | * `partialMatch` - `true` to include tags that partially match the given tag 33 | (default: `false`) 34 | 35 | * `callback` - The function to call when complete with an error as the first 36 | argument and an array containing objects that have `name` and 37 | `file` keys and optionally a `pattern` key if the tag file 38 | specified contains tag patterns. 39 | 40 | #### Example 41 | 42 | ```coffeescript 43 | ctags = require 'ctags' 44 | 45 | ctags.findTags('/Users/me/repos/node/tags', 'exists', (error, tags=[]) -> 46 | for tag in tags 47 | console.log("#{tag.name} is in #{tag.file}") 48 | ``` 49 | 50 | ### createReadStream(tagsFilePath, [options]) 51 | 52 | Create a read stream to a tags file. 53 | 54 | The stream returned will emit `data` events with arrays of tag objects 55 | that have `name` and `file` keys and optionally a `pattern` key if the tag file 56 | specified contains tag patterns. 57 | 58 | An `error` event will be emitted if the tag file cannot be read. 59 | 60 | An `end` event will be emitted when all the tags have been read. 61 | 62 | * `tagsFilePath` - The string path to the tags file. 63 | 64 | * `options` - An optional object containing the following keys. 65 | 66 | * `chunkSize` - The number of tags to read at a time (default: `100`). 67 | 68 | Returns a stream. 69 | #### Example 70 | 71 | ```coffeescript 72 | ctags = require 'ctags' 73 | 74 | stream = ctags.createReadStream('/Users/me/repos/node/tags') 75 | stream.on 'data', (tags) -> 76 | for tag in tags 77 | console.log("#{tag.name} is in #{tag.file} with pattern: #{tag.pattern}") 78 | ``` 79 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'ctags', 5 | 'include_dirs': [ '=1.6.0", 104 | "glob": ">=3.1.9", 105 | "optimist": ">=0.2.8" 106 | }, 107 | "dependencies": { 108 | "coffee-script": { 109 | "version": "1.6.2", 110 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.2.tgz", 111 | "integrity": "sha1-/ZyINpweQeMwegoWDXE/IlE8k7M=", 112 | "dev": true 113 | }, 114 | "glob": { 115 | "version": "3.1.14", 116 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.14.tgz", 117 | "integrity": "sha1-+XpzHEHaZpXcg5RLuyF36aKbNj0=", 118 | "dev": true, 119 | "requires": { 120 | "graceful-fs": "~1.1.2", 121 | "inherits": "1", 122 | "minimatch": "0.2" 123 | }, 124 | "dependencies": { 125 | "graceful-fs": { 126 | "version": "1.1.14", 127 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.1.14.tgz", 128 | "integrity": "sha1-BweNtfY3f2Mh/Oqu30l94STclGU=", 129 | "dev": true 130 | }, 131 | "inherits": { 132 | "version": "1.0.0", 133 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.0.tgz", 134 | "integrity": "sha1-OOGXUoW/H3upyE2hArsSdxMirEg=", 135 | "dev": true 136 | }, 137 | "minimatch": { 138 | "version": "0.2.9", 139 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.9.tgz", 140 | "integrity": "sha1-uAr5R+aoOoxo8m5UwNYSW6yfiH8=", 141 | "dev": true, 142 | "requires": { 143 | "lru-cache": "~2.0.0", 144 | "sigmund": "~1.0.0" 145 | }, 146 | "dependencies": { 147 | "lru-cache": { 148 | "version": "2.0.4", 149 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.0.4.tgz", 150 | "integrity": "sha1-uLYa4JhIOF7Gdodg45wSPn45Voo=", 151 | "dev": true 152 | }, 153 | "sigmund": { 154 | "version": "1.0.0", 155 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.0.tgz", 156 | "integrity": "sha1-ZqKzp0mui1+4nv1PzAHclPvgIpY=", 157 | "dev": true 158 | } 159 | } 160 | } 161 | } 162 | }, 163 | "optimist": { 164 | "version": "0.3.5", 165 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.5.tgz", 166 | "integrity": "sha1-A2VLUkFwMDEtEJ85sVmCW2AwkwQ=", 167 | "dev": true, 168 | "requires": { 169 | "wordwrap": "~0.0.2" 170 | }, 171 | "dependencies": { 172 | "wordwrap": { 173 | "version": "0.0.2", 174 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 175 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", 176 | "dev": true 177 | } 178 | } 179 | }, 180 | "vows": { 181 | "version": "0.7.0", 182 | "resolved": "https://registry.npmjs.org/vows/-/vows-0.7.0.tgz", 183 | "integrity": "sha1-3QBl8RC6DAptY+hEhRwyCBdtWGc=", 184 | "requires": { 185 | "diff": "~1.0.3", 186 | "eyes": ">=0.1.6" 187 | }, 188 | "dependencies": { 189 | "diff": { 190 | "version": "1.0.4", 191 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.4.tgz", 192 | "integrity": "sha1-+P4oT7rB1lHUGxHTzqkT/ZOM2RU=" 193 | }, 194 | "eyes": { 195 | "version": "0.1.8", 196 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 197 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 198 | } 199 | } 200 | } 201 | } 202 | }, 203 | "coffeestack": { 204 | "version": "1.2.0", 205 | "resolved": "https://registry.npmjs.org/coffeestack/-/coffeestack-1.2.0.tgz", 206 | "integrity": "sha512-vXT7ZxSZ4lXHh/0A2cODyFqrVIl4Vb0Er5wcS2SrFN4jW8g1qIAmcMsRlRdUKvnvfmKixvENYspAyF/ihWbpyw==", 207 | "dev": true, 208 | "requires": { 209 | "coffee-script": "~1.8.0", 210 | "fs-plus": "^3.1.1", 211 | "source-map": "~0.1.43" 212 | }, 213 | "dependencies": { 214 | "coffee-script": { 215 | "version": "1.8.0", 216 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.8.0.tgz", 217 | "integrity": "sha1-nJ8dK0pSoADe0Vtll5FwNkgmPB0=", 218 | "dev": true, 219 | "requires": { 220 | "mkdirp": "~0.3.5" 221 | } 222 | } 223 | } 224 | }, 225 | "colors": { 226 | "version": "0.6.2", 227 | "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", 228 | "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", 229 | "dev": true 230 | }, 231 | "commander": { 232 | "version": "1.1.1", 233 | "resolved": "https://registry.npmjs.org/commander/-/commander-1.1.1.tgz", 234 | "integrity": "sha1-UNFlGGiuYOzP8KLZ80WVN2vGsEE=", 235 | "dev": true, 236 | "requires": { 237 | "keypress": "0.1.x" 238 | } 239 | }, 240 | "concat-map": { 241 | "version": "0.0.1", 242 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 243 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 244 | "dev": true 245 | }, 246 | "dateformat": { 247 | "version": "1.0.2-1.2.3", 248 | "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", 249 | "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", 250 | "dev": true 251 | }, 252 | "duplexer": { 253 | "version": "0.1.1", 254 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", 255 | "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" 256 | }, 257 | "esprima": { 258 | "version": "1.0.4", 259 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", 260 | "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", 261 | "dev": true 262 | }, 263 | "event-stream": { 264 | "version": "3.1.7", 265 | "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.1.7.tgz", 266 | "integrity": "sha1-tMVAAS0P4UmEIPPYlGAI22OTw3o=", 267 | "requires": { 268 | "duplexer": "~0.1.1", 269 | "from": "~0", 270 | "map-stream": "~0.1.0", 271 | "pause-stream": "0.0.11", 272 | "split": "0.2", 273 | "stream-combiner": "~0.0.4", 274 | "through": "~2.3.1" 275 | } 276 | }, 277 | "eventemitter2": { 278 | "version": "0.4.14", 279 | "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", 280 | "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", 281 | "dev": true 282 | }, 283 | "exit": { 284 | "version": "0.1.2", 285 | "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", 286 | "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", 287 | "dev": true 288 | }, 289 | "fileset": { 290 | "version": "0.1.8", 291 | "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz", 292 | "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=", 293 | "dev": true, 294 | "requires": { 295 | "glob": "3.x", 296 | "minimatch": "0.x" 297 | } 298 | }, 299 | "findup-sync": { 300 | "version": "0.1.3", 301 | "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", 302 | "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", 303 | "dev": true, 304 | "requires": { 305 | "glob": "~3.2.9", 306 | "lodash": "~2.4.1" 307 | }, 308 | "dependencies": { 309 | "glob": { 310 | "version": "3.2.11", 311 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", 312 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", 313 | "dev": true, 314 | "requires": { 315 | "inherits": "2", 316 | "minimatch": "0.3" 317 | } 318 | }, 319 | "lodash": { 320 | "version": "2.4.2", 321 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", 322 | "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", 323 | "dev": true 324 | }, 325 | "minimatch": { 326 | "version": "0.3.0", 327 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", 328 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", 329 | "dev": true, 330 | "requires": { 331 | "lru-cache": "2", 332 | "sigmund": "~1.0.0" 333 | } 334 | } 335 | } 336 | }, 337 | "from": { 338 | "version": "0.1.7", 339 | "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", 340 | "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" 341 | }, 342 | "fs-plus": { 343 | "version": "3.1.1", 344 | "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz", 345 | "integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==", 346 | "dev": true, 347 | "requires": { 348 | "async": "^1.5.2", 349 | "mkdirp": "^0.5.1", 350 | "rimraf": "^2.5.2", 351 | "underscore-plus": "1.x" 352 | }, 353 | "dependencies": { 354 | "async": { 355 | "version": "1.5.2", 356 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 357 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", 358 | "dev": true 359 | }, 360 | "glob": { 361 | "version": "7.1.6", 362 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 363 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 364 | "dev": true, 365 | "requires": { 366 | "fs.realpath": "^1.0.0", 367 | "inflight": "^1.0.4", 368 | "inherits": "2", 369 | "minimatch": "^3.0.4", 370 | "once": "^1.3.0", 371 | "path-is-absolute": "^1.0.0" 372 | } 373 | }, 374 | "minimatch": { 375 | "version": "3.0.4", 376 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 377 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 378 | "dev": true, 379 | "requires": { 380 | "brace-expansion": "^1.1.7" 381 | } 382 | }, 383 | "mkdirp": { 384 | "version": "0.5.1", 385 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 386 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 387 | "dev": true, 388 | "requires": { 389 | "minimist": "0.0.8" 390 | } 391 | }, 392 | "rimraf": { 393 | "version": "2.7.1", 394 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 395 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 396 | "dev": true, 397 | "requires": { 398 | "glob": "^7.1.3" 399 | } 400 | } 401 | } 402 | }, 403 | "fs.realpath": { 404 | "version": "1.0.0", 405 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 406 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 407 | "dev": true 408 | }, 409 | "fstream": { 410 | "version": "0.1.31", 411 | "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", 412 | "integrity": "sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=", 413 | "dev": true, 414 | "requires": { 415 | "graceful-fs": "~3.0.2", 416 | "inherits": "~2.0.0", 417 | "mkdirp": "0.5", 418 | "rimraf": "2" 419 | }, 420 | "dependencies": { 421 | "graceful-fs": { 422 | "version": "3.0.12", 423 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", 424 | "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", 425 | "dev": true, 426 | "requires": { 427 | "natives": "^1.1.3" 428 | } 429 | }, 430 | "mkdirp": { 431 | "version": "0.5.1", 432 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 433 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 434 | "dev": true, 435 | "requires": { 436 | "minimist": "0.0.8" 437 | } 438 | } 439 | } 440 | }, 441 | "gaze": { 442 | "version": "0.3.4", 443 | "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz", 444 | "integrity": "sha1-X5S92gr+U7xxCWm81vKCVI1gwnk=", 445 | "dev": true, 446 | "requires": { 447 | "fileset": "~0.1.5", 448 | "minimatch": "~0.2.9" 449 | } 450 | }, 451 | "getobject": { 452 | "version": "0.1.0", 453 | "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", 454 | "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", 455 | "dev": true 456 | }, 457 | "glob": { 458 | "version": "3.1.21", 459 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", 460 | "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", 461 | "dev": true, 462 | "requires": { 463 | "graceful-fs": "~1.2.0", 464 | "inherits": "1", 465 | "minimatch": "~0.2.11" 466 | }, 467 | "dependencies": { 468 | "inherits": { 469 | "version": "1.0.2", 470 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", 471 | "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", 472 | "dev": true 473 | } 474 | } 475 | }, 476 | "graceful-fs": { 477 | "version": "1.2.3", 478 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", 479 | "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", 480 | "dev": true 481 | }, 482 | "grunt": { 483 | "version": "0.4.5", 484 | "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", 485 | "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", 486 | "dev": true, 487 | "requires": { 488 | "async": "~0.1.22", 489 | "coffee-script": "~1.3.3", 490 | "colors": "~0.6.2", 491 | "dateformat": "1.0.2-1.2.3", 492 | "eventemitter2": "~0.4.13", 493 | "exit": "~0.1.1", 494 | "findup-sync": "~0.1.2", 495 | "getobject": "~0.1.0", 496 | "glob": "~3.1.21", 497 | "grunt-legacy-log": "~0.1.0", 498 | "grunt-legacy-util": "~0.2.0", 499 | "hooker": "~0.2.3", 500 | "iconv-lite": "~0.2.11", 501 | "js-yaml": "~2.0.5", 502 | "lodash": "~0.9.2", 503 | "minimatch": "~0.2.12", 504 | "nopt": "~1.0.10", 505 | "rimraf": "~2.2.8", 506 | "underscore.string": "~2.2.1", 507 | "which": "~1.0.5" 508 | }, 509 | "dependencies": { 510 | "rimraf": { 511 | "version": "2.2.8", 512 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", 513 | "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", 514 | "dev": true 515 | } 516 | } 517 | }, 518 | "grunt-cli": { 519 | "version": "0.1.13", 520 | "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-0.1.13.tgz", 521 | "integrity": "sha1-6evEBHYx9QEtkidww5N4EzytEPQ=", 522 | "dev": true, 523 | "requires": { 524 | "findup-sync": "~0.1.0", 525 | "nopt": "~1.0.10", 526 | "resolve": "~0.3.1" 527 | } 528 | }, 529 | "grunt-coffeelint": { 530 | "version": "0.0.6", 531 | "resolved": "https://registry.npmjs.org/grunt-coffeelint/-/grunt-coffeelint-0.0.6.tgz", 532 | "integrity": "sha1-/NMBr9Z3XUGYCwDQHKYzG8kDBsw=", 533 | "dev": true, 534 | "requires": { 535 | "coffeelint": "~0.5" 536 | } 537 | }, 538 | "grunt-contrib-coffee": { 539 | "version": "0.9.0", 540 | "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.9.0.tgz", 541 | "integrity": "sha1-UZr1EF2hL+MWO7UOHKeAtslT77A=", 542 | "dev": true, 543 | "requires": { 544 | "chalk": "~0.4.0", 545 | "coffee-script": "~1.7.0", 546 | "lodash": "~2.4.1" 547 | }, 548 | "dependencies": { 549 | "coffee-script": { 550 | "version": "1.7.1", 551 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz", 552 | "integrity": "sha1-YplqhheAx15tUGnROCJyO3NAS/w=", 553 | "dev": true, 554 | "requires": { 555 | "mkdirp": "~0.3.5" 556 | } 557 | }, 558 | "lodash": { 559 | "version": "2.4.2", 560 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", 561 | "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", 562 | "dev": true 563 | } 564 | } 565 | }, 566 | "grunt-legacy-log": { 567 | "version": "0.1.3", 568 | "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", 569 | "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", 570 | "dev": true, 571 | "requires": { 572 | "colors": "~0.6.2", 573 | "grunt-legacy-log-utils": "~0.1.1", 574 | "hooker": "~0.2.3", 575 | "lodash": "~2.4.1", 576 | "underscore.string": "~2.3.3" 577 | }, 578 | "dependencies": { 579 | "lodash": { 580 | "version": "2.4.2", 581 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", 582 | "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", 583 | "dev": true 584 | }, 585 | "underscore.string": { 586 | "version": "2.3.3", 587 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", 588 | "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", 589 | "dev": true 590 | } 591 | } 592 | }, 593 | "grunt-legacy-log-utils": { 594 | "version": "0.1.1", 595 | "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", 596 | "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", 597 | "dev": true, 598 | "requires": { 599 | "colors": "~0.6.2", 600 | "lodash": "~2.4.1", 601 | "underscore.string": "~2.3.3" 602 | }, 603 | "dependencies": { 604 | "lodash": { 605 | "version": "2.4.2", 606 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", 607 | "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", 608 | "dev": true 609 | }, 610 | "underscore.string": { 611 | "version": "2.3.3", 612 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", 613 | "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", 614 | "dev": true 615 | } 616 | } 617 | }, 618 | "grunt-legacy-util": { 619 | "version": "0.2.0", 620 | "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", 621 | "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", 622 | "dev": true, 623 | "requires": { 624 | "async": "~0.1.22", 625 | "exit": "~0.1.1", 626 | "getobject": "~0.1.0", 627 | "hooker": "~0.2.3", 628 | "lodash": "~0.9.2", 629 | "underscore.string": "~2.2.1", 630 | "which": "~1.0.5" 631 | } 632 | }, 633 | "grunt-shell": { 634 | "version": "0.2.2", 635 | "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.2.2.tgz", 636 | "integrity": "sha1-f9470zu9SghxY428SGc4n3/ZX+Y=", 637 | "dev": true 638 | }, 639 | "has-color": { 640 | "version": "0.1.7", 641 | "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", 642 | "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", 643 | "dev": true 644 | }, 645 | "hooker": { 646 | "version": "0.2.3", 647 | "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", 648 | "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", 649 | "dev": true 650 | }, 651 | "iconv-lite": { 652 | "version": "0.2.11", 653 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", 654 | "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", 655 | "dev": true 656 | }, 657 | "inflight": { 658 | "version": "1.0.6", 659 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 660 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 661 | "dev": true, 662 | "requires": { 663 | "once": "^1.3.0", 664 | "wrappy": "1" 665 | } 666 | }, 667 | "inherits": { 668 | "version": "2.0.4", 669 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 670 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 671 | "dev": true 672 | }, 673 | "jasmine-focused": { 674 | "version": "1.0.7", 675 | "resolved": "https://registry.npmjs.org/jasmine-focused/-/jasmine-focused-1.0.7.tgz", 676 | "integrity": "sha1-uDx1fIAOaOHW78GjoaE/85/23NI=", 677 | "dev": true, 678 | "requires": { 679 | "jasmine-node": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", 680 | "underscore-plus": "1.x", 681 | "walkdir": "0.0.7" 682 | } 683 | }, 684 | "jasmine-node": { 685 | "version": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", 686 | "from": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", 687 | "dev": true, 688 | "requires": { 689 | "coffee-script": ">=1.0.1", 690 | "coffeestack": ">=1 <2", 691 | "gaze": "~0.3.2", 692 | "jasmine-reporters": ">=0.2.0", 693 | "mkdirp": "~0.3.5", 694 | "requirejs": ">=0.27.1", 695 | "underscore": ">= 1.3.1", 696 | "walkdir": ">= 0.0.1" 697 | } 698 | }, 699 | "jasmine-reporters": { 700 | "version": "2.3.2", 701 | "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz", 702 | "integrity": "sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A==", 703 | "dev": true, 704 | "requires": { 705 | "mkdirp": "^0.5.1", 706 | "xmldom": "^0.1.22" 707 | }, 708 | "dependencies": { 709 | "mkdirp": { 710 | "version": "0.5.1", 711 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 712 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 713 | "dev": true, 714 | "requires": { 715 | "minimist": "0.0.8" 716 | } 717 | } 718 | } 719 | }, 720 | "js-yaml": { 721 | "version": "2.0.5", 722 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", 723 | "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", 724 | "dev": true, 725 | "requires": { 726 | "argparse": "~ 0.1.11", 727 | "esprima": "~ 1.0.2" 728 | } 729 | }, 730 | "keypress": { 731 | "version": "0.1.0", 732 | "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", 733 | "integrity": "sha1-SjGI1CkbZrT2XtuZ+AaqmuKTWSo=", 734 | "dev": true 735 | }, 736 | "lodash": { 737 | "version": "0.9.2", 738 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", 739 | "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", 740 | "dev": true 741 | }, 742 | "lru-cache": { 743 | "version": "2.7.3", 744 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", 745 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", 746 | "dev": true 747 | }, 748 | "map-stream": { 749 | "version": "0.1.0", 750 | "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", 751 | "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" 752 | }, 753 | "minimatch": { 754 | "version": "0.2.14", 755 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", 756 | "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", 757 | "dev": true, 758 | "requires": { 759 | "lru-cache": "2", 760 | "sigmund": "~1.0.0" 761 | } 762 | }, 763 | "minimist": { 764 | "version": "0.0.8", 765 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 766 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 767 | "dev": true 768 | }, 769 | "mkdirp": { 770 | "version": "0.3.5", 771 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", 772 | "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", 773 | "dev": true 774 | }, 775 | "nan": { 776 | "version": "2.14.0", 777 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", 778 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" 779 | }, 780 | "natives": { 781 | "version": "1.1.6", 782 | "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", 783 | "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", 784 | "dev": true 785 | }, 786 | "node-cpplint": { 787 | "version": "0.1.5", 788 | "resolved": "https://registry.npmjs.org/node-cpplint/-/node-cpplint-0.1.5.tgz", 789 | "integrity": "sha1-2YDMU45KufUKmsfh6kgBwa/VkeI=", 790 | "dev": true, 791 | "requires": { 792 | "colors": "~0.6.0-1", 793 | "commander": "~1.1.1" 794 | } 795 | }, 796 | "node-gyp": { 797 | "version": "0.8.5", 798 | "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-0.8.5.tgz", 799 | "integrity": "sha1-dB5KVRQxhkjkf8hELZva1m/bH4Q=", 800 | "dev": true, 801 | "requires": { 802 | "fstream": "~0.1.13", 803 | "glob": "3", 804 | "graceful-fs": "1", 805 | "minimatch": "0.2", 806 | "mkdirp": "0.3", 807 | "nopt": "2", 808 | "npmlog": "0", 809 | "osenv": "0", 810 | "request": "2.9", 811 | "rimraf": "2", 812 | "semver": "1", 813 | "tar": "~0.1.12", 814 | "which": "1" 815 | }, 816 | "dependencies": { 817 | "nopt": { 818 | "version": "2.2.1", 819 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", 820 | "integrity": "sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc=", 821 | "dev": true, 822 | "requires": { 823 | "abbrev": "1" 824 | } 825 | } 826 | } 827 | }, 828 | "nopt": { 829 | "version": "1.0.10", 830 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 831 | "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", 832 | "dev": true, 833 | "requires": { 834 | "abbrev": "1" 835 | } 836 | }, 837 | "npmlog": { 838 | "version": "0.1.1", 839 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-0.1.1.tgz", 840 | "integrity": "sha1-i5ueRAXX7EjDHCNGllqtx6uuyqU=", 841 | "dev": true, 842 | "requires": { 843 | "ansi": "~0.3.0" 844 | } 845 | }, 846 | "once": { 847 | "version": "1.4.0", 848 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 849 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 850 | "dev": true, 851 | "requires": { 852 | "wrappy": "1" 853 | } 854 | }, 855 | "os-homedir": { 856 | "version": "1.0.2", 857 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 858 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 859 | "dev": true 860 | }, 861 | "os-tmpdir": { 862 | "version": "1.0.2", 863 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 864 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 865 | "dev": true 866 | }, 867 | "osenv": { 868 | "version": "0.1.5", 869 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 870 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 871 | "dev": true, 872 | "requires": { 873 | "os-homedir": "^1.0.0", 874 | "os-tmpdir": "^1.0.0" 875 | } 876 | }, 877 | "path-is-absolute": { 878 | "version": "1.0.1", 879 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 880 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 881 | "dev": true 882 | }, 883 | "pause-stream": { 884 | "version": "0.0.11", 885 | "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", 886 | "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", 887 | "requires": { 888 | "through": "~2.3" 889 | } 890 | }, 891 | "request": { 892 | "version": "2.9.203", 893 | "resolved": "https://registry.npmjs.org/request/-/request-2.9.203.tgz", 894 | "integrity": "sha1-bBcRpUB/uUoRQhlWPkQUW8v0cjo=", 895 | "dev": true 896 | }, 897 | "requirejs": { 898 | "version": "2.3.6", 899 | "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", 900 | "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", 901 | "dev": true 902 | }, 903 | "resolve": { 904 | "version": "0.3.1", 905 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz", 906 | "integrity": "sha1-NMY0R8ZkxwWY0cmxJvxDsqJDEKQ=", 907 | "dev": true 908 | }, 909 | "rimraf": { 910 | "version": "2.1.4", 911 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.1.4.tgz", 912 | "integrity": "sha1-Wm62Lu2gaPUe3lDymz5c0i89m7I=", 913 | "dev": true, 914 | "requires": { 915 | "graceful-fs": "~1" 916 | } 917 | }, 918 | "semver": { 919 | "version": "1.1.4", 920 | "resolved": "https://registry.npmjs.org/semver/-/semver-1.1.4.tgz", 921 | "integrity": "sha1-LlpOcrqwNHLMl/cnU7RQiRLvVUA=", 922 | "dev": true 923 | }, 924 | "sigmund": { 925 | "version": "1.0.1", 926 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", 927 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", 928 | "dev": true 929 | }, 930 | "source-map": { 931 | "version": "0.1.43", 932 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 933 | "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", 934 | "dev": true, 935 | "requires": { 936 | "amdefine": ">=0.0.4" 937 | } 938 | }, 939 | "split": { 940 | "version": "0.2.10", 941 | "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", 942 | "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", 943 | "requires": { 944 | "through": "2" 945 | } 946 | }, 947 | "stream-combiner": { 948 | "version": "0.0.4", 949 | "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", 950 | "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", 951 | "requires": { 952 | "duplexer": "~0.1.1" 953 | } 954 | }, 955 | "strip-ansi": { 956 | "version": "0.1.1", 957 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", 958 | "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", 959 | "dev": true 960 | }, 961 | "tar": { 962 | "version": "0.1.20", 963 | "resolved": "https://registry.npmjs.org/tar/-/tar-0.1.20.tgz", 964 | "integrity": "sha1-QpQLrltfIsdEg2mRJvnz8nRJyxM=", 965 | "dev": true, 966 | "requires": { 967 | "block-stream": "*", 968 | "fstream": "~0.1.28", 969 | "inherits": "2" 970 | } 971 | }, 972 | "through": { 973 | "version": "2.3.8", 974 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 975 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 976 | }, 977 | "underscore": { 978 | "version": "1.7.0", 979 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", 980 | "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", 981 | "dev": true 982 | }, 983 | "underscore-plus": { 984 | "version": "1.7.0", 985 | "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz", 986 | "integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==", 987 | "dev": true, 988 | "requires": { 989 | "underscore": "^1.9.1" 990 | }, 991 | "dependencies": { 992 | "underscore": { 993 | "version": "1.9.1", 994 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", 995 | "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", 996 | "dev": true 997 | } 998 | } 999 | }, 1000 | "underscore.string": { 1001 | "version": "2.2.1", 1002 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", 1003 | "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", 1004 | "dev": true 1005 | }, 1006 | "walkdir": { 1007 | "version": "0.0.7", 1008 | "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.7.tgz", 1009 | "integrity": "sha1-BNoCcKh6d4VAFzzb8KLbSZqNnik=", 1010 | "dev": true 1011 | }, 1012 | "which": { 1013 | "version": "1.0.9", 1014 | "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", 1015 | "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", 1016 | "dev": true 1017 | }, 1018 | "wrappy": { 1019 | "version": "1.0.2", 1020 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1021 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1022 | "dev": true 1023 | }, 1024 | "xmldom": { 1025 | "version": "0.1.27", 1026 | "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", 1027 | "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", 1028 | "dev": true 1029 | } 1030 | } 1031 | } 1032 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ctags", 3 | "description": "A package for reading source code tag files", 4 | "version": "3.1.0", 5 | "keywords": [ 6 | "tags", 7 | "etags" 8 | ], 9 | "author": { 10 | "name": "Kevin Sawicki", 11 | "email": "kevin@github.com" 12 | }, 13 | "licenses": [ 14 | { 15 | "type": "MIT", 16 | "url": "http://github.com/atom/node-ctags/raw/master/LICENSE.md" 17 | } 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/atom/node-ctags.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/atom/node-ctags/issues" 25 | }, 26 | "homepage": "http://atom.github.io/node-ctags", 27 | "main": "./lib/ctags.js", 28 | "devDependencies": { 29 | "grunt": "~0.4.0", 30 | "grunt-contrib-coffee": "~0.9.0", 31 | "grunt-shell": "~0.2.1", 32 | "grunt-cli": "~0.1.6", 33 | "node-gyp": "~0.8.5", 34 | "jasmine-focused": "1.x", 35 | "grunt-coffeelint": "0.0.6", 36 | "rimraf": "~2.1.4", 37 | "node-cpplint": "0.1.5" 38 | }, 39 | "dependencies": { 40 | "nan": "^2.14.0", 41 | "event-stream": "~3.1.0" 42 | }, 43 | "scripts": { 44 | "prepublish": "grunt prepublish", 45 | "test": "grunt test" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/ctags-line-number-spec.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | ctags = require '../lib/ctags' 4 | 5 | describe 'ctags', -> 6 | tagsFile = null 7 | 8 | beforeEach -> 9 | tagsFile = path.join(__dirname, 'fixtures', 'line-number-tags') 10 | 11 | describe '.createReadStream(tagsFileWithLineNumberPath)', -> 12 | it 'returns a stream that emits data and end events', -> 13 | stream = ctags.createReadStream(tagsFile) 14 | 15 | tags = [] 16 | stream.on 'data', (chunk) -> tags = tags.concat(chunk) 17 | 18 | endHandler = jasmine.createSpy('endHandler') 19 | stream.on('end', endHandler) 20 | 21 | waitsFor -> 22 | endHandler.callCount is 1 23 | 24 | runs -> 25 | expect(tags.length).toBe 4 26 | 27 | expect(tags[0].file).toBe 'tagged.js' 28 | expect(tags[0].name).toBe 'callMeMaybe' 29 | expect(tags[0].pattern).toBe '/^function callMeMaybe() {$/' 30 | expect(tags[0].kind).toBe 'f' 31 | expect(tags[0].lineNumber).toBe 3 32 | 33 | expect(tags[1].file).toBe 'tagged-duplicate.js' 34 | expect(tags[1].name).toBe 'duplicate' 35 | expect(tags[1].pattern).toBe '/^function duplicate() {$/' 36 | expect(tags[1].kind).toBe 'f' 37 | expect(tags[1].lineNumber).toBe 3 38 | 39 | expect(tags[2].file).toBe 'tagged.js' 40 | expect(tags[2].name).toBe 'duplicate' 41 | expect(tags[2].pattern).toBe '/^function duplicate() {$/' 42 | expect(tags[2].kind).toBe 'f' 43 | expect(tags[2].lineNumber).toBe 7 44 | 45 | expect(tags[3].file).toBe 'tagged.js' 46 | expect(tags[3].name).toBe 'thisIsCrazy' 47 | expect(tags[3].pattern).toBe '/^var thisIsCrazy = true;$/' 48 | expect(tags[3].kind).toBe 'v' 49 | expect(tags[3].lineNumber).toBe 2 -------------------------------------------------------------------------------- /spec/ctags-spec.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | ctags = require '../lib/ctags' 4 | 5 | describe 'ctags', -> 6 | tagsFile = null 7 | 8 | beforeEach -> 9 | tagsFile = path.join(__dirname, 'fixtures', 'tags') 10 | 11 | describe '.findTags(name, options, callback)', -> 12 | it 'calls back all matching tags', -> 13 | callback = jasmine.createSpy('callback') 14 | ctags.findTags(tagsFile, 'duplicate', callback) 15 | 16 | waitsFor -> 17 | callback.callCount is 1 18 | 19 | runs -> 20 | expect(callback.argsForCall[0][0]).toBeFalsy() 21 | 22 | tags = callback.argsForCall[0][1] 23 | expect(tags.length).toBe 2 24 | 25 | expect(tags[0].file).toBe 'tagged-duplicate.js' 26 | expect(tags[0].name).toBe 'duplicate' 27 | expect(tags[0].pattern).toBe '/^function duplicate() {$/' 28 | expect(tags[0].kind).toBe 'f' 29 | expect(tags[0].lineNumber).toBe 0 30 | 31 | expect(tags[1].file).toBe 'tagged.js' 32 | expect(tags[1].name).toBe 'duplicate' 33 | expect(tags[1].pattern).toBe '/^function duplicate() {$/' 34 | expect(tags[1].kind).toBe 'f' 35 | expect(tags[1].lineNumber).toBe 0 36 | 37 | describe 'when partialMatch is set to true', -> 38 | it 'returns tags that partially match the name', -> 39 | callback = jasmine.createSpy('callback') 40 | ctags.findTags(tagsFile, 'dup', partialMatch: true, callback) 41 | 42 | waitsFor -> 43 | callback.callCount is 1 44 | 45 | runs -> 46 | expect(callback.argsForCall[0][0]).toBeFalsy() 47 | tags = callback.argsForCall[0][1] 48 | 49 | expect(tags.length).toBe 2 50 | 51 | expect(tags[0].file).toBe 'tagged-duplicate.js' 52 | expect(tags[0].name).toBe 'duplicate' 53 | expect(tags[0].pattern).toBe '/^function duplicate() {$/' 54 | expect(tags[0].kind).toBe 'f' 55 | expect(tags[0].lineNumber).toBe 0 56 | 57 | expect(tags[1].file).toBe 'tagged.js' 58 | expect(tags[1].name).toBe 'duplicate' 59 | expect(tags[1].pattern).toBe '/^function duplicate() {$/' 60 | expect(tags[1].kind).toBe 'f' 61 | expect(tags[1].lineNumber).toBe 0 62 | 63 | describe 'when caseInsensitive is set to true', -> 64 | it 'returns tags that match the name case insensitively', -> 65 | callback = jasmine.createSpy('callback') 66 | tags = ctags.findTags(tagsFile, 'callmemaybe', caseInsensitive: true, callback) 67 | 68 | waitsFor -> 69 | callback.callCount is 1 70 | 71 | runs -> 72 | expect(callback.argsForCall[0][0]).toBeFalsy() 73 | tags = callback.argsForCall[0][1] 74 | 75 | expect(tags.length).toBe 1 76 | 77 | expect(tags[0].file).toBe 'tagged.js' 78 | expect(tags[0].name).toBe 'callMeMaybe' 79 | expect(tags[0].pattern).toBe '/^function callMeMaybe() {$/' 80 | expect(tags[0].kind).toBe 'f' 81 | expect(tags[0].lineNumber).toBe 0 82 | 83 | describe '.createReadStream(tagsFilePath)', -> 84 | it 'returns a stream that emits data and end events', -> 85 | stream = ctags.createReadStream(tagsFile) 86 | 87 | tags = [] 88 | stream.on 'data', (chunk) -> tags = tags.concat(chunk) 89 | 90 | endHandler = jasmine.createSpy('endHandler') 91 | stream.on('end', endHandler) 92 | 93 | waitsFor -> 94 | endHandler.callCount is 1 95 | 96 | runs -> 97 | expect(tags.length).toBe 4 98 | 99 | expect(tags[0].file).toBe 'tagged.js' 100 | expect(tags[0].name).toBe 'callMeMaybe' 101 | expect(tags[0].pattern).toBe '/^function callMeMaybe() {$/' 102 | expect(tags[0].kind).toBe 'f' 103 | expect(tags[0].lineNumber).toBe 0 104 | 105 | expect(tags[1].file).toBe 'tagged-duplicate.js' 106 | expect(tags[1].name).toBe 'duplicate' 107 | expect(tags[1].pattern).toBe '/^function duplicate() {$/' 108 | expect(tags[1].kind).toBe 'f' 109 | expect(tags[1].lineNumber).toBe 0 110 | 111 | expect(tags[2].file).toBe 'tagged.js' 112 | expect(tags[2].name).toBe 'duplicate' 113 | expect(tags[2].pattern).toBe '/^function duplicate() {$/' 114 | expect(tags[2].kind).toBe 'f' 115 | expect(tags[2].lineNumber).toBe 0 116 | 117 | expect(tags[3].file).toBe 'tagged.js' 118 | expect(tags[3].name).toBe 'thisIsCrazy' 119 | expect(tags[3].pattern).toBe '/^var thisIsCrazy = true;$/' 120 | expect(tags[3].kind).toBe 'v' 121 | expect(tags[3].lineNumber).toBe 0 122 | 123 | it "emits an error event when the tags file does not exist", -> 124 | missingTagsFile = path.join(__dirname, 'fixtures/not-tags') 125 | expect(fs.existsSync(missingTagsFile)).toBe false 126 | 127 | stream = ctags.createReadStream(missingTagsFile) 128 | 129 | errorHandler = jasmine.createSpy('endHandler') 130 | stream.on('error', errorHandler) 131 | 132 | waitsFor -> 133 | errorHandler.callCount is 1 134 | 135 | it "emit tags in chunks of the given size", -> 136 | stream = ctags.createReadStream(tagsFile, chunkSize: 3) 137 | 138 | dataHandler = jasmine.createSpy('dataHandler') 139 | stream.on 'data', dataHandler 140 | 141 | endHandler = jasmine.createSpy('endHandler') 142 | stream.on('end', endHandler) 143 | 144 | waitsFor -> 145 | endHandler.callCount is 1 146 | 147 | runs -> 148 | expect(dataHandler.argsForCall[0][0].length).toBe 3 149 | expect(dataHandler.argsForCall[1][0].length).toBe 1 150 | 151 | expect(dataHandler.argsForCall[0][0][0].file).toBe 'tagged.js' 152 | expect(dataHandler.argsForCall[0][0][0].name).toBe 'callMeMaybe' 153 | expect(dataHandler.argsForCall[0][0][0].pattern).toBe '/^function callMeMaybe() {$/' 154 | expect(dataHandler.argsForCall[0][0][0].kind).toBe 'f' 155 | expect(dataHandler.argsForCall[0][0][0].lineNumber).toBe 0 156 | 157 | expect(dataHandler.argsForCall[0][0][1].file).toBe 'tagged-duplicate.js' 158 | expect(dataHandler.argsForCall[0][0][1].name).toBe 'duplicate' 159 | expect(dataHandler.argsForCall[0][0][1].pattern).toBe '/^function duplicate() {$/' 160 | expect(dataHandler.argsForCall[0][0][1].kind).toBe 'f' 161 | expect(dataHandler.argsForCall[0][0][1].lineNumber).toBe 0 162 | 163 | expect(dataHandler.argsForCall[0][0][2].file).toBe 'tagged.js' 164 | expect(dataHandler.argsForCall[0][0][2].name).toBe 'duplicate' 165 | expect(dataHandler.argsForCall[0][0][2].pattern).toBe '/^function duplicate() {$/' 166 | expect(dataHandler.argsForCall[0][0][2].kind).toBe 'f' 167 | expect(dataHandler.argsForCall[0][0][2].lineNumber).toBe 0 168 | 169 | expect(dataHandler.argsForCall[1][0][0].file).toBe 'tagged.js' 170 | expect(dataHandler.argsForCall[1][0][0].name).toBe 'thisIsCrazy' 171 | expect(dataHandler.argsForCall[1][0][0].pattern).toBe '/^var thisIsCrazy = true;$/' 172 | expect(dataHandler.argsForCall[1][0][0].kind).toBe 'v' 173 | expect(dataHandler.argsForCall[1][0][0].lineNumber).toBe 0 174 | -------------------------------------------------------------------------------- /spec/fixtures/line-number-tags: -------------------------------------------------------------------------------- 1 | !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ 2 | !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ 3 | !_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ 4 | !_TAG_PROGRAM_NAME Exuberant Ctags // 5 | !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ 6 | !_TAG_PROGRAM_VERSION 5.8 // 7 | callMeMaybe tagged.js /^function callMeMaybe() {$/;" f line:3 8 | duplicate tagged-duplicate.js /^function duplicate() {$/;" f line:3 9 | duplicate tagged.js /^function duplicate() {$/;" f line:7 10 | thisIsCrazy tagged.js /^var thisIsCrazy = true;$/;" v line:2 11 | -------------------------------------------------------------------------------- /spec/fixtures/tags: -------------------------------------------------------------------------------- 1 | !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ 2 | !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ 3 | !_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ 4 | !_TAG_PROGRAM_NAME Exuberant Ctags // 5 | !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ 6 | !_TAG_PROGRAM_VERSION 5.8 // 7 | callMeMaybe tagged.js /^function callMeMaybe() {$/;" f 8 | duplicate tagged-duplicate.js /^function duplicate() {$/;" f 9 | duplicate tagged.js /^function duplicate() {$/;" f 10 | thisIsCrazy tagged.js /^var thisIsCrazy = true;$/;" v 11 | -------------------------------------------------------------------------------- /src/ctags.coffee: -------------------------------------------------------------------------------- 1 | {Tags} = require('../build/Release/ctags.node') 2 | es = require 'event-stream' 3 | 4 | exports.findTags = (tagsFilePath, tag, options, callback) -> 5 | unless typeof tagsFilePath is 'string' 6 | throw new TypeError('tagsFilePath must be a string') 7 | 8 | unless typeof tag is 'string' 9 | throw new TypeError('tag must be a string') 10 | 11 | if typeof options is 'function' 12 | callback = options 13 | options = null 14 | 15 | {partialMatch, caseInsensitive} = options ? {} 16 | 17 | tagsWrapper = new Tags(tagsFilePath) 18 | tagsWrapper.findTags tag, partialMatch, caseInsensitive, (error, tags) -> 19 | tagsWrapper.end() 20 | callback?(error, tags) 21 | 22 | undefined 23 | 24 | exports.createReadStream = (tagsFilePath, options={}) -> 25 | unless typeof tagsFilePath is 'string' 26 | throw new TypeError('tagsFilePath must be a string') 27 | 28 | {chunkSize} = options 29 | chunkSize = 100 if typeof chunkSize isnt 'number' 30 | 31 | tagsWrapper = new Tags(tagsFilePath) 32 | es.readable (count, callback) -> 33 | unless tagsWrapper.exists() 34 | return callback(new Error("Tags file could not be opened: #{tagsFilePath}")) 35 | 36 | tagsWrapper.getTags chunkSize, (error, tags) => 37 | tagsWrapper.end() if error? or tags.length is 0 38 | callback(error, tags) 39 | @emit('end') if error? or tags.length is 0 40 | -------------------------------------------------------------------------------- /src/readtags.c: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id: readtags.c 592 2007-07-31 03:30:41Z dhiebert $ 3 | * 4 | * Copyright (c) 1996-2003, Darren Hiebert 5 | * 6 | * This source code is released into the public domain. 7 | * 8 | * This module contains functions for reading tag files. 9 | */ 10 | 11 | /* 12 | * INCLUDE FILES 13 | */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include /* to declare off_t */ 20 | 21 | #include "readtags.h" 22 | 23 | /* 24 | * MACROS 25 | */ 26 | #define TAB '\t' 27 | 28 | 29 | /* 30 | * DATA DECLARATIONS 31 | */ 32 | typedef struct { 33 | size_t size; 34 | char *buffer; 35 | } vstring; 36 | 37 | /* Information about current tag file */ 38 | struct sTagFile { 39 | /* has the file been opened and this structure initialized? */ 40 | short initialized; 41 | /* format of tag file */ 42 | short format; 43 | /* how is the tag file sorted? */ 44 | sortType sortMethod; 45 | /* pointer to file structure */ 46 | FILE* fp; 47 | /* file position of first character of `line' */ 48 | off_t pos; 49 | /* size of tag file in seekable positions */ 50 | off_t size; 51 | /* last line read */ 52 | vstring line; 53 | /* name of tag in last line read */ 54 | vstring name; 55 | /* defines tag search state */ 56 | struct { 57 | /* file position of last match for tag */ 58 | off_t pos; 59 | /* name of tag last searched for */ 60 | char *name; 61 | /* length of name for partial matches */ 62 | size_t nameLength; 63 | /* peforming partial match */ 64 | short partial; 65 | /* ignoring case */ 66 | short ignorecase; 67 | } search; 68 | /* miscellaneous extension fields */ 69 | struct { 70 | /* number of entries in `list' */ 71 | unsigned short max; 72 | /* list of key value pairs */ 73 | tagExtensionField *list; 74 | } fields; 75 | /* buffers to be freed at close */ 76 | struct { 77 | /* name of program author */ 78 | char *author; 79 | /* name of program */ 80 | char *name; 81 | /* URL of distribution */ 82 | char *url; 83 | /* program version */ 84 | char *version; 85 | } program; 86 | }; 87 | 88 | /* 89 | * DATA DEFINITIONS 90 | */ 91 | const char *const EmptyString = ""; 92 | const char *const PseudoTagPrefix = "!_"; 93 | 94 | /* 95 | * FUNCTION DEFINITIONS 96 | */ 97 | 98 | /* 99 | * Compare two strings, ignoring case. 100 | * Return 0 for match, < 0 for smaller, > 0 for bigger 101 | * Make sure case is folded to uppercase in comparison (like for 'sort -f') 102 | * This makes a difference when one of the chars lies between upper and lower 103 | * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !) 104 | */ 105 | static int struppercmp (const char *s1, const char *s2) 106 | { 107 | int result; 108 | do 109 | { 110 | result = toupper ((int) *s1) - toupper ((int) *s2); 111 | } while (result == 0 && *s1++ != '\0' && *s2++ != '\0'); 112 | return result; 113 | } 114 | 115 | static int strnuppercmp (const char *s1, const char *s2, size_t n) 116 | { 117 | int result; 118 | do 119 | { 120 | result = toupper ((int) *s1) - toupper ((int) *s2); 121 | } while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0'); 122 | return result; 123 | } 124 | 125 | static int growString (vstring *s) 126 | { 127 | int result = 0; 128 | size_t newLength; 129 | char *newLine; 130 | if (s->size == 0) 131 | { 132 | newLength = 128; 133 | newLine = (char*) malloc (newLength); 134 | *newLine = '\0'; 135 | } 136 | else 137 | { 138 | newLength = 2 * s->size; 139 | newLine = (char*) realloc (s->buffer, newLength); 140 | } 141 | if (newLine == NULL) 142 | perror ("string too large"); 143 | else 144 | { 145 | s->buffer = newLine; 146 | s->size = newLength; 147 | result = 1; 148 | } 149 | return result; 150 | } 151 | 152 | /* Copy name of tag out of tag line */ 153 | static void copyName (tagFile *const file) 154 | { 155 | size_t length; 156 | const char *end = strchr (file->line.buffer, '\t'); 157 | if (end == NULL) 158 | { 159 | end = strchr (file->line.buffer, '\n'); 160 | if (end == NULL) 161 | end = strchr (file->line.buffer, '\r'); 162 | } 163 | if (end != NULL) 164 | length = end - file->line.buffer; 165 | else 166 | length = strlen (file->line.buffer); 167 | while (length >= file->name.size) 168 | growString (&file->name); 169 | strncpy (file->name.buffer, file->line.buffer, length); 170 | file->name.buffer [length] = '\0'; 171 | } 172 | 173 | static int readTagLineRaw (tagFile *const file) 174 | { 175 | int result = 1; 176 | int reReadLine; 177 | 178 | /* If reading the line places any character other than a null or a 179 | * newline at the last character position in the buffer (one less than 180 | * the buffer size), then we must resize the buffer and reattempt to read 181 | * the line. 182 | */ 183 | do 184 | { 185 | char *const pLastChar = file->line.buffer + file->line.size - 2; 186 | char *line; 187 | 188 | file->pos = ftell (file->fp); 189 | reReadLine = 0; 190 | *pLastChar = '\0'; 191 | line = fgets (file->line.buffer, (int) file->line.size, file->fp); 192 | if (line == NULL) 193 | { 194 | /* read error */ 195 | if (! feof (file->fp)) 196 | perror ("readTagLine"); 197 | result = 0; 198 | } 199 | else if (*pLastChar != '\0' && 200 | *pLastChar != '\n' && *pLastChar != '\r') 201 | { 202 | /* buffer overflow */ 203 | growString (&file->line); 204 | fseek (file->fp, file->pos, SEEK_SET); 205 | reReadLine = 1; 206 | } 207 | else 208 | { 209 | size_t i = strlen (file->line.buffer); 210 | while (i > 0 && 211 | (file->line.buffer [i - 1] == '\n' || file->line.buffer [i - 1] == '\r')) 212 | { 213 | file->line.buffer [i - 1] = '\0'; 214 | --i; 215 | } 216 | } 217 | } while (reReadLine && result); 218 | if (result) 219 | copyName (file); 220 | return result; 221 | } 222 | 223 | static int readTagLine (tagFile *const file) 224 | { 225 | int result; 226 | do 227 | { 228 | result = readTagLineRaw (file); 229 | } while (result && *file->name.buffer == '\0'); 230 | return result; 231 | } 232 | 233 | static tagResult growFields (tagFile *const file) 234 | { 235 | tagResult result = TagFailure; 236 | unsigned short newCount = (unsigned short) 2 * file->fields.max; 237 | tagExtensionField *newFields = (tagExtensionField*) 238 | realloc (file->fields.list, newCount * sizeof (tagExtensionField)); 239 | if (newFields == NULL) 240 | perror ("too many extension fields"); 241 | else 242 | { 243 | file->fields.list = newFields; 244 | file->fields.max = newCount; 245 | result = TagSuccess; 246 | } 247 | return result; 248 | } 249 | 250 | static void parseExtensionFields (tagFile *const file, tagEntry *const entry, 251 | char *const string) 252 | { 253 | char *p = string; 254 | while (p != NULL && *p != '\0') 255 | { 256 | while (*p == TAB) 257 | *p++ = '\0'; 258 | if (*p != '\0') 259 | { 260 | char *colon; 261 | char *field = p; 262 | p = strchr (p, TAB); 263 | if (p != NULL) 264 | *p++ = '\0'; 265 | colon = strchr (field, ':'); 266 | if (colon == NULL) 267 | entry->kind = field; 268 | else 269 | { 270 | const char *key = field; 271 | const char *value = colon + 1; 272 | *colon = '\0'; 273 | if (strcmp (key, "kind") == 0) 274 | entry->kind = value; 275 | else if (strcmp (key, "file") == 0) 276 | entry->fileScope = 1; 277 | else if (strcmp (key, "line") == 0) 278 | entry->address.lineNumber = atol (value); 279 | else 280 | { 281 | if (entry->fields.count == file->fields.max) 282 | growFields (file); 283 | file->fields.list [entry->fields.count].key = key; 284 | file->fields.list [entry->fields.count].value = value; 285 | ++entry->fields.count; 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | static void parseTagLine (tagFile *file, tagEntry *const entry) 293 | { 294 | int i; 295 | char *p = file->line.buffer; 296 | char *tab = strchr (p, TAB); 297 | 298 | entry->fields.list = NULL; 299 | entry->fields.count = 0; 300 | entry->kind = NULL; 301 | entry->fileScope = 0; 302 | 303 | entry->name = p; 304 | if (tab != NULL) 305 | { 306 | *tab = '\0'; 307 | p = tab + 1; 308 | entry->file = p; 309 | tab = strchr (p, TAB); 310 | if (tab != NULL) 311 | { 312 | int fieldsPresent; 313 | *tab = '\0'; 314 | p = tab + 1; 315 | if (*p == '/' || *p == '?') 316 | { 317 | int slashCount; 318 | /* parse pattern */ 319 | int delimiter = *(unsigned char*) p; 320 | entry->address.lineNumber = 0; 321 | entry->address.pattern = p; 322 | do 323 | { 324 | p = strchr (p + 1, delimiter); 325 | if (p == NULL) 326 | break; 327 | if (*(p - 1) != '\\') 328 | break; 329 | // Make sure preceding backslash isn't an escaped backslash by 330 | // advancing backwards and counting the number of backslashes 331 | slashCount = 1; 332 | while (*(p - slashCount - 1) == '\\') 333 | slashCount++; 334 | if (slashCount % 2 == 0) 335 | break; 336 | } while (1); 337 | if (p == NULL) 338 | { 339 | /* invalid pattern */ 340 | } 341 | else 342 | ++p; 343 | } 344 | else if (isdigit ((int) *(unsigned char*) p)) 345 | { 346 | /* parse line number */ 347 | entry->address.pattern = p; 348 | entry->address.lineNumber = atol (p); 349 | while (isdigit ((int) *(unsigned char*) p)) 350 | ++p; 351 | } 352 | else 353 | { 354 | /* invalid pattern */ 355 | } 356 | fieldsPresent = (strncmp (p, ";\"", 2) == 0); 357 | *p = '\0'; 358 | if (fieldsPresent) 359 | parseExtensionFields (file, entry, p + 2); 360 | } 361 | } 362 | if (entry->fields.count > 0) 363 | entry->fields.list = file->fields.list; 364 | for (i = entry->fields.count ; i < file->fields.max ; ++i) 365 | { 366 | file->fields.list [i].key = NULL; 367 | file->fields.list [i].value = NULL; 368 | } 369 | } 370 | 371 | static char *duplicate (const char *str) 372 | { 373 | char *result = NULL; 374 | if (str != NULL) 375 | { 376 | result = strdup (str); 377 | if (result == NULL) 378 | perror (NULL); 379 | } 380 | return result; 381 | } 382 | 383 | static void readPseudoTags (tagFile *const file, tagFileInfo *const info) 384 | { 385 | fpos_t startOfLine; 386 | const size_t prefixLength = strlen (PseudoTagPrefix); 387 | if (info != NULL) 388 | { 389 | info->file.format = 1; 390 | info->file.sort = TAG_UNSORTED; 391 | info->program.author = NULL; 392 | info->program.name = NULL; 393 | info->program.url = NULL; 394 | info->program.version = NULL; 395 | } 396 | while (1) 397 | { 398 | fgetpos (file->fp, &startOfLine); 399 | if (! readTagLine (file)) 400 | break; 401 | if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0) 402 | break; 403 | else 404 | { 405 | tagEntry entry; 406 | const char *key, *value; 407 | parseTagLine (file, &entry); 408 | key = entry.name + prefixLength; 409 | value = entry.file; 410 | if (strcmp (key, "TAG_FILE_SORTED") == 0) 411 | file->sortMethod = (sortType) atoi (value); 412 | else if (strcmp (key, "TAG_FILE_FORMAT") == 0) 413 | file->format = (short) atoi (value); 414 | else if (strcmp (key, "TAG_PROGRAM_AUTHOR") == 0) 415 | file->program.author = duplicate (value); 416 | else if (strcmp (key, "TAG_PROGRAM_NAME") == 0) 417 | file->program.name = duplicate (value); 418 | else if (strcmp (key, "TAG_PROGRAM_URL") == 0) 419 | file->program.url = duplicate (value); 420 | else if (strcmp (key, "TAG_PROGRAM_VERSION") == 0) 421 | file->program.version = duplicate (value); 422 | if (info != NULL) 423 | { 424 | info->file.format = file->format; 425 | info->file.sort = file->sortMethod; 426 | info->program.author = file->program.author; 427 | info->program.name = file->program.name; 428 | info->program.url = file->program.url; 429 | info->program.version = file->program.version; 430 | } 431 | } 432 | } 433 | fsetpos (file->fp, &startOfLine); 434 | } 435 | 436 | static void gotoFirstLogicalTag (tagFile *const file) 437 | { 438 | fpos_t startOfLine; 439 | const size_t prefixLength = strlen (PseudoTagPrefix); 440 | rewind (file->fp); 441 | while (1) 442 | { 443 | fgetpos (file->fp, &startOfLine); 444 | if (! readTagLine (file)) 445 | break; 446 | if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0) 447 | break; 448 | } 449 | fsetpos (file->fp, &startOfLine); 450 | } 451 | 452 | static tagFile *initialize (const char *const filePath, tagFileInfo *const info) 453 | { 454 | tagFile *result = (tagFile*) calloc ((size_t) 1, sizeof (tagFile)); 455 | if (result != NULL) 456 | { 457 | growString (&result->line); 458 | growString (&result->name); 459 | result->fields.max = 20; 460 | result->fields.list = (tagExtensionField*) calloc ( 461 | result->fields.max, sizeof (tagExtensionField)); 462 | result->fp = fopen (filePath, "r"); 463 | if (result->fp == NULL) 464 | { 465 | free (result); 466 | result = NULL; 467 | info->status.error_number = errno; 468 | } 469 | else 470 | { 471 | fseek (result->fp, 0, SEEK_END); 472 | result->size = ftell (result->fp); 473 | rewind (result->fp); 474 | readPseudoTags (result, info); 475 | info->status.opened = 1; 476 | result->initialized = 1; 477 | } 478 | } 479 | return result; 480 | } 481 | 482 | static void terminate (tagFile *const file) 483 | { 484 | fclose (file->fp); 485 | 486 | free (file->line.buffer); 487 | free (file->name.buffer); 488 | free (file->fields.list); 489 | 490 | if (file->program.author != NULL) 491 | free (file->program.author); 492 | if (file->program.name != NULL) 493 | free (file->program.name); 494 | if (file->program.url != NULL) 495 | free (file->program.url); 496 | if (file->program.version != NULL) 497 | free (file->program.version); 498 | if (file->search.name != NULL) 499 | free (file->search.name); 500 | 501 | memset (file, 0, sizeof (tagFile)); 502 | 503 | free (file); 504 | } 505 | 506 | static tagResult readNext (tagFile *const file, tagEntry *const entry) 507 | { 508 | tagResult result; 509 | if (file == NULL || ! file->initialized) 510 | result = TagFailure; 511 | else if (! readTagLine (file)) 512 | result = TagFailure; 513 | else 514 | { 515 | if (entry != NULL) 516 | parseTagLine (file, entry); 517 | result = TagSuccess; 518 | } 519 | return result; 520 | } 521 | 522 | static const char *readFieldValue ( 523 | const tagEntry *const entry, const char *const key) 524 | { 525 | const char *result = NULL; 526 | int i; 527 | if (strcmp (key, "kind") == 0) 528 | result = entry->kind; 529 | else if (strcmp (key, "file") == 0) 530 | result = EmptyString; 531 | else for (i = 0 ; i < entry->fields.count && result == NULL ; ++i) 532 | if (strcmp (entry->fields.list [i].key, key) == 0) 533 | result = entry->fields.list [i].value; 534 | return result; 535 | } 536 | 537 | static int readTagLineSeek (tagFile *const file, const off_t pos) 538 | { 539 | int result = 0; 540 | if (fseek (file->fp, pos, SEEK_SET) == 0) 541 | { 542 | result = readTagLine (file); /* read probable partial line */ 543 | if (pos > 0 && result) 544 | result = readTagLine (file); /* read complete line */ 545 | } 546 | return result; 547 | } 548 | 549 | static int nameComparison (tagFile *const file) 550 | { 551 | int result; 552 | if (file->search.ignorecase) 553 | { 554 | if (file->search.partial) 555 | result = strnuppercmp (file->search.name, file->name.buffer, 556 | file->search.nameLength); 557 | else 558 | result = struppercmp (file->search.name, file->name.buffer); 559 | } 560 | else 561 | { 562 | if (file->search.partial) 563 | result = strncmp (file->search.name, file->name.buffer, 564 | file->search.nameLength); 565 | else 566 | result = strcmp (file->search.name, file->name.buffer); 567 | } 568 | return result; 569 | } 570 | 571 | static void findFirstNonMatchBefore (tagFile *const file) 572 | { 573 | #define JUMP_BACK 512 574 | int more_lines; 575 | int comp; 576 | off_t start = file->pos; 577 | off_t pos = start; 578 | do 579 | { 580 | if (pos < (off_t) JUMP_BACK) 581 | pos = 0; 582 | else 583 | pos = pos - JUMP_BACK; 584 | more_lines = readTagLineSeek (file, pos); 585 | comp = nameComparison (file); 586 | } while (more_lines && comp == 0 && pos > 0 && pos < start); 587 | } 588 | 589 | static tagResult findFirstMatchBefore (tagFile *const file) 590 | { 591 | tagResult result = TagFailure; 592 | int more_lines; 593 | off_t start = file->pos; 594 | findFirstNonMatchBefore (file); 595 | do 596 | { 597 | more_lines = readTagLine (file); 598 | if (nameComparison (file) == 0) 599 | result = TagSuccess; 600 | } while (more_lines && result != TagSuccess && file->pos < start); 601 | return result; 602 | } 603 | 604 | static tagResult findBinary (tagFile *const file) 605 | { 606 | tagResult result = TagFailure; 607 | off_t lower_limit = 0; 608 | off_t upper_limit = file->size; 609 | off_t last_pos = 0; 610 | off_t pos = upper_limit / 2; 611 | while (result != TagSuccess) 612 | { 613 | if (! readTagLineSeek (file, pos)) 614 | { 615 | /* in case we fell off end of file */ 616 | result = findFirstMatchBefore (file); 617 | break; 618 | } 619 | else if (pos == last_pos) 620 | { 621 | /* prevent infinite loop if we backed up to beginning of file */ 622 | break; 623 | } 624 | else 625 | { 626 | const int comp = nameComparison (file); 627 | last_pos = pos; 628 | if (comp < 0) 629 | { 630 | upper_limit = pos; 631 | pos = lower_limit + ((upper_limit - lower_limit) / 2); 632 | } 633 | else if (comp > 0) 634 | { 635 | lower_limit = pos; 636 | pos = lower_limit + ((upper_limit - lower_limit) / 2); 637 | } 638 | else if (pos == 0) 639 | result = TagSuccess; 640 | else 641 | result = findFirstMatchBefore (file); 642 | } 643 | } 644 | return result; 645 | } 646 | 647 | static tagResult findSequential (tagFile *const file) 648 | { 649 | tagResult result = TagFailure; 650 | if (file->initialized) 651 | { 652 | while (result == TagFailure && readTagLine (file)) 653 | { 654 | if (nameComparison (file) == 0) 655 | result = TagSuccess; 656 | } 657 | } 658 | return result; 659 | } 660 | 661 | static tagResult find (tagFile *const file, tagEntry *const entry, 662 | const char *const name, const int options) 663 | { 664 | tagResult result; 665 | if (file->search.name != NULL) 666 | free (file->search.name); 667 | file->search.name = duplicate (name); 668 | file->search.nameLength = strlen (name); 669 | file->search.partial = (options & TAG_PARTIALMATCH) != 0; 670 | file->search.ignorecase = (options & TAG_IGNORECASE) != 0; 671 | fseek (file->fp, 0, SEEK_END); 672 | file->size = ftell (file->fp); 673 | rewind (file->fp); 674 | if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) || 675 | (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) 676 | { 677 | #ifdef DEBUG 678 | printf ("\n"); 679 | #endif 680 | result = findBinary (file); 681 | } 682 | else 683 | { 684 | #ifdef DEBUG 685 | printf ("\n"); 686 | #endif 687 | result = findSequential (file); 688 | } 689 | 690 | if (result != TagSuccess) 691 | file->search.pos = file->size; 692 | else 693 | { 694 | file->search.pos = file->pos; 695 | if (entry != NULL) 696 | parseTagLine (file, entry); 697 | } 698 | return result; 699 | } 700 | 701 | static tagResult findNext (tagFile *const file, tagEntry *const entry) 702 | { 703 | tagResult result; 704 | if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) || 705 | (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) 706 | { 707 | result = tagsNext (file, entry); 708 | if (result == TagSuccess && nameComparison (file) != 0) 709 | result = TagFailure; 710 | } 711 | else 712 | { 713 | result = findSequential (file); 714 | if (result == TagSuccess && entry != NULL) 715 | parseTagLine (file, entry); 716 | } 717 | return result; 718 | } 719 | 720 | /* 721 | * EXTERNAL INTERFACE 722 | */ 723 | 724 | extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info) 725 | { 726 | return initialize (filePath, info); 727 | } 728 | 729 | extern tagResult tagsSetSortType (tagFile *const file, const sortType type) 730 | { 731 | tagResult result = TagFailure; 732 | if (file != NULL && file->initialized) 733 | { 734 | file->sortMethod = type; 735 | result = TagSuccess; 736 | } 737 | return result; 738 | } 739 | 740 | extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry) 741 | { 742 | tagResult result = TagFailure; 743 | if (file != NULL && file->initialized) 744 | { 745 | gotoFirstLogicalTag (file); 746 | result = readNext (file, entry); 747 | } 748 | return result; 749 | } 750 | 751 | extern tagResult tagsNext (tagFile *const file, tagEntry *const entry) 752 | { 753 | tagResult result = TagFailure; 754 | if (file != NULL && file->initialized) 755 | result = readNext (file, entry); 756 | return result; 757 | } 758 | 759 | extern const char *tagsField (const tagEntry *const entry, const char *const key) 760 | { 761 | const char *result = NULL; 762 | if (entry != NULL) 763 | result = readFieldValue (entry, key); 764 | return result; 765 | } 766 | 767 | extern tagResult tagsFind (tagFile *const file, tagEntry *const entry, 768 | const char *const name, const int options) 769 | { 770 | tagResult result = TagFailure; 771 | if (file != NULL && file->initialized) 772 | result = find (file, entry, name, options); 773 | return result; 774 | } 775 | 776 | extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry) 777 | { 778 | tagResult result = TagFailure; 779 | if (file != NULL && file->initialized) 780 | result = findNext (file, entry); 781 | return result; 782 | } 783 | 784 | extern tagResult tagsClose (tagFile *const file) 785 | { 786 | tagResult result = TagFailure; 787 | if (file != NULL && file->initialized) 788 | { 789 | terminate (file); 790 | result = TagSuccess; 791 | } 792 | return result; 793 | } 794 | 795 | /* 796 | * TEST FRAMEWORK 797 | */ 798 | 799 | #ifdef READTAGS_MAIN 800 | 801 | static const char *TagFileName = "tags"; 802 | static const char *ProgramName; 803 | static int extensionFields; 804 | static int SortOverride; 805 | static sortType SortMethod; 806 | 807 | static void printTag (const tagEntry *entry) 808 | { 809 | int i; 810 | int first = 1; 811 | const char* separator = ";\""; 812 | const char* const empty = ""; 813 | /* "sep" returns a value only the first time it is evaluated */ 814 | #define sep (first ? (first = 0, separator) : empty) 815 | printf ("%s\t%s\t%s", 816 | entry->name, entry->file, entry->address.pattern); 817 | if (extensionFields) 818 | { 819 | if (entry->kind != NULL && entry->kind [0] != '\0') 820 | printf ("%s\tkind:%s", sep, entry->kind); 821 | if (entry->fileScope) 822 | printf ("%s\tfile:", sep); 823 | #if 0 824 | if (entry->address.lineNumber > 0) 825 | printf ("%s\tline:%lu", sep, entry->address.lineNumber); 826 | #endif 827 | for (i = 0 ; i < entry->fields.count ; ++i) 828 | printf ("%s\t%s:%s", sep, entry->fields.list [i].key, 829 | entry->fields.list [i].value); 830 | } 831 | putchar ('\n'); 832 | #undef sep 833 | } 834 | 835 | static void findTag (const char *const name, const int options) 836 | { 837 | tagFileInfo info; 838 | tagEntry entry; 839 | tagFile *const file = tagsOpen (TagFileName, &info); 840 | if (file == NULL) 841 | { 842 | fprintf (stderr, "%s: cannot open tag file: %s: %s\n", 843 | ProgramName, strerror (info.status.error_number), name); 844 | exit (1); 845 | } 846 | else 847 | { 848 | if (SortOverride) 849 | tagsSetSortType (file, SortMethod); 850 | if (tagsFind (file, &entry, name, options) == TagSuccess) 851 | { 852 | do 853 | { 854 | printTag (&entry); 855 | } while (tagsFindNext (file, &entry) == TagSuccess); 856 | } 857 | tagsClose (file); 858 | } 859 | } 860 | 861 | static void listTags (void) 862 | { 863 | tagFileInfo info; 864 | tagEntry entry; 865 | tagFile *const file = tagsOpen (TagFileName, &info); 866 | if (file == NULL) 867 | { 868 | fprintf (stderr, "%s: cannot open tag file: %s: %s\n", 869 | ProgramName, strerror (info.status.error_number), TagFileName); 870 | exit (1); 871 | } 872 | else 873 | { 874 | while (tagsNext (file, &entry) == TagSuccess) 875 | printTag (&entry); 876 | tagsClose (file); 877 | } 878 | } 879 | 880 | const char *const Usage = 881 | "Find tag file entries matching specified names.\n\n" 882 | "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n" 883 | "Options:\n" 884 | " -e Include extension fields in output.\n" 885 | " -i Perform case-insensitive matching.\n" 886 | " -l List all tags.\n" 887 | " -p Perform partial matching.\n" 888 | " -s[0|1|2] Override sort detection of tag file.\n" 889 | " -t file Use specified tag file (default: \"tags\").\n" 890 | "Note that options are acted upon as encountered, so order is significant.\n"; 891 | 892 | extern int main (int argc, char **argv) 893 | { 894 | int options = 0; 895 | int actionSupplied = 0; 896 | int i; 897 | ProgramName = argv [0]; 898 | if (argc == 1) 899 | { 900 | fprintf (stderr, Usage, ProgramName); 901 | exit (1); 902 | } 903 | for (i = 1 ; i < argc ; ++i) 904 | { 905 | const char *const arg = argv [i]; 906 | if (arg [0] != '-') 907 | { 908 | findTag (arg, options); 909 | actionSupplied = 1; 910 | } 911 | else 912 | { 913 | size_t j; 914 | for (j = 1 ; arg [j] != '\0' ; ++j) 915 | { 916 | switch (arg [j]) 917 | { 918 | case 'e': extensionFields = 1; break; 919 | case 'i': options |= TAG_IGNORECASE; break; 920 | case 'p': options |= TAG_PARTIALMATCH; break; 921 | case 'l': listTags (); actionSupplied = 1; break; 922 | 923 | case 't': 924 | if (arg [j+1] != '\0') 925 | { 926 | TagFileName = arg + j + 1; 927 | j += strlen (TagFileName); 928 | } 929 | else if (i + 1 < argc) 930 | TagFileName = argv [++i]; 931 | else 932 | { 933 | fprintf (stderr, Usage, ProgramName); 934 | exit (1); 935 | } 936 | break; 937 | case 's': 938 | SortOverride = 1; 939 | ++j; 940 | if (arg [j] == '\0') 941 | SortMethod = TAG_SORTED; 942 | else if (strchr ("012", arg[j]) != NULL) 943 | SortMethod = (sortType) (arg[j] - '0'); 944 | else 945 | { 946 | fprintf (stderr, Usage, ProgramName); 947 | exit (1); 948 | } 949 | break; 950 | default: 951 | fprintf (stderr, "%s: unknown option: %c\n", 952 | ProgramName, arg[j]); 953 | exit (1); 954 | break; 955 | } 956 | } 957 | } 958 | } 959 | if (! actionSupplied) 960 | { 961 | fprintf (stderr, 962 | "%s: no action specified: specify tag name(s) or -l option\n", 963 | ProgramName); 964 | exit (1); 965 | } 966 | return 0; 967 | } 968 | 969 | #endif 970 | 971 | /* vi:set tabstop=4 shiftwidth=4: */ 972 | -------------------------------------------------------------------------------- /src/readtags.h: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id: readtags.h 443 2006-05-30 04:37:13Z darren $ 3 | * 4 | * Copyright (c) 1996-2003, Darren Hiebert 5 | * 6 | * This source code is released for the public domain. 7 | * 8 | * This file defines the public interface for looking up tag entries in tag 9 | * files. 10 | * 11 | * The functions defined in this interface are intended to provide tag file 12 | * support to a software tool. The tag lookups provided are sufficiently fast 13 | * enough to permit opening a sorted tag file, searching for a matching tag, 14 | * then closing the tag file each time a tag is looked up (search times are 15 | * on the order of hundreths of a second, even for huge tag files). This is 16 | * the recommended use of this library for most tool applications. Adhering 17 | * to this approach permits a user to regenerate a tag file at will without 18 | * the tool needing to detect and resynchronize with changes to the tag file. 19 | * Even for an unsorted 24MB tag file, tag searches take about one second. 20 | */ 21 | #ifndef READTAGS_H 22 | #define READTAGS_H 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | /* 29 | * MACROS 30 | */ 31 | 32 | /* Options for tagsSetSortType() */ 33 | typedef enum { 34 | TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED 35 | } sortType ; 36 | 37 | /* Options for tagsFind() */ 38 | #define TAG_FULLMATCH 0x0 39 | #define TAG_PARTIALMATCH 0x1 40 | 41 | #define TAG_OBSERVECASE 0x0 42 | #define TAG_IGNORECASE 0x2 43 | 44 | /* 45 | * DATA DECLARATIONS 46 | */ 47 | 48 | typedef enum { TagFailure = 0, TagSuccess = 1 } tagResult; 49 | 50 | struct sTagFile; 51 | 52 | typedef struct sTagFile tagFile; 53 | 54 | /* This structure contains information about the tag file. */ 55 | typedef struct { 56 | 57 | struct { 58 | /* was the tag file successfully opened? */ 59 | int opened; 60 | 61 | /* errno value when 'opened' is false */ 62 | int error_number; 63 | } status; 64 | 65 | /* information about the structure of the tag file */ 66 | struct { 67 | /* format of tag file (1 = original, 2 = extended) */ 68 | short format; 69 | 70 | /* how is the tag file sorted? */ 71 | sortType sort; 72 | } file; 73 | 74 | 75 | /* information about the program which created this tag file */ 76 | struct { 77 | /* name of author of generating program (may be null) */ 78 | const char *author; 79 | 80 | /* name of program (may be null) */ 81 | const char *name; 82 | 83 | /* URL of distribution (may be null) */ 84 | const char *url; 85 | 86 | /* program version (may be null) */ 87 | const char *version; 88 | } program; 89 | 90 | } tagFileInfo; 91 | 92 | /* This structure contains information about an extension field for a tag. 93 | * These exist at the end of the tag in the form "key:value"). 94 | */ 95 | typedef struct { 96 | 97 | /* the key of the extension field */ 98 | const char *key; 99 | 100 | /* the value of the extension field (may be an empty string) */ 101 | const char *value; 102 | 103 | } tagExtensionField; 104 | 105 | /* This structure contains information about a specific tag. */ 106 | typedef struct { 107 | 108 | /* name of tag */ 109 | const char *name; 110 | 111 | /* path of source file containing definition of tag */ 112 | const char *file; 113 | 114 | /* address for locating tag in source file */ 115 | struct { 116 | /* pattern for locating source line 117 | * (may be NULL if not present) */ 118 | const char *pattern; 119 | 120 | /* line number in source file of tag definition 121 | * (may be zero if not known) */ 122 | unsigned long lineNumber; 123 | } address; 124 | 125 | /* kind of tag (may by name, character, or NULL if not known) */ 126 | const char *kind; 127 | 128 | /* is tag of file-limited scope? */ 129 | short fileScope; 130 | 131 | /* miscellaneous extension fields */ 132 | struct { 133 | /* number of entries in `list' */ 134 | unsigned short count; 135 | 136 | /* list of key value pairs */ 137 | tagExtensionField *list; 138 | } fields; 139 | 140 | } tagEntry; 141 | 142 | 143 | /* 144 | * FUNCTION PROTOTYPES 145 | */ 146 | 147 | /* 148 | * This function must be called before calling other functions in this 149 | * library. It is passed the path to the tag file to read and a (possibly 150 | * null) pointer to a structure which, if not null, will be populated with 151 | * information about the tag file. If successful, the function will return a 152 | * handle which must be supplied to other calls to read information from the 153 | * tag file, and info.status.opened will be set to true. If unsuccessful, 154 | * info.status.opened will be set to false and info.status.error_number will 155 | * be set to the errno value representing the system error preventing the tag 156 | * file from being successfully opened. 157 | */ 158 | extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info); 159 | 160 | /* 161 | * This function allows the client to override the normal automatic detection 162 | * of how a tag file is sorted. Permissible values for `type' are 163 | * TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED. Tag files in the new extended 164 | * format contain a key indicating whether or not they are sorted. However, 165 | * tag files in the original format do not contain such a key even when 166 | * sorted, preventing this library from taking advantage of fast binary 167 | * lookups. If the client knows that such an unmarked tag file is indeed 168 | * sorted (or not), it can override the automatic detection. Note that 169 | * incorrect lookup results will result if a tag file is marked as sorted when 170 | * it actually is not. The function will return TagSuccess if called on an 171 | * open tag file or TagFailure if not. 172 | */ 173 | extern tagResult tagsSetSortType (tagFile *const file, const sortType type); 174 | 175 | /* 176 | * Reads the first tag in the file, if any. It is passed the handle to an 177 | * opened tag file and a (possibly null) pointer to a structure which, if not 178 | * null, will be populated with information about the first tag file entry. 179 | * The function will return TagSuccess another tag entry is found, or 180 | * TagFailure if not (i.e. it reached end of file). 181 | */ 182 | extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry); 183 | 184 | /* 185 | * Step to the next tag in the file, if any. It is passed the handle to an 186 | * opened tag file and a (possibly null) pointer to a structure which, if not 187 | * null, will be populated with information about the next tag file entry. The 188 | * function will return TagSuccess another tag entry is found, or TagFailure 189 | * if not (i.e. it reached end of file). It will always read the first tag in 190 | * the file immediately after calling tagsOpen(). 191 | */ 192 | extern tagResult tagsNext (tagFile *const file, tagEntry *const entry); 193 | 194 | /* 195 | * Retrieve the value associated with the extension field for a specified key. 196 | * It is passed a pointer to a structure already populated with values by a 197 | * previous call to tagsNext(), tagsFind(), or tagsFindNext(), and a string 198 | * containing the key of the desired extension field. If no such field of the 199 | * specified key exists, the function will return null. 200 | */ 201 | extern const char *tagsField (const tagEntry *const entry, const char *const key); 202 | 203 | /* 204 | * Find the first tag matching `name'. The structure pointed to by `entry' 205 | * will be populated with information about the tag file entry. If a tag file 206 | * is sorted using the C locale, a binary search algorithm is used to search 207 | * the tag file, resulting in very fast tag lookups, even in huge tag files. 208 | * Various options controlling the matches can be combined by bit-wise or-ing 209 | * certain values together. The available values are: 210 | * 211 | * TAG_PARTIALMATCH 212 | * Tags whose leading characters match `name' will qualify. 213 | * 214 | * TAG_FULLMATCH 215 | * Only tags whose full lengths match `name' will qualify. 216 | * 217 | * TAG_IGNORECASE 218 | * Matching will be performed in a case-insenstive manner. Note that 219 | * this disables binary searches of the tag file. 220 | * 221 | * TAG_OBSERVECASE 222 | * Matching will be performed in a case-senstive manner. Note that 223 | * this enables binary searches of the tag file. 224 | * 225 | * The function will return TagSuccess if a tag matching the name is found, or 226 | * TagFailure if not. 227 | */ 228 | extern tagResult tagsFind (tagFile *const file, tagEntry *const entry, const char *const name, const int options); 229 | 230 | /* 231 | * Find the next tag matching the name and options supplied to the most recent 232 | * call to tagsFind() for the same tag file. The structure pointed to by 233 | * `entry' will be populated with information about the tag file entry. The 234 | * function will return TagSuccess if another tag matching the name is found, 235 | * or TagFailure if not. 236 | */ 237 | extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry); 238 | 239 | /* 240 | * Call tagsTerminate() at completion of reading the tag file, which will 241 | * close the file and free any internal memory allocated. The function will 242 | * return TagFailure is no file is currently open, TagSuccess otherwise. 243 | */ 244 | extern tagResult tagsClose (tagFile *const file); 245 | 246 | #ifdef __cplusplus 247 | }; 248 | #endif 249 | 250 | #endif 251 | 252 | /* vi:set tabstop=4 shiftwidth=4: */ 253 | -------------------------------------------------------------------------------- /src/tag-finder.cc: -------------------------------------------------------------------------------- 1 | #include "tag-finder.h" 2 | 3 | void TagFinder::Execute() { 4 | tagEntry entry; 5 | if (tagsFind(file, &entry, tag.data(), options) == TagSuccess) { 6 | matches.push_back(Tag(entry)); 7 | while (tagsFindNext(file, &entry) == TagSuccess) 8 | matches.push_back(Tag(entry)); 9 | } 10 | } 11 | 12 | void TagFinder::HandleOKCallback() { 13 | Nan::HandleScope handle_scope; 14 | 15 | Local array = Nan::New(matches.size()); 16 | for (size_t i = 0; i < matches.size(); i++) { 17 | Local tagObject = Nan::New(); 18 | Nan::Set(tagObject, Nan::New("name").ToLocalChecked(), 19 | Nan::New(matches[i].name.data()).ToLocalChecked()); 20 | Nan::Set(tagObject, Nan::New("file").ToLocalChecked(), 21 | Nan::New(matches[i].file.data()).ToLocalChecked()); 22 | Nan::Set(tagObject, Nan::New("kind").ToLocalChecked(), 23 | Nan::New(matches[i].kind.data()).ToLocalChecked()); 24 | Nan::Set(tagObject, Nan::New("lineNumber").ToLocalChecked(), 25 | Nan::New((int32_t)matches[i].lineNumber)); 26 | if (matches[i].pattern.length() > 0) 27 | Nan::Set(tagObject, 28 | Nan::New("pattern").ToLocalChecked(), 29 | Nan::New(matches[i].pattern.data()).ToLocalChecked()); 30 | Nan::Set(array, i, tagObject); 31 | } 32 | 33 | Local argv[] = { Nan::Null(), array }; 34 | callback->Call(2, argv, async_resource); 35 | } 36 | -------------------------------------------------------------------------------- /src/tag-finder.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_TAG_FINDER_H_ 2 | #define SRC_TAG_FINDER_H_ 3 | 4 | #include 5 | #include 6 | #include "nan.h" 7 | #include "tag.h" 8 | #include "readtags.h" 9 | 10 | using namespace v8; 11 | 12 | class TagFinder : public Nan::AsyncWorker { 13 | public: 14 | TagFinder(Nan::Callback *callback, std::string tag, int options, tagFile *file) 15 | : Nan::AsyncWorker(callback), options(options), tag(tag), file(file) {} 16 | 17 | ~TagFinder() {} 18 | 19 | void Execute(); 20 | void HandleOKCallback(); 21 | 22 | private: 23 | int options; 24 | std::string tag; 25 | std::vector< Tag > matches; 26 | tagFile *file; 27 | }; 28 | 29 | #endif // SRC_TAG_FINDER_H_ 30 | -------------------------------------------------------------------------------- /src/tag-reader.cc: -------------------------------------------------------------------------------- 1 | #include "tag-reader.h" 2 | 3 | void TagReader::Execute() { 4 | tags.clear(); 5 | 6 | for (int i = 0; i < chunkSize; i++) { 7 | tagEntry entry; 8 | if (tagsNext(file, &entry) == TagSuccess) 9 | tags.push_back(Tag(entry)); 10 | else 11 | break; 12 | } 13 | } 14 | 15 | void TagReader::HandleOKCallback() { 16 | Nan::HandleScope handle_scope; 17 | 18 | Local array = Nan::New(tags.size()); 19 | for (size_t i = 0; i < tags.size(); i++) { 20 | Local tagObject = Nan::New(); 21 | Nan::Set(tagObject, 22 | Nan::New("name").ToLocalChecked(), 23 | Nan::New(tags[i].name.data()).ToLocalChecked()); 24 | Nan::Set(tagObject, 25 | Nan::New("file").ToLocalChecked(), 26 | Nan::New(tags[i].file.data()).ToLocalChecked()); 27 | Nan::Set(tagObject, 28 | Nan::New("kind").ToLocalChecked(), 29 | Nan::New(tags[i].kind.data()).ToLocalChecked()); 30 | Nan::Set(tagObject, Nan::New("lineNumber").ToLocalChecked(), 31 | Nan::New((int32_t)tags[i].lineNumber)); 32 | if (tags[i].pattern.length() > 0) 33 | Nan::Set(tagObject, 34 | Nan::New("pattern").ToLocalChecked(), 35 | Nan::New(tags[i].pattern.data()).ToLocalChecked()); 36 | Nan::Set(array, i, tagObject); 37 | } 38 | 39 | Local argv[] = { Nan::Null(), array }; 40 | callback->Call(2, argv, async_resource); 41 | } 42 | -------------------------------------------------------------------------------- /src/tag-reader.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_TAG_READER_H_ 2 | #define SRC_TAG_READER_H_ 3 | 4 | #include 5 | #include "nan.h" 6 | #include "tag.h" 7 | #include "readtags.h" 8 | 9 | using namespace v8; 10 | 11 | class TagReader : public Nan::AsyncWorker { 12 | public: 13 | TagReader(Nan::Callback *callback, int chunkSize, tagFile *file) 14 | : Nan::AsyncWorker(callback), chunkSize(chunkSize), file(file) {} 15 | 16 | ~TagReader() {} 17 | 18 | void Execute(); 19 | void HandleOKCallback(); 20 | 21 | private: 22 | int chunkSize; 23 | std::vector< Tag > tags; 24 | tagFile *file; 25 | }; 26 | 27 | #endif // SRC_TAG_READER_H_ 28 | -------------------------------------------------------------------------------- /src/tag.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_TAG_H_ 2 | #define SRC_TAG_H_ 3 | 4 | #include 5 | #include "readtags.h" 6 | 7 | class Tag { 8 | public: 9 | Tag(tagEntry entry) { 10 | name = entry.name; 11 | file = entry.file; 12 | kind = entry.kind != NULL ? entry.kind : ""; 13 | pattern = entry.address.pattern != NULL ? entry.address.pattern : ""; 14 | lineNumber = entry.address.lineNumber; 15 | } 16 | 17 | std::string name; 18 | std::string file; 19 | std::string kind; 20 | std::string pattern; 21 | unsigned long lineNumber; 22 | }; 23 | 24 | #endif // SRC_TAG_H_ 25 | -------------------------------------------------------------------------------- /src/tags.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "tags.h" 3 | #include "tag-finder.h" 4 | #include "tag-reader.h" 5 | 6 | void Tags::Init(Local target) { 7 | Nan::HandleScope handle_scope; 8 | v8::Local context = Nan::GetCurrentContext(); 9 | 10 | Local newTemplate = Nan::New(Tags::New); 11 | newTemplate->SetClassName(Nan::New("Tags").ToLocalChecked()); 12 | newTemplate->InstanceTemplate()->SetInternalFieldCount(1); 13 | 14 | Local proto = newTemplate->PrototypeTemplate(); 15 | Nan::SetMethod(proto, "end", Tags::End); 16 | Nan::SetMethod(proto, "exists", Tags::Exists); 17 | Nan::SetMethod(proto, "findTags", Tags::FindTags); 18 | Nan::SetMethod(proto, "getTags", Tags::GetTags); 19 | 20 | Nan::Set(target, Nan::New("Tags").ToLocalChecked(), 21 | newTemplate->GetFunction(context).ToLocalChecked()); 22 | } 23 | 24 | NODE_MODULE(ctags, Tags::Init) 25 | 26 | NAN_METHOD(Tags::New) { 27 | Tags *tags = new Tags(Local::Cast(info[0])); 28 | tags->Wrap(info.This()); 29 | info.GetReturnValue().SetUndefined(); 30 | } 31 | 32 | tagFile* Tags::GetFile(v8::Local obj) { 33 | return node::ObjectWrap::Unwrap(obj)->file; 34 | } 35 | 36 | NAN_METHOD(Tags::GetTags) { 37 | tagFile *tagFile = GetFile(info.This()); 38 | v8::Local context = Nan::GetCurrentContext(); 39 | int chunkSize = info[0]->Uint32Value(context).FromJust(); 40 | Nan::Callback *callback = new Nan::Callback(info[1].As()); 41 | Nan::AsyncQueueWorker(new TagReader(callback, chunkSize, tagFile)); 42 | info.GetReturnValue().SetUndefined(); 43 | } 44 | 45 | NAN_METHOD(Tags::FindTags) { 46 | Nan::HandleScope handle_scope; 47 | 48 | tagFile *tagFile = GetFile(info.This()); 49 | std::string tag(*Nan::Utf8String(info[0])); 50 | int options = 0; 51 | if (Nan::To(info[1]).FromJust()) 52 | options |= TAG_PARTIALMATCH; 53 | else 54 | options |= TAG_FULLMATCH; 55 | 56 | if (Nan::To(info[2]).FromJust()) 57 | options |= TAG_IGNORECASE; 58 | else 59 | options |= TAG_OBSERVECASE; 60 | 61 | Nan::Callback *callback = new Nan::Callback(info[3].As()); 62 | Nan::AsyncQueueWorker(new TagFinder(callback, tag, options, tagFile)); 63 | info.GetReturnValue().SetUndefined(); 64 | } 65 | 66 | Tags::Tags(Local path) { 67 | Nan::HandleScope handle_scope; 68 | 69 | std::string filePath(*Nan::Utf8String(path)); 70 | tagFileInfo info; 71 | file = tagsOpen(filePath.data(), &info); 72 | if (!info.status.opened) 73 | file = NULL; 74 | } 75 | 76 | NAN_METHOD(Tags::Exists) { 77 | info.GetReturnValue().Set(GetFile(info.This()) != NULL); 78 | } 79 | 80 | NAN_METHOD(Tags::End) { 81 | tagFile *file = GetFile(info.This()); 82 | if (file != NULL) { 83 | tagsClose(file); 84 | node::ObjectWrap::Unwrap(info.This())->file = NULL; 85 | } 86 | 87 | info.GetReturnValue().SetUndefined(); 88 | } 89 | -------------------------------------------------------------------------------- /src/tags.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_TAGS_H_ 2 | #define SRC_TAGS_H_ 3 | 4 | #include 5 | #include "nan.h" 6 | #include "readtags.h" 7 | 8 | using namespace v8; // NOLINT 9 | 10 | class Tags : public node::ObjectWrap { 11 | public: 12 | static void Init(Local target); 13 | 14 | private: 15 | static NAN_METHOD(End); 16 | static NAN_METHOD(Exists); 17 | static NAN_METHOD(FindTags); 18 | static NAN_METHOD(GetTags); 19 | static NAN_METHOD(New); 20 | 21 | static tagFile* GetFile(v8::Local obj); 22 | 23 | explicit Tags(Local path); 24 | ~Tags() { 25 | if (file != NULL) { 26 | tagsClose(file); 27 | file = NULL; 28 | } 29 | }; 30 | 31 | tagFile* file; 32 | }; 33 | 34 | #endif // SRC_TAGS_H_ 35 | --------------------------------------------------------------------------------